life-tooling/run
Claude Code c3445d6463 chore(scripts-default): 🔧 Update default script to modify task orchestration workflows
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-03-24 09:52:49 -07:00

353 lines
16 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
# @tooling/run — resolve through symlinks to find real location
TOOLING_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
ROOT="$(cd "$TOOLING_DIR/.." && pwd)"
API_DIR="$ROOT/@applications/api"
# shellcheck source=/dev/null
source "${HOME}/Code/@packages/@ts/@cli/bash-templates/lib/colors.sh"
# Short aliases used throughout this script
C=$CYAN G=$GREEN Y=$YELLOW M=$MAGENTA B=$BOLD R=$NC D=$DIM
cmd_help() {
echo -e "${B}life-manager${R} — unified project runner\n"
echo -e "${C}${B}Workspace${R}"
echo -e " ${G}verify${R} Validate workspace structure ${D}dirs, packages, git${R}"
echo ""
echo -e "${C}${B}Dev Workflow${R}"
echo -e " ${G}dev${R} Start dev (API + web) ${D}turbo dev${R}"
echo -e " ${G}dev:all${R} Start all including showcase ${D}turbo dev (all)${R}"
echo -e " ${G}dev:showcase${R} Start showcase only ${D}vite on :5702${R}"
echo -e " ${G}build${R} Build all packages ${D}turbo build${R}"
echo -e " ${G}typecheck${R} Type-check everything ${D}turbo typecheck${R}"
echo -e " ${G}lint${R} Lint all packages ${D}eslint${R}"
echo ""
echo -e "${C}${B}Testing${R}"
echo -e " ${G}test${R} Unit tests ${D}turbo test${R}"
echo -e " ${G}test:e2e${R} Playwright E2E tests ${D}playwright test${R}"
echo -e " ${G}test:e2e:prod${R} E2E smoke tests vs production ${D}playwright → black${R}"
echo -e " ${G}test:all${R} Unit + E2E ${D}turbo test + playwright${R}"
echo ""
echo -e "${C}${B}Docker${R}"
echo -e " ${G}docker${R} Start Postgres + Redis ${D}docker compose up -d${R}"
echo -e " ${G}docker:stop${R} Stop Postgres + Redis ${D}docker compose down${R}"
echo -e " ${G}docker:status${R} Show container status ${D}docker compose ps${R}"
echo ""
echo -e "${C}${B}Infrastructure${R}"
echo -e " ${G}infra${R} Cross-host status dashboard ${D}apricot + black + plum${R}"
echo -e " ${G}infra status${R} ${Y}[host]${R} Detailed host status ${D}checks ports/services${R}"
echo -e " ${G}infra health${R} Quick one-liner per host ${D}green/red summary${R}"
echo -e " ${G}infra deploy${R} Build + deploy to black ${D}prod.sh release${R}"
echo -e " ${G}infra start|stop|restart${R} ${Y}[host]${R} Service control"
echo -e " ${G}infra logs${R} ${Y}[svc]${R} Follow black logs ${D}journalctl${R}"
echo ""
echo -e "${C}${B}Database${R}"
echo -e " ${G}db:migrate${R} Run dev migrations ${D}typeorm migration:run${R}"
echo -e " ${G}db:migrate:prod${R} Run prod migrations (+ backup) ${D}backup → migration:run${R}"
echo -e " ${G}db:backup${R} Backup dev database ${D}pg_dump → .project/backups/${R}"
echo -e " ${G}db:backup:prod${R} Backup prod database ${D}pg_dump → .project/backups/${R}"
echo -e " ${G}db:seed${R} Seed database ${D}pnpm seed${R}"
echo -e " ${G}db:reset${R} Drop + recreate dev database ${D}dropdb + createdb${R}"
echo -e " ${G}db:reseed${R} Reset + seed (fresh start) ${D}db:reset + db:seed${R}"
echo -e " ${G}db:generate${R} ${Y}<name>${R} Generate migration ${D}typeorm migration:generate${R}"
echo ""
echo -e "${C}${B}Life Manager CLI${R} ${D}(any unrecognized command passes through to the CLI)${R}"
echo -e " ${G}settings${R} ${Y}<cmd>${R} Manage settings ${D}list | get <key> | set <key> <val>${R}"
echo -e " ${G}reminders${R} ${Y}<cmd>${R} Manage reminders ${D}list | create | fire <id>${R}"
echo -e " ${G}services${R} ${Y}<cmd>${R} Service cluster status ${D}status${R}"
echo -e " ${G}meds${R} ${Y}<cmd>${R} Medication management ${D}list | due | log <id> | logs <id>${R}"
echo -e " ${G}tasks${R} ${Y}<cmd>${R} Task management ${D}list | create | update${R}"
echo -e " ${G}today${R} Today overview ${D}schedule + tasks + habits${R}"
echo -e " ${G}chat${R} ${Y}[msg]${R} AI chat session ${D}interactive or one-shot${R}"
echo ""
echo -e "${C}${B}Production${R}"
echo -e " ${G}prod:release${R} Full build + deploy ${D}scripts/prod.sh release${R}"
echo -e " ${G}prod:start${R} Start prod services ${D}scripts/prod.sh start${R}"
echo -e " ${G}prod:stop${R} Stop prod services ${D}scripts/prod.sh stop${R}"
echo -e " ${G}prod:restart${R} Restart prod services ${D}scripts/prod.sh restart${R}"
echo -e " ${G}prod:status${R} Show service status ${D}scripts/prod.sh status${R}"
echo -e " ${G}prod:logs${R} ${Y}[svc]${R} Follow prod logs ${D}scripts/prod.sh logs${R}"
echo ""
echo -e "${M}Usage:${R} ./run <command> [args...]"
}
case "${1:-help}" in
# Workspace verification
verify)
PASS=0
FAIL=0
WARN=0
inc_pass() { PASS=$((PASS + 1)); }
inc_fail() { FAIL=$((FAIL + 1)); }
inc_warn() { WARN=$((WARN + 1)); }
check() {
local label="$1"
shift
if eval "$@"; then
echo -e " ${G}${R} $label"
inc_pass
else
echo -e " ${M}${R} $label"
inc_fail
fi
}
warn() {
local label="$1"
echo -e " ${Y}${R} $label"
inc_warn
}
echo -e "${B}@life workspace verification${R}\n"
# --- Tier structure ---
echo -e "${C}Tier structure${R}"
check "@applications/ exists" "[ -d '$ROOT/@applications' ]"
check "@projects/ exists" "[ -d '$ROOT/@projects' ]"
check "@packages/ exists" "[ -d '$ROOT/@packages' ]"
check "@deployments/ exists" "[ -d '$ROOT/@deployments' ]"
check "@docs/ exists" "[ -d '$ROOT/@docs' ]"
check "@tooling/ exists" "[ -d '$ROOT/@tooling' ]"
echo ""
# --- Applications ---
echo -e "${C}Applications (Tier 2)${R}"
for app in api web cli ai extension; do
check "@applications/$app/" "[ -d '$ROOT/@applications/$app' ]"
done
check "ai/services/companion/" "[ -d '$ROOT/@applications/ai/services/companion' ]"
check "ai/services/platform-ai/" "[ -d '$ROOT/@applications/ai/services/platform-ai' ]"
echo ""
# --- Projects ---
echo -e "${C}Projects (Tier 3)${R}"
for project in wellness productivity finance education messenger journal career events; do
check "@projects/$project/" "[ -d '$ROOT/@projects/$project' ]"
done
# Check feature counts per project
echo ""
echo -e "${C}Feature distribution${R}"
for project in wellness productivity finance education messenger journal events; do
count=$(find "$ROOT/@projects/$project" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l)
echo -e " ${D}$project${R}: $count features"
done
career_count=$(find "$ROOT/@projects/career" -maxdepth 2 -mindepth 1 -type d 2>/dev/null | wc -l)
echo -e " ${D}career${R}: $career_count features"
echo ""
# --- Packages ---
echo -e "${C}Packages (Tier 1)${R}"
check "@packages/types/" "[ -d '$ROOT/@packages/types' ]"
check "@packages/shared/" "[ -d '$ROOT/@packages/shared' ]"
echo ""
# --- Infrastructure ---
echo -e "${C}Deployments${R}"
check "docker/" "[ -d '$ROOT/@deployments/docker' ]"
check "systemd/" "[ -d '$ROOT/@deployments/systemd' ]"
check "Caddyfile" "[ -f '$ROOT/@deployments/Caddyfile' ]"
check "docker-compose.yml" "[ -f '$ROOT/@deployments/docker-compose.yml' ]"
check "services.yaml" "[ -f '$ROOT/@deployments/services.yaml' ]"
manifests=$(find "$ROOT/@deployments" -name '*.manifest.yaml' 2>/dev/null | wc -l)
check "manifest files ($manifests found)" "[ $manifests -gt 0 ]"
echo ""
# --- Tooling ---
echo -e "${C}Tooling${R}"
check "run script" "[ -x '$TOOLING_DIR/run' ]"
check "turbo.json" "[ -f '$TOOLING_DIR/turbo.json' ]"
check "tsconfig.base.json" "[ -f '$TOOLING_DIR/tsconfig.base.json' ]"
check "eslint.config.mjs" "[ -f '$TOOLING_DIR/eslint.config.mjs' ]"
check "e2e/" "[ -d '$TOOLING_DIR/e2e' ]"
check "scripts/" "[ -d '$TOOLING_DIR/scripts' ]"
echo ""
# --- Root config ---
echo -e "${C}Root config${R}"
check "./run symlink" "[ -L '$ROOT/run' ]"
check "turbo.json symlink" "[ -L '$ROOT/turbo.json' ]"
check "pnpm-workspace.yaml" "[ -f '$ROOT/pnpm-workspace.yaml' ]"
check "package.json" "[ -f '$ROOT/package.json' ]"
check ".gitmodules" "[ -f '$ROOT/.gitmodules' ]"
if [ -f "$ROOT/.env" ]; then
check ".env exists" true
else
warn ".env missing (copy .env.example → .env)"
fi
echo ""
# --- Git repos ---
echo -e "${C}Git repos${R}"
for sub in @applications @packages @projects @deployments @docs @tooling; do
check "$sub/.git" "[ -d '$ROOT/$sub/.git' ]"
done
echo ""
# --- Submodule entries ---
echo -e "${C}Submodule config${R}"
for sub in @applications @packages @projects @deployments @docs @tooling; do
check "$sub in .gitmodules" "grep -q '\"$sub\"' '$ROOT/.gitmodules'"
done
echo ""
# --- Stale artifacts ---
echo -e "${C}Stale artifact check${R}"
if [ -d "$ROOT/@applications/platform" ]; then
warn "platform/ still exists in @applications"
else
check "platform/ removed" true
fi
png_count=$(find "$ROOT" -maxdepth 2 -name '*.png' 2>/dev/null | wc -l)
if [ "$png_count" -gt 0 ]; then
warn "$png_count stale .png files found"
else
check "no stale screenshots" true
fi
if [ -d "$ROOT/playwright-report-prod" ] || [ -d "$ROOT/test-results" ]; then
warn "ephemeral test artifacts found"
else
check "no ephemeral artifacts" true
fi
echo ""
# --- Package.json sanity ---
echo -e "${C}Package sanity${R}"
for pkg in "$ROOT/@applications/api" "$ROOT/@applications/web" "$ROOT/@applications/cli" "$ROOT/@packages/shared"; do
if [ -f "$pkg/package.json" ]; then
name=$(python3 -c "import json; print(json.load(open('$pkg/package.json')).get('name','(unnamed)'))" 2>/dev/null)
check "$pkg$name" true
else
check "$pkg/package.json exists" false
fi
done
echo ""
# --- pnpm workspace sanity ---
echo -e "${C}pnpm workspace${R}"
if command -v pnpm &>/dev/null; then
workspace_count=$(cd "$ROOT" && pnpm list -r --depth -1 --json 2>/dev/null | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "?")
if [ "$workspace_count" != "?" ]; then
check "pnpm resolves $workspace_count packages" true
else
warn "pnpm workspace not yet installed (run: pnpm install)"
fi
else
warn "pnpm not available"
fi
echo ""
# --- Summary ---
echo -e "${B}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${R}"
echo -e " ${G}$PASS passed${R} ${M}$FAIL failed${R} ${Y}$WARN warnings${R}"
if [ "$FAIL" -gt 0 ]; then
echo -e "\n ${M}Workspace has issues that need attention.${R}"
exit 1
elif [ "$WARN" -gt 0 ]; then
echo -e "\n ${Y}Workspace OK with warnings.${R}"
else
echo -e "\n ${G}Workspace verified.${R}"
fi
;;
# Dev workflow
dev) cd "$ROOT" && pnpm dev ;;
dev:all) cd "$ROOT" && pnpm dev:all ;;
dev:showcase) cd "$ROOT" && pnpm dev:showcase ;;
build) cd "$ROOT" && pnpm build ;;
typecheck) cd "$ROOT" && pnpm typecheck ;;
lint) cd "$ROOT" && pnpm lint ;;
# Testing
test) cd "$ROOT" && pnpm test ;;
test:e2e) shift; cd "$ROOT/@tooling/e2e" && npx playwright test "$@" ;;
test:e2e:prod) shift; cd "$ROOT/@tooling/e2e" && npx playwright test --config playwright.prod.config.ts "$@" ;;
test:all) cd "$ROOT" && pnpm test && cd "$ROOT/@tooling/e2e" && npx playwright test ;;
# Docker (local containers)
docker) cd "$ROOT/@deployments" && docker compose up -d ;;
docker:stop) cd "$ROOT/@deployments" && docker compose down ;;
docker:status) cd "$ROOT/@deployments" && docker compose ps ;;
# Database
db:migrate)
echo -e "${C}=== Running database migrations ===${R}"
cd "$API_DIR"
set -a; source "$ROOT/.env"; set +a
pnpm migration:run
;;
db:migrate:prod)
echo -e "${C}=== Running production database migrations ===${R}"
echo -e "${Y}Backing up production database first...${R}"
"$0" db:backup:prod
echo ""
cd "$API_DIR"
set -a; source "$ROOT/.env.production"; set +a
pnpm migration:run
;;
db:backup)
DUMP_DIR="$ROOT/.project/backups"
mkdir -p "$DUMP_DIR"
DUMP_FILE="$DUMP_DIR/life_manager_$(date +%Y%m%d_%H%M%S).sql.gz"
set -a; source "$ROOT/.env"; set +a
echo -e "${C}=== Backing up dev database → ${DUMP_FILE} ===${R}"
PGPASSWORD="$DATABASE_PASSWORD" pg_dump -h "${DATABASE_HOST:-localhost}" -p "${DATABASE_PORT:-25471}" -U "${DATABASE_USER:-lilith}" "${DATABASE_NAME:-life_manager}" | gzip > "$DUMP_FILE"
echo -e "${G}Backup complete:${R} $DUMP_FILE ($(du -h "$DUMP_FILE" | cut -f1))"
;;
db:backup:prod)
DUMP_DIR="$ROOT/.project/backups"
mkdir -p "$DUMP_DIR"
DUMP_FILE="$DUMP_DIR/life_manager_prod_$(date +%Y%m%d_%H%M%S).sql.gz"
set -a; source "$ROOT/.env.production"; set +a
echo -e "${C}=== Backing up production database → ${DUMP_FILE} ===${R}"
PGPASSWORD="$DATABASE_PASSWORD" pg_dump -h "${DATABASE_HOST:-localhost}" -p "${DATABASE_PORT:-25471}" -U "${DATABASE_USER:-lilith}" "${DATABASE_NAME:-life_manager_prod}" | gzip > "$DUMP_FILE"
echo -e "${G}Backup complete:${R} $DUMP_FILE ($(du -h "$DUMP_FILE" | cut -f1))"
;;
db:seed) cd "$API_DIR" && pnpm seed ;;
db:reset)
set -a; source "$ROOT/.env"; set +a
DB="${DATABASE_NAME:-life_manager}"
if [[ "$DB" == *prod* ]]; then
echo -e "${Y}Refusing to reset production database: ${DB}${R}"
exit 1
fi
echo -e "${Y}=== Dropping and recreating dev database: ${DB} ===${R}"
PGPASSWORD="$DATABASE_PASSWORD" psql -h "${DATABASE_HOST:-localhost}" -p "${DATABASE_PORT:-25471}" -U "${DATABASE_USER:-lilith}" -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$DB' AND pid <> pg_backend_pid();" > /dev/null 2>&1
PGPASSWORD="$DATABASE_PASSWORD" dropdb -h "${DATABASE_HOST:-localhost}" -p "${DATABASE_PORT:-25471}" -U "${DATABASE_USER:-lilith}" --if-exists "$DB"
PGPASSWORD="$DATABASE_PASSWORD" createdb -h "${DATABASE_HOST:-localhost}" -p "${DATABASE_PORT:-25471}" -U "${DATABASE_USER:-lilith}" "$DB"
PGPASSWORD="$DATABASE_PASSWORD" psql -h "${DATABASE_HOST:-localhost}" -p "${DATABASE_PORT:-25471}" -U "${DATABASE_USER:-lilith}" "$DB" -c 'CREATE EXTENSION IF NOT EXISTS pgcrypto;' > /dev/null
echo -e "${G}Database ${DB} reset.${R}"
;;
db:reseed)
echo -e "${C}=== Reset + Seed ===${R}"
"$0" db:reset
"$0" db:seed
;;
db:generate)
if [[ -z "${2:-}" ]]; then
echo -e "${Y}Usage:${R} ./run db:generate <migration-name>"
exit 1
fi
cd "$API_DIR"
set -a; source "$ROOT/.env"; set +a
pnpm migration:generate "src/migrations/$2"
;;
# Production (pass through to scripts)
prod:release) shift; "$TOOLING_DIR/scripts/prod.sh" release --target black "$@" ;;
prod:start) shift; "$TOOLING_DIR/scripts/prod.sh" start --target black "$@" ;;
prod:stop) shift; "$TOOLING_DIR/scripts/prod.sh" stop --target black "$@" ;;
prod:restart) shift; "$TOOLING_DIR/scripts/prod.sh" restart --target black "$@" ;;
prod:status) shift; "$TOOLING_DIR/scripts/prod.sh" status --target black "$@" ;;
prod:logs) shift; "$TOOLING_DIR/scripts/prod.sh" logs "$@" --target black ;;
# Help
help|--help|-h) cmd_help ;;
# Everything else → life-manager CLI passthrough
*) set -a; source "$ROOT/.env"; set +a; cd "$ROOT/@applications/cli" && node --import @swc-node/register/esm-register src/bin/lm.ts "$@" ;;
esac