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.
- package/.claude-plugin/plugin.json +11 -0
- package/LICENSE +21 -0
- package/README.md +431 -0
- package/commands/capture.md +29 -0
- package/commands/start.md +27 -0
- package/dist-bundle/backthread.js +33021 -0
- package/hooks/hooks.json +15 -0
- package/package.json +57 -0
|
@@ -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.
|