Process Compose Operations Process Compose is a Go-based supervisor for non-containerized services. Single binary, YAML config, built-in TUI, REST API, MCP server , and proper Windows support. Replacement for PM2/supervisord/Foreman in the local-dev role. Why not PM2: PM2 5.x has 15+ known CVEs (axios/lodash/tar/minimist transitive npm exposure). PC compiles all deps in at build time with hashes — structurally resistant to TanStack-style npm worm attacks. Why not Docker Compose: Container overhead is unnecessary for local Python/Node/Go dev servers running directly. PC gives you health checks…

) {\n [Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process')\n }\n }\n}\n\n# Launch headless\n& $pcExe -p 8888 -t=false -L \"$root\\logs\\process-compose.log\" up -f \"$root\\process-compose.yaml\"\n```\n\nRegister as a Task Scheduler entry with `LogonType S4U` (runs at boot, no password, no interactive logon needed):\n\n```powershell\n$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType S4U -RunLevel Highest\n$action = New-ScheduledTaskAction -Execute \"powershell.exe\" `\n -Argument \"-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `\"$root\\scripts\\boot-start.ps1`\"\"\n$trigger = New-ScheduledTaskTrigger -AtStartup\nRegister-ScheduledTask -TaskName \"ProcessCompose-Boot\" `\n -Action $action -Trigger $trigger -Principal $principal -Force\n```\n\n## YAML Gotchas\n\n| Gotcha | Symptom | Fix |\n|---|---|---|\n| Windows PATH with backslashes in double-quoted YAML | `yaml: found unknown escape character` | Use single quotes: `- 'PATH=C:\\Program Files\\Git\\usr\\bin;...'` |\n| `command` with quoted paths containing spaces | First arg eaten | Wrap whole command in single quotes, inner paths in double: `'\"C:/Program Files/foo.exe\" arg1 arg2'` |\n| Forgot `working_dir` | Process starts in PC's cwd, can't find files | Always specify absolute `working_dir` |\n| Health probe wrong port | Process restart-loops with `Not Ready` | Match `readiness_probe.http_get.port` to where the process actually binds |\n| Secrets in YAML | Committed to git | Use `environment` to pass-through; set in shell env or gitignored `.env` |\n\n## Common Operations\n\n```bash\n# Validate config before applying\nprocess-compose up --dry-run -f process-compose.yaml\n\n# Hot-reload after editing config\nprocess-compose -p 8888 project update -f process-compose.yaml\n\n# Restart one service after code change\nprocess-compose -p 8888 process restart axiom\n\n# Watch logs of a misbehaving service\nprocess-compose -p 8888 process logs axiom --follow\n\n# Stop one service temporarily for debugging\nprocess-compose -p 8888 process stop axiom\n# Now run it manually with your debugger, then:\nprocess-compose -p 8888 process start axiom\n```\n\n## When to Use Process Compose vs Alternatives\n\n| Need | Tool |\n|---|---|\n| Local non-containerized services with health/dependencies/MCP | **Process Compose** |\n| Production node.js process supervision | PM2 (despite age) |\n| Container-based stack | Docker Compose |\n| Job queue with cron + DAGs | Dagu, Temporal, Airflow |\n| System service supervision | systemd (Linux), Windows Services |\n| One-shot Procfile run | Foreman / Overmind / Hivemind (Unix-only) |\n\n## Worked Example\n\nSee `X:\\00_Orchestration\\compose-portless\\` for an 11-process production stack:\n- `process-compose.yaml` — health-checked services with depends_on chains\n- `scripts/boot-start.ps1` — PATH-aware boot wrapper\n- `docs/MIGRATION-LOG.md` — full migration from PM2 + Caddy, every gotcha documented\n- `docs/SUPPLY-CHAIN.md` — binary verification procedure\n\n## Anti-Patterns\n\n```\nBAD: process-compose up --detached # flag does not exist\nGOOD: process-compose up -t=false & # background via shell\n\nBAD: put secrets in process-compose.yaml (commits to git)\nGOOD: source from gitignored .env in boot wrapper\n\nBAD: use API port 8080 (clashes with Dagu, Tomcat, others)\nGOOD: -p 8888 (or any free port), document the choice\n\nBAD: ignore readiness_probe and just hope services come up\nGOOD: configure http_get probe on a real endpoint; depends_on uses process_healthy\n\nBAD: upgrade PC by running an installer (npm install -g, scoop install, brew install)\nGOOD: download specific version, verify SHA-256 against upstream checksums.txt, commit binary\n```\n\n## Resources in this skill\n\n### `references/`\n- `schema-reference.md` — full process-compose.yaml schema with field semantics, defaults, and command-quoting gotchas\n- `probe-patterns.md` — readiness probe recipes by stack (Python, Go, Node, TCP-only, daemons)\n- `dependency-patterns.md` — `depends_on` patterns: companion daemons, DB-before-app, tunnel-after-service, one-shot init\n- `tui-shortcuts.md` — TUI cheatsheet (keys, status legend, search/sort/filter)\n- `boot-persistence-windows.md` — Task Scheduler setup with S4U logon, PATH-aware wrapper\n- `supply-chain-verification.md` — full SHA-256 verification procedure for the binary\n\n### `scripts/`\n- `install-process-compose.ps1` — download + verify + extract a pinned version, writes VERIFICATION.md\n- `verify-binary.ps1` — re-verify committed binary hash (monthly / pre-commit)\n- `boot-start.template.ps1` — PATH-aware boot wrapper (copy + adapt per machine)\n- `boot-task-install.template.ps1` — Task Scheduler entry registration (S4U logon)\n\n### `assets/`\n- `python-uvicorn.yaml` — uvicorn/FastAPI/Django basic service template\n- `django-with-companions.yaml` — Django + queue daemon + audit watcher chain\n- `go-binary-service.yaml` — Go binary with HTTP or TCP probe\n- `tunnel-with-dependency.yaml` — Cloudflare tunnel waiting on its target service\n- `cron-job.yaml` — scheduled task patterns\n\n## Related Skills\n\n- `portless-ops` — the routing layer we pair with PC (replaces Caddy)\n- `docker-ops` — container alternative for the same role\n- `mcp-ops` — PC's MCP server fits this ecosystem\n- `cli-ops` — general CLI tool patterns\n---","attachment_filenames":["assets/cron-job.yaml","assets/django-with-companions.yaml","assets/go-binary-service.yaml","assets/python-uvicorn.yaml","assets/tunnel-with-dependency.yaml","references/boot-persistence-windows.md","references/dependency-patterns.md","references/probe-patterns.md","references/schema-reference.md","references/supply-chain-verification.md","references/tui-shortcuts.md"],"attachments":[{"filename":"assets/cron-job.yaml","content":"# process-compose.yaml — Scheduled (cron) job\n#\n# Pattern: Periodic batch task that PC runs on schedule, not as a daemon.\n\nversion: \"0.5\"\n\nprocesses:\n\n daily-backup:\n command: \"python scripts/backup.py --target s3://my-bucket\"\n working_dir: \"X:/path/to/scripts\"\n\n environment:\n - \"PYTHONUNBUFFERED=1\"\n # AWS creds, etc., from boot-start .env loading\n\n availability:\n restart: exit_on_failure # Stop the whole project if this fails\n schedule: \"0 2 * * *\" # cron syntax: 02:00 every day\n # Or interval-based:\n # schedule: \"@every 6h\"\n\n log_location: \"logs/daily-backup.log\"\n\n hourly-cleanup:\n command: \"python scripts/cleanup.py\"\n working_dir: \"X:/path/to/scripts\"\n environment:\n - \"PYTHONUNBUFFERED=1\"\n availability:\n restart: no # One-shot per schedule run\n schedule: \"0 * * * *\" # Every hour on the hour\n log_location: \"logs/hourly-cleanup.log\"\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":968,"content_sha256":"9eaab9f504525f586b1e154e217b4b5905b27ae0520f8ae14097132dce930ece"},{"filename":"assets/django-with-companions.yaml","content":"# process-compose.yaml — Django web app + queue daemon + audit watcher\n#\n# Pattern: A main HTTP service with two long-running companion daemons\n# that depend on the main service being up.\n#\n# Notes:\n# - depends_on ensures startup ordering, not runtime coupling.\n# If the web app restarts, the daemons keep running.\n# - The audit watcher uses Git Bash for a wrapper script that needs\n# coreutils on PATH (single-quoted YAML to escape backslashes).\n# - The daemon enforces an OAuth-only policy: ANTHROPIC_API_KEY must\n# be unset (handled by the boot-start wrapper).\n\nversion: \"0.5\"\n\nprocesses:\n\n webapp:\n command: \"uv run python manage.py serve --host 127.0.0.1 --port 8000 --no-reload\"\n working_dir: \"X:/path/to/myapp\"\n environment:\n - \"DJANGO_SETTINGS_MODULE=myapp.settings.local\"\n - \"PYTHONUNBUFFERED=1\"\n readiness_probe:\n http_get:\n host: localhost\n port: 8000\n path: /\n initial_delay_seconds: 30 # Django w/ migrations is slow to come up\n period_seconds: 15\n timeout_seconds: 5\n failure_threshold: 3\n availability:\n restart: always\n backoff_seconds: 5\n max_restarts: 20\n log_location: \"logs/webapp.log\"\n\n webapp-daemon:\n command: \"uv run python -m myapp.worker.daemon-start\"\n working_dir: \"X:/path/to/myapp\"\n environment:\n - \"PYTHONUNBUFFERED=1\"\n # NOTE: ANTHROPIC_API_KEY must be UNSET — daemon enforces OAuth-only.\n # The boot-start wrapper handles this; for manual runs, unset it first.\n depends_on:\n webapp:\n condition: process_started # Just needs webapp's pid to exist;\n # daemon polls webapp itself for readiness\n availability:\n restart: always\n backoff_seconds: 5\n max_restarts: 20\n shutdown:\n signal: 15 # SIGTERM\n timeout_seconds: 35 # Allow daemon's 30s graceful shutdown_grace_s\n log_location: \"logs/webapp-daemon.log\"\n\n webapp-feedback:\n # Bash wrapper script — paths in single quotes to avoid YAML escape interpretation\n command: '\"C:/Program Files/Git/usr/bin/bash.exe\" --login X:/path/to/myapp/scripts/feedback-wrapper.sh'\n working_dir: \"X:/path/to/myapp\"\n environment:\n - \"PYTHONUNBUFFERED=1\"\n # Belt-and-braces PATH for the bash wrapper:\n - 'PATH=C:\\Program Files\\Git\\usr\\bin;C:\\Program Files\\Git\\bin;C:\\Users\\me\\AppData\\Local\\Programs\\Python\\Python313\\Scripts;C:\\Windows\\System32'\n depends_on:\n webapp:\n condition: process_started\n availability:\n restart: always\n backoff_seconds: 15\n max_restarts: 20\n log_location: \"logs/webapp-feedback.log\"\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":2670,"content_sha256":"cc7f4ac59190fd674798591369b72c2efc538268bdab671f609040305885a0a6"},{"filename":"assets/go-binary-service.yaml","content":"# process-compose.yaml — Go binary service\n#\n# Pattern: Fast-startup Go service with TCP probe.\n# Use HTTP probe if it serves HTTP; tcp_socket is the fallback for\n# protocols that aren't HTTP.\n\nversion: \"0.5\"\n\nprocesses:\n\n mysvc:\n command: \"X:/path/to/mysvc/bin/mysvc.exe serve\"\n working_dir: \"X:/path/to/mysvc\"\n\n readiness_probe:\n http_get: # Use http_get if the service is HTTP\n host: localhost\n port: 8080\n path: /\n # OR for non-HTTP protocols:\n # tcp_socket:\n # host: localhost\n # port: 5432\n initial_delay_seconds: 1 # Go services usually come up in \u003c 1s\n period_seconds: 5\n timeout_seconds: 3\n failure_threshold: 3\n\n availability:\n restart: always\n backoff_seconds: 10\n max_restarts: 10\n\n log_location: \"logs/mysvc.log\"\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":848,"content_sha256":"40f564115a345ce149d994ca8a27ba8e8a1e3008589aec3ec93be4ff4bd4ba62"},{"filename":"assets/python-uvicorn.yaml","content":"# process-compose.yaml — Python uvicorn/FastAPI/Django service\n#\n# Pattern: Python web service with health endpoint, always-restart, dedicated log file.\n# Copy + adapt to your stack.\n\nversion: \"0.5\"\n\nprocesses:\n\n myapp:\n # Wrap pythonw exe path in single quotes if it contains spaces; use forward\n # slashes inside or single-quote backslash-paths to avoid YAML escapes.\n command: \"pythonw -m uvicorn myapp.main:app --host 127.0.0.1 --port 8000\"\n working_dir: \"X:/path/to/myapp\"\n\n environment:\n - \"PYTHONUNBUFFERED=1\"\n - \"DJANGO_SETTINGS_MODULE=myapp.settings.local\"\n # Don't put secrets here — source from gitignored .env via boot-start wrapper\n\n readiness_probe:\n http_get:\n host: localhost\n port: 8000\n path: / # bare root if no /health endpoint; redirects count as healthy\n initial_delay_seconds: 5\n period_seconds: 10\n timeout_seconds: 3\n failure_threshold: 3\n\n availability:\n restart: always\n backoff_seconds: 5\n max_restarts: 20\n\n log_location: \"logs/myapp.log\"\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":1082,"content_sha256":"4c90f193c2b86f5ab67cf0060103916c6afa19aa31a6d36a54e58b0eaf942b8e"},{"filename":"assets/tunnel-with-dependency.yaml","content":"# process-compose.yaml — Cloudflare tunnel exposing a local service\n#\n# Pattern: A web service + a Cloudflare tunnel that forwards external\n# traffic to it. The tunnel must depend on the service being HEALTHY,\n# not just started, to avoid Cloudflare seeing repeated connection\n# refused errors during service warmup.\n\nversion: \"0.5\"\n\nprocesses:\n\n internal-svc:\n command: \"myservice serve --port 8000\"\n working_dir: \"X:/path/to/myservice\"\n\n readiness_probe:\n http_get:\n host: localhost\n port: 8000\n path: /\n initial_delay_seconds: 5\n period_seconds: 15\n timeout_seconds: 3\n failure_threshold: 3\n\n availability:\n restart: always\n backoff_seconds: 3\n max_restarts: 20\n\n log_location: \"logs/internal-svc.log\"\n\n tunnel:\n # Single-quoted YAML string with embedded paths-with-spaces.\n # Tunnel UUID + cert paths should come from a gitignored .env.\n command: '\"C:/Program Files (x86)/cloudflared/cloudflared.exe\" tunnel --origincert C:/Users/me/.cloudflared/cert.pem --credentials-file C:/Users/me/.cloudflared/\u003cTUNNEL_UUID>.json run --url http://localhost:8000 my-tunnel'\n working_dir: \"C:/Users/me/.cloudflared\"\n\n depends_on:\n internal-svc:\n condition: process_healthy # Critical: don't open tunnel until service is ready\n\n availability:\n restart: always\n backoff_seconds: 5\n max_restarts: 50 # Tunnels can disconnect; allow many retries\n\n log_location: \"logs/tunnel.log\"\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":1510,"content_sha256":"172a25089b828190a6f1184ae92f0b58331073846d95f570a8249128ea6d6c3d"},{"filename":"references/boot-persistence-windows.md","content":"# Boot Persistence on Windows\n\nProcess Compose has no built-in `service install` (unlike portless). On Windows, register a Task Scheduler entry.\n\n## Key Constraints\n\n1. Task Scheduler runs with a **minimal PATH** — Python, uv, Git tools, custom binaries won't be found unless we set PATH explicitly\n2. Tasks running at boot-before-logon need **LogonType S4U** (no stored password, no interactive logon)\n3. Hidden window style avoids console flash on login\n\n## Two-File Pattern\n\nUse a wrapper script that sets the environment, then have Task Scheduler launch the wrapper. Keeps task definition simple and lets you tweak env without re-registering.\n\n### File 1 — `boot-start.ps1` (wrapper)\n\n```powershell\n\u003c#\n.SYNOPSIS\n Boot-time launcher for Process Compose. Sets PATH and launches headless.\n#>\n\n[CmdletBinding()]\nparam()\n\n$ErrorActionPreference = 'Continue'\n\n$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path\n$root = (Resolve-Path (Join-Path $scriptDir '..')).Path\n$pcExe = Join-Path $root 'bin\\process-compose.exe'\n$pcYaml = Join-Path $root 'process-compose.yaml'\n$logFile = Join-Path $root 'logs\\process-compose.log'\n$bootLog = Join-Path $root 'logs\\boot-start.log'\n\nNew-Item -ItemType Directory -Force -Path (Join-Path $root 'logs') | Out-Null\n\n\"[$(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK')] boot-start invoked. User: $env:USERNAME\" | Out-File -FilePath $bootLog -Append\n\n# Build PATH explicitly. Tune for your machine.\n$pathParts = @(\n \"$root\\bin\" # PC + any committed binaries\n \"C:\\Program Files\\Git\\usr\\bin\" # openssl, bash, coreutils\n \"C:\\Program Files\\Git\\bin\" # git\n \"C:\\Users\\$env:USERNAME\\AppData\\Local\\Programs\\Python\\Python313\" # python, pythonw\n \"C:\\Users\\$env:USERNAME\\AppData\\Local\\Programs\\Python\\Python313\\Scripts\" # uv, pip, etc.\n \"C:\\Program Files (x86)\\cloudflared\" # optional: cloudflared\n \"C:\\Windows\\System32\"\n \"C:\\Windows\"\n $env:PATH\n)\n$env:PATH = ($pathParts -join ';')\n\n# Optional: load secrets from gitignored .env (e.g. API keys)\n$envFile = Join-Path $root '.env'\nif (Test-Path $envFile) {\n Get-Content $envFile | ForEach-Object {\n if ($_ -match '^\\s*([A-Z_]+)\\s*=\\s*(.+?)\\s*

Process Compose Operations Process Compose is a Go-based supervisor for non-containerized services. Single binary, YAML config, built-in TUI, REST API, MCP server , and proper Windows support. Replacement for PM2/supervisord/Foreman in the local-dev role. Why not PM2: PM2 5.x has 15+ known CVEs (axios/lodash/tar/minimist transitive npm exposure). PC compiles all deps in at build time with hashes — structurally resistant to TanStack-style npm worm attacks. Why not Docker Compose: Container overhead is unnecessary for local Python/Node/Go dev servers running directly. PC gives you health checks…

) {\n [Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process')\n }\n }\n}\n\n# Ensure incompatible env vars are unset (example: OAuth-only services that\n# refuse to start with stale API keys)\n# [Environment]::SetEnvironmentVariable('SOME_API_KEY', $null, 'Process')\n\n\"[$(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK')] Starting process-compose...\" | Out-File -FilePath $bootLog -Append\n\n# -p 8888 API port (pick something free, avoid 8080 if you have other tools there)\n# -t=false no TUI (headless daemon mode)\n# -L PC's own log file\n& $pcExe -p 8888 -t=false -L $logFile up -f $pcYaml\n\n\"[$(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK')] process-compose exited code $LASTEXITCODE\" | Out-File -FilePath $bootLog -Append\n```\n\n### File 2 — `boot-task-install.ps1` (registers the task)\n\n```powershell\n[CmdletBinding()]\nparam()\n\n$ErrorActionPreference = 'Stop'\n\n# Must be admin to create scheduled tasks\n$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()\n$principal = New-Object Security.Principal.WindowsPrincipal($currentUser)\nif (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {\n throw \"Run as Administrator.\"\n}\n\n$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path\n$root = (Resolve-Path (Join-Path $scriptDir '..')).Path\n$bootScript = Join-Path $scriptDir 'boot-start.ps1'\n\n$taskName = \"ProcessCompose-MyStack\" # rename per project\n\n# Idempotent: remove existing if present\nGet-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue |\n Unregister-ScheduledTask -Confirm:$false\n\n$action = New-ScheduledTaskAction `\n -Execute \"powershell.exe\" `\n -Argument \"-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `\"$bootScript`\"\" `\n -WorkingDirectory $root\n\n$trigger = New-ScheduledTaskTrigger -AtStartup\n\n$settings = New-ScheduledTaskSettingsSet `\n -ExecutionTimeLimit (New-TimeSpan -Seconds 0) `\n -AllowStartIfOnBatteries `\n -DontStopIfGoingOnBatteries `\n -RestartCount 3 `\n -RestartInterval (New-TimeSpan -Minutes 1)\n\n# S4U: run at boot as user without interactive logon or stored password\n$taskPrincipal = New-ScheduledTaskPrincipal `\n -UserId $env:USERNAME `\n -LogonType S4U `\n -RunLevel Highest\n\nRegister-ScheduledTask -TaskName $taskName `\n -Action $action -Trigger $trigger -Settings $settings -Principal $taskPrincipal `\n -Description \"Starts Process Compose at boot.\"\n```\n\n## LogonType Trade-offs\n\n| LogonType | Runs at boot before logon? | Needs password? | Capability |\n|---|---|---|---|\n| `Interactive` | No — waits for user logon | No | Full user context (UI, network shares) |\n| `S4U` | Yes | No | User context but no UI, no network shares |\n| `Password` | Yes | Yes (stored encrypted) | Full user context |\n| `ServiceAccount` | Yes (as Local System / Network Service) | No | Limited to service account perms — typically can't read user files |\n\nFor Process Compose managing user-scoped dev services, **S4U** is usually the right choice: services run as the user (can read `C:\\Users\\\u003cuser>\\...`) without requiring an interactive logon.\n\n## Verify After Registration\n\n```powershell\n# Check task exists\nGet-ScheduledTask -TaskName \"ProcessCompose-MyStack\" |\n Format-List TaskName, State, Triggers, Principal\n\n# Manually run the task to test before reboot\nStart-ScheduledTask -TaskName \"ProcessCompose-MyStack\"\n\n# Wait, then check PC is up\nStart-Sleep -Seconds 10\nprocess-compose -p 8888 process list\n```\n\n## Troubleshooting Boot Failures\n\nAfter a reboot, if services don't come up:\n\n1. **Check the boot log:** `\u003croot>/logs/boot-start.log` — confirm the wrapper actually ran\n2. **Check PC's log:** `\u003croot>/logs/process-compose.log` — confirm PC started and look for process-spawn errors\n3. **Check Task Scheduler history:** Right-click the task → History tab. Look for failure reasons.\n4. **Reproduce manually:** open elevated PS, run `.\\scripts\\boot-start.ps1` and watch what happens.\n\nCommon failures:\n- PATH missing a tool → add to `pathParts` array\n- Working dir not absolute → ensure all paths in `process-compose.yaml` are absolute\n- Secrets not loaded → `.env` file not in expected location\n- Port collision (PC API port 8888 occupied) → check `netstat -ano | findstr :8888`\n\n## Pair with portless service install\n\nportless has its own boot task. The two are independent — register both:\n\n```powershell\nportless service install # registers portless's task\n.\\scripts\\boot-task-install.ps1 # registers PC's task\n\n# Verify both\nGet-ScheduledTask | Where-Object {\n $_.TaskName -like \"*ortless*\" -or $_.TaskName -like \"*ompose*\"\n}\n```\n\n## Uninstall\n\n```powershell\n# In the same script:\nGet-ScheduledTask -TaskName \"ProcessCompose-MyStack\" -ErrorAction SilentlyContinue |\n Unregister-ScheduledTask -Confirm:$false\n\nportless service uninstall\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7203,"content_sha256":"9ebb4b40f2fd33d3877c75a4ce49498fcd8487c066a0987aec6153a5f049cb11"},{"filename":"references/dependency-patterns.md","content":"# Dependency Patterns (depends_on)\n\nHow to express startup ordering and runtime dependencies between processes.\n\n## The Four Conditions\n\n| Condition | Meaning | Best for |\n|---|---|---|\n| `process_started` | Dependency has spawned (PID exists, may not be ready) | Coarse ordering when readiness doesn't matter |\n| `process_healthy` | Dependency's `readiness_probe` passes | Runtime services that must be queryable |\n| `process_completed` | Dependency exited (any code) | One-shot tasks that may fail |\n| `process_completed_successfully` | Dependency exited with code 0 | One-shot init that must succeed |\n\n## Pattern 1 — Web app + companion daemon\n\nA common pattern: a web service + a worker daemon that talks to the same DB or queue. Daemon should start AFTER the web app has its DB connection pool warm.\n\n```yaml\nprocesses:\n webapp:\n command: \"uv run python manage.py serve --port 8000\"\n working_dir: \"X:/Forge/MyApp\"\n readiness_probe:\n http_get: { host: localhost, port: 8000, path: / }\n initial_delay_seconds: 10\n availability: { restart: always }\n\n worker:\n command: \"uv run python -m myapp.worker\"\n working_dir: \"X:/Forge/MyApp\"\n depends_on:\n webapp:\n condition: process_healthy\n availability: { restart: always }\n```\n\nResult: `worker` doesn't start until `webapp`'s readiness probe passes. If `webapp` restarts, `worker` keeps running (depends_on is a startup ordering rule, not a runtime tether).\n\n## Pattern 2 — Three-tier chain\n\nWeb app + background daemon + audit watcher (Axiom pattern):\n\n```yaml\nprocesses:\n app:\n command: \"...\"\n readiness_probe: { ... }\n\n app-daemon:\n command: \"...\"\n depends_on:\n app:\n condition: process_healthy\n\n app-feedback:\n command: \"...\"\n depends_on:\n app:\n condition: process_started # weaker — just needs app's pid to exist\n```\n\n## Pattern 3 — Database before app\n\nPostgres in the same PC stack, app depends on it:\n\n```yaml\nprocesses:\n postgres:\n command: \"postgres -D /var/lib/pg\"\n readiness_probe:\n tcp_socket: { host: localhost, port: 5432 }\n initial_delay_seconds: 3\n\n migrate:\n command: \"alembic upgrade head\"\n working_dir: \"X:/MyApp\"\n depends_on:\n postgres:\n condition: process_healthy\n availability:\n restart: exit_on_failure # one-shot; if it fails, the whole stack fails\n\n app:\n command: \"uvicorn main:app\"\n working_dir: \"X:/MyApp\"\n depends_on:\n migrate:\n condition: process_completed_successfully\n postgres:\n condition: process_healthy\n availability: { restart: always }\n```\n\n`migrate` runs once, must succeed. `app` waits for both `migrate` (success) and `postgres` (healthy).\n\n## Pattern 4 — Tunnel that depends on the service it tunnels\n\nE.g. Cloudflare tunnel exposing a local service:\n\n```yaml\nprocesses:\n mcp-server:\n command: \"fastmcp serve --port 8000\"\n readiness_probe:\n http_get: { host: localhost, port: 8000, path: / }\n initial_delay_seconds: 5\n\n mcp-tunnel:\n command: '\"C:/Program Files/cloudflared/cloudflared.exe\" tunnel run my-tunnel'\n depends_on:\n mcp-server:\n condition: process_healthy # don't open tunnel until server is ready\n availability:\n restart: always\n backoff_seconds: 5\n max_restarts: 50 # tunnels can disconnect, allow many retries\n```\n\n## Pattern 5 — Static (one-time) setup task\n\n```yaml\nprocesses:\n fetch-secrets:\n command: \"python scripts/fetch_secrets.py\"\n availability:\n restart: exit_on_failure # must complete; stop the project if it fails\n # No readiness_probe — task either completes or doesn't\n\n app:\n command: \"...\"\n depends_on:\n fetch-secrets:\n condition: process_completed_successfully\n```\n\n## Cycle Detection\n\nPC detects cycles at startup. This fails immediately:\n\n```yaml\nprocesses:\n a: { depends_on: { b: { condition: process_started } } }\n b: { depends_on: { a: { condition: process_started } } }\n# Error: dependency cycle detected: a -> b -> a\n```\n\n## What `depends_on` Does NOT Do\n\n- **Does not** restart dependents when a dependency restarts. If `webapp` crashes and recovers, `worker` doesn't automatically restart.\n- **Does not** stop a dependent when the dependency stops. You'll need to model this with `restart: exit_on_failure` and probes.\n- **Does not** enforce shutdown order (PC shuts down in any order unless `--ordered-shutdown` flag is used).\n\nFor runtime coupling, the dependent process needs application-level reconnect/retry logic.\n\n## Shutdown Ordering\n\nBy default PC shuts processes down in any order. For services with stateful deps, use:\n\n```bash\nprocess-compose down --ordered-shutdown\n# Stops in reverse dependency order: dependents first, then dependencies\n```\n\n## See Also\n\n- `probe-patterns.md` for crafting good `readiness_probe`s (without these, `process_healthy` is useless)\n- `schema-reference.md` for full availability/shutdown field semantics\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":4978,"content_sha256":"9307f9ee1acdceeb515ccf7866db3b47fc5177889e2af7e57a110ad2dd423d1d"},{"filename":"references/probe-patterns.md","content":"# Readiness Probe Patterns\n\nConcrete probe recipes by stack. Used in `process-compose.yaml`'s `readiness_probe` field.\n\n## Python web servers (Django, Flask, FastAPI)\n\n### Has a health endpoint\n\n```yaml\nreadiness_probe:\n http_get:\n host: localhost\n port: 8000\n path: /health/\n initial_delay_seconds: 5\n period_seconds: 10\n timeout_seconds: 3\n failure_threshold: 3\n```\n\n### No health endpoint (use any 200-returning path)\n\n```yaml\nreadiness_probe:\n http_get:\n host: localhost\n port: 8000\n path: / # bare root; whatever returns 200\n initial_delay_seconds: 10 # Django often takes 5-15s to come up\n period_seconds: 10\n failure_threshold: 3\n```\n\n### Auth-required app\n\nIf `/` returns 302 redirecting to login, that's still healthy (server is up). Probe a path that handles redirects:\n\n```yaml\nreadiness_probe:\n http_get:\n host: localhost\n port: 8000\n path: /\n # PC follows redirects; 200/302/301 all count as healthy\n initial_delay_seconds: 10\n```\n\n### Long-running startup (DB migrations, model loading)\n\n```yaml\nreadiness_probe:\n http_get:\n host: localhost\n port: 8000\n path: /ready\n initial_delay_seconds: 30 # give Django apps with migrations ~30s\n period_seconds: 15\n timeout_seconds: 5\n failure_threshold: 5 # tolerant during warmup\navailability:\n restart: always\n backoff_seconds: 10 # longer backoff matching startup cost\n```\n\n## Go binaries\n\nMost Go web servers come up in \u003c 1 second:\n\n```yaml\nreadiness_probe:\n http_get:\n host: localhost\n port: 8080\n path: /\n initial_delay_seconds: 1\n period_seconds: 5\n failure_threshold: 3\n```\n\n## Node.js / Express / Next.js\n\n```yaml\nreadiness_probe:\n http_get:\n host: localhost\n port: 3000\n path: /\n initial_delay_seconds: 5 # Next.js cold start\n period_seconds: 10\n timeout_seconds: 3\n```\n\nFor dev servers (`next dev`), the initial route may not be ready immediately. Use a known static asset path or `_next/static/` if the home route is dynamic.\n\n## Static file servers (`python -m http.server`)\n\n```yaml\nreadiness_probe:\n http_get:\n host: localhost\n port: 8000\n path: /\n initial_delay_seconds: 1\n period_seconds: 5\n```\n\n## TCP-only services (databases, message queues, custom protocols)\n\n```yaml\nreadiness_probe:\n tcp_socket:\n host: localhost\n port: 5432\n initial_delay_seconds: 5\n period_seconds: 10\n failure_threshold: 3\n```\n\n## Stuff that doesn't expose ports (daemons, watchers, cron-like)\n\nUse `exec` probe with a custom check, or skip probes entirely:\n\n```yaml\n# Option A — exec probe checks daemon's pid file or self-reported status\nreadiness_probe:\n exec:\n command: \"test -f /var/run/myd.pid\"\n initial_delay_seconds: 5\n period_seconds: 30\n\n# Option B — no probe at all; depends_on only uses process_started\n# (no readiness_probe block, just availability config)\navailability:\n restart: always\n```\n\n## When the probe is failing — debugging\n\n1. **Check the actual port:** does the service really bind that port? `netstat -ano | grep :8000` (Linux/Mac: `lsof -i :8000`)\n2. **Check the actual path:** does `curl -i http://localhost:8000/health` return 2xx/3xx? PC follows redirects but doesn't accept 4xx/5xx as healthy.\n3. **Check initial_delay_seconds:** does the service take longer than this to come up?\n4. **Check failure_threshold:** is the service flaky, returning 5xx intermittently?\n\n## Anti-patterns\n\n```yaml\n# BAD: probing a path that requires auth and returns 401\nreadiness_probe:\n http_get: { port: 8000, path: /api/users/me }\n\n# BAD: probing a path that 404s during startup but eventually 200s\n# (the probe sees 404 → marks Not Ready → never recovers)\nreadiness_probe:\n http_get: { port: 8000, path: /admin/some-resource }\n\n# BAD: zero initial_delay_seconds — guarantees first probe sees connection refused\nreadiness_probe:\n http_get: { port: 8000, path: / }\n initial_delay_seconds: 0 # don't do this\n```\n\n## See Also\n\n- `dependency-patterns.md` for using readiness probes with `depends_on`\n- Upstream docs: https://f1bonacc1.github.io/process-compose/health/\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":4104,"content_sha256":"d1932387f9428c0a1202a7f7fd61d67d937a778e6c292e9870bfc28bdf8219d3"},{"filename":"references/schema-reference.md","content":"# process-compose.yaml Schema Reference\n\nComprehensive reference for the YAML schema (`version: \"0.5\"`). Annotated with field semantics, defaults, and gotchas.\n\n## Top-level\n\n```yaml\nversion: \"0.5\" # required; current schema version\n\nlog_level: info # debug | info | warn | error (default: info)\nlog_length: 1000 # lines retained in TUI's in-memory log buffer\nlog_no_color: false # disable ANSI colour in log file\nlog_timestamps: true # prepend ISO-8601 timestamp to each line\nlog_truncate: false # truncate logs on startup instead of appending\n\nprocesses: # required; map of process-name → spec\n \u003cname>:\n ... # see Process Spec below\n\nenvironment: # OPTIONAL global env vars applied to every process\n - \"GLOBAL_VAR=value\"\n```\n\n## Process Spec\n\n### Required\n\n```yaml\ncommand: \"string\" # shell command to execute (no shell interpolation\n # unless explicitly wrapped — see \"command quoting\"\n # below)\n```\n\n### Recommended\n\n```yaml\nworking_dir: \"/abs/path\" # cwd; otherwise inherits PC's cwd\nenvironment: # array of KEY=value strings\n - \"ENV_VAR=value\"\n - \"PYTHONUNBUFFERED=1\"\n```\n\n### Lifecycle\n\n```yaml\navailability:\n restart: always # always | exit_on_failure | on_failure | no\n backoff_seconds: 5 # delay before next restart attempt\n max_restarts: 20 # absolute cap; 0 = unlimited\n exit_on_skipped: false # treat skipped-by-dep failure as exit\n schedule: \"0 2 * * *\" # cron schedule for periodic execution\n # (mutually exclusive with restart: always)\n\nshutdown:\n signal: 15 # SIGTERM (default) or 9 for SIGKILL\n timeout_seconds: 30 # SIGKILL escalation deadline\n command: \"graceful-cli stop\" # optional pre-stop command\n```\n\n### Health checks\n\n```yaml\nreadiness_probe: # marks process \"Ready\" for depends_on\n http_get:\n host: localhost\n port: 8000\n path: /health # bare \"/\" is fine if no health endpoint\n scheme: HTTP # HTTP (default) or HTTPS\n # OR alternative probe types:\n # exec:\n # command: \"curl -f http://localhost:8000/health\"\n # tcp_socket:\n # host: localhost\n # port: 8000\n initial_delay_seconds: 5 # wait before first probe\n period_seconds: 10 # interval between probes\n timeout_seconds: 3 # per-probe timeout\n success_threshold: 1 # consecutive successes to mark Ready\n failure_threshold: 3 # consecutive failures to mark Not Ready\n\nliveness_probe: # restarts process if probe fails\n ... # same shape as readiness_probe\n```\n\n### Dependencies\n\n```yaml\ndepends_on:\n database:\n condition: process_healthy # wait until database's readiness_probe passes\n migrations:\n condition: process_completed_successfully # wait for one-shot init\n```\n\nConditions:\n\n| Condition | Wait until... | Use when |\n|---|---|---|\n| `process_started` | Dependency spawned (PID exists) | Weakest; use only when ordering matters but readiness doesn't |\n| `process_healthy` | Dependency's readiness_probe passes | Strongest; preferred for runtime services |\n| `process_completed` | Dependency exited (any code) | One-shot init that may fail |\n| `process_completed_successfully` | Dependency exited 0 | One-shot init that must succeed |\n\n### Logging\n\n```yaml\nlog_location: \"logs/myapp.log\" # relative to PC's cwd or absolute path\nlog_max_size_kb: 0 # rotation threshold; 0 = no rotation\nlog_max_backups: 0 # rotated files to retain\nlog_max_age_days: 0 # age-based rotation\nlog_compress: false # gzip rotated logs\n```\n\n### Identity / grouping\n\n```yaml\nnamespace: \"backend\" # group processes; --namespace flag filters by it\nreplicas: 3 # spawn N independent copies (named myapp@0, @1, @2)\ndisabled: false # exclude from `up` without deleting the spec\nis_daemon: false # set true for processes that fork and exit\n # (e.g. systemd-style daemons)\n```\n\n### Visibility\n\n```yaml\nis_foreground: false # show full output in TUI immediately\nis_tty: false # allocate a PTY (interactive processes)\ndisable_ansi_colors: false # strip ANSI from this process's logs\n```\n\n## Command Quoting\n\nYAML loves to surprise here. Three reliable patterns:\n\n```yaml\n# Pattern 1 — simple command, no quotes anywhere\ncommand: pythonw manage.py runserver 0.0.0.0:8000\n\n# Pattern 2 — single-quoted (literal, no escapes processed)\ncommand: 'pythonw \"C:\\Program Files\\foo\\app.py\" --port 8000'\n\n# Pattern 3 — double-quoted (escapes processed, watch the backslashes)\ncommand: \"pythonw -m my_module\"\n\n# Pattern 4 — wrap the exe path in double quotes inside a single-quoted string\ncommand: '\"C:/Program Files/Git/usr/bin/bash.exe\" --login script.sh'\n```\n\n**Windows PATH env vars must be single-quoted** to escape backslashes:\n\n```yaml\nenvironment:\n # WRONG: double quotes try to interpret \\P, \\U, etc. as escape codes\n # - \"PATH=C:\\Program Files\\Git\\usr\\bin;...\"\n\n # RIGHT:\n - 'PATH=C:\\Program Files\\Git\\usr\\bin;C:\\Users\\me\\AppData\\Local\\Programs\\Python\\Python313'\n```\n\n## File Composition\n\nYou can split config across files and merge:\n\n```bash\nprocess-compose up -f base.yaml -f overrides.yaml -f local.yaml\n```\n\nLater files override earlier ones. Useful pattern: base config in repo + per-machine overrides in gitignored `local.yaml`.\n\n## Validation\n\n```bash\nprocess-compose up -f process-compose.yaml --dry-run\n# → \"Validated N configured processes from M files.\"\n```\n\nRun before committing. Catches:\n- YAML parse errors (escape issues, indentation)\n- Missing required fields\n- Invalid restart policy values\n- Circular depends_on chains\n\n## Hot Reload\n\n```bash\nprocess-compose -p 8888 project update -f process-compose.yaml\n```\n\nReloads the config in a running PC instance. Added processes start; removed processes stop; changed processes restart.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6128,"content_sha256":"6a17c62c83305145ef81a0bdb0ce284182e24e07e4b487c5c2055f1c172ba7d5"},{"filename":"references/supply-chain-verification.md","content":"# Supply-Chain Verification for Process Compose\n\nProcess Compose ships as a single Go binary via GitHub Releases with SHA-256 checksums. This is structurally safer than npm/PyPI packages but still requires verification before use.\n\n## Why bother\n\nEven with Go's `go.sum` model:\n- The compiled binary is what runs, not the source — must verify the binary matches what was built from verified source\n- GitHub Releases artifacts can theoretically be tampered if repo permissions are compromised\n- Checksums file is on GitHub Releases too, so without a separate signature, you're trusting GitHub auth integrity\n- No GPG signing on Process Compose releases (as of v1.110.0) — relies entirely on GitHub Release access controls\n\n## The Procedure\n\n### 1. Download from official release\n\n```bash\nVER=\"v1.110.0\" # pin a specific tag\nBASE=\"https://github.com/F1bonacc1/process-compose/releases/download/$VER\"\n\ncurl -fsSL -o pc.zip \"$BASE/process-compose_windows_amd64.zip\"\ncurl -fsSL -o process-compose_checksums.txt \"$BASE/process-compose_checksums.txt\"\n```\n\n### 2. Verify hash BEFORE extraction\n\n```bash\nEXPECTED=$(grep \"process-compose_windows_amd64.zip\" process-compose_checksums.txt | awk '{print $1}')\nACTUAL=$(sha256sum pc.zip | awk '{print $1}')\n\n[ \"$EXPECTED\" = \"$ACTUAL\" ] || { echo \"HASH MISMATCH - ABORT\"; exit 1; }\n```\n\n**Never** extract or run the binary before this check passes.\n\n### 3. Extract, record the binary's own hash\n\n```bash\nunzip pc.zip\nEXE_HASH=$(sha256sum process-compose.exe | awk '{print $1}')\necho \"$EXE_HASH\" > bin/EXE_HASH\n```\n\nThis is the hash you re-verify on future installs to confirm the binary in your repo hasn't been tampered.\n\n### 4. Commit binary + checksums to repo\n\n```bash\ngit add bin/process-compose.exe \\\n bin/process-compose_checksums.txt \\\n bin/VERSION # contains \"v1.110.0\"\ngit commit -m \"feat: pin process-compose $VER, verified SHA-256\"\n```\n\n### 5. Document the verification\n\nWrite a `bin/VERIFICATION.md`:\n\n```markdown\n# Binary Verification\n\n## process-compose.exe — v1.110.0\n\n- Pinned: 2026-MM-DD\n- Source: https://github.com/F1bonacc1/process-compose/releases/tag/v1.110.0\n- ZIP SHA-256: 018c660f... (matched checksums.txt)\n- EXE SHA-256: 2e2a09a9...858637 (recorded for re-verification)\n- Runtime check: \"Process Compose v1.110.0, Commit cd7f6af\"\n\nTrust anchor: GitHub Releases (HTTPS, requires repo write access to tamper).\nLimitation: No GPG signing on Process Compose releases.\n```\n\n## Re-verification\n\nPeriodically — or as part of CI — re-hash the committed binary and confirm it matches the recorded value:\n\n```powershell\n# Windows\n$expected = (Get-Content bin/EXE_HASH).Trim()\n$actual = (Get-FileHash bin/process-compose.exe -Algorithm SHA256).Hash.ToLower()\nif ($expected -ne $actual) { throw \"binary tampered\" }\n```\n\n```bash\n# Unix\nexpected=$(cat bin/EXE_HASH | tr -d '[:space:]')\nactual=$(sha256sum bin/process-compose.exe | awk '{print $1}')\n[ \"$expected\" = \"$actual\" ] || { echo \"binary tampered\"; exit 1; }\n```\n\n## Upgrade Procedure\n\nTo bump versions:\n\n1. Run the download + verify procedure above with the new version tag\n2. Replace `bin/process-compose.exe`, `bin/process-compose_checksums.txt`, `bin/VERSION`, `bin/EXE_HASH`\n3. Update `VERIFICATION.md` with new hashes + date\n4. Run a parallel test (non-prod port) before cutting over\n5. Single PR with all of the above; review before merge\n\n## What's NOT Covered\n\nThe verification confirms **you got the binary the project intended to publish**. It does NOT cover:\n\n- Compromise of the project's source code (would need full source audit)\n- Compromise of the build environment (GoReleaser + GitHub Actions infrastructure)\n- The Go modules the binary was compiled with (transitive dependency risk)\n\nFor deeper supply-chain analysis: tools like `osv-scanner`, `govulncheck`, or commercial tools (Socket.dev, Snyk) inspect the **source** dependency tree. Use those upstream of the verification step.\n\n## See Also\n\n- `boot-persistence-windows.md` for how to launch the verified binary at boot\n- The repo's own AGENTS.md should document the pinned version policy\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":4164,"content_sha256":"765edeb96f34932f44a6a2ac894ad913b8ffa94b016f91e9fe79281a4fe2cb89"},{"filename":"references/tui-shortcuts.md","content":"# TUI Shortcuts Cheatsheet\n\nLaunch:\n\n```bash\nprocess-compose attach # connect to the default port (8080)\nprocess-compose -p 8888 attach # connect to a non-default API port\n```\n\nIf you launched PC with `-t=false` (headless), `attach` is how you bring up the TUI later. Quitting the TUI with `q` does **not** stop PC.\n\n## Layout\n\n```\n┌──────────────────────────────────────────────────────────────┐\n│ Version + Project info │\n│ Resources (RAM, CPU) │\n├──────────────────────────────────────────────────────────────┤\n│ PROCESS LIST (focused by default) │\n│ PID NAME NS STATUS AGE HEALTH RESTARTS EX │\n│ ... │\n├──────────────────────────────────────────────────────────────┤\n│ LOG PANE (logs for selected process) │\n│ [timestamp] log line │\n│ [timestamp] log line │\n├──────────────────────────────────────────────────────────────┤\n│ F1 Shortcuts F2 Scale F3 Find F4 Maximize ... │\n└──────────────────────────────────────────────────────────────┘\n```\n\n## Navigation\n\n| Key | Action |\n|---|---|\n| `↑` / `↓` | Move process selection up/down |\n| `k` / `j` | Same (vim-style) |\n| `Tab` | Move focus between process list and log pane |\n| `Home` / `End` | Jump to first / last process |\n| `Page Up` / `Page Down` | Page through process list |\n\n## Pane controls\n\n| Key | Action |\n|---|---|\n| `F4` | Maximize current pane (toggle — second press un-maximizes) |\n| `F5` | Toggle log follow (unfollow lets you scroll history) |\n| `F6` | Toggle log wrap |\n| `Ctrl-S` | Toggle \"select on\" — clicks select process |\n\n## Process control (selected process)\n\n| Key | Action |\n|---|---|\n| `r` | Restart |\n| `s` | Stop (graceful — SIGTERM) |\n| `t` | Start (if stopped) |\n| `Ctrl-D` | Disable the process (won't auto-restart) |\n| `Ctrl-E` | Re-enable a disabled process |\n\n## Search / filter\n\n| Key | Action |\n|---|---|\n| `/` | Open filter input (filter process list by name) |\n| `F3` | Find in logs (search current log pane) |\n| `n` / `N` | Next / previous match in logs |\n| `Esc` | Clear filter / cancel input |\n\n## Sorting\n\n| Key | Action |\n|---|---|\n| `Ctrl-N` | Sort by Name |\n| `Ctrl-T` | Sort by Status |\n| `Ctrl-A` | Sort by Age |\n| `Ctrl-H` | Sort by Health |\n| `R` (uppercase) | Reverse current sort |\n\n## Status column legend\n\n| Status | Meaning |\n|---|---|\n| `Running` | Process is up |\n| `Ready` | `readiness_probe` passing (only shown if probe defined) |\n| `Not Ready` | Probe failing; PC will keep checking |\n| `Restarting` | Between restart attempts (`backoff_seconds`) |\n| `Completed` | Exited successfully (only for non-restart processes) |\n| `Failed` | Exited with error and out of restart budget |\n| `Pending` | Waiting on `depends_on` to be satisfied |\n| `Disabled` | Manually disabled or `disabled: true` in YAML |\n| `Skipped` | A dependency failed so this process was skipped |\n\n## Exit\n\n| Key | Action |\n|---|---|\n| `q` | Quit TUI (PC keeps running headless) |\n| `Ctrl-C` | Same |\n| `?` | Show help overlay |\n\n## Headless workflow (no TUI)\n\nIf you don't want the TUI but need to peek at state:\n\n```bash\n# Process list\nprocess-compose -p 8888 process list\n\n# Logs for one process\nprocess-compose -p 8888 process logs my-service --follow\nprocess-compose -p 8888 process logs my-service # one-shot, no follow\n\n# Control without TUI\nprocess-compose -p 8888 process restart my-service\nprocess-compose -p 8888 process stop my-service\nprocess-compose -p 8888 process start my-service\n```\n\n## See Also\n\n- Schema reference for `is_foreground`, `is_tty`, `disable_ansi_colors` which affect log rendering in the TUI\n- Upstream docs: https://f1bonacc1.github.io/process-compose/tui/\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":4583,"content_sha256":"f8fa592c5b407b7718d7156806f22ec4c4e563e3da2bfa2ddf3c9967021c1710"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Process Compose Operations","type":"text"}]},{"type":"paragraph","content":[{"text":"Process Compose is a Go-based supervisor for non-containerized services. Single binary, YAML config, built-in TUI, REST API, ","type":"text"},{"text":"MCP server","type":"text","marks":[{"type":"strong"}]},{"text":", and proper Windows support. Replacement for PM2/supervisord/Foreman in the local-dev role.","type":"text"}]},{"type":"paragraph","content":[{"text":"Why not PM2:","type":"text","marks":[{"type":"strong"}]},{"text":" PM2 5.x has 15+ known CVEs (axios/lodash/tar/minimist transitive npm exposure). PC compiles all deps in at build time with ","type":"text"},{"text":"go.sum","type":"text","marks":[{"type":"code_inline"}]},{"text":" hashes — structurally resistant to TanStack-style npm worm attacks.","type":"text"}]},{"type":"paragraph","content":[{"text":"Why not Docker Compose:","type":"text","marks":[{"type":"strong"}]},{"text":" Container overhead is unnecessary for local Python/Node/Go dev servers running directly. PC gives you health checks, dependencies, and restart policies without the container layer.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Install (verified)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Pin a specific version, verify SHA-256 against upstream checksums\nVER=\"v1.110.0\"\nBASE=\"https://github.com/F1bonacc1/process-compose/releases/download/$VER\"\n\ncurl -fsSL -o pc.zip \"$BASE/process-compose_windows_amd64.zip\"\ncurl -fsSL -o checksums.txt \"$BASE/process-compose_checksums.txt\"\n\nEXPECTED=$(grep \"process-compose_windows_amd64.zip\" checksums.txt | awk '{print $1}')\nACTUAL=$(sha256sum pc.zip | awk '{print $1}')\n[ \"$EXPECTED\" = \"$ACTUAL\" ] || { echo \"HASH MISMATCH\"; exit 1; }\n\nunzip pc.zip\n# Commit process-compose.exe to your repo's bin/ directory","type":"text"}]},{"type":"paragraph","content":[{"text":"Record the binary's hash in your repo's ","type":"text"},{"text":"SUPPLY-CHAIN.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for re-verification on next upgrade.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"process-compose.yaml Quick Reference","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"version: \"0.5\"\n\nlog_level: info\nlog_length: 1000\n\nprocesses:\n\n my-service:\n command: \"pythonw -m uvicorn main:app --host 127.0.0.1 --port 8000\"\n working_dir: \"X:/path/to/repo\"\n environment:\n - \"DJANGO_SETTINGS_MODULE=myapp.settings\"\n - \"PYTHONUNBUFFERED=1\"\n readiness_probe:\n http_get:\n host: localhost\n port: 8000\n path: /\n initial_delay_seconds: 5\n period_seconds: 10\n timeout_seconds: 3\n failure_threshold: 3\n availability:\n restart: always # always | exit_on_failure | on_failure | no\n backoff_seconds: 5\n max_restarts: 20\n depends_on:\n database:\n condition: process_healthy # process_started | process_healthy | process_completed\n shutdown:\n signal: 15 # SIGTERM\n timeout_seconds: 30\n log_location: \"logs/my-service.log\"\n\n scheduled-job:\n command: \"python backup.py\"\n schedule: \"0 2 * * *\" # 2am daily cron\n availability:\n restart: exit_on_failure","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Restart Policies","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Policy","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Restarts on...","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"always","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Any exit (success or failure) — best for long-running daemons","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"on_failure","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Non-zero exit codes only","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"exit_on_failure","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stops PC entirely if this process fails — use for critical deps","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"no","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Never restart","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Dependency Conditions","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Condition","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wait until...","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"process_started","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dependency spawned (PID exists). Fastest, weakest guarantee.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"process_healthy","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dependency's readiness_probe passes. Strong guarantee.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"process_completed","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dependency exited successfully (for init/setup processes).","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"CLI Reference","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Lifecycle\nprocess-compose up -f config.yaml # Start (foreground TUI by default)\nprocess-compose up -f config.yaml -t=false # Headless (no TUI)\nprocess-compose up -f config.yaml --dry-run # Validate config without starting\nprocess-compose down # Stop all processes + project\n\n# Inspection (against running PC)\nprocess-compose -p 8888 process list # all processes + status\nprocess-compose -p 8888 process logs \u003cname> --follow\nprocess-compose -p 8888 attach # TUI for running project\n\n# Process control\nprocess-compose -p 8888 process restart \u003cname>\nprocess-compose -p 8888 process stop \u003cname>\nprocess-compose -p 8888 process start \u003cname>\n\n# Reload config without stopping (hot update)\nprocess-compose -p 8888 project update -f config.yaml\n\n# Standalone inspection (no running PC)\nprocess-compose info # config home info\nprocess-compose graph -f config.yaml # dependency graph\nprocess-compose analyze -f config.yaml # startup timing analysis","type":"text"}]},{"type":"paragraph","content":[{"text":"Key flag gotcha:","type":"text","marks":[{"type":"strong"}]},{"text":" there's no ","type":"text"},{"text":"--detached","type":"text","marks":[{"type":"code_inline"}]},{"text":" flag. To run in background:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Linux/Mac: ","type":"text"},{"text":"process-compose up -t=false &","type":"text","marks":[{"type":"code_inline"}]},{"text":" (shell backgrounding)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Windows: launch via Task Scheduler or ","type":"text"},{"text":"Start-Process","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"-WindowStyle Hidden","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"TUI Navigation","type":"text"}]},{"type":"paragraph","content":[{"text":"Launch: ","type":"text"},{"text":"process-compose attach","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or ","type":"text"},{"text":"up","type":"text","marks":[{"type":"code_inline"}]},{"text":" without ","type":"text"},{"text":"-t=false","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Key","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Action","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"↑","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"↓","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"j","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"k","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Navigate process list","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tab","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Switch focus between process list and log pane","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"F4","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Maximize current pane (toggle)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"F5","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Unfollow logs (lets you scroll history)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"F6","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Unwrap log lines","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"r","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Restart selected process","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"s","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stop selected process","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"t","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Start selected process","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Filter process list","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"?","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Help overlay","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"q","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Quit TUI (PC keeps running in background)","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"MCP Server Integration","type":"text"}]},{"type":"paragraph","content":[{"text":"PC ships a built-in MCP server exposing processes as tools for AI agents. Enable via the config or CLI flag. With the MCP server on, a Claude Code agent can directly:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"List running processes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Get process status/health","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Restart/stop/start processes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read process logs","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"This replaces shell-based glue scripts (the old PM2-broker pattern).","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"API Port Selection","type":"text"}]},{"type":"paragraph","content":[{"text":"Default API port is 8080. Common collisions:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Port 8080 user","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Workaround","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dagu dashboard","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"-p 8888","type":"text","marks":[{"type":"code_inline"}]},{"text":" until Dagu decommissioned","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tomcat / Spring Boot dev","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"-p 8888","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Other dev tool defaults","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pick anything free in 8000–9999 range","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"If you change the API port, every subsequent CLI call needs ","type":"text"},{"text":"-p \u003cport>","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"process-compose -p 8888 process list\nprocess-compose -p 8888 process logs axiom --follow","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Windows Boot Persistence Pattern","type":"text"}]},{"type":"paragraph","content":[{"text":"Task Scheduler runs with minimal PATH. Use a wrapper script that sets PATH explicitly before launching PC.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"powershell"},"content":[{"text":"# scripts/boot-start.ps1\n$root = \"X:\\00_Orchestration\\compose-portless\"\n$pcExe = \"$root\\bin\\process-compose.exe\"\n\n# Explicit PATH for managed services (Python, uv, Git tools, cloudflared, etc.)\n$env:PATH = (@(\n \"$root\\bin\"\n \"C:\\Program Files\\Git\\usr\\bin\" # openssl, bash\n \"C:\\Users\\\u003cuser>\\AppData\\Local\\Programs\\Python\\Python313\\Scripts\"\n \"$env:PATH\"\n) -join ';')\n\n# Optional: source secrets from gitignored .env\n$envFile = \"$root\\.env\"\nif (Test-Path $envFile) {\n Get-Content $envFile | ForEach-Object {\n if ($_ -match '^\\s*([A-Z_]+)\\s*=\\s*(.+?)\\s*

Process Compose Operations Process Compose is a Go-based supervisor for non-containerized services. Single binary, YAML config, built-in TUI, REST API, MCP server , and proper Windows support. Replacement for PM2/supervisord/Foreman in the local-dev role. Why not PM2: PM2 5.x has 15+ known CVEs (axios/lodash/tar/minimist transitive npm exposure). PC compiles all deps in at build time with hashes — structurally resistant to TanStack-style npm worm attacks. Why not Docker Compose: Container overhead is unnecessary for local Python/Node/Go dev servers running directly. PC gives you health checks…

) {\n [Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process')\n }\n }\n}\n\n# Launch headless\n& $pcExe -p 8888 -t=false -L \"$root\\logs\\process-compose.log\" up -f \"$root\\process-compose.yaml\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Register as a Task Scheduler entry with ","type":"text"},{"text":"LogonType S4U","type":"text","marks":[{"type":"code_inline"}]},{"text":" (runs at boot, no password, no interactive logon needed):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"powershell"},"content":[{"text":"$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType S4U -RunLevel Highest\n$action = New-ScheduledTaskAction -Execute \"powershell.exe\" `\n -Argument \"-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `\"$root\\scripts\\boot-start.ps1`\"\"\n$trigger = New-ScheduledTaskTrigger -AtStartup\nRegister-ScheduledTask -TaskName \"ProcessCompose-Boot\" `\n -Action $action -Trigger $trigger -Principal $principal -Force","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"YAML Gotchas","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Gotcha","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Symptom","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Windows PATH with backslashes in double-quoted YAML","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"yaml: found unknown escape character","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use single quotes: ","type":"text"},{"text":"- 'PATH=C:\\Program Files\\Git\\usr\\bin;...'","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"command","type":"text","marks":[{"type":"code_inline"}]},{"text":" with quoted paths containing spaces","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"First arg eaten","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wrap whole command in single quotes, inner paths in double: ","type":"text"},{"text":"'\"C:/Program Files/foo.exe\" arg1 arg2'","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Forgot ","type":"text"},{"text":"working_dir","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Process starts in PC's cwd, can't find files","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Always specify absolute ","type":"text"},{"text":"working_dir","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Health probe wrong port","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Process restart-loops with ","type":"text"},{"text":"Not Ready","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Match ","type":"text"},{"text":"readiness_probe.http_get.port","type":"text","marks":[{"type":"code_inline"}]},{"text":" to where the process actually binds","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Secrets in YAML","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Committed to git","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"environment","type":"text","marks":[{"type":"code_inline"}]},{"text":" to pass-through; set in shell env or gitignored ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Operations","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Validate config before applying\nprocess-compose up --dry-run -f process-compose.yaml\n\n# Hot-reload after editing config\nprocess-compose -p 8888 project update -f process-compose.yaml\n\n# Restart one service after code change\nprocess-compose -p 8888 process restart axiom\n\n# Watch logs of a misbehaving service\nprocess-compose -p 8888 process logs axiom --follow\n\n# Stop one service temporarily for debugging\nprocess-compose -p 8888 process stop axiom\n# Now run it manually with your debugger, then:\nprocess-compose -p 8888 process start axiom","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use Process Compose vs Alternatives","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Need","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tool","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Local non-containerized services with health/dependencies/MCP","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Process Compose","type":"text","marks":[{"type":"strong"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Production node.js process supervision","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PM2 (despite age)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Container-based stack","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Docker Compose","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Job queue with cron + DAGs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dagu, Temporal, Airflow","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"System service supervision","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"systemd (Linux), Windows Services","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"One-shot Procfile run","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Foreman / Overmind / Hivemind (Unix-only)","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Worked Example","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"X:\\00_Orchestration\\compose-portless\\","type":"text","marks":[{"type":"code_inline"}]},{"text":" for an 11-process production stack:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"process-compose.yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" — health-checked services with depends_on chains","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/boot-start.ps1","type":"text","marks":[{"type":"code_inline"}]},{"text":" — PATH-aware boot wrapper","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"docs/MIGRATION-LOG.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — full migration from PM2 + Caddy, every gotcha documented","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"docs/SUPPLY-CHAIN.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — binary verification procedure","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Anti-Patterns","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"BAD: process-compose up --detached # flag does not exist\nGOOD: process-compose up -t=false & # background via shell\n\nBAD: put secrets in process-compose.yaml (commits to git)\nGOOD: source from gitignored .env in boot wrapper\n\nBAD: use API port 8080 (clashes with Dagu, Tomcat, others)\nGOOD: -p 8888 (or any free port), document the choice\n\nBAD: ignore readiness_probe and just hope services come up\nGOOD: configure http_get probe on a real endpoint; depends_on uses process_healthy\n\nBAD: upgrade PC by running an installer (npm install -g, scoop install, brew install)\nGOOD: download specific version, verify SHA-256 against upstream checksums.txt, commit binary","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Resources in this skill","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"references/","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"schema-reference.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — full process-compose.yaml schema with field semantics, defaults, and command-quoting gotchas","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"probe-patterns.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — readiness probe recipes by stack (Python, Go, Node, TCP-only, daemons)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"dependency-patterns.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — ","type":"text"},{"text":"depends_on","type":"text","marks":[{"type":"code_inline"}]},{"text":" patterns: companion daemons, DB-before-app, tunnel-after-service, one-shot init","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"tui-shortcuts.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — TUI cheatsheet (keys, status legend, search/sort/filter)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"boot-persistence-windows.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Task Scheduler setup with S4U logon, PATH-aware wrapper","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"supply-chain-verification.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — full SHA-256 verification procedure for the binary","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"scripts/","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"install-process-compose.ps1","type":"text","marks":[{"type":"code_inline"}]},{"text":" — download + verify + extract a pinned version, writes VERIFICATION.md","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"verify-binary.ps1","type":"text","marks":[{"type":"code_inline"}]},{"text":" — re-verify committed binary hash (monthly / pre-commit)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"boot-start.template.ps1","type":"text","marks":[{"type":"code_inline"}]},{"text":" — PATH-aware boot wrapper (copy + adapt per machine)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"boot-task-install.template.ps1","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Task Scheduler entry registration (S4U logon)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"assets/","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"python-uvicorn.yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" — uvicorn/FastAPI/Django basic service template","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"django-with-companions.yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Django + queue daemon + audit watcher chain","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"go-binary-service.yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Go binary with HTTP or TCP probe","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"tunnel-with-dependency.yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Cloudflare tunnel waiting on its target service","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"cron-job.yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" — scheduled task patterns","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Related Skills","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"portless-ops","type":"text","marks":[{"type":"code_inline"}]},{"text":" — the routing layer we pair with PC (replaces Caddy)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"docker-ops","type":"text","marks":[{"type":"code_inline"}]},{"text":" — container alternative for the same role","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"mcp-ops","type":"text","marks":[{"type":"code_inline"}]},{"text":" — PC's MCP server fits this ecosystem","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"cli-ops","type":"text","marks":[{"type":"code_inline"}]},{"text":" — general CLI tool patterns","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"process-compose-ops","author":"@skillopedia","source":{"stars":21,"repo_name":"claude-mods","origin_url":"https://github.com/0xdarkmatter/claude-mods/blob/HEAD/skills/process-compose-ops/SKILL.md","repo_owner":"0xdarkmatter","body_sha256":"f5ede7cc320d9ee9bfd689dd293ccaf3655842c693d9cd8be4fe0944bbf6f766","cluster_key":"eaf87aa5eb4865b92d15e3bdc06ec53f199158fb7e1e46b894308e21189bd953","clean_bundle":{"format":"clean-skill-bundle-v1","source":"0xdarkmatter/claude-mods/skills/process-compose-ops/SKILL.md","attachments":[{"id":"d7efb6d9-c1b2-5deb-8092-64401914006e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d7efb6d9-c1b2-5deb-8092-64401914006e/attachment.yaml","path":"assets/cron-job.yaml","size":968,"sha256":"9eaab9f504525f586b1e154e217b4b5905b27ae0520f8ae14097132dce930ece","contentType":"application/yaml; charset=utf-8"},{"id":"2b0fae22-9e4b-5d4c-a4e8-81dd1a57c495","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2b0fae22-9e4b-5d4c-a4e8-81dd1a57c495/attachment.yaml","path":"assets/django-with-companions.yaml","size":2670,"sha256":"cc7f4ac59190fd674798591369b72c2efc538268bdab671f609040305885a0a6","contentType":"application/yaml; charset=utf-8"},{"id":"b6bc52f3-340c-5c0b-8a59-aa38d0c6e945","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b6bc52f3-340c-5c0b-8a59-aa38d0c6e945/attachment.yaml","path":"assets/go-binary-service.yaml","size":848,"sha256":"40f564115a345ce149d994ca8a27ba8e8a1e3008589aec3ec93be4ff4bd4ba62","contentType":"application/yaml; charset=utf-8"},{"id":"f1eff4ea-6aab-5909-aed5-b5f0f8aaf2b6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f1eff4ea-6aab-5909-aed5-b5f0f8aaf2b6/attachment.yaml","path":"assets/python-uvicorn.yaml","size":1082,"sha256":"4c90f193c2b86f5ab67cf0060103916c6afa19aa31a6d36a54e58b0eaf942b8e","contentType":"application/yaml; charset=utf-8"},{"id":"1bc37d99-e360-5f22-b1e8-155a534574d2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1bc37d99-e360-5f22-b1e8-155a534574d2/attachment.yaml","path":"assets/tunnel-with-dependency.yaml","size":1510,"sha256":"172a25089b828190a6f1184ae92f0b58331073846d95f570a8249128ea6d6c3d","contentType":"application/yaml; charset=utf-8"},{"id":"3e52f8c1-f784-5cf2-b544-ca2269135056","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3e52f8c1-f784-5cf2-b544-ca2269135056/attachment.md","path":"references/boot-persistence-windows.md","size":7203,"sha256":"9ebb4b40f2fd33d3877c75a4ce49498fcd8487c066a0987aec6153a5f049cb11","contentType":"text/markdown; charset=utf-8"},{"id":"4df0f063-7380-5921-910a-e827e5b32968","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4df0f063-7380-5921-910a-e827e5b32968/attachment.md","path":"references/dependency-patterns.md","size":4978,"sha256":"9307f9ee1acdceeb515ccf7866db3b47fc5177889e2af7e57a110ad2dd423d1d","contentType":"text/markdown; charset=utf-8"},{"id":"aa5bc286-79c8-5f99-bea7-07db121d6cd5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/aa5bc286-79c8-5f99-bea7-07db121d6cd5/attachment.md","path":"references/probe-patterns.md","size":4104,"sha256":"d1932387f9428c0a1202a7f7fd61d67d937a778e6c292e9870bfc28bdf8219d3","contentType":"text/markdown; charset=utf-8"},{"id":"451038e6-4720-5b90-904f-1f0203456f69","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/451038e6-4720-5b90-904f-1f0203456f69/attachment.md","path":"references/schema-reference.md","size":6128,"sha256":"6a17c62c83305145ef81a0bdb0ce284182e24e07e4b487c5c2055f1c172ba7d5","contentType":"text/markdown; charset=utf-8"},{"id":"3935a80a-5df6-5c02-a937-6f9d36e5ac6f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3935a80a-5df6-5c02-a937-6f9d36e5ac6f/attachment.md","path":"references/supply-chain-verification.md","size":4164,"sha256":"765edeb96f34932f44a6a2ac894ad913b8ffa94b016f91e9fe79281a4fe2cb89","contentType":"text/markdown; charset=utf-8"},{"id":"0b9e745a-e545-55e4-a39e-c2278495dd91","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0b9e745a-e545-55e4-a39e-c2278495dd91/attachment.md","path":"references/tui-shortcuts.md","size":4583,"sha256":"f8fa592c5b407b7718d7156806f22ec4c4e563e3da2bfa2ddf3c9967021c1710","contentType":"text/markdown; charset=utf-8"},{"id":"da3b732e-9820-5a5b-9892-a130379efd40","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/da3b732e-9820-5a5b-9892-a130379efd40/attachment.ps1","path":"scripts/boot-start.template.ps1","size":3672,"sha256":"22d64e6f91b2dc6306956b773154acb7dfc90448b894bb3d7946497a482695ed","contentType":"text/plain; charset=utf-8"},{"id":"e36a002a-cc0f-5d58-98de-9ef3312f9162","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e36a002a-cc0f-5d58-98de-9ef3312f9162/attachment.ps1","path":"scripts/boot-task-install.template.ps1","size":3243,"sha256":"add6f406a9d93b5c8717a49126ce9eefa6a5ce8f231a9c056730164dc7434326","contentType":"text/plain; charset=utf-8"},{"id":"b6f2f2aa-20e7-5ac0-bdd0-0b51ab0aae49","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b6f2f2aa-20e7-5ac0-bdd0-0b51ab0aae49/attachment.ps1","path":"scripts/install-process-compose.ps1","size":3817,"sha256":"180d66853afc0015a9a31f589394b7c5a0401e13d8d8a6b32163aab3093c316c","contentType":"text/plain; charset=utf-8"},{"id":"d64dab1e-fe46-5c81-ac71-f6c30ce9a900","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d64dab1e-fe46-5c81-ac71-f6c30ce9a900/attachment.ps1","path":"scripts/verify-binary.ps1","size":1715,"sha256":"043b267d448c190c433ae60619ce862196646facd9da880f775f5613bfd31a31","contentType":"text/plain; charset=utf-8"}],"bundle_sha256":"a87a5ebe8b7f5723501c7f841539fe3043378935738f9387536950176402cb59","attachment_count":15,"text_attachments":11,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":4,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/process-compose-ops/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"browser-automation-scraping","category_label":"Browser"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"browser-automation-scraping","metadata":{"author":"claude-mods","upstream":"https://github.com/F1bonacc1/process-compose","related-skills":"portless-ops, docker-ops, cli-ops"},"import_tag":"clean-skills-v1","description":"Process Compose orchestration for non-containerized local services. Use for: process-compose.yaml schema, replacing PM2/supervisord/Foreman, health checks (readiness_probe, liveness_probe), restart policies (always/exit_on_failure/no), process dependencies (depends_on conditions), TUI navigation and shortcuts (F4 maximize, Tab panes, r/s/t process control), REST API and MCP server integration, headless mode (-t=false for daemons), per-process and consolidated logging (log_location), cron and interval scheduling (availability.schedule), namespace grouping for multi-stack composition, environment variable handling (env files, secrets), Windows Task Scheduler boot persistence, supply-chain verified single-binary install, multi-replica processes, foreground/serial execution patterns, dry-run validation, project update (hot reload without restart), process restart/stop/start via CLI or TUI, log tailing and follow modes, shutdown timeouts and signals, agent-friendly MCP tools for process control.","allowed-tools":"Read Write Bash Edit"}},"renderedAt":1782979467520}

Process Compose Operations Process Compose is a Go-based supervisor for non-containerized services. Single binary, YAML config, built-in TUI, REST API, MCP server , and proper Windows support. Replacement for PM2/supervisord/Foreman in the local-dev role. Why not PM2: PM2 5.x has 15+ known CVEs (axios/lodash/tar/minimist transitive npm exposure). PC compiles all deps in at build time with hashes — structurally resistant to TanStack-style npm worm attacks. Why not Docker Compose: Container overhead is unnecessary for local Python/Node/Go dev servers running directly. PC gives you health checks…