diff --git a/.cursor/rules/hack.mdc b/.cursor/rules/hack.mdc index 835164c17..8f55277b3 100644 --- a/.cursor/rules/hack.mdc +++ b/.cursor/rules/hack.mdc @@ -37,6 +37,12 @@ hack down hack global status hack session hack tickets list +hack node list +hack node status --watch +hack node ssh setup --node +hack node routes status +hack node routes repair +hack dispatch run --project --node default --branch --runner generic -- "pwd" hack internal extra-hosts list hack internal extra-hosts set hack internal extra-hosts unset diff --git a/.hack/README.md b/.hack/README.md index 0d930ede5..74032c7e4 100644 --- a/.hack/README.md +++ b/.hack/README.md @@ -7,8 +7,8 @@ This repo is dogfooding the tickets extension. - `hack x tickets setup` - `hack x tickets create --title "..." --body-stdin` - `hack x tickets list` - - `hack x tickets show T-00001` - - `hack x tickets status T-00001 in_progress` + - `hack x tickets show T-AB12CD34EF` + - `hack x tickets status T-AB12CD34EF in_progress` - `hack x tickets sync` No services are required for this repo; `docker-compose.yml` is intentionally empty. diff --git a/AGENTS.md b/AGENTS.md index 141a51dc5..2865c3736 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -202,7 +202,7 @@ Most formatting and common issues are automatically fixed by Biome. Run `bun x u ## hack CLI (local dev + MCP) -Use `hack` as the single interface for local runtime orchestration (compose, DNS/TLS, logs, sessions). +Use `hack` as the single interface for local runtime orchestration (compose, DNS/TLS, logs, persistent project workspaces). Operating rules: - Prefer `hack` over raw `docker` / `docker compose` for project workflows. @@ -270,12 +270,12 @@ Lifecycle + startup: - Use `lifecycle.up.before` for pre-start hooks and `lifecycle.processes` for long-running host tasks. - Inspect lifecycle status via `hack projects --details` and stream via `hack logs `. -Sessions (mux-managed): -- Picker: `hack session` -- Start/attach: `hack session start ` -- Force isolated agent session: `hack session start --new --name agent-1` -- Execute in session: `hack session exec ""` -- Stop session: `hack session stop ` +Workspaces (mux-managed): +- Picker: `hack session` for persistent project workspaces. +- Reuse/create: `hack session start ` +- Force isolated agent workspace: `hack session start --new --name agent-1` (`--agent-1`). +- Execute in workspace: `hack session exec ""` +- Stop workspace: `hack session stop ` Tickets (git-backed): - Create: `hack tickets create --title "..." --body-stdin` diff --git a/CLAUDE.md b/CLAUDE.md index 5387e89bc..dc08202fa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -97,7 +97,7 @@ This project uses Obsidian for project context, specs, research, and progress tr ## hack CLI (local dev + MCP) -Use `hack` as the single interface for local runtime orchestration (compose, DNS/TLS, logs, sessions). +Use `hack` as the single interface for local runtime orchestration (compose, DNS/TLS, logs, persistent project workspaces). Operating rules: - Prefer `hack` over raw `docker` / `docker compose` for project workflows. @@ -165,12 +165,12 @@ Lifecycle + startup: - Use `lifecycle.up.before` for pre-start hooks and `lifecycle.processes` for long-running host tasks. - Inspect lifecycle status via `hack projects --details` and stream via `hack logs `. -Sessions (mux-managed): -- Picker: `hack session` -- Start/attach: `hack session start ` -- Force isolated agent session: `hack session start --new --name agent-1` -- Execute in session: `hack session exec ""` -- Stop session: `hack session stop ` +Workspaces (mux-managed): +- Picker: `hack session` for persistent project workspaces. +- Reuse/create: `hack session start ` +- Force isolated agent workspace: `hack session start --new --name agent-1` (`--agent-1`). +- Execute in workspace: `hack session exec ""` +- Stop workspace: `hack session stop ` Tickets (git-backed): - Create: `hack tickets create --title "..." --body-stdin` diff --git a/README.md b/README.md index 31d64cc93..09318e24b 100644 --- a/README.md +++ b/README.md @@ -1,942 +1,311 @@ - +# Hack - -
-
-
+```text
  █████   █████   █████████     █████████  █████   ████
-░░███   ░░███   ███░░░░░███   ███░░░░░███░░███   ███░ 
- ░███    ░███  ░███    ░███  ███     ░░░  ░███  ███   
- ░███████████  ░███████████ ░███          ░███████    
- ░███░░░░░███  ░███░░░░░███ ░███          ░███░░███   
- ░███    ░███  ░███    ░███ ░░███     ███ ░███ ░░███  
+░░███   ░░███   ███░░░░░███   ███░░░░░███░░███   ███░
+ ░███    ░███  ░███    ░███  ███     ░░░  ░███  ███
+ ░███████████  ░███████████ ░███          ░███████
+ ░███░░░░░███  ░███░░░░░███ ░███          ░███░░███
+ ░███    ░███  ░███    ░███ ░░███     ███ ░███ ░░███
  █████   █████ █████   █████ ░░█████████  █████ ░░████
-░░░░░   ░░░░░ ░░░░░   ░░░░░   ░░░░░░░░░  ░░░░░   ░░░░ 
-

-
-
-

- Opinionated local-dev orchestration for running multiple projects at the same time without port conflicts. -
-

- ---- -**Network isolation per repo / branch**: every instance runs on its own Docker network (so Postgres/Redis/etc can stay on default ports *inside* the project). - -**Stable HTTPS hostnames**: `https://.hack` (and subdomains like `https://api..hack`) routed by a global Caddy proxy. - -**Good logs UX**: instant `docker compose logs` tailing, plus Loki/Grafana for querying + history. - -**Opt-in per repo**: no invasive changes to your codebase; config lives in `.hack/`. - ---- - -### Why this exists - -Most of my projects run the same stack. That’s fine until you want to: - -- run two projects at the same time -- run two branches of the same repo -- run multiple worktrees in parallel - -At that point everything fights over `localhost` and default ports. - -The daily choice: +░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░ ░░░░ +``` -- **Option A:** stop A → start B → ship fix → stop B → restart A - you just spent 5–10 minutes paying the orchestration tax and nuking your focus. -- **Option B:** don’t run it. code blind. push. let CI tell you what you broke. +Hack is a local-first platform for developers and agents who need to run more than one project, branch, or workflow at once. -Neither scales. Both slow you down in dumb, repeatable ways. +It makes three promises: **run projects in parallel**, **normalize all your tickets**, and **manage project env safely**. +When local dev breaks down, it usually breaks down in the same places: ports collide, work gets split across too many ticket systems, and env setup turns into tribal knowledge. Hack is the opinionated layer that makes those problems boring again. -## Installation +Sessions and integrations support those promises directly: sessions keep terminal work durable, and integrations let agents, ticket systems, and remote surfaces plug into the same project model. -### Prerequisites -- **macOS**: supported. -- **Docker + Compose**: [Orbstack](https://docs.orbstack.dev/quick-start) | [Docker Desktop](https://www.docker.com/get-started/) +## The Three Core Promises -### Install +### 1. Run projects in parallel -**Option A: Shell script (CLI only)** +Hack lets each repo or branch run as its own isolated local environment instead of fighting over `localhost`, shared Docker networks, or ad-hoc port maps. -```bash -curl -fsSL https://github.com/hack-dance/hack/releases/latest/download/hack-install.sh | bash -``` +- Run multiple repos at the same time. +- Run multiple branches or worktrees of the same repo at the same time. +- Keep service defaults like Postgres and Redis on their normal container ports. +- Open stable HTTPS hostnames like `https://.hack` and `https://api..hack`. -**Option B: macOS DMG (CLI + Desktop App)** +Core commands: -Download the latest DMG from [GitHub Releases](https://github.com/hack-dance/hack/releases/latest). - -The DMG includes: -- **Hack Desktop** - macOS menu bar app for managing projects -- **Install Hack CLI** - double-click to install the CLI to `~/.hack/bin` - -### Setup ```bash -hack global install -``` - -### Quick start -Manual (CLI): - -```bash -cd /path/to/your-repo -hack init hack up --detach +hack up --branch feature-x --detach hack open +hack logs --pretty ``` -Agent-assisted (Cursor/Claude/Codex with shell access): - -```bash -hack setup cursor # or hack setup claude / hack setup codex -hack setup agents # optional: adds AGENTS.md + CLAUDE.md snippets -hack setup sync --all-scopes # optional: refresh project + user integrations -hack agent init --client cursor # or --client claude / --client codex -hack agent patterns # optional: dependency/ops checklist -``` - -If you omit `--client`, `hack agent init` will prompt you to choose (TTY only). -If your agent does not auto-open, paste the output into the chat. Example: - -``` -I ran `hack agent init` and pasted the output below. Please follow it to set up hack for this repo, -then run: -- `hack init` (use --auto if dev scripts are detected) -- `hack up --detach` -- `hack open --json` -If anything fails, use `hack logs --pretty` and summarize next steps. - - -``` - -If the agent cannot run shell commands, use MCP instead: `hack setup mcp` and `hack mcp serve`. - -### Remote node runtime image (multi-node) - -Use the prebuilt node-runtime container to bootstrap remote nodes quickly (Linux/macOS hosts with Docker): - -```bash -bun run build:node-runtime-image --tag hack-node-runtime:dev -``` - -Full operator guide: [`docs/guides/remote-node-container.md`](docs/guides/remote-node-container.md) - -### Tickets (optional, git-backed issue tracking) - -`hack` includes a lightweight, git-backed ticket system for tracking work without leaving your repo. Tickets are stored in a hidden git ref (`refs/hack/tickets`) so they sync with your code but don't clutter your branch list. - -**Quick setup:** - -```bash -# Enable tickets for this repo -hack x tickets setup - -# Create a ticket -hack x tickets create --title "Add dark mode support" - -# List tickets -hack x tickets list - -# Interactive TUI for managing tickets -hack x tickets tui - -# Update status -hack x tickets status T-00001 in_progress - -# Sync to remote -hack x tickets sync -``` - -**Why use it:** -- No external service required - tickets live in git -- Agent-friendly - Claude/Cursor/Codex can create and manage tickets -- Syncs with `git push/pull` via hidden refs -- Works offline - -Tickets are ideal for solo devs, small teams, or agent-driven workflows where you want lightweight task tracking without context-switching to Jira/Linear/GitHub Issues. - -Full docs: [`docs/guides/tickets.md`](docs/guides/tickets.md) - -### macOS app (experimental) -Source lives in `apps/macos`. See `apps/macos/README.md` for XcodeGen and build/run steps. -Quick commands (repo root): - -```bash -bun run macos:project-gen -bun run macos:open -bun run macos:dev -bun run macos:build -bun run macos:test -``` - -### Configuration (.hack/hack.config.json) -- `name`: project slug (also used for Docker Compose project name) -- `dev_host`: base hostname (`.hack`) +### 2. Normalize all your tickets -*Optional `[logs]` settings* -- `follow_backend`: `compose|loki` (default: `compose`) -- `snapshot_backend`: `compose|loki` (default: `loki`) -- `clear_on_down`: when running `hack down`, request Loki delete for this project (best-effort) -- `retention_period`: e.g. `"24h"`; on `hack down`, prune older logs (best-effort) +Hack keeps a local-first, git-backed ticket substrate in your repo, then lets external systems like Linear sync into that substrate instead of forcing your workflow to live in five different places. -*Optional `[internal]` settings (container DNS/TLS)* -- `dns`: use CoreDNS to resolve `*.hack` inside containers (default: `true`) -- `tls`: mount Caddy Local CA + set common SSL env vars (default: `true`) -- `extra_hosts`: static Compose `extra_hosts` entries (hostname → IP/target), merged into the internal override +- Track work in git with lightweight hack tickets. +- Sync issues and projects with Linear when you need shared coordination. +- Preserve ownership and provenance so local and synced tickets stay understandable. +- Give the CLI, agents, and the macOS app one normalized ticket model to work against. -If you need **dynamic** `extra_hosts` (e.g. Pulumi outputs / local tunnels that change), use: +Core commands: ```bash -hack internal extra-hosts set -hack internal extra-hosts unset -hack internal extra-hosts list +hack tickets setup +hack tickets create --title "Investigate flaky auth flow" +hack tickets list +hack linear project-bind --project-id "" +hack linear sync-project --from linear --project-id "" ``` -This writes `.hack/.internal/extra-hosts.json` and is merged into `extra_hosts` when you run `hack up` / `hack restart`. +Docs: -*Optional `[oauth]` settings (OAuth-safe alias host)* -- `enabled`: when true, `hack init` generates Caddy labels so routed services answer on both: -- primary: `https://` -- OAuth alias: `https://.` (e.g. `https://sickemail.hack.gy`) -- `tld`: optional (default: `"gy"`). Only `*.hack.gy` is bootstrapped automatically by `hack global install`; other TLDs require manual DNS setup. +- [Tickets guide](docs/guides/tickets.md) +- [Linear integration architecture](docs/guides/linear-integration-architecture.md) -The file includes a JSON Schema reference for editor validation: +### 3. Manage project env safely -```json -{ - "$schema": "https://schemas.hack/hack.config.schema.json" -} -``` +Hack separates shareable env structure from secret values so teams and agents can understand what a project needs without leaking sensitive material into git. -Schemas are served locally by the global Caddy proxy at `https://schemas.hack`. +- Commit an env contract in `.hack/hack.env.json`. +- Keep plain local values in gitignored `.hack/.env`. +- Store secrets in the OS keychain or another configured backend. +- Resolve env requirements before `hack up`, `hack run`, or `hack restart`. -Quick edits: +Core commands: ```bash -hack config get dev_host -hack config set dev_host "myapp.hack" -hack config set logs.snapshot_backend "compose" +hack env list +hack env set AWS_PROFILE=dev +hack env set --secret DATABASE_URL=postgres://... +hack env backend status ``` -## Commands (high level) - -Run `hack help` (or `hack help `) for full usage. - -Common: -- `hack global install|up|down` -- `hack init|up|down|logs|open|tui` -- `hack status` -- `hack remote setup` -- `hack gateway enable` +Docs: -Full command table + flags: `docs/cli.md`. +- [Env and secrets](docs/env.md) -Run `hack help ` for detailed help. +## First Run -Project commands that call Docker Compose accept `--profile` (up/down/restart/ps/logs/run). - -## JSON output - -Use `--json` for machine-readable output: - -- `hack projects --json` -- `hack ps --json` -- `hack logs --json` (NDJSON stream; use `--no-follow` for snapshots) -- `hack open --json` (returns `{ "url": "..." }`) - -When the daemon is running, `hack projects --json` and `hack ps --json` use it for faster results. - -`hack logs --json` emits event envelopes (`start`, `log`, `end`) so MCP/TUI consumers can stream safely. - -## Daemon (optional) - -`hackd` is a local daemon that caches Docker state for fast status/ps queries. +### Prerequisites -```bash -hack daemon start -hack daemon status -hack daemon metrics -hack daemon stop -hack daemon logs -``` +- macOS +- Docker + Compose via [OrbStack](https://docs.orbstack.dev/quick-start) or [Docker Desktop](https://www.docker.com/get-started/) -### launchd integration (macOS) +### 1. Install Hack -On macOS, you can install `hackd` as a launchd service for automatic management: +CLI only: ```bash -# Install (auto-start on login by default) -hack daemon install - -# Install without auto-start (manual start/stop via launchd) -hack daemon install --no-run-at-load - -# Uninstall the launchd service -hack daemon uninstall +curl -fsSL \ + https://github.com/hack-dance/hack/releases/latest/download/hack-install.sh \ + | bash ``` -Options: -- `--run-at-load` / `--no-run-at-load`: Start automatically on login -- `--gui-only` / `--no-gui-only`: Only run in GUI sessions (default: GUI only) - -The service uses label `dance.hack.hackd` and writes to `~/Library/LaunchAgents/`. - -Use it when: -- You run `hack projects --json` / `hack ps --json` frequently (scripts, agent workflows, TUI). -- You want faster status snapshots without shelling out to Docker each time. - -Skip it when: -- You prefer zero background processes. -- You rarely use JSON status/ps outputs. - -If it is not running (or version-mismatched), the CLI falls back to direct Docker calls. +CLI + desktop app: -## Control plane + extensions (optional) +- Download the latest DMG from [GitHub Releases](https://github.com/hack-dance/hack/releases/latest) +- Install the CLI from the DMG +- Optionally keep the macOS app for the beta desktop surface -`hack` includes a small control-plane kernel so features like jobs, tickets, and remote access can -ship as extensions without bloating the core CLI. - -- Extension commands run via `hack x `. -- Global control-plane config lives at `~/.hack/hack.config.json` (`hack config set --global ...`). -- Per-project overrides live in `.hack/hack.config.json` and win over global values. -- `controlPlane.gateway.enabled` is project-scoped and implicitly enables the gateway extension. - -See `SPECS/control-plane/consolidated.md` and `docs/extensions.md` for the extension SDK surface. - -### Gateway (remote access) - -The gateway exposes `hackd` over HTTP/WS with token auth. It binds to `127.0.0.1` by default and -should be exposed via a Zero Trust/VPN or SSH tunnel when needed. `hack remote setup` is the -one-command flow that enables the gateway, creates a token, and can configure + start exposure -(Cloudflare/Tailscale/SSH) via prompts. It prints a QR by default (use `--no-qr` to skip). +### 2. Set up your machine ```bash -hack remote setup -# or: -hack gateway setup -# or manually: -hack gateway enable -hack daemon stop && hack daemon start -hack x gateway token-create +hack global install ``` -Gateway tokens default to read-only. For non-GET requests, set -`controlPlane.gateway.allowWrites = true` globally and create a write-scoped token: +### 3. Start your first project ```bash -hack config set --global 'controlPlane.gateway.allowWrites' true -``` - -```bash -hack x gateway token-create --scope write +cd /path/to/your-repo +hack init +hack up --detach +hack open ``` -Current gateway API (HTTP/WS): -- status/metrics/projects/ps (`/v1/*`) -- supervisor jobs: list/create/show/cancel + log/event stream (`/control-plane/*`) -- supervisor shells: create/show + PTY stream (write token + allowWrites) -- CLI shell client: `hack x supervisor shell` (gateway + write token required) - -Interactive shells are available over the gateway WebSocket; the CLI can attach with -`hack x supervisor shell` (write token + allowWrites required). Use SSH/Zero Trust for a full -terminal UI, or run commands via supervisor jobs. - -See `docs/gateway-api.md` for full API usage, structured workflow patterns, and a runnable demo. +What those commands do: -Remote access options (recommended order): -1) SSH tunnel to the gateway port for quick, ad-hoc access. -2) Zero Trust/VPN (Tailscale, Cloudflare, etc.) for persistent access. -3) Optional Caddy route (`https://gateway.hack`) for local convenience. +- `hack global install` boots the machine-level DNS, TLS, and logging services +- `hack init` creates the `.hack/` project config +- `hack up --detach` starts your stack on an isolated network +- `hack open` opens the project URL in your browser -Note: Cloudflare Tunnel is ideal for the gateway HTTP/WS surface. It is not a direct SSH -replacement on iOS; use Tailscale/VPN for SSH access from mobile clients. -If you already use Cloudflare WARP, configure a private network route to your laptop and -use that IP/hostname for SSH in your mobile client. - -Remote helper: -- `hack remote` shows status and offers to run setup when needed. -- `hack remote status` prints gateway + exposure status. -- `hack remote qr` prints a QR payload for SSH or gateway usage (confirm before sharing). -- `hack remote monitor` opens a mini TUI (status + gateway audit log tail). - -Cloudflare tunnel helper: +Useful next commands: ```bash -hack x cloudflare tunnel-setup --hostname gateway.example.com -hack x cloudflare tunnel-start -``` - -Tailscale helper: - -```bash -hack x tailscale setup -hack x tailscale status +hack logs --pretty +hack run +hack status +hack down ``` -DNS note: `cloudflared tunnel route dns ` creates the required CNAME to -`.cfargotunnel.com` in your Cloudflare zone (proxied). - -### Supervisor (jobs + shells) - -The supervisor is the job/shell runner that powers agent workflows and remote execution. It can run -commands, stream logs, and host PTY-backed shells. Use it locally with `hack x supervisor` or -remotely over the gateway. - -Docs: `docs/supervisor.md`. - -## Docs - -Start here: -- `docs/README.md` (index) -- `docs/architecture.md` -- `docs/gateway.md` -- `docs/extensions.md` +If you want the shortest non-interactive path for an existing repo, use `hack init --auto`. -## Agent setup (CLI-first) +### 4. Optional: install agent integrations -Use `hack setup` to install local integrations. Default is project scope; add `--global` for user scope. +Use these when you want Cursor, Claude, or Codex to understand Hack workflows directly. ```bash hack setup cursor hack setup claude hack setup codex -hack setup agents hack setup sync --all-scopes +hack agent init --client codex ``` -What each setup command does: -- `hack setup cursor`: installs Cursor rules in `.cursor/rules/hack.mdc` -- `hack setup claude`: installs Claude Code hooks in `.claude/settings.local.json` (or user scope) -- `hack setup codex`: installs the Codex skill in `.codex/skills/hack-cli/SKILL.md` -- `hack setup agents`: adds/updates hack usage snippets in `AGENTS.md` and `CLAUDE.md` -- `hack setup mcp`: writes MCP configs for no-shell clients -- `hack setup sync --all-scopes`: refreshes docs, skills, and MCP config at project + user scope - -Automatic guardrail: -- Project-level `hack` commands (interactive sessions) auto-check docs/skills/MCP drift and attempt auto-sync. -- To warn-only: `HACK_SETUP_SYNC_MODE=warn` -- To disable: `HACK_SETUP_SYNC_MODE=off` - -Primer helpers: -- `hack agent prime`: short CLI-first primer used by Claude Code hooks -- `hack agent init`: repo-specific setup prompt agents can follow to scaffold/verify hack config -- `hack agent init --client cursor|claude|codex`: open the prompt directly in an agent client -- `hack agent patterns`: dependency/ops checklist for agents - -Recommended flow: -- Use `hack setup cursor|claude|codex` for your agent client -- Use `hack setup agents` to document hack usage inside the repo -- Use `hack setup sync --all-scopes` after `hack update` to refresh generated integrations -- Use `hack setup mcp` only when the agent has no shell access - -## MCP (no-shell clients) - -Use MCP only when the CLI is not available (e.g. no shell access). - -Run the MCP server locally over stdio: +If your agent does not have shell access, use MCP instead: ```bash -hack mcp serve -``` - -Most MCP clients spawn this on demand. Running it directly will wait for a client connection. - -Install MCP configs (Cursor, Claude CLI, Codex): - -```bash -# Project-scoped (default via setup) -hack setup mcp - -# User-scoped (default; writes to your home config directories) -hack mcp install --all - -# Project-scoped (writes .cursor/.claude/.codex in the repo) hack mcp install --all --scope project +hack setup mcp +hack mcp serve ``` -Project scope writes `.cursor/mcp.json`, `.claude/settings.json`, and `.codex/config.toml`. -For `--scope project`, `hack mcp install` also updates `AGENTS.md` and `CLAUDE.md` by default. +Docs: -Print config snippets without writing: +- [Initialize a project](docs/guides/init-project.md) +- [CLI reference](docs/cli.md) +- [GitHub workflows](docs/guides/github-workflows.md) -```bash -hack mcp print --codex -``` +## Adjacent capabilities at a glance -`hack init` can prompt to install local agent integrations after scaffolding a repo. -For agent-driven scaffolding without prompts, use `hack init --auto` and run `hack setup` manually. +Not every repo needs every `hack` capability on day one. These are the common adjacent tools people run +into after `hack init`, `hack up`, and `hack open` are already working: -## Branch builds (worktree-friendly) +- **GitHub**: Connect GitHub when you want `hack` to help with pull requests and branch handoff from the + same place you run local work. It becomes relevant once you are ready to open, update, or share PRs. + Docs: [`docs/guides/github-workflows.md`](docs/guides/github-workflows.md) +- **Linear**: Connect Linear when your team plans work there and you want Hack to bridge repo work with + project and issue tracking. It becomes relevant when a repo maps to a Linear project or when you want + sync between Linear issues and repo-local work. Docs: + [`docs/guides/linear-integration-architecture.md`](docs/guides/linear-integration-architecture.md) +- **Tickets**: Use tickets when you want lightweight task tracking that stays with the repo and works + well for solo work, small teams, or agents. It becomes relevant as soon as you want to capture work + items without pushing everyone into a separate tracker. Docs: + [`docs/guides/tickets.md`](docs/guides/tickets.md) +- **Sessions**: Use sessions when you want a terminal workspace you can come back to, share over SSH, + or hand off to an agent without rebuilding context. It becomes relevant when you keep long-running + shells, multiple projects, or multiple branches alive at once. Docs: + [`docs/sessions.md`](docs/sessions.md) +- **Env**: Use env when a project needs local configuration like API keys, database URLs, or per-service + settings that should be explicit instead of tribal knowledge. It becomes relevant as soon as `hack up`, + `hack run`, or remote workflows depend on values that should not be hardcoded into the repo. Docs: + [`docs/env.md`](docs/env.md) -Use `--branch ` on project commands to run isolated instances with unique hostnames and compose -project names: +## Supporting Capabilities -```bash -hack up --branch feature-x --detach -hack logs --branch feature-x -hack open --branch feature-x -``` +### Sessions -Using `--branch` will create/update `.hack/hack.branches.json` with a `last_used_at` timestamp. Branch -instances show up in `hack projects --details`. +Sessions keep terminal work durable. They are not the headline promise, but they are a first-class support capability for parallel project work, remote access, and long-running agents. -Optional: track branch aliases in `.hack/hack.branches.json` for quick lookup: +- Backed by `tmux` or `zellij` +- Started from the project root, not from `.hack/` +- Useful for remote attach, agent execution, and multi-terminal workflows ```bash -hack branch add feature-x --note "worktree for PR 123" -hack branch list -hack branch open feature-x +hack session +hack session start my-project --up +hack session attach my-project +hack session exec my-project "bun test" ``` +Docs: -## Service-to-service connections (HTTP vs DB/Redis) +- [Sessions](docs/sessions.md) -If your app runs in Docker (the default in `hack`), don’t connect to `127.0.0.1` / `localhost` for Postgres/Redis. -Inside a container, `localhost` is that container, not the other compose services. +### Integrations -If you previously ran everything on your host and used `localhost:PORT`, update those references when you -move into containers: +Integrations make the core promises more usable without changing what Hack is. -- **HTTP services**: use the same `https://*.hack` hostname you open from the host (whatever you configured in Caddy labels, e.g. `https://api.myapp.hack`). -- **Non-HTTP services** (DB/Redis/etc.): use the Compose service hostname (e.g. `db`, `redis`). +- Agent integrations via `hack setup` +- MCP for no-shell clients via `hack mcp` +- Linear as a ticket-normalization extension, not the center of the product +- GitHub when you want PR automation, PR updates, and private-repo bootstrap help -For HTTP services, use the same `https://*.hack` URLs you use on the host. `hack up` injects internal DNS, -TLS trust, and `extra_hosts` mappings so `*.hack` resolves reliably inside containers. If you see `ENOTFOUND` -inside containers, run `hack restart` to refresh the host mappings. +If your agent has shell access, prefer the CLI. Use MCP only when shell access is unavailable. -For non-HTTP services, use the Compose service hostname on the default network: +Docs: -- `Postgres: db:5432` -- `Redis: redis:6379` +- [Extensions](docs/extensions.md) +- [Gateway overview](docs/gateway.md) -Note: Caddy’s CA is mounted into containers when `internal.tls: true` so HTTPS calls to `*.hack` work for most runtimes. -If you’re using Java/Kotlin, you’ll need to import the CA into the JVM truststore manually. +## Beta Capabilities -Example: +> Beta surfaces are real and useful, but they are not the core promise of Hack yet. -```yaml -environment: - DATABASE_URL: postgres://postgres:postgres@db:5432/mydb - REDIS_URL: redis://redis:6379 -``` - -If you need host access for debugging, prefer `docker compose exec` so you don’t reintroduce port conflicts: - -```bash -docker compose -f .hack/docker-compose.yml exec db psql -U postgres -d mydb -docker compose -f .hack/docker-compose.yml exec redis redis-cli -``` - -## Logs (why both Compose and Loki) - -By default, `hack logs` uses `docker compose logs` because it’s the lowest latency tail. -The daemon does not proxy logs yet; `hack logs` still talks directly to Docker Compose or Loki. - -Loki is still valuable for: - -- querying across time (history) -- filtering by labels (project/service/container) -- Grafana Explore / dashboards - -### CLI - -```bash -# Tail (fast) -hack logs --pretty +### Remote control plane -# Snapshot -hack logs --no-follow --pretty +The gateway, supervisor, and remote shell flows let you control Hack over HTTP and remote transports. This is the foundation for off-device control, but it is still a secondary surface. -# Snapshot JSON -hack logs --no-follow --json +Docs: -# Query/history (force Loki) -hack logs --loki --pretty +- [Gateway overview](docs/gateway.md) +- [Gateway API](docs/gateway-api.md) +- [Supervisor](docs/supervisor.md) -# Range (Loki only) -hack logs --loki --since 2h --pretty -hack logs --loki --since 4h --until 1h --pretty +### Multi-node -# Filter Loki by service -hack logs --loki --services api,worker --pretty +Hack can pair remote nodes, bootstrap workspaces, and dispatch work across machines. This is useful when your local machine is not enough, but it is still beta-level product surface area. -# Raw LogQL -hack logs --loki --query '{project="my-project"} |= "error"' --pretty -``` +Docs: -### Grafana +- [Remote node quickstart](docs/guides/remote-node-quickstart.md) +- [Remote node container guide](docs/guides/remote-node-container.md) +- [Remote supervisor guide](docs/guides/remote-supervisor.md) -- Open: `hack open logs` or visit `https://logs.hack` -- Explore queries: +### macOS app -```logql -{project="my-project"} -{project="my-project", service="api"} -``` +Hack Desktop gives you a native dashboard for projects, tickets, sessions, and integrations. It is a beta companion to the CLI, not the primary entry point. -Alloy labels logs with: +Docs: -- project: `Docker Compose project name` -- service: `Docker Compose service name` -- container: `Docker container name` +- [macOS app README](apps/macos/README.md) +## How Hack Works -## Projects registry (bird’s-eye view) +- Project config lives in `.hack/`. +- Each project or branch instance gets its own isolated Docker network and compose identity. +- A global Caddy proxy gives routed services stable local HTTPS hostnames. +- `hack logs` uses Compose for the fast path and Loki/Grafana for history and querying. +- `hackd` is an optional daemon for faster `projects` and `ps` snapshots. -`hack` maintains a best-effort registry under `~/.hack/projects.json` so you can target a project from anywhere: +Useful commands: ```bash +hack help hack projects -hack logs --project my-project --pretty -hack up --project my-project -``` - -## .hack and valid tld requirements - -OAuth providers (notably Google) require `localhost` or a host that ends with a real public suffix. - -We keep `.hack` as the primary local dev domain, and optionally expose an alias domain for OAuth flows. -If the OAuth alias is enabled, `hack global install` configures `*.hack.gy` to resolve to the Caddy -container IP via dnsmasq + the OS resolver (bypasses port forwarding issues with Tailscale/VPNs). - -Routing rules: - -- `https://*.hack` is the default local-dev hostname space. -- `https://*.hack.gy` is an OAuth-safe alias host space (public-suffix-style domain). -- Only services with Caddy labels and attached to `hack-dev` are exposed via these hostnames. -- These domains are local routes by default; use tunnels/remote ingress separately for internet-reachable webhooks. - -If you use Next.js (or another dev server that cares about dev origins), configure its dev allowlist to include the proxy domains. -Next.js supports `allowedDevOrigins` (wildcards supported) in `next.config.js`: - -```js -module.exports = { - allowedDevOrigins: ["*.hack", "*.hack.gy"], -} -``` - -Optionally you can pass in your own custom `dev_host` to the config. - - -## SSL - -`hack` uses Caddy’s internal PKI to issue certs for `*.hack` (and any OAuth alias host). This covers -HTTPS for services routed through Caddy, but it does not create cert/key files for services running -outside of Caddy. - -- macOS: run `hack global trust` to trust the Caddy Local CA in the System keychain. -- Other OS: run `hack global ca` to export the CA cert path, then add it to your OS/browser trust store. -- If you need the PEM directly: `hack global ca --print`. -- If you are running a local service outside of Caddy, use `hack global cert ` (mkcert required) to generate a cert/key under `~/.hack/certs` and wire it into your service. This is only needed for non-Caddy services that still want trusted TLS. -- macOS: `hack global install` can optionally install mkcert (needed for `hack global cert`). - -Install mkcert if you don't already have it (macOS example): - -```bash -brew install mkcert -mkcert -install -``` - -Example (non-Caddy service): - -```bash -hack global cert --install api.myapp.hack -``` - -Use `--out ` if you want certs written somewhere else. - -## Internal DNS (containers) - -`hack global install` runs CoreDNS on the `hack-dev` network. CoreDNS answers `*.hack` and `*.hack.*` with -Caddy’s current IP so containers can use the same `https://*.hack` URLs as the host. - -This is the core isolation model: each repo keeps its own compose network, while routed HTTP services get stable hostnames -through shared Caddy ingress. That means you can call local services by hostname from host or container without relying on -host-published `localhost:` patterns. - -Some runtimes don’t honor custom DNS for `*.hack` reliably, so `hack up` also injects `extra_hosts` mappings -to the Caddy IP. If the Caddy IP changes, `hack status`, `hack doctor`, and the TUI show a warning; fix it -with `hack restart` to refresh the mapping. - -If you also need `extra_hosts` for non-`*.hack` hostnames (common when you run host-local tunnels/proxies -and want containers to reach them by their real domain), use `hack internal extra-hosts set` to write a -repo-local `.hack/.internal/extra-hosts.json` that gets merged into the generated Compose override. - -When `internal.tls` is enabled, `hack up` mounts the Caddy Local CA into each container and sets common -SSL env vars so HTTPS to `*.hack` is trusted inside containers. - -If you update `hack`, rerun `hack global install` once to refresh the CoreDNS config. - - - - -## Why not just use X? - -### Docker / Compose alone - -They run containers. They don’t give you stable hostnames, HTTPS, or a way to run many isolated copies of the same stack without custom glue. - -You can build that layer yourself. I did. That’s this. - -### Kubernetes - -Kubernetes solves cluster orchestration. This problem is local parallelism. -It adds complexity without fixing ports, routing, or developer feedback loops. - -### Different ports - -This is the default answer and it doesn’t scale. -Ports leak into config, break OAuth and cookies, and turn into debt. -Hostnames scale. Ports don’t. - -There isn’t an off-the-shelf tool that gives you full local network isolation, real HTTPS, and near zero per-repo setup. - -If you want that, you have to build it yourself. - - -## How it works - -`hack` is a thin layer on top of Docker Compose plus a tiny global proxy. - -- each project/branch runs in its own Docker network -- services use their normal ports inside that network -- a shared proxy routes `https://*.hack` and handles HTTPS -- logs are captured centrally - -Your code doesn’t change. Your mental overhead does. - -### Global (once per machine) - -`hack global install` provisions `~/.hack/` and starts: - -- **Caddy** (`lucaslorentz/caddy-docker-proxy`) on ports `80/443` - - watches Docker labels and auto-routes `https://*.hack` -- **Logging stack**: Grafana + Loki + Alloy - - reachable via `https://logs.hack` - -### Per-project (per repo) - -`hack init` creates `.hack/` in the repo root: - -- `.hack/docker-compose.yml`: your project services -- `.hack/hack.config.json`: project config (name, dev host, log preferences) - -Each project’s compose network stays isolated; only services you want “public” get attached to the shared ingress network so Caddy can reach them. - - -## Common patterns (deps + ops) - -Agents: run `hack agent patterns` for a compact checklist based on this section. - -### 1) Containerized dependency installs (recommended) - -If you run `bun install` on the host and then start Linux containers, you can hit platform -mismatches (native modules, postinstall scripts, OS-specific binaries). The clean pattern is to -install dependencies **inside** Docker and share them via a volume. - -Add a one-shot deps service and make your app services depend on it: - -```yaml -deps: - image: imbios/bun-node:latest - working_dir: /app - volumes: - - ..:/app - - node_modules:/app/node_modules - command: bun install - networks: - - default - -www: - image: imbios/bun-node:latest - working_dir: /app/apps/www - volumes: - - ..:/app - - node_modules:/app/node_modules - depends_on: - deps: - condition: service_completed_successfully -``` - -This keeps dependency resolution in the same OS as your containers and avoids host/guest drift. - -### 2) Networked ops commands (DB schema tooling, migrations) - -Because `hack` avoids publishing DB ports to your host, run schema/ops commands inside the -compose network. - -Option A (recommended): add an ops-only service: - -```yaml -db-ops: - image: imbios/bun-node:latest - working_dir: /app/packages/db # where your db schema + package.json live - volumes: - - ..:/app - - node_modules:/app/node_modules - environment: - DATABASE_URL: postgres://postgres:postgres@db:5432/mydb - depends_on: - - db - - deps - networks: - - default - profiles: ["ops"] - # Examples: - # - Prisma: bunx prisma migrate deploy - # - Drizzle: bunx drizzle-kit push - command: bun run db:push -``` - -Run it on demand: - -```bash -docker compose -f .hack/docker-compose.yml --profile ops run --rm db-ops -``` - -Option B: run via `hack run` (thin wrapper over `docker compose run --rm`): - -```bash -hack run --workdir /app/packages/db email-sync -- bunx prisma generate -hack run --workdir /app/packages/db email-sync -- bunx prisma migrate dev -hack run --workdir /app/packages/db email-sync -- bunx drizzle-kit push -hack run --workdir /app bun run turbo db:migrate -``` - -If your ops service is behind a compose profile, enable it: - -```bash -hack run --profile ops --workdir /app/packages/db db-ops -- bun run db:push +hack status +hack daemon start +hack open logs ``` -See examples: -- `examples/next-app/README.md` - - -## Troubleshooting - -- `*.hack` doesn’t resolve: run `hack doctor`, then `hack global install` (macOS: ensure dnsmasq is running). - -- Stale global setup / CoreDNS issues: run `hack doctor --fix` (refreshes network + CoreDNS + CA). - -- TLS warnings: run `hack global trust` (macOS). - -- Logs missing in Grafana: ensure Alloy is running (`hack global status`) and try `{app="docker"}` in Explore. +## Documentation -- `ENOTFOUND` for `*.hack` inside containers: run `hack restart` to refresh `extra_hosts` mappings (check - `hack status` or the TUI for Caddy IP mismatch warnings). +Docs are split into three paths: -- `EAI_AGAIN` for external domains inside containers (e.g. `api.clerk.com`): CoreDNS isn’t forwarding. - Run `hack global install` and restart CoreDNS: - `docker compose -f ~/.hack/caddy/docker-compose.yml restart coredns`. +- [Docs hub](docs/README.md) +- [Core docs](docs/core.md) for the default local product story +- [Beta workflows](docs/beta.md) for remote and control-plane flows +- [Extensions & reference](docs/reference.md) for commands, APIs, and integrations -- `hack global up` warns about `hack-dev` network labels or missing subnet: remove the network and reinstall: - `docker network rm hack-dev` then `hack global install`. - -- OAuth redirect errors: use the OAuth alias host (`*.hack.gy`) or `localhost` (providers may reject non-public suffixes like `.hack`). - - - -## Development - -### From source +## Develop From Source ```bash bun install bun run install:dev hack --help ``` -This installs a small `hack` shim into `~/.hack/bin/hack` that runs your working tree directly (no rebuild needed). -If `hack` isn’t found, add this to your shell config: +Useful repo commands: ```bash -export PATH="$HOME/.hack/bin:$PATH" +bun test +bun x ultracite check ``` -### Compiled binary (release-like) - -```bash -bun install -bun run install:bin -hack --help -``` -This builds `dist/hack` via `bun build --compile` and installs it to `~/.hack/bin/hack`. - -### Check which install is active - -```bash -bun run install:status -``` -Reports whether `hack` is a dev shim or a compiled binary (and where it points). - - -### Run in place - -```bash -bun dev --help -``` - -### Tests - -```bash -bun run test -``` - -Runs workspace tests via Turbo (`@hack/cli`, `@hack/auth-broker`, `@hack/db`). - -### Workspace command model - -Root commands are orchestration wrappers. For package-local work, run commands directly: - -```bash -bun run --cwd packages/cli build -bun run --cwd packages/cli macos:ghostty:bundle -bun run --cwd services/auth-broker test -bun run --cwd packages/db db:migrate -``` - -### Conventional commits - -```bash -bun run commit -``` - -Commitlint runs on `git commit` via husky, and semantic-release uses Conventional Commits to -compute versions. - -### Build a standalone binary - -```bash -bun run build -./dist/hack --help -``` - -### Build a release bundle - -```bash -bun run build:release -``` -Produces `dist/release/hack--release/`, a tarball, and `hack-install.sh`. - -### Release (tag + GitHub workflow) - -```bash -bun run release:prepare -git push --follow-tags -``` - -Updates `CHANGELOG.md` plus root/workspace `package.json` versions, creates the release commit and tag, and triggers the GitHub Release workflow on push. - -See `PACKAGING.md` for details. - - -See also: -- [Examples](examples/next-app/README.md) -- [Docs](docs/README.md) -- [Architecture](docs/architecture.md) - +Useful repo-level commands: -

(back to top)

+- `bun run build` +- `bun run test` +- `bun run check` +- `bun run macos:build` diff --git a/apps/macos/Packages/Features/DashboardFeature/Sources/DashboardFeature/SettingsOverlayView.swift b/apps/macos/Packages/Features/DashboardFeature/Sources/DashboardFeature/SettingsOverlayView.swift index f99c08202..40da800d8 100644 --- a/apps/macos/Packages/Features/DashboardFeature/Sources/DashboardFeature/SettingsOverlayView.swift +++ b/apps/macos/Packages/Features/DashboardFeature/Sources/DashboardFeature/SettingsOverlayView.swift @@ -3530,7 +3530,7 @@ private struct LinearExtensionSettingsView: View { } private var linearSyncPolicySummary: String { - "Authority follows ticket origin. Origin-owned fields such as title, body, status, and project binding stay authoritative on the side that created the ticket, while assignees, labels, dependencies, and sub-issue links are best-effort mergeable when enabled. Comments are append-only and synced in FIFO order rather than edited in place." + "Authority follows ticket origin. Origin-owned fields such as title, body, and status stay authoritative on the side that created the ticket. Linear linkage metadata refreshes from Linear, while Hack project placement stays local. Assignees, labels, dependencies, and sub-issue links are best-effort mergeable when enabled. Comments are append-only and synced in FIFO order rather than edited in place." } private var linearSyncReviewSummary: String { diff --git a/apps/macos/Packages/Shared/Models/Sources/HackDesktopModels/Models.swift b/apps/macos/Packages/Shared/Models/Sources/HackDesktopModels/Models.swift index eb6106e45..81033232d 100644 --- a/apps/macos/Packages/Shared/Models/Sources/HackDesktopModels/Models.swift +++ b/apps/macos/Packages/Shared/Models/Sources/HackDesktopModels/Models.swift @@ -2479,7 +2479,7 @@ public struct LinearSyncUXState: Hashable { guard isLinkedToLinear else { return "Local only. Connect to Linear to sync this ticket." } - return "\(authority.label) controls title, body, status, and project. Comments append only. Assignee, labels, and dependencies sync best effort." + return "\(authority.label) controls title, body, and status. Linear linkage metadata refreshes from Linear, while Hack project placement stays local. Comments append only. Assignee, labels, and dependencies sync best effort." } private static func makeReviewHint( diff --git a/apps/macos/Packages/Shared/Models/Tests/HackDesktopModelsTests/LinearSyncUXModelTests.swift b/apps/macos/Packages/Shared/Models/Tests/HackDesktopModelsTests/LinearSyncUXModelTests.swift index 4fb045486..a8faa2161 100644 --- a/apps/macos/Packages/Shared/Models/Tests/HackDesktopModelsTests/LinearSyncUXModelTests.swift +++ b/apps/macos/Packages/Shared/Models/Tests/HackDesktopModelsTests/LinearSyncUXModelTests.swift @@ -37,7 +37,8 @@ final class LinearSyncUXModelTests: XCTestCase { XCTAssertEqual(ticket.linearSyncAuthority, .hack) XCTAssertTrue(ticket.linearSyncUXState.isLinkedToLinear) XCTAssertNil(ticket.linearSyncUXState.reviewHint) - XCTAssertTrue(ticket.linearSyncUXState.shortGuidance.contains("Hack controls title, body, status, and project.")) + XCTAssertTrue(ticket.linearSyncUXState.shortGuidance.contains("Hack controls title, body, and status.")) + XCTAssertTrue(ticket.linearSyncUXState.shortGuidance.contains("Hack project placement stays local.")) } func testLinearOriginTicketIsLinearAuthoritative() { @@ -52,7 +53,8 @@ final class LinearSyncUXModelTests: XCTestCase { XCTAssertEqual(ticket.linearSyncAuthority, .linear) XCTAssertTrue(ticket.linearSyncUXState.isLinkedToLinear) XCTAssertNil(ticket.linearSyncUXState.reviewHint) - XCTAssertTrue(ticket.linearSyncUXState.shortGuidance.contains("Linear controls title, body, status, and project.")) + XCTAssertTrue(ticket.linearSyncUXState.shortGuidance.contains("Linear controls title, body, and status.")) + XCTAssertTrue(ticket.linearSyncUXState.shortGuidance.contains("Hack project placement stays local.")) } func testLinkedLinearTicketWithMismatchedOwnershipNeedsReviewHint() { diff --git a/apps/macos/README.md b/apps/macos/README.md index d6dd111c5..a9b3ec9eb 100644 --- a/apps/macos/README.md +++ b/apps/macos/README.md @@ -1,7 +1,9 @@ -# Hack Desktop (macOS) +# Hack Desktop for macOS (Beta) Native macOS app for managing local hack projects and daemon status. +> Beta: the macOS app is useful, but it is still outside Hack's core getting-started path. + ## Requirements - macOS 14+ diff --git a/docs/README.md b/docs/README.md index e493be242..57b8547ca 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,45 +1,55 @@ # Docs -This directory contains the public documentation for hack. Specs remain in `SPECS/` (working notes). - -## Core docs - -- [CLI reference](cli.md) -- [Architecture](architecture.md) -- [Sessions](sessions.md) - mux session management (tmux or zellij) + API -- [Env & secrets](env.md) - env contract + `.hack/.env` + OS keychain secrets -- [Lifecycle](lifecycle.md) - project startup hooks + managed host processes -- [Gateway overview](gateway.md) -- [Gateway API](gateway-api.md) -- [Supervisor](supervisor.md) -- [Extensions](extensions.md) -- [Control-plane SDK](sdk.md) - -Monorepo note: -- Root scripts orchestrate workspace tasks through Turbo. -- Package-local commands remain available via `bun run --cwd