From a158d6bfe83eaa5cba7f82ac75f253e681a92d58 Mon Sep 17 00:00:00 2001 From: iceBear67 Date: Sun, 16 Aug 2026 02:42:22 +0000 Subject: [PATCH] add readme --- AGENTS.md | 287 +++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 288 +----------------------------------------------------- README.md | 161 ++++++++++++++++++++++++++++++ 3 files changed, 449 insertions(+), 287 deletions(-) create mode 100644 AGENTS.md mode change 100644 => 120000 CLAUDE.md create mode 100644 README.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..db490dd --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,287 @@ +# CLAUDE.md — working on grok-glance + +grok-glance is the web control plane for grok's `/rc` remote control. Read +[ARCHITECTURE.md](ARCHITECTURE.md) first — it explains *why* the pieces are shaped the way +they are. This file is the operational half: how to build it, how to test it, and the +handful of things that will waste an afternoon if you learn them the hard way. + +The Rust half lives in the sibling `newgrok/` tree as patches `0003`/`0004`. **The two are +one feature.** A change to the wire format is a change to both. + +--- + +## Layout + +``` +cmd/glance/main.go serve | apikey add·list·rm | bootstrap | version +cmd/fakeagent/main.go a fake grok, for UI work without rebuilding Rust +internal/ + acp/ JSON-RPC framing + the ACP subset glance needs ~280 lines + hub/ agents, browsers, ring buffer, interaction table the live system + auth/ TOTP, bootstrap gate, signed cookies + state/ ~/.grok/glance/state.json (0600) + httpapi/ chi router, both WS upgrades, embedded SPA +web/src/ + App.tsx status gate: Setup / Login / Console + pages/ Setup, Login, Sessions, Session + components/ Transcript, ToolCall, PermissionDialog, PromptBox, TurnStatus + lib/ api.ts (REST), ws.ts (GlanceSocket), acp.ts (frames → transcript) +``` + +If you are looking for where a decision lives: + +| Question | File | +|---|---| +| What does the browser send/receive? | `internal/hub/browser.go` | +| What happens when two people click Allow? | `internal/hub/agent.go` → `Answer`, `retract` | +| Which methods does glance refuse? | `internal/hub/agent.go` → `handleFrame` | +| Is this frame part of the transcript? | `internal/acp/jsonrpc.go` → `IsTranscript` | +| Why is `/setup` a 404? | `internal/auth/auth.go` → `BootstrapToken` | +| How does a frame become a bubble? | `web/src/lib/acp.ts` → `applyFrame` | + +--- + +## Build and run + +```sh +make build # frontend, then binary → bin/glance +make server # binary only, keeping the last frontend build (fast server loop) +make web # frontend only +make check # go vet + go test + tsc --noEmit +make dev # Go server + Vite with hot reload → http://localhost:5173 +``` + +`//go:embed all:dist` resolves at compile time, so **`go build` ships whatever `make web` +last produced**. If a UI change does not appear in `bin/glance`, that is why. + +Use `make dev` for frontend work: Vite proxies `/api` to the Go server, which is what keeps +the `__Host-glance` cookie working — it is `SameSite=Strict` and would never survive a +cross-origin request. Hitting the Go port directly on 7717 with the Vite UI will look like +a mysterious auth failure. + +### First run + +```sh +make build +bin/glance serve --addr 127.0.0.1:7717 --insecure-cookie +# → prints a bootstrap URL; open it, scan the QR, enter one code +bin/glance apikey add dev +# → prints glance_sk_… once, plus a [remote_control] block for ~/.grok/config.toml +``` + +`--insecure-cookie` drops the `Secure` attribute so a plain-HTTP localhost session works. +It is refused on a non-loopback address, deliberately. In production put glance behind a +TLS-terminating proxy and leave the flag off. + +Lost the authenticator? Delete `~/.grok/glance/state.json` and start over. That is the +whole recovery story, on purpose — see ARCHITECTURE.md. **`~/.grok/glance/` also contains +`secret.key` and `hook.secret` that belong to something else entirely. Do not touch them.** + +--- + +## Testing without rebuilding grok + +`cmd/fakeagent` dials the agent socket exactly as the real bridge does and plays a scripted +turn: streamed text, a thought, a plan, a tool call, and a **real** +`session/request_permission` that waits for a real answer. + +```sh +bin/glance serve --insecure-cookie & +make fakeagent KEY=glance_sk_… # or: go run ./cmd/fakeagent --key … +``` + +Then in the browser: prompt it, watch it stream, answer the permission. Useful flags: + +- `--terminal-after 3s` — the "terminal" answers the permission first, so you can watch the + browser's card retract by itself. This is the path a browser alone cannot exercise. +- `--speed 1s` — slow the stream down to catch layout problems mid-turn. +- Send the prompt and hit **Stop**: the fake agent honours `session/cancel` and finishes the + turn with `stopReason: cancelled`. + +Run several at once with different `--title` to test the session list. + +It emits `turn_completed` on the **xAI rail** (`x.ai/session_notification`) on purpose: if +the turn stops showing as finished in the UI, rail mirroring has regressed. That is the +most likely thing to break silently after an upstream sync. + +What fakeagent does *not* prove is that the real bridge speaks this dialect. Only the +end-to-end checklist does. + +--- + +## Where the ACP types come from + +There is no Go SDK for ACP. `internal/acp` is hand-written against +[`agentclientprotocol/agent-client-protocol`](https://github.com/agentclientprotocol/agent-client-protocol) +(`schema/v1/schema.json`), and it is thin on purpose — glance correlates ids, recognises a +dozen methods, and passes payloads through to the browser as `json.RawMessage`. + +**Do not "finish" it by modelling every update variant.** The stable `session/update` rail +is a small closed set; the xAI rail has ~60 grok-internal variants that drift with every +upstream sync. Typed structs for those would be a large amount of code whose only effect is +to turn an upstream rename into a parse error that kills a live connection. The contract is: + +- **The stable rail carries correctness.** Turn state comes from `update.sessionUpdate`, + read by `classifyUpdate` in `internal/hub/agent.go` — one field, both rails. +- **The xAI rail is presentation.** Unrecognised variants are stored, forwarded, and + skipped by the renderer. Never an error. +- **`_meta` is forwarded byte-for-byte.** `eventId`, `promptId`, `chunkId` and `isReplay` + are how a viewer dedups and orders; rewriting the envelope would break replay. + +To regenerate anything, clone the spec repo and read `schema/v1/schema.json`. There is no +codegen step and adding one would be a mistake at this size. + +### Response shapes are pinned to grok's Rust types, not to the spec + +The three interaction replies are built in `web/src/components/PermissionDialog.tsx`, and +their exact shapes were read off grok's source, not guessed: + +| Interaction | Reply | +|---|---| +| `session/request_permission` | `{"outcome":{"outcome":"selected","optionId":"…"}}`, or `{"outcome":{"outcome":"cancelled"}}` | +| `x.ai/exit_plan_mode` | `{"outcome":"approved"}`, `{"outcome":"cancelled","feedback":"…"}`, `{"outcome":"abandoned"}` | +| `x.ai/ask_user_question` | `{"outcome":"accepted","answers":{"":["