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.
/ork:dev/ork:dev — Lab-Stack Boot
One command boots the four moving parts of a Vercel-Labs-flavored dev loop:
- portless → named HTTPS
https://<branch>.localhost(no port collisions across worktrees) - emulate → stateful API emulators on the same origin via
@emulators/adapter-next - dev server →
pnpm dev/npm run dev/yarn dev(auto-detected) - 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:devwarms 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
| Situation | Command |
|---|---|
| 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 tableFor 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.jsonWorktree 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:
- Reads
.claude/state/dev-stack.jsonto find the agent-browser session and base URL. - Computes the affected route from the file path (
app/dashboard/page.tsx→/dashboard). - Drives agent-browser against
<baseUrl><route>using the live session. - 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 -CCsession — agent-browser dashboard incompatible with iTerm2 tmux integration.
References
| File | Purpose |
|---|---|
references/boot-sequence.md | Step-by-step boot annotated with commands |
references/state-schema.md | Full JSON shape + field semantics |
Related skills
/ork:expect— diff-aware browser tests; reuses the agent-browser session this skill warms/ork:emulate-seed— generates the emulator config that step 3 consumesportless(skill) — underlying tool docsbrowser-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
| Signal | Manager |
|---|---|
packageManager field in package.json | use that |
pnpm-lock.yaml | pnpm |
yarn.lock | yarn |
bun.lockb | bun |
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.localhostCap 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 --backgroundFlags rationale:
--tls(default since 0.10) — agent-browser sessions trust portless CA via auto-injectedNODE_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 viaportless 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 --autoReuses 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)
- agent-browser session stop
- dev server (SIGTERM, fall back to SIGKILL after 5s)
- emulate (SIGTERM)
- portless stop --domain $SUBDOMAIN
- 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.
Design To Code
Mockup-to-component pipeline using Google Stitch, 21st.dev, and Storybook MCP. Accepts screenshots, descriptions, or URLs as input and produces production-ready React components. Checks existing Storybook components before generating, orchestrates design extraction via Stitch MCP, component matching via 21st.dev registry, adaptation to project design tokens, and self-healing verification via run-story-tests. Use when converting visual designs to code, implementing UI from mockups, or building components from screenshots.
Devops Deployment
Use when setting up CI/CD pipelines, containerizing applications, deploying to Kubernetes, or writing infrastructure as code. DevOps & Deployment covers GitHub Actions, Docker, Helm, and Terraform patterns.
Last updated on