hack-cli-hack-dance-hack
Use the hack CLI for local dev environments (docker compose, logs, run commands) and agent integration setup. Trigger when asked to start/stop services, open project URLs, inspect logs, run commands i
SKILL.md.codex/skills/hack-cli/SKILL.md
GitHubhack CLI
Use hack as the primary interface for local-first development.
Integration freshness
- These instructions were generated by hack CLI v4.0.3; use the content revision and command behavior to assess freshness.
- Check guidance only when Hack is relevant and there is evidence of drift; use targeted
hack setupchecks for a read-only inventory. Routine tasks do not require a startup audit. - Content revision:
38d5802c523f(version alone is not a freshness guarantee).
Product boundary
- Supported CLI surface: project init, up/down/restart, open, logs, env, host exec/shell, sessions, doctor, and daemon.
- Hosted auth/account/org/team flows, the web dashboard, and built-in GitHub/Linear integrations are retired. The retained macOS app is unsupported and is not distributed with the CLI.
- Experimental and unsupported: remote/gateway/node/dispatch commands. They are hidden from default help (list with
hack help --all) and warn on use; do not use them unless explicitly requested.
Operating rules
- Prefer
hackover rawdocker/docker composefor project workflows. - Do not start/stop services from Docker Desktop UI for
hack-managed projects. - Treat
.hack/.internaland.hack/.branchas hack-managed artifacts; do not hand-edit generated files there. - Use
--jsonfor machine-readable output when available;hack up/down/restart/doctor --jsonemit an{ok, data | error: {code, message}}envelope with stable E_* error codes. - Scripted/agent runs: pass
--no-interactive(or setHACK_NO_INTERACTIVE=1) so commands never block on prompts — they apply documented defaults or fail fast with E_INTERACTIVE_REQUIRED. - Use MCP only when shell access is unavailable.
- A task request authorizes its stated scope, not unrelated service, global config, or production changes. Preserve native credential/trust approvals and destructive-cleanup gates; do not ask again for the same authorized action.
- Cleanup must prove resource ownership. Successful one-shot containers may exit with code 0; adopted external processes must remain running when Hack stops its own services.
- If runtime state looks wrong, inspect with
hack doctor; applyhack doctor --fixonly for repairs covered by the request. Warnings do not authorize broader changes.
Core objects
- Project: a repo with
.hack/config + compose file. - Service: a compose service (e.g. api, web, worker).
- Instance: a running project; branch instances are separate copies started with
--branch.
Config + schema
- Project config:
.hack/hack.config.json - Global config:
~/.hack/hack.config.json - Schema URL:
https://schemas.hack/hack.config.schema.json - Prefer CLI writes:
hack config get <path>,hack config set <path> <value>,hack config set --global <path> <value>
Hostname routing + Caddy labels
- Primary host comes from
dev_host(default:<project>.hack). - Subdomain pattern is
<sub>.<dev_host>(for example:api.myapp.hack). - OAuth alias (when enabled) also routes
<dev_host>.<tld>and<sub>.<dev_host>.<tld>(default tld:gy). - Browser launches automatically prefer a routed OAuth alias when enabled; custom development hosts outside
.hackstay on the primary host. Setopen.preferor passhack open --prefer <auto|alias|dev>to override. - Not every compose service is routable: only services with Caddy labels and on
hack-devare exposed. - Required labels for HTTP services:
caddy,caddy.reverse_proxy,caddy.tls=internal. - Quick checks:
hack open,hack open <sub>,hack open --json.
TLS + valid-hostname constraints
hackuses Caddy internal PKI for HTTPS on routed hosts; trust CA withhack global trust.- Containers get a combined public+local trust bundle (SSL_CERT_FILE etc.) once
hack global trusthas run; public TLS (package registries, external APIs) keeps working alongside*.hacktrust. - If the combined bundle is missing, only Node gets
*.hacktrust (NODE_EXTRA_CA_CERTS); OpenSSL-based tools keep public roots — runhack global trustto enable both. .hackis local-first and great for dev, but it is not a public suffix.- Use OAuth alias hosts (for example
*.hack.gy) when providers require public-suffix-style callback domains. - Alias hosts are still local-dev routes unless you add an external tunnel/remote ingress path.
Project files (managed vs generated)
- Source-of-truth files:
.hack/docker-compose.yml,.hack/hack.config.json,.hack/hack.env.default.yaml, and optional.hack/hack.env.<overlay>.yaml. - Worktree-local env override files:
.hack/hack.env.local.yamland.hack/hack.env.<overlay>.local.yaml. - Local-only files:
.hack.secret.key, optional.hack/.envcompatibility output,.hack/.env.state.json, and.hack/.internal/(runtime/local machine state; keep gitignored). - Generated (do not hand-edit):
.hack/.internal/compose.override.yml,.hack/.internal/compose.env.override.yml,.hack/.internal/compose.runtime.override.yml,.hack/.branch/compose.<branch>.override.yml,.hack/.branch/compose.<branch>.runtime.override.yml. - Managed via CLI:
.hack/.internal/extra-hosts.json(usehack internal extra-hosts ...commands). - Lifecycle runtime files:
.hack/.internal/lifecycle/state.json,.hack/.internal/lifecycle/*.log. - Ignore rules: hack owns a committed
.hack/.gitignore(self-healing on init/up) covering machine-local generated files (.internal/,.branch/,.env,.env.state.json,hack.env*.local.yaml) plus the retiredtickets/cache path for upgrade safety; keep it committed, and if generated files leaked into git,hack doctor --fixuntracks them (files stay on disk).
Linked git worktrees
- Secret key inherits from the primary checkout automatically through the shared git common dir; set
HACK_ENV_SECRET_KEYfor CI or detached environments. hack upin a linked worktree defaults to a branch instance named after the worktree's git branch; a detached linked worktree requires an explicit--branch, unless configworktree.auto_branch=falseexplicitly opts into the base instance.- Before
hack uporhack restartauto-targets a new branch instance, Hack warns when the same worktree already owns a non-terminal instance; pass--branch <name>to make the target explicit. - Implicit
hack downretargets a uniquely owned same-checkout runtime after a Git branch rename, including Created and stopped containers; when multiple runtimes belong to the checkout, pass--branch <name>explicitly. hack doctorflags divergent secret keys and dev_host collisions across checkouts.
Advanced networking (extra_hosts + local proxies/tunnels)
- Static host mappings: set
internal.extra_hostsin.hack/hack.config.json. - Dynamic host mappings:
hack internal extra-hosts set <hostname> <target>/unset/list. - For host-local proxies/tunnels, prefer
host-gatewayas target when possible. - After mapping changes or proxy IP churn:
hack restartand thenhack doctor.
Standard workflow
- If
.hack/is missing:hack init - Start services:
hack up --detach(orhack up -d) - Check status:
hack psorhack status - Open app URL:
hack open --json - Restart:
hack restart - Stop services:
hack down
Running things (decision guide)
- One-off command in a fresh service container (deps started as needed):
hack run <service> <cmd...>. - Command inside an already-running service container:
hack exec <service> -- <cmd...>. - Host script that needs managed env:
hack host exec --env <overlay> --scope <service> -- <cmd...>. Inspect variable names and non-secret config; never expose secret values in prompts, command arguments, logs, or source control. - Interactive host shell with injected env:
hack host shell --env <overlay> --scope <service>. - Browser/host URL: use
hack open <service> --json(OAuth aliases are preferred when enabled). Container-to-container traffic should use Compose DNS.
Logs (default is compose)
- Fast tail:
hack logs --pretty - Per-service tail:
hack logs <service> - Machine snapshot:
hack logs --json --no-follow - Loki history/query:
hack logs --loki --since 2h --prettyorhack logs --loki --query '{project="<name>"}' - Force compose backend:
hack logs --compose - Global infra logs:
hack global logs caddy --no-follow --tail 200
Lifecycle + startup
- Put host setup in
.hack/hack.config.jsonunderstartup/lifecycle(not ad-hoc terminal tabs). - Use
lifecycle.up.beforefor pre-start hooks andlifecycle.processesfor long-running host tasks. - Lifecycle hooks and processes receive the selected overlay's
globalvalues plushostoverrides; service-scoped values remain container-specific unless a host command explicitly selects that scope. - For fixed-port host helpers such as SSM tunnels or local proxies, set
singleton.portsand usuallyonConflict: "adopt"so Hack reuses a healthy existing listener instead of starting duplicate tunnel stacks. singletonis a listener guard, not process ownership transfer; adopted external processes are left running onhack down.- Inspect lifecycle status via
hack projects --detailsand stream viahack logs <service-or-process>. - Lifecycle session recovery is ownership-proven: Hack adopts healthy token-, definition-, and environment-matched sessions, replaces owned stale sessions, and refuses to kill same-name sessions without deterministic ownership proof.
hack doctor --fixreaps an orphan lifecycle session only when mux ownership is proven and its Compose instance is absent; unverified same-name sessions are never modified.- After
hack uporhack restart, running services and successful one-shot services (exitedwith code 0) count as successful. Hack recognizes dependency installers,hack.service.one-shot=true, and services referenced by Composecondition: service_completed_successfully; other states returnE_STARTUP_INCOMPLETE, andhack doctorwarns about containers stuck inCreated. - Detached startup is bounded; a hung Compose operation returns
E_STARTUP_TIMEOUT, terminates its process group, andhack doctor --fixcan start exact containers left inCreated. - Target only affected services with
hack up <service...> --detach,hack restart <service...>, orhack env apply --service <service>; scoped operations skip project lifecycle hooks and implicit dependency startup. - Use
hack env explain <KEY> --env <overlay> --service <service> --target <host|compose>for redacted source, precedence, availability, and delivery diagnostics. - Dependency installer services are detected generically by command or
hack.dependencies.bootstrap=true; registry env references are preflighted before container mutation. Optionalhack.dependencies.cache-volume,hack.dependencies.lockfiles, andhack.dependencies.runtime-fileslabels enable lockfile/runtime-keyed volumes shared across compatible worktrees. hack down --prune-cachescan remove only confirmed Compose-owned named volumes mounted exclusively at.nextdestinations or explicitly labeledhack.cache.disposable=true; it is confirmation-gated, requires--yesfor JSON/scripted runs, and never performs broad volume pruning.
Workspaces (mux-managed, tmux-first by default)
- Picker:
hack sessionfor persistent project workspaces. - Reuse/create:
hack session start <project> - Env-scoped workspace:
hack session start <project> --env qa --service api --detach - Force isolated agent workspace:
hack session start <project> --new --name agent-1(<project>--agent-1). - Execute in workspace:
hack session exec <workspace> "<command>" - Execute in workspace with injected env:
hack session exec <workspace> --env qa --service api "bun db:migrate" - Stop workspace:
hack session stop <workspace>
Branch instances (parallel envs)
- Use a branch instance when you need two versions running at once (PR review, experiments, migrations) or want to keep a stable environment while testing another branch.
- Target one with
--branch <name>on up/open/logs/down (for example:hack up --branch <name> --detach). - Linked worktrees pick a branch instance automatically (see Linked git worktrees).
- Containers receive
HACK_RUNTIME_METADATAplusHACK_DEV_URL,HACK_ALIAS_URL, and current-service URL fields derived from effective Caddy routes. Use Compose DNS for internal traffic and this metadata for browser-facing links, OAuth callbacks, and webhooks.
Project targeting
- From repo root, commands use that project automatically.
- Else use
--project <name>(registry) or--path <repo-root>. - List projects:
hack projects --json
Global infra
- Bootstrap once:
hack global install - Start/stop/status:
hack global up,hack global down,hack global status - Use
hack global upbefore Loki/Grafana queries if global logging is offline.
Daemon (optional)
- Start for faster JSON status/ps:
hack daemon start - Check status:
hack daemon status
Agent integration maintenance
- Ordinary Hack commands,
hack update,hack doctor --fix, and primer hooks never install plugins or mutate agent integration files. - Use the matching
hack setupcommand only for the affected client and authorized project scope; add--globalonly when user-scope changes are authorized. Use--checkfor read-only inspection. Reservehack setup sync --all-scopesfor an explicitly requested full refresh. - Standalone setup and optional native plugins are alternative integration paths. A missing optional integration does not require installation. Verify the replacement loads before explicitly removing duplicate Hack-owned entries; preserve customizations.
- CLI upgrades do not authorize integration changes. Edit guidance at its generator source and render the affected targets; restart only if the client cannot reload the changed hooks or skills.
Agent setup (CLI-first)
- Cursor rules:
hack setup cursor - Claude hooks:
hack setup claude - Codex skill:
hack setup codex - Agent-assisted onboarding:
hack init --with claude|codex|both(new repos) orhack agent onboard(existing projects) print/hand off the full setup prompt; the/hack-initskill and thehack-initMCP prompt return the same content. - Init prompt:
hack agent init(use --client cursor|claude|codex to open) - Init patterns:
hack agent patterns - MCP (no-shell only):
hack setup mcp - MCP install: choose the requested client and scope with
hack mcp install --help; do not configure other clients as a side effect.