backthread 0.1.0 → 0.1.1

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 CHANGED
@@ -1,431 +1,84 @@
1
- # backthread — CLI / plugin
1
+ # backthread
2
2
 
3
- The local **agent plugin** spine for Backthread's live capture loop + in-Claude-Code
4
- query (F7). This package is the shared entrypoint the later F7 surfaces hang off:
5
- the `SessionEnd`/`Stop` capture hook (F7.2), the `/backthread capture` slash command
6
- (F7.3), and the MCP server (F7.4) all call into it.
3
+ [![npm](https://img.shields.io/npm/v/backthread?logo=npm)](https://www.npmjs.com/package/backthread)
4
+ [![license](https://img.shields.io/npm/l/backthread?label=license)](./LICENSE)
7
5
 
8
- It is a **separate npm package** from the diagram app (mirrors `worker/`): it
9
- does **not** modify the root `package.json` and has no app dependencies. Its only
10
- runtime dependency is the official MCP SDK (`@modelcontextprotocol/sdk`), pulled
11
- in by the F7.4 `backthread mcp` server; everything else uses Node builtins, so `npx backthread` stays a small, audit-light download.
6
+ **Keep the thread on what your AI agent actually shipped.**
12
7
 
13
- ## Status (F7.1 / ARP-432, F7.2 / ARP-433, F7.3 / ARP-434, F7.4 / ARP-435, F7.7 / ARP-438)
8
+ When you hand code to AI agents (Claude Code, Codex, Cursor), you stop reading
9
+ every change — and a few weeks later you own a codebase you never internalized.
10
+ Debugging slows down, refactors get scary.
14
11
 
15
- Scaffold + `backthread login` (the browser OAuth-loopback device-token client), the
16
- `backthread capture` SessionEnd/Stop hook handler (F7.2), the `/backthread:capture` MANUAL
17
- slash command (F7.3), the `backthread mcp` server (F7.4) exposing the in-Claude-Code
18
- `capture` + `query` tools, and `backthread install` (F7.7) — the onboarding glue that
19
- authorizes the device, registers the SessionEnd hook, and backfills history.
12
+ Backthread captures the **why** behind each change straight from your agent
13
+ sessions, so you can ask *"how does X work?"* and stay oriented without
14
+ spelunking through PRs. The decisions become a live **"How it works"** diagram
15
+ and changelog at [backthread.dev](https://backthread.dev).
20
16
 
21
- ## Commands
22
-
23
- ```
24
- backthread start First-run setup: trust copy + one-tap auth + your next step (backs /backthread:start)
25
- backthread login Authorize this device (opens your browser)
26
- backthread login --device Headless / SSH login (device-code flow — STUBBED, see below)
27
- backthread whoami Show the current device's config (token is never printed)
28
- backthread capture Capture this session's decisions (run by the SessionEnd/Stop hook)
29
- backthread capture --manual Manually capture a session now (backs the /backthread:capture slash command)
30
- backthread mcp Start the MCP server (capture + query tools) over stdio
31
- backthread install Set up capture for this repo: login + register hook + backfill history
32
- backthread help Usage
33
- ```
34
-
35
- ## `backthread login` — the loopback flow
36
-
37
- 1. Starts a localhost server on `http://127.0.0.1:<random-port>/callback`.
38
- 2. Opens the browser to `app.backthread.dev/cli-auth?port=<port>&state=<nonce>`.
39
- 3. You click **Authorize** (you're already signed into the web app). The page
40
- mints a capture-scoped `backthread_pat_…` device token via the `mint-device-token`
41
- Edge Function (F7.0 / ARP-448) and redirects back to the loopback with the
42
- token + the same `state` nonce.
43
- 4. The CLI validates the `state` nonce (CSRF guard) and writes the token to
44
- `~/.backthread/config.json` at **chmod `0600`**.
45
-
46
- One browser click, zero copy-paste. **The token never touches the clipboard or
47
- terminal scrollback** — it goes straight from the loopback query string into the
48
- 0600 config file and is never printed or logged.
49
-
50
- ## Inference router — `infer.ts` (F7.IR / ARP-450)
51
-
52
- `inferDecisions(transcript, config, opts)` decides **how** decisions get derived
53
- from a normalized + redacted transcript and **whose** credentials pay, then
54
- returns the derived decisions for the caller (F7.2 hook / F7.4 MCP) to POST to
55
- `ingest-decisions` — **unless** the result says the server already persisted them.
56
-
57
- - **Model 2 (server-side, our keys) — the DEFAULT.** POSTs the redacted
58
- transcript to the Worker's F7.SF `POST /infer-decisions` (auth: the F7.0
59
- `backthread_pat_` device token), which runs the tuned Gemini-bulk → Sonnet-tiebreak
60
- pipeline with our keys. Pass `persist: true` + `repo` to have the server also
61
- write the decisions (membership-gated) — then `result.persisted` is `true` and
62
- the caller MUST NOT re-POST to `ingest-decisions` (double-write). Default is
63
- derive-only: the caller persists.
64
- - **Model 3 (BYO API key) — a power-user override, SEAM ONLY.** `localByokInfer`
65
- is a stub marked `// TODO(F7.19 / ARP-463)` that always reports "no BYOK
66
- configured", so the router falls through to Model 2. Local BYOK execution +
67
- key storage/validation/settings UX ships in F7.18/19 (ARP-462/463).
68
- - **Model 4 — parked, not modelled here** (post-MVP, opt-in; ToS-blocked variants
69
- are off the table per the F7.IV spike, ARP-449).
70
-
71
- **Trust boundary (be honest — Model 2 is the weaker claim).** Because the
72
- default path runs inference on our servers, the thing that leaves your machine
73
- on Model 2 is **the redacted transcript** — natural-language prose only. The
74
- plugin redacts LOCALLY first (drops every tool-use / tool-result record and
75
- redacts fenced code to `[code redacted]`), so **no source code and no tool I/O
76
- ever leave the machine**; the Worker re-runs the fenced-code scrub server-side
77
- as a fail-closed backstop against a plugin redaction bug, derives the decisions,
78
- and **discards the transcript right after extraction — processed in memory,
79
- never stored.** That is a *weaker* claim than the BYOK/Model-3 path, where
80
- nothing but the derived decisions ever leaves the machine — and we say so out
81
- loud rather than paper over it. (Model 3 is still a SEAM only; until F7.19/20
82
- ship, every run takes the Model-2 server path.) This is distinct from the
83
- hosted **repo-ingestion** pipeline (the diagram), which clones your code into a
84
- destroy-on-exit sandbox and never stores source either — two separate paths,
85
- same never-store-source posture for actual code. The canonical statement
86
- lives at [`/security`](https://backthread.dev/security).
87
-
88
- Dependency-free (global `fetch`); a `fetchImpl` seam lets tests run without a
89
- network or Worker. Worker origin is overridable via `BACKTHREAD_WORKER_URL`
90
- (e.g. `http://localhost:8787` for `wrangler dev`).
91
-
92
- ## `backthread capture` — the SessionEnd/Stop hook (F7.2 / ARP-433)
93
-
94
- The self-maintaining moat: at the end of every agent session, derive the
95
- session's **decisions** (the "why") and land them in the hosted log, so it stays
96
- current after the one-time F5 backfill. `backthread capture` is the headless hook
97
- handler. Pipeline (all LOCAL until the last network hop):
98
-
99
- 1. Read the hook input (JSON on **stdin**; `transcript_path` + `cwd` +
100
- `session_id`). A `BACKTHREAD_HOOK_INPUT` env var is accepted as a dev/test fallback.
101
- 2. Read the `.jsonl` transcript off disk.
102
- 3. **Redact LOCALLY** (`redact.ts` — the security fence): drop every
103
- tool-use / tool-result record, keep only natural-language prose, redact fenced
104
- code blocks to `[code redacted]`. **No source code or tool I/O ever leaves the
105
- machine.**
106
- 4. Derive decisions via the **F7.IR router** (`inferDecisions`). When a repo
107
- resolves from `cwd`, we pass `persist: true` so the server (membership-gated)
108
- also writes them — then `result.persisted` is `true`.
109
- 5. Persist:
110
- - `result.persisted` → **done** (re-POSTing would double-write).
111
- - else POST the **derived** decisions to `ingest-decisions`, which routes
112
- connected vs **repo-less** (F7.8) server-side.
113
-
114
- **Best-effort + non-blocking (the load-bearing contract):** `runCapture` never
115
- throws — every failure mode resolves a structured outcome — and `backthread capture`
116
- **always exits 0**. A capture hiccup can never disrupt or delay the user's Claude
117
- Code session. If there's no device token, it kicks off `backthread login`
118
- fire-and-forget (never awaited) and **skips this capture** — the next session
119
- captures once a token exists.
120
-
121
- ### Registering the hook — TWO mechanisms (F7.7 / ARP-438)
122
-
123
- Claude Code runs `SessionEnd` hooks, passing the session JSON on the hook
124
- process's **stdin**. `npx backthread capture` reads that payload, derives the session's
125
- decisions (locally-redacted), and persists them best-effort — always exiting 0, so
126
- the hook host never blocks on it and never sees a non-zero exit. There are two ways
127
- the hook gets registered, and Backthread uses both depending on how it was installed:
128
-
129
- 1. **PRIMARY — the plugin manifest.** When Backthread is installed as a Claude Code
130
- plugin, the hook is declared in **`hooks/hooks.json`** (referenced by the
131
- `hooks` field of `.claude-plugin/plugin.json`) and Claude Code registers it
132
- **automatically on install** — the user's `.claude/settings.json` is never
133
- touched. This is the right home: the hook ships + versions with the plugin and
134
- is removed cleanly on uninstall. (Schema confirmed against the current Claude
135
- Code plugin docs: `hooks/hooks.json`, `command` type.)
136
- 2. **FALLBACK — `backthread install` writes `.claude/settings.json`.** For the bare
137
- `npx backthread` (non-plugin) path there is no manifest doing the wiring, so
138
- `backthread install` merges this entry into the project's `.claude/settings.json`
139
- itself (idempotent — re-running never duplicates it; a strict merge that never
140
- clobbers other settings/hooks):
141
-
142
- ```jsonc
143
- {
144
- "hooks": {
145
- "SessionEnd": [
146
- { "hooks": [ { "type": "command", "command": "npx backthread capture" } ] }
147
- ]
148
- }
149
- }
150
- ```
151
-
152
- ## `backthread start` — the plugin first-run (8B.5 / ARP-486)
153
-
154
- The **in-agent** first-run for the plugin half of the both-equal front door (ARP-482),
155
- behind the **`/backthread:start`** slash command. Where `backthread install` wires the
156
- bare-`npx` (non-plugin) path (hook registration + backfill), `start` is the experience
157
- a founder gets after a **marketplace 1-click** install — the SessionEnd hook is already
158
- armed by the manifest, so `start` only does the *human* part:
159
-
160
- 1. **Idempotence gate.** Reads `~/.backthread/first-run.json`. A returning user
161
- (`onboarded` flag set) is **never re-onboarded** — it just confirms they're set up.
162
- 2. **Trust gate.** Prints the **never-store-source** trust copy (the `TRUST_COPY` from
163
- `install.ts`, consistent with [`/security`](https://backthread.dev/security)) **before
164
- anything else** — the AC requires the claim to precede any transcript processing.
165
- 3. **One-tap auth.** `--claim <code>` (8B.9) exchanges a web-app-minted claim code for a
166
- device token — no browser, the web-initiated door. With no claim code it runs the
167
- browser **loopback** (`ensureAuth`); an already-authed device short-circuits.
168
- `--device` (headless device-code, F7.23) is **out of scope** (→ ARP-467) and is
169
- **refused with the loud stub** rather than silently falling back to the loopback
170
- (which would hang a headless box).
171
- 4. **State-driven next step.** Reads the **unified onboarding state** (`fetchOnboardingState`,
172
- 8B.6 — the SAME backend signal the web wizard reads) and renders its canonical next
173
- step. When the repo isn't connected this IS the **F7.10 connect nudge** copy
174
- (server-driven); a terminal state renders cleanly with the diagram deep-link.
175
- 5. **Mark onboarded.** Persists the flag so step 1 short-circuits next time.
176
-
177
- Exits non-zero **only** on a genuine auth failure (capture won't run until you act);
178
- the auth failure does **not** mark onboarded, so re-running retries.
179
-
180
- **"Onboarded" is an explicit flag**, not the derived "token present + ≥1 capture": the
181
- trust gate must precede the very first capture (so we can't key onboarding off having
182
- already captured), and a token rotation must not re-trigger the wizard. The derived
183
- onboarding *state* still drives the *next-step* copy — it's just not the onboarding gate.
184
-
185
- ### The two confirmation surfaces in the capture path
186
-
187
- Two 8B.5 lines live in the capture path itself (not in `start`), each once-per-install:
17
+ ## Your code never leaves your machine
188
18
 
189
- - **Trust gate on the silent hook path** (`maybeShowTrustGate`): the manifest-armed
190
- SessionEnd hook can fire **before** any `start`/`install` ran, and `runCapture`'s
191
- no-auth path fire-and-forgets `ensureAuth` (which can open a browser). So `runCapture`
192
- prints the trust copy **once, before reading the transcript or firing login** — the
193
- never-store-source claim holds on both entrypoints. Best-effort + never-throws +
194
- never-blocks: it can't break the always-exit-0 capture contract.
195
- - **First-capture confirmation** (`maybeFirstCaptureConfirm`): after the **first** capture
196
- that lands decisions against a **connected** repo, a once-only **"captured N — view them
197
- in your "How it works" diagram: <deep-link>"** line. Mutually exclusive with the connect
198
- nudge (which owns the not-connected case). Throttled via `firstCaptureShown` in the
199
- same `first-run.json`. Best-effort.
19
+ Backthread reads your agent **transcripts**, not your repo. Before anything is
20
+ sent, the CLI redacts every transcript **locally**:
200
21
 
201
- ## `backthread install`onboarding (F7.7 / ARP-438)
22
+ - **Drops** every tool call and tool result where source code and command output live.
23
+ - **Keeps** only natural-language prompts and the agent's reasoning.
24
+ - **Redacts** any fenced code block to `[code redacted]`.
202
25
 
203
- The one-motion first-run that lands capture in the rescue-mode aha moment. It runs
204
- three steps, reporting each, and exits non-zero **only** on a genuine auth failure
205
- (the hook + backfill legs are best-effort and never fail the install):
26
+ Only the derived, natural-language *decisions* ever leave your machine. The
27
+ redaction fence is open source ([`@backthread/redact`](https://www.npmjs.com/package/@backthread/redact))
28
+ so you can verify it read more at [backthread.dev/security](https://backthread.dev/security).
206
29
 
207
- 1. **Auth handshake** — reuses `backthread login` via `ensureAuth` (F7.1): if a device
208
- token already exists it's reused; otherwise the browser OAuth-loopback runs once.
209
- 2. **Register the SessionEnd hook** — writes the `.claude/settings.json` fallback
210
- above (skip with `--skip-hook` when installed as a plugin, where the manifest
211
- already registers it).
212
- 3. **Chain the backfill** — runs `backthread`'s **cli-native backfill** (below) so the
213
- decision log is **non-empty at the aha moment**, then self-maintaining via the
214
- live hook. Best-effort; skipped automatically when not yet authorized.
30
+ ## Quick start
215
31
 
216
- Flags: `--skip-auth`, `--skip-hook`, `--skip-backfill`.
32
+ In your project:
217
33
 
218
- Before any transcript is read, `backthread install` prints the **never-store-source**
219
- trust copy (consistent with [`/security`](https://backthread.dev/security)):
220
- redaction happens locally; source code + tool I/O never leave the machine; only
221
- the **derived decisions** are stored. On the default server-inference path a
222
- *redacted, natural-language-only* transcript does leave the machine — never
223
- source, never tool output — and it's **discarded right after extraction, never
224
- stored** (the server re-redacts as a fail-closed backstop). The copy states this
225
- weaker claim plainly rather than hiding it behind "only decisions leave."
226
-
227
- ### Cli-native backfill — `backfill.ts` (the architecture decision, FLAGGED)
228
-
229
- The original F5 backfill is `scripts/ingest/decisions/backfill-cli.ts`, but the
230
- `npx backthread` plugin **cannot ship `scripts/`** (separate dependency-light bundle,
231
- `rootDir: src`). So `backthread install` chains a **cli-native** backfill instead: it
232
- enumerates this repo's existing Claude Code transcripts at
233
- `~/.claude/projects/<encoded-cwd>/*.jsonl` (the layout F7.3 established) and runs
234
- each one through the **same `runCapture` pipeline** as the live hook. It does not
235
- reimplement the redact/derive/persist fence — the never-store-source posture is
236
- inherited verbatim. It is sequential (a one-shot seed has no latency budget),
237
- best-effort (a missing dir / unreadable file / per-transcript failure is tallied,
238
- never fatal), and idempotent (the pipeline's dedupe key makes re-running safe).
239
-
240
- > **SCOPE (flagged):** this cli-native backfill is **Claude-Code-only**. Claude
241
- > Code's per-repo transcript layout is enumerable with zero dependencies; the
242
- > **multi-agent** backfill (Codex / Cursor / Gemini CLI via the F7.26 provider
243
- > registry) stays the **dogfood / server path** in `scripts/`, whose adapters the
244
- > plugin must not depend on. A founder using other agents still gets *live* capture
245
- > for them via the per-agent surfaces; only the one-shot history seed is
246
- > Claude-Code-only here. A shared backfill layer is a follow-up.
247
-
248
- ## `/backthread:capture` — the manual slash command (F7.3 / ARP-434)
249
-
250
- The explicit/manual counterpart to the automatic SessionEnd hook: the founder who
251
- wants to capture **mid-session** ("capture what we just decided, now") or re-run a
252
- session runs it. It drives the **same `runCapture` pipeline** as F7.2 (local-redact
253
- → F7.IR router-derive → hosted-POST) — the redact fence is never reimplemented —
254
- but differs from the hook in two ways:
255
-
256
- 1. **It resolves the transcript itself.** The hook is fed `transcript_path` on
257
- STDIN by Claude Code; a slash command is **not** (Claude Code exposes
258
- `${CLAUDE_SESSION_ID}` and the cwd to a command, but not the transcript path).
259
- So the manual path derives it from Claude Code's on-disk layout:
260
- `~/.claude/projects/<slugified-cwd>/<session_id>.jsonl` (the slug replaces every
261
- non-alphanumeric char in the absolute cwd with `-`). An explicit
262
- `--transcript <path>` always wins. If neither resolves a readable file, it
263
- prints an **actionable hint** (run with `--transcript <path>`) — never a silent
264
- no-op, and never a browser pop.
265
- 2. **It surfaces a per-run summary to STDOUT** (status + decision count +
266
- repo-connected state) and exits non-zero on a genuine failure or when not logged
267
- in — the manual analogue of the local pipeline's `summarize()`. (The hook stays
268
- silent-to-stderr and always exits 0.) A missing device token surfaces as a
269
- `run backthread login` hint; manual mode injects a **no-op `ensureAuth`** so — unlike
270
- the best-effort hook — it never kicks off a background browser login.
271
-
272
- ### The slash-command mechanism (FLAGGED design choice)
273
-
274
- Claude Code custom commands are merged into **skills**: a file at
275
- `<plugin>/commands/capture.md` in a plugin named `backthread` becomes **`/backthread:capture`**
276
- (plugin-namespaced — the faithful realization of the ticket's `/backthread capture`). The
277
- command file uses Claude Code's **`!`shell injection`**` to run
278
- `npx backthread capture --manual --session "${CLAUDE_SESSION_ID}" --cwd "$(pwd)"` at
279
- render time; the bin's summary is inlined into the prompt and the agent relays it.
280
-
281
- This package therefore ships a minimal plugin manifest (`.claude-plugin/plugin.json`)
282
- + the command file (`commands/capture.md`), in addition to the `backthread` bin. Both are
283
- in package.json `files` so they ship via npm. The same files load standalone for
284
- local dev with `claude --plugin-dir ./cli`. (The hook + MCP-server wiring into a
285
- user's settings is install-time automation owned by F7.7 / ARP-438, sequenced after
286
- this task; the command file is the slash-command half.)
287
-
288
- ## `backthread mcp` — the MCP server (F7.4 / ARP-435)
289
-
290
- The in-Claude-Code surface for both halves of HMW #2 ("CC = capture/queries
291
- entry"). `backthread mcp` starts a long-running MCP server over **stdio** (stdout is the
292
- JSON-RPC channel; all diagnostics go to stderr) exposing two tools:
293
-
294
- - **`capture`** — capture the current/given session's DECISIONS (the "why"). It
295
- reuses the **F7.2 `runCapture` pipeline verbatim** (local-redact → derive →
296
- hosted-POST) — the redact fence is never reimplemented. Best-effort: a hiccup
297
- never disrupts the session. Args: `transcript_path` (**required** — unlike the
298
- hook, the MCP tool has no STDIN, so the host must supply the transcript path;
299
- without it the tool returns an actionable hint and captures nothing), plus
300
- optional `cwd` and `session_id`.
301
- - **`query`** ("how does X work?") — reads the **salience-ranked Flows +
302
- Decisions** for the configured repo via the F7.5 `read-decisions` endpoint
303
- (authenticated with the `backthread_pat_` device token), and returns them **plus a
304
- deep-link** into the web-app "How it works" diagram
305
- (`https://app.backthread.dev/<owner>/<repo>`) so the founder can jump from CC to the
306
- visual. Read-only. Args (all optional): `question` (narrated against the returned
307
- log, not a server-side filter), `repo` (`owner/name` override; else `config.repo`,
308
- else the cwd git remote), `cwd`. Unauthed → it reports "run `backthread login`" — it
309
- **never** triggers a browser login itself (unlike the best-effort capture hook).
310
-
311
- Register it in `.claude/settings.json` (install-time wiring is F7.7 / ARP-438):
312
-
313
- ```jsonc
314
- {
315
- "mcpServers": {
316
- "backthread": { "command": "npx", "args": ["backthread", "mcp"] }
317
- }
318
- }
34
+ ```bash
35
+ npx backthread install
319
36
  ```
320
37
 
321
- The server module (`src/mcp.ts`) is thin: the tool handlers delegate to
322
- `runCapture` / `queryDecisions` (their own modules + tests). Both handlers and the
323
- full tool wiring are unit-tested over the SDK's in-memory transport with mocked
324
- impls — no live network, no browser, no real auth.
325
-
326
- ### How `cli/` accesses the redact/parse fence (architecture decision)
327
-
328
- The redaction fence is owned by `scripts/ingest/decisions/transcript.ts` (the
329
- F3/ARP-427 fence, behind the F7.11 provider seam). The `cli/` package, however,
330
- is a **separate, dependency-light bundle** whose distribution target is `npx backthread`
331
- — where **`scripts/` does not ship** — and its tsconfig pins `rootDir: src`, so a
332
- `../scripts/...` import would also break the build layout. So the fence is
333
- **vendored** into `cli/src/redact.ts` (+ the git-remote→repo parser into
334
- `cli/src/repo.ts`) as pure, zero-dependency copies. Parity with the canonical
335
- implementations is enforced by golden cases in `redact.test.ts` / `repo.test.ts`,
336
- so the two cannot silently drift.
337
-
338
- > **Distribution follow-up (flagged on ARP-433):** the vendored copy is the
339
- > pragmatic dogfood call, **not** the end state. The end state is a tiny shared
340
- > package (e.g. `@backthread/redact`) that both `scripts/ingest/decisions` and `cli/`
341
- > depend on, so the security fence has exactly **one** implementation. Until then,
342
- > a change to one fence must be mirrored in the other (the parity tests will fail
343
- > loudly if not). This is a follow-up ticket, not in-scope here.
38
+ That's the whole setup. `install`:
344
39
 
345
- ## Local config`~/.backthread/config.json`
346
-
347
- Read by F7.2 / F7.3 / F7.4. Shape (all fields optional):
348
-
349
- ```json
350
- {
351
- "account": "<account uuid>",
352
- "repo": "owner/name",
353
- "device_token": "backthread_pat_…"
354
- }
355
- ```
40
+ 1. **Signs you in** opens your browser for one click (you'll need a free
41
+ [Backthread](https://backthread.dev) account; the CLI never sees a password,
42
+ and your device token is never printed or copied to the clipboard).
43
+ 2. **Wires up capture** — registers a hook so each Claude Code session is
44
+ captured automatically when it ends.
45
+ 3. **Backfills history** — replays your recent Claude Code sessions in this repo
46
+ so your "How it works" log isn't empty on day one.
356
47
 
357
- Always written at `0600` (dir `~/.backthread` at `0700`).
48
+ Already added Backthread as a Claude Code plugin? The hook is wired for you —
49
+ run `/backthread:start` (or `npx backthread start`) just to sign in.
358
50
 
359
- ## Environment overrides (dev/testing)
51
+ ## Onboard yourself in 3 steps
360
52
 
361
- - `BACKTHREAD_APP_URL`point the loopback at a local web app (e.g.
362
- `http://localhost:5173`) instead of production.
363
- - `BACKTHREAD_WORKER_URL` — point the inference router at a local ingest Worker (e.g.
364
- `http://localhost:8787` for `wrangler dev`) instead of production.
365
- - `BACKTHREAD_FUNCTIONS_URL` point the Supabase Functions calls (`ingest-decisions`,
366
- and the F7.4 `query` tool's `read-decisions`) at a local stack (e.g.
367
- `http://localhost:54321/functions/v1`) instead of production.
368
- - `BACKTHREAD_CONFIG_DIR` — point the config at a temp dir instead of `~/.backthread`
369
- (used by the unit tests; no real `$HOME` is touched).
53
+ 1. **Install**`npx backthread install` in your repo. One browser click to authorize.
54
+ 2. **Keep coding** — at the end of every Claude Code session, Backthread captures
55
+ the decisions automatically. Nothing to remember.
56
+ 3. **Ask "how does X work?"** — query your decision log right inside Claude Code
57
+ (the `backthread` MCP server exposes a `query` tool), or open the live diagram
58
+ at [app.backthread.dev](https://app.backthread.dev).
370
59
 
371
- ## Distribution choice (was open in ARP-432)
372
-
373
- **Default: `npx backthread`** — a standalone npm package with a `backthread` bin. Rationale:
374
- it's the most portable spine — the F7.2 hook, F7.3 slash command, and F7.4 MCP
375
- all shell out to / import the same bin regardless of which agent host invokes
376
- them, and `npx backthread login` works on any machine with Node. A **Claude Code
377
- plugin manifest** can wrap this bin later (it would just declare the hook +
378
- slash command and call `backthread`), so picking `npx backthread` now does not foreclose the
379
- plugin-manifest path — it's the lower layer underneath it. Per-agent adapters
380
- (Cursor/Codex) are explicitly later (Thread B / ARP-455→458).
381
-
382
- ## `--device` fallback — STUBBED in F7.1
383
-
384
- The headless device-code flow (`gh`-style: "go to <url>, enter code ABCD-1234")
385
- needs a **server-side device-authorization endpoint** (a `/device/code` +
386
- `/device/token` pair) that does not exist yet — the F7.0 primitive (ARP-448)
387
- only ships the session-path mint. `backthread login --device` currently prints clear
388
- guidance (run `backthread login` with a browser, or mint from Account → Connected
389
- devices) and exits non-zero. Full impl is a follow-up.
390
-
391
- ## Build / test
60
+ ## Commands
392
61
 
393
62
  ```
394
- npm --prefix cli install # dev deps (tsx, typescript, @types/node, esbuild)
395
- npm --prefix cli run typecheck
396
- npm --prefix cli test # node:test + tsx (NOT vitest)
397
- npm --prefix cli run build # tsc → dist/ (dev + `npx backthread` path)
398
- npm --prefix cli run bundle # esbuild dist-bundle/backthread.js (self-contained)
63
+ backthread install Set up capture for this repo (sign in + hook + backfill)
64
+ backthread start First-run for the Claude Code plugin (sign in + your next step)
65
+ backthread login Authorize this device (opens your browser)
66
+ backthread whoami Show this device's config (your token is never printed)
67
+ backthread capture Capture a session's decisions (run automatically by the hook)
68
+ backthread mcp Start the MCP server — the capture + "how does X work?" query tools
69
+ backthread help Show usage
399
70
  ```
400
71
 
401
- The root `vitest run` excludes `cli/` (own test runner, like `worker/`).
402
-
403
- ## Two build paths (8A.1 / ARP-474)
72
+ ## Requirements
404
73
 
405
- There are **two** ways to build the bin, for two distribution targets:
74
+ - **Node.js 22.18**
406
75
 
407
- - **`npm run build` (tsc) → `dist/`** — the **dev + npm-package path**. Emits
408
- multi-file `dist/` and resolves the one runtime dep
409
- (`@modelcontextprotocol/sdk`) from `node_modules` at runtime. This is what
410
- `npx backthread` uses (npm installs the dep tree), and what `dist/bin/backthread.js` — the
411
- `package.json` `bin` target — points at.
412
- - **`npm run bundle` (esbuild) → `dist-bundle/backthread.js`** — the **self-contained
413
- distribution path**. Bundles the bin **and** that runtime dep into a single
414
- executable ESM file (`esbuild.config.mjs`: `--bundle --platform=node
415
- --format=esm --target=node22`, tree-shaken so only the `server/mcp.js` +
416
- `server/stdio.js` subpaths of the SDK are inlined). It runs with **no
417
- `npm install`** — drop the one file anywhere with Node 22 and run it.
76
+ ## Learn more
418
77
 
419
- The bundle is what:
78
+ - **Live app** — [backthread.dev](https://backthread.dev)
79
+ - **How your data is handled** — [backthread.dev/security](https://backthread.dev/security)
80
+ - **Source & internals** — [github.com/backthread/backthread](https://github.com/backthread/backthread)
420
81
 
421
- - the **Claude Code plugin (8A.4)** references via
422
- `${CLAUDE_PLUGIN_ROOT}/cli/dist-bundle/backthread.js` (so the plugin ships a runnable
423
- bin without vendoring `node_modules`), and
424
- - a **future standalone binary** (Node SEA / `pkg`) wraps.
82
+ ## License
425
83
 
426
- Both `dist/` and `dist-bundle/` are git-ignored (regenerated from `src/`); both
427
- are listed in `package.json` `files` so they ship via npm/`npm pack`. A
428
- `prepack` script (`build && bundle`) regenerates both from current source
429
- whenever the package is packed, so a tarball never ships stale/empty `dist*`.
430
- The bundle does not replace the tsc build — they coexist for their two targets.
431
- esbuild is the only new devDep.
84
+ [MIT](./LICENSE) © Backthread
@@ -7272,12 +7272,12 @@ function deviceLogin(log) {
7272
7272
  "Headless (--device) login is not available yet.",
7273
7273
  "",
7274
7274
  "The device-code fallback needs a server-side device-authorization endpoint",
7275
- "that ships in a later F7 task. For now, run `backthread login` on a machine with a",
7275
+ "that ships in a later task. For now, run `backthread login` on a machine with a",
7276
7276
  "browser, or mint a token from the web app (Account \u2192 Connected devices) and",
7277
7277
  'place it in ~/.backthread/config.json under "device_token".'
7278
7278
  ].join("\n")
7279
7279
  );
7280
- return { ok: false, message: "--device fallback not implemented yet (F7.1 stub)." };
7280
+ return { ok: false, message: "--device fallback not implemented yet." };
7281
7281
  }
7282
7282
  async function ensureAuth(opts = {}) {
7283
7283
  const env = opts.env ?? process.env;
@@ -7469,7 +7469,7 @@ async function serverInfer(transcript, config2, opts = {}) {
7469
7469
  Authorization: `Bearer ${token}`,
7470
7470
  "Content-Type": "application/json",
7471
7471
  ...versionHeaders()
7472
- // x-backthread-version — server-side compat guard (ARP-479)
7472
+ // x-backthread-version — server-side compat guard
7473
7473
  },
7474
7474
  body: JSON.stringify(body)
7475
7475
  });
@@ -7974,7 +7974,7 @@ async function fetchOnboardingState(input = {}, deps = {}) {
7974
7974
  // never logged
7975
7975
  "Content-Type": "application/json",
7976
7976
  ...versionHeaders()
7977
- // x-backthread-version — server-side compat guard (ARP-479)
7977
+ // x-backthread-version — server-side compat guard
7978
7978
  },
7979
7979
  // CLI shape: repo_slug = "owner/name". Omitted when no repo resolved.
7980
7980
  body: JSON.stringify(repo ? { repo_slug: `${repo.owner}/${repo.name}` } : {})
@@ -8314,10 +8314,10 @@ async function runCapture(input, deps = {}) {
8314
8314
  env,
8315
8315
  fetchImpl: deps.fetchImpl,
8316
8316
  log,
8317
- // Carry the session id so the connect-nudge (F7.10) can throttle once-per-session
8317
+ // Carry the session id so the connect-nudge can throttle once-per-session
8318
8318
  // — the SessionEnd hook fires once, but manual/MCP captures fire many times.
8319
8319
  sessionId,
8320
- // 8B.5 first-capture confirmation seam (threaded so tests can stub it).
8320
+ // first-capture confirmation seam (threaded so tests can stub it).
8321
8321
  firstCaptureConfirmImpl: deps.firstCaptureConfirmImpl
8322
8322
  });
8323
8323
  } catch (e) {
@@ -8346,7 +8346,7 @@ async function persistDerived(decisions, repo, config2, decidedAt, ctx) {
8346
8346
  // device token — never logged
8347
8347
  "Content-Type": "application/json",
8348
8348
  ...versionHeaders()
8349
- // x-backthread-version — server-side compat guard (ARP-479)
8349
+ // x-backthread-version — server-side compat guard
8350
8350
  },
8351
8351
  body: JSON.stringify(body)
8352
8352
  });
@@ -32696,7 +32696,7 @@ async function queryDecisions(input, deps = {}) {
32696
32696
  Authorization: `Bearer ${config2.device_token}`,
32697
32697
  "Content-Type": "application/json",
32698
32698
  ...versionHeaders()
32699
- // x-backthread-version — server-side compat guard (ARP-479)
32699
+ // x-backthread-version — server-side compat guard
32700
32700
  },
32701
32701
  body: JSON.stringify({ repo: { owner: repo.owner, name: repo.name } })
32702
32702
  });
package/hooks/hooks.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$comment": "F7.7 / ARP-438 — the SessionEnd capture hook, declared in the PLUGIN MANIFEST (referenced from .claude-plugin/plugin.json). When Backthread is installed as a Claude Code plugin, this registers the hook automatically — no mutation of the user's .claude/settings.json. `npx backthread capture` reads the SessionEnd payload off stdin, derives this session's decisions LOCALLY-redacted, and persists them best-effort; it always exits 0 so a capture hiccup can never disrupt the session. Mirrored by the .claude/settings.json fallback that `backthread install` writes for the bare-npx (non-plugin) path. We register ONLY SessionEnd (once per session) on purpose — `runCapture` also handles a Stop payload, but Stop fires on every turn-end, which would capture far too aggressively, so Stop is intentionally NOT registered here.",
2
+ "$comment": "The SessionEnd capture hook, declared in the PLUGIN MANIFEST (referenced from .claude-plugin/plugin.json). When Backthread is installed as a Claude Code plugin, this registers the hook automatically — no mutation of the user's .claude/settings.json. `npx backthread capture` reads the SessionEnd payload off stdin, derives this session's decisions LOCALLY-redacted, and persists them best-effort; it always exits 0 so a capture hiccup can never disrupt the session. Mirrored by the .claude/settings.json fallback that `backthread install` writes for the bare-npx (non-plugin) path. We register ONLY SessionEnd (once per session) on purpose — `runCapture` also handles a Stop payload, but Stop fires on every turn-end, which would capture far too aggressively, so Stop is intentionally NOT registered here.",
3
3
  "hooks": {
4
4
  "SessionEnd": [
5
5
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backthread",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Backthread CLI — capture the *why* of your AI-coded changes from your Claude Code sessions, and query your codebase's architectural memory without leaving the terminal. Source code and tool I/O are redacted locally before anything leaves your machine.",
5
5
  "license": "MIT",
6
6
  "author": "Backthread",