plugins/claude-code-dev-hermit/skills/dev-up/SKILL.md
Boot a session-scoped dev server via the core Monitor tool. Reads commands.dev_start, dev_required_ports, dev_health_url, dev_auth_check, and dev_expected_listeners from .claude-code-hermit/config.json. Run before browser-testing or any task that exercises the running app.
npx skillsauth add gtapps/claude-code-hermit dev-upInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Boot the project's dev server in a Monitor-managed subprocess so its stdout/stderr stream into the conversation as notifications. The dev server is session-scoped: it stops on /session-close and on the next session-start (per claude-code-hermit:watch SKILL.md:149-152). For a long-lived server, run it in your own tmux/systemd/foreman and treat the agent as a client.
Operator-invoked. Not auto-triggered. If you want it to fire on browser-testing tasks, add a project memory rule (Whenever I start a browser-testing task, invoke /dev-up first).
.claude-code-hermit/sessions/ exists. If not: tell the operator to run /claude-code-hermit:hatch and /claude-code-dev-hermit:hatch, then exit..claude-code-hermit/config.json once at start. Cache claude-code-dev-hermit.commands.dev_start, commands.dev_stop, dev_required_ports, dev_expected_listeners, dev_health_url, dev_health_timeout_secs, dev_auth_check, dev_log_path_pattern, and dev_error_pattern for use across gates.commands.dev_start configuredIf commands.dev_start is null or empty:
commands.dev_start not configured — run /claude-code-dev-hermit:dev-adapt
FAIL.
Idempotency is within-session only. The runtime registry clears at session-start, so the first /dev-up of a fresh session always boots even if a previous session left a dev-server running externally.
Read .claude-code-hermit/state/monitors.runtime.json. If an entry with id: "dev-server" exists:
Liveness check first — Monitor self-exit notifications usually sweep the registry, but there's a race window where the process died and the notification hasn't been processed yet. If the registry entry has a pid, run kill -0 <pid> 2>/dev/null; if the pid does not exist, treat the entry as stale: remove it from the registry, log to SHELL.md ## Monitoring - [STALE-CLEARED] dev-server, and continue to Gate 2 as if no entry existed. (If the entry has no pid recorded, fall through to step 2 — we cannot verify, must trust.)
Health probe — if dev_health_url is set: probe it once via node ${CLAUDE_PLUGIN_ROOT}/scripts/lib/health-poll.js "$dev_health_url" 5. If 2xx, return PASS-with-noop:
dev-up
monitor: dev-server already registered (pid <pid> alive)
health: 200 OK at <url>
status: already up
No health probe configured — if dev_health_url is unset and the liveness check passed, return PASS-with-noop with the explicit caveat:
dev-up
monitor: dev-server already registered (pid <pid> alive)
status: already up (no health probe — process exists, application readiness not verified)
Health probe failed but pid is alive — registry has the entry, process is up, but the URL is not 2xx. Surface and stop:
dev-server is registered (pid <pid> alive) but health probe failed at <url>: <error>
recovery: /dev-down then /dev-up, OR investigate the existing process
If no entry exists (or the stale-clear branch ran): continue to Gate 2.
If dev_auth_check is set, run it via bash -c "$dev_auth_check". If exit code is non-zero:
auth probe failed: <stderr tail>
the dev server may also fail to authenticate; fix before booting
FAIL.
Note: a passing auth probe is a positive signal, not a guarantee. The probe and the spawned dev-server inherit env from the same shell, but if your secret loader (Infisical, op, direnv) caches per-process tokens or shells, the probe and server may diverge. Treat a green auth probe as "necessary, not sufficient."
If dev_required_ports is empty: skip with PASS ports: not configured.
Otherwise, probe via the helper:
node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/port-check.js" "$(jq -nc --argjson ports "$ports_json" --argjson exp "$expected_json" '{ports:$ports,expected:$exp}')"
(or compose the JSON inline; the helper accepts a single JSON-string arg.)
The helper returns one record per port: free / allowed (matched an dev_expected_listeners[].process_match) / held (occupied by an unexpected process).
free or allowed → record for the report.
held → FAIL:
port <port> held by <process> (pid <pid>) — dev server cannot bind
if this listener is expected (e.g., a daemon that coexists with the dev server),
add to claude-code-dev-hermit.dev_expected_listeners:
{ "port": <port>, "process_match": "<process>" }
via /dev-adapt or by editing config.json directly
If dev_required_ports is non-empty: at least one of lsof or ss must be in PATH. The helper detects automatically; if it returns error: "no probing tool available", FAIL with install lsof or iproute2.
If dev_health_url is set: curl must be in PATH (or skip Gate 6 — the health-poll helper uses Node's http/https modules, so curl is not strictly required; this gate is informational only). Helper-only path: no extra dependency.
Dev servers (Next.js, Vite, pnpm workspaces) emit hundreds of lines/sec during startup and hot-reload. Piping raw output through Monitor exhausts its notification budget and causes Monitor to kill the entire process tree, taking the dev server down with it. To prevent this, we funnel the full stream to a log file via tee and pass only error-matched lines to Monitor. Monitor stays calm; TaskStop still owns the whole bash process group for clean teardown.
The pipeline is composed by scripts/lib/dev-server-command.js so shell escaping for commands.dev_start, the log path, and the error pattern is handled in one place — never hand-build the pipeline in this skill. Compose:
node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/dev-server-command.js" "$(jq -nc \
--arg start "$dev_start" \
--arg log ".claude-code-hermit/state/dev-server.log" \
--arg pat "$dev_error_pattern" \
'{devStart:$start, logPath:$log, errorPattern:$pat}')"
(Omit the errorPattern field if dev_error_pattern is unset — the helper falls back to its built-in default, which is anchored on common Node/Vite/Next.js error signals and avoids false-positives on the bare lowercase word "error" that Next.js writes during normal compile feedback.) The helper returns { command, pattern, usedDefault }.
We need a stable id dev-server so /dev-down can find this entry. The /watch skill's ad-hoc form auto-generates adhoc-<epoch>-... ids (see claude-code-hermit:watch SKILL.md "Starting an ad-hoc watch"), so we invoke the Monitor tool directly and append the registry entry ourselves with the chosen id — same recipe as /watch's ad-hoc steps 5–8, but with our id.
Steps:
> .claude-code-hermit/state/dev-server.log (logs are session-scoped and overwritten on each /dev-up boot).command and pattern.description: "dev-server: <commands.dev_start>" (shown in every notification)command: the helper's command field, used verbatimtimeout_ms: 300000 (required by tool schema; ignored when persistent)persistent: true.claude-code-hermit/state/monitors.runtime.json (create if missing per the watch skill's contract).{
"id": "dev-server",
"task_id": "<returned by Monitor>",
"description": "dev-server: <commands.dev_start>",
"started_at": "<ISO 8601>",
"source": "adhoc",
"class": "stream"
}
## Monitoring: - [ACTIVE] dev-server (started HH:MM).If the Monitor invocation errors, FAIL and surface the error. Do not partially append to the registry.
Lifecycle notes:
- The helper appends
|| trueto the grep stage so a healthy server (zero error matches) doesn't return exit 1 and kill the pipeline.- On
TaskStop, Monitor SIGTERMs the bash wrapper with a 10s drain. Clean teardown via SIGPIPE only fires when the dev server next writes — a quiet server is reaped by the SIGKILL at end-of-drain. Don't expect graceful shutdown semantics.- If Claude Code restarts this Monitor task on its own, the pipeline will try to re-spawn
commands.dev_startwhile the previous instance may still hold the port. The next/dev-upGate 3 will catch that asport held; recover by running/dev-downfirst.
If dev_health_url is unset: skip with PASS health: not configured.
Otherwise, run:
node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/health-poll.js" "$dev_health_url" "${dev_health_timeout_secs:-30}"
Helper returns JSON { ok, status, elapsedMs, error? } and exits 0 on 2xx, 1 on timeout.
2xx within timeout → record 200 OK after Ns.
Timeout / non-2xx → FAIL:
health probe failed at <url> after <elapsedMs>ms: <error>
the dev server may still be coming up; tail recent notifications for errors
or run /dev-down to stop the partially-booted process
dev-up
start: npm run dev (commands.dev_start)
ports: 3000 free, 4000 held by encore (allowed via dev_expected_listeners)
filter: ^\s*(Error|TypeError|...):|EADDRINUSE|... (default; set dev_error_pattern to override)
log: .claude-code-hermit/state/dev-server.log (full stdout, session-scoped)
monitor: dev-server registered (session-scoped)
health: 200 OK at http://localhost:3000/api/health (after 4123ms)
status: up
On Gate 1 short-circuit:
dev-up
monitor: dev-server already registered
health: 200 OK at <url>
status: already up
On any FAIL: emit the gate name, the failure reason, and the recovery hint. No partial-success reports.
/dev-up. Stale processes from a prior session were already killed by session-close (per watch SKILL.md:149); if Gate 1 sees a registry entry that's stale (process gone), the Monitor's own self-exit notification removes it. Do not race with that.tools
Presence history & tracker-health report — current home/away state, reliability, recent arrival/departure transitions, and activity patterns for person/device_tracker entities. Use when the operator asks about presence history or when a presence-dependent automation (locks, alarm, vacuum, climate) misbehaves.
development
Evening house brief — end-of-day security check, device status, and energy snapshot. Runs as a daily routine at 22:30 or on demand.
tools
Browse and explain the hermit's Home Assistant automations — list by topic, filter by keyword with plain-language YAML explanations, or sort by last-fired. Read-only. Use when the operator asks "what automations do I have / what does this one do / which haven't fired."
tools
On-demand HA-voice brainstorm — reads entity inventory, automation/script listings, and operator intent to surface at most 2 capability-gap ideas, each gated by proposal-triage before becoming a PROP. Invoke when the operator asks "what automations am I missing?", "any coverage gaps?", or "brainstorm improvements". Never runs autonomously.