glimmervoid 0.35.1 → 0.37.0

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.
Files changed (50) hide show
  1. package/README.md +86 -82
  2. package/dist/bin/glimmervoid.js +58 -114
  3. package/dist/chunks/{adapters-CADejSpq.js → adapters-BmfVPxlH.js} +29 -61
  4. package/dist/chunks/adapters-CqFFmM2R.js +1 -0
  5. package/dist/chunks/{agent-api-cli-CRj_FYbz.js → agent-api-cli-BTHEyJKS.js} +1 -1
  6. package/dist/chunks/{agent-api-cli-vGwstJ1j.js → agent-api-cli-CBgOKXoO.js} +41 -12
  7. package/dist/chunks/{agent-api-core-D1233u1U.js → agent-api-core-DzKhakCv.js} +94 -3
  8. package/dist/chunks/{agent-setup-cli-BTAFcckd.js → agent-setup-cli-C8DGxlA-.js} +1 -1
  9. package/dist/chunks/{config-rPbbL4nW.js → config-BYc2z1Oc.js} +60 -779
  10. package/dist/chunks/config-store-B7MpiYxG.js +4181 -0
  11. package/dist/chunks/config-store-CsUoy88Q.js +1 -0
  12. package/dist/chunks/{contracts-DjIgIuDk.js → contracts-C6ipN_zw.js} +100 -12
  13. package/dist/chunks/git-version-6QFnKbWa.js +139 -0
  14. package/dist/chunks/{grok-KKgdSe-7.js → grok-CZ6kP_vD.js} +7 -3
  15. package/dist/chunks/guardrails-deps-Cb-ki4EU.js +70 -0
  16. package/dist/chunks/{sandbox-proxy-core-hnQgKR0T.js → guardrails-proxy-core-BI3rdY-m.js} +4 -4
  17. package/dist/chunks/{hook-command-core-DclEoE9e.js → hook-command-core-qTNK-xMF.js} +4 -1
  18. package/dist/chunks/{hook-tools-UX2fmt81.js → hook-tools-BkxllUHE.js} +9 -16
  19. package/dist/chunks/{knowledge-graph-cli-Rj5nj87L.js → knowledge-graph-cli-DApeMzA8.js} +1 -1
  20. package/dist/chunks/{loopback-post-DepQfdcM.js → loopback-post-DJ6iYWuL.js} +9 -9
  21. package/dist/chunks/{main-C-T2Uk20.js → main-BUfjroXE.js} +16282 -12595
  22. package/dist/chunks/{pair-cli-Dcyzv3In.js → pair-cli-GZ9t1mIO.js} +2 -2
  23. package/dist/chunks/{remote-config-Dd1T-VJA.js → remote-config-D2Is89Kj.js} +1 -1
  24. package/dist/chunks/{rtk-hook-core-BmEz2aq9.js → rtk-hook-core-VrUafGAp.js} +15 -5
  25. package/dist/chunks/rtk-resolver-B8BPgN9L.js +1 -0
  26. package/dist/chunks/{rtk-resolver-B9pzjLsM.js → rtk-resolver-C_micGrL.js} +1 -1
  27. package/dist/chunks/session-BZbariv5.js +1032 -0
  28. package/dist/chunks/{session-routes-BYy3am9m.js → session-routes-C4t8O8yg.js} +9 -9
  29. package/dist/chunks/settings-cli-DQOjmjev.js +484 -0
  30. package/dist/chunks/{ssh-sign-cli-DPHpT22B.js → ssh-sign-cli-LIBP8ppO.js} +2 -2
  31. package/dist/client/assets/index-B_Qt7fbZ.css +1 -0
  32. package/dist/client/assets/index-D-upBor0.js +265 -0
  33. package/dist/client/index.html +5 -3
  34. package/dist/scripts/postinstall-path-check.js +4 -4
  35. package/dist/server/index.js +1 -1
  36. package/dist/session/command-hook-relay.js +1 -1
  37. package/dist/session/{sandbox-open-launcher.js → guardrails-open-launcher.js} +10 -10
  38. package/dist/session/hook-relay.js +1 -1
  39. package/dist/session/hook-tool-relay.js +3 -3
  40. package/dist/session/statusline-relay.js +1 -1
  41. package/package.json +6 -5
  42. package/dist/chunks/adapters-B37wqNti.js +0 -1
  43. package/dist/chunks/config-store-BeIxAZNn.js +0 -1
  44. package/dist/chunks/config-store-BtsYbNC3.js +0 -1713
  45. package/dist/chunks/git-version-CUcDtF5k.js +0 -58
  46. package/dist/chunks/rtk-resolver-SWU5VVM7.js +0 -1
  47. package/dist/chunks/sandbox-deps-CWiH1d1w.js +0 -70
  48. package/dist/chunks/session-D6xXN0UU.js +0 -232
  49. package/dist/client/assets/index-CUxRHbCS.js +0 -254
  50. package/dist/client/assets/index-DE2GUmqs.css +0 -1
package/README.md CHANGED
@@ -1,42 +1,45 @@
1
1
  # Glimmervoid
2
2
 
3
3
  [![CI](https://github.com/johncwaters/glimmervoid/actions/workflows/test.yml/badge.svg)](https://github.com/johncwaters/glimmervoid/actions/workflows/test.yml)
4
+ [![npm](https://img.shields.io/npm/v/glimmervoid.svg)](https://www.npmjs.com/package/glimmervoid)
4
5
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
6
  [![Node.js](https://img.shields.io/badge/node-%3E%3D22.18-brightgreen.svg)](https://nodejs.org)
6
7
  [![Platform: Windows | Linux | macOS](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-0078d4)](https://github.com/johncwaters/glimmervoid)
7
8
 
8
9
  **Mission control for your coding agents. Run dozens of Claude Code, Codex and Grok sessions at once. See every one. Miss nothing.**
9
10
 
10
- Running more than a couple of coding agents at once turns into alt-tabbing between terminal windows, missing the exact moment one finishes or silently blocks on a prompt, and merging work you never watched happen. Glimmervoid is one browser dashboard with live terminal output for every session, exact status instead of a guess, a notification the moment an agent needs you, and per-agent git worktrees you can review and merge without leaving the page.
11
+ [glimmervoid.dev](https://glimmervoid.dev)
11
12
 
12
- Glimmervoid is an agent orchestrator, sometimes called an agent manager or an agentic development environment. It is not an agent harness: Claude Code, Codex and Grok are the harnesses, the loop that turns a model into an agent. Glimmervoid runs those harnesses unmodified, as real processes in real terminals on your own machine, and supervises all of them from one screen. No cloud service, no account.
13
+ Glimmervoid is one browser dashboard for all your coding agents: a live terminal for every session, exact status instead of a guess, a notification the moment an agent needs you, and a git worktree per agent that you review and merge from the same page.
13
14
 
14
- Glimmervoid is developed inside Glimmervoid.
15
+ It is an orchestrator, not a harness. Claude Code, Codex and Grok run unmodified, as real processes in real terminals on your own machine. No cloud service, no account. Glimmervoid is developed inside Glimmervoid.
15
16
 
16
17
  ![Glimmervoid dashboard replaying recorded Claude Code sessions: one fixes a flaky upload test with its diff in the terminal, three more work on their own projects in the session rail](assets/pictures/glimmervoid-demo.gif)
17
18
 
18
19
  ## Quickstart
19
20
 
20
- Windows and macOS:
21
+ **1. Start it.** Windows and macOS:
21
22
 
22
23
  ```bash
23
24
  npx glimmervoid@latest
24
25
  ```
25
26
 
26
- Linux (node-pty ships no Linux prebuilds, so it compiles at install time, and npm runs that build only when allowed):
27
+ Linux, where node-pty compiles at install time:
27
28
 
28
29
  ```bash
29
30
  sudo apt install build-essential python3 git
30
31
  npx --allow-scripts=node-pty glimmervoid@latest
31
32
  ```
32
33
 
33
- To install it globally instead, run `npm install -g glimmervoid` (add `--allow-scripts=node-pty` on Linux); if that fails with `EACCES`, see [docs/troubleshooting.md](docs/troubleshooting.md#eacces-on-a-global-install) and never use `sudo npm`.
34
+ **2. Open http://localhost:3000.** The first start from a terminal opens it in your browser for you; pass `--no-open` to skip that.
34
35
 
35
- Open http://localhost:3000. A fresh install knows no projects, so **+ Session** shows "No projects found" until you say where your repositories live: open **Settings**, then **Repositories**, and add the folder that holds your git checkouts under **Repository roots** (each subfolder becomes a project). For a one-off, **+ Session** then **Advanced options** takes a name and a path directly. That is the whole setup; everything else is optional.
36
+ **3. Follow the checklist.** The empty dashboard walks you through three steps: an agent CLI it can find, the folder that holds your git checkouts, and your first session. It suggests folders such as `~/Projects` that already hold git repos, or you can type a path. Each subfolder becomes a project. For a folder outside your repository roots, open **Run in a folder outside your repository roots** in the **+ Session** dialog.
36
37
 
37
- To check notifications, allow them when the dashboard asks (or turn on **Desktop notifications** under **Settings**, **Appearance and alerts**) and keep a dashboard tab open. A notification only appears while that tab is not focused, since a focused tab already shows the change. With no dashboard tab open anywhere, only Telegram reaches you (the `telegram` keys in [docs/configuration.md](docs/configuration.md)).
38
+ That is the whole setup. Allow notifications when the dashboard asks, and keep a dashboard tab open: a notification appears whenever that tab is in the background, unless Streamer mode is on.
38
39
 
39
- The Linux flag matters on npm 12, which blocks dependency install scripts by default; npm 10 and 11 run them (npm 11 prints a notice), so there it is harmless. Keep `@latest` so npx checks for a new release instead of rerunning the version it cached first. If the native module still fails to load, the server refuses to start and prints the repair command; `glimmervoid doctor` runs the same check. See [docs/troubleshooting.md](docs/troubleshooting.md).
40
+ Keep `@latest` so npx picks up new releases. To install globally instead, run `npm install -g glimmervoid` (add `--allow-scripts=node-pty` on Linux), then `glimmervoid`. Never use `sudo npm`; see [EACCES on a global install](docs/troubleshooting.md#eacces-on-a-global-install).
41
+
42
+ If something fails, run `glimmervoid doctor` and see [troubleshooting](docs/troubleshooting.md).
40
43
 
41
44
  ### From source
42
45
 
@@ -48,92 +51,96 @@ npm run build
48
51
  npm start
49
52
  ```
50
53
 
51
- Update a clone with `git pull --ff-only && npm ci && npm run build`, then restart the server. The dashboard's update check prints the right command for whichever way you installed.
54
+ To update, run `git pull --ff-only && npm ci && npm run build` and restart. The dashboard's update banner shows the right command for however you installed.
52
55
 
53
56
  ## Requirements
54
57
 
55
- - **Node.js >= 22.18.0** (the `engines` floor). npm 12 itself needs Node 22.22.2 or newer; distro-packaged Node is usually older than either, so use nodesource, nvm or the official installer.
58
+ - **Node.js 22.18 or newer.** Distro-packaged Node is usually older; use nvm, NodeSource or the official installer.
56
59
  - **Windows 11, Linux or macOS.** CI runs on Linux; Windows and macOS are tested by hand.
57
- - **Claude Code CLI** on PATH, or another supported agent below.
60
+ - **An agent CLI** on PATH: Claude Code, Codex, Grok or another terminal agent.
58
61
  - **git** on PATH, for per-session worktrees.
59
- - **Linux only:** `build-essential` and `python3` for node-pty.
60
- - **Linux only:** `bubblewrap` (`bwrap`), `socat` and `ripgrep` (`rg`) for the sandbox runtime behind session guardrails and factory checks (`sudo apt install bubblewrap socat ripgrep`). `glimmervoid doctor` reports all three.
62
+ - **Linux only:** `build-essential` and `python3` to compile node-pty, plus `bubblewrap`, `socat` and `ripgrep` for session guardrails (`sudo apt install bubblewrap socat ripgrep`).
63
+
64
+ `glimmervoid doctor` checks all of these.
61
65
 
62
66
  ## Supported agents
63
67
 
64
- - **Claude Code** (`claude`), the default and the most complete: hooks, background agent gating, auto-resume.
65
- - **Codex CLI** (`codex`), with hook-based status. Codex has no notification event, so a question it asks in prose looks like a finished turn.
66
- - **Grok Build** (`grok`). Run `glimmervoid agent setup grok` once to install its hook relay.
67
- - **Any other terminal agent**, declared under `customAgents` in `config.json` with an `id`, `label`, `command` and optional `args`, `idleTitle` and `busyTitle`. Custom agents get status from the terminal title only.
68
+ | Agent | Command | Status from |
69
+ |-------|---------|-------------|
70
+ | Claude Code (default, most complete) | `claude` | Hooks, with background agent gating and auto-resume |
71
+ | Codex CLI | `codex` | Hooks. Codex has no notification event, so a question asked in prose looks like a finished turn |
72
+ | Grok Build | `grok` | Hooks, after running `glimmervoid agent setup grok` once |
73
+ | Any other terminal agent | your own | Terminal title only |
68
74
 
69
- The Add Session dialog only offers agents whose command resolves on PATH, and `glimmervoid doctor` lists what it found. A project can default to an agent with `projects[].agent`.
75
+ Declare another agent under `customAgents` in `config.json` with an `id`, `label` and `command`, plus optional `args`, `idleTitle` and `busyTitle`. The **+ Session** dialog offers only the agents it finds on PATH, and a project can default to one with `projects[].agent`.
70
76
 
71
- ## Usage
77
+ ## Features
78
+
79
+ - **Focus view.** A rail of every session on the left, the selected session's live terminal in the middle, its changes on the right.
80
+ - **A worktree per agent.** Every session in a git repo gets its own worktree, so agents never step on your checkout or each other. Review the diff and merge with one click while the agent keeps working. A conflicted merge parks, and **Resolve in session** hands it back to the agent that wrote it.
81
+ - **Exact status.** Hooks report every state change and the terminal title fills the gaps. The screen is never scraped ([how](#how-status-detection-works)).
82
+ - **Notifications.** Browser notifications when a session needs input, finishes or fails, with your own alert sounds if you like (drop `.ogg`, `.mp3`, `.wav`, `.m4a` or `.webm` files into `~/.glimmervoid/sounds/`). Telegram covers you when no dashboard is open.
83
+ - **Phone layout.** Board, terminal and review screens, attention first, with a terminal that resizes around the keyboard.
84
+ - **Plan review.** Read, edit and approve Claude Code plans from the dashboard or your phone.
85
+ - **Guardrails.** Guarded Claude Code sessions on macOS and Linux (on unless a project or folder opts out) cannot touch credentials or Glimmervoid's own state, and Sane YOLO, once its hooks are installed and trusted, refuses commands like `git reset --hard` and `git push --force` in sessions that skip permission prompts.
86
+ - **Usage.** Tokens and estimated cost from local Claude Code, Codex and Grok transcripts, with optional budgets.
87
+ - **Auto-resume.** Sessions live when Glimmervoid stopped come back with their conversation resumed.
88
+ - **Remote access** (opt-in). Reach the dashboard from another device through single-use pairing links.
89
+ - **Radar** (opt-in). Watches PostHog error tracking, pings Telegram on a spike, regression or new issue, and sends an agent to investigate. Optional auto-fix opens a pull request the agent can never push or merge itself.
90
+ - **Team PR review** (opt-in). Drafts a review of each open pull request from a GitHub team in a guarded agent, optionally with your own review skill. Nothing posts until you choose Approve or Comment.
91
+
92
+ ### Keyboard
93
+
94
+ Cmd on a Mac, Alt elsewhere:
95
+
96
+ | Keys | Action |
97
+ |------|--------|
98
+ | `Cmd/Alt+J` | Next session that needs you |
99
+ | `Cmd/Alt+1` to `9` | Jump to a session |
100
+ | `Cmd/Alt+Up` / `Down` | Move through the rail |
101
+ | `Cmd/Alt+I` | Merge the selected session |
102
+ | `Cmd/Alt+U` | Resolve a parked merge, or resync the base branch |
103
+ | `Cmd/Alt+0` | Add a session |
104
+
105
+ ## Commands
72
106
 
73
107
  ```
74
- glimmervoid # start the server on port 3000
75
- glimmervoid --port 3001 # start on another port
76
- glimmervoid doctor # diagnose the install, PATH, agents and native module
77
- glimmervoid pair --name phone # mint a single-use pairing link for a remote device
78
- glimmervoid --version
108
+ glimmervoid # start the server on port 3000
109
+ glimmervoid --port 3001 # start on another port
110
+ glimmervoid doctor # check the install, PATH, agents and native module
111
+ glimmervoid settings list # read and change settings from the terminal
112
+ glimmervoid pair --name phone # pair a remote device
79
113
  ```
80
114
 
81
115
  `glimmervoid --help` lists every command.
82
116
 
83
- ## Features
84
-
85
- - Focus view: a roster rail of every session, the selected session's live terminal in the center, and a worktree review sidebar
86
- - Per-session git worktree isolation: review and merge each agent's committed work from the dashboard while it keeps running
87
- - Real-time terminal output via xterm.js with WebGL rendering
88
- - Structural status detection: hooks as the authoritative signal, an OSC-0 title fallback, never screen scraping (see below)
89
- - Background sub-agent completion gate: a session with live background agents or tasks stays out of Complete until they finish
90
- - Browser notifications when a session needs input, finishes or fails
91
- - Custom alert sounds: drop your own `.ogg`, `.mp3`, `.wav`, `.m4a` or `.webm` files into `~/.glimmervoid/sounds/` (or `$GLIMMERVOID_HOME/sounds/`) and pick one in Settings
92
- - Phone layout as a first-class second layout: board, terminal and review screens, attention-first ordering, and a terminal that resizes around the soft keyboard instead of hiding behind it
93
- - Remote access (opt-in): a separate listener with single-use device pairing and cookie auth
94
- - Telegram notifications (opt-in), sent only when no dashboard tab is open anywhere
95
- - Plan review: read, edit and approve a Claude Code plan from the dashboard or phone
96
- - Usage tracking: token use and estimated cost from local Claude Code, Codex and Grok transcripts, with optional budgets
97
- - Radar (opt-in): polls PostHog error tracking, pings Telegram when an issue spikes, regresses or first appears, and sends an agent to investigate; optional auto-fix opens a pull request the agent itself can never push or merge
98
- - Team PR review (opt-in): drafts a review of each open pull request from a GitHub team in a sandboxed agent, optionally with your own Claude Code review skill (`teamReview.skill`); nothing posts until you choose Approve or Comment
99
- - Auto-resume: sessions that were live when Glimmervoid stopped come back with their conversation resumed
100
- - Hot-reloaded configuration and a Settings view for most of it
101
-
102
- ## Why the status detection is hard (and how Glimmervoid does it)
103
-
104
- The obvious way to know if a Claude Code session finished, is waiting on you, or is still working is to scrape the terminal: watch for a prompt string, a spinner glyph, some text pattern. It breaks constantly. Every TUI redraw, every theme change, every Claude Code release that adjusts spacing invalidates the scrape. Glimmervoid never does this.
105
-
106
- Instead, at spawn Glimmervoid injects Claude Code hooks scoped to that one session, no changes to the target repo, that POST to a local HTTP endpoint on every lifecycle event: prompt submitted, turn stopped, notification raised, sub-agent started or finished. These hooks are the authoritative signal. An OSC-0 terminal title fallback (spinner glyph = working, idle glyph = ready) covers the gap for anything that predates or bypasses the hooks. The two are merged with explicit precedence (hook beats title) and a short conflict window so a racing signal can still win before the UI settles.
107
-
108
- That design didn't arrive whole. Three incidents shaped it:
109
-
110
- `/clear` and `/compact` fire no `UserPromptSubmit` and no `Stop`, but the terminal redraw briefly flashes a spinner then an idle glyph in the title. Early on, that flash looked exactly like a finished work cycle, so Glimmervoid fired a "session complete" notification on every `/clear`. (The bug report was, more or less, "why did my terminal congratulate me for clearing the screen.") The fix: on a detected clear/compact, reset both signal sources and mute title-only signals until the next real prompt.
111
-
112
- A background sub-agent (launched via `Task` with `run_in_background`, or Ctrl+B) can still be running when the main agent's own turn ends and its `Stop` hook fires. Treating that `Stop` as completion closed the card while real work was still happening in the background. The fix is a completion gate: Glimmervoid counts live sub-agents from `SubagentStart`/`SubagentStop` and reconciles that count against `background_tasks`, a field Claude Code's own hook payloads declare independently. Staleness between the two is resolved by a sequence number, not a timestamp, because concurrent signals routinely land in the same millisecond.
117
+ ## Configuration
113
118
 
114
- Boot auto-resume (reattaching a session's Claude conversation after Glimmervoid restarts) depends on capturing Claude's session id from a hook. It was wired to capture that id from `SessionStart`, which seemed reasonable until testing showed Claude Code doesn't reliably fire `SessionStart` on interactive startup at all, silently dead in production, no error, resume just never happened. The fix: capture the session id from whichever main-agent hook arrives first, since they all carry it.
119
+ Glimmervoid creates `~/.glimmervoid/config.json` on first run. Most settings live in the dashboard's **Settings** view; the rest you can change with `glimmervoid settings` or by editing the file, which the server reloads on save. Every key, default and environment variable is in the [configuration reference](docs/configuration.md), generated from the code.
115
120
 
116
- Every session also writes a JSONL forensic recording by default (hook payloads and state transitions, not raw terminal bytes), and a version-aware replay harness drives recorded traffic back through the detection code as regression fixtures. That's how bugs like the ones above get diagnosed from real session data instead of guesswork, and how they stay caught if the logic regresses.
121
+ Worktrees branch from each repo's default branch (`origin/HEAD`, then `main`, then `master`) unless you set `integrationBranch`.
117
122
 
118
- ## Focus
123
+ Glimmervoid sends anonymous usage and error data by default. [Telemetry](docs/configuration.md#telemetry) lists exactly what is sent and how to turn it off.
119
124
 
120
- Glimmervoid centers on one session at a time. A left **roster rail** lists one pill per session (grouped by project, with a live working heartbeat and a "needs you" queue); the **center** borrows that session's live terminal as the work surface; a right **review sidebar** shows its changes.
125
+ ## How status detection works
121
126
 
122
- Every git-repo session runs in its own git worktree forked from the integration branch, so an agent's edits stay out of your main checkout until you review them. `integrationBranch` is unset by default, which means each repo's own default branch: `origin/HEAD`, then `main`, then `master`. The sidebar splits **Committed** (the mergeable unit) from **Uncommitted** work, keeps the diff live, and merges into the integration branch with one click while the session keeps running. If a merge hits conflicts it parks, and **Resolve in session** hands the conflict back to the agent that owns the worktree with a ready-to-run prompt.
127
+ The obvious way to tell whether an agent is working, waiting or done is to scrape the terminal for a prompt or a spinner. That breaks on every redraw, theme change and CLI release, so Glimmervoid never does it.
123
128
 
124
- Navigate it from the keyboard with Cmd on a Mac and Alt elsewhere, never more than two keys: `Cmd/Alt+J` steps through the sessions needing attention, `Cmd/Alt+1`..`9` jump to a session, `Cmd/Alt+Up`/`Down` move through the rail, `Cmd/Alt+I` merges the selected one, `Cmd/Alt+U` resolves or resyncs it, and `Cmd/Alt+0` opens Add Session.
129
+ Instead, each session gets Claude Code hooks scoped to that one session, with no change to your repo. They POST every lifecycle event to a local endpoint: prompt submitted, turn stopped, notification raised, sub-agent started or finished. Hooks are the authoritative signal. The terminal title (spinner glyph means working, idle glyph means ready) covers anything the hooks miss, and a hook wins any conflict.
125
130
 
126
- ## Configuration
131
+ Three bugs shaped the details:
127
132
 
128
- On first run Glimmervoid creates `~/.glimmervoid/config.json`. Most settings are edited from the dashboard's Settings view, and the server reloads the file when you edit it by hand. Every key, its default and every environment variable are in [docs/configuration.md](docs/configuration.md), which is generated from the code.
133
+ - **`/clear` looked like a finished turn.** It fires no hooks but flashes a spinner, then an idle glyph, in the title, so early builds sent "session complete" on every clear. Now a clear or compact resets both signals and mutes the title until the next real prompt.
134
+ - **A background sub-agent outlived its parent's turn.** The main agent's `Stop` fired while a sub-agent was still working. Now a session stays out of Complete until the live sub-agent count, reconciled against Claude Code's own `background_tasks` field, reaches zero.
135
+ - **Auto-resume silently never ran.** It waited for `SessionStart`, which Claude Code does not reliably fire on interactive startup. Now the conversation id comes from whichever hook arrives first.
129
136
 
130
- Glimmervoid sends anonymous usage and error data by default; what it sends and how to turn it off are in [Telemetry](docs/configuration.md#telemetry).
137
+ Every session writes a JSONL recording of hook payloads and state transitions (never terminal bytes), and a replay harness runs recorded sessions back through the detection code as regression tests.
131
138
 
132
139
  ## Remote access
133
140
 
134
- Remote access is off by default. It is a second loopback listener meant to sit behind an HTTPS reverse proxy, never a wider bind, and it is created only at startup:
141
+ Off by default. Remote access is a second listener on `127.0.0.1` that you put behind an HTTPS reverse proxy; Glimmervoid never binds a wider address unless `GLIMMERVOID_INSECURE_BIND=1` is set.
135
142
 
136
- 1. Add a `remote` block to `~/.glimmervoid/config.json`. `enabled` defaults to `false`; `port` has no default and is required once enabled (it must differ from the local port); `publicHost` defaults to empty and is the hostname your proxy serves, used for shareable pairing links and the allowed origin.
143
+ 1. Add a `remote` block to `~/.glimmervoid/config.json`. `port` must differ from the dashboard port, and `publicHost` is the hostname your proxy serves:
137
144
 
138
145
  ```json
139
146
  {
@@ -141,16 +148,14 @@ Remote access is off by default. It is a second loopback listener meant to sit b
141
148
  }
142
149
  ```
143
150
 
144
- 2. Restart Glimmervoid (stop and start it, or `systemctl --user restart glimmervoid` when it runs as a service). The log line `Glimmervoid remote listener on http://127.0.0.1:3456 (paired devices only)` confirms the listener is up.
145
- 3. Point an HTTPS reverse proxy at that port. With Tailscale, `tailscale serve --bg 3456` serves `https://<machine>.<tailnet>.ts.net`, which is the name `publicHost` should hold. When the port is reached solely through HTTP-mode `tailscale serve`, also set `"ownerLogin": "you@example.com"` in the `remote` block: the listener then refuses every request and WebSocket whose `Tailscale-User-Login` is not that login, including Funnel and tagged-device traffic, which carry none. Only HTTP-mode `tailscale serve` strips a client-sent `Tailscale-User-Login`; behind any other proxy, or `tailscale serve --tcp` or `--tls-terminated-tcp`, a client can forge it, so `ownerLogin` proves nothing there. It works on top of pairing, never instead of it.
146
- 4. Verify from the device: `https://<publicHost>/` should answer with Glimmervoid's "Pairing required" page.
147
- 5. On the host, run `glimmervoid pair --name phone`. It prints a single-use URL valid for 10 minutes that sets an auth cookie when opened on the device.
148
-
149
- `glimmervoid pair --list` and `glimmervoid pair --revoke <id>` manage devices, and a revoke applies without a restart. A pairing grants full control of the machine as the server account, so treat pairing URLs as passwords.
151
+ 2. Restart Glimmervoid. The log shows `Glimmervoid remote listener on http://127.0.0.1:3456 (paired devices only)`.
152
+ 3. Point an HTTPS reverse proxy at that port. With Tailscale, `tailscale serve --bg 3456` serves `https://<machine>.<tailnet>.ts.net`, which is what `publicHost` should hold.
153
+ 4. From the device, open `https://<publicHost>/`. You should see Glimmervoid's "Pairing required" page.
154
+ 5. On the host, run `glimmervoid pair --name phone` and open the printed link on the device. It is single-use and expires in 10 minutes.
150
155
 
151
- ## Security
156
+ `glimmervoid pair --list` shows paired devices, and `glimmervoid pair --revoke <id>` removes one without a restart.
152
157
 
153
- Glimmervoid runs agents with your account's privileges and trusts every local process. Read [SECURITY.md](SECURITY.md) for the trust model and how to report a vulnerability, and [Limitations](#limitations) for the short version.
158
+ A paired device has full control of the machine as your account, so treat pairing links like passwords. Behind HTTP-mode `tailscale serve`, you can also set `"ownerLogin": "you@example.com"` in the `remote` block so only that Tailscale login gets through, on top of pairing. Only HTTP-mode `tailscale serve` strips a forged `Tailscale-User-Login` header; behind any other proxy, or `tailscale serve --tcp` or `--tls-terminated-tcp`, `ownerLogin` proves nothing.
154
159
 
155
160
  ## Running as a service (Linux)
156
161
 
@@ -176,20 +181,19 @@ systemctl --user enable --now glimmervoid
176
181
  loginctl enable-linger "$USER"
177
182
  ```
178
183
 
179
- `secrets.env` is optional and can hold `GLIMMERVOID_POSTHOG_API_KEY` and `GLIMMERVOID_TELEGRAM_BOT_TOKEN`, which keeps both out of `config.json`. The unit's `PATH` must reach `node`, `glimmervoid` and your agent CLIs; with nvm, set `Environment=PATH=...` or point `ExecStart` at absolute paths. For a source checkout, use `ExecStart=/usr/bin/env node dist/server/index.js` with `WorkingDirectory=` set to the checkout.
184
+ The unit's `PATH` must reach `node`, `glimmervoid` and your agent CLIs. With nvm, set `Environment=PATH=...` or use absolute paths in `ExecStart`. For a source checkout, use `ExecStart=/usr/bin/env node dist/server/index.js` with `WorkingDirectory=` set to the checkout.
180
185
 
181
- ## Development
186
+ `secrets.env` is optional. Put `GLIMMERVOID_POSTHOG_API_KEY` and `GLIMMERVOID_TELEGRAM_BOT_TOKEN` there to keep them out of `config.json`.
182
187
 
183
- See [CONTRIBUTING.md](CONTRIBUTING.md).
188
+ ## Security
184
189
 
185
- ## Limitations
190
+ Glimmervoid runs agents with your account's privileges. The local dashboard has no login and binds only `127.0.0.1` unless `GLIMMERVOID_INSECURE_BIND=1` is set, so any local process can drive it and the port must never be exposed to the network. Use [remote access](#remote-access) to reach it from elsewhere.
186
191
 
187
- - **Local-first, with an opt-in remote door.** By default Glimmervoid binds `127.0.0.1` and has no login on the local listener, so any local process can drive it. That is a deliberate single-user choice, and it means the local port must never be exposed to the network. Remote access is a separate listener gated by single-use pairing tokens and cookies, meant to sit behind a reverse proxy; a pairing cookie grants full code execution as the server account.
188
- - **Requires an agent CLI.** Glimmervoid spawns and supervises Claude Code, Codex, Grok or another terminal agent; it does not replace them.
192
+ [SECURITY.md](SECURITY.md) has the full trust model and how to report a vulnerability privately.
189
193
 
190
- ## Changelog
194
+ ## Contributing
191
195
 
192
- See [CHANGELOG.md](CHANGELOG.md) for release history.
196
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Release history is in [CHANGELOG.md](CHANGELOG.md).
193
197
 
194
198
  ## License
195
199
 
@@ -2,123 +2,59 @@
2
2
  import { o as nodePtyRebuildHint, r as firstLine, t as probeNodePty } from "../chunks/node-pty-preflight-C8QIlrtU.js";
3
3
  import { i as errorText } from "../chunks/text-DoirglWV.js";
4
4
  import { a as packageRoot } from "../chunks/runtime-paths-BkeftE60.js";
5
- import { t as AGENT_API_VERBS } from "../chunks/session-D6xXN0UU.js";
5
+ import { t as AGENT_API_VERBS } from "../chunks/session-BZbariv5.js";
6
6
  import { i as execSync } from "../chunks/child-process-safe-CbQnyh7D.js";
7
7
  import { t as renderTable } from "../chunks/ascii-figure-core-pDAwtlKO.js";
8
8
  import { n as glimmervoidHomeDir, t as decideConfigPath } from "../chunks/config-path-core-DFt-TemS.js";
9
- import { i as sandboxDoctorRows, r as probeSandboxDependencies } from "../chunks/sandbox-deps-CWiH1d1w.js";
10
- import { r as gitDoctorRows, t as probeGitVersion } from "../chunks/git-version-CUcDtF5k.js";
9
+ import { i as parseCommandLine, r as gitDoctorRows, t as probeGitVersion } from "../chunks/git-version-6QFnKbWa.js";
10
+ import { i as guardrailsDoctorRows, r as probeGuardrailsDependencies } from "../chunks/guardrails-deps-Cb-ki4EU.js";
11
11
  import { i as pnpmGlobalBinDir, n as npmGlobalBinDir, r as onPath, t as formatPathNotice } from "../chunks/path-doctor-C23VAqeu.js";
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
14
  import os from "node:os";
15
- //#region server/core/command-line-core.ts
16
- var SUBCOMMANDS_HONORING_TRAILING_GLOBAL_OPTIONS = /* @__PURE__ */ new Set([
17
- "doctor",
18
- "pair",
19
- "visions"
20
- ]);
21
- var SUBCOMMANDS_HONORING_A_LEADING_HELP_OR_VERSION = new Set(AGENT_API_VERBS);
22
- var OPTIONS_TAKING_A_VALUE = {
23
- "--config": "configPath",
24
- "--port": "port"
25
- };
26
- function scanGlobalOption(argv, index, state) {
27
- const token = argv[index];
28
- const valueKey = OPTIONS_TAKING_A_VALUE[token];
29
- if (valueKey) {
30
- const value = argv[index + 1];
31
- if (value !== void 0) state[valueKey] = value;
32
- return index + 2;
33
- }
34
- if (token === "--help" || token === "-h") state.wantsHelp = true;
35
- if (token === "--version") state.wantsVersion = true;
36
- if (token === "--doctor") state.wantsDoctor = true;
37
- return index + 1;
38
- }
39
- function scanTrailingGlobalOptions(subcommandArgs, state) {
40
- let index = 0;
41
- while (index < subcommandArgs.length) {
42
- if (!subcommandArgs[index].startsWith("-")) {
43
- index += 1;
44
- continue;
45
- }
46
- index = scanGlobalOption(subcommandArgs, index, state);
47
- }
48
- }
49
- function scanLeadingHelpOrVersion(subcommandArgs, state) {
50
- const firstArgument = subcommandArgs[0];
51
- if (firstArgument === "--help" || firstArgument === "-h") state.wantsHelp = true;
52
- if (firstArgument === "--version") state.wantsVersion = true;
53
- }
54
- function parseCommandLine(argv) {
55
- const state = {
56
- configPath: null,
57
- port: null,
58
- wantsHelp: false,
59
- wantsVersion: false,
60
- wantsDoctor: false
61
- };
62
- let index = 0;
63
- while (index < argv.length && argv[index].startsWith("-")) index = scanGlobalOption(argv, index, state);
64
- const name = argv[index];
65
- const subcommandArgs = argv.slice(index + 1);
66
- if (name !== void 0 && SUBCOMMANDS_HONORING_TRAILING_GLOBAL_OPTIONS.has(name)) scanTrailingGlobalOptions(subcommandArgs, state);
67
- if (name !== void 0 && SUBCOMMANDS_HONORING_A_LEADING_HELP_OR_VERSION.has(name)) scanLeadingHelpOrVersion(subcommandArgs, state);
68
- if (state.wantsHelp) return { kind: "help" };
69
- if (state.wantsVersion) return { kind: "version" };
70
- const globalOptions = {
71
- configPath: state.configPath,
72
- port: state.port
73
- };
74
- if (state.wantsDoctor) return {
75
- kind: "subcommand",
76
- name: "doctor",
77
- subcommandArgs: [],
78
- globalOptions
79
- };
80
- if (name === void 0) return {
81
- kind: "server",
82
- globalOptions
83
- };
84
- return {
85
- kind: "subcommand",
86
- name,
87
- subcommandArgs,
88
- globalOptions
89
- };
90
- }
91
- //#endregion
92
15
  //#region bin/glimmervoid.ts
93
16
  var args = process.argv.slice(2);
94
17
  var pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
95
18
  var USAGE = `Usage: glimmervoid [command] [options]
96
19
 
20
+ With no command, starts the server.
21
+
97
22
  Commands:
98
- doctor Diagnose install / PATH issues and exit
99
- agent setup grok Install Glimmervoid's env-inert Grok hook relay
100
- pair Mint a single-use pairing link for a remote device
101
- pair --list List paired devices
23
+ doctor Check the install, PATH, agent CLIs and native module, then exit
24
+ settings <cmd> Read and change config.json: list, get, set, unset, search, schema (each takes --json)
25
+ pair Mint a single-use pairing link for a remote device
26
+ pair --list List paired devices
102
27
  pair --revoke <id> Revoke a paired device
103
- visions relay Run the Visions LSP relay on stdio (what an editor's LSP client spawns)
104
- visions install Install the Visions extension into every VS Code family editor on PATH
105
- visions setup Print LSP client config for Neovim, Helix, Emacs, Kate, Sublime, JetBrains
106
- visions status Report the relay path and which editors carry the extension
107
- spawn <prompt> From inside a Glimmervoid session, start a sibling session on that prompt
108
- attention <note> From inside a Glimmervoid session, flag it as needing the operator
109
- board From inside a Glimmervoid session, list the live sessions
110
- push From inside a Glimmervoid session, land its commits on the integration branch and push it
111
- edit <path> From inside a Glimmervoid session, surface a text file for the operator to edit in the dashboard
112
- limits From inside a Glimmervoid session, list which of these commands answer and what its guardrails block
113
- ssh-sign What git runs inside a Glimmervoid session to sign a commit with the operator's forwarded ssh agent
114
- kg [command] Experimental typed knowledge and task graph (Settings > Lanes > Knowledge graph)
28
+ agent setup grok Install the Grok hook relay, which does nothing outside Glimmervoid
29
+
30
+ Editor integration (Visions):
31
+ visions install Install the Visions extension into every VS Code family editor on PATH
32
+ visions uninstall Remove the Visions extension and the editor config it wrote
33
+ visions setup Print LSP client config for Neovim, Helix, Emacs, Kate, Sublime, JetBrains
34
+ visions status Show the relay path and which editors have the extension
35
+ visions relay Run the Visions LSP relay on stdio (an editor's LSP client starts this)
36
+
37
+ Inside a Glimmervoid session:
38
+ spawn <prompt> Start a sibling session on that prompt
39
+ attention <note> Flag this session as needing the operator
40
+ board List the live sessions
41
+ push Land this session's commits on the integration branch and push it
42
+ edit <path> Open a text file for the operator to edit in the dashboard
43
+ limits List which of these commands answer here and what the guardrails block
44
+ factory status Print each factory intent's tickets, state, last reason and next action
45
+ factory retry <id> Reopen the parked tickets of a factory intent
46
+ ssh-sign Sign a commit with the operator's forwarded ssh agent (git runs this)
47
+
48
+ Experimental:
49
+ kg [command] Typed knowledge and task graph (Settings > Lanes > Knowledge graph)
115
50
 
116
51
  Options:
117
- --name <label> Label for the device being paired (with: pair)
118
- --port <number> Override the server port (default: 3000)
119
- --config <path> Path to config file (default: ~/.glimmervoid/config.json)
120
- --version Show version number
121
- --help, -h Show this help message`;
52
+ --port <number> Server port (default: 3000)
53
+ --config <path> Config file (default: ~/.glimmervoid/config.json)
54
+ --name <label> Name for the device being paired (with pair)
55
+ --no-open Do not open the dashboard in a browser on first start
56
+ --version Print the version
57
+ --help, -h Print this help`;
122
58
  var commandLine = parseCommandLine(args);
123
59
  if (commandLine.kind === "help") {
124
60
  console.log(USAGE);
@@ -138,33 +74,41 @@ var SUBCOMMAND_RUNNERS = new Map([
138
74
  return 0;
139
75
  }],
140
76
  ["pair", async (subcommandArgs) => {
141
- const { runPairCli } = await import("../chunks/pair-cli-Dcyzv3In.js");
77
+ const { runPairCli } = await import("../chunks/pair-cli-GZ9t1mIO.js");
142
78
  return runPairCli(subcommandArgs);
143
79
  }],
144
80
  ["agent", async (subcommandArgs) => {
145
- const { runAgentSetupCli } = await import("../chunks/agent-setup-cli-BTAFcckd.js");
81
+ const { runAgentSetupCli } = await import("../chunks/agent-setup-cli-C8DGxlA-.js");
146
82
  return runAgentSetupCli(subcommandArgs);
147
83
  }],
148
84
  ["kg", async (subcommandArgs) => {
149
- const { runKnowledgeGraphCommand } = await import("../chunks/knowledge-graph-cli-Rj5nj87L.js");
85
+ const { runKnowledgeGraphCommand } = await import("../chunks/knowledge-graph-cli-DApeMzA8.js");
150
86
  return runKnowledgeGraphCommand(subcommandArgs);
151
87
  }],
88
+ ["settings", async (subcommandArgs) => {
89
+ const { runSettingsCommand } = await import("../chunks/settings-cli-DQOjmjev.js");
90
+ return runSettingsCommand(subcommandArgs);
91
+ }],
152
92
  ["visions", async (subcommandArgs) => {
153
93
  const { runVisionsCli } = await import("../chunks/visions-cli-CYujESdJ.js");
154
94
  return runVisionsCli(subcommandArgs);
155
95
  }],
156
96
  ["ssh-sign", async (subcommandArgs) => {
157
- const { runSshSignCli } = await import("../chunks/ssh-sign-cli-DPHpT22B.js");
97
+ const { runSshSignCli } = await import("../chunks/ssh-sign-cli-LIBP8ppO.js");
158
98
  return runSshSignCli(subcommandArgs);
159
99
  }],
100
+ ["factory", async (subcommandArgs) => {
101
+ const { runAgentApiCli } = await import("../chunks/agent-api-cli-BTHEyJKS.js");
102
+ return runAgentApiCli(["factory", ...subcommandArgs]);
103
+ }],
160
104
  ...AGENT_API_VERBS.map((verb) => [verb, async (subcommandArgs, subcommandName) => {
161
- const { runAgentApiCli } = await import("../chunks/agent-api-cli-CRj_FYbz.js");
105
+ const { runAgentApiCli } = await import("../chunks/agent-api-cli-BTHEyJKS.js");
162
106
  return runAgentApiCli([subcommandName, ...subcommandArgs]);
163
107
  }])
164
108
  ]);
165
109
  async function printSessionLimits() {
166
110
  if (!process.env["GLIMMERVOID_AGENT_URL"]) return;
167
- const { sessionLimitsHelpLines } = await import("../chunks/agent-api-cli-CRj_FYbz.js");
111
+ const { sessionLimitsHelpLines } = await import("../chunks/agent-api-cli-BTHEyJKS.js");
168
112
  const limitsLines = await sessionLimitsHelpLines();
169
113
  console.log(`\n${limitsLines.join("\n")}`);
170
114
  }
@@ -221,7 +165,7 @@ async function readDeclaredCustomAgents() {
221
165
  declared: [],
222
166
  error: null
223
167
  };
224
- const { loadConfigFile } = await import("../chunks/config-store-BeIxAZNn.js");
168
+ const { loadConfigFile } = await import("../chunks/config-store-CsUoy88Q.js");
225
169
  try {
226
170
  const loaded = loadConfigFile(decided.path, { exitOnError: false });
227
171
  if (!loaded.config) return {
@@ -297,7 +241,7 @@ async function runDoctor() {
297
241
  }
298
242
  switchSection("Agents");
299
243
  try {
300
- const { listAgentIds, describeAgentResolvability, setCustomAgents } = await import("../chunks/adapters-B37wqNti.js");
244
+ const { listAgentIds, describeAgentResolvability, setCustomAgents } = await import("../chunks/adapters-CqFFmM2R.js");
301
245
  const declaredCustomAgents = await readDeclaredCustomAgents();
302
246
  if (declaredCustomAgents.error) line("config", declaredCustomAgents.error);
303
247
  setCustomAgents(declaredCustomAgents.declared);
@@ -305,7 +249,7 @@ async function runDoctor() {
305
249
  const { label, path: resolvedPath } = describeAgentResolvability(id);
306
250
  line(`${id} (${label})`, resolvedPath ?? "not found on PATH");
307
251
  }
308
- const { inspectGrokAgentSetup } = await import("../chunks/agent-setup-cli-BTAFcckd.js");
252
+ const { inspectGrokAgentSetup } = await import("../chunks/agent-setup-cli-C8DGxlA-.js");
309
253
  const grokSetup = inspectGrokAgentSetup();
310
254
  line("grok hook setup", `${grokSetup.classification}: ${grokSetup.filePath}`);
311
255
  } catch (err) {
@@ -313,7 +257,7 @@ async function runDoctor() {
313
257
  }
314
258
  switchSection("rtk");
315
259
  try {
316
- const { getRtkPath } = await import("../chunks/rtk-resolver-SWU5VVM7.js");
260
+ const { getRtkPath } = await import("../chunks/rtk-resolver-B8BPgN9L.js");
317
261
  line("rtk", getRtkPath() || "not installed (Glimmervoid installs it when the rtk setting is on)");
318
262
  } catch (err) {
319
263
  line("rtk", `probe failed: ${firstLine(errorText(err))}`);
@@ -322,7 +266,7 @@ async function runDoctor() {
322
266
  try {
323
267
  const { resolvePackageBin } = await import("../chunks/runtime-paths-DSmormp7.js");
324
268
  const { execFileSync } = await import("../chunks/child-process-safe-ChRmWAJP.js");
325
- const { inspectGrokAgentSetup } = await import("../chunks/agent-setup-cli-BTAFcckd.js");
269
+ const { inspectGrokAgentSetup } = await import("../chunks/agent-setup-cli-C8DGxlA-.js");
326
270
  const binPath = resolvePackageBin("cc-safety-net", "cc-safety-net");
327
271
  line("Sane YOLO", binPath || "not installed");
328
272
  if (binPath) line("cc-safety-net version", execFileSync(process.execPath, [binPath, "--version"], {
@@ -342,8 +286,8 @@ async function runDoctor() {
342
286
  line("reason", nodePty.reason);
343
287
  line("hint", nodePtyRebuildHint(platform));
344
288
  }
345
- switchSection("Sandbox");
346
- for (const [label, value] of sandboxDoctorRows(probeSandboxDependencies({
289
+ switchSection("Guardrails");
290
+ for (const [label, value] of guardrailsDoctorRows(probeGuardrailsDependencies({
347
291
  platform,
348
292
  exec: execSync
349
293
  }))) line(label, value);