Skip to main content
OrchestKit v7.73.0 — 107 skills, 37 agents, 185 hooks · Claude Code 2.1.118+
OrchestKit
Skills

Dev

One-command dev loop boot. Spins up portless (named HTTPS subdomain), emulate (stateful API mocks), the project's dev server, and an agent-browser session — all using the current git branch as the namespace key. Replaces the 4-terminal manual setup with a single `/ork:dev` invocation. Use when starting a new feature branch, switching worktrees, or returning to a project after a break. Skip silently when prerequisite binaries (portless, emulate, agent-browser) are missing — emits install hints.

Command medium
Invoke
/ork:dev

/ork:dev — Lab-Stack Boot

One command boots the four moving parts of a Vercel-Labs-flavored dev loop:

  1. portless → named HTTPS https://<branch>.localhost (no port collisions across worktrees)
  2. emulate → stateful API emulators on the same origin via @emulators/adapter-next
  3. dev serverpnpm dev / npm run dev / yarn dev (auto-detected)
  4. agent-browser → pre-warmed session named after the branch

State lives in .claude/state/dev-stack.json. Teardown via /ork:dev stop reads the PIDs and signals SIGTERM in reverse boot order.

Paired with /ork:expect: the agent-browser session that /ork:dev warms is the same one /ork:expect (and the M125 #2 auto-trigger) attach to — no second startup latency on the first UI test.

When to invoke

SituationCommand
Start work on a new branch/ork:dev
Resume after a session break/ork:dev (idempotent — skips already-live processes)
Tear down before deleting branch/ork:dev stop
Inspect state/ork:dev status

Boot sequence

The skill executes the steps below in strict order. Each step is a precondition for the next.

0. Detect package manager      pnpm > npm > yarn > bun  (read packageManager field, fall back to lockfile)
1. Resolve subdomain            <git branch slug>.localhost  (replace / with -, lowercase)
2. portless start --domain $SUBDOMAIN --tls --lan
3. emulate-seed --auto          (M125 #4 — only if emulators absent)
4. emulate up                   (services from auto-seed config)
5. <pkg-mgr> dev                (background; logs to .claude/state/dev.log)
6. wait-on https://$SUBDOMAIN   (TLS handshake confirms portless reachable)
7. agent-browser session start --name $SUBDOMAIN
8. write .claude/state/dev-stack.json with PIDs + subdomain
9. print summary table

For a worked example walking through each step on this repo, load references/boot-sequence.md.

State file shape

{
  "bootedAt": "2026-04-27T12:34:56Z",
  "branch": "feat/m125-lane-b",
  "subdomain": "feat-m125-lane-b.localhost",
  "baseUrl": "https://feat-m125-lane-b.localhost",
  "processes": {
    "portless":     { "pid": 12345, "command": "portless start --domain ..." },
    "emulate":      { "pid": 12346, "command": "emulate up" },
    "devServer":    { "pid": 12347, "command": "pnpm dev" },
    "agentBrowser": { "sessionName": "feat-m125-lane-b" }
  },
  "emulators": ["github", "stripe", "google-oauth"]
}

Full schema: references/state-schema.md.

Prerequisites + graceful no-op

$ /ork:dev
✗ portless not found.   Install: npm i -g portless
✗ emulate not found.    Install: npm i -g emulate
✓ agent-browser  found  v0.26.0
✗ dev server     no `dev` script in package.json

Skipping boot — install missing tools and re-run.

The skill does not boot a partial stack. Either all 4 components are present or it exits 0 and prints install hints. Half-stacks confuse more than they help.

For non-Vercel projects (no portless/emulate), the skill suggests the closest analogues (e.g. caddy for HTTPS proxy) but does not auto-install them.

Status + teardown

$ /ork:dev status
ork:dev — feat/m125-lane-b
  portless      pid 12345  https://feat-m125-lane-b.localhost   ✓ live
  emulate       pid 12346  3 services (github, stripe, google-oauth)  ✓ live
  dev server    pid 12347  next dev (port 3000)  ✓ live
  agent-browser session "feat-m125-lane-b"  ✓ connected
  Uptime: 2h 14m
  Booted from commit: ca73a6411

$ /ork:dev stop
Sending SIGTERM in reverse boot order…
  ✓ agent-browser session stopped
  ✓ next dev (pid 12347) stopped
  ✓ emulate (pid 12346) stopped
  ✓ portless (pid 12345) stopped
Cleared .claude/state/dev-stack.json

Worktree behavior

Each git worktree gets its own subdomain — feat-foo.localhost and feat-bar.localhost coexist on the same machine. The state file lives under each worktree's .claude/state/, so /ork:dev from one worktree doesn't see the other's processes.

Idempotency

Re-running /ork:dev while the stack is already live is a no-op:

$ /ork:dev
ork:dev — feat/m125-lane-b already running.
  https://feat-m125-lane-b.localhost  (uptime 2h 14m)
Run /ork:dev stop to tear down, or /ork:dev status for detail.

Liveness probe: process.kill(pid, 0) against each tracked PID. If any are dead, the skill prints which ones and offers to clean up state and reboot.

How agent-browser composes

The session name equals the subdomain — agent-browser commands targeting that session don't need a --session flag if it's the only one connected:

agent-browser open "https://feat-m125-lane-b.localhost/dashboard"
# implicit session = "feat-m125-lane-b" because it's the only one

/ork:expect (M125 #2) reads the dev-stack state file and reuses this same session — no second handshake.

Integration with /ork:expect (M125 #2)

When auto-expect fires after a .tsx edit, it:

  1. Reads .claude/state/dev-stack.json to find the agent-browser session and base URL.
  2. Computes the affected route from the file path (app/dashboard/page.tsx/dashboard).
  3. Drives agent-browser against &lt;baseUrl&gt;&lt;route&gt; using the live session.
  4. Records the ARIA snapshot to memory keyed by (route, parentCommit) (M125 #6).

If the dev stack isn't live, auto-expect skips silently — /ork:dev is the prerequisite, not a hard dep.

When NOT to use

  • CI — set CI=1; the skill exits 0 without booting.
  • Production deploys — never; this is dev-loop only.
  • Non-Vercel-Labs stacks — falls back to install hints; you can still run the underlying tools manually.
  • Inside a tmux -CC session — agent-browser dashboard incompatible with iTerm2 tmux integration.

References

FilePurpose
references/boot-sequence.mdStep-by-step boot annotated with commands
references/state-schema.mdFull JSON shape + field semantics
  • /ork:expect — diff-aware browser tests; reuses the agent-browser session this skill warms
  • /ork:emulate-seed — generates the emulator config that step 3 consumes
  • portless (skill) — underlying tool docs
  • browser-tools (skill) — agent-browser command reference

References (2)

Boot Sequence

/ork:dev boot sequence — annotated walkthrough

Each step below has a precondition and a post-condition. If a precondition fails, the skill exits 0 with a hint and does NOT proceed to the next step.

Step 0 — detect package manager

SignalManager
packageManager field in package.jsonuse that
pnpm-lock.yamlpnpm
yarn.lockyarn
bun.lockbbun
package-lock.json (or none of the above)npm

Step 1 — resolve subdomain

git rev-parse --abbrev-ref HEAD                     # feat/m125-lane-b
| sed 's|/|-|g'                                      # feat-m125-lane-b
| tr '[:upper:]' '[:lower:]'
| sed 's/[^a-z0-9-]//g'                              # strip anything not URL-safe
+ ".localhost"                                       # feat-m125-lane-b.localhost

Cap at 63 chars (DNS label limit). Falsy result (detached HEAD, no git) → fall back to dev.localhost.

Step 2 — portless

portless start --domain "$SUBDOMAIN" --tls --lan --background

Flags rationale:

  • --tls (default since 0.10) — agent-browser sessions trust portless CA via auto-injected NODE_EXTRA_CA_CERTS.
  • --lan — phone/tablet on same wifi can hit the same URL via mDNS, useful for responsive testing.
  • --background — daemonize; we track the PID via portless ls --json.

Wait condition: portless ls --json | jq ".[] | select(.domain==\"$SUBDOMAIN\")" returns a row.

Step 3 — emulate-seed (if needed)

test -f emulate.config.yaml || /ork:emulate-seed --auto

Reuses M125 #4. Skipped silently if emulate.config.yaml already exists.

Step 4 — emulate up

emulate up --config emulate.config.yaml --port-base 4000 &

Wait condition: each declared service responds to GET /healthz on its assigned port (matrix in emulate-seed/SKILL.md).

Step 5 — dev server

$PKG_MGR dev > .claude/state/dev.log 2>&1 &

@emulators/adapter-next (Next.js projects) routes /api/_emulators/* to the emulators on the same origin so OAuth callback URLs match production.

Step 6 — wait-on

npx wait-on --httpTimeout 30000 --tlsCheck false "https://$SUBDOMAIN/healthz" || \
npx wait-on --httpTimeout 30000 --tlsCheck false "https://$SUBDOMAIN/"

/healthz first (most apps that have it return 200 fast); fall back to root.

Step 7 — agent-browser session

agent-browser session start --name "$SUBDOMAIN" --keep-alive
agent-browser open "https://$SUBDOMAIN/"     # warm the connection

--keep-alive — session survives the parent shell exiting; /ork:expect reattaches by name.

Step 8 — write state

JSON shape: see references/state-schema.md. Atomic: write .claude/state/dev-stack.json.tmp then rename — no half-written state if the skill is killed mid-write.

Step 9 — print summary

ork:dev — feat/m125-lane-b
  ✓ portless     https://feat-m125-lane-b.localhost
  ✓ emulate      3 services (github, stripe, google-oauth)
  ✓ pnpm dev     port 3000
  ✓ agent-browser  session "feat-m125-lane-b"
Booted in 4.2s. Open https://feat-m125-lane-b.localhost or run /ork:expect.

Teardown order (reverse of boot)

  1. agent-browser session stop
  2. dev server (SIGTERM, fall back to SIGKILL after 5s)
  3. emulate (SIGTERM)
  4. portless stop --domain $SUBDOMAIN
  5. clear .claude/state/dev-stack.json

The reverse order matters: killing portless before agent-browser leaves agent-browser holding a TLS connection to nothing, which logs noise but isn't fatal.

State Schema

.claude/state/dev-stack.json schema

Written atomically by /ork:dev. Read by /ork:dev status, /ork:dev stop, the ui-change-detector hook, and the expect-snapshot-recorder hook.

interface DevStackState {
  bootedAt: string;        // ISO 8601, set once at boot
  branch: string;          // git branch at boot time
  subdomain: string;       // portless domain — e.g. "feat-m125-lane-b.localhost"
  baseUrl: string;         // "https://" + subdomain
  processes: {
    portless?: {
      pid: number;
      command: string;     // for diagnostic display
    };
    emulate?: {
      pid: number;
      command: string;
    };
    devServer?: {
      pid: number;
      command: string;     // "pnpm dev" / "npm run dev" / etc.
    };
    agentBrowser?: {
      sessionName: string; // == subdomain
      pid?: number;        // optional; agent-browser daemon may be detached
    };
  };
  emulators: string[];     // service names — ["github", "stripe", "google-oauth"]
}

Liveness probe

A "live" stack has at least one PID in processes that responds to process.kill(pid, 0). If all PIDs are dead, treat the file as stale and offer to clear it.

Reader contract

External consumers (other hooks, skills) MUST treat the file as read-only and tolerate it not existing. The shared library src/hooks/src/lib/dev-stack-state.ts exposes readDevStackState() which returns null for missing/malformed files — never throws.

Writer contract

Only /ork:dev writes the full state. Hooks may mutate individual fields (e.g. expect-snapshot-recorder does NOT touch this file — snapshots go to the memory graph, not here). If a future feature needs to amend dev-stack state, do it through writeDevStackState() for atomicity.

Worktree isolation

Each git worktree has its own .claude/state/ directory, so state files don't cross worktree boundaries. This is the correct behavior — separate worktrees should have separate subdomains.

Edit on GitHub

Last updated on