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.
- package/README.md +86 -82
- package/dist/bin/glimmervoid.js +58 -114
- package/dist/chunks/{adapters-CADejSpq.js → adapters-BmfVPxlH.js} +29 -61
- package/dist/chunks/adapters-CqFFmM2R.js +1 -0
- package/dist/chunks/{agent-api-cli-CRj_FYbz.js → agent-api-cli-BTHEyJKS.js} +1 -1
- package/dist/chunks/{agent-api-cli-vGwstJ1j.js → agent-api-cli-CBgOKXoO.js} +41 -12
- package/dist/chunks/{agent-api-core-D1233u1U.js → agent-api-core-DzKhakCv.js} +94 -3
- package/dist/chunks/{agent-setup-cli-BTAFcckd.js → agent-setup-cli-C8DGxlA-.js} +1 -1
- package/dist/chunks/{config-rPbbL4nW.js → config-BYc2z1Oc.js} +60 -779
- package/dist/chunks/config-store-B7MpiYxG.js +4181 -0
- package/dist/chunks/config-store-CsUoy88Q.js +1 -0
- package/dist/chunks/{contracts-DjIgIuDk.js → contracts-C6ipN_zw.js} +100 -12
- package/dist/chunks/git-version-6QFnKbWa.js +139 -0
- package/dist/chunks/{grok-KKgdSe-7.js → grok-CZ6kP_vD.js} +7 -3
- package/dist/chunks/guardrails-deps-Cb-ki4EU.js +70 -0
- package/dist/chunks/{sandbox-proxy-core-hnQgKR0T.js → guardrails-proxy-core-BI3rdY-m.js} +4 -4
- package/dist/chunks/{hook-command-core-DclEoE9e.js → hook-command-core-qTNK-xMF.js} +4 -1
- package/dist/chunks/{hook-tools-UX2fmt81.js → hook-tools-BkxllUHE.js} +9 -16
- package/dist/chunks/{knowledge-graph-cli-Rj5nj87L.js → knowledge-graph-cli-DApeMzA8.js} +1 -1
- package/dist/chunks/{loopback-post-DepQfdcM.js → loopback-post-DJ6iYWuL.js} +9 -9
- package/dist/chunks/{main-C-T2Uk20.js → main-BUfjroXE.js} +16282 -12595
- package/dist/chunks/{pair-cli-Dcyzv3In.js → pair-cli-GZ9t1mIO.js} +2 -2
- package/dist/chunks/{remote-config-Dd1T-VJA.js → remote-config-D2Is89Kj.js} +1 -1
- package/dist/chunks/{rtk-hook-core-BmEz2aq9.js → rtk-hook-core-VrUafGAp.js} +15 -5
- package/dist/chunks/rtk-resolver-B8BPgN9L.js +1 -0
- package/dist/chunks/{rtk-resolver-B9pzjLsM.js → rtk-resolver-C_micGrL.js} +1 -1
- package/dist/chunks/session-BZbariv5.js +1032 -0
- package/dist/chunks/{session-routes-BYy3am9m.js → session-routes-C4t8O8yg.js} +9 -9
- package/dist/chunks/settings-cli-DQOjmjev.js +484 -0
- package/dist/chunks/{ssh-sign-cli-DPHpT22B.js → ssh-sign-cli-LIBP8ppO.js} +2 -2
- package/dist/client/assets/index-B_Qt7fbZ.css +1 -0
- package/dist/client/assets/index-D-upBor0.js +265 -0
- package/dist/client/index.html +5 -3
- package/dist/scripts/postinstall-path-check.js +4 -4
- package/dist/server/index.js +1 -1
- package/dist/session/command-hook-relay.js +1 -1
- package/dist/session/{sandbox-open-launcher.js → guardrails-open-launcher.js} +10 -10
- package/dist/session/hook-relay.js +1 -1
- package/dist/session/hook-tool-relay.js +3 -3
- package/dist/session/statusline-relay.js +1 -1
- package/package.json +6 -5
- package/dist/chunks/adapters-B37wqNti.js +0 -1
- package/dist/chunks/config-store-BeIxAZNn.js +0 -1
- package/dist/chunks/config-store-BtsYbNC3.js +0 -1713
- package/dist/chunks/git-version-CUcDtF5k.js +0 -58
- package/dist/chunks/rtk-resolver-SWU5VVM7.js +0 -1
- package/dist/chunks/sandbox-deps-CWiH1d1w.js +0 -70
- package/dist/chunks/session-D6xXN0UU.js +0 -232
- package/dist/client/assets/index-CUxRHbCS.js +0 -254
- package/dist/client/assets/index-DE2GUmqs.css +0 -1
package/README.md
CHANGED
|
@@ -1,42 +1,45 @@
|
|
|
1
1
|
# Glimmervoid
|
|
2
2
|
|
|
3
3
|
[](https://github.com/johncwaters/glimmervoid/actions/workflows/test.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/glimmervoid)
|
|
4
5
|
[](https://opensource.org/licenses/MIT)
|
|
5
6
|
[](https://nodejs.org)
|
|
6
7
|
[](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
|
-
|
|
11
|
+
[glimmervoid.dev](https://glimmervoid.dev)
|
|
11
12
|
|
|
12
|
-
Glimmervoid is
|
|
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
|

|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
- **
|
|
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`
|
|
60
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
|
75
|
-
glimmervoid --port 3001
|
|
76
|
-
glimmervoid doctor
|
|
77
|
-
glimmervoid
|
|
78
|
-
glimmervoid --
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
121
|
+
Worktrees branch from each repo's default branch (`origin/HEAD`, then `main`, then `master`) unless you set `integrationBranch`.
|
|
117
122
|
|
|
118
|
-
|
|
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
|
-
|
|
125
|
+
## How status detection works
|
|
121
126
|
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
131
|
+
Three bugs shaped the details:
|
|
127
132
|
|
|
128
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`. `
|
|
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
|
|
145
|
-
3. Point an HTTPS reverse proxy at that port. With Tailscale, `tailscale serve --bg 3456` serves `https://<machine>.<tailnet>.ts.net`, which is
|
|
146
|
-
4.
|
|
147
|
-
5. On the host, run `glimmervoid pair --name phone
|
|
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
|
-
|
|
156
|
+
`glimmervoid pair --list` shows paired devices, and `glimmervoid pair --revoke <id>` removes one without a restart.
|
|
152
157
|
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
188
|
+
## Security
|
|
184
189
|
|
|
185
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
194
|
+
## Contributing
|
|
191
195
|
|
|
192
|
-
See [
|
|
196
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Release history is in [CHANGELOG.md](CHANGELOG.md).
|
|
193
197
|
|
|
194
198
|
## License
|
|
195
199
|
|
package/dist/bin/glimmervoid.js
CHANGED
|
@@ -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-
|
|
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
|
|
10
|
-
import {
|
|
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
|
|
99
|
-
|
|
100
|
-
pair
|
|
101
|
-
pair --list
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
visions
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
--
|
|
118
|
-
--
|
|
119
|
-
--
|
|
120
|
-
--
|
|
121
|
-
--
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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("
|
|
346
|
-
for (const [label, value] of
|
|
289
|
+
switchSection("Guardrails");
|
|
290
|
+
for (const [label, value] of guardrailsDoctorRows(probeGuardrailsDependencies({
|
|
347
291
|
platform,
|
|
348
292
|
exec: execSync
|
|
349
293
|
}))) line(label, value);
|