backthread 0.1.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.
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "backthread",
3
+ "displayName": "Backthread",
4
+ "description": "Keep the thread on what the agent shipped: Backthread captures the \"why\" of every session and answers \"how does X work?\" on a live \"how it works\" diagram — no diving through PRs. Provides the /backthread:capture slash command, the SessionEnd capture hook, and the backthread MCP server (capture + query).",
5
+ "version": "0.1.0",
6
+ "author": {
7
+ "name": "Backthread"
8
+ },
9
+ "homepage": "https://backthread.dev",
10
+ "hooks": "./hooks/hooks.json"
11
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Backthread
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,431 @@
1
+ # backthread — CLI / plugin
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.
7
+
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.
12
+
13
+ ## Status (F7.1 / ARP-432, F7.2 / ARP-433, F7.3 / ARP-434, F7.4 / ARP-435, F7.7 / ARP-438)
14
+
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.
20
+
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:
188
+
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.
200
+
201
+ ## `backthread install` — onboarding (F7.7 / ARP-438)
202
+
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):
206
+
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.
215
+
216
+ Flags: `--skip-auth`, `--skip-hook`, `--skip-backfill`.
217
+
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
+ }
319
+ ```
320
+
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.
344
+
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
+ ```
356
+
357
+ Always written at `0600` (dir `~/.backthread` at `0700`).
358
+
359
+ ## Environment overrides (dev/testing)
360
+
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).
370
+
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
392
+
393
+ ```
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)
399
+ ```
400
+
401
+ The root `vitest run` excludes `cli/` (own test runner, like `worker/`).
402
+
403
+ ## Two build paths (8A.1 / ARP-474)
404
+
405
+ There are **two** ways to build the bin, for two distribution targets:
406
+
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.
418
+
419
+ The bundle is what:
420
+
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.
425
+
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.
@@ -0,0 +1,29 @@
1
+ ---
2
+ description: Manually capture this Claude Code session's decisions (the "why") into your Backthread "How it works" log. Use mid-session to capture what you just decided, or to re-run a session's capture.
3
+ argument-hint: "[transcript-path]"
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # /backthread:capture — capture this session now
8
+
9
+ Captures this session's DECISIONS (the "why" behind the changes) into your Backthread
10
+ decision log, using the same local-redact → derive → persist pipeline as the
11
+ automatic SessionEnd hook. No source code or tool I/O ever leaves the machine.
12
+
13
+ The capture has already run for this session (its summary is below). It resolves
14
+ the transcript from this session id + working directory; if that failed, the
15
+ summary tells you to re-run with an explicit path:
16
+ `backthread capture --manual --transcript <path>`.
17
+
18
+ ## Capture result
19
+
20
+ !`npx backthread capture --manual --session "${CLAUDE_SESSION_ID}" --cwd "$(pwd)" $ARGUMENTS`
21
+
22
+ ## Your task
23
+
24
+ Relay the capture result above to the user verbatim — the status line and the
25
+ decision count. Do not re-run the capture or invoke any other tool. If the result
26
+ says "not logged in", tell the user to run `backthread login`. If it says nothing was
27
+ captured, that's a normal outcome (the session may have been all code / tool work)
28
+ — just report it. If it reports a number of decisions captured, confirm that and,
29
+ when a "How it works" diagram link is present, surface it.
@@ -0,0 +1,27 @@
1
+ ---
2
+ description: First-run setup for Backthread — shows how your code is handled, authorizes this device, and tells you your next step. Run once after installing; it's idempotent (a returning user is not re-onboarded).
3
+ argument-hint: "[--claim <code>]"
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # /backthread:start — set up Backthread
8
+
9
+ Runs the one-time first-run setup: the never-store-source trust note (how Backthread
10
+ handles your code), a one-tap device authorization, and your next step toward a
11
+ non-empty "How it works" diagram. It is idempotent — if you've already set up
12
+ Backthread on this machine, it just tells you you're good to go.
13
+
14
+ If the web app handed you a claim code, pass it: `/backthread:start --claim <code>`
15
+ (no browser needed). Otherwise it opens your browser once to authorize this device.
16
+
17
+ ## Setup result
18
+
19
+ !`npx backthread start $ARGUMENTS`
20
+
21
+ ## Your task
22
+
23
+ Relay the setup result above to the user verbatim — the trust note, the auth status
24
+ line, and the next-step line (including any link). Do not run any other tool. If the
25
+ result says auth failed, tell the user to run `/backthread:start` again (or `backthread
26
+ login`) to authorize. If it says they're already set up, just confirm that. When a
27
+ "How it works" diagram link or a connect link is present, surface it.