@better-openclaw/core 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/bare-metal-partition.test.mjs +1 -1
  2. package/dist/composer.snapshot.test.mjs +1 -1
  3. package/dist/composer.test.mjs +1 -1
  4. package/dist/generate.d.mts.map +1 -1
  5. package/dist/generate.mjs +7 -1
  6. package/dist/generate.mjs.map +1 -1
  7. package/dist/generate.test.mjs +1 -1
  8. package/dist/generators/bare-metal-install.test.mjs +1 -1
  9. package/dist/generators/caddy.test.d.mts +1 -0
  10. package/dist/generators/caddy.test.mjs +45 -0
  11. package/dist/generators/caddy.test.mjs.map +1 -0
  12. package/dist/generators/env.test.d.mts +1 -0
  13. package/dist/generators/env.test.mjs +60 -0
  14. package/dist/generators/env.test.mjs.map +1 -0
  15. package/dist/generators/health-check.d.mts +18 -0
  16. package/dist/generators/health-check.d.mts.map +1 -0
  17. package/dist/generators/health-check.mjs +705 -0
  18. package/dist/generators/health-check.mjs.map +1 -0
  19. package/dist/generators/health-check.test.d.mts +1 -0
  20. package/dist/generators/health-check.test.mjs +85 -0
  21. package/dist/generators/health-check.test.mjs.map +1 -0
  22. package/dist/generators/scripts.test.d.mts +1 -0
  23. package/dist/generators/scripts.test.mjs +52 -0
  24. package/dist/generators/scripts.test.mjs.map +1 -0
  25. package/dist/generators/traefik.test.mjs +1 -1
  26. package/dist/generators/traefik.test.mjs.map +1 -1
  27. package/dist/index.d.mts +4 -2
  28. package/dist/index.mjs +3 -1
  29. package/dist/migrations.d.mts.map +1 -1
  30. package/dist/migrations.mjs.map +1 -1
  31. package/dist/migrations.test.mjs +1 -1
  32. package/dist/migrations.test.mjs.map +1 -1
  33. package/dist/presets/registry.test.mjs +1 -1
  34. package/dist/resolver.test.mjs +1 -1
  35. package/dist/schema.test.mjs +1 -1
  36. package/dist/services/definitions/convex.mjs.map +1 -1
  37. package/dist/services/definitions/desktop-environment.mjs.map +1 -1
  38. package/dist/services/definitions/index.d.mts +2 -2
  39. package/dist/services/definitions/index.mjs +3 -3
  40. package/dist/services/definitions/index.mjs.map +1 -1
  41. package/dist/services/definitions/mission-control.mjs.map +1 -1
  42. package/dist/services/definitions/stream-gateway.mjs.map +1 -1
  43. package/dist/services/registry.test.mjs +1 -1
  44. package/dist/skills/registry.d.mts.map +1 -1
  45. package/dist/skills/registry.mjs +498 -6
  46. package/dist/skills/registry.mjs.map +1 -1
  47. package/dist/skills/skill-manifest.d.mts +20 -0
  48. package/dist/skills/skill-manifest.d.mts.map +1 -0
  49. package/dist/skills/skill-manifest.mjs +28 -0
  50. package/dist/skills/skill-manifest.mjs.map +1 -0
  51. package/dist/validator.test.mjs +1 -1
  52. package/dist/version-manager.test.mjs +1 -1
  53. package/dist/version-manager.test.mjs.map +1 -1
  54. package/dist/{vi.2VT5v0um-Qk6MgAnK.mjs → vi.2VT5v0um-YSByewHe.mjs} +5 -5
  55. package/dist/{vi.2VT5v0um-Qk6MgAnK.mjs.map → vi.2VT5v0um-YSByewHe.mjs.map} +1 -1
  56. package/package.json +1 -1
  57. package/src/generate.ts +15 -3
  58. package/src/generators/caddy.test.ts +56 -0
  59. package/src/generators/env.test.ts +73 -0
  60. package/src/generators/health-check.test.ts +118 -0
  61. package/src/generators/health-check.ts +774 -0
  62. package/src/generators/scripts.test.ts +60 -0
  63. package/src/generators/traefik.test.ts +9 -17
  64. package/src/index.ts +12 -6
  65. package/src/migrations.test.ts +1 -1
  66. package/src/migrations.ts +1 -4
  67. package/src/services/definitions/convex.ts +2 -4
  68. package/src/services/definitions/desktop-environment.ts +1 -9
  69. package/src/services/definitions/index.ts +6 -6
  70. package/src/services/definitions/mission-control.ts +2 -4
  71. package/src/services/definitions/stream-gateway.ts +1 -2
  72. package/src/skills/registry.ts +336 -6
  73. package/src/skills/skill-manifest.ts +52 -0
  74. package/src/version-manager.test.ts +15 -4
  75. package/tsdown.config.ts +6 -6
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-check.mjs","names":[],"sources":["../../src/generators/health-check.ts"],"sourcesContent":["import type { ResolverOutput } from \"../types.js\";\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\ninterface HealthCheckOptions {\n\tprojectName: string;\n\tdeploymentType?: \"docker\" | \"bare-metal\";\n}\n\n// ─── Generator ──────────────────────────────────────────────────────────────\n\n/**\n * Generates stack-specific health check and verification scripts.\n *\n * Each generated script is dynamic — it contains the exact service names,\n * ports, and health check commands from the resolved stack so users get\n * a precise, zero-configuration verifier.\n */\nexport function generateHealthCheck(\n\tresolved: ResolverOutput,\n\toptions: HealthCheckOptions,\n): Record<string, string> {\n\tconst files: Record<string, string> = {};\n\n\tfiles[\"scripts/health-check.sh\"] = generateBashScript(resolved, options);\n\tfiles[\"scripts/health-check.ps1\"] = generatePowerShellScript(resolved, options);\n\n\treturn files;\n}\n\n// ─── Service metadata extraction ────────────────────────────────────────────\n\ninterface ServiceCheck {\n\tid: string;\n\tname: string;\n\ticon: string;\n\tports: Array<{ host: number; container: number; description: string; exposed: boolean }>;\n\thealthCheckCmd: string | null;\n}\n\nfunction extractServiceChecks(resolved: ResolverOutput): ServiceCheck[] {\n\treturn resolved.services.map((svc) => ({\n\t\tid: svc.definition.id,\n\t\tname: svc.definition.name,\n\t\ticon: svc.definition.icon,\n\t\tports: svc.definition.ports.map((p) => ({\n\t\t\thost: p.host,\n\t\t\tcontainer: p.container,\n\t\t\tdescription: p.description,\n\t\t\texposed: p.exposed,\n\t\t})),\n\t\thealthCheckCmd: svc.definition.healthcheck?.test ?? null,\n\t}));\n}\n\nfunction escapeShell(s: string): string {\n\treturn s.replace(/'/g, \"'\\\\''\").replace(/\"/g, '\\\\\"');\n}\n\n// Use L() to build lines safely — avoids template literal issues with shell syntax\nfunction L(...parts: string[]): string {\n\treturn parts.join(\"\");\n}\n\n// ─── Bash Script ────────────────────────────────────────────────────────────\n\nfunction generateBashScript(resolved: ResolverOutput, options: HealthCheckOptions): string {\n\tconst checks = extractServiceChecks(resolved);\n\tconst name = options.projectName;\n\tconst total = checks.length;\n\n\t// Build per-service docker check calls\n\tconst dockerChecks: string[] = [];\n\tfor (const svc of checks) {\n\t\tdockerChecks.push(L(\" # ── \", svc.icon, \" \", svc.name, \" ──\"));\n\t\tdockerChecks.push(L(' check_container \"', svc.id, '\" \"', svc.name, '\" \"', svc.icon, '\"'));\n\t\tfor (const p of svc.ports.filter((pp) => pp.exposed)) {\n\t\t\tdockerChecks.push(L(' check_port \"', svc.id, '\" ', String(p.host), ' \"', p.description, '\"'));\n\t\t}\n\t\tif (svc.healthCheckCmd) {\n\t\t\tdockerChecks.push(\n\t\t\t\tL(' check_health_cmd \"', svc.id, '\" \"', escapeShell(svc.healthCheckCmd), '\"'),\n\t\t\t);\n\t\t} else {\n\t\t\tdockerChecks.push(\" # No healthcheck defined — skipping exec check\");\n\t\t}\n\t\tdockerChecks.push(\"\");\n\t}\n\n\t// Build per-service bare-metal check calls\n\tconst bmChecks: string[] = [];\n\tfor (const svc of checks) {\n\t\tbmChecks.push(L(\" # ── \", svc.icon, \" \", svc.name, \" (bare-metal) ──\"));\n\t\tbmChecks.push(L(' check_process \"', svc.id, '\" \"', svc.name, '\" \"', svc.icon, '\"'));\n\t\tfor (const p of svc.ports.filter((pp) => pp.exposed)) {\n\t\t\tbmChecks.push(L(' check_port \"', svc.id, '\" ', String(p.host), ' \"', p.description, '\"'));\n\t\t}\n\t\tbmChecks.push(\"\");\n\t}\n\n\t// Build the log-scan service list\n\tconst svcIdList = checks.map((s) => '\"' + s.id + '\"').join(\" \");\n\n\tconst lines: string[] = [\n\t\t\"#!/usr/bin/env bash\",\n\t\t\"set -uo pipefail\",\n\t\t\"\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\tL(\"# 🐾 OpenClaw Stack Health Check — \", name),\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"#\",\n\t\t\"# Auto-generated verification script for your stack.\",\n\t\tL(\"# Checks \", String(total), \" services across 6 phases:\"),\n\t\t\"# 1. Environment — prerequisites, .env, secrets\",\n\t\t\"# 2. Container — running state, health status, restart count\",\n\t\t\"# 3. Port — TCP reachability for exposed ports\",\n\t\t\"# 4. Health Command — execute each service's health check\",\n\t\t\"# 5. Resources — memory/CPU warnings\",\n\t\t\"# 6. Logs — scan for ERROR/FATAL/panic patterns\",\n\t\t\"#\",\n\t\t\"# Usage:\",\n\t\t\"# ./scripts/health-check.sh # Full check\",\n\t\t\"# ./scripts/health-check.sh --quick # Skip log scan\",\n\t\t\"# ./scripts/health-check.sh --json # Output as JSON\",\n\t\t\"#\",\n\t\t\"# Exit codes: 0 = all healthy, 1 = some issues, 2 = critical failure\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"\",\n\t\t'SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"',\n\t\t'PROJECT_DIR=\"$(dirname \"$SCRIPT_DIR\")\"',\n\t\t'cd \"$PROJECT_DIR\"',\n\t\t\"\",\n\t\t\"# ── Arguments ────────────────────────────────────────────────────────\",\n\t\t\"QUICK_MODE=false\",\n\t\t\"JSON_MODE=false\",\n\t\t\"VERBOSE=false\",\n\t\t'for arg in \"$@\"; do',\n\t\t' case \"$arg\" in',\n\t\t\" --quick) QUICK_MODE=true ;;\",\n\t\t\" --json) JSON_MODE=true ;;\",\n\t\t\" --verbose|-v) VERBOSE=true ;;\",\n\t\t\" esac\",\n\t\t\"done\",\n\t\t\"\",\n\t\t\"# ── Colour helpers ───────────────────────────────────────────────────\",\n\t\t'if [ -t 1 ] && [ \"$JSON_MODE\" = false ]; then',\n\t\t\" RED='\\\\033[0;31m'; GREEN='\\\\033[0;32m'; YELLOW='\\\\033[1;33m'\",\n\t\t\" CYAN='\\\\033[0;36m'; DIM='\\\\033[2m'; BOLD='\\\\033[1m'; NC='\\\\033[0m'\",\n\t\t\"else\",\n\t\t\" RED=''; GREEN=''; YELLOW=''; CYAN=''; DIM=''; BOLD=''; NC=''\",\n\t\t\"fi\",\n\t\t\"\",\n\t\t\"# ── Counters ─────────────────────────────────────────────────────────\",\n\t\tL(\"TOTAL=\", String(total)),\n\t\t\"HEALTHY=0\",\n\t\t\"WARNING=0\",\n\t\t\"FAILED=0\",\n\t\t\"ERRORS=()\",\n\t\t\"WARNINGS_LIST=()\",\n\t\t\"SERVICE_RESULTS=()\",\n\t\t\"\",\n\t\t\"# ── Utility functions ────────────────────────────────────────────────\",\n\t\t'pass() { echo -e \" ${GREEN}✅ $*${NC}\"; HEALTHY=$((HEALTHY + 1)); }',\n\t\t'warn_svc() { echo -e \" ${YELLOW}⚠️ $*${NC}\"; WARNING=$((WARNING + 1)); WARNINGS_LIST+=(\"$*\"); }',\n\t\t'fail() { echo -e \" ${RED}❌ $*${NC}\"; FAILED=$((FAILED + 1)); ERRORS+=(\"$*\"); }',\n\t\t'info() { echo -e \" ${CYAN}ℹ $*${NC}\"; }',\n\t\t'dim() { echo -e \" ${DIM}$*${NC}\"; }',\n\t\t\"\",\n\t\t\"pad_name() {\",\n\t\t' local name=\"$1\"',\n\t\t\" local pad=25\",\n\t\t' printf \"%-${pad}s\" \"$name\"',\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"# Phase 1: Environment\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"phase_environment() {\",\n\t\t' if [ \"$JSON_MODE\" = false ]; then',\n\t\t' echo \"\"',\n\t\t' echo -e \"${BOLD}── Phase 1: Environment ────────────────────────────────────${NC}\"',\n\t\t' echo \"\"',\n\t\t\" fi\",\n\t\t\"\",\n\t\t\" # Docker\",\n\t\t\" if command -v docker &>/dev/null; then\",\n\t\t\" if docker info &>/dev/null 2>&1; then\",\n\t\t' pass \"Docker daemon is running\"',\n\t\t\" DOCKER_AVAILABLE=true\",\n\t\t\" else\",\n\t\t' fail \"Docker daemon is NOT running\"',\n\t\t\" DOCKER_AVAILABLE=false\",\n\t\t\" fi\",\n\t\t\" else\",\n\t\t' warn_svc \"Docker is not installed — running bare-metal checks only\"',\n\t\t\" DOCKER_AVAILABLE=false\",\n\t\t\" fi\",\n\t\t\"\",\n\t\t\" # Docker Compose\",\n\t\t' if [ \"$DOCKER_AVAILABLE\" = true ]; then',\n\t\t\" if docker compose version &>/dev/null 2>&1; then\",\n\t\t' pass \"Docker Compose v2 available\"',\n\t\t\" else\",\n\t\t' warn_svc \"Docker Compose v2 not found\"',\n\t\t\" fi\",\n\t\t\" fi\",\n\t\t\"\",\n\t\t\" # .env\",\n\t\t' if [ -f \".env\" ]; then',\n\t\t' pass \".env file exists\"',\n\t\t\" EMPTY_SECRETS=0\",\n\t\t\" while IFS='=' read -r key value; do\",\n\t\t' [[ \"$key\" =~ ^#.*$ ]] && continue',\n\t\t' [[ -z \"$key\" ]] && continue',\n\t\t' if [[ \"$key\" =~ (PASSWORD|TOKEN|SECRET|SESSION_KEY|COOKIE|API_KEY) ]] && [[ -z \"${value:-}\" ]]; then',\n\t\t' if [ \"$VERBOSE\" = true ]; then',\n\t\t' warn_svc \"Empty secret: $key\"',\n\t\t\" fi\",\n\t\t\" EMPTY_SECRETS=$((EMPTY_SECRETS + 1))\",\n\t\t\" fi\",\n\t\t\" done < .env 2>/dev/null || true\",\n\t\t' if [ \"$EMPTY_SECRETS\" -gt 0 ]; then',\n\t\t' warn_svc \"$EMPTY_SECRETS secret(s) are empty in .env\"',\n\t\t\" else\",\n\t\t' pass \"All secrets are populated\"',\n\t\t\" fi\",\n\t\t\" else\",\n\t\t' warn_svc \".env file not found\"',\n\t\t\" fi\",\n\t\t\"\",\n\t\t\" # Disk space\",\n\t\t\" AVAIL_KB=$(df -k . 2>/dev/null | awk 'NR==2{print $4}' || echo \\\"0\\\")\",\n\t\t\" AVAIL_GB=$((AVAIL_KB / 1024 / 1024))\",\n\t\t' if [ \"$AVAIL_GB\" -lt 2 ]; then',\n\t\t' warn_svc \"Low disk space: ${AVAIL_GB}GB available\"',\n\t\t\" else\",\n\t\t' pass \"Disk space: ${AVAIL_GB}GB available\"',\n\t\t\" fi\",\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"# Phase 2: Container Status\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"check_container() {\",\n\t\t' local id=\"$1\" name=\"$2\" icon=\"$3\"',\n\t\t\"\",\n\t\t\" local status restarts\",\n\t\t' status=$(docker compose ps \"$id\" --format json 2>/dev/null | head -1)',\n\t\t\"\",\n\t\t' if [ -z \"$status\" ]; then',\n\t\t' fail \"$icon $(pad_name \"$name\") — Container not found\"',\n\t\t\" return\",\n\t\t\" fi\",\n\t\t\"\",\n\t\t\" local state health_status\",\n\t\t\" state=$(echo \\\"$status\\\" | grep -o '\\\"State\\\":\\\"[^\\\"]*\\\"' | cut -d'\\\"' -f4 || echo \\\"unknown\\\")\",\n\t\t\" health_status=$(echo \\\"$status\\\" | grep -o '\\\"Health\\\":\\\"[^\\\"]*\\\"' | cut -d'\\\"' -f4 || echo \\\"none\\\")\",\n\t\t\"\",\n\t\t' restarts=$(docker inspect --format=\"{{.RestartCount}}\" \"$(docker compose ps -q \"$id\" 2>/dev/null | head -1)\" 2>/dev/null || echo \"0\")',\n\t\t\"\",\n\t\t' if [ \"$state\" = \"running\" ]; then',\n\t\t' if [ \"$health_status\" = \"healthy\" ]; then',\n\t\t' pass \"$icon $(pad_name \"$name\") Running (healthy) restarts: $restarts\"',\n\t\t' elif [ \"$health_status\" = \"starting\" ]; then',\n\t\t' warn_svc \"$icon $(pad_name \"$name\") Running (starting) restarts: $restarts\"',\n\t\t' elif [ \"$health_status\" = \"unhealthy\" ]; then',\n\t\t' fail \"$icon $(pad_name \"$name\") Running (UNHEALTHY) restarts: $restarts\"',\n\t\t\" else\",\n\t\t' pass \"$icon $(pad_name \"$name\") Running restarts: $restarts\"',\n\t\t\" fi\",\n\t\t' if [ \"$restarts\" -gt 5 ] 2>/dev/null; then',\n\t\t' warn_svc \" └─ $name has restarted $restarts times (possible crash loop)\"',\n\t\t\" fi\",\n\t\t' elif [ \"$state\" = \"exited\" ]; then',\n\t\t\" local exit_code\",\n\t\t' exit_code=$(docker inspect --format=\"{{.State.ExitCode}}\" \"$(docker compose ps -q \"$id\" 2>/dev/null | head -1)\" 2>/dev/null || echo \"?\")',\n\t\t' fail \"$icon $(pad_name \"$name\") Exited (code $exit_code)\"',\n\t\t' if [ \"$exit_code\" = \"137\" ]; then',\n\t\t' ERRORS+=(\" └─ $name: OOM killed (exit 137). Increase memory limits.\")',\n\t\t' elif [ \"$exit_code\" = \"1\" ]; then',\n\t\t' ERRORS+=(\" └─ $name: Application error (exit 1). Check logs: docker compose logs $id\")',\n\t\t\" fi\",\n\t\t\" else\",\n\t\t' fail \"$icon $(pad_name \"$name\") State: $state\"',\n\t\t\" fi\",\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"# Phase 3: Port Reachability\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"check_port() {\",\n\t\t' local id=\"$1\" port=\"$2\" desc=\"$3\"',\n\t\t\" if command -v nc &>/dev/null; then\",\n\t\t' if nc -z -w 2 localhost \"$port\" 2>/dev/null; then',\n\t\t' dim \" Port $port ($desc) — reachable\"',\n\t\t\" else\",\n\t\t' warn_svc \" └─ $id: Port $port ($desc) — NOT reachable\"',\n\t\t\" fi\",\n\t\t\" elif command -v curl &>/dev/null; then\",\n\t\t' if curl -sf --max-time 2 \"http://localhost:$port/\" &>/dev/null; then',\n\t\t' dim \" Port $port ($desc) — reachable\"',\n\t\t\" else\",\n\t\t' warn_svc \" └─ $id: Port $port ($desc) — NOT reachable\"',\n\t\t\" fi\",\n\t\t\" else\",\n\t\t' dim \" Port $port ($desc) — skipped (no nc or curl)\"',\n\t\t\" fi\",\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"# Phase 4: Health Check Commands\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"check_health_cmd() {\",\n\t\t' local id=\"$1\" cmd=\"$2\"',\n\t\t' local cid=$(docker compose ps -q \"$id\" 2>/dev/null | head -1)',\n\t\t' [ -z \"$cid\" ] && return',\n\t\t' if docker exec \"$cid\" sh -c \"$cmd\" &>/dev/null; then',\n\t\t' dim \" Health command passed\"',\n\t\t\" else\",\n\t\t' warn_svc \" └─ $id: Health check command failed\"',\n\t\t' if [ \"$VERBOSE\" = true ]; then dim \" Command: $cmd\"; fi',\n\t\t\" fi\",\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"# Phase 5: Resource Usage\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"phase_resources() {\",\n\t\t' if [ \"$JSON_MODE\" = false ]; then',\n\t\t' echo \"\"',\n\t\t' echo -e \"${BOLD}── Phase 5: Resource Usage ─────────────────────────────────${NC}\"',\n\t\t' echo \"\"',\n\t\t\" fi\",\n\t\t' docker stats --no-stream --format \"table {{.Name}}\\\\t{{.CPUPerc}}\\\\t{{.MemUsage}}\\\\t{{.MemPerc}}\" 2>/dev/null || true',\n\t\t' if [ \"$VERBOSE\" = false ] && [ \"$JSON_MODE\" = false ]; then',\n\t\t' info \"Use --verbose to see warnings for high memory usage\"',\n\t\t\" fi\",\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"# Phase 6: Log Scan\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"phase_logs() {\",\n\t\t' if [ \"$QUICK_MODE\" = true ]; then',\n\t\t' if [ \"$JSON_MODE\" = false ]; then echo \"\"; info \"Skipping log scan (--quick mode)\"; fi',\n\t\t\" return\",\n\t\t\" fi\",\n\t\t' if [ \"$JSON_MODE\" = false ]; then',\n\t\t' echo \"\"',\n\t\t' echo -e \"${BOLD}── Phase 6: Log Scan ───────────────────────────────────────${NC}\"',\n\t\t' echo \"\"',\n\t\t\" fi\",\n\t\t\" local has_errors=false\",\n\t\tL(\" for svc_id in \", svcIdList, \"; do\"),\n\t\t\" local error_lines\",\n\t\t' error_lines=$(docker compose logs --tail=50 \"$svc_id\" 2>/dev/null | grep -iE \"(error|fatal|panic|exception|segfault|killed|oom)\" | tail -5 || true)',\n\t\t' if [ -n \"$error_lines\" ]; then',\n\t\t\" has_errors=true\",\n\t\t' warn_svc \"$svc_id — found error patterns in logs:\"',\n\t\t' echo \"$error_lines\" | while IFS= read -r eline; do',\n\t\t' dim \" | $eline\"',\n\t\t\" done\",\n\t\t\" fi\",\n\t\t\" done\",\n\t\t' if [ \"$has_errors\" = false ] && [ \"$JSON_MODE\" = false ]; then',\n\t\t' pass \"No error patterns found in recent logs\"',\n\t\t\" fi\",\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ── Bare-metal process check ────────────────────────────────────────\",\n\t\t\"check_process() {\",\n\t\t' local id=\"$1\" name=\"$2\" icon=\"$3\"',\n\t\t\" if command -v systemctl &>/dev/null; then\",\n\t\t' if systemctl is-active --quiet \"$id\" 2>/dev/null; then',\n\t\t' pass \"$icon $(pad_name \"$name\") Active (systemd)\"',\n\t\t\" return\",\n\t\t\" fi\",\n\t\t\" fi\",\n\t\t' if pgrep -f \"$id\" &>/dev/null; then',\n\t\t' pass \"$icon $(pad_name \"$name\") Running (process)\"',\n\t\t\" else\",\n\t\t' fail \"$icon $(pad_name \"$name\") NOT running\"',\n\t\t\" fi\",\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"# Main\",\n\t\t\"# ═══════════════════════════════════════════════════════════════════════\",\n\t\t\"\",\n\t\t\"DOCKER_AVAILABLE=false\",\n\t\t\"\",\n\t\t'if [ \"$JSON_MODE\" = false ]; then',\n\t\t' echo \"\"',\n\t\t' echo \"═══════════════════════════════════════════════════════════════════════\"',\n\t\tL(' echo \" 🐾 OpenClaw Stack Health Report — ', name, '\"'),\n\t\t' echo \"═══════════════════════════════════════════════════════════════════════\"',\n\t\t\"fi\",\n\t\t\"\",\n\t\t\"phase_environment\",\n\t\t\"\",\n\t\t'if [ \"$DOCKER_AVAILABLE\" = true ]; then',\n\t\t' if [ \"$JSON_MODE\" = false ]; then',\n\t\t' echo \"\"',\n\t\t' echo -e \"${BOLD}── Phase 2–4: Service Checks ──────────────────────────────${NC}\"',\n\t\t' echo \"\"',\n\t\t\" fi\",\n\t\t\"\",\n\t\t...dockerChecks,\n\t\t\"\",\n\t\t\" phase_resources\",\n\t\t\" phase_logs\",\n\t\t\"\",\n\t\t\"else\",\n\t\t' if [ \"$JSON_MODE\" = false ]; then',\n\t\t' echo \"\"',\n\t\t' echo -e \"${BOLD}── Bare-Metal Service Checks ──────────────────────────────${NC}\"',\n\t\t' echo \"\"',\n\t\t\" fi\",\n\t\t\"\",\n\t\t...bmChecks,\n\t\t\"fi\",\n\t\t\"\",\n\t\t\"# ── Summary ──────────────────────────────────────────────────────────\",\n\t\t'echo \"\"',\n\t\t'echo \"═══════════════════════════════════════════════════════════════════════\"',\n\t\t'echo \"\"',\n\t\t'echo -e \" ${BOLD}Summary${NC}: $TOTAL services checked\"',\n\t\t'echo -e \" ${GREEN}Healthy: $HEALTHY${NC} | ${YELLOW}Warnings: $WARNING${NC} | ${RED}Failed: $FAILED${NC}\"',\n\t\t\"\",\n\t\t\"if [ ${#ERRORS[@]} -gt 0 ]; then\",\n\t\t' echo \"\"',\n\t\t' echo -e \" ${BOLD}${RED}── Errors ─────────────────────────────────────────────────${NC}\"',\n\t\t' for e in \"${ERRORS[@]}\"; do',\n\t\t' echo -e \" ${RED}$e${NC}\"',\n\t\t\" done\",\n\t\t\"fi\",\n\t\t\"\",\n\t\t'echo \"\"',\n\t\t'echo \"═══════════════════════════════════════════════════════════════════════\"',\n\t\t'echo \"\"',\n\t\t\"\",\n\t\t'if [ \"$FAILED\" -gt 0 ]; then',\n\t\t' echo -e \" ${RED}Some services need attention. Run with --verbose for details.${NC}\"',\n\t\t\" exit 1\",\n\t\t\"else\",\n\t\t' echo -e \" ${GREEN}🎉 All services are running!${NC}\"',\n\t\t\" exit 0\",\n\t\t\"fi\",\n\t];\n\n\treturn lines.join(\"\\n\") + \"\\n\";\n}\n\n// ─── PowerShell Script ──────────────────────────────────────────────────────\n\nfunction generatePowerShellScript(resolved: ResolverOutput, options: HealthCheckOptions): string {\n\tconst checks = extractServiceChecks(resolved);\n\tconst name = options.projectName;\n\tconst total = checks.length;\n\n\t// Build per-service docker check calls\n\tconst dockerChecks: string[] = [];\n\tfor (const svc of checks) {\n\t\tdockerChecks.push(L(\" # \", svc.icon, \" \", svc.name));\n\t\tdockerChecks.push(\n\t\t\tL(' Test-Container -ServiceId \"', svc.id, '\" -ServiceName \"', svc.name, '\" -Icon \"', svc.icon, '\"'),\n\t\t);\n\t\tfor (const p of svc.ports.filter((pp) => pp.exposed)) {\n\t\t\tdockerChecks.push(\n\t\t\t\tL(' Test-Port -ServiceId \"', svc.id, '\" -Port ', String(p.host), ' -Description \"', p.description, '\"'),\n\t\t\t);\n\t\t}\n\t\tdockerChecks.push(\"\");\n\t}\n\n\t// Build per-service bare-metal check calls\n\tconst bmChecks: string[] = [];\n\tfor (const svc of checks) {\n\t\tbmChecks.push(L(\" # \", svc.icon, \" \", svc.name));\n\t\tbmChecks.push(\n\t\t\tL(' Test-ProcessRunning -ServiceId \"', svc.id, '\" -ServiceName \"', svc.name, '\" -Icon \"', svc.icon, '\"'),\n\t\t);\n\t\tfor (const p of svc.ports.filter((pp) => pp.exposed)) {\n\t\t\tbmChecks.push(\n\t\t\t\tL(' Test-Port -ServiceId \"', svc.id, '\" -Port ', String(p.host), ' -Description \"', p.description, '\"'),\n\t\t\t);\n\t\t}\n\t\tbmChecks.push(\"\");\n\t}\n\n\tconst svcIdList = checks.map((s) => '\"' + s.id + '\"').join(\", \");\n\n\tconst lines: string[] = [\n\t\t\"#Requires -Version 5.1\",\n\t\t\"<#\",\n\t\t\".SYNOPSIS\",\n\t\tL(\" OpenClaw Stack Health Check — \", name),\n\t\t\"\",\n\t\t\".DESCRIPTION\",\n\t\t\" Auto-generated verification script for your stack.\",\n\t\tL(\" Checks \", String(total), \" services: container status, port reachability, and log errors.\"),\n\t\t\"\",\n\t\t\".PARAMETER Quick\",\n\t\t\" Skip log scanning for faster results.\",\n\t\t\"\",\n\t\t\".PARAMETER Json\",\n\t\t\" Output results as JSON.\",\n\t\t\"\",\n\t\t\".EXAMPLE\",\n\t\t\" .\\\\scripts\\\\health-check.ps1\",\n\t\t\" .\\\\scripts\\\\health-check.ps1 -Quick\",\n\t\t\" .\\\\scripts\\\\health-check.ps1 -Json\",\n\t\t\"#>\",\n\t\t\"param(\",\n\t\t\" [switch]$Quick,\",\n\t\t\" [switch]$Json,\",\n\t\t\" [switch]$Detailed\",\n\t\t\")\",\n\t\t\"\",\n\t\t'$ErrorActionPreference = \"Continue\"',\n\t\t\"\",\n\t\t\"# ── Counters ─────────────────────────────────────────────────────────\",\n\t\tL(\"$script:Total = \", String(total)),\n\t\t\"$script:Healthy = 0\",\n\t\t\"$script:Warnings = 0\",\n\t\t\"$script:Failed = 0\",\n\t\t\"$script:Errors = @()\",\n\t\t\"$script:ServiceResults = @()\",\n\t\t\"$script:DockerAvailable = $false\",\n\t\t\"\",\n\t\t\"# ── Helpers ──────────────────────────────────────────────────────────\",\n\t\t\"\",\n\t\t'function Write-Pass($msg) { Write-Host \" ✅ $msg\" -ForegroundColor Green; $script:Healthy++ }',\n\t\t'function Write-Warn($msg) { Write-Host \" ⚠️ $msg\" -ForegroundColor Yellow; $script:Warnings++ }',\n\t\t'function Write-Fail($msg) { Write-Host \" ❌ $msg\" -ForegroundColor Red; $script:Failed++; $script:Errors += $msg }',\n\t\t'function Write-Info($msg) { Write-Host \" ℹ $msg\" -ForegroundColor Cyan }',\n\t\t'function Write-Dim($msg) { Write-Host \" $msg\" -ForegroundColor DarkGray }',\n\t\t\"\",\n\t\t\"function Pad-Name([string]$n) { return $n.PadRight(25) }\",\n\t\t\"\",\n\t\t\"# ═════════════════════════════════════════════════════════════════════\",\n\t\t\"# Phase 1: Environment\",\n\t\t\"# ═════════════════════════════════════════════════════════════════════\",\n\t\t\"function Test-Environment {\",\n\t\t\" if (-not $Json) {\",\n\t\t' Write-Host \"\"',\n\t\t' Write-Host \"── Phase 1: Environment ────────────────────────────────────\" -ForegroundColor White',\n\t\t' Write-Host \"\"',\n\t\t\" }\",\n\t\t\" try {\",\n\t\t\" $null = docker info 2>&1\",\n\t\t\" if ($LASTEXITCODE -eq 0) {\",\n\t\t' Write-Pass \"Docker daemon is running\"',\n\t\t\" $script:DockerAvailable = $true\",\n\t\t\" } else {\",\n\t\t' Write-Fail \"Docker daemon is NOT running\"',\n\t\t\" }\",\n\t\t\" } catch {\",\n\t\t' Write-Warn \"Docker is not installed — running bare-metal checks only\"',\n\t\t\" }\",\n\t\t\" if ($script:DockerAvailable) {\",\n\t\t\" try {\",\n\t\t\" $null = docker compose version 2>&1\",\n\t\t\" if ($LASTEXITCODE -eq 0) {\",\n\t\t' Write-Pass \"Docker Compose v2 available\"',\n\t\t\" } else {\",\n\t\t' Write-Warn \"Docker Compose v2 not found\"',\n\t\t\" }\",\n\t\t\" } catch {\",\n\t\t' Write-Warn \"Docker Compose not available\"',\n\t\t\" }\",\n\t\t\" }\",\n\t\t' $envPath = Join-Path $PSScriptRoot \"..\\\\.env\"',\n\t\t\" if (Test-Path $envPath) {\",\n\t\t' Write-Pass \".env file exists\"',\n\t\t\" $emptySecrets = 0\",\n\t\t\" Get-Content $envPath | ForEach-Object {\",\n\t\t' if ($_ -match \"(PASSWORD|TOKEN|SECRET|SESSION_KEY|COOKIE|API_KEY).*=\\\\s*$\") { $emptySecrets++ }',\n\t\t\" }\",\n\t\t\" if ($emptySecrets -gt 0) {\",\n\t\t' Write-Warn \"$emptySecrets secret(s) are empty in .env\"',\n\t\t\" } else {\",\n\t\t' Write-Pass \"All secrets are populated\"',\n\t\t\" }\",\n\t\t\" } else {\",\n\t\t' Write-Warn \".env file not found\"',\n\t\t\" }\",\n\t\t\" $drive = (Get-Item $PSScriptRoot).PSDrive\",\n\t\t\" $freeGB = [math]::Round($drive.Free / 1GB, 1)\",\n\t\t\" if ($freeGB -lt 2) {\",\n\t\t' Write-Warn \"Low disk space: ${freeGB}GB available\"',\n\t\t\" } else {\",\n\t\t' Write-Pass \"Disk space: ${freeGB}GB available\"',\n\t\t\" }\",\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ═════════════════════════════════════════════════════════════════════\",\n\t\t\"# Phase 2: Container Check\",\n\t\t\"# ═════════════════════════════════════════════════════════════════════\",\n\t\t\"function Test-Container {\",\n\t\t\" param([string]$ServiceId, [string]$ServiceName, [string]$Icon)\",\n\t\t\" try {\",\n\t\t\" $status = docker compose ps $ServiceId --format json 2>&1 | ConvertFrom-Json\",\n\t\t\" } catch {\",\n\t\t' Write-Fail \"$Icon $(Pad-Name $ServiceName) Container not found\"',\n\t\t\" return\",\n\t\t\" }\",\n\t\t\" if (-not $status) {\",\n\t\t' Write-Fail \"$Icon $(Pad-Name $ServiceName) Container not found\"',\n\t\t\" return\",\n\t\t\" }\",\n\t\t\" $state = $status.State\",\n\t\t\" $health = $status.Health\",\n\t\t' if ($state -eq \"running\") {',\n\t\t' if ($health -eq \"healthy\") {',\n\t\t' Write-Pass \"$Icon $(Pad-Name $ServiceName) Running (healthy)\"',\n\t\t' } elseif ($health -eq \"starting\") {',\n\t\t' Write-Warn \"$Icon $(Pad-Name $ServiceName) Running (starting)\"',\n\t\t' } elseif ($health -eq \"unhealthy\") {',\n\t\t' Write-Fail \"$Icon $(Pad-Name $ServiceName) Running (UNHEALTHY)\"',\n\t\t\" } else {\",\n\t\t' Write-Pass \"$Icon $(Pad-Name $ServiceName) Running\"',\n\t\t\" }\",\n\t\t' } elseif ($state -eq \"exited\") {',\n\t\t' Write-Fail \"$Icon $(Pad-Name $ServiceName) Exited\"',\n\t\t\" } else {\",\n\t\t' Write-Fail \"$Icon $(Pad-Name $ServiceName) State: $state\"',\n\t\t\" }\",\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ═════════════════════════════════════════════════════════════════════\",\n\t\t\"# Phase 3: Port Check\",\n\t\t\"# ═════════════════════════════════════════════════════════════════════\",\n\t\t\"function Test-Port {\",\n\t\t\" param([string]$ServiceId, [int]$Port, [string]$Description)\",\n\t\t\" try {\",\n\t\t\" $tcp = New-Object System.Net.Sockets.TcpClient\",\n\t\t' $tcp.ConnectAsync(\"localhost\", $Port).Wait(2000) | Out-Null',\n\t\t\" if ($tcp.Connected) {\",\n\t\t' Write-Dim \" Port $Port ($Description) — reachable\"',\n\t\t\" $tcp.Close()\",\n\t\t\" } else {\",\n\t\t' Write-Warn \" └─ ${ServiceId}: Port $Port ($Description) — NOT reachable\"',\n\t\t\" }\",\n\t\t\" } catch {\",\n\t\t' Write-Warn \" └─ ${ServiceId}: Port $Port ($Description) — NOT reachable\"',\n\t\t\" }\",\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ── Bare-metal process check ────────────────────────────────────────\",\n\t\t\"function Test-ProcessRunning {\",\n\t\t\" param([string]$ServiceId, [string]$ServiceName, [string]$Icon)\",\n\t\t\" $svc = Get-Service -Name $ServiceId -ErrorAction SilentlyContinue\",\n\t\t\" if ($svc) {\",\n\t\t' if ($svc.Status -eq \"Running\") {',\n\t\t' Write-Pass \"$Icon $(Pad-Name $ServiceName) Active (service)\"',\n\t\t\" } else {\",\n\t\t' Write-Fail \"$Icon $(Pad-Name $ServiceName) $($svc.Status)\"',\n\t\t\" }\",\n\t\t\" return\",\n\t\t\" }\",\n\t\t\" $proc = Get-Process -Name $ServiceId -ErrorAction SilentlyContinue\",\n\t\t\" if ($proc) {\",\n\t\t' Write-Pass \"$Icon $(Pad-Name $ServiceName) Running (process)\"',\n\t\t\" } else {\",\n\t\t' Write-Fail \"$Icon $(Pad-Name $ServiceName) NOT running\"',\n\t\t\" }\",\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ═════════════════════════════════════════════════════════════════════\",\n\t\t\"# Phase 6: Log Scan\",\n\t\t\"# ═════════════════════════════════════════════════════════════════════\",\n\t\t\"function Test-Logs {\",\n\t\t\" if ($Quick) {\",\n\t\t' if (-not $Json) { Write-Info \"Skipping log scan (-Quick mode)\" }',\n\t\t\" return\",\n\t\t\" }\",\n\t\t\" if (-not $Json) {\",\n\t\t' Write-Host \"\"',\n\t\t' Write-Host \"── Phase 6: Log Scan ───────────────────────────────────────\" -ForegroundColor White',\n\t\t' Write-Host \"\"',\n\t\t\" }\",\n\t\t\" $hasErrors = $false\",\n\t\tL(\" $svcIds = @(\", svcIdList, \")\"),\n\t\t\" foreach ($svcId in $svcIds) {\",\n\t\t\" try {\",\n\t\t\" $logs = docker compose logs --tail=50 $svcId 2>&1 | Out-String\",\n\t\t' $errorLines = $logs -split \"`n\" | Where-Object { $_ -match \"(error|fatal|panic|exception|segfault|killed|oom)\" } | Select-Object -Last 5',\n\t\t\" if ($errorLines) {\",\n\t\t\" $hasErrors = $true\",\n\t\t' Write-Warn \"$svcId — found error patterns in logs:\"',\n\t\t' $errorLines | ForEach-Object { Write-Dim \" | $_\" }',\n\t\t\" }\",\n\t\t\" } catch {}\",\n\t\t\" }\",\n\t\t\" if (-not $hasErrors -and -not $Json) {\",\n\t\t' Write-Pass \"No error patterns found in recent logs\"',\n\t\t\" }\",\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ═════════════════════════════════════════════════════════════════════\",\n\t\t\"# Main\",\n\t\t\"# ═════════════════════════════════════════════════════════════════════\",\n\t\t\"\",\n\t\t\"if (-not $Json) {\",\n\t\t' Write-Host \"\"',\n\t\t' Write-Host \"═══════════════════════════════════════════════════════════════════════\" ',\n\t\tL(' Write-Host \" 🐾 OpenClaw Stack Health Report — ', name, '\"'),\n\t\t' Write-Host \"═══════════════════════════════════════════════════════════════════════\"',\n\t\t\"}\",\n\t\t\"\",\n\t\t\"Test-Environment\",\n\t\t\"\",\n\t\t\"if ($script:DockerAvailable) {\",\n\t\t\" if (-not $Json) {\",\n\t\t' Write-Host \"\"',\n\t\t' Write-Host \"── Phase 2–4: Service Checks ──────────────────────────────\" -ForegroundColor White',\n\t\t' Write-Host \"\"',\n\t\t\" }\",\n\t\t\"\",\n\t\t...dockerChecks,\n\t\t\" Test-Logs\",\n\t\t\"} else {\",\n\t\t\" if (-not $Json) {\",\n\t\t' Write-Host \"\"',\n\t\t' Write-Host \"── Bare-Metal Service Checks ──────────────────────────────\" -ForegroundColor White',\n\t\t' Write-Host \"\"',\n\t\t\" }\",\n\t\t\"\",\n\t\t...bmChecks,\n\t\t\"}\",\n\t\t\"\",\n\t\t\"# ── Summary ──────────────────────────────────────────────────────────\",\n\t\t\"if ($Json) {\",\n\t\t\" $result = @{\",\n\t\tL(' project = \"', name, '\"'),\n\t\t' timestamp = (Get-Date -Format \"o\")',\n\t\t\" summary = @{\",\n\t\t\" total = $script:Total\",\n\t\t\" healthy = $script:Healthy\",\n\t\t\" warnings = $script:Warnings\",\n\t\t\" failed = $script:Failed\",\n\t\t\" }\",\n\t\t\" errors = $script:Errors\",\n\t\t\" }\",\n\t\t\" $result | ConvertTo-Json -Depth 5\",\n\t\t\"} else {\",\n\t\t' Write-Host \"\"',\n\t\t' Write-Host \"═══════════════════════════════════════════════════════════════════════\"',\n\t\t' Write-Host \"\"',\n\t\t' Write-Host \" Summary: $($script:Total) services checked\"',\n\t\t' Write-Host \" Healthy: $($script:Healthy)\" -ForegroundColor Green -NoNewline',\n\t\t' Write-Host \" | Warnings: $($script:Warnings)\" -ForegroundColor Yellow -NoNewline',\n\t\t' Write-Host \" | Failed: $($script:Failed)\" -ForegroundColor Red',\n\t\t\" if ($script:Errors.Count -gt 0) {\",\n\t\t' Write-Host \"\"',\n\t\t' Write-Host \" ── Errors ──────────────────────────────────────────────────\" -ForegroundColor Red',\n\t\t' $script:Errors | ForEach-Object { Write-Host \" $_\" -ForegroundColor Red }',\n\t\t\" }\",\n\t\t' Write-Host \"\"',\n\t\t' Write-Host \"═══════════════════════════════════════════════════════════════════════\"',\n\t\t' Write-Host \"\"',\n\t\t\" if ($script:Failed -gt 0) {\",\n\t\t' Write-Host \" Some services need attention. Run with -Detailed for more.\" -ForegroundColor Red',\n\t\t\" exit 1\",\n\t\t\" } else {\",\n\t\t' Write-Host \" 🎉 All services are running!\" -ForegroundColor Green',\n\t\t\" exit 0\",\n\t\t\" }\",\n\t\t\"}\",\n\t];\n\n\treturn lines.join(\"\\n\") + \"\\n\";\n}\n"],"mappings":";;;;;;;;AAkBA,SAAgB,oBACf,UACA,SACyB;CACzB,MAAM,QAAgC,EAAE;AAExC,OAAM,6BAA6B,mBAAmB,UAAU,QAAQ;AACxE,OAAM,8BAA8B,yBAAyB,UAAU,QAAQ;AAE/E,QAAO;;AAaR,SAAS,qBAAqB,UAA0C;AACvE,QAAO,SAAS,SAAS,KAAK,SAAS;EACtC,IAAI,IAAI,WAAW;EACnB,MAAM,IAAI,WAAW;EACrB,MAAM,IAAI,WAAW;EACrB,OAAO,IAAI,WAAW,MAAM,KAAK,OAAO;GACvC,MAAM,EAAE;GACR,WAAW,EAAE;GACb,aAAa,EAAE;GACf,SAAS,EAAE;GACX,EAAE;EACH,gBAAgB,IAAI,WAAW,aAAa,QAAQ;EACpD,EAAE;;AAGJ,SAAS,YAAY,GAAmB;AACvC,QAAO,EAAE,QAAQ,MAAM,QAAQ,CAAC,QAAQ,MAAM,OAAM;;AAIrD,SAAS,EAAE,GAAG,OAAyB;AACtC,QAAO,MAAM,KAAK,GAAG;;AAKtB,SAAS,mBAAmB,UAA0B,SAAqC;CAC1F,MAAM,SAAS,qBAAqB,SAAS;CAC7C,MAAM,OAAO,QAAQ;CACrB,MAAM,QAAQ,OAAO;CAGrB,MAAM,eAAyB,EAAE;AACjC,MAAK,MAAM,OAAO,QAAQ;AACzB,eAAa,KAAK,EAAE,WAAW,IAAI,MAAM,KAAK,IAAI,MAAM,MAAM,CAAC;AAC/D,eAAa,KAAK,EAAE,wBAAuB,IAAI,IAAI,SAAO,IAAI,MAAM,SAAO,IAAI,MAAM,KAAI,CAAC;AAC1F,OAAK,MAAM,KAAK,IAAI,MAAM,QAAQ,OAAO,GAAG,QAAQ,CACnD,cAAa,KAAK,EAAE,mBAAkB,IAAI,IAAI,OAAM,OAAO,EAAE,KAAK,EAAE,OAAM,EAAE,aAAa,KAAI,CAAC;AAE/F,MAAI,IAAI,eACP,cAAa,KACZ,EAAE,yBAAwB,IAAI,IAAI,SAAO,YAAY,IAAI,eAAe,EAAE,KAAI,CAC9E;MAED,cAAa,KAAK,mDAAmD;AAEtE,eAAa,KAAK,GAAG;;CAItB,MAAM,WAAqB,EAAE;AAC7B,MAAK,MAAM,OAAO,QAAQ;AACzB,WAAS,KAAK,EAAE,WAAW,IAAI,MAAM,KAAK,IAAI,MAAM,mBAAmB,CAAC;AACxE,WAAS,KAAK,EAAE,sBAAqB,IAAI,IAAI,SAAO,IAAI,MAAM,SAAO,IAAI,MAAM,KAAI,CAAC;AACpF,OAAK,MAAM,KAAK,IAAI,MAAM,QAAQ,OAAO,GAAG,QAAQ,CACnD,UAAS,KAAK,EAAE,mBAAkB,IAAI,IAAI,OAAM,OAAO,EAAE,KAAK,EAAE,OAAM,EAAE,aAAa,KAAI,CAAC;AAE3F,WAAS,KAAK,GAAG;;CAIlB,MAAM,YAAY,OAAO,KAAK,MAAM,OAAM,EAAE,KAAK,KAAI,CAAC,KAAK,IAAI;AA6V/D,QA3VwB;EACvB;EACA;EACA;EACA;EACA,EAAE,uCAAuC,KAAK;EAC9C;EACA;EACA;EACA,EAAE,aAAa,OAAO,MAAM,EAAE,6BAA6B;EAC3D;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,EAAE,UAAU,OAAO,MAAM,CAAC;EAC1B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,EAAE,oBAAoB,WAAW,OAAO;EACxC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,EAAE,gDAA+C,MAAM,KAAI;EAC3D;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,GAAG;EACH;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,GAAG;EACH;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,CAEY,KAAK,KAAK,GAAG;;AAK3B,SAAS,yBAAyB,UAA0B,SAAqC;CAChG,MAAM,SAAS,qBAAqB,SAAS;CAC7C,MAAM,OAAO,QAAQ;CACrB,MAAM,QAAQ,OAAO;CAGrB,MAAM,eAAyB,EAAE;AACjC,MAAK,MAAM,OAAO,QAAQ;AACzB,eAAa,KAAK,EAAE,UAAU,IAAI,MAAM,KAAK,IAAI,KAAK,CAAC;AACvD,eAAa,KACZ,EAAE,oCAAmC,IAAI,IAAI,sBAAoB,IAAI,MAAM,eAAa,IAAI,MAAM,KAAI,CACtG;AACD,OAAK,MAAM,KAAK,IAAI,MAAM,QAAQ,OAAO,GAAG,QAAQ,CACnD,cAAa,KACZ,EAAE,+BAA8B,IAAI,IAAI,aAAY,OAAO,EAAE,KAAK,EAAE,oBAAmB,EAAE,aAAa,KAAI,CAC1G;AAEF,eAAa,KAAK,GAAG;;CAItB,MAAM,WAAqB,EAAE;AAC7B,MAAK,MAAM,OAAO,QAAQ;AACzB,WAAS,KAAK,EAAE,UAAU,IAAI,MAAM,KAAK,IAAI,KAAK,CAAC;AACnD,WAAS,KACR,EAAE,yCAAwC,IAAI,IAAI,sBAAoB,IAAI,MAAM,eAAa,IAAI,MAAM,KAAI,CAC3G;AACD,OAAK,MAAM,KAAK,IAAI,MAAM,QAAQ,OAAO,GAAG,QAAQ,CACnD,UAAS,KACR,EAAE,+BAA8B,IAAI,IAAI,aAAY,OAAO,EAAE,KAAK,EAAE,oBAAmB,EAAE,aAAa,KAAI,CAC1G;AAEF,WAAS,KAAK,GAAG;;CAGlB,MAAM,YAAY,OAAO,KAAK,MAAM,OAAM,EAAE,KAAK,KAAI,CAAC,KAAK,KAAK;AA0RhE,QAxRwB;EACvB;EACA;EACA;EACA,EAAE,sCAAsC,KAAK;EAC7C;EACA;EACA;EACA,EAAE,eAAe,OAAO,MAAM,EAAE,kEAAkE;EAClG;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,EAAE,oBAAoB,OAAO,MAAM,CAAC;EACpC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,EAAE,oBAAoB,WAAW,IAAI;EACrC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,EAAE,wDAAuD,MAAM,KAAI;EACnE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,GAAG;EACH;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,GAAG;EACH;EACA;EACA;EACA;EACA;EACA,EAAE,wBAAuB,MAAM,KAAI;EACnC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,CAEY,KAAK,KAAK,GAAG"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,85 @@
1
+ import { n as describe, r as it, t as globalExpect } from "../vi.2VT5v0um-YSByewHe.mjs";
2
+ import { generate } from "../generate.mjs";
3
+
4
+ //#region src/generators/health-check.test.ts
5
+ describe("generateHealthCheck (via generate)", () => {
6
+ const baseInput = {
7
+ projectName: "health-test",
8
+ services: ["redis", "postgresql"],
9
+ skillPacks: [],
10
+ proxy: "none",
11
+ gpu: false,
12
+ platform: "linux/amd64",
13
+ deployment: "local",
14
+ generateSecrets: true,
15
+ openclawVersion: "latest"
16
+ };
17
+ it("generates health-check.sh and health-check.ps1", () => {
18
+ const result = generate(baseInput);
19
+ globalExpect(result.files).toHaveProperty("scripts/health-check.sh");
20
+ globalExpect(result.files).toHaveProperty("scripts/health-check.ps1");
21
+ });
22
+ it("health-check.sh contains project name", () => {
23
+ const sh = generate(baseInput).files["scripts/health-check.sh"];
24
+ globalExpect(sh).toContain("health-test");
25
+ });
26
+ it("health-check.sh contains service-specific checks for each resolved service", () => {
27
+ const sh = generate(baseInput).files["scripts/health-check.sh"];
28
+ globalExpect(sh).toContain("redis");
29
+ globalExpect(sh).toContain("postgresql");
30
+ });
31
+ it("health-check.sh includes port checks for exposed ports", () => {
32
+ const sh = generate(baseInput).files["scripts/health-check.sh"];
33
+ globalExpect(sh).toContain("6379");
34
+ globalExpect(sh).toContain("5432");
35
+ });
36
+ it("health-check.sh has usage/help docs", () => {
37
+ const sh = generate(baseInput).files["scripts/health-check.sh"];
38
+ globalExpect(sh).toContain("--quick");
39
+ globalExpect(sh).toContain("--json");
40
+ globalExpect(sh).toContain("--verbose");
41
+ });
42
+ it("health-check.sh starts with shebang", () => {
43
+ const sh = generate(baseInput).files["scripts/health-check.sh"];
44
+ globalExpect(sh.startsWith("#!/usr/bin/env bash")).toBe(true);
45
+ });
46
+ it("health-check.ps1 contains project name", () => {
47
+ const ps1 = generate(baseInput).files["scripts/health-check.ps1"];
48
+ globalExpect(ps1).toContain("health-test");
49
+ });
50
+ it("health-check.ps1 contains service checks", () => {
51
+ const ps1 = generate(baseInput).files["scripts/health-check.ps1"];
52
+ globalExpect(ps1).toContain("redis");
53
+ globalExpect(ps1).toContain("postgresql");
54
+ });
55
+ it("health-check.ps1 includes PowerShell-style parameters", () => {
56
+ const ps1 = generate(baseInput).files["scripts/health-check.ps1"];
57
+ globalExpect(ps1).toContain("[switch]$Quick");
58
+ globalExpect(ps1).toContain("[switch]$Json");
59
+ });
60
+ it("health check scripts include all resolved services including dependencies", () => {
61
+ const sh = generate({
62
+ ...baseInput,
63
+ services: ["postiz"]
64
+ }).files["scripts/health-check.sh"];
65
+ globalExpect(sh).toContain("postiz");
66
+ globalExpect(sh).toContain("redis");
67
+ globalExpect(sh).toContain("postgresql");
68
+ });
69
+ it("health check script includes health check commands when service defines one", () => {
70
+ const sh = generate(baseInput).files["scripts/health-check.sh"];
71
+ globalExpect(sh).toContain("check_health_cmd");
72
+ });
73
+ it("health check scripts contain the phase structure", () => {
74
+ const sh = generate(baseInput).files["scripts/health-check.sh"];
75
+ globalExpect(sh).toContain("Phase 1");
76
+ globalExpect(sh).toContain("phase_environment");
77
+ globalExpect(sh).toContain("check_container");
78
+ globalExpect(sh).toContain("check_port");
79
+ globalExpect(sh).toContain("phase_logs");
80
+ });
81
+ });
82
+
83
+ //#endregion
84
+ export { };
85
+ //# sourceMappingURL=health-check.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-check.test.mjs","names":[],"sources":["../../src/generators/health-check.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { generate } from \"../generate.js\";\n\ndescribe(\"generateHealthCheck (via generate)\", () => {\n\tconst baseInput = {\n\t\tprojectName: \"health-test\",\n\t\tservices: [\"redis\", \"postgresql\"],\n\t\tskillPacks: [] as string[],\n\t\tproxy: \"none\" as const,\n\t\tgpu: false,\n\t\tplatform: \"linux/amd64\" as const,\n\t\tdeployment: \"local\" as const,\n\t\tgenerateSecrets: true,\n\t\topenclawVersion: \"latest\",\n\t};\n\n\tit(\"generates health-check.sh and health-check.ps1\", () => {\n\t\tconst result = generate(baseInput);\n\n\t\texpect(result.files).toHaveProperty(\"scripts/health-check.sh\");\n\t\texpect(result.files).toHaveProperty(\"scripts/health-check.ps1\");\n\t});\n\n\tit(\"health-check.sh contains project name\", () => {\n\t\tconst result = generate(baseInput);\n\t\tconst sh = result.files[\"scripts/health-check.sh\"]!;\n\n\t\texpect(sh).toContain(\"health-test\");\n\t});\n\n\tit(\"health-check.sh contains service-specific checks for each resolved service\", () => {\n\t\tconst result = generate(baseInput);\n\t\tconst sh = result.files[\"scripts/health-check.sh\"]!;\n\n\t\texpect(sh).toContain(\"redis\");\n\t\texpect(sh).toContain(\"postgresql\");\n\t});\n\n\tit(\"health-check.sh includes port checks for exposed ports\", () => {\n\t\tconst result = generate(baseInput);\n\t\tconst sh = result.files[\"scripts/health-check.sh\"]!;\n\n\t\t// Redis exposes port 6379, PostgreSQL exposes 5432\n\t\texpect(sh).toContain(\"6379\");\n\t\texpect(sh).toContain(\"5432\");\n\t});\n\n\tit(\"health-check.sh has usage/help docs\", () => {\n\t\tconst result = generate(baseInput);\n\t\tconst sh = result.files[\"scripts/health-check.sh\"]!;\n\n\t\texpect(sh).toContain(\"--quick\");\n\t\texpect(sh).toContain(\"--json\");\n\t\texpect(sh).toContain(\"--verbose\");\n\t});\n\n\tit(\"health-check.sh starts with shebang\", () => {\n\t\tconst result = generate(baseInput);\n\t\tconst sh = result.files[\"scripts/health-check.sh\"]!;\n\n\t\texpect(sh.startsWith(\"#!/usr/bin/env bash\")).toBe(true);\n\t});\n\n\tit(\"health-check.ps1 contains project name\", () => {\n\t\tconst result = generate(baseInput);\n\t\tconst ps1 = result.files[\"scripts/health-check.ps1\"]!;\n\n\t\texpect(ps1).toContain(\"health-test\");\n\t});\n\n\tit(\"health-check.ps1 contains service checks\", () => {\n\t\tconst result = generate(baseInput);\n\t\tconst ps1 = result.files[\"scripts/health-check.ps1\"]!;\n\n\t\texpect(ps1).toContain(\"redis\");\n\t\texpect(ps1).toContain(\"postgresql\");\n\t});\n\n\tit(\"health-check.ps1 includes PowerShell-style parameters\", () => {\n\t\tconst result = generate(baseInput);\n\t\tconst ps1 = result.files[\"scripts/health-check.ps1\"]!;\n\n\t\texpect(ps1).toContain(\"[switch]$Quick\");\n\t\texpect(ps1).toContain(\"[switch]$Json\");\n\t});\n\n\tit(\"health check scripts include all resolved services including dependencies\", () => {\n\t\tconst result = generate({\n\t\t\t...baseInput,\n\t\t\tservices: [\"postiz\"], // postiz depends on redis and postgresql\n\t\t});\n\t\tconst sh = result.files[\"scripts/health-check.sh\"]!;\n\n\t\texpect(sh).toContain(\"postiz\");\n\t\t// Dependencies should be auto-resolved and included\n\t\texpect(sh).toContain(\"redis\");\n\t\texpect(sh).toContain(\"postgresql\");\n\t});\n\n\tit(\"health check script includes health check commands when service defines one\", () => {\n\t\tconst result = generate(baseInput);\n\t\tconst sh = result.files[\"scripts/health-check.sh\"]!;\n\n\t\t// Redis has a healthcheck command (redis-cli ping)\n\t\texpect(sh).toContain(\"check_health_cmd\");\n\t});\n\n\tit(\"health check scripts contain the phase structure\", () => {\n\t\tconst result = generate(baseInput);\n\t\tconst sh = result.files[\"scripts/health-check.sh\"]!;\n\n\t\texpect(sh).toContain(\"Phase 1\");\n\t\texpect(sh).toContain(\"phase_environment\");\n\t\texpect(sh).toContain(\"check_container\");\n\t\texpect(sh).toContain(\"check_port\");\n\t\texpect(sh).toContain(\"phase_logs\");\n\t});\n});\n"],"mappings":";;;;AAGA,SAAS,4CAA4C;CACpD,MAAM,YAAY;EACjB,aAAa;EACb,UAAU,CAAC,SAAS,aAAa;EACjC,YAAY,EAAE;EACd,OAAO;EACP,KAAK;EACL,UAAU;EACV,YAAY;EACZ,iBAAiB;EACjB,iBAAiB;EACjB;AAED,IAAG,wDAAwD;EAC1D,MAAM,SAAS,SAAS,UAAU;AAElC,eAAO,OAAO,MAAM,CAAC,eAAe,0BAA0B;AAC9D,eAAO,OAAO,MAAM,CAAC,eAAe,2BAA2B;GAC9D;AAEF,IAAG,+CAA+C;EAEjD,MAAM,KADS,SAAS,UAAU,CAChB,MAAM;AAExB,eAAO,GAAG,CAAC,UAAU,cAAc;GAClC;AAEF,IAAG,oFAAoF;EAEtF,MAAM,KADS,SAAS,UAAU,CAChB,MAAM;AAExB,eAAO,GAAG,CAAC,UAAU,QAAQ;AAC7B,eAAO,GAAG,CAAC,UAAU,aAAa;GACjC;AAEF,IAAG,gEAAgE;EAElE,MAAM,KADS,SAAS,UAAU,CAChB,MAAM;AAGxB,eAAO,GAAG,CAAC,UAAU,OAAO;AAC5B,eAAO,GAAG,CAAC,UAAU,OAAO;GAC3B;AAEF,IAAG,6CAA6C;EAE/C,MAAM,KADS,SAAS,UAAU,CAChB,MAAM;AAExB,eAAO,GAAG,CAAC,UAAU,UAAU;AAC/B,eAAO,GAAG,CAAC,UAAU,SAAS;AAC9B,eAAO,GAAG,CAAC,UAAU,YAAY;GAChC;AAEF,IAAG,6CAA6C;EAE/C,MAAM,KADS,SAAS,UAAU,CAChB,MAAM;AAExB,eAAO,GAAG,WAAW,sBAAsB,CAAC,CAAC,KAAK,KAAK;GACtD;AAEF,IAAG,gDAAgD;EAElD,MAAM,MADS,SAAS,UAAU,CACf,MAAM;AAEzB,eAAO,IAAI,CAAC,UAAU,cAAc;GACnC;AAEF,IAAG,kDAAkD;EAEpD,MAAM,MADS,SAAS,UAAU,CACf,MAAM;AAEzB,eAAO,IAAI,CAAC,UAAU,QAAQ;AAC9B,eAAO,IAAI,CAAC,UAAU,aAAa;GAClC;AAEF,IAAG,+DAA+D;EAEjE,MAAM,MADS,SAAS,UAAU,CACf,MAAM;AAEzB,eAAO,IAAI,CAAC,UAAU,iBAAiB;AACvC,eAAO,IAAI,CAAC,UAAU,gBAAgB;GACrC;AAEF,IAAG,mFAAmF;EAKrF,MAAM,KAJS,SAAS;GACvB,GAAG;GACH,UAAU,CAAC,SAAS;GACpB,CAAC,CACgB,MAAM;AAExB,eAAO,GAAG,CAAC,UAAU,SAAS;AAE9B,eAAO,GAAG,CAAC,UAAU,QAAQ;AAC7B,eAAO,GAAG,CAAC,UAAU,aAAa;GACjC;AAEF,IAAG,qFAAqF;EAEvF,MAAM,KADS,SAAS,UAAU,CAChB,MAAM;AAGxB,eAAO,GAAG,CAAC,UAAU,mBAAmB;GACvC;AAEF,IAAG,0DAA0D;EAE5D,MAAM,KADS,SAAS,UAAU,CAChB,MAAM;AAExB,eAAO,GAAG,CAAC,UAAU,UAAU;AAC/B,eAAO,GAAG,CAAC,UAAU,oBAAoB;AACzC,eAAO,GAAG,CAAC,UAAU,kBAAkB;AACvC,eAAO,GAAG,CAAC,UAAU,aAAa;AAClC,eAAO,GAAG,CAAC,UAAU,aAAa;GACjC;EACD"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,52 @@
1
+ import { n as describe, r as it, t as globalExpect } from "../vi.2VT5v0um-YSByewHe.mjs";
2
+ import { generateScripts } from "./scripts.mjs";
3
+
4
+ //#region src/generators/scripts.test.ts
5
+ describe("generateScripts", () => {
6
+ it("generates all 5 expected scripts", () => {
7
+ const result = generateScripts();
8
+ for (const script of [
9
+ "scripts/start.sh",
10
+ "scripts/stop.sh",
11
+ "scripts/update.sh",
12
+ "scripts/backup.sh",
13
+ "scripts/status.sh"
14
+ ]) {
15
+ globalExpect(result).toHaveProperty(script);
16
+ globalExpect(result[script].length).toBeGreaterThan(0);
17
+ }
18
+ });
19
+ it("start.sh calls docker compose up", () => {
20
+ const result = generateScripts();
21
+ globalExpect(result["scripts/start.sh"]).toContain("docker compose");
22
+ globalExpect(result["scripts/start.sh"]).toContain("up");
23
+ });
24
+ it("stop.sh calls docker compose down", () => {
25
+ const result = generateScripts();
26
+ globalExpect(result["scripts/stop.sh"]).toContain("docker compose");
27
+ globalExpect(result["scripts/stop.sh"]).toContain("down");
28
+ });
29
+ it("update.sh calls docker compose pull", () => {
30
+ const result = generateScripts();
31
+ globalExpect(result["scripts/update.sh"]).toContain("docker compose");
32
+ globalExpect(result["scripts/update.sh"]).toContain("pull");
33
+ });
34
+ it("backup.sh references volumes or backup", () => {
35
+ const backup = generateScripts()["scripts/backup.sh"];
36
+ globalExpect(backup).toBeDefined();
37
+ globalExpect(backup.length).toBeGreaterThan(50);
38
+ });
39
+ it("status.sh calls docker compose ps", () => {
40
+ const result = generateScripts();
41
+ globalExpect(result["scripts/status.sh"]).toContain("docker compose");
42
+ globalExpect(result["scripts/status.sh"]).toContain("ps");
43
+ });
44
+ it("all scripts start with bash shebang", () => {
45
+ const result = generateScripts();
46
+ for (const [, content] of Object.entries(result)) globalExpect(content.startsWith("#!/")).toBe(true);
47
+ });
48
+ });
49
+
50
+ //#endregion
51
+ export { };
52
+ //# sourceMappingURL=scripts.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scripts.test.mjs","names":[],"sources":["../../src/generators/scripts.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { generateScripts } from \"./scripts.js\";\n\ndescribe(\"generateScripts\", () => {\n\tit(\"generates all 5 expected scripts\", () => {\n\t\tconst result = generateScripts();\n\n\t\tconst expectedScripts = [\n\t\t\t\"scripts/start.sh\",\n\t\t\t\"scripts/stop.sh\",\n\t\t\t\"scripts/update.sh\",\n\t\t\t\"scripts/backup.sh\",\n\t\t\t\"scripts/status.sh\",\n\t\t];\n\n\t\tfor (const script of expectedScripts) {\n\t\t\texpect(result).toHaveProperty(script);\n\t\t\texpect(result[script]!.length).toBeGreaterThan(0);\n\t\t}\n\t});\n\n\tit(\"start.sh calls docker compose up\", () => {\n\t\tconst result = generateScripts();\n\t\texpect(result[\"scripts/start.sh\"]).toContain(\"docker compose\");\n\t\texpect(result[\"scripts/start.sh\"]).toContain(\"up\");\n\t});\n\n\tit(\"stop.sh calls docker compose down\", () => {\n\t\tconst result = generateScripts();\n\t\texpect(result[\"scripts/stop.sh\"]).toContain(\"docker compose\");\n\t\texpect(result[\"scripts/stop.sh\"]).toContain(\"down\");\n\t});\n\n\tit(\"update.sh calls docker compose pull\", () => {\n\t\tconst result = generateScripts();\n\t\texpect(result[\"scripts/update.sh\"]).toContain(\"docker compose\");\n\t\texpect(result[\"scripts/update.sh\"]).toContain(\"pull\");\n\t});\n\n\tit(\"backup.sh references volumes or backup\", () => {\n\t\tconst result = generateScripts();\n\t\tconst backup = result[\"scripts/backup.sh\"]!;\n\t\texpect(backup).toBeDefined();\n\t\texpect(backup.length).toBeGreaterThan(50);\n\t});\n\n\tit(\"status.sh calls docker compose ps\", () => {\n\t\tconst result = generateScripts();\n\t\texpect(result[\"scripts/status.sh\"]).toContain(\"docker compose\");\n\t\texpect(result[\"scripts/status.sh\"]).toContain(\"ps\");\n\t});\n\n\tit(\"all scripts start with bash shebang\", () => {\n\t\tconst result = generateScripts();\n\n\t\tfor (const [, content] of Object.entries(result)) {\n\t\t\texpect(content.startsWith(\"#!/\")).toBe(true);\n\t\t}\n\t});\n});\n"],"mappings":";;;;AAGA,SAAS,yBAAyB;AACjC,IAAG,0CAA0C;EAC5C,MAAM,SAAS,iBAAiB;AAUhC,OAAK,MAAM,UARa;GACvB;GACA;GACA;GACA;GACA;GACA,EAEqC;AACrC,gBAAO,OAAO,CAAC,eAAe,OAAO;AACrC,gBAAO,OAAO,QAAS,OAAO,CAAC,gBAAgB,EAAE;;GAEjD;AAEF,IAAG,0CAA0C;EAC5C,MAAM,SAAS,iBAAiB;AAChC,eAAO,OAAO,oBAAoB,CAAC,UAAU,iBAAiB;AAC9D,eAAO,OAAO,oBAAoB,CAAC,UAAU,KAAK;GACjD;AAEF,IAAG,2CAA2C;EAC7C,MAAM,SAAS,iBAAiB;AAChC,eAAO,OAAO,mBAAmB,CAAC,UAAU,iBAAiB;AAC7D,eAAO,OAAO,mBAAmB,CAAC,UAAU,OAAO;GAClD;AAEF,IAAG,6CAA6C;EAC/C,MAAM,SAAS,iBAAiB;AAChC,eAAO,OAAO,qBAAqB,CAAC,UAAU,iBAAiB;AAC/D,eAAO,OAAO,qBAAqB,CAAC,UAAU,OAAO;GACpD;AAEF,IAAG,gDAAgD;EAElD,MAAM,SADS,iBAAiB,CACV;AACtB,eAAO,OAAO,CAAC,aAAa;AAC5B,eAAO,OAAO,OAAO,CAAC,gBAAgB,GAAG;GACxC;AAEF,IAAG,2CAA2C;EAC7C,MAAM,SAAS,iBAAiB;AAChC,eAAO,OAAO,qBAAqB,CAAC,UAAU,iBAAiB;AAC/D,eAAO,OAAO,qBAAqB,CAAC,UAAU,KAAK;GAClD;AAEF,IAAG,6CAA6C;EAC/C,MAAM,SAAS,iBAAiB;AAEhC,OAAK,MAAM,GAAG,YAAY,OAAO,QAAQ,OAAO,CAC/C,cAAO,QAAQ,WAAW,MAAM,CAAC,CAAC,KAAK,KAAK;GAE5C;EACD"}
@@ -1,4 +1,4 @@
1
- import { n as describe, r as it, t as globalExpect } from "../vi.2VT5v0um-Qk6MgAnK.mjs";
1
+ import { n as describe, r as it, t as globalExpect } from "../vi.2VT5v0um-YSByewHe.mjs";
2
2
  import { resolve } from "../resolver.mjs";
3
3
  import { generateTraefikConfig } from "./traefik.mjs";
4
4
 
@@ -1 +1 @@
1
- {"version":3,"file":"traefik.test.mjs","names":[],"sources":["../../src/generators/traefik.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { generateTraefikConfig } from \"./traefik.js\";\nimport type { ResolverOutput } from \"../types.js\";\nimport { resolve } from \"../resolver.js\";\n\nfunction resolveWith(services: string[]): ResolverOutput {\n\treturn resolve({ services, skillPacks: [], proxy: \"traefik\", gpu: false });\n}\n\ndescribe(\"generateTraefikConfig\", () => {\n\tconst domain = \"example.com\";\n\n\tit(\"generates static config with domain email and entrypoints\", () => {\n\t\tconst resolved = resolveWith([\"redis\"]);\n\t\tconst { staticConfig } = generateTraefikConfig(resolved, domain);\n\n\t\texpect(staticConfig).toContain(\"admin@example.com\");\n\t\texpect(staticConfig).toContain('address: \":80\"');\n\t\texpect(staticConfig).toContain('address: \":443\"');\n\t\texpect(staticConfig).toContain(\"exposedByDefault: false\");\n\t\texpect(staticConfig).toContain(\"openclaw-network\");\n\t});\n\n\tit(\"generates labels for services with exposed ports\", () => {\n\t\tconst resolved = resolveWith([\"redis\"]);\n\t\tconst { serviceLabels } = generateTraefikConfig(resolved, domain);\n\n\t\tconst redisLabels = serviceLabels.get(\"redis\");\n\t\texpect(redisLabels).toBeDefined();\n\t\texpect(redisLabels![\"traefik.enable\"]).toBe(\"true\");\n\t\texpect(redisLabels![\"traefik.http.routers.redis.rule\"]).toBe(\n\t\t\t\"Host(`redis.example.com`)\",\n\t\t);\n\t\texpect(redisLabels![\"traefik.http.routers.redis.entrypoints\"]).toBe(\"websecure\");\n\t\texpect(redisLabels![\"traefik.http.routers.redis.tls.certresolver\"]).toBe(\"letsencrypt\");\n\t\texpect(redisLabels![\"traefik.http.services.redis.loadbalancer.server.port\"]).toBe(\"6379\");\n\t});\n\n\tit(\"generates HTTP to HTTPS redirect labels\", () => {\n\t\tconst resolved = resolveWith([\"redis\"]);\n\t\tconst { serviceLabels } = generateTraefikConfig(resolved, domain);\n\n\t\tconst redisLabels = serviceLabels.get(\"redis\")!;\n\t\texpect(redisLabels[\"traefik.http.routers.redis-http.entrypoints\"]).toBe(\"web\");\n\t\texpect(redisLabels[\"traefik.http.routers.redis-http.middlewares\"]).toBe(\n\t\t\t\"redirect-to-https\",\n\t\t);\n\t});\n\n\tit(\"assigns root domain to gateway\", () => {\n\t\tconst resolved = resolveWith([\"redis\"]);\n\t\tconst { serviceLabels } = generateTraefikConfig(resolved, domain);\n\n\t\tconst gwLabels = serviceLabels.get(\"openclaw-gateway\");\n\t\texpect(gwLabels).toBeDefined();\n\t\texpect(gwLabels![\"traefik.http.routers.gateway.rule\"]).toBe(\n\t\t\t\"Host(`example.com`)\",\n\t\t);\n\t\texpect(gwLabels![\"traefik.http.services.gateway.loadbalancer.server.port\"]).toBe(\"18789\");\n\t});\n\n\tit(\"adds global redirect middleware on traefik service\", () => {\n\t\tconst resolved = resolveWith([\"redis\"]);\n\t\tconst { serviceLabels } = generateTraefikConfig(resolved, domain);\n\n\t\tconst traefikLabels = serviceLabels.get(\"traefik\");\n\t\texpect(traefikLabels).toBeDefined();\n\t\texpect(\n\t\t\ttraefikLabels![\"traefik.http.middlewares.redirect-to-https.redirectscheme.scheme\"],\n\t\t).toBe(\"https\");\n\t\texpect(\n\t\t\ttraefikLabels![\"traefik.http.middlewares.redirect-to-https.redirectscheme.permanent\"],\n\t\t).toBe(\"true\");\n\t});\n\n\tit(\"skips proxy services and services without exposed ports\", () => {\n\t\tconst resolved = resolveWith([\"redis\", \"ffmpeg\"]);\n\t\tconst { serviceLabels } = generateTraefikConfig(resolved, domain);\n\n\t\t// ffmpeg has no exposed ports\n\t\texpect(serviceLabels.has(\"ffmpeg\")).toBe(false);\n\t\t// traefik itself is handled separately (not as a regular service)\n\t\texpect(serviceLabels.get(\"traefik\")![\"traefik.http.routers.traefik.rule\"]).toBeUndefined();\n\t});\n\n\tit(\"sanitizes service names with hyphens in router names\", () => {\n\t\tconst resolved = resolveWith([\"open-webui\"]);\n\t\tconst { serviceLabels } = generateTraefikConfig(resolved, domain);\n\n\t\tconst labels = serviceLabels.get(\"open-webui\");\n\t\texpect(labels).toBeDefined();\n\t\t// Router name has hyphens removed\n\t\texpect(labels![\"traefik.http.routers.openwebui.rule\"]).toBe(\n\t\t\t\"Host(`open-webui.example.com`)\",\n\t\t);\n\t});\n});\n"],"mappings":";;;;;AAKA,SAAS,YAAY,UAAoC;AACxD,QAAO,QAAQ;EAAE;EAAU,YAAY,EAAE;EAAE,OAAO;EAAW,KAAK;EAAO,CAAC;;AAG3E,SAAS,+BAA+B;CACvC,MAAM,SAAS;AAEf,IAAG,mEAAmE;EAErE,MAAM,EAAE,iBAAiB,sBADR,YAAY,CAAC,QAAQ,CAAC,EACkB,OAAO;AAEhE,eAAO,aAAa,CAAC,UAAU,oBAAoB;AACnD,eAAO,aAAa,CAAC,UAAU,mBAAiB;AAChD,eAAO,aAAa,CAAC,UAAU,oBAAkB;AACjD,eAAO,aAAa,CAAC,UAAU,0BAA0B;AACzD,eAAO,aAAa,CAAC,UAAU,mBAAmB;GACjD;AAEF,IAAG,0DAA0D;EAE5D,MAAM,EAAE,kBAAkB,sBADT,YAAY,CAAC,QAAQ,CAAC,EACmB,OAAO;EAEjE,MAAM,cAAc,cAAc,IAAI,QAAQ;AAC9C,eAAO,YAAY,CAAC,aAAa;AACjC,eAAO,YAAa,kBAAkB,CAAC,KAAK,OAAO;AACnD,eAAO,YAAa,mCAAmC,CAAC,KACvD,4BACA;AACD,eAAO,YAAa,0CAA0C,CAAC,KAAK,YAAY;AAChF,eAAO,YAAa,+CAA+C,CAAC,KAAK,cAAc;AACvF,eAAO,YAAa,wDAAwD,CAAC,KAAK,OAAO;GACxF;AAEF,IAAG,iDAAiD;EAEnD,MAAM,EAAE,kBAAkB,sBADT,YAAY,CAAC,QAAQ,CAAC,EACmB,OAAO;EAEjE,MAAM,cAAc,cAAc,IAAI,QAAQ;AAC9C,eAAO,YAAY,+CAA+C,CAAC,KAAK,MAAM;AAC9E,eAAO,YAAY,+CAA+C,CAAC,KAClE,oBACA;GACA;AAEF,IAAG,wCAAwC;EAE1C,MAAM,EAAE,kBAAkB,sBADT,YAAY,CAAC,QAAQ,CAAC,EACmB,OAAO;EAEjE,MAAM,WAAW,cAAc,IAAI,mBAAmB;AACtD,eAAO,SAAS,CAAC,aAAa;AAC9B,eAAO,SAAU,qCAAqC,CAAC,KACtD,sBACA;AACD,eAAO,SAAU,0DAA0D,CAAC,KAAK,QAAQ;GACxF;AAEF,IAAG,4DAA4D;EAE9D,MAAM,EAAE,kBAAkB,sBADT,YAAY,CAAC,QAAQ,CAAC,EACmB,OAAO;EAEjE,MAAM,gBAAgB,cAAc,IAAI,UAAU;AAClD,eAAO,cAAc,CAAC,aAAa;AACnC,eACC,cAAe,oEACf,CAAC,KAAK,QAAQ;AACf,eACC,cAAe,uEACf,CAAC,KAAK,OAAO;GACb;AAEF,IAAG,iEAAiE;EAEnE,MAAM,EAAE,kBAAkB,sBADT,YAAY,CAAC,SAAS,SAAS,CAAC,EACS,OAAO;AAGjE,eAAO,cAAc,IAAI,SAAS,CAAC,CAAC,KAAK,MAAM;AAE/C,eAAO,cAAc,IAAI,UAAU,CAAE,qCAAqC,CAAC,eAAe;GACzF;AAEF,IAAG,8DAA8D;EAEhE,MAAM,EAAE,kBAAkB,sBADT,YAAY,CAAC,aAAa,CAAC,EACc,OAAO;EAEjE,MAAM,SAAS,cAAc,IAAI,aAAa;AAC9C,eAAO,OAAO,CAAC,aAAa;AAE5B,eAAO,OAAQ,uCAAuC,CAAC,KACtD,iCACA;GACA;EACD"}
1
+ {"version":3,"file":"traefik.test.mjs","names":[],"sources":["../../src/generators/traefik.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { resolve } from \"../resolver.js\";\nimport type { ResolverOutput } from \"../types.js\";\nimport { generateTraefikConfig } from \"./traefik.js\";\n\nfunction resolveWith(services: string[]): ResolverOutput {\n\treturn resolve({ services, skillPacks: [], proxy: \"traefik\", gpu: false });\n}\n\ndescribe(\"generateTraefikConfig\", () => {\n\tconst domain = \"example.com\";\n\n\tit(\"generates static config with domain email and entrypoints\", () => {\n\t\tconst resolved = resolveWith([\"redis\"]);\n\t\tconst { staticConfig } = generateTraefikConfig(resolved, domain);\n\n\t\texpect(staticConfig).toContain(\"admin@example.com\");\n\t\texpect(staticConfig).toContain('address: \":80\"');\n\t\texpect(staticConfig).toContain('address: \":443\"');\n\t\texpect(staticConfig).toContain(\"exposedByDefault: false\");\n\t\texpect(staticConfig).toContain(\"openclaw-network\");\n\t});\n\n\tit(\"generates labels for services with exposed ports\", () => {\n\t\tconst resolved = resolveWith([\"redis\"]);\n\t\tconst { serviceLabels } = generateTraefikConfig(resolved, domain);\n\n\t\tconst redisLabels = serviceLabels.get(\"redis\");\n\t\texpect(redisLabels).toBeDefined();\n\t\texpect(redisLabels![\"traefik.enable\"]).toBe(\"true\");\n\t\texpect(redisLabels![\"traefik.http.routers.redis.rule\"]).toBe(\"Host(`redis.example.com`)\");\n\t\texpect(redisLabels![\"traefik.http.routers.redis.entrypoints\"]).toBe(\"websecure\");\n\t\texpect(redisLabels![\"traefik.http.routers.redis.tls.certresolver\"]).toBe(\"letsencrypt\");\n\t\texpect(redisLabels![\"traefik.http.services.redis.loadbalancer.server.port\"]).toBe(\"6379\");\n\t});\n\n\tit(\"generates HTTP to HTTPS redirect labels\", () => {\n\t\tconst resolved = resolveWith([\"redis\"]);\n\t\tconst { serviceLabels } = generateTraefikConfig(resolved, domain);\n\n\t\tconst redisLabels = serviceLabels.get(\"redis\")!;\n\t\texpect(redisLabels[\"traefik.http.routers.redis-http.entrypoints\"]).toBe(\"web\");\n\t\texpect(redisLabels[\"traefik.http.routers.redis-http.middlewares\"]).toBe(\"redirect-to-https\");\n\t});\n\n\tit(\"assigns root domain to gateway\", () => {\n\t\tconst resolved = resolveWith([\"redis\"]);\n\t\tconst { serviceLabels } = generateTraefikConfig(resolved, domain);\n\n\t\tconst gwLabels = serviceLabels.get(\"openclaw-gateway\");\n\t\texpect(gwLabels).toBeDefined();\n\t\texpect(gwLabels![\"traefik.http.routers.gateway.rule\"]).toBe(\"Host(`example.com`)\");\n\t\texpect(gwLabels![\"traefik.http.services.gateway.loadbalancer.server.port\"]).toBe(\"18789\");\n\t});\n\n\tit(\"adds global redirect middleware on traefik service\", () => {\n\t\tconst resolved = resolveWith([\"redis\"]);\n\t\tconst { serviceLabels } = generateTraefikConfig(resolved, domain);\n\n\t\tconst traefikLabels = serviceLabels.get(\"traefik\");\n\t\texpect(traefikLabels).toBeDefined();\n\t\texpect(traefikLabels![\"traefik.http.middlewares.redirect-to-https.redirectscheme.scheme\"]).toBe(\n\t\t\t\"https\",\n\t\t);\n\t\texpect(\n\t\t\ttraefikLabels![\"traefik.http.middlewares.redirect-to-https.redirectscheme.permanent\"],\n\t\t).toBe(\"true\");\n\t});\n\n\tit(\"skips proxy services and services without exposed ports\", () => {\n\t\tconst resolved = resolveWith([\"redis\", \"ffmpeg\"]);\n\t\tconst { serviceLabels } = generateTraefikConfig(resolved, domain);\n\n\t\t// ffmpeg has no exposed ports\n\t\texpect(serviceLabels.has(\"ffmpeg\")).toBe(false);\n\t\t// traefik itself is handled separately (not as a regular service)\n\t\texpect(serviceLabels.get(\"traefik\")![\"traefik.http.routers.traefik.rule\"]).toBeUndefined();\n\t});\n\n\tit(\"sanitizes service names with hyphens in router names\", () => {\n\t\tconst resolved = resolveWith([\"open-webui\"]);\n\t\tconst { serviceLabels } = generateTraefikConfig(resolved, domain);\n\n\t\tconst labels = serviceLabels.get(\"open-webui\");\n\t\texpect(labels).toBeDefined();\n\t\t// Router name has hyphens removed\n\t\texpect(labels![\"traefik.http.routers.openwebui.rule\"]).toBe(\"Host(`open-webui.example.com`)\");\n\t});\n});\n"],"mappings":";;;;;AAKA,SAAS,YAAY,UAAoC;AACxD,QAAO,QAAQ;EAAE;EAAU,YAAY,EAAE;EAAE,OAAO;EAAW,KAAK;EAAO,CAAC;;AAG3E,SAAS,+BAA+B;CACvC,MAAM,SAAS;AAEf,IAAG,mEAAmE;EAErE,MAAM,EAAE,iBAAiB,sBADR,YAAY,CAAC,QAAQ,CAAC,EACkB,OAAO;AAEhE,eAAO,aAAa,CAAC,UAAU,oBAAoB;AACnD,eAAO,aAAa,CAAC,UAAU,mBAAiB;AAChD,eAAO,aAAa,CAAC,UAAU,oBAAkB;AACjD,eAAO,aAAa,CAAC,UAAU,0BAA0B;AACzD,eAAO,aAAa,CAAC,UAAU,mBAAmB;GACjD;AAEF,IAAG,0DAA0D;EAE5D,MAAM,EAAE,kBAAkB,sBADT,YAAY,CAAC,QAAQ,CAAC,EACmB,OAAO;EAEjE,MAAM,cAAc,cAAc,IAAI,QAAQ;AAC9C,eAAO,YAAY,CAAC,aAAa;AACjC,eAAO,YAAa,kBAAkB,CAAC,KAAK,OAAO;AACnD,eAAO,YAAa,mCAAmC,CAAC,KAAK,4BAA4B;AACzF,eAAO,YAAa,0CAA0C,CAAC,KAAK,YAAY;AAChF,eAAO,YAAa,+CAA+C,CAAC,KAAK,cAAc;AACvF,eAAO,YAAa,wDAAwD,CAAC,KAAK,OAAO;GACxF;AAEF,IAAG,iDAAiD;EAEnD,MAAM,EAAE,kBAAkB,sBADT,YAAY,CAAC,QAAQ,CAAC,EACmB,OAAO;EAEjE,MAAM,cAAc,cAAc,IAAI,QAAQ;AAC9C,eAAO,YAAY,+CAA+C,CAAC,KAAK,MAAM;AAC9E,eAAO,YAAY,+CAA+C,CAAC,KAAK,oBAAoB;GAC3F;AAEF,IAAG,wCAAwC;EAE1C,MAAM,EAAE,kBAAkB,sBADT,YAAY,CAAC,QAAQ,CAAC,EACmB,OAAO;EAEjE,MAAM,WAAW,cAAc,IAAI,mBAAmB;AACtD,eAAO,SAAS,CAAC,aAAa;AAC9B,eAAO,SAAU,qCAAqC,CAAC,KAAK,sBAAsB;AAClF,eAAO,SAAU,0DAA0D,CAAC,KAAK,QAAQ;GACxF;AAEF,IAAG,4DAA4D;EAE9D,MAAM,EAAE,kBAAkB,sBADT,YAAY,CAAC,QAAQ,CAAC,EACmB,OAAO;EAEjE,MAAM,gBAAgB,cAAc,IAAI,UAAU;AAClD,eAAO,cAAc,CAAC,aAAa;AACnC,eAAO,cAAe,oEAAoE,CAAC,KAC1F,QACA;AACD,eACC,cAAe,uEACf,CAAC,KAAK,OAAO;GACb;AAEF,IAAG,iEAAiE;EAEnE,MAAM,EAAE,kBAAkB,sBADT,YAAY,CAAC,SAAS,SAAS,CAAC,EACS,OAAO;AAGjE,eAAO,cAAc,IAAI,SAAS,CAAC,CAAC,KAAK,MAAM;AAE/C,eAAO,cAAc,IAAI,UAAU,CAAE,qCAAqC,CAAC,eAAe;GACzF;AAEF,IAAG,8DAA8D;EAEhE,MAAM,EAAE,kBAAkB,sBADT,YAAY,CAAC,aAAa,CAAC,EACc,OAAO;EAEjE,MAAM,SAAS,cAAc,IAAI,aAAa;AAC9C,eAAO,OAAO,CAAC,aAAa;AAE5B,eAAO,OAAQ,uCAAuC,CAAC,KAAK,iCAAiC;GAC5F;EACD"}
package/dist/index.d.mts CHANGED
@@ -7,17 +7,19 @@ import { generate, generateServicesDoc } from "./generate.mjs";
7
7
  import { generateCaddyfile } from "./generators/caddy.mjs";
8
8
  import { EnvVarGroup, generateEnvFiles, getStructuredEnvVars } from "./generators/env.mjs";
9
9
  import { generateGrafanaConfig, generateGrafanaDashboard } from "./generators/grafana.mjs";
10
+ import { generateHealthCheck } from "./generators/health-check.mjs";
10
11
  import { generateN8nWorkflows } from "./generators/n8n-workflows.mjs";
11
12
  import { generatePostgresInit, getDbRequirements } from "./generators/postgres-init.mjs";
12
13
  import { generatePrometheusConfig } from "./generators/prometheus.mjs";
13
14
  import { generateReadme } from "./generators/readme.mjs";
14
15
  import { generateScripts } from "./generators/scripts.mjs";
15
16
  import { generateSkillFiles } from "./generators/skills.mjs";
17
+ import { CURRENT_CONFIG_VERSION, migrateConfig, needsMigration } from "./migrations.mjs";
16
18
  import { getAllPresets, getPresetById, presetRegistry } from "./presets/registry.mjs";
17
19
  import { resolve } from "./resolver.mjs";
18
20
  import { getAllServices, getServiceById, getServicesByCategory, serviceRegistry } from "./services/registry.mjs";
19
21
  import { getAllSkillPacks, getCompatibleSkillPacks, getSkillPackById, skillPackRegistry } from "./skills/registry.mjs";
22
+ import { SkillManifestEntry, getAllManifestSkills, getManifestSkillById, getManifestSkillCount } from "./skills/skill-manifest.mjs";
20
23
  import { validate } from "./validator.mjs";
21
- import { CURRENT_CONFIG_VERSION, migrateConfig, needsMigration } from "./migrations.mjs";
22
24
  import { checkCompatibility, getImageReference, getImageTag, pinImageTags } from "./version-manager.mjs";
23
- export { type AddedDependency, AddedDependencySchema, type ApiError, ApiErrorSchema, CURRENT_CONFIG_VERSION, type CategoryInfo, type ComposeOptions, ComposeOptionsSchema, type ComposeResult, type Deploy, DeploySchema, type DeploymentTarget, DeploymentTargetSchema, type DeploymentType, DeploymentTypeSchema, type EnvVarGroup, type EnvVariable, EnvVariableSchema, ErrorSchema, type GeneratedFiles, type GenerationInput, GenerationInputSchema, type GenerationMetadata, type GenerationResult, type HealthCheck, HealthCheckSchema, type Maturity, MaturitySchema, type NativePlatform, NativePlatformSchema, type NativeRecipe, NativeRecipeSchema, type OutputFormat, OutputFormatSchema, type Platform, PlatformSchema, type PortMapping, PortMappingSchema, type Preset, PresetSchema, type ProxyType, ProxyTypeSchema, type ResolvedService, ResolvedServiceSchema, type ResolverError, type ResolverInput, type ResolverOutput, ResolverOutputSchema, type ResourceLimits, ResourceLimitsSchema, type RestartPolicy, RestartPolicySchema, SERVICE_CATEGORIES, type ServiceCategory, ServiceCategorySchema, type ServiceDefinition, ServiceDefinitionSchema, type SkillBinding, SkillBindingSchema, type SkillPack, SkillPackSchema, StackConfigError, type ValidateRequest, ValidateRequestSchema, type ValidateResponse, ValidateResponseSchema, ValidationError, type VolumeMapping, VolumeMappingSchema, type Warning, WarningSchema, checkCompatibility, compose, composeMultiFile, generate, generateCaddyfile, generateEnvFiles, generateGrafanaConfig, generateGrafanaDashboard, generateN8nWorkflows, generatePostgresInit, generatePrometheusConfig, generateReadme, generateScripts, generateServicesDoc, generateSkillFiles, getAllPresets, getAllServices, getAllSkillPacks, getCompatibleSkillPacks, getDbRequirements, getImageReference, getImageTag, getPresetById, getServiceById, getServicesByCategory, getSkillPackById, getStructuredEnvVars, migrateConfig, needsMigration, partitionBareMetal, pinImageTags, platformToNativePlatform, presetRegistry, resolve, resolvedWithOnlyServices, serviceRegistry, skillPackRegistry, validate };
25
+ export { type AddedDependency, AddedDependencySchema, type ApiError, ApiErrorSchema, CURRENT_CONFIG_VERSION, type CategoryInfo, type ComposeOptions, ComposeOptionsSchema, type ComposeResult, type Deploy, DeploySchema, type DeploymentTarget, DeploymentTargetSchema, type DeploymentType, DeploymentTypeSchema, type EnvVarGroup, type EnvVariable, EnvVariableSchema, ErrorSchema, type GeneratedFiles, type GenerationInput, GenerationInputSchema, type GenerationMetadata, type GenerationResult, type HealthCheck, HealthCheckSchema, type Maturity, MaturitySchema, type NativePlatform, NativePlatformSchema, type NativeRecipe, NativeRecipeSchema, type OutputFormat, OutputFormatSchema, type Platform, PlatformSchema, type PortMapping, PortMappingSchema, type Preset, PresetSchema, type ProxyType, ProxyTypeSchema, type ResolvedService, ResolvedServiceSchema, type ResolverError, type ResolverInput, type ResolverOutput, ResolverOutputSchema, type ResourceLimits, ResourceLimitsSchema, type RestartPolicy, RestartPolicySchema, SERVICE_CATEGORIES, type ServiceCategory, ServiceCategorySchema, type ServiceDefinition, ServiceDefinitionSchema, type SkillBinding, SkillBindingSchema, type SkillManifestEntry, type SkillPack, SkillPackSchema, StackConfigError, type ValidateRequest, ValidateRequestSchema, type ValidateResponse, ValidateResponseSchema, ValidationError, type VolumeMapping, VolumeMappingSchema, type Warning, WarningSchema, checkCompatibility, compose, composeMultiFile, generate, generateCaddyfile, generateEnvFiles, generateGrafanaConfig, generateGrafanaDashboard, generateHealthCheck, generateN8nWorkflows, generatePostgresInit, generatePrometheusConfig, generateReadme, generateScripts, generateServicesDoc, generateSkillFiles, getAllManifestSkills, getAllPresets, getAllServices, getAllSkillPacks, getCompatibleSkillPacks, getDbRequirements, getImageReference, getImageTag, getManifestSkillById, getManifestSkillCount, getPresetById, getServiceById, getServicesByCategory, getSkillPackById, getStructuredEnvVars, migrateConfig, needsMigration, partitionBareMetal, pinImageTags, platformToNativePlatform, presetRegistry, resolve, resolvedWithOnlyServices, serviceRegistry, skillPackRegistry, validate };
package/dist/index.mjs CHANGED
@@ -8,6 +8,7 @@ import { generateCaddyfile } from "./generators/caddy.mjs";
8
8
  import { generatePostgresInit, getDbRequirements } from "./generators/postgres-init.mjs";
9
9
  import { generateEnvFiles, getStructuredEnvVars } from "./generators/env.mjs";
10
10
  import { generateGrafanaConfig, generateGrafanaDashboard } from "./generators/grafana.mjs";
11
+ import { generateHealthCheck } from "./generators/health-check.mjs";
11
12
  import { generateN8nWorkflows } from "./generators/n8n-workflows.mjs";
12
13
  import { generatePrometheusConfig } from "./generators/prometheus.mjs";
13
14
  import { generateReadme } from "./generators/readme.mjs";
@@ -18,7 +19,8 @@ import { validate } from "./validator.mjs";
18
19
  import { generate, generateServicesDoc } from "./generate.mjs";
19
20
  import { getAllPresets, getPresetById, presetRegistry } from "./presets/registry.mjs";
20
21
  import { AddedDependencySchema, ApiErrorSchema, ComposeOptionsSchema, DeploySchema, DeploymentTargetSchema, DeploymentTypeSchema, EnvVariableSchema, ErrorSchema, GenerationInputSchema, HealthCheckSchema, MaturitySchema, NativePlatformSchema, NativeRecipeSchema, OutputFormatSchema, PlatformSchema, PortMappingSchema, PresetSchema, ProxyTypeSchema, ResolvedServiceSchema, ResolverOutputSchema, ResourceLimitsSchema, RestartPolicySchema, ServiceCategorySchema, ServiceDefinitionSchema, SkillBindingSchema, SkillPackSchema, ValidateRequestSchema, ValidateResponseSchema, VolumeMappingSchema, WarningSchema } from "./schema.mjs";
22
+ import { getAllManifestSkills, getManifestSkillById, getManifestSkillCount } from "./skills/skill-manifest.mjs";
21
23
  import { SERVICE_CATEGORIES } from "./types.mjs";
22
24
  import { checkCompatibility, getImageReference, getImageTag, pinImageTags } from "./version-manager.mjs";
23
25
 
24
- export { AddedDependencySchema, ApiErrorSchema, CURRENT_CONFIG_VERSION, ComposeOptionsSchema, DeploySchema, DeploymentTargetSchema, DeploymentTypeSchema, EnvVariableSchema, ErrorSchema, GenerationInputSchema, HealthCheckSchema, MaturitySchema, NativePlatformSchema, NativeRecipeSchema, OutputFormatSchema, PlatformSchema, PortMappingSchema, PresetSchema, ProxyTypeSchema, ResolvedServiceSchema, ResolverOutputSchema, ResourceLimitsSchema, RestartPolicySchema, SERVICE_CATEGORIES, ServiceCategorySchema, ServiceDefinitionSchema, SkillBindingSchema, SkillPackSchema, StackConfigError, ValidateRequestSchema, ValidateResponseSchema, ValidationError, VolumeMappingSchema, WarningSchema, checkCompatibility, compose, composeMultiFile, generate, generateCaddyfile, generateEnvFiles, generateGrafanaConfig, generateGrafanaDashboard, generateN8nWorkflows, generatePostgresInit, generatePrometheusConfig, generateReadme, generateScripts, generateServicesDoc, generateSkillFiles, getAllPresets, getAllServices, getAllSkillPacks, getCompatibleSkillPacks, getDbRequirements, getImageReference, getImageTag, getPresetById, getServiceById, getServicesByCategory, getSkillPackById, getStructuredEnvVars, migrateConfig, needsMigration, partitionBareMetal, pinImageTags, platformToNativePlatform, presetRegistry, resolve, resolvedWithOnlyServices, serviceRegistry, skillPackRegistry, validate };
26
+ export { AddedDependencySchema, ApiErrorSchema, CURRENT_CONFIG_VERSION, ComposeOptionsSchema, DeploySchema, DeploymentTargetSchema, DeploymentTypeSchema, EnvVariableSchema, ErrorSchema, GenerationInputSchema, HealthCheckSchema, MaturitySchema, NativePlatformSchema, NativeRecipeSchema, OutputFormatSchema, PlatformSchema, PortMappingSchema, PresetSchema, ProxyTypeSchema, ResolvedServiceSchema, ResolverOutputSchema, ResourceLimitsSchema, RestartPolicySchema, SERVICE_CATEGORIES, ServiceCategorySchema, ServiceDefinitionSchema, SkillBindingSchema, SkillPackSchema, StackConfigError, ValidateRequestSchema, ValidateResponseSchema, ValidationError, VolumeMappingSchema, WarningSchema, checkCompatibility, compose, composeMultiFile, generate, generateCaddyfile, generateEnvFiles, generateGrafanaConfig, generateGrafanaDashboard, generateHealthCheck, generateN8nWorkflows, generatePostgresInit, generatePrometheusConfig, generateReadme, generateScripts, generateServicesDoc, generateSkillFiles, getAllManifestSkills, getAllPresets, getAllServices, getAllSkillPacks, getCompatibleSkillPacks, getDbRequirements, getImageReference, getImageTag, getManifestSkillById, getManifestSkillCount, getPresetById, getServiceById, getServicesByCategory, getSkillPackById, getStructuredEnvVars, migrateConfig, needsMigration, partitionBareMetal, pinImageTags, platformToNativePlatform, presetRegistry, resolve, resolvedWithOnlyServices, serviceRegistry, skillPackRegistry, validate };
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.mts","names":[],"sources":["../src/migrations.ts"],"mappings":";cAEa,sBAAA;AAAb;;;;AAAA,iBAiBgB,aAAA,CAAc,KAAA,EAAO,MAAA,oBAA0B,MAAA;AAA/D;;;AAAA,iBA0BgB,cAAA,CAAe,KAAA,EAAO,MAAA"}
1
+ {"version":3,"file":"migrations.d.mts","names":[],"sources":["../src/migrations.ts"],"mappings":";cACa,sBAAA;AAAb;;;;AAAA,iBAiBgB,aAAA,CAAc,KAAA,EAAO,MAAA,oBAA0B,MAAA;AAA/D;;;AAAA,iBAwBgB,cAAA,CAAe,KAAA,EAAO,MAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.mjs","names":[],"sources":["../src/migrations.ts"],"sourcesContent":["import type { GenerationInput } from \"./types.js\";\n\nexport const CURRENT_CONFIG_VERSION = 2;\n\ntype MigrationFn = (input: Record<string, unknown>) => Record<string, unknown>;\n\nconst migrations: Record<number, MigrationFn> = {\n\t// v1 → v2: ensure deploymentType field exists (defaulting to \"docker\")\n\t1: (input) => ({\n\t\t...input,\n\t\tconfigVersion: 2,\n\t\tdeploymentType: (input.deploymentType as string) ?? \"docker\",\n\t}),\n};\n\n/**\n * Applies sequential migrations to bring a config from its current version\n * to CURRENT_CONFIG_VERSION. Returns the config unchanged if already current.\n */\nexport function migrateConfig(input: Record<string, unknown>): Record<string, unknown> {\n\tlet version = (input.configVersion as number) ?? 1;\n\n\tif (version > CURRENT_CONFIG_VERSION) {\n\t\tthrow new Error(\n\t\t\t`No migration path from config version ${version}`,\n\t\t);\n\t}\n\n\tlet current = { ...input };\n\n\twhile (version < CURRENT_CONFIG_VERSION) {\n\t\tconst migrationFn = migrations[version];\n\t\tif (!migrationFn) {\n\t\t\tthrow new Error(`No migration path from config version ${version}`);\n\t\t}\n\t\tcurrent = migrationFn(current);\n\t\tversion++;\n\t}\n\n\treturn current;\n}\n\n/**\n * Returns true if the config needs migration to the current version.\n */\nexport function needsMigration(input: Record<string, unknown>): boolean {\n\tconst version = (input.configVersion as number) ?? 1;\n\treturn version < CURRENT_CONFIG_VERSION;\n}\n"],"mappings":";AAEA,MAAa,yBAAyB;AAItC,MAAM,aAA0C,EAE/C,IAAI,WAAW;CACd,GAAG;CACH,eAAe;CACf,gBAAiB,MAAM,kBAA6B;CACpD,GACD;;;;;AAMD,SAAgB,cAAc,OAAyD;CACtF,IAAI,UAAW,MAAM,iBAA4B;AAEjD,KAAI,UAAU,uBACb,OAAM,IAAI,MACT,yCAAyC,UACzC;CAGF,IAAI,UAAU,EAAE,GAAG,OAAO;AAE1B,QAAO,UAAU,wBAAwB;EACxC,MAAM,cAAc,WAAW;AAC/B,MAAI,CAAC,YACJ,OAAM,IAAI,MAAM,yCAAyC,UAAU;AAEpE,YAAU,YAAY,QAAQ;AAC9B;;AAGD,QAAO;;;;;AAMR,SAAgB,eAAe,OAAyC;AAEvE,SADiB,MAAM,iBAA4B,KAClC"}
1
+ {"version":3,"file":"migrations.mjs","names":[],"sources":["../src/migrations.ts"],"sourcesContent":["\nexport const CURRENT_CONFIG_VERSION = 2;\n\ntype MigrationFn = (input: Record<string, unknown>) => Record<string, unknown>;\n\nconst migrations: Record<number, MigrationFn> = {\n\t// v1 → v2: ensure deploymentType field exists (defaulting to \"docker\")\n\t1: (input) => ({\n\t\t...input,\n\t\tconfigVersion: 2,\n\t\tdeploymentType: (input.deploymentType as string) ?? \"docker\",\n\t}),\n};\n\n/**\n * Applies sequential migrations to bring a config from its current version\n * to CURRENT_CONFIG_VERSION. Returns the config unchanged if already current.\n */\nexport function migrateConfig(input: Record<string, unknown>): Record<string, unknown> {\n\tlet version = (input.configVersion as number) ?? 1;\n\n\tif (version > CURRENT_CONFIG_VERSION) {\n\t\tthrow new Error(`No migration path from config version ${version}`);\n\t}\n\n\tlet current = { ...input };\n\n\twhile (version < CURRENT_CONFIG_VERSION) {\n\t\tconst migrationFn = migrations[version];\n\t\tif (!migrationFn) {\n\t\t\tthrow new Error(`No migration path from config version ${version}`);\n\t\t}\n\t\tcurrent = migrationFn(current);\n\t\tversion++;\n\t}\n\n\treturn current;\n}\n\n/**\n * Returns true if the config needs migration to the current version.\n */\nexport function needsMigration(input: Record<string, unknown>): boolean {\n\tconst version = (input.configVersion as number) ?? 1;\n\treturn version < CURRENT_CONFIG_VERSION;\n}\n"],"mappings":";AACA,MAAa,yBAAyB;AAItC,MAAM,aAA0C,EAE/C,IAAI,WAAW;CACd,GAAG;CACH,eAAe;CACf,gBAAiB,MAAM,kBAA6B;CACpD,GACD;;;;;AAMD,SAAgB,cAAc,OAAyD;CACtF,IAAI,UAAW,MAAM,iBAA4B;AAEjD,KAAI,UAAU,uBACb,OAAM,IAAI,MAAM,yCAAyC,UAAU;CAGpE,IAAI,UAAU,EAAE,GAAG,OAAO;AAE1B,QAAO,UAAU,wBAAwB;EACxC,MAAM,cAAc,WAAW;AAC/B,MAAI,CAAC,YACJ,OAAM,IAAI,MAAM,yCAAyC,UAAU;AAEpE,YAAU,YAAY,QAAQ;AAC9B;;AAGD,QAAO;;;;;AAMR,SAAgB,eAAe,OAAyC;AAEvE,SADiB,MAAM,iBAA4B,KAClC"}
@@ -1,4 +1,4 @@
1
- import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-Qk6MgAnK.mjs";
1
+ import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-YSByewHe.mjs";
2
2
  import { CURRENT_CONFIG_VERSION, migrateConfig, needsMigration } from "./migrations.mjs";
3
3
 
4
4
  //#region src/migrations.test.ts
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.test.mjs","names":[],"sources":["../src/migrations.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { migrateConfig, needsMigration, CURRENT_CONFIG_VERSION } from \"./migrations.js\";\n\ndescribe(\"config migrations\", () => {\n\tit(\"migrates v1 config to current version\", () => {\n\t\tconst v1 = { projectName: \"test\", services: [\"redis\"], skillPacks: [] };\n\t\tconst result = migrateConfig(v1);\n\t\texpect(result.configVersion).toBe(CURRENT_CONFIG_VERSION);\n\t\texpect(result.deploymentType).toBe(\"docker\");\n\t});\n\n\tit(\"preserves existing deploymentType during migration\", () => {\n\t\tconst v1 = { configVersion: 1, deploymentType: \"bare-metal\" };\n\t\tconst result = migrateConfig(v1);\n\t\texpect(result.deploymentType).toBe(\"bare-metal\");\n\t});\n\n\tit(\"passes through current version unchanged\", () => {\n\t\tconst current = { configVersion: CURRENT_CONFIG_VERSION, projectName: \"test\" };\n\t\tconst result = migrateConfig(current);\n\t\texpect(result).toEqual(current);\n\t});\n\n\tit(\"needsMigration returns true for old configs\", () => {\n\t\texpect(needsMigration({ configVersion: 1 })).toBe(true);\n\t\texpect(needsMigration({})).toBe(true); // no version = v1\n\t});\n\n\tit(\"needsMigration returns false for current configs\", () => {\n\t\texpect(needsMigration({ configVersion: CURRENT_CONFIG_VERSION })).toBe(false);\n\t});\n\n\tit(\"throws for unknown version with no migration path\", () => {\n\t\texpect(() => migrateConfig({ configVersion: 99 })).toThrow(\"No migration path\");\n\t});\n});\n"],"mappings":";;;;AAGA,SAAS,2BAA2B;AACnC,IAAG,+CAA+C;EAEjD,MAAM,SAAS,cADJ;GAAE,aAAa;GAAQ,UAAU,CAAC,QAAQ;GAAE,YAAY,EAAE;GAAE,CACvC;AAChC,eAAO,OAAO,cAAc,CAAC,KAAK,uBAAuB;AACzD,eAAO,OAAO,eAAe,CAAC,KAAK,SAAS;GAC3C;AAEF,IAAG,4DAA4D;AAG9D,eADe,cADJ;GAAE,eAAe;GAAG,gBAAgB;GAAc,CAC7B,CAClB,eAAe,CAAC,KAAK,aAAa;GAC/C;AAEF,IAAG,kDAAkD;EACpD,MAAM,UAAU;GAAE,eAAe;GAAwB,aAAa;GAAQ;AAE9E,eADe,cAAc,QAAQ,CACvB,CAAC,QAAQ,QAAQ;GAC9B;AAEF,IAAG,qDAAqD;AACvD,eAAO,eAAe,EAAE,eAAe,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK;AACvD,eAAO,eAAe,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK;GACpC;AAEF,IAAG,0DAA0D;AAC5D,eAAO,eAAe,EAAE,eAAe,wBAAwB,CAAC,CAAC,CAAC,KAAK,MAAM;GAC5E;AAEF,IAAG,2DAA2D;AAC7D,qBAAa,cAAc,EAAE,eAAe,IAAI,CAAC,CAAC,CAAC,QAAQ,oBAAoB;GAC9E;EACD"}
1
+ {"version":3,"file":"migrations.test.mjs","names":[],"sources":["../src/migrations.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { CURRENT_CONFIG_VERSION, migrateConfig, needsMigration } from \"./migrations.js\";\n\ndescribe(\"config migrations\", () => {\n\tit(\"migrates v1 config to current version\", () => {\n\t\tconst v1 = { projectName: \"test\", services: [\"redis\"], skillPacks: [] };\n\t\tconst result = migrateConfig(v1);\n\t\texpect(result.configVersion).toBe(CURRENT_CONFIG_VERSION);\n\t\texpect(result.deploymentType).toBe(\"docker\");\n\t});\n\n\tit(\"preserves existing deploymentType during migration\", () => {\n\t\tconst v1 = { configVersion: 1, deploymentType: \"bare-metal\" };\n\t\tconst result = migrateConfig(v1);\n\t\texpect(result.deploymentType).toBe(\"bare-metal\");\n\t});\n\n\tit(\"passes through current version unchanged\", () => {\n\t\tconst current = { configVersion: CURRENT_CONFIG_VERSION, projectName: \"test\" };\n\t\tconst result = migrateConfig(current);\n\t\texpect(result).toEqual(current);\n\t});\n\n\tit(\"needsMigration returns true for old configs\", () => {\n\t\texpect(needsMigration({ configVersion: 1 })).toBe(true);\n\t\texpect(needsMigration({})).toBe(true); // no version = v1\n\t});\n\n\tit(\"needsMigration returns false for current configs\", () => {\n\t\texpect(needsMigration({ configVersion: CURRENT_CONFIG_VERSION })).toBe(false);\n\t});\n\n\tit(\"throws for unknown version with no migration path\", () => {\n\t\texpect(() => migrateConfig({ configVersion: 99 })).toThrow(\"No migration path\");\n\t});\n});\n"],"mappings":";;;;AAGA,SAAS,2BAA2B;AACnC,IAAG,+CAA+C;EAEjD,MAAM,SAAS,cADJ;GAAE,aAAa;GAAQ,UAAU,CAAC,QAAQ;GAAE,YAAY,EAAE;GAAE,CACvC;AAChC,eAAO,OAAO,cAAc,CAAC,KAAK,uBAAuB;AACzD,eAAO,OAAO,eAAe,CAAC,KAAK,SAAS;GAC3C;AAEF,IAAG,4DAA4D;AAG9D,eADe,cADJ;GAAE,eAAe;GAAG,gBAAgB;GAAc,CAC7B,CAClB,eAAe,CAAC,KAAK,aAAa;GAC/C;AAEF,IAAG,kDAAkD;EACpD,MAAM,UAAU;GAAE,eAAe;GAAwB,aAAa;GAAQ;AAE9E,eADe,cAAc,QAAQ,CACvB,CAAC,QAAQ,QAAQ;GAC9B;AAEF,IAAG,qDAAqD;AACvD,eAAO,eAAe,EAAE,eAAe,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK;AACvD,eAAO,eAAe,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK;GACpC;AAEF,IAAG,0DAA0D;AAC5D,eAAO,eAAe,EAAE,eAAe,wBAAwB,CAAC,CAAC,CAAC,KAAK,MAAM;GAC5E;AAEF,IAAG,2DAA2D;AAC7D,qBAAa,cAAc,EAAE,eAAe,IAAI,CAAC,CAAC,CAAC,QAAQ,oBAAoB;GAC9E;EACD"}
@@ -1,4 +1,4 @@
1
- import { n as describe, r as it, t as globalExpect } from "../vi.2VT5v0um-Qk6MgAnK.mjs";
1
+ import { n as describe, r as it, t as globalExpect } from "../vi.2VT5v0um-YSByewHe.mjs";
2
2
  import { getAllPresets, getPresetById } from "./registry.mjs";
3
3
 
4
4
  //#region src/presets/registry.test.ts
@@ -1,4 +1,4 @@
1
- import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-Qk6MgAnK.mjs";
1
+ import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-YSByewHe.mjs";
2
2
  import { resolve } from "./resolver.mjs";
3
3
 
4
4
  //#region src/resolver.test.ts
@@ -1,4 +1,4 @@
1
- import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-Qk6MgAnK.mjs";
1
+ import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-YSByewHe.mjs";
2
2
  import { EnvVariableSchema, GenerationInputSchema, HealthCheckSchema, PortMappingSchema, PresetSchema, ServiceCategorySchema, ServiceDefinitionSchema, SkillPackSchema, VolumeMappingSchema } from "./schema.mjs";
3
3
 
4
4
  //#region src/schema.test.ts
@@ -1 +1 @@
1
- {"version":3,"file":"convex.mjs","names":[],"sources":["../../../src/services/definitions/convex.ts"],"sourcesContent":["import type { ServiceDefinition } from \"../../types.js\";\n\nexport const convexDefinition: ServiceDefinition = {\n\tid: \"convex\",\n\tname: \"Convex\",\n\tdescription:\n\t\t\"Self-hosted Convex reactive backend. Real-time database, server functions, and file storage in a single service. Required by Mission Control.\",\n\tcategory: \"database\",\n\ticon: \"⚡\",\n\n\timage: \"ghcr.io/get-convex/convex-backend\",\n\timageTag: \"latest\",\n\tports: [\n\t\t{\n\t\t\thost: 3210,\n\t\t\tcontainer: 3210,\n\t\t\tdescription: \"Convex API (client connections & CLI)\",\n\t\t\texposed: true,\n\t\t},\n\t\t{\n\t\t\thost: 3211,\n\t\t\tcontainer: 3211,\n\t\t\tdescription: \"Convex HTTP actions / site proxy\",\n\t\t\texposed: true,\n\t\t},\n\t],\n\tvolumes: [\n\t\t{\n\t\t\tname: \"convex-data\",\n\t\t\tcontainerPath: \"/convex/data\",\n\t\t\tdescription: \"Convex data directory (SQLite by default)\",\n\t\t},\n\t],\n\tenvironment: [\n\t\t{\n\t\t\tkey: \"CONVEX_CLOUD_ORIGIN\",\n\t\t\tdefaultValue: \"http://127.0.0.1:3210\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Public URL for the Convex API endpoint\",\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tkey: \"CONVEX_SITE_ORIGIN\",\n\t\t\tdefaultValue: \"http://127.0.0.1:3211\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Public URL for Convex HTTP actions\",\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tkey: \"INSTANCE_NAME\",\n\t\t\tdefaultValue: \"openclaw-convex\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Instance name for this Convex deployment\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"INSTANCE_SECRET\",\n\t\t\tdefaultValue: \"\",\n\t\t\tsecret: true,\n\t\t\tdescription: \"Instance secret (generate a random value for production)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"DATABASE_URL\",\n\t\t\tdefaultValue: \"\",\n\t\t\tsecret: true,\n\t\t\tdescription:\n\t\t\t\t\"Optional Postgres connection string for production (leave empty to use SQLite)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"RUST_LOG\",\n\t\t\tdefaultValue: \"info\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Log level for the Convex backend (debug, info, warn, error)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"DISABLE_BEACON\",\n\t\t\tdefaultValue: \"\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Set to any value to disable anonymous usage telemetry\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"CONVEX_SELF_HOSTED_ADMIN_KEY\",\n\t\t\tdefaultValue: \"\",\n\t\t\tsecret: true,\n\t\t\tdescription:\n\t\t\t\t\"Admin key for CLI access. Generate with: docker compose exec convex ./generate_admin_key.sh\",\n\t\t\trequired: false,\n\t\t},\n\t],\n\thealthcheck: {\n\t\ttest: \"curl -f http://localhost:3210/version\",\n\t\tinterval: \"5s\",\n\t\ttimeout: \"5s\",\n\t\tretries: 5,\n\t\tstartPeriod: \"10s\",\n\t},\n\tdependsOn: [],\n\trestartPolicy: \"unless-stopped\",\n\tnetworks: [\"openclaw-network\"],\n\n\tskills: [],\n\topenclawEnvVars: [],\n\n\tdocsUrl: \"https://github.com/get-convex/convex-backend\",\n\tselfHostedDocsUrl:\n\t\t\"https://github.com/get-convex/convex-backend/blob/main/self-hosted/README.md\",\n\ttags: [\"convex\", \"database\", \"reactive\", \"real-time\", \"backend\", \"self-hosted\"],\n\tmaturity: \"stable\",\n\n\trequires: [],\n\trecommends: [\"convex-dashboard\"],\n\tconflictsWith: [],\n\tmandatory: true,\n\tremovalWarning:\n\t\t\"Convex is the backend for Mission Control. Without it, Mission Control will not be able to store or retrieve any data and will be completely non-functional.\",\n\n\tminMemoryMB: 256,\n\tgpuRequired: false,\n};\n"],"mappings":";AAEA,MAAa,mBAAsC;CAClD,IAAI;CACJ,MAAM;CACN,aACC;CACD,UAAU;CACV,MAAM;CAEN,OAAO;CACP,UAAU;CACV,OAAO,CACN;EACC,MAAM;EACN,WAAW;EACX,aAAa;EACb,SAAS;EACT,EACD;EACC,MAAM;EACN,WAAW;EACX,aAAa;EACb,SAAS;EACT,CACD;CACD,SAAS,CACR;EACC,MAAM;EACN,eAAe;EACf,aAAa;EACb,CACD;CACD,aAAa;EACZ;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aACC;GACD,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aACC;GACD,UAAU;GACV;EACD;CACD,aAAa;EACZ,MAAM;EACN,UAAU;EACV,SAAS;EACT,SAAS;EACT,aAAa;EACb;CACD,WAAW,EAAE;CACb,eAAe;CACf,UAAU,CAAC,mBAAmB;CAE9B,QAAQ,EAAE;CACV,iBAAiB,EAAE;CAEnB,SAAS;CACT,mBACC;CACD,MAAM;EAAC;EAAU;EAAY;EAAY;EAAa;EAAW;EAAc;CAC/E,UAAU;CAEV,UAAU,EAAE;CACZ,YAAY,CAAC,mBAAmB;CAChC,eAAe,EAAE;CACjB,WAAW;CACX,gBACC;CAED,aAAa;CACb,aAAa;CACb"}
1
+ {"version":3,"file":"convex.mjs","names":[],"sources":["../../../src/services/definitions/convex.ts"],"sourcesContent":["import type { ServiceDefinition } from \"../../types.js\";\n\nexport const convexDefinition: ServiceDefinition = {\n\tid: \"convex\",\n\tname: \"Convex\",\n\tdescription:\n\t\t\"Self-hosted Convex reactive backend. Real-time database, server functions, and file storage in a single service. Required by Mission Control.\",\n\tcategory: \"database\",\n\ticon: \"⚡\",\n\n\timage: \"ghcr.io/get-convex/convex-backend\",\n\timageTag: \"latest\",\n\tports: [\n\t\t{\n\t\t\thost: 3210,\n\t\t\tcontainer: 3210,\n\t\t\tdescription: \"Convex API (client connections & CLI)\",\n\t\t\texposed: true,\n\t\t},\n\t\t{\n\t\t\thost: 3211,\n\t\t\tcontainer: 3211,\n\t\t\tdescription: \"Convex HTTP actions / site proxy\",\n\t\t\texposed: true,\n\t\t},\n\t],\n\tvolumes: [\n\t\t{\n\t\t\tname: \"convex-data\",\n\t\t\tcontainerPath: \"/convex/data\",\n\t\t\tdescription: \"Convex data directory (SQLite by default)\",\n\t\t},\n\t],\n\tenvironment: [\n\t\t{\n\t\t\tkey: \"CONVEX_CLOUD_ORIGIN\",\n\t\t\tdefaultValue: \"http://127.0.0.1:3210\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Public URL for the Convex API endpoint\",\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tkey: \"CONVEX_SITE_ORIGIN\",\n\t\t\tdefaultValue: \"http://127.0.0.1:3211\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Public URL for Convex HTTP actions\",\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tkey: \"INSTANCE_NAME\",\n\t\t\tdefaultValue: \"openclaw-convex\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Instance name for this Convex deployment\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"INSTANCE_SECRET\",\n\t\t\tdefaultValue: \"\",\n\t\t\tsecret: true,\n\t\t\tdescription: \"Instance secret (generate a random value for production)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"DATABASE_URL\",\n\t\t\tdefaultValue: \"\",\n\t\t\tsecret: true,\n\t\t\tdescription: \"Optional Postgres connection string for production (leave empty to use SQLite)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"RUST_LOG\",\n\t\t\tdefaultValue: \"info\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Log level for the Convex backend (debug, info, warn, error)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"DISABLE_BEACON\",\n\t\t\tdefaultValue: \"\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Set to any value to disable anonymous usage telemetry\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"CONVEX_SELF_HOSTED_ADMIN_KEY\",\n\t\t\tdefaultValue: \"\",\n\t\t\tsecret: true,\n\t\t\tdescription:\n\t\t\t\t\"Admin key for CLI access. Generate with: docker compose exec convex ./generate_admin_key.sh\",\n\t\t\trequired: false,\n\t\t},\n\t],\n\thealthcheck: {\n\t\ttest: \"curl -f http://localhost:3210/version\",\n\t\tinterval: \"5s\",\n\t\ttimeout: \"5s\",\n\t\tretries: 5,\n\t\tstartPeriod: \"10s\",\n\t},\n\tdependsOn: [],\n\trestartPolicy: \"unless-stopped\",\n\tnetworks: [\"openclaw-network\"],\n\n\tskills: [],\n\topenclawEnvVars: [],\n\n\tdocsUrl: \"https://github.com/get-convex/convex-backend\",\n\tselfHostedDocsUrl: \"https://github.com/get-convex/convex-backend/blob/main/self-hosted/README.md\",\n\ttags: [\"convex\", \"database\", \"reactive\", \"real-time\", \"backend\", \"self-hosted\"],\n\tmaturity: \"stable\",\n\n\trequires: [],\n\trecommends: [\"convex-dashboard\"],\n\tconflictsWith: [],\n\tmandatory: true,\n\tremovalWarning:\n\t\t\"Convex is the backend for Mission Control. Without it, Mission Control will not be able to store or retrieve any data and will be completely non-functional.\",\n\n\tminMemoryMB: 256,\n\tgpuRequired: false,\n};\n"],"mappings":";AAEA,MAAa,mBAAsC;CAClD,IAAI;CACJ,MAAM;CACN,aACC;CACD,UAAU;CACV,MAAM;CAEN,OAAO;CACP,UAAU;CACV,OAAO,CACN;EACC,MAAM;EACN,WAAW;EACX,aAAa;EACb,SAAS;EACT,EACD;EACC,MAAM;EACN,WAAW;EACX,aAAa;EACb,SAAS;EACT,CACD;CACD,SAAS,CACR;EACC,MAAM;EACN,eAAe;EACf,aAAa;EACb,CACD;CACD,aAAa;EACZ;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aACC;GACD,UAAU;GACV;EACD;CACD,aAAa;EACZ,MAAM;EACN,UAAU;EACV,SAAS;EACT,SAAS;EACT,aAAa;EACb;CACD,WAAW,EAAE;CACb,eAAe;CACf,UAAU,CAAC,mBAAmB;CAE9B,QAAQ,EAAE;CACV,iBAAiB,EAAE;CAEnB,SAAS;CACT,mBAAmB;CACnB,MAAM;EAAC;EAAU;EAAY;EAAY;EAAa;EAAW;EAAc;CAC/E,UAAU;CAEV,UAAU,EAAE;CACZ,YAAY,CAAC,mBAAmB;CAChC,eAAe,EAAE;CACjB,WAAW;CACX,gBACC;CAED,aAAa;CACb,aAAa;CACb"}
@@ -1 +1 @@
1
- {"version":3,"file":"desktop-environment.mjs","names":[],"sources":["../../../src/services/definitions/desktop-environment.ts"],"sourcesContent":["import type { ServiceDefinition } from \"../../types.js\";\n\nexport const desktopEnvironmentDefinition: ServiceDefinition = {\n\tid: \"desktop-environment\",\n\tname: \"Desktop Environment\",\n\tdescription:\n\t\t\"Isolated KasmVNC-based Linux desktop that gives AI agents full computer-use capabilities — screen vision, mouse/keyboard control, file management, and application launching (VS Code, Chrome, Firefox). OBS Studio is pre-installed for optional recording or live-streaming.\",\n\tcategory: \"desktop\",\n\ticon: \"🖥️\",\n\n\timage: \"kasmweb/core-ubuntu-jammy\",\n\timageTag: \"1.16.0\",\n\tports: [\n\t\t{\n\t\t\thost: 6901,\n\t\t\tcontainer: 6901,\n\t\t\tdescription: \"KasmVNC web interface (browser-based desktop access)\",\n\t\t\texposed: true,\n\t\t},\n\t\t{\n\t\t\thost: 5900,\n\t\t\tcontainer: 5900,\n\t\t\tdescription: \"VNC protocol port (native VNC clients)\",\n\t\t\texposed: false,\n\t\t},\n\t\t{\n\t\t\thost: 4455,\n\t\t\tcontainer: 4455,\n\t\t\tdescription: \"OBS WebSocket control port (when OBS is running)\",\n\t\t\texposed: false,\n\t\t},\n\t],\n\tvolumes: [\n\t\t{\n\t\t\tname: \"desktop-config\",\n\t\t\tcontainerPath: \"/home/kasm-user\",\n\t\t\tdescription: \"Desktop user home with config persistence\",\n\t\t},\n\t\t{\n\t\t\tname: \"desktop-workspace\",\n\t\t\tcontainerPath: \"/home/kasm-user/workspace\",\n\t\t\tdescription: \"Shared workspace directory for files and projects\",\n\t\t},\n\t],\n\tenvironment: [\n\t\t{\n\t\t\tkey: \"VNC_PW\",\n\t\t\tdefaultValue: \"changeme\",\n\t\t\tsecret: true,\n\t\t\tdescription: \"Password for VNC / KasmVNC web access\",\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tkey: \"VNC_RESOLUTION\",\n\t\t\tdefaultValue: \"1920x1080\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Desktop screen resolution (WidthxHeight)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"SSL_VNC_ONLY\",\n\t\t\tdefaultValue: \"false\",\n\t\t\tsecret: false,\n\t\t\tdescription:\n\t\t\t\t\"When false, allows HTTP/WS connections without SSL (useful for local/Docker networking)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"OBS_WS_PORT\",\n\t\t\tdefaultValue: \"4455\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"OBS WebSocket port (used when OBS is launched manually)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"OBS_PASSWORD\",\n\t\t\tdefaultValue: \"changeme\",\n\t\t\tsecret: true,\n\t\t\tdescription: \"OBS WebSocket password (used when OBS is launched manually)\",\n\t\t\trequired: false,\n\t\t},\n\t],\n\thealthcheck: {\n\t\ttest: \"curl -sfk https://localhost:6901/ > /dev/null || curl -sf http://localhost:6901/ > /dev/null || exit 1\",\n\t\tinterval: \"10s\",\n\t\ttimeout: \"10s\",\n\t\tretries: 6,\n\t\tstartPeriod: \"60s\",\n\t},\n\tdependsOn: [],\n\trestartPolicy: \"unless-stopped\",\n\tnetworks: [\"openclaw-network\"],\n\n\tdeploy: {\n\t\tresources: {\n\t\t\tlimits: { cpus: \"4.0\", memory: \"8G\" },\n\t\t\treservations: { cpus: \"2.0\", memory: \"4G\" },\n\t\t},\n\t},\n\n\tskills: [{ skillId: \"desktop-use\", autoInstall: true }],\n\topenclawEnvVars: [\n\t\t{\n\t\t\tkey: \"DESKTOP_HOST\",\n\t\t\tdefaultValue: \"desktop-environment\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Hostname of the desktop-environment container\",\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tkey: \"DESKTOP_VNC_PORT\",\n\t\t\tdefaultValue: \"6901\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"KasmVNC web port\",\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tkey: \"DESKTOP_VNC_PASSWORD\",\n\t\t\tdefaultValue: \"${VNC_PW}\",\n\t\t\tsecret: true,\n\t\t\tdescription: \"VNC password (references service password)\",\n\t\t\trequired: true,\n\t\t},\n\t],\n\n\tdocsUrl: \"https://www.kasmweb.com/docs/latest/index.html\",\n\ttags: [\n\t\t\"computer-use\",\n\t\t\"vnc\",\n\t\t\"desktop\",\n\t\t\"screen-capture\",\n\t\t\"automation\",\n\t\t\"obs\",\n\t\t\"kasm\",\n\t],\n\tmaturity: \"experimental\",\n\n\trequires: [],\n\trecommends: [\"stream-gateway\"],\n\tconflictsWith: [],\n\n\tremovalWarning:\n\t\t\"⚠️ HEAVY RESOURCE USAGE: The desktop environment requires at least 4 GB RAM (8 GB recommended) and 2+ CPU cores. It runs a full Linux desktop with XFCE inside KasmVNC. OBS Studio is pre-installed but NOT auto-started — launch it manually or via agent tools when needed. Ensure your deployment target can handle these resources alongside other services.\",\n\tminMemoryMB: 4096,\n\tgpuRequired: false,\n\n\tnativeSupported: true,\n\tnativeRecipes: [\n\t\t{\n\t\t\tplatform: \"linux\",\n\t\t\tinstallSteps: [\n\t\t\t\t\"command -v Xvfb >/dev/null 2>&1 || (command -v apt-get >/dev/null 2>&1 && sudo apt-get update -qq && sudo apt-get install -y -qq xvfb xfce4 xfce4-terminal tigervnc-standalone-server scrot xdotool xclip)\",\n\t\t\t\t\"command -v obs >/dev/null 2>&1 || (sudo add-apt-repository -y ppa:obsproject/obs-studio && sudo apt-get update -qq && sudo apt-get install -y -qq obs-studio)\",\n\t\t\t],\n\t\t\tstartCommand:\n\t\t\t\t\"vncserver :1 -geometry 1920x1080 -depth 24 -localhost no 2>/dev/null || Xvfb :1 -screen 0 1920x1080x24 &\",\n\t\t\tstopCommand: \"vncserver -kill :1 2>/dev/null; killall Xvfb 2>/dev/null\",\n\t\t\tconfigPath: \"/etc/vnc/xstartup\",\n\t\t\tconfigTemplate:\n\t\t\t\t\"#!/bin/sh\\n# Generated for OpenClaw bare-metal desktop\\nexport DISPLAY=:1\\nstartxfce4 &\\n\",\n\t\t},\n\t],\n};\n"],"mappings":";AAEA,MAAa,+BAAkD;CAC9D,IAAI;CACJ,MAAM;CACN,aACC;CACD,UAAU;CACV,MAAM;CAEN,OAAO;CACP,UAAU;CACV,OAAO;EACN;GACC,MAAM;GACN,WAAW;GACX,aAAa;GACb,SAAS;GACT;EACD;GACC,MAAM;GACN,WAAW;GACX,aAAa;GACb,SAAS;GACT;EACD;GACC,MAAM;GACN,WAAW;GACX,aAAa;GACb,SAAS;GACT;EACD;CACD,SAAS,CACR;EACC,MAAM;EACN,eAAe;EACf,aAAa;EACb,EACD;EACC,MAAM;EACN,eAAe;EACf,aAAa;EACb,CACD;CACD,aAAa;EACZ;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aACC;GACD,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;CACD,aAAa;EACZ,MAAM;EACN,UAAU;EACV,SAAS;EACT,SAAS;EACT,aAAa;EACb;CACD,WAAW,EAAE;CACb,eAAe;CACf,UAAU,CAAC,mBAAmB;CAE9B,QAAQ,EACP,WAAW;EACV,QAAQ;GAAE,MAAM;GAAO,QAAQ;GAAM;EACrC,cAAc;GAAE,MAAM;GAAO,QAAQ;GAAM;EAC3C,EACD;CAED,QAAQ,CAAC;EAAE,SAAS;EAAe,aAAa;EAAM,CAAC;CACvD,iBAAiB;EAChB;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;CAED,SAAS;CACT,MAAM;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACD,UAAU;CAEV,UAAU,EAAE;CACZ,YAAY,CAAC,iBAAiB;CAC9B,eAAe,EAAE;CAEjB,gBACC;CACD,aAAa;CACb,aAAa;CAEb,iBAAiB;CACjB,eAAe,CACd;EACC,UAAU;EACV,cAAc,CACb,8MACA,gKACA;EACD,cACC;EACD,aAAa;EACb,YAAY;EACZ,gBACC;EACD,CACD;CACD"}
1
+ {"version":3,"file":"desktop-environment.mjs","names":[],"sources":["../../../src/services/definitions/desktop-environment.ts"],"sourcesContent":["import type { ServiceDefinition } from \"../../types.js\";\n\nexport const desktopEnvironmentDefinition: ServiceDefinition = {\n\tid: \"desktop-environment\",\n\tname: \"Desktop Environment\",\n\tdescription:\n\t\t\"Isolated KasmVNC-based Linux desktop that gives AI agents full computer-use capabilities — screen vision, mouse/keyboard control, file management, and application launching (VS Code, Chrome, Firefox). OBS Studio is pre-installed for optional recording or live-streaming.\",\n\tcategory: \"desktop\",\n\ticon: \"🖥️\",\n\n\timage: \"kasmweb/core-ubuntu-jammy\",\n\timageTag: \"1.16.0\",\n\tports: [\n\t\t{\n\t\t\thost: 6901,\n\t\t\tcontainer: 6901,\n\t\t\tdescription: \"KasmVNC web interface (browser-based desktop access)\",\n\t\t\texposed: true,\n\t\t},\n\t\t{\n\t\t\thost: 5900,\n\t\t\tcontainer: 5900,\n\t\t\tdescription: \"VNC protocol port (native VNC clients)\",\n\t\t\texposed: false,\n\t\t},\n\t\t{\n\t\t\thost: 4455,\n\t\t\tcontainer: 4455,\n\t\t\tdescription: \"OBS WebSocket control port (when OBS is running)\",\n\t\t\texposed: false,\n\t\t},\n\t],\n\tvolumes: [\n\t\t{\n\t\t\tname: \"desktop-config\",\n\t\t\tcontainerPath: \"/home/kasm-user\",\n\t\t\tdescription: \"Desktop user home with config persistence\",\n\t\t},\n\t\t{\n\t\t\tname: \"desktop-workspace\",\n\t\t\tcontainerPath: \"/home/kasm-user/workspace\",\n\t\t\tdescription: \"Shared workspace directory for files and projects\",\n\t\t},\n\t],\n\tenvironment: [\n\t\t{\n\t\t\tkey: \"VNC_PW\",\n\t\t\tdefaultValue: \"changeme\",\n\t\t\tsecret: true,\n\t\t\tdescription: \"Password for VNC / KasmVNC web access\",\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tkey: \"VNC_RESOLUTION\",\n\t\t\tdefaultValue: \"1920x1080\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Desktop screen resolution (WidthxHeight)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"SSL_VNC_ONLY\",\n\t\t\tdefaultValue: \"false\",\n\t\t\tsecret: false,\n\t\t\tdescription:\n\t\t\t\t\"When false, allows HTTP/WS connections without SSL (useful for local/Docker networking)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"OBS_WS_PORT\",\n\t\t\tdefaultValue: \"4455\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"OBS WebSocket port (used when OBS is launched manually)\",\n\t\t\trequired: false,\n\t\t},\n\t\t{\n\t\t\tkey: \"OBS_PASSWORD\",\n\t\t\tdefaultValue: \"changeme\",\n\t\t\tsecret: true,\n\t\t\tdescription: \"OBS WebSocket password (used when OBS is launched manually)\",\n\t\t\trequired: false,\n\t\t},\n\t],\n\thealthcheck: {\n\t\ttest: \"curl -sfk https://localhost:6901/ > /dev/null || curl -sf http://localhost:6901/ > /dev/null || exit 1\",\n\t\tinterval: \"10s\",\n\t\ttimeout: \"10s\",\n\t\tretries: 6,\n\t\tstartPeriod: \"60s\",\n\t},\n\tdependsOn: [],\n\trestartPolicy: \"unless-stopped\",\n\tnetworks: [\"openclaw-network\"],\n\n\tdeploy: {\n\t\tresources: {\n\t\t\tlimits: { cpus: \"4.0\", memory: \"8G\" },\n\t\t\treservations: { cpus: \"2.0\", memory: \"4G\" },\n\t\t},\n\t},\n\n\tskills: [{ skillId: \"desktop-use\", autoInstall: true }],\n\topenclawEnvVars: [\n\t\t{\n\t\t\tkey: \"DESKTOP_HOST\",\n\t\t\tdefaultValue: \"desktop-environment\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"Hostname of the desktop-environment container\",\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tkey: \"DESKTOP_VNC_PORT\",\n\t\t\tdefaultValue: \"6901\",\n\t\t\tsecret: false,\n\t\t\tdescription: \"KasmVNC web port\",\n\t\t\trequired: true,\n\t\t},\n\t\t{\n\t\t\tkey: \"DESKTOP_VNC_PASSWORD\",\n\t\t\tdefaultValue: \"${VNC_PW}\",\n\t\t\tsecret: true,\n\t\t\tdescription: \"VNC password (references service password)\",\n\t\t\trequired: true,\n\t\t},\n\t],\n\n\tdocsUrl: \"https://www.kasmweb.com/docs/latest/index.html\",\n\ttags: [\"computer-use\", \"vnc\", \"desktop\", \"screen-capture\", \"automation\", \"obs\", \"kasm\"],\n\tmaturity: \"experimental\",\n\n\trequires: [],\n\trecommends: [\"stream-gateway\"],\n\tconflictsWith: [],\n\n\tremovalWarning:\n\t\t\"⚠️ HEAVY RESOURCE USAGE: The desktop environment requires at least 4 GB RAM (8 GB recommended) and 2+ CPU cores. It runs a full Linux desktop with XFCE inside KasmVNC. OBS Studio is pre-installed but NOT auto-started — launch it manually or via agent tools when needed. Ensure your deployment target can handle these resources alongside other services.\",\n\tminMemoryMB: 4096,\n\tgpuRequired: false,\n\n\tnativeSupported: true,\n\tnativeRecipes: [\n\t\t{\n\t\t\tplatform: \"linux\",\n\t\t\tinstallSteps: [\n\t\t\t\t\"command -v Xvfb >/dev/null 2>&1 || (command -v apt-get >/dev/null 2>&1 && sudo apt-get update -qq && sudo apt-get install -y -qq xvfb xfce4 xfce4-terminal tigervnc-standalone-server scrot xdotool xclip)\",\n\t\t\t\t\"command -v obs >/dev/null 2>&1 || (sudo add-apt-repository -y ppa:obsproject/obs-studio && sudo apt-get update -qq && sudo apt-get install -y -qq obs-studio)\",\n\t\t\t],\n\t\t\tstartCommand:\n\t\t\t\t\"vncserver :1 -geometry 1920x1080 -depth 24 -localhost no 2>/dev/null || Xvfb :1 -screen 0 1920x1080x24 &\",\n\t\t\tstopCommand: \"vncserver -kill :1 2>/dev/null; killall Xvfb 2>/dev/null\",\n\t\t\tconfigPath: \"/etc/vnc/xstartup\",\n\t\t\tconfigTemplate:\n\t\t\t\t\"#!/bin/sh\\n# Generated for OpenClaw bare-metal desktop\\nexport DISPLAY=:1\\nstartxfce4 &\\n\",\n\t\t},\n\t],\n};\n"],"mappings":";AAEA,MAAa,+BAAkD;CAC9D,IAAI;CACJ,MAAM;CACN,aACC;CACD,UAAU;CACV,MAAM;CAEN,OAAO;CACP,UAAU;CACV,OAAO;EACN;GACC,MAAM;GACN,WAAW;GACX,aAAa;GACb,SAAS;GACT;EACD;GACC,MAAM;GACN,WAAW;GACX,aAAa;GACb,SAAS;GACT;EACD;GACC,MAAM;GACN,WAAW;GACX,aAAa;GACb,SAAS;GACT;EACD;CACD,SAAS,CACR;EACC,MAAM;EACN,eAAe;EACf,aAAa;EACb,EACD;EACC,MAAM;EACN,eAAe;EACf,aAAa;EACb,CACD;CACD,aAAa;EACZ;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aACC;GACD,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;CACD,aAAa;EACZ,MAAM;EACN,UAAU;EACV,SAAS;EACT,SAAS;EACT,aAAa;EACb;CACD,WAAW,EAAE;CACb,eAAe;CACf,UAAU,CAAC,mBAAmB;CAE9B,QAAQ,EACP,WAAW;EACV,QAAQ;GAAE,MAAM;GAAO,QAAQ;GAAM;EACrC,cAAc;GAAE,MAAM;GAAO,QAAQ;GAAM;EAC3C,EACD;CAED,QAAQ,CAAC;EAAE,SAAS;EAAe,aAAa;EAAM,CAAC;CACvD,iBAAiB;EAChB;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;GACC,KAAK;GACL,cAAc;GACd,QAAQ;GACR,aAAa;GACb,UAAU;GACV;EACD;CAED,SAAS;CACT,MAAM;EAAC;EAAgB;EAAO;EAAW;EAAkB;EAAc;EAAO;EAAO;CACvF,UAAU;CAEV,UAAU,EAAE;CACZ,YAAY,CAAC,iBAAiB;CAC9B,eAAe,EAAE;CAEjB,gBACC;CACD,aAAa;CACb,aAAa;CAEb,iBAAiB;CACjB,eAAe,CACd;EACC,UAAU;EACV,cAAc,CACb,8MACA,gKACA;EACD,cACC;EACD,aAAa;EACb,YAAY;EACZ,gBACC;EACD,CACD;CACD"}
@@ -36,8 +36,8 @@ import { matomoDefinition } from "./matomo.mjs";
36
36
  import { matrixSynapseDefinition } from "./matrix-synapse.mjs";
37
37
  import { mattermostDefinition } from "./mattermost.mjs";
38
38
  import { meilisearchDefinition } from "./meilisearch.mjs";
39
- import { missionControlDefinition } from "./mission-control.mjs";
40
39
  import { minioDefinition } from "./minio.mjs";
40
+ import { missionControlDefinition } from "./mission-control.mjs";
41
41
  import { mixpostDefinition } from "./mixpost.mjs";
42
42
  import { motionCanvasDefinition } from "./motion-canvas.mjs";
43
43
  import { n8nDefinition } from "./n8n.mjs";
@@ -67,8 +67,8 @@ import { tailscaleDefinition } from "./tailscale.mjs";
67
67
  import { temporalDefinition } from "./temporal.mjs";
68
68
  import { traefikDefinition } from "./traefik.mjs";
69
69
  import { umamiDefinition } from "./umami.mjs";
70
- import { usesendDefinition } from "./usesend.mjs";
71
70
  import { uptimeKumaDefinition } from "./uptime-kuma.mjs";
71
+ import { usesendDefinition } from "./usesend.mjs";
72
72
  import { valkeyDefinition } from "./valkey.mjs";
73
73
  import { watchtowerDefinition } from "./watchtower.mjs";
74
74
  import { weaviateDefinition } from "./weaviate.mjs";
@@ -7,10 +7,10 @@ import { calComDefinition } from "./cal-com.mjs";
7
7
  import { chromadbDefinition } from "./chromadb.mjs";
8
8
  import { claudeCodeDefinition } from "./claude-code.mjs";
9
9
  import { codeServerDefinition } from "./code-server.mjs";
10
+ import { codexDefinition } from "./codex.mjs";
10
11
  import { comfyuiDefinition } from "./comfyui.mjs";
11
12
  import { convexDefinition } from "./convex.mjs";
12
13
  import { convexDashboardDefinition } from "./convex-dashboard.mjs";
13
- import { codexDefinition } from "./codex.mjs";
14
14
  import { coolifyDefinition } from "./coolify.mjs";
15
15
  import { desktopEnvironmentDefinition } from "./desktop-environment.mjs";
16
16
  import { difyDefinition } from "./dify.mjs";
@@ -35,8 +35,8 @@ import { matomoDefinition } from "./matomo.mjs";
35
35
  import { matrixSynapseDefinition } from "./matrix-synapse.mjs";
36
36
  import { mattermostDefinition } from "./mattermost.mjs";
37
37
  import { meilisearchDefinition } from "./meilisearch.mjs";
38
- import { missionControlDefinition } from "./mission-control.mjs";
39
38
  import { minioDefinition } from "./minio.mjs";
39
+ import { missionControlDefinition } from "./mission-control.mjs";
40
40
  import { mixpostDefinition } from "./mixpost.mjs";
41
41
  import { motionCanvasDefinition } from "./motion-canvas.mjs";
42
42
  import { n8nDefinition } from "./n8n.mjs";
@@ -66,8 +66,8 @@ import { tailscaleDefinition } from "./tailscale.mjs";
66
66
  import { temporalDefinition } from "./temporal.mjs";
67
67
  import { traefikDefinition } from "./traefik.mjs";
68
68
  import { umamiDefinition } from "./umami.mjs";
69
- import { usesendDefinition } from "./usesend.mjs";
70
69
  import { uptimeKumaDefinition } from "./uptime-kuma.mjs";
70
+ import { usesendDefinition } from "./usesend.mjs";
71
71
  import { valkeyDefinition } from "./valkey.mjs";
72
72
  import { watchtowerDefinition } from "./watchtower.mjs";
73
73
  import { weaviateDefinition } from "./weaviate.mjs";