hooks/hooks.json was generated from a template by scripts/gen-hooks.mjs. Once every
hook became a command hook that reads hook.secret from $GLANCE_HOME itself, the
template had exactly two placeholders left: {{HOOK_TOKEN}}, which nothing had ever
substituted into anything, and {{APPROVAL_TIMEOUT_SECS}}. Generating a whole file to
compute one number is not a good trade, so the number is now fixed at 125s in the
committed hooks.json and the coupling is enforced in code instead: the daemon clamps
approval.timeoutMs to APPROVAL_MAX_WAIT_MS (90s), which keeps the script inside its
own hook timeout no matter what a hand-edited config.json says. Losing that clamp is
what would actually hurt — a killed script never runs its fail-open path.
Also removed:
- `glance sync-hooks`, `npm run build:hooks`, and hookSecret({create}). The daemon is
the only thing that should ever mint the secret.
- The ?k= query-string carrier for the hook secret. It existed for hooks that cannot
set headers; there are none, and a secret in a URL lands in logs and shell history.
- Snapshot.now and SessionView.startedAt, which were written on every snapshot and
every persist and read by nobody.
- An unused crypto import.
Docs and the e2e suite follow. The suite's ~12 sync-hooks assertions become static
checks on the committed file, plus new ones that hooks.json, APPROVAL_HOOK_TIMEOUT_SECS
and APPROVAL_MAX_WAIT_MS still agree, and that ?k= is refused. 220 checks, all passing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.0 KiB
name, description
| name | description |
|---|---|
| glance | Set up, inspect, or control grok-glance — the passkey-guarded phone dashboard for this Grok Build session. Use when the user asks to watch one or several sessions from their phone, enrol a device, expose the dashboard over Tailscale, or turn remote approve/deny on or off. |
grok-glance
A local daemon plus web dashboard that shows what Grok Build is doing, readable from a phone behind a WebAuthn passkey. It can also pause risky tool calls until someone taps approve.
One daemon covers every session on the machine, so several agents running at once all appear on the same dashboard — no per-session setup.
The daemon is started automatically by the SessionStart hook. Everything below is done through
the glance CLI at $GROK_PLUGIN_ROOT/bin/glance.
First check whether it is even built
The plugin ships as TypeScript and must be built once:
cd "$GROK_PLUGIN_ROOT" && npm install && npm run build
glance status prints a "not built" error with this same instruction if it is missing. Do not
attempt to skip the build — the daemon entry point is dist/server/index.js.
The commands
glance status # is it running, which origin, how many devices
glance up # start the daemon in the background
glance stop # stop it
glance logs # last 60 lines of the daemon log
glance enroll # mint a one-time code + URL for a new phone
glance set-origin <https-url> # set the public origin and WebAuthn RP ID
glance devices # list enrolled devices
glance revoke <id-prefix> # revoke one
glance approval <off|risky|all> # remote approve/deny policy
Watching several agents
Nothing to configure — every session that runs the hooks shows up. glance status reports the
roster and what each agent is doing:
sessions : 4 (1 waiting on you, 1 error, 2 working)
Points worth passing on to the user:
- Agents are identified by a coloured badge (
●1,●2) as well as the workspace name, because two agents in the same repo carry the same label. The badge is stable across daemon restarts. - The list is ordered by who needs attention (waiting → error → working → idle → ended) and never re-sorts underneath a tap.
- Approval cards say which agent is asking; with
approval riskyon and several agents, expect several cards. - One noisy agent will not push the others out of the timeline — the event ring is trimmed from whichever session is using the most of it.
Getting it onto a phone
The dashboard listens on 127.0.0.1 only. Passkeys need a real hostname with valid TLS — a bare
IP can never be a WebAuthn RP ID — so the supported path is Tailscale Serve:
tailscale serve --bg 127.0.0.1:8791
tailscale serve status # read the https://<box>.<tailnet>.ts.net URL
glance set-origin https://<box>.<tailnet>.ts.net
glance enroll
Then open the printed URL on the phone, type the code, and create the passkey. The phone must be on the same tailnet.
Changing the origin changes the RP ID, which invalidates existing passkeys. Say so before running
set-origin on a working setup.
Remote approve/deny
glance approval risky makes Bash, Write, Edit, MultiEdit and NotebookEdit calls pause
and wait for a tap on the phone. Defaults that matter:
- Nothing waits unless a phone is actually watching the dashboard (
requireWatcher). - If nobody answers within 90s the call is allowed, not denied. Flip that on the phone's settings panel if you want the opposite.
- Every failure path is fail-open: daemon down, timeout, bad JSON, a rejected hook secret — the tool call proceeds. This is a convenience gate, not a security boundary.
glance approval off (the default) means Grok Build never blocks on the phone.
When something does not work
- "not running" →
glance up, thenglance logs. - Passkey prompt fails with a security error → the phone is on a hostname the RP ID does not
cover. Compare
glance status'srp idwith the hostname in the phone's address bar. - Dashboard loads but shows nothing → hooks are not firing.
glance statuswarns if the hook secret in$GLANCE_HOME/hook.secretno longer matches the one the daemon loaded (events are being dropped with a 403);glance stop && glance upfixes that. Otherwise check thathooks/hooks.jsonexists and that the plugin is registered with Grok Build. - Page says "run npm install && npm run build" → the web bundle is missing; build it.
- Only one agent shows up → the others were started before the plugin was installed, or in an environment where the hooks are not registered. A session appears on its next hook event; nothing can be back-filled for one that already ran.
What it deliberately does not do
Read-only plus approve/deny. It cannot send prompts, edit files, run tools, or resume a session. Do not tell the user otherwise.