Documentation
Stacksmith docs
Describe a local development stack in a .stacksmith.yml file, then run, inspect, diagnose, and control it from the macOS app or over MCP.
Quick start
Create .stacksmith.yml in your project root, open the project in Stacksmith, then start the stack. Stacksmith validates the config, resolves references, starts components in dependency order, and keeps runtime state tied to each component. It never modifies your project folder unless you explicitly create an example project.
version: 1.Configuration
One file describes the whole stack: apps, workers, jobs, tunnels, links, interpolation, and dependency-aware startup. References resolve after YAML decoding and before validation, so ports, URLs, commands, paths, and conditions are still checked.
1version: 12name: my_awesome_app3 4apps:5 api:6 name: API Server7 command: cargo run --bin api8 cwd: .9 env:10 DATABASE_URL: ${secret.DATABASE_URL}11 STRIPE_KEY: ${dotenv.STRIPE_KEY}12 port: 808013 url: http://localhost:${apps.api.port}14 health: ${apps.api.url}/health15 16workers:17 worker:18 name: Background Worker19 command: cargo run --bin worker20 cwd: .21 depends_on:22 api: healthy23 24jobs:25 migrate:26 name: Migrate Databases27 command: cargo run --bin migrate28 cwd: .29 env:30 DATABASE_URL: ${secret.DATABASE_URL}31 depends_on:32 api: port_listening33 34tunnels:35 stripe_webhook:36 command: ngrok http --url=${tunnels.stripe_webhook.public_url} ${apps.api.port}37 cwd: .38 public_url: https://guiding-lucky-tick.ngrok-free.app39 forwards_to: api40 depends_on:41 api: healthy42 43links:44 api: ${apps.api.url}Component roles
Every runnable entry has a role and a run policy. Component ids must be globally unique across apps, workers, jobs, and tunnels. Display order is apps, workers, tunnels, then jobs.
apps
Long-running foreground processes: the services you run.
commandShell command that starts the process. Required.requiredcwdWorking directory the command runs in.requirednameDisplay name shown in the UI.optionalportOwned port. Enables preflight, conflict, and release checks.optionalurlProject URL, often built from the port via interpolation.optionalhealthHTTP URL or command probe that gates readiness.optionalenvStructured environment values resolved at launch.optionaldepends_onMap of component id to the condition to wait for.optionalworkers
Long-running background processes and queue runners.
commandShell command that starts the worker. Required.requiredcwdWorking directory.requirednameDisplay name shown in the UI.optionalhealthOptional HTTP or command health check.optionalenvStructured environment values resolved at launch.optionaldepends_onUsually waits on an app being healthy.optionaljobs
One-shot commands that complete, fail, and can be rerun.
commandShell command to run once. Required.requiredcwdWorking directory.requirednameDisplay name shown in the UI.optionalauto_startSet false to skip Start All; run explicitly instead. Default true.optionalenvStructured environment values resolved at launch.optionaldepends_onConditions that must hold before the job runs.optionaltunnels
Auxiliary processes that expose a public URL for a local app.
commandTunnel command, e.g. an ngrok invocation. Required.requiredcwdWorking directory.requiredpublic_urlThe public URL the tunnel exposes.requiredforwards_toId of the app this tunnel forwards to. Must be an app.requirednameDisplay name shown in the UI.optionalenvStructured environment values resolved at launch.optionaldepends_onUsually the forwarded app being healthy.optionallinks
Named URLs shown with the project. Not runnable components.
<id>: <url>Maps a name to a URL, e.g. api: ${apps.api.url}.requiredHealth checks
A health check gates a component's readiness and is what satisfies the healthy dependency condition. It can be an HTTP probe or a command probe.
HTTP health
Provide a URL string. The check passes on any 2xx response.
1apps:2 api:3 command: cargo run --bin api4 port: 80805 health: http://localhost:8080/healthCommand health
Provide a mapping with a command. The check passes when the command exits 0; a nonzero exit, launch failure, or timeout is unhealthy. Probes run serially and never overlap. Timing fields are in seconds.
1apps:2 db:3 command: postgres -D ./data4 health:5 command: pg_isready -h localhost -p 54326 interval: 57 timeout: 28 start_period: 5commandShell command to run as the probe. Exit 0 is healthy.requiredintervalSeconds between probes.optionaltimeoutSeconds before a probe is killed and marked unhealthy.optionalstart_periodGrace seconds before failures start counting.optionalcwdWorking directory for the probe. Defaults to the component cwd, then the project root.optionaldepends_on: completed for one-shot setup or migrations that should run once and unblock dependents.Interpolation
String values may reference other parts of the config with ${...}. A string can contain more than one reference. Missing or unsupported references are validation errors. Stacksmith does not evaluate shell syntax, expand environment variables, or support conditionals or expressions.
${apps.<id>.port}The app's configured port.${apps.<id>.url}The app's configured URL.${apps.<id>.health}The app's health-check URL.${tunnels.<id>.public_url}A tunnel's public URL.${links.<id>}A declared link URL.1apps:2 api:3 port: 80804 url: http://localhost:${apps.api.port}5 health: ${apps.api.url}/health6 7tunnels:8 stripe_webhook:9 command: ngrok http ${apps.api.port}Secrets & environment
Components can define structured env values. Use ${secret.NAME} for Keychain secrets and ${dotenv.NAME} for local dotenv values. Stacksmith resolves values immediately before process launch, injects them into the child process environment, and does not store resolved values in runtime snapshots.
1jobs:2 migrate:3 command: cargo run --bin migrate4 env:5 DATABASE_URL: ${secret.DATABASE_URL}6 STRIPE_KEY: ${dotenv.STRIPE_KEY}${secret.NAME}Reads from project-scoped macOS Keychain secrets.${dotenv.NAME}Reads from configured dotenv files, with later files overriding earlier ones.${env.NAME}Advanced: reads from Stacksmith's parent process environment.Dotenv files
Stacksmith reads .env from the project root by default when it exists. Add environment.dotenv.files only when you want custom or multiple dotenv files such as .env.local. Files are read in order, and later files override earlier values.
1environment:2 dotenv:3 files:4 - .env5 - .env.localDotenv parsing supports KEY=value, KEY="value", KEY='value', comments, and blank lines. It does not evaluate shell syntax, command substitution, or scripts.
.stacksmith.yml, logs, snapshots, or project data.Dependencies
depends_on controls startup order across all roles. Each entry maps a component id to the condition it must satisfy before the dependent component starts. Cycles across any roles are invalid.
startedThe dependency process has launched.Any componenthealthyThe dependency passed its health check.Components with healthport_listeningThe dependency owns and listens on its configured port.Components with a portcompletedThe dependency job exited successfully.Jobs onlyList more than one dependency to require all of them. The entries combine with AND, so the component only starts once every listed dependency is satisfied. Each healthy dependency must point at a component that defines a health: check.
1tunnels:2 stripe_webhook:3 command: ngrok http ${apps.api.port}4 public_url: https://example.ngrok-free.app5 forwards_to: api6 depends_on:7 api: healthy8 worker: healthycompleted, Start All will not run it automatically. The dependant stays blocked until you run the job and it succeeds.Lifecycle
Apps, workers, and tunnels are long-running. Readiness is determined by health check first, then owned port, then startup settling. Any unexpected exit is a failure, including exit code 0.
Jobs are one-shot. A successful job enters completed, is no longer active, is skipped by Stop All, and can be run again explicitly. Jobs default to auto_start: true; set auto_start: false for migrations and other commands that should only run on an explicit Run action or stacksmith_run_job call.
Ports & URLs
Port ownership applies to apps. Port preflight, conflicts, and release checks only apply to components with an owned port. A tunnel does not own the forwarded app port: the public URL belongs to the tunnel, while the forwarded local port stays owned by the app.
Diagnosis
Diagnosis is role-aware. Stacksmith reports the failure, the affected component, the evidence, and a recommended action instead of leaving you to infer state from scattered logs.
- Port conflicts are reported only against apps that own ports.
- Health failures apply only to components with health checks.
- Failed jobs include exit status and rerun guidance; completed jobs are healthy context.
- Blocked workers and tunnels show the dependency they are waiting on.
- Long-running exit
0is treated as unexpected. - Tunnel issues focus on public URL and forwarding state, not the forwarded app port.
On-device AI
The app always builds a deterministic diagnosis from observed runtime state and bounded log evidence. When Apple's on-device language model is available, the debug area can generate a grounded incident brief from the same confirmed evidence and allowed recovery actions. Generated explanations are validated before display and fall back to the deterministic brief when generation is unavailable or invalid. Nothing leaves your Mac.
MCP server
Stacksmith bundles the stacksmith-mcp helper. It uses MCP's stdio transport and forwards requests over a local Unix socket to the running app, which remains the owner of all project state and processes. The helper lives at a stable path in an installed app:
1/Applications/Stacksmith.app/Contents/Helpers/stacksmith-mcpConfigure a generic MCP client to launch it:
1{2 "mcpServers": {3 "stacksmith": {4 "command": "/Applications/Stacksmith.app/Contents/Helpers/stacksmith-mcp"5 }6 }7}Or register the same executable with Codex:
1codex mcp add stacksmith -- \2 /Applications/Stacksmith.app/Contents/Helpers/stacksmith-mcp3 4codex mcp get stacksmith.stacksmith.yml files and drive these MCP tools. Get it from the agent-skill folder on GitHub.Tools
stacksmith_get_project_statusreadOverall project and health summary.stacksmith_list_componentsreadAll components with role and current state.stacksmith_get_component_statusreadOne component's current snapshot.stacksmith_get_recent_logsreadBounded recent log tail for a component.stacksmith_get_diagnosisreadDeterministic diagnosis brief from runtime state.stacksmith_validate_loaded_projectreadValidate the loaded .stacksmith.yml.stacksmith_start_componentcontrolStart an app, worker, or tunnel.stacksmith_stop_componentcontrolStop an app, worker, or tunnel.stacksmith_restart_componentcontrolRestart an app, worker, or tunnel.stacksmith_run_jobcontrolRun (or rerun) a one-shot job.Read-only tools never change runtime state. Mutating tools (start, stop, restart, run_job) require the local component-control permission, affect only the named component, and never touch dependencies or dependants. Jobs are exclusive to stacksmith_run_job.
Troubleshooting
The app must be open so the local IPC server is available. To verify the running app is accepting bridge requests, check its Unix socket, or use the non-mutating Test Connection action on the MCP settings page:
1ls -l "$HOME/Library/Application Support/Stacksmith/runtime.sock"2lsof -U | rg 'Stacksmith/runtime.sock'Licensing
Stacksmith starts with a one-time 7-day free trial. Download it, open the app, and start the trial with no licence key required. The trial unlocks the full app for seven days; when it ends, enter a licence key to keep using Stacksmith. The trial is recorded locally so it cannot be reset by reinstalling.
The direct-distribution build uses Lemon Squeezy for activation and update eligibility, with licence credentials stored in your macOS Keychain. An already-activated installation keeps working when licensing is temporarily unavailable, and licensing never stops managed processes or edits project data.