codex-claude-relay 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 context-relay contributors
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
13
+ all 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
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,534 @@
1
+ # codex-claude-relay
2
+
3
+ Hand off context between **OpenAI Codex CLI** and **Anthropic Claude Code** by reading their native session transcripts. Pick the right past session for the current repo, condense it into one handoff prompt, and launch the other CLI with that prompt as its first message.
4
+
5
+ No database. No daemon. No writes to either tool's session files.
6
+
7
+ 🌐 中文文档: [README.zh.md](./README.zh.md)
8
+
9
+ ```bash
10
+ codex # work for a while in Codex
11
+ relay claude # condense the Codex session, launch `claude` pre-loaded with it
12
+ relay codex # later: condense the Claude session, launch `codex` pre-loaded with it
13
+ ```
14
+
15
+ ## How it works
16
+
17
+ ```
18
+ ┌──────────────────────────────────┐
19
+ │ ~/.codex/sessions/**/ │ ──┐
20
+ │ rollout-*.jsonl │ │
21
+ └──────────────────────────────────┘ │ pick best session for $PWD
22
+ │ (cwd match + recency)
23
+
24
+ ┌──────────────────────┐
25
+ │ stream-parse JSONL │
26
+ │ normalize events │
27
+ │ redact secrets │ ─→ handoff (markdown, ~6–12 KB)
28
+ │ render template │
29
+ └──────────────────────┘
30
+
31
+ ┌──────────────────────────────────┐ │
32
+ │ ~/.claude/projects/<encoded>/ │ ──┘
33
+ │ <session-uuid>.jsonl │
34
+ │ memory/MEMORY.md │
35
+ └──────────────────────────────────┘
36
+
37
+
38
+ spawn `claude` or `codex`
39
+ with the handoff as the
40
+ initial user prompt
41
+ ```
42
+
43
+ ## Why
44
+
45
+ Both major coding agents already write rich transcripts to disk. They just don't read each other's. So when you switch tools mid-task — Codex → Claude or Claude → Codex — you spend five minutes pasting back what you were doing, which files were touched, which commands failed.
46
+
47
+ The usual response is a "memory sync" tool. Those almost always introduce a *third* store (vector DB, JSON cache, `.ai/handoff.md`) that neither native tool consults. Now you have three sources of truth and the new one drifts out of date.
48
+
49
+ codex-claude-relay never stores anything. Every invocation re-reads the native transcripts. The two tools remain the only sources of truth.
50
+
51
+ | Approach | Persistent store | Mutates native files | Reads native transcripts |
52
+ | ------------------------------------------- | :--------------: | :------------------: | :----------------------: |
53
+ | Manual copy-paste | — | no | — |
54
+ | Vector-DB memory layer | yes | no | sometimes |
55
+ | `.ai/handoff.md` checked into repo | yes | no | no |
56
+ | **codex-claude-relay** | no | no | yes |
57
+
58
+ ## Install
59
+
60
+ Requires Node.js 20+ (check with `node --version`).
61
+
62
+ ### Option A — clone, build, link (recommended)
63
+
64
+ `npm install` only fetches the two devDependencies (`typescript`, `tsx`). `npm run build` produces `dist/cli.js`. **`npm link` is the separate step that puts the `relay` command on your `PATH`.** Skipping it is the most common reason for `command not found: relay`.
65
+
66
+ ```bash
67
+ git clone https://github.com/Picrew/codex-claude-relay
68
+ cd codex-claude-relay
69
+ npm install # fetch devDeps (typescript, tsx)
70
+ npm run build # compile src/ → dist/
71
+ npm link # register `relay` and `codex-claude-relay` globally
72
+ ```
73
+
74
+ Verify:
75
+
76
+ ```bash
77
+ which relay # should print a path under your npm global prefix
78
+ relay --version # should print 0.1.0
79
+ relay inspect # should list discovered sessions
80
+ ```
81
+
82
+ If `npm link` reports a permission error, your global `npm` prefix isn't user-writable. Either fix the prefix (`npm config set prefix "$HOME/.npm-global"` and add `$HOME/.npm-global/bin` to `PATH`) or use Option B.
83
+
84
+ ### Option B — no link, run via node
85
+
86
+ If you don't want to link globally:
87
+
88
+ ```bash
89
+ node /absolute/path/to/codex-claude-relay/dist/cli.js inspect
90
+ ```
91
+
92
+ A shell alias is the lightest workaround:
93
+
94
+ ```bash
95
+ echo 'alias relay="node $HOME/path/to/codex-claude-relay/dist/cli.js"' >> ~/.zshrc
96
+ source ~/.zshrc
97
+ ```
98
+
99
+ ### Option C — install globally from a tarball
100
+
101
+ If the package is published (it currently isn't, but the layout supports it):
102
+
103
+ ```bash
104
+ npm install -g codex-claude-relay
105
+ ```
106
+
107
+ ### Requirements for actually launching agents
108
+
109
+ `claude` and `codex` must be on `PATH` for `relay claude` and `relay codex` to spawn them:
110
+
111
+ ```bash
112
+ which claude codex
113
+ ```
114
+
115
+ If either is missing, install it from its vendor — `relay preview`, `relay inspect`, and `--dry-run` still work without them.
116
+
117
+ ### Uninstall
118
+
119
+ ```bash
120
+ cd codex-claude-relay
121
+ npm unlink -g # remove the global symlink
122
+ ```
123
+
124
+ ## Commands
125
+
126
+ | Command | Effect |
127
+ | ------------------------ | --------------------------------------------------------------------- |
128
+ | `relay claude` | Build handoff from latest relevant Codex session → launch `claude` |
129
+ | `relay codex` | Build handoff from latest relevant Claude session → launch `codex` |
130
+ | `relay preview <target>` | Print the handoff that would be sent; don't launch |
131
+ | `relay inspect` | Show what would be picked, with scores and reasons |
132
+
133
+ ### Flags
134
+
135
+ ```
136
+ --last Use the most recently modified session, skipping the
137
+ cwd-based ranking. Useful when cwd doesn't match.
138
+ --with-diff Append the current `git diff HEAD` to the handoff.
139
+ --max-chars N Cap the rendered handoff length (default 12000).
140
+ --dry-run Build the handoff and print it; do not launch.
141
+ --no-redact Disable secret redaction. Default is ON.
142
+ --debug Verbose discovery / parsing info on stderr.
143
+ ```
144
+
145
+ ## Walkthrough
146
+
147
+ The two main flows are *Claude Code → Codex* and *Codex → Claude Code*. They are symmetric; pick the one that matches the direction you're switching.
148
+
149
+ ### Scenario A: Claude Code → Codex
150
+
151
+ You've been working in Claude Code, you want Codex to continue.
152
+
153
+ **Step 1 — Leave Claude Code (or just open a new terminal).**
154
+
155
+ You don't need to exit cleanly. Claude Code flushes its JSONL transcript as you go, so even mid-conversation the latest events are on disk. Either type `/exit` inside Claude Code, or open a new terminal tab (`Cmd+T` in iTerm / Terminal) and keep Claude running in the background — both work.
156
+
157
+ > ⚠ Do **not** type `relay codex` into the Claude Code prompt itself. The Claude REPL will treat it as a message to the model, not a shell command. Always run `relay` from a regular shell.
158
+
159
+ **Step 2 — `cd` into the repo.**
160
+
161
+ ```bash
162
+ cd /path/to/your/repo
163
+ ```
164
+
165
+ The current directory matters: context-relay uses it (plus `git rev-parse --show-toplevel`) to pick the right past session.
166
+
167
+ **Step 3 — Sanity check what would be sent.**
168
+
169
+ ```bash
170
+ relay preview codex --max-chars 8000 | less
171
+ ```
172
+
173
+ Scroll through and verify:
174
+
175
+ - **Original task** — does the first paragraph match what you originally asked Claude to do?
176
+ - **Subsequent user instructions** — are your follow-up messages there?
177
+ - **Files touched or inspected** — is the list reasonable?
178
+ - **Recent conversation tail** — does it cover the last few exchanges?
179
+
180
+ If the original task is truncated and you want more of it, raise `--max-chars` (e.g. `--max-chars 16000`).
181
+
182
+ If `relay inspect` was already showing the right session at score ≥ 90, you can usually skip this step. Press `q` to exit `less`.
183
+
184
+ **Step 4 — Launch Codex with the handoff.**
185
+
186
+ ```bash
187
+ relay codex
188
+ ```
189
+
190
+ You'll see stderr print `codex-claude-relay: launching \`codex\` with handoff (N chars)`, then Codex's TUI opens. The handoff arrives as Codex's first user message:
191
+
192
+ - If the handoff is ≤ 8 KB, it's passed inline as the first message.
193
+ - If it's > 8 KB, context-relay writes it to a `0600` temp file (under `$TMPDIR`) and passes a short reference prompt: *"Read the handoff context file at … and continue."* Codex calls its file-read tool, reads the handoff, and proceeds. The temp file is deleted when Codex exits.
194
+
195
+ Either way, Codex's first response will acknowledge the context (~10 s round-trip). After that you continue typing as usual.
196
+
197
+ **Step 5 — Verify the handoff actually landed.**
198
+
199
+ Once Codex finishes its first reply, ask it something only the prior session would know:
200
+
201
+ ```
202
+ 我之前在 Claude 里问的第一个问题是什么?逐字告诉我。
203
+ 列一下 Claude 都动过哪些文件,跑过哪些命令。
204
+ ```
205
+
206
+ Or, in English:
207
+
208
+ ```
209
+ Quote my original first question to the previous agent verbatim.
210
+ List every file the previous agent touched and the commands it ran.
211
+ ```
212
+
213
+ If Codex can answer accurately, the handoff is working. Continue your work.
214
+
215
+ ### Scenario B: Codex → Claude Code
216
+
217
+ Symmetric to Scenario A. You've been working in Codex, you want Claude Code to continue.
218
+
219
+ **Step 1 — Leave Codex (or open a new terminal).** Codex also flushes its rollout JSONL continuously.
220
+
221
+ **Step 2 — `cd` into the repo.**
222
+
223
+ ```bash
224
+ cd /path/to/your/repo
225
+ ```
226
+
227
+ **Step 3 — Preview.**
228
+
229
+ ```bash
230
+ relay preview claude --max-chars 8000 | less
231
+ ```
232
+
233
+ If Codex worked in a worktree or under a subdir, the cwd recorded in the session might not match your current git root. `relay inspect` will show the score; if it's low (< 60), try `--last`:
234
+
235
+ ```bash
236
+ relay preview claude --last
237
+ ```
238
+
239
+ **Step 4 — Launch.**
240
+
241
+ ```bash
242
+ relay claude
243
+ ```
244
+
245
+ Claude Code's TUI opens. The handoff becomes its first user message (or temp-file ref for handoffs > 8 KB).
246
+
247
+ **Step 5 — Verify.**
248
+
249
+ ```
250
+ Quote my original first question to the previous Codex session verbatim.
251
+ Summarize what Codex got working and what's still open.
252
+ ```
253
+
254
+ ### Side-by-side mode (don't actually "switch", run both)
255
+
256
+ You don't have to close one to use the other. Open two terminal tabs:
257
+
258
+ | Tab 1 | Tab 2 |
259
+ | ---------------------------- | ------------------------------ |
260
+ | `codex` (or `claude`) keeps running | `cd repo && relay claude` (or `relay codex`) |
261
+
262
+ Both agents now have the same repo state plus the same prior context. Useful when you want a second opinion on the same problem without losing your original session.
263
+
264
+ ### Useful flags in practice
265
+
266
+ ```bash
267
+ # Cwd doesn't match the recorded one (worktree, moved repo, symlink, etc.)
268
+ relay codex --last
269
+
270
+ # Receiving agent should see your uncommitted work too
271
+ relay codex --with-diff
272
+
273
+ # Bigger handoff — more original task + more conversation tail
274
+ relay codex --max-chars 20000
275
+
276
+ # See what would happen without launching
277
+ relay codex --dry-run
278
+
279
+ # Diagnose which session was picked and why
280
+ relay codex --debug --dry-run
281
+
282
+ # Trust the transcript, skip redaction (rare)
283
+ relay codex --no-redact
284
+ ```
285
+
286
+ ### What does *not* work
287
+
288
+ - Running `relay` inside the source agent's REPL itself. Always run from a regular shell.
289
+ - Switching between repos in one command. `relay` operates on the **current** git root only.
290
+ - Cross-machine handoff. Transcripts are local. Copy the JSONL across by hand if you really need to.
291
+ - Forging a fake "previous session" so the receiving agent thinks it's literally resuming. The handoff is structured context, not a session import — see [FAQ](#faq).
292
+
293
+ ## Example: `relay inspect`
294
+
295
+ ```
296
+ $ cd ~/work/my-project
297
+ $ relay inspect
298
+ codex-claude-relay v0.1.0 inspect
299
+
300
+ Git context:
301
+ cwd: /Users/alice/work/my-project
302
+ inRepo: true
303
+ root: /Users/alice/work/my-project
304
+ branch: main
305
+
306
+ Codex sessions (~/.codex/sessions):
307
+ dir exists: true
308
+ count: 137
309
+ best: ~/.codex/sessions/2026/05/14/rollout-2026-05-14T09-15-22-…jsonl
310
+ score=88.7 mtime=2026-05-14T01:22:08.142Z
311
+ cwd=/Users/alice/work/my-project
312
+ reasons: cwd matches git root exactly | recency +28.7 (age 0.4d)
313
+
314
+ Claude Code sessions (~/.claude/projects):
315
+ dir exists: true
316
+ count: 42
317
+ best: ~/.claude/projects/-Users-alice-work-my-project/8c3f….jsonl
318
+ score=130.0 mtime=2026-05-19T14:10:01.000Z
319
+ cwd=/Users/alice/work/my-project
320
+ reasons: inside encoded project dir | cwd matches git root exactly | recency +30.0 (age 0.0d)
321
+
322
+ Claude memory for this project:
323
+ exists: false
324
+ bytes: 0
325
+
326
+ Binaries on PATH:
327
+ claude: yes
328
+ codex: yes
329
+ ```
330
+
331
+ ## Example: what's actually in the handoff
332
+
333
+ ```
334
+ You are continuing work in this repository after a context handoff from
335
+ OpenAI Codex CLI to Anthropic Claude Code.
336
+
337
+ Repository:
338
+ - Path: /Users/alice/work/my-project
339
+ - Branch: main
340
+ - Git status summary:
341
+ M src/server.ts
342
+
343
+ Original task:
344
+ Add rate limiting to the /api/upload endpoint.
345
+
346
+ Subsequent user instructions:
347
+ - use redis, not in-memory
348
+ - ignore /health pings
349
+
350
+ What has already been done / key decisions:
351
+ - Implemented sliding-window limiter in src/middleware/rate.ts
352
+ - Chose redis pipelining over a Lua script for simpler ops
353
+
354
+ Files touched or inspected:
355
+ - src/middleware/rate.ts
356
+ - src/server.ts
357
+ - test/rate.test.ts
358
+
359
+ Commands run (★ = errored):
360
+ - `npm test -- rate`
361
+ - ★ `redis-cli ping`
362
+
363
+ Errors observed:
364
+ - redis-cli ping: Could not connect to Redis at 127.0.0.1:6379
365
+
366
+ Recent conversation tail:
367
+ - User: it should also rate-limit anonymous IPs
368
+ - Assistant: Added a fallback bucket keyed by IP for unauthenticated calls.
369
+
370
+ Safety notes for you, the receiving agent:
371
+ - Do not assume the prior agent's conclusions are still correct.
372
+ - Re-check current files and `git status` / `git diff` before editing.
373
+ - Prefer the live repository state over anything implied by this transcript.
374
+ ```
375
+
376
+ ## Session discovery & ranking
377
+
378
+ Both providers walk the canonical directory recursively, peek each file's metadata cheaply, then rank:
379
+
380
+ ```
381
+ score = cwd_match_signal + recency_decay
382
+ ```
383
+
384
+ | Signal | Codex weight | Claude weight |
385
+ | ------------------------------------- | :----------: | :-----------: |
386
+ | Recorded cwd equals git root | +60 | +60 |
387
+ | Recorded cwd inside git root | +50 | +50 |
388
+ | Recorded cwd mentions repo name | +25 | +20 |
389
+ | Inside Claude's encoded project dir | — | +40 |
390
+ | Recency: linear decay 0 → 14 days | +0 … +30 | +0 … +30 |
391
+
392
+ Pass `--last` to skip ranking and force the most-recent-by-mtime file. Pass `--debug` to see the reasons string for the chosen candidate.
393
+
394
+ ## Native transcript formats
395
+
396
+ codex-claude-relay parses the formats the official CLIs already write. It does not invent any file shape.
397
+
398
+ **Codex CLI** — one JSONL per session, one line per event:
399
+
400
+ ```
401
+ ~/.codex/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl
402
+ ```
403
+
404
+ Each line is `{ "type", "payload", "timestamp" }`. Useful payload types:
405
+
406
+ | `payload.type` | What it carries |
407
+ | ------------------------ | ---------------------------------------------- |
408
+ | `session_meta` | `cwd`, `id`, originator, model |
409
+ | `message` (role=user) | The user's message turn (`content[].input_text`) |
410
+ | `message` (role=assistant) | The model's message turn (`content[].output_text`) |
411
+ | `function_call` | Tool invocation (`name`, JSON-string `arguments`) |
412
+ | `function_call_output` | Tool result (`output` is the captured text) |
413
+
414
+ **Claude Code** — one JSONL per session, grouped per project:
415
+
416
+ ```
417
+ ~/.claude/projects/<encoded-cwd>/<session-uuid>.jsonl
418
+ ```
419
+
420
+ `<encoded-cwd>` is the absolute project path with `/` and `.` replaced by `-`. Each useful line:
421
+
422
+ | `type` | Carries |
423
+ | ---------- | -------------------------------------------------------------------- |
424
+ | `user` | User turn or `tool_result` block(s); also `cwd`, `gitBranch` |
425
+ | `assistant`| Model turn: text + `tool_use` blocks (`name`, `input`) |
426
+
427
+ Tool calls live inside `message.content[]` as `{ type: "tool_use" }`. Their results land inside the next `user` line as `{ type: "tool_result" }`.
428
+
429
+ If `~/.claude/projects/<encoded-cwd>/memory/` exists, its `MEMORY.md` and the `.md` files it links are included when generating the Claude → Codex handoff.
430
+
431
+ ## What gets filtered out
432
+
433
+ To keep the handoff readable, the parser drops:
434
+
435
+ - Codex `reasoning` events (the model's private chain-of-thought)
436
+ - Codex `event_msg` / `turn_context` / `token_count` framing
437
+ - Codex environment-context user messages (`<environment_context>…`)
438
+ - Claude `<task-notification>`, `<system-reminder>`, lone `[Image: source: …]` markers, slash-command framing
439
+ - Claude sidechain messages (`isSidechain: true`)
440
+ - Anything matching the secret-redaction rules (see below)
441
+
442
+ Tool outputs are clipped to ~400 chars per event so a single noisy `cat large-file` doesn't crowd out everything else.
443
+
444
+ ## Safety
445
+
446
+ | Concern | What codex-claude-relay does |
447
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------ |
448
+ | Native files | Opened read-only. Never written to `~/.codex/sessions/` or `~/.claude/projects/`. |
449
+ | Secrets in transcripts | Redacted by default. See list below. |
450
+ | Process-list / argv leakage | Handoffs > 8 KB are written to a `0600` temp file in a per-invocation `0700` dir; only a short ref prompt goes in `argv`. The temp file is unlinked when the child exits. |
451
+ | Shell interpolation | `spawn(..., { shell: false })`. The handoff is never interpreted by a shell. |
452
+ | Stale data | If the source session is > 24 h old, the handoff includes a `⚠ stale` notice. |
453
+
454
+ Redaction covers (case-insensitive where applicable):
455
+
456
+ - OpenAI keys: `sk-…`, `sk-proj-…`, `sk-ant-…`
457
+ - GitHub tokens: `ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_`
458
+ - AWS access key IDs (`AKIA…`)
459
+ - Google API keys (`AIza…`)
460
+ - JWTs (three base64url segments separated by dots)
461
+ - PEM private key blocks (`-----BEGIN … PRIVATE KEY-----` … `-----END …-----`)
462
+ - `Authorization:` and `Set-Cookie:` headers
463
+ - Env-var style `*SECRET*=`, `*TOKEN*=`, `*PASSWORD*=`, `*API_KEY*=`, `*CREDENTIAL*=` with ≥ 6-char values
464
+
465
+ Pass `--no-redact` only when you trust the transcript.
466
+
467
+ ## Limitations
468
+
469
+ | Situation | What happens |
470
+ | ---------------------------------------------------- | ----------------------------------------------------------------------- |
471
+ | Transcripts deleted or disabled | Nothing to read; `relay inspect` reports `count=0`. |
472
+ | Two repos with the same basename | Cwd-exact-match (+60) still wins; otherwise check `--debug`. |
473
+ | Same project across multiple Claude sessions | Highest cwd-score + most recent wins; pin with `--last` if you need to. |
474
+ | Codex/Claude transcript schema changes upstream | Parser skips malformed lines and reports the count in `--debug`. |
475
+ | Cross-machine handoff | Not supported. Transcripts are local-only. |
476
+ | Huge tool outputs | Clipped to ~400 chars per event in the handoff. |
477
+ | Codex/Claude rotate or compact session files | Once the file is gone, so is the context source. |
478
+
479
+ ## FAQ
480
+
481
+ **Does Claude actually resume a Codex session?**
482
+ No. Claude starts a fresh native session and reads the handoff as its first user message. The handoff is structured for an agent to consume but it is not a literal session import.
483
+
484
+ **Does codex-claude-relay write anywhere on disk?**
485
+ Only the optional temp file used when the prompt exceeds the inline-argv limit (~8 KB). It lives under `$TMPDIR` and is unlinked when the child exits.
486
+
487
+ **Can I save the handoff into my repo if I want to?**
488
+ Sure — pipe it: `relay preview codex > .ai/handoff.md`. codex-claude-relay won't do this by default because the point is to stay stateless.
489
+
490
+ **Why not always include `git diff`?**
491
+ Most sessions already touched files you've committed; the diff would be noise. Pass `--with-diff` when uncommitted work actually matters.
492
+
493
+ **Why one big prompt, not a folder of fragments?**
494
+ The receiving agent reads one prompt at the start of its turn. Splitting just forces it to re-concatenate.
495
+
496
+ **Can I run `relay` from inside Codex or Claude?**
497
+ You can, but it'll generate a handoff for the *current* session it's reading — usually not what you want. The intended use is in a separate shell, after you step out of the agent.
498
+
499
+ **Why does ranking depend on cwd, not on content?**
500
+ Cheap and accurate enough in practice. Content-based ranking would need to read every transcript fully on every invocation. cwd-match + recency picks the right session for the current repo in milliseconds.
501
+
502
+ ## Development
503
+
504
+ ```bash
505
+ npm install
506
+ npm run typecheck # strict TS, zero suppressions
507
+ npm test # node --test on the 17 unit tests
508
+ npm run build # tsc → dist/
509
+ node dist/cli.js inspect
510
+ ```
511
+
512
+ Project layout:
513
+
514
+ ```
515
+ src/
516
+ cli.ts # arg parsing, command dispatch
517
+ index.ts # programmatic exports
518
+ types.ts # shared types only — no runtime
519
+ git.ts # git rev-parse / branch / diff
520
+ parse/jsonl.ts # streaming JSONL reader + helpers
521
+ providers/
522
+ codex.ts # ~/.codex/sessions discovery + parsing
523
+ claude.ts # ~/.claude/projects discovery + parsing + memory
524
+ redact.ts # secret patterns
525
+ summarize.ts # event digest + handoff template
526
+ launch.ts # child_process spawn + temp-file fallback
527
+ test/ # node:test unit tests
528
+ ```
529
+
530
+ Dependencies: only `typescript` and `tsx` as devDependencies. Zero runtime dependencies — the CLI uses Node built-ins exclusively.
531
+
532
+ ## License
533
+
534
+ MIT. See [LICENSE](./LICENSE).