winter-super-cli 2026.6.26 → 2026.6.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -5
- package/README.md +66 -0
- package/package.json +5 -1
- package/resources/local/gsap-skills/.claude-plugin/marketplace.json +20 -0
- package/resources/local/gsap-skills/.claude-plugin/plugin.json +6 -0
- package/resources/local/gsap-skills/.cursor-plugin/marketplace.json +13 -0
- package/resources/local/gsap-skills/.cursor-plugin/plugin.json +22 -0
- package/resources/local/gsap-skills/.github/copilot-instructions.md +17 -0
- package/resources/local/gsap-skills/.github/instructions/react.instructions.md +15 -0
- package/resources/local/gsap-skills/.github/instructions/scrolltrigger.instructions.md +18 -0
- package/resources/local/gsap-skills/AGENTS.md +27 -0
- package/resources/local/gsap-skills/CLAUDE.md +1 -0
- package/resources/local/gsap-skills/GEMINI.md +1 -0
- package/resources/local/gsap-skills/LICENSE +21 -0
- package/resources/local/gsap-skills/README.md +163 -0
- package/resources/local/gsap-skills/assets/gsap-green.svg +7 -0
- package/resources/local/gsap-skills/assets/gsap-icon-inverted.svg +15 -0
- package/resources/local/gsap-skills/assets/gsap-icon-square.svg +1 -0
- package/resources/local/gsap-skills/assets/gsap-white.svg +7 -0
- package/resources/local/gsap-skills/examples/README.md +29 -0
- package/resources/local/gsap-skills/examples/nuxt/app/app.vue +3 -0
- package/resources/local/gsap-skills/examples/nuxt/app/composables/useGSAP.ts +91 -0
- package/resources/local/gsap-skills/examples/nuxt/app/pages/index.vue +55 -0
- package/resources/local/gsap-skills/examples/nuxt/nuxt.config.ts +4 -0
- package/resources/local/gsap-skills/examples/nuxt/package.json +18 -0
- package/resources/local/gsap-skills/examples/react/App.jsx +46 -0
- package/resources/local/gsap-skills/examples/react/index.html +12 -0
- package/resources/local/gsap-skills/examples/react/main.jsx +9 -0
- package/resources/local/gsap-skills/examples/react/package.json +21 -0
- package/resources/local/gsap-skills/examples/react/vite.config.js +7 -0
- package/resources/local/gsap-skills/examples/vanilla/index.html +33 -0
- package/resources/local/gsap-skills/examples/vanilla/main.js +36 -0
- package/resources/local/gsap-skills/examples/vue/app.vue +47 -0
- package/resources/local/gsap-skills/examples/vue/index.html +15 -0
- package/resources/local/gsap-skills/examples/vue/main.js +9 -0
- package/resources/local/gsap-skills/examples/vue/package.json +19 -0
- package/resources/local/gsap-skills/examples/vue/vite.config.js +7 -0
- package/resources/local/gsap-skills/skills/gsap-core/SKILL.md +254 -0
- package/resources/local/gsap-skills/skills/gsap-frameworks/SKILL.md +266 -0
- package/resources/local/gsap-skills/skills/gsap-performance/SKILL.md +79 -0
- package/resources/local/gsap-skills/skills/gsap-plugins/SKILL.md +433 -0
- package/resources/local/gsap-skills/skills/gsap-react/SKILL.md +136 -0
- package/resources/local/gsap-skills/skills/gsap-scrolltrigger/SKILL.md +296 -0
- package/resources/local/gsap-skills/skills/gsap-timeline/SKILL.md +107 -0
- package/resources/local/gsap-skills/skills/gsap-utils/SKILL.md +284 -0
- package/resources/local/gsap-skills/skills/llms.txt +39 -0
- package/resources/local/hermes-agent-core/AGENTS.md +1132 -0
- package/resources/local/hermes-agent-core/LICENSE +21 -0
- package/resources/local/hermes-agent-core/README.md +215 -0
- package/resources/local/hermes-agent-core/docs/2026-05-07-s6-overlay-dynamic-subagent-gateways.md +434 -0
- package/resources/local/hermes-agent-core/hermes-already-has-routines.md +160 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/DESCRIPTION.md +3 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/claude-code/SKILL.md +745 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/codex/SKILL.md +130 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/hermes-agent/SKILL.md +1021 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +277 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +57 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/opencode/SKILL.md +219 -0
- package/resources/local/hermes-agent-core/skills/github/DESCRIPTION.md +3 -0
- package/resources/local/hermes-agent-core/skills/github/codebase-inspection/SKILL.md +116 -0
- package/resources/local/hermes-agent-core/skills/github/github-auth/SKILL.md +247 -0
- package/resources/local/hermes-agent-core/skills/github/github-auth/scripts/gh-env.sh +66 -0
- package/resources/local/hermes-agent-core/skills/github/github-code-review/SKILL.md +481 -0
- package/resources/local/hermes-agent-core/skills/github/github-code-review/references/review-output-template.md +74 -0
- package/resources/local/hermes-agent-core/skills/github/github-issues/SKILL.md +370 -0
- package/resources/local/hermes-agent-core/skills/github/github-issues/templates/bug-report.md +35 -0
- package/resources/local/hermes-agent-core/skills/github/github-issues/templates/feature-request.md +31 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/SKILL.md +367 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/references/ci-troubleshooting.md +183 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/references/conventional-commits.md +71 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +35 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/templates/pr-body-feature.md +33 -0
- package/resources/local/hermes-agent-core/skills/github/github-repo-management/SKILL.md +516 -0
- package/resources/local/hermes-agent-core/skills/github/github-repo-management/references/github-api-cheatsheet.md +161 -0
- package/resources/local/hermes-agent-core/skills/mcp/DESCRIPTION.md +3 -0
- package/resources/local/hermes-agent-core/skills/mcp/native-mcp/SKILL.md +357 -0
- package/resources/local/hermes-agent-core/skills/software-development/debugging-hermes-tui-commands/SKILL.md +152 -0
- package/resources/local/hermes-agent-core/skills/software-development/hermes-agent-skill-authoring/SKILL.md +165 -0
- package/resources/local/hermes-agent-core/skills/software-development/hermes-s6-container-supervision/SKILL.md +176 -0
- package/resources/local/hermes-agent-core/skills/software-development/node-inspect-debugger/SKILL.md +319 -0
- package/resources/local/hermes-agent-core/skills/software-development/plan/SKILL.md +58 -0
- package/resources/local/hermes-agent-core/skills/software-development/python-debugpy/SKILL.md +375 -0
- package/resources/local/hermes-agent-core/skills/software-development/requesting-code-review/SKILL.md +280 -0
- package/resources/local/hermes-agent-core/skills/software-development/spike/SKILL.md +197 -0
- package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/SKILL.md +352 -0
- package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/references/context-budget-discipline.md +53 -0
- package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/references/gates-taxonomy.md +93 -0
- package/resources/local/hermes-agent-core/skills/software-development/systematic-debugging/SKILL.md +367 -0
- package/resources/local/hermes-agent-core/skills/software-development/test-driven-development/SKILL.md +343 -0
- package/resources/local/hermes-agent-core/skills/software-development/writing-plans/SKILL.md +297 -0
- package/resources/local/manifest.json +12 -0
- package/rule.md +2 -0
- package/scripts/audit-pack.js +5 -0
- package/scripts/smoke-browser.js +53 -0
- package/scripts/smoke-package.js +38 -4
- package/skill.md +36 -4
- package/skills/gsap.md +26 -0
- package/skills/hermes-agent.md +17 -0
- package/src/agent/agent-definitions.js +4 -4
- package/src/agent/runtime.js +206 -5
- package/src/agent/subagent-child.js +44 -0
- package/src/ai/capability-scorecard.js +193 -14
- package/src/ai/hermes-core.js +77 -0
- package/src/ai/model-capabilities.js +42 -2
- package/src/ai/prompts/system-prompt.js +16 -2
- package/src/ai/small-model-amplifier.js +35 -7
- package/src/ai/workflow-selector.js +22 -1
- package/src/cli/commands.js +21 -1
- package/src/cli/config.js +42 -4
- package/src/cli/context-loader.js +253 -9
- package/src/cli/conversation-format.js +5 -0
- package/src/cli/input-controller.js +79 -10
- package/src/cli/prompt-builder.js +45 -8
- package/src/cli/repl-commands.js +123 -2
- package/src/cli/repl.js +183 -87
- package/src/cli/slash-commands.js +3 -1
- package/src/cli/tui.js +133 -37
- package/src/mcp/client.js +46 -5
- package/src/tools/agent.js +316 -25
- package/src/tools/executor.js +314 -11
- package/src/tools/permission.js +20 -17
- package/winter.d.ts +112 -10
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hermes-s6-container-supervision
|
|
3
|
+
description: Modify, debug, or extend the s6-overlay supervision tree inside the Hermes Agent Docker image — adding new services, debugging profile gateways, understanding the Architecture B main-program pattern.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
author: Hermes Agent
|
|
6
|
+
license: MIT
|
|
7
|
+
metadata:
|
|
8
|
+
hermes:
|
|
9
|
+
tags: [docker, s6, supervision, gateway, profiles]
|
|
10
|
+
related_skills: [hermes-agent, hermes-agent-dev]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Hermes s6-overlay Container Supervision
|
|
14
|
+
|
|
15
|
+
## When to use this skill
|
|
16
|
+
|
|
17
|
+
Load this skill when you're working on:
|
|
18
|
+
- Adding or removing a static service in the Hermes Docker image (something that should be supervised at every container start, like the dashboard)
|
|
19
|
+
- Diagnosing why a per-profile gateway isn't starting, restarting, or surviving `docker restart`
|
|
20
|
+
- Understanding why the container's CMD is `/opt/hermes/docker/main-wrapper.sh` and how leading-dash args reach the user's program
|
|
21
|
+
- Modifying `cont-init.d` boot scripts (UID remap, volume seeding, profile reconciliation)
|
|
22
|
+
- Changing the rendered run-script for per-profile gateways (Phase 4)
|
|
23
|
+
|
|
24
|
+
If you're just running the Hermes Agent and want to use Docker, see `website/docs/user-guide/docker.md` instead.
|
|
25
|
+
|
|
26
|
+
## Architecture at a glance
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
/init ← PID 1 (s6-overlay v3.2.3.0)
|
|
30
|
+
├── cont-init.d ← oneshot setup, runs as root
|
|
31
|
+
│ ├── 01-hermes-setup ← docker/stage2-hook.sh
|
|
32
|
+
│ │ ├── UID/GID remap
|
|
33
|
+
│ │ ├── chown /opt/data
|
|
34
|
+
│ │ ├── chown /opt/data/profiles (every boot)
|
|
35
|
+
│ │ ├── seed .env / config.yaml / SOUL.md
|
|
36
|
+
│ │ └── skills_sync.py
|
|
37
|
+
│ └── 02-reconcile-profiles ← hermes_cli.container_boot
|
|
38
|
+
│ ├── chown /run/service (hermes-writable for runtime register)
|
|
39
|
+
│ └── walk $HERMES_HOME/profiles/<name>/gateway_state.json
|
|
40
|
+
│ → recreate /run/service/gateway-<name>/
|
|
41
|
+
│ → auto-start only those with prior_state == "running"
|
|
42
|
+
│
|
|
43
|
+
├── s6-rc.d (static services, in /etc/s6-overlay/s6-rc.d/)
|
|
44
|
+
│ ├── main-hermes/run ← exec sleep infinity (no-op slot)
|
|
45
|
+
│ └── dashboard/run ← if HERMES_DASHBOARD=1, runs `hermes dashboard`
|
|
46
|
+
│
|
|
47
|
+
├── /run/service (s6-svscan watches; tmpfs)
|
|
48
|
+
│ ├── gateway-coder/ ← runtime-registered per-profile
|
|
49
|
+
│ │ ├── type ("longrun")
|
|
50
|
+
│ │ ├── run ("#!/command/with-contenv sh ... exec s6-setuidgid hermes hermes -p coder gateway run")
|
|
51
|
+
│ │ ├── down (marker — present means "registered but don't auto-start")
|
|
52
|
+
│ │ └── log/run (s6-log → $HERMES_HOME/logs/gateways/coder/current)
|
|
53
|
+
│ └── ...
|
|
54
|
+
│
|
|
55
|
+
└── CMD ("main program") ← /opt/hermes/docker/main-wrapper.sh
|
|
56
|
+
└── routes user args: bare exec | hermes subcommand | hermes (no args)
|
|
57
|
+
— exec'd by /init with stdin/stdout/stderr inherited (TTY for --tui)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Key files
|
|
61
|
+
|
|
62
|
+
| Path | Role |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `Dockerfile` | s6-overlay install + cont-init.d wiring + `ENTRYPOINT ["/init", "/opt/hermes/docker/main-wrapper.sh"]` |
|
|
65
|
+
| `docker/stage2-hook.sh` | The "old entrypoint logic" — UID remap, chown, seed, skills sync. Runs as cont-init.d/01-hermes-setup. |
|
|
66
|
+
| `docker/cont-init.d/02-reconcile-profiles` | Calls `hermes_cli.container_boot` on every boot to restore profile gateway slots from the persistent volume. |
|
|
67
|
+
| `docker/main-wrapper.sh` | The container's CMD. Routes user args, drops to hermes via `s6-setuidgid`, exec's the chosen program. |
|
|
68
|
+
| `docker/s6-rc.d/main-hermes/run` | No-op `sleep infinity` — slot exists so the s6-rc user bundle is valid; main hermes runs as the CMD, not as a supervised service. |
|
|
69
|
+
| `docker/s6-rc.d/dashboard/run` | Conditional service — `exec sleep infinity` unless `HERMES_DASHBOARD` is truthy. |
|
|
70
|
+
| `docker/entrypoint.sh` | Back-compat shim that `exec`s the stage2 hook. External scripts that hard-coded the old entrypoint path still work. |
|
|
71
|
+
| `hermes_cli/service_manager.py` | `S6ServiceManager`: `register_profile_gateway`, `unregister_profile_gateway`, `start/stop/restart/is_running`, `list_profile_gateways`. |
|
|
72
|
+
| `hermes_cli/container_boot.py` | `reconcile_profile_gateways()` — walks persistent profiles, regenerates s6 slots, emits `container-boot.log`. |
|
|
73
|
+
| `hermes_cli/gateway.py::_dispatch_via_service_manager_if_s6` | Intercepts `hermes gateway start/stop/restart` and routes to s6 when running in a container. |
|
|
74
|
+
|
|
75
|
+
## Why Architecture B (CMD as main program, not s6-supervised)
|
|
76
|
+
|
|
77
|
+
The original plan (v1–v3) called for main hermes to run as a supervised s6-rc service. Two real s6-overlay v3 mechanics blocked that:
|
|
78
|
+
|
|
79
|
+
1. **cont-init.d scripts receive no CMD args** — so the stage2 hook can't parse `docker run <image> chat -q "hi"` to set `HERMES_ARGS` for a service `run` script to consume.
|
|
80
|
+
2. **`/run/s6/basedir/bin/halt` does NOT propagate the exit code** written to `/run/s6-linux-init-container-results/exitcode`. Containers always exit 143 (SIGTERM) regardless. Confirmed by skarnet (s6 author) in [issue #477](https://github.com/just-containers/s6-overlay/issues/477): _"if you want a container shutdown, you need to either have your CMD exit, or, if you have no CMD, write the container exit code you want then call halt"_.
|
|
81
|
+
|
|
82
|
+
So we use the s6-overlay-native CMD pattern: `ENTRYPOINT ["/init", "/opt/hermes/docker/main-wrapper.sh"]`. /init prepends the wrapper to user args automatically — so `docker run <image> --version` becomes `/init main-wrapper.sh --version`, and `--version` doesn't get intercepted by /init's POSIX shell. The wrapper drops to hermes via `s6-setuidgid`, then exec's the chosen program. The program's exit code becomes the container exit code, exactly matching the pre-s6 tini contract.
|
|
83
|
+
|
|
84
|
+
Trade-off: main hermes is unsupervised under s6. That exactly matches its behavior under tini (the pre-s6 image). Dashboard supervision is the only **new** guarantee — and per-profile gateways under `/run/service/` get full supervision.
|
|
85
|
+
|
|
86
|
+
## Quick recipes
|
|
87
|
+
|
|
88
|
+
### Verify s6 is PID 1 in a running container
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
docker exec <c> sh -c 'cat /proc/1/comm; readlink /proc/1/exe'
|
|
92
|
+
# Expect: s6-svscan or init / /package/admin/s6/.../s6-svscan
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Inspect a profile gateway service
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
# /command/ isn't on docker-exec PATH — use absolute path
|
|
99
|
+
docker exec <c> /command/s6-svstat /run/service/gateway-<name>
|
|
100
|
+
# "up (pid …) … seconds" → running
|
|
101
|
+
# "down (exitcode N) … seconds, normally up, want up, …" → s6 wants it up but the process keeps exiting (crash loop)
|
|
102
|
+
# "down … normally up, ready …" → user stopped it
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Bring a service up/down manually
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
docker exec <c> /command/s6-svc -u /run/service/gateway-<name> # up
|
|
109
|
+
docker exec <c> /command/s6-svc -d /run/service/gateway-<name> # down
|
|
110
|
+
docker exec <c> /command/s6-svc -t /run/service/gateway-<name> # SIGTERM (restart)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Watch the cont-init reconciler log
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
docker exec <c> tail -n 50 /opt/data/logs/container-boot.log
|
|
117
|
+
# 2026-05-21T06:18:05+0000 profile=coder prior_state=running action=started
|
|
118
|
+
# 2026-05-21T06:18:05+0000 profile=writer prior_state=stopped action=registered
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Add a new static service
|
|
122
|
+
|
|
123
|
+
1. Create `docker/s6-rc.d/<name>/type` with `longrun\n` and `docker/s6-rc.d/<name>/run` (use `#!/command/with-contenv sh` + `# shellcheck shell=sh`).
|
|
124
|
+
2. Drop to hermes via `s6-setuidgid hermes` at the top of run (unless you specifically need root).
|
|
125
|
+
3. Create empty `docker/s6-rc.d/<name>/dependencies.d/base` so it waits for the base bundle.
|
|
126
|
+
4. Create empty `docker/s6-rc.d/user/contents.d/<name>` so it joins the user bundle.
|
|
127
|
+
5. The `COPY docker/s6-rc.d/` in the Dockerfile picks it up automatically — no other changes.
|
|
128
|
+
|
|
129
|
+
### Change the per-profile gateway run command
|
|
130
|
+
|
|
131
|
+
Edit `S6ServiceManager._render_run_script` in `hermes_cli/service_manager.py`. The function is also called by `hermes_cli/container_boot.py::_register_service` during boot reconciliation, so it's the single source of truth. Update the corresponding assertion in `tests/hermes_cli/test_service_manager.py::test_s6_register_creates_service_dir_and_triggers_scan`.
|
|
132
|
+
|
|
133
|
+
### Run the docker test harness
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
docker build -t hermes-agent-harness:latest .
|
|
137
|
+
HERMES_TEST_IMAGE=hermes-agent-harness:latest scripts/run_tests.sh tests/docker/ -v
|
|
138
|
+
# Expect 19 passed, 0 xfailed against the s6 image
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The harness lives in `tests/docker/` and skips when Docker isn't available. The per-test timeout is bumped to 180s (see `tests/docker/conftest.py`).
|
|
142
|
+
|
|
143
|
+
## Common pitfalls
|
|
144
|
+
|
|
145
|
+
### "command not found" via `docker exec`
|
|
146
|
+
|
|
147
|
+
`/command/` (where s6-overlay puts its binaries) is on PATH only for processes spawned by the supervision tree — services, cont-init.d, main-wrapper.sh. `docker exec <c> s6-svstat …` will fail with "command not found"; always use the absolute path `/command/s6-svstat`. The `hermes` binary works because the Dockerfile adds `/opt/hermes/.venv/bin` to the runtime `ENV PATH`.
|
|
148
|
+
|
|
149
|
+
### Profile directory ownership
|
|
150
|
+
|
|
151
|
+
The cont-init reconciler runs as hermes (`s6-setuidgid hermes` in `02-reconcile-profiles`). If a profile dir ends up root-owned (e.g. because `docker exec <c> hermes profile create …` ran as root by default), the reconciler can't read SOUL.md and fails with `PermissionError`. Mitigation: `stage2-hook.sh` chowns `$HERMES_HOME/profiles` to hermes on **every** boot, idempotently. Don't remove that block.
|
|
152
|
+
|
|
153
|
+
### Files written by `docker exec` are root-owned
|
|
154
|
+
|
|
155
|
+
`docker exec` defaults to root. Either pass `--user hermes` or rely on the stage2 chown sweep next reboot. Don't write files under `$HERMES_HOME/profiles/<name>/` as root manually — the next reconcile pass will sweep them but in-flight operations may hit perm errors.
|
|
156
|
+
|
|
157
|
+
### Service slot exists but s6-svstat says "s6-supervise not running"
|
|
158
|
+
|
|
159
|
+
The service directory is on tmpfs and was wiped on container restart. Either the cont-init reconciler hasn't run yet (give it a moment after `docker restart`) or it failed. Check `docker logs <c> | grep '02-reconcile'`.
|
|
160
|
+
|
|
161
|
+
### Gateway starts then immediately exits (`down (exitcode 1)` in svstat)
|
|
162
|
+
|
|
163
|
+
Most likely the profile has no model or auth configured. The service slot is correct — the gateway itself is unconfigured. Run `hermes -p <profile> setup` first. The s6 supervisor will keep restarting it; that's the desired behavior (when you fix the config, the next attempt succeeds and stays up).
|
|
164
|
+
|
|
165
|
+
### Reconciler skipped a profile
|
|
166
|
+
|
|
167
|
+
The reconciler keys on the **presence of `SOUL.md`** as the "real profile" marker. `hermes profile create` always seeds it. If a profile dir is missing SOUL.md (stray directory, partial restore, backup-in-progress), the reconciler skips it intentionally. Add a `SOUL.md` (even empty) to opt back in.
|
|
168
|
+
|
|
169
|
+
### "Help, the container exits 143!"
|
|
170
|
+
|
|
171
|
+
Check whether something is invoking `s6-svscanctl -t` or `/run/s6/basedir/bin/halt` — both cause /init to begin stage 3 shutdown but return 143 (SIGTERM) rather than the desired exit code. This was the Phase 2 architecture pivot from A to B. For container shutdown with a real exit code, you must let the CMD (main-wrapper.sh) exit normally; do **not** try to control exit from a finish script.
|
|
172
|
+
|
|
173
|
+
## Related skills
|
|
174
|
+
|
|
175
|
+
- `hermes-agent-dev`: General hermes-agent codebase navigation
|
|
176
|
+
- `hermes-tool-quirks`: Specific Hermes-tool workarounds (sed/grep/etc.) — load when debugging the s6 stack's interaction with hermes built-in tools.
|
package/resources/local/hermes-agent-core/skills/software-development/node-inspect-debugger/SKILL.md
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: node-inspect-debugger
|
|
3
|
+
description: "Debug Node.js via --inspect + Chrome DevTools Protocol CLI."
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
author: Hermes Agent
|
|
6
|
+
license: MIT
|
|
7
|
+
platforms: [linux, macos, windows]
|
|
8
|
+
metadata:
|
|
9
|
+
hermes:
|
|
10
|
+
tags: [debugging, nodejs, node-inspect, cdp, breakpoints, ui-tui]
|
|
11
|
+
related_skills: [systematic-debugging, python-debugpy, debugging-hermes-tui-commands]
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Node.js Inspect Debugger
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
When `console.log` isn't enough, drive Node's built-in V8 inspector programmatically from the terminal. You get real breakpoints, step in/over/out, call-stack walking, local/closure scope dumps, and arbitrary expression evaluation in the paused frame.
|
|
19
|
+
|
|
20
|
+
Two tools, pick one:
|
|
21
|
+
|
|
22
|
+
- **`node inspect`** — built-in, zero install, CLI REPL. Best for quick poking.
|
|
23
|
+
- **`ndb` / CDP via `chrome-remote-interface`** — scriptable from Node/Python; best when you want to automate many breakpoints, collect state across runs, or debug non-interactively from an agent loop.
|
|
24
|
+
|
|
25
|
+
**Prefer `node inspect` first.** It's always available and the REPL is fast.
|
|
26
|
+
|
|
27
|
+
## When to Use
|
|
28
|
+
|
|
29
|
+
- A Node test fails and you need to see intermediate state
|
|
30
|
+
- ui-tui crashes or behaves wrong and you want to inspect React/Ink state pre-render
|
|
31
|
+
- tui_gateway child processes (`_SlashWorker`, PTY bridge workers) misbehave
|
|
32
|
+
- You need to inspect a value in a closure that `console.log` can't reach without patching
|
|
33
|
+
- Perf: attach to a running process to capture a CPU profile or heap snapshot
|
|
34
|
+
|
|
35
|
+
**Don't use for:** things `console.log` solves in under a minute. Breakpoint-driven debugging is heavier; use it when the payoff is real.
|
|
36
|
+
|
|
37
|
+
## Quick Reference: `node inspect` REPL
|
|
38
|
+
|
|
39
|
+
Launch paused on first line:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
node inspect path/to/script.js
|
|
43
|
+
# or with tsx
|
|
44
|
+
node --inspect-brk $(which tsx) path/to/script.ts
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The `debug>` prompt accepts:
|
|
48
|
+
|
|
49
|
+
| Command | Action |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `c` or `cont` | continue |
|
|
52
|
+
| `n` or `next` | step over |
|
|
53
|
+
| `s` or `step` | step into |
|
|
54
|
+
| `o` or `out` | step out |
|
|
55
|
+
| `pause` | pause running code |
|
|
56
|
+
| `sb('file.js', 42)` | set breakpoint at file.js line 42 |
|
|
57
|
+
| `sb(42)` | set breakpoint at line 42 of current file |
|
|
58
|
+
| `sb('functionName')` | break when function is called |
|
|
59
|
+
| `cb('file.js', 42)` | clear breakpoint |
|
|
60
|
+
| `breakpoints` | list all breakpoints |
|
|
61
|
+
| `bt` | backtrace (call stack) |
|
|
62
|
+
| `list(5)` | show 5 lines of source around current position |
|
|
63
|
+
| `watch('expr')` | evaluate expr on every pause |
|
|
64
|
+
| `watchers` | show watched expressions |
|
|
65
|
+
| `repl` | drop into REPL in current scope (Ctrl+C to exit REPL) |
|
|
66
|
+
| `exec expr` | evaluate expression once |
|
|
67
|
+
| `restart` | restart script |
|
|
68
|
+
| `kill` | kill the script |
|
|
69
|
+
| `.exit` | quit debugger |
|
|
70
|
+
|
|
71
|
+
**In the `repl` sub-mode:** type any JS expression, including access to locals/closure variables. `Ctrl+C` exits back to `debug>`.
|
|
72
|
+
|
|
73
|
+
## Attaching to a Running Process
|
|
74
|
+
|
|
75
|
+
When the process is already running (e.g. a long-lived dev server or the TUI gateway):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# 1. Send SIGUSR1 to enable the inspector on an existing process
|
|
79
|
+
kill -SIGUSR1 <pid>
|
|
80
|
+
# Node prints: Debugger listening on ws://127.0.0.1:9229/<uuid>
|
|
81
|
+
|
|
82
|
+
# 2. Attach the debugger CLI
|
|
83
|
+
node inspect -p <pid>
|
|
84
|
+
# or by URL
|
|
85
|
+
node inspect ws://127.0.0.1:9229/<uuid>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
To start a process with the inspector from the beginning:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
node --inspect script.js # listen on 127.0.0.1:9229, keep running
|
|
92
|
+
node --inspect-brk script.js # listen AND pause on first line
|
|
93
|
+
node --inspect=0.0.0.0:9230 script.js # custom host:port
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For TypeScript via tsx:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
node --inspect-brk --import tsx script.ts
|
|
100
|
+
# or older tsx
|
|
101
|
+
node --inspect-brk -r tsx/cjs script.ts
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Programmatic CDP (scripting from terminal)
|
|
105
|
+
|
|
106
|
+
When you want to automate — set many breakpoints, capture scope state, script a repro — use `chrome-remote-interface`:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm i -g chrome-remote-interface # or project-local
|
|
110
|
+
# Start your target:
|
|
111
|
+
node --inspect-brk=9229 target.js &
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Driver script (save as `/tmp/cdp-debug.js`):
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
const CDP = require('chrome-remote-interface');
|
|
118
|
+
|
|
119
|
+
(async () => {
|
|
120
|
+
const client = await CDP({ port: 9229 });
|
|
121
|
+
const { Debugger, Runtime } = client;
|
|
122
|
+
|
|
123
|
+
Debugger.paused(async ({ callFrames, reason }) => {
|
|
124
|
+
const top = callFrames[0];
|
|
125
|
+
console.log(`PAUSED: ${reason} @ ${top.url}:${top.location.lineNumber + 1}`);
|
|
126
|
+
|
|
127
|
+
// Walk scopes for locals
|
|
128
|
+
for (const scope of top.scopeChain) {
|
|
129
|
+
if (scope.type === 'local' || scope.type === 'closure') {
|
|
130
|
+
const { result } = await Runtime.getProperties({
|
|
131
|
+
objectId: scope.object.objectId,
|
|
132
|
+
ownProperties: true,
|
|
133
|
+
});
|
|
134
|
+
for (const p of result) {
|
|
135
|
+
console.log(` ${scope.type}.${p.name} =`, p.value?.value ?? p.value?.description);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Evaluate an expression in the paused frame
|
|
141
|
+
const { result } = await Debugger.evaluateOnCallFrame({
|
|
142
|
+
callFrameId: top.callFrameId,
|
|
143
|
+
expression: 'typeof state !== "undefined" ? JSON.stringify(state) : "n/a"',
|
|
144
|
+
});
|
|
145
|
+
console.log('state =', result.value ?? result.description);
|
|
146
|
+
|
|
147
|
+
await Debugger.resume();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await Runtime.enable();
|
|
151
|
+
await Debugger.enable();
|
|
152
|
+
|
|
153
|
+
// Set a breakpoint by URL regex + line
|
|
154
|
+
await Debugger.setBreakpointByUrl({
|
|
155
|
+
urlRegex: '.*app\\.tsx$',
|
|
156
|
+
lineNumber: 119, // 0-indexed
|
|
157
|
+
columnNumber: 0,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await Runtime.runIfWaitingForDebugger();
|
|
161
|
+
})();
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Run it:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
node /tmp/cdp-debug.js
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Hermes-specific note: `chrome-remote-interface` is NOT in `ui-tui/package.json`. Install it to a throwaway location if you don't want to dirty the project:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
mkdir -p /tmp/cdp-tools && cd /tmp/cdp-tools && npm i chrome-remote-interface
|
|
174
|
+
NODE_PATH=/tmp/cdp-tools/node_modules node /tmp/cdp-debug.js
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Debugging Hermes ui-tui
|
|
178
|
+
|
|
179
|
+
The TUI is built Ink + tsx. Two common scenarios:
|
|
180
|
+
|
|
181
|
+
### Debugging a single Ink component under dev
|
|
182
|
+
|
|
183
|
+
`ui-tui/package.json` has `npm run dev` (tsx --watch). Add `--inspect-brk` by running tsx directly:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
cd /home/bb/hermes-agent/ui-tui
|
|
187
|
+
npm run build # produce dist/ once so transpile isn't needed on first load
|
|
188
|
+
node --inspect-brk dist/entry.js
|
|
189
|
+
# In another terminal:
|
|
190
|
+
node inspect -p <node pid>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Then inside `debug>`:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
sb('dist/app.js', 220) # or wherever the suspect render is
|
|
197
|
+
cont
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
When it pauses, `repl` → inspect `props`, state refs, `useInput` handler values, etc.
|
|
201
|
+
|
|
202
|
+
### Debugging a running `hermes --tui`
|
|
203
|
+
|
|
204
|
+
The TUI spawns Node from the Python CLI. Easiest path:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# 1. Launch TUI
|
|
208
|
+
hermes --tui &
|
|
209
|
+
TUI_PID=$(pgrep -f 'ui-tui/dist/entry' | head -1)
|
|
210
|
+
|
|
211
|
+
# 2. Enable inspector on that Node PID
|
|
212
|
+
kill -SIGUSR1 "$TUI_PID"
|
|
213
|
+
|
|
214
|
+
# 3. Find the WS URL
|
|
215
|
+
curl -s http://127.0.0.1:9229/json/list | jq -r '.[0].webSocketDebuggerUrl'
|
|
216
|
+
|
|
217
|
+
# 4. Attach
|
|
218
|
+
node inspect ws://127.0.0.1:9229/<uuid>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Interacting with the TUI (typing in its window) continues to advance execution; your debugger can pause it on a breakpoint at any `sb(...)`.
|
|
222
|
+
|
|
223
|
+
### Debugging `_SlashWorker` / PTY child processes
|
|
224
|
+
|
|
225
|
+
Those are Python, not Node — use the `python-debugpy` skill for them. Only Node portions (Ink UI, tui_gateway client, tsx-run tests under `ui-tui/`) use this skill.
|
|
226
|
+
|
|
227
|
+
## Running Vitest Tests Under the Debugger
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
cd /home/bb/hermes-agent/ui-tui
|
|
231
|
+
# Run a single test file paused on entry
|
|
232
|
+
node --inspect-brk ./node_modules/vitest/vitest.mjs run --no-file-parallelism src/app/foo.test.tsx
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
In another terminal: `node inspect -p <pid>`, then `sb('src/app/foo.tsx', 42)`, `cont`.
|
|
236
|
+
|
|
237
|
+
Use `--no-file-parallelism` (vitest) or `--runInBand` (jest) so only one worker exists — debugging a pool is painful.
|
|
238
|
+
|
|
239
|
+
## Heap Snapshots & CPU Profiles (Non-interactive)
|
|
240
|
+
|
|
241
|
+
From the CDP driver above, swap Debugger for `HeapProfiler` / `Profiler`:
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
// CPU profile for 5 seconds
|
|
245
|
+
await client.Profiler.enable();
|
|
246
|
+
await client.Profiler.start();
|
|
247
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
248
|
+
const { profile } = await client.Profiler.stop();
|
|
249
|
+
require('fs').writeFileSync('/tmp/cpu.cpuprofile', JSON.stringify(profile));
|
|
250
|
+
// Open /tmp/cpu.cpuprofile in Chrome DevTools → Performance tab
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
// Heap snapshot
|
|
255
|
+
await client.HeapProfiler.enable();
|
|
256
|
+
const chunks = [];
|
|
257
|
+
client.HeapProfiler.addHeapSnapshotChunk(({ chunk }) => chunks.push(chunk));
|
|
258
|
+
await client.HeapProfiler.takeHeapSnapshot({ reportProgress: false });
|
|
259
|
+
require('fs').writeFileSync('/tmp/heap.heapsnapshot', chunks.join(''));
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Common Pitfalls
|
|
263
|
+
|
|
264
|
+
1. **Wrong line numbers in TS source.** Breakpoints hit the emitted JS, not the `.ts`. Either (a) break in the built `dist/*.js`, or (b) enable sourcemaps (`node --enable-source-maps`) and use `sb('src/app.tsx', N)` — but only with CDP clients that follow sourcemaps. `node inspect` CLI does not.
|
|
265
|
+
|
|
266
|
+
2. **`--inspect` vs `--inspect-brk`.** `--inspect` starts the inspector but doesn't pause; your script races past your first breakpoint if you attach too late. Use `--inspect-brk` when you need to set breakpoints before any code runs.
|
|
267
|
+
|
|
268
|
+
3. **Port collisions.** Default is `9229`. If multiple Node processes are inspecting, pass `--inspect=0` (random port) and read the actual URL from `/json/list`:
|
|
269
|
+
```bash
|
|
270
|
+
curl -s http://127.0.0.1:9229/json/list # lists all inspectable targets on the host
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
4. **Child processes.** `--inspect` on a parent does NOT inspect its children. Use `NODE_OPTIONS='--inspect-brk' node parent.js` to propagate to every child; be aware they all need unique ports (Node auto-increments when `NODE_OPTIONS='--inspect'` is inherited).
|
|
274
|
+
|
|
275
|
+
5. **Background kills.** If you `Ctrl+C` out of `node inspect` while the target is paused, the target stays paused. Either `cont` first, or `kill` the target explicitly.
|
|
276
|
+
|
|
277
|
+
6. **Running `node inspect` through an agent terminal.** It's a PTY-friendly REPL. In Hermes, launch it with `terminal(pty=true)` or `background=true` + `process(action='submit', data='...')`. Non-PTY foreground mode will work for one-shot commands but not for interactive stepping.
|
|
278
|
+
|
|
279
|
+
7. **Security.** `--inspect=0.0.0.0:9229` exposes arbitrary code execution. Always bind to `127.0.0.1` (the default) unless you have an isolated network.
|
|
280
|
+
|
|
281
|
+
## Verification Checklist
|
|
282
|
+
|
|
283
|
+
After setting up a debug session, verify:
|
|
284
|
+
|
|
285
|
+
- [ ] `curl -s http://127.0.0.1:9229/json/list` returns exactly the target you expect
|
|
286
|
+
- [ ] First breakpoint actually hits (if it doesn't, you likely missed `--inspect-brk` or attached after execution completed)
|
|
287
|
+
- [ ] Source listing at pause shows the right file (mismatch = sourcemap issue, see pitfall 1)
|
|
288
|
+
- [ ] `exec process.pid` in `repl` returns the PID you meant to attach to
|
|
289
|
+
|
|
290
|
+
## One-Shot Recipes
|
|
291
|
+
|
|
292
|
+
**"Why is this variable undefined at line X?"**
|
|
293
|
+
```bash
|
|
294
|
+
node --inspect-brk script.js &
|
|
295
|
+
node inspect -p $!
|
|
296
|
+
# debug>
|
|
297
|
+
sb('script.js', X)
|
|
298
|
+
cont
|
|
299
|
+
# paused. Now:
|
|
300
|
+
repl
|
|
301
|
+
> myVariable
|
|
302
|
+
> Object.keys(this)
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**"What's the call path into this function?"**
|
|
306
|
+
```
|
|
307
|
+
debug> sb('suspectFn')
|
|
308
|
+
debug> cont
|
|
309
|
+
# paused on entry
|
|
310
|
+
debug> bt
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**"This async chain hangs — where?"**
|
|
314
|
+
```
|
|
315
|
+
# Start with --inspect (no -brk), let it run to the hang, then:
|
|
316
|
+
debug> pause
|
|
317
|
+
debug> bt
|
|
318
|
+
# Now you see the stuck frame
|
|
319
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plan
|
|
3
|
+
description: "Plan mode: write markdown plan to .hermes/plans/, no exec."
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
author: Hermes Agent
|
|
6
|
+
license: MIT
|
|
7
|
+
platforms: [linux, macos, windows]
|
|
8
|
+
metadata:
|
|
9
|
+
hermes:
|
|
10
|
+
tags: [planning, plan-mode, implementation, workflow]
|
|
11
|
+
related_skills: [writing-plans, subagent-driven-development]
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Plan Mode
|
|
15
|
+
|
|
16
|
+
Use this skill when the user wants a plan instead of execution.
|
|
17
|
+
|
|
18
|
+
## Core behavior
|
|
19
|
+
|
|
20
|
+
For this turn, you are planning only.
|
|
21
|
+
|
|
22
|
+
- Do not implement code.
|
|
23
|
+
- Do not edit project files except the plan markdown file.
|
|
24
|
+
- Do not run mutating terminal commands, commit, push, or perform external actions.
|
|
25
|
+
- You may inspect the repo or other context with read-only commands/tools when needed.
|
|
26
|
+
- Your deliverable is a markdown plan saved inside the active workspace under `.hermes/plans/`.
|
|
27
|
+
|
|
28
|
+
## Output requirements
|
|
29
|
+
|
|
30
|
+
Write a markdown plan that is concrete and actionable.
|
|
31
|
+
|
|
32
|
+
Include, when relevant:
|
|
33
|
+
- Goal
|
|
34
|
+
- Current context / assumptions
|
|
35
|
+
- Proposed approach
|
|
36
|
+
- Step-by-step plan
|
|
37
|
+
- Files likely to change
|
|
38
|
+
- Tests / validation
|
|
39
|
+
- Risks, tradeoffs, and open questions
|
|
40
|
+
|
|
41
|
+
If the task is code-related, include exact file paths, likely test targets, and verification steps.
|
|
42
|
+
|
|
43
|
+
## Save location
|
|
44
|
+
|
|
45
|
+
Save the plan with `write_file` under:
|
|
46
|
+
- `.hermes/plans/YYYY-MM-DD_HHMMSS-<slug>.md`
|
|
47
|
+
|
|
48
|
+
Treat that as relative to the active working directory / backend workspace. Hermes file tools are backend-aware, so using this relative path keeps the plan with the workspace on local, docker, ssh, modal, and daytona backends.
|
|
49
|
+
|
|
50
|
+
If the runtime provides a specific target path, use that exact path.
|
|
51
|
+
If not, create a sensible timestamped filename yourself under `.hermes/plans/`.
|
|
52
|
+
|
|
53
|
+
## Interaction style
|
|
54
|
+
|
|
55
|
+
- If the request is clear enough, write the plan directly.
|
|
56
|
+
- If no explicit instruction accompanies `/plan`, infer the task from the current conversation context.
|
|
57
|
+
- If it is genuinely underspecified, ask a brief clarifying question instead of guessing.
|
|
58
|
+
- After saving the plan, reply briefly with what you planned and the saved path.
|