elliot-stack 1.0.18 → 1.0.20

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.
Files changed (50) hide show
  1. package/README.md +11 -0
  2. package/bin/install.cjs +134 -49
  3. package/package.json +1 -1
  4. package/skills/estack-read-claude-session-history/SKILL.md +203 -0
  5. package/skills/estack-read-claude-session-history/references/jsonl-schema.md +126 -0
  6. package/skills/estack-read-claude-session-history/references/modes.md +423 -0
  7. package/skills/estack-read-claude-session-history/references/recipes.md +271 -0
  8. package/skills/estack-read-claude-session-history/scripts/lib/__init__.py +1 -0
  9. package/skills/estack-read-claude-session-history/scripts/lib/parser.py +460 -0
  10. package/skills/estack-read-claude-session-history/scripts/lib/paths.py +234 -0
  11. package/skills/estack-read-claude-session-history/scripts/lib/search.py +179 -0
  12. package/skills/estack-read-claude-session-history/scripts/lib/subagents.py +88 -0
  13. package/skills/estack-read-claude-session-history/scripts/lib/tools.py +144 -0
  14. package/skills/estack-read-claude-session-history/scripts/read_transcript.py +1776 -0
  15. package/skills/estack-read-claude-session-history/scripts/tests/conftest.py +40 -0
  16. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/README.md +20 -0
  17. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/all-noise.jsonl +4 -0
  18. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/basic-session.jsonl +2 -0
  19. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-gaps.jsonl +9 -0
  20. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-noise.jsonl +7 -0
  21. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-parallel-a.jsonl +3 -0
  22. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-parallel-b.jsonl +3 -0
  23. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-waiting.jsonl +5 -0
  24. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/interrupted.jsonl +2 -0
  25. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/multi-compact.jsonl +8 -0
  26. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/pending-user.jsonl +2 -0
  27. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-no-meta/subagents/agent-aaa.jsonl +2 -0
  28. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-no-meta.jsonl +2 -0
  29. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent/subagents/agent-xyz123.jsonl +2 -0
  30. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent/subagents/agent-xyz123.meta.json +1 -0
  31. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent.jsonl +4 -0
  32. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/time-spread.jsonl +6 -0
  33. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/timeline-day-test.jsonl +5 -0
  34. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/tool-zoo.jsonl +10 -0
  35. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/truncated.jsonl +3 -0
  36. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/unicode.jsonl +2 -0
  37. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-advisor.jsonl +3 -0
  38. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-compact.jsonl +5 -0
  39. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-thinking.jsonl +2 -0
  40. package/skills/estack-read-claude-session-history/scripts/tests/test_backup_roots.py +56 -0
  41. package/skills/estack-read-claude-session-history/scripts/tests/test_engagement.py +239 -0
  42. package/skills/estack-read-claude-session-history/scripts/tests/test_json_format.py +201 -0
  43. package/skills/estack-read-claude-session-history/scripts/tests/test_modes.py +199 -0
  44. package/skills/estack-read-claude-session-history/scripts/tests/test_parser.py +195 -0
  45. package/skills/estack-read-claude-session-history/scripts/tests/test_paths.py +133 -0
  46. package/skills/estack-read-claude-session-history/scripts/tests/test_search.py +78 -0
  47. package/skills/estack-read-claude-session-history/scripts/tests/test_subagents.py +43 -0
  48. package/skills/estack-read-claude-session-history/scripts/tests/test_timeline.py +179 -0
  49. package/skills/estack-read-claude-session-history/scripts/tests/test_timezone_and_project.py +212 -0
  50. package/skills/estack-read-claude-session-history/scripts/tests/test_tools.py +80 -0
package/README.md CHANGED
@@ -56,6 +56,17 @@ npx elliot-stack@latest
56
56
 
57
57
  External contributions are welcome via pull request. Direct pushes to `main` are blocked — fork the repo, push your changes to a branch, and open a PR. Only the maintainer (Elliot) can merge to `main` and cut releases. This is intentional: `elliot-stack` is a security-sensitive npm package and the release tag can only be pushed by the maintainer.
58
58
 
59
+ ### Testing locally
60
+
61
+ Run the installer straight from your checkout to preview what a real install would do to your `~/.claude/`:
62
+
63
+ ```bash
64
+ node bin/install.cjs # dry run — previews changes, writes nothing
65
+ node bin/install.cjs --install # actually sync your local edits to ~/.claude/skills/
66
+ ```
67
+
68
+ Run from the repo, the installer **dry-runs by default** so testing never clobbers your live `~/.claude/skills/` install. Pass `--install` once the preview looks right. (`--dry-run` forces a preview even under `npx`.)
69
+
59
70
  See [`docs/publishing.md`](docs/publishing.md) for the release flow and security model.
60
71
 
61
72
  ## License
package/bin/install.cjs CHANGED
@@ -21,6 +21,19 @@ const PACKAGE_HOOKS_DIR = path.join(__dirname, '..', 'hooks');
21
21
  // ── Flags ──────────────────────────────────────────────────────────────────
22
22
  const SILENT = process.argv.includes('--silent');
23
23
  const STARTUP = process.argv.includes('--startup');
24
+ // When run directly from the repo (not via npx/node_modules), default to dry-run
25
+ // so local testing never silently clobbers the live ~/.claude/skills install.
26
+ // Pass --install to actually write files, or --dry-run to force preview mode.
27
+ const IS_LOCAL = !__dirname.includes('node_modules');
28
+ const DRY_RUN = process.argv.includes('--dry-run') ||
29
+ (IS_LOCAL && !process.argv.includes('--install'));
30
+
31
+ // ── Deprecated skills ──────────────────────────────────────────────────────
32
+ // Skills that were renamed or removed. The installer removes these on every
33
+ // run so users don't end up with both the old and new name installed.
34
+ const DEPRECATED_SKILLS = [
35
+ 'estack-prompt-builder', // renamed to estack-prompt-builder-coach
36
+ ];
24
37
 
25
38
  // ── Helpers ────────────────────────────────────────────────────────────────
26
39
 
@@ -44,7 +57,8 @@ function walkDir(dir, base) {
44
57
  function computeFileHash(filePath) {
45
58
  if (!fs.existsSync(filePath)) return null;
46
59
  const hash = crypto.createHash('sha256');
47
- hash.update(fs.readFileSync(filePath));
60
+ const raw = fs.readFileSync(filePath);
61
+ hash.update(Buffer.from(raw.toString('utf8').replace(/\r\n/g, '\n')));
48
62
  return hash.digest('hex');
49
63
  }
50
64
 
@@ -54,9 +68,9 @@ function computeSkillHash(skillDir) {
54
68
  const files = walkDir(skillDir, skillDir);
55
69
  for (const relPath of files) {
56
70
  const fullPath = path.join(skillDir, relPath);
57
- const contents = fs.readFileSync(fullPath);
71
+ const raw = fs.readFileSync(fullPath);
58
72
  hash.update(relPath.replace(/\\/g, '/'));
59
- hash.update(contents);
73
+ hash.update(Buffer.from(raw.toString('utf8').replace(/\r\n/g, '\n')));
60
74
  }
61
75
  return hash.digest('hex');
62
76
  }
@@ -137,7 +151,9 @@ function getSkillDescription(skillDir) {
137
151
 
138
152
  // ── Hook setup ─────────────────────────────────────────────────────────────
139
153
 
140
- function setupStartupHook() {
154
+ // Returns true if the hook was added (or would be added in dryRun), false if
155
+ // it was already configured. In dryRun mode nothing is written to disk.
156
+ function setupStartupHook(dryRun) {
141
157
  let settings = {};
142
158
  if (fs.existsSync(SETTINGS_FILE)) {
143
159
  try {
@@ -160,6 +176,8 @@ function setupStartupHook() {
160
176
  }
161
177
  }
162
178
 
179
+ if (dryRun) return true;
180
+
163
181
  if (!settings.hooks) settings.hooks = {};
164
182
  if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
165
183
 
@@ -181,7 +199,9 @@ function setupStartupHook() {
181
199
  return true;
182
200
  }
183
201
 
184
- function setupRepoSearchNudgeHook() {
202
+ // Returns true if the hook was added (or would be added in dryRun), false if
203
+ // it was already configured. In dryRun mode nothing is written to disk.
204
+ function setupRepoSearchNudgeHook(dryRun) {
185
205
  let settings = {};
186
206
  if (fs.existsSync(SETTINGS_FILE)) {
187
207
  try {
@@ -203,6 +223,8 @@ function setupRepoSearchNudgeHook() {
203
223
  }
204
224
  }
205
225
 
226
+ if (dryRun) return true;
227
+
206
228
  if (!settings.hooks) settings.hooks = {};
207
229
  if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
208
230
 
@@ -223,6 +245,29 @@ function setupRepoSearchNudgeHook() {
223
245
  // ── Main ────────────────────────────────────────────────────────────────────
224
246
 
225
247
  async function main() {
248
+ // 0. Remove deprecated skills (renamed/deleted from the package)
249
+ if (fs.existsSync(SKILLS_DIR)) {
250
+ const newChecksums0 = fs.existsSync(CHECKSUMS_FILE)
251
+ ? (() => { try { return JSON.parse(fs.readFileSync(CHECKSUMS_FILE, 'utf8')); } catch (_) { return {}; } })()
252
+ : {};
253
+ let changed = false;
254
+ for (const name of DEPRECATED_SKILLS) {
255
+ const dir = path.join(SKILLS_DIR, name);
256
+ if (fs.existsSync(dir)) {
257
+ if (!DRY_RUN) fs.rmSync(dir, { recursive: true, force: true });
258
+ delete newChecksums0[name];
259
+ changed = true;
260
+ if (!SILENT && !STARTUP) {
261
+ console.log((DRY_RUN ? ' [dry run] Would remove deprecated skill: ' : ' Removed deprecated skill: ') + name);
262
+ }
263
+ } else if (newChecksums0[name]) {
264
+ delete newChecksums0[name];
265
+ changed = true;
266
+ }
267
+ }
268
+ if (changed && !DRY_RUN) fs.writeFileSync(CHECKSUMS_FILE, JSON.stringify(newChecksums0, null, 2));
269
+ }
270
+
226
271
  // 1. Scan package skills
227
272
  if (!fs.existsSync(PACKAGE_SKILLS_DIR)) {
228
273
  if (!SILENT && !STARTUP) {
@@ -287,7 +332,7 @@ async function main() {
287
332
  } else if (currentHash !== storedChecksums[name]) {
288
333
  // Stored checksum exists but current doesn't match — user modified it
289
334
  modifiedSkills.push(name);
290
- if (storedChecksums[name] !== packageHashes[name]) {
335
+ if (currentHash !== packageHashes[name]) {
291
336
  needsUpdate.push(name);
292
337
  }
293
338
  } else if (currentHash !== packageHashes[name]) {
@@ -467,32 +512,39 @@ async function main() {
467
512
  console.log(' - ' + filename);
468
513
  }
469
514
  }
470
- console.log('\nChoose an action:');
471
- console.log(' [o] Overwrite all (replace with latest)');
472
- console.log(' [s] Skip all (keep local versions)');
473
- console.log(' [m] Merge (backup local, install new, merge in Claude Code)');
474
- console.log(' [a] Abort (cancel installation)');
475
- console.log('');
476
515
 
477
- const answer = await promptChar('Your choice (o/s/m/a): ');
478
-
479
- if (answer === 'a') {
480
- console.log('Installation aborted.');
481
- process.exit(0);
482
- } else if (answer === 's') {
483
- modifiedAction = 'skip';
484
- } else if (answer === 'm') {
485
- modifiedAction = 'merge';
486
- } else if (answer === 'o') {
516
+ if (DRY_RUN) {
517
+ console.log('\n[dry run] Would prompt: overwrite / skip / merge / abort');
518
+ console.log('[dry run] Showing what would happen with default overwrite...');
487
519
  modifiedAction = 'overwrite';
488
520
  } else {
489
- console.log('Invalid choice. Installation aborted.');
490
- process.exit(1);
521
+ console.log('\nChoose an action:');
522
+ console.log(' [o] Overwrite all (replace with latest)');
523
+ console.log(' [s] Skip all (keep local versions)');
524
+ console.log(' [m] Merge (backup local, install new, merge in Claude Code)');
525
+ console.log(' [a] Abort (cancel installation)');
526
+ console.log('');
527
+
528
+ const answer = await promptChar('Your choice (o/s/m/a): ');
529
+
530
+ if (answer === 'a') {
531
+ console.log('Installation aborted.');
532
+ process.exit(0);
533
+ } else if (answer === 's') {
534
+ modifiedAction = 'skip';
535
+ } else if (answer === 'm') {
536
+ modifiedAction = 'merge';
537
+ } else if (answer === 'o') {
538
+ modifiedAction = 'overwrite';
539
+ } else {
540
+ console.log('Invalid choice. Installation aborted.');
541
+ process.exit(1);
542
+ }
491
543
  }
492
544
  }
493
545
 
494
546
  // 8. Install skills
495
- fs.mkdirSync(SKILLS_DIR, { recursive: true });
547
+ if (!DRY_RUN) fs.mkdirSync(SKILLS_DIR, { recursive: true });
496
548
  const newChecksums = Object.assign({}, storedChecksums);
497
549
  let installedCount = 0;
498
550
  const mergedSkills = [];
@@ -506,23 +558,29 @@ async function main() {
506
558
  continue;
507
559
  }
508
560
  if (modifiedAction === 'merge') {
509
- backupSkill(name);
561
+ if (!DRY_RUN) backupSkill(name);
510
562
  mergedSkills.push(name);
511
- console.log(' Backed up ' + name + ' → ~/.claude/.estack-backup/' + name);
563
+ console.log((DRY_RUN ? ' [dry run] Would back up ' : ' Backed up ') + name + ' → ~/.claude/.estack-backup/' + name);
512
564
  }
513
565
  // overwrite or merge — fall through to install
514
566
  } else if (!needsUpdate.includes(name) && fs.existsSync(path.join(SKILLS_DIR, name))) {
515
567
  // Already installed and up-to-date
568
+ if (DRY_RUN) console.log(' [dry run] Up to date (no change): ' + name);
516
569
  continue;
517
570
  }
518
- copyDir(path.join(PACKAGE_SKILLS_DIR, name), path.join(SKILLS_DIR, name));
571
+ const isUpdate = fs.existsSync(path.join(SKILLS_DIR, name));
572
+ if (!DRY_RUN) copyDir(path.join(PACKAGE_SKILLS_DIR, name), path.join(SKILLS_DIR, name));
519
573
  newChecksums[name] = packageHashes[name];
520
574
  installedCount++;
521
- console.log(' Installed ' + name);
575
+ if (DRY_RUN) {
576
+ console.log(' [dry run] Would ' + (isUpdate ? 'update ' : 'install ') + name);
577
+ } else {
578
+ console.log(' Installed ' + name);
579
+ }
522
580
  }
523
581
 
524
582
  // 8b. Install hooks
525
- fs.mkdirSync(HOOKS_DIR, { recursive: true });
583
+ if (!DRY_RUN) fs.mkdirSync(HOOKS_DIR, { recursive: true });
526
584
  let installedHookCount = 0;
527
585
  const mergedHooks = [];
528
586
 
@@ -535,33 +593,48 @@ async function main() {
535
593
  continue;
536
594
  }
537
595
  if (modifiedAction === 'merge') {
538
- backupHook(filename);
596
+ if (!DRY_RUN) backupHook(filename);
539
597
  mergedHooks.push(filename);
540
- console.log(' Backed up hook ' + filename + ' → ~/.claude/.estack-backup/hooks/' + filename);
598
+ console.log((DRY_RUN ? ' [dry run] Would back up hook ' : ' Backed up hook ') + filename + ' → ~/.claude/.estack-backup/hooks/' + filename);
541
599
  }
542
600
  // overwrite or merge — fall through to install
543
601
  } else if (!hooksNeedingUpdate.includes(filename) && fs.existsSync(path.join(HOOKS_DIR, filename))) {
544
602
  // Already installed and up-to-date
603
+ if (DRY_RUN) console.log(' [dry run] Up to date (no change): hook ' + filename);
545
604
  continue;
546
605
  }
547
- fs.copyFileSync(path.join(PACKAGE_HOOKS_DIR, filename), path.join(HOOKS_DIR, filename));
606
+ const isHookUpdate = fs.existsSync(path.join(HOOKS_DIR, filename));
607
+ if (!DRY_RUN) fs.copyFileSync(path.join(PACKAGE_HOOKS_DIR, filename), path.join(HOOKS_DIR, filename));
548
608
  newChecksums['hook:' + filename] = packageHookHashes[filename];
549
609
  installedHookCount++;
550
- console.log(' Installed hook ' + filename);
610
+ if (DRY_RUN) {
611
+ console.log(' [dry run] Would ' + (isHookUpdate ? 'update hook ' : 'install hook ') + filename);
612
+ } else {
613
+ console.log(' Installed hook ' + filename);
614
+ }
551
615
  }
552
616
 
553
617
  // 9. Write checksums
554
- fs.writeFileSync(CHECKSUMS_FILE, JSON.stringify(newChecksums, null, 2));
618
+ if (!DRY_RUN) fs.writeFileSync(CHECKSUMS_FILE, JSON.stringify(newChecksums, null, 2));
555
619
 
556
620
  // 10. Setup startup hook and repo-search nudge hook
557
- const hookInstalled = setupStartupHook();
558
- const nudgeHookInstalled = setupRepoSearchNudgeHook();
621
+ // In dry-run these inspect settings.json read-only and report would-be action.
622
+ const hookInstalled = setupStartupHook(DRY_RUN);
623
+ const nudgeHookInstalled = setupRepoSearchNudgeHook(DRY_RUN);
559
624
 
560
625
  // 11. Summary output
561
- console.log('\nestack installed successfully!\n');
562
- console.log(' ' + installedCount + ' skill' + (installedCount !== 1 ? 's' : '') + ' installed to ~/.claude/skills/');
563
- if (installedHookCount > 0) {
564
- console.log(' ' + installedHookCount + ' hook' + (installedHookCount !== 1 ? 's' : '') + ' installed to ~/.claude/hooks/');
626
+ if (DRY_RUN) {
627
+ console.log('\n[dry run] No files were changed. Run with --install to apply.\n');
628
+ console.log(' ' + installedCount + ' skill' + (installedCount !== 1 ? 's' : '') + ' would be installed/updated in ~/.claude/skills/');
629
+ if (installedHookCount > 0) {
630
+ console.log(' ' + installedHookCount + ' hook' + (installedHookCount !== 1 ? 's' : '') + ' would be installed/updated in ~/.claude/hooks/');
631
+ }
632
+ } else {
633
+ console.log('\nestack installed successfully!\n');
634
+ console.log(' ' + installedCount + ' skill' + (installedCount !== 1 ? 's' : '') + ' installed to ~/.claude/skills/');
635
+ if (installedHookCount > 0) {
636
+ console.log(' ' + installedHookCount + ' hook' + (installedHookCount !== 1 ? 's' : '') + ' installed to ~/.claude/hooks/');
637
+ }
565
638
  }
566
639
  console.log('');
567
640
  console.log('Skills available:');
@@ -582,15 +655,27 @@ async function main() {
582
655
  console.log('Backed up to ~/.claude/.estack-backup/hooks/');
583
656
  }
584
657
 
585
- if (hookInstalled) {
586
- console.log('\nAuto-update hook added to ~/.claude/settings.json');
587
- console.log('Skills will update automatically when you start Claude Code.');
658
+ if (DRY_RUN) {
659
+ if (hookInstalled) {
660
+ console.log('\n[dry run] Would add auto-update hook to ~/.claude/settings.json');
661
+ } else {
662
+ console.log('\nAuto-update hook already configured (no change).');
663
+ }
664
+ if (nudgeHookInstalled) {
665
+ console.log('[dry run] Would register repo-search nudge hook in settings.json.');
666
+ } else {
667
+ console.log('Repo-search nudge hook already configured (no change).');
668
+ }
588
669
  } else {
589
- console.log('\nAuto-update hook already configured.');
590
- }
591
-
592
- if (nudgeHookInstalled) {
593
- console.log('Repo-search nudge hook registered in settings.json.');
670
+ if (hookInstalled) {
671
+ console.log('\nAuto-update hook added to ~/.claude/settings.json');
672
+ console.log('Skills will update automatically when you start Claude Code.');
673
+ } else {
674
+ console.log('\nAuto-update hook already configured.');
675
+ }
676
+ if (nudgeHookInstalled) {
677
+ console.log('Repo-search nudge hook registered in settings.json.');
678
+ }
594
679
  }
595
680
 
596
681
  console.log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elliot-stack",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "Elliot's skill stack for Claude Code — install via npx elliot-stack@latest",
5
5
  "bin": {
6
6
  "elliot-stack": "bin/install.cjs"
@@ -0,0 +1,203 @@
1
+ ---
2
+ name: estack-read-claude-session-history
3
+ description: (read-claude-session-history) Invoke for ANY task involving Claude Code session history, transcripts, or .jsonl files — this is the only way to read, parse, or search them; do not attempt to use Bash or Read on .jsonl directly. Use for: recovering context after /compact ("what were we doing before compact"), advisor response retrieval ("what did the advisor say"), subagent output collection ("get all subagent finals"), cross-project session search by keyword, session listing and triage, UUID and title lookup, resume-command generation, file-edit and tool-call forensics, session diff between two sessions or subagents, weekly work journal, day timeline of activity blocks and idle gaps, engagement/attention-time accounting (active vs elapsed time, break detection, parallel-chat-safe totals), recovering from .claude-backups after data loss, session count queries, and reading the last agent message before a crash or interrupt. Trigger phrases: "session history", "before compact", "what did claude do", "what did I work on", "search my sessions", "find that session", "what did the advisor say", "what did the agent edit", "from the backup", "list my sessions", "subagent outputs", "session journal", "resume previous", "which files did claude touch", "go back and look", "what did I do yesterday", "where did my day go", "timeline of my day", "how much time on", "how long did that actually take", "how much did I actually work", "active time", "time I spent".
4
+ ---
5
+
6
+ # Read Claude Session History
7
+
8
+ Search, read, recover, and compare Claude Code session history — across the current session, prior sessions, sibling subagents, all projects, and `.claude-backups` snapshots.
9
+
10
+ Sessions are stored as `.jsonl` files. Reading them raw is hopeless: 1,000–5,000+ lines of dense JSON per session, 33+ project directories, hundreds of historical sessions. This skill wraps a single CLI that knows the entry schema and exposes ~20 modes.
11
+
12
+ ## Quick start
13
+
14
+ ```bash
15
+ PY="C:\Users\2supe\.claude\skills\read-claude-session-history\scripts\read_transcript.py"
16
+
17
+ # What was the last thing the agent said in this session?
18
+ python "$PY" --file <current-session.jsonl> --mode last
19
+
20
+ # Get a 6-line summary of any session (intent, last activity, edits, tool counts, subagent fanout)
21
+ python "$PY" --file <session.jsonl> --mode brief
22
+
23
+ # Recover what got cut off by the most recent /compact
24
+ python "$PY" --file <session.jsonl> --mode pre-compact
25
+
26
+ # Find a session by UUID prefix across all projects
27
+ python "$PY" --mode lookup --uuid abc123de
28
+
29
+ # Search every session in every project for a phrase
30
+ python "$PY" --mode search --all-projects --query "supabase migration"
31
+
32
+ # Pull subagent outputs from a fan-out investigation
33
+ python "$PY" --file <parent.jsonl> --mode subagent-finals
34
+
35
+ # Block-grouped timeline of a whole day across all sessions, with idle gaps
36
+ python "$PY" --mode timeline --date yesterday
37
+
38
+ # How much focused time did today actually consume? (your attention, not Claude's)
39
+ python "$PY" --mode engagement --date today
40
+
41
+ # Any mode as structured JSON for piping into the next step
42
+ python "$PY" --mode list --project keel --since 7d --format json
43
+ ```
44
+
45
+ ## Time handling — READ THIS before doing anything with time
46
+
47
+ **Every time the CLI displays is already the user's local time.** JSONL files store
48
+ UTC; the script converts on output. Do NOT add or subtract timezone offsets
49
+ yourself, do NOT cross-reference file mtimes to infer the timezone, and do NOT
50
+ treat raw `"timestamp"` fields from a .jsonl (which ARE UTC) as comparable to CLI
51
+ output. If you need a different zone, pass `--tz` (IANA name like
52
+ `America/New_York`, `UTC`, or an offset like `-4`) — never convert manually.
53
+ `--since/--until/--date` specs are interpreted in that same display timezone.
54
+
55
+ ## Decision tree
56
+
57
+ ```
58
+ What are you trying to do?
59
+
60
+ ├─ Read the current session / one specific session
61
+ │ ├─ Last assistant message ──────────────────── --mode last
62
+ │ ├─ All advisor responses ───────────────────── --mode advisor
63
+ │ ├─ Content cut off by /compact ─────────────── --mode pre-compact
64
+ │ ├─ Full human-readable dump ────────────────── --mode dump (size-aware)
65
+ │ ├─ 6-line summary for triage ───────────────── --mode brief
66
+ │ └─ Schema/structural diagnosis ─────────────── --mode debug
67
+
68
+ ├─ Find a session I don't have the path for
69
+ │ ├─ By UUID prefix ──────────────────────────── --mode lookup --uuid <prefix>
70
+ │ ├─ By title or first prompt ────────────────── --mode find --title|--first-prompt
71
+ │ └─ Generate a `claude --resume` command ───── --mode resume-cmd --uuid <prefix>
72
+
73
+ ├─ Search content
74
+ │ ├─ One session ─────────────────────────────── --mode search --file …
75
+ │ ├─ One project ─────────────────────────────── --mode search --cwd …
76
+ │ ├─ All projects ────────────────────────────── --mode search --all-projects
77
+ │ └─ Filter to user msgs / tool-use inputs ──── --role user --in tool_use
78
+
79
+ ├─ Forensics on a session
80
+ │ ├─ Chronological tool-call log ────────────── --mode changelog
81
+ │ ├─ Every file touched ─────────────────────── --mode file-edits
82
+ │ └─ Every tool call (optionally filtered) ──── --mode tool-calls --tool Bash,Edit
83
+
84
+ ├─ Subagent (fan-out) work
85
+ │ ├─ List spawned subagents ─────────────────── --mode subagent-list
86
+ │ ├─ Get every subagent's final message ─────── --mode subagent-finals
87
+ │ └─ Forensics on one subagent ──────────────── --mode subagent-tools|subagent-files --subagent …
88
+
89
+ ├─ Cross-cutting reporting
90
+ │ ├─ "What did I do this week?" ──────────────── --mode journal --since 7d
91
+ │ ├─ "What was I doing, when?" / day map ─────── --mode timeline --date yesterday
92
+ │ ├─ "How long did X actually take ME?" ──────── --mode engagement --date … | --project … | --file …
93
+ │ ├─ Count sessions matching a query ─────────── --mode count --query …
94
+ │ └─ Resume where I left off in this project ─── --mode resume-prev --cwd …
95
+
96
+ └─ Compare two sessions or two sibling subagents
97
+ └─ Interleaved diff ──────────────────────── --mode diff --file-a … --file-b … (or --subagents-of …)
98
+ ```
99
+
100
+ ## Quick reference
101
+
102
+ | Mode | Required flags | Returns |
103
+ |---|---|---|
104
+ | `last` | `--file` | Last N assistant text outputs |
105
+ | `advisor` | `--file` | All `advisor_tool_result` payloads |
106
+ | `pre-compact` | `--file` | 40 exchanges before the most recent `/compact` |
107
+ | `dump` | `--file` | Human-readable dump (auto-degrades on transcripts >5MB) |
108
+ | `search` | `--query` + scope | Matches windowed for context (supports `--role`, `--in text|tool_use|thinking|all`) |
109
+ | `debug` | `--file` | Entry/block type distributions + probes |
110
+ | `brief` | `--file` | 6-line summary: uuid·project·mtime·status / intent / last / edits / tools / subagents |
111
+ | `list` | `--cwd` or `--all-projects` | Rich table: mtime, size, uuid, msg count, flags, status, title |
112
+ | `lookup` | `--uuid <prefix>` | Absolute path (exit 1 missing, exit 2 ambiguous) |
113
+ | `find` | `--title` or `--first-prompt` | Sessions ranked by recency |
114
+ | `resume-cmd` | `--uuid <prefix>` | `cd <cwd>; claude --resume <uuid>` snippet |
115
+ | `changelog` | `--file` | `HH:MM:SS TOOL one-line-summary`, day-grouped |
116
+ | `file-edits` | `--file` | Unique paths sorted with op tags |
117
+ | `tool-calls` | `--file` (+ `--tool` filter) | Timestamped per-call blocks |
118
+ | `subagent-list` | `--file` | List sibling subagents with agentType + description |
119
+ | `subagent-finals` | `--file` | Every subagent's final assistant message |
120
+ | `subagent-tools` | `--subagent` | Forensics on one subagent |
121
+ | `subagent-files` | `--subagent` | Files one subagent touched |
122
+ | `resume-prev` | `--cwd` | Banner + dump-style tail of last 10 exchanges |
123
+ | `count` | `--query` (+ scope) | `<N>` to stdout, summary to stderr |
124
+ | `journal` | `--since` (+ scope) | Per-session 5-line block: date·uuid / prompt / ended / edits / tools |
125
+ | `timeline` | `--date` or `--since/--until` (defaults: today, all projects) | Map of WHAT was active WHEN: blocks + idle gaps (no attention claim — that's `engagement`) |
126
+ | `engagement` | `--date` or `--since/--until` or `--file` (defaults: today, all projects) | YOUR attention time: active vs elapsed + ratio per session, parallel-chat-safe totals, breaks |
127
+ | `diff` | `--file-a` + `--file-b` OR `--subagents-of` | Timestamp-interleaved A>/B> output |
128
+
129
+ ## Global flags
130
+
131
+ - `--root {live|mirror|snapshot-24h|snapshot-1w|snapshot-1mo|<abs-path>}` — read from a `.claude-backups` mirror or snapshot instead of live. Default `live`.
132
+ - `--cwd <path>` — single-project scope. Use the original working directory (e.g. `"C:\Users\2supe\Other Claude Code"`).
133
+ - `--all-projects` — walk every project under `--root`.
134
+ - `--project <name>` — filter projects by name substring, case-insensitive, matches encoded or decoded form (`--project keel`, `--project "Other Claude Code"`). Works on `list`, `journal`, `search`, `count`, `find`, `timeline`, `engagement`. Use this instead of `--cwd` when you know the project's name but not its exact path. (Note: for `engagement`, scope filters which sessions are *reported* — the attention stream is always computed across all projects so parallel chats never double-count.)
135
+ - `--file <path>` — single-session scope.
136
+ - `--since <spec>` / `--until <spec>` — accepts ISO date, ISO datetime, relative (`30m`, `24h`, `7d`, `1w`, `1mo`), named (`today`, `yesterday`, `now`).
137
+ - `--date <spec>` — single-day window for `timeline` (`--date yesterday`, `--date 2026-06-01`).
138
+ - `--gap <spec>` — idle-gap threshold for `timeline` blocks (`15m` default, `1h`).
139
+ - `--break <spec>` — break threshold for `engagement` (`10m` default; `5m` strict, `20m` forgiving). Gaps between your prompts longer than this count as breaks unless you replied right after Claude finished working.
140
+ - `--tz <spec>` — display timezone override (IANA name, `UTC`, or offset like `-4`). Default: system local time.
141
+ - `--format json` (or `--json`) — structured JSON output on every mode (except the legacy `--list`/`--list-subagents` aliases). Pipe-friendly: paths are strings, timestamps ISO.
142
+ - `--exclude-current` — drop the current session (detected via `CLAUDE_SESSION_ID`) from `list`, `journal`, `search`, `count`, `timeline`, and `engagement`.
143
+ - `--include-subagents` — fold subagent finals into `brief`, `last`, `dump` output, each tagged `[subagent <id-short> · <agentType>]`.
144
+ - `--force-dump` — bypass the 5 MB `dump` guard.
145
+ - `-n N` — count modifier (default 5 for `last`, 80 for `dump`, 10 for `resume-prev`).
146
+
147
+ The current session is marked with `[*]` in `list` output. Status glyphs: ✓ clean, ! interrupted, ? pending-user, ● active. Sessions with a compact marker get `[C]`; sessions with subagents get `[S]`.
148
+
149
+ ## Backup-aware reads
150
+
151
+ In March 2026 a Claude Code auto-update deleted live `.jsonl` transcripts (GitHub #41591). To survive that class of incident, this machine maintains four backup roots under `C:\Users\2supe\.claude-backups\`:
152
+
153
+ - `mirror` — continuous mirror
154
+ - `snapshot-24h` — 24-hour-old snapshot
155
+ - `snapshot-1w` — 1-week-old snapshot
156
+ - `snapshot-1mo` — 1-month-old snapshot
157
+
158
+ Any mode accepts `--root <name>`. The resolved root is printed to stderr.
159
+
160
+ ```bash
161
+ # Find a session that was deleted from live but still in yesterday's snapshot
162
+ python "$PY" --root snapshot-24h --mode lookup --uuid <prefix>
163
+
164
+ # Compare today's mirror against a week ago to confirm what was lost
165
+ python "$PY" --root snapshot-1w --cwd "C:\Users\2supe\Other Claude Code" --list
166
+ ```
167
+
168
+ See `references/recipes.md` → "Deletion-incident recovery" for the full playbook.
169
+
170
+ ## Common workflows
171
+
172
+ | Need | Command |
173
+ |---|---|
174
+ | Recover advisor output that scrolled out of context | `--file <session> --mode advisor` |
175
+ | Get back to what you were doing before `/compact` | `--file <session> --mode pre-compact` |
176
+ | Fan-out triage: 14 subagents, want all of their finals | `--file <parent> --mode subagent-finals` (or `--mode brief --include-subagents`) |
177
+ | Find "that session where I asked about supabase rate limits" | `--mode search --all-projects --query "supabase rate limits"` |
178
+ | Resume a project after a few days away | `--mode resume-prev --cwd "<project path>"` |
179
+ | Daily/weekly journal | `--mode journal --since 7d --all-projects` |
180
+ | "Where did yesterday go?" (map of activity) | `--mode timeline --date yesterday` |
181
+ | "How much did I actually work today?" | `--mode engagement --date today` |
182
+ | "How much time on Keel today?" | `--mode engagement --project keel --date today` |
183
+ | "How long did that session take me?" | `--mode engagement --file <session.jsonl>` |
184
+ | Feed session data into a script | any mode + `--format json` |
185
+
186
+ See `references/recipes.md` for fuller multi-step workflows.
187
+
188
+ ## Windows notes
189
+
190
+ - Use `python` (not `python3`) on this Windows setup.
191
+ - The script handles UTF-8 stdout/stderr internally — both PowerShell and Bash work fine for single commands.
192
+ - **Piping `--format json` into another command: use Bash.** PowerShell 5.1 pipes inject a UTF-8 BOM and re-encode through the console codepage, breaking `json.load` (see `references/recipes.md` §5c for the PowerShell workaround).
193
+ - File paths with spaces need quoting: `--cwd "C:\Users\2supe\Other Claude Code"`.
194
+
195
+ ## Reference docs
196
+
197
+ - `references/modes.md` — complete per-mode reference (every flag, every example, exit codes).
198
+ - `references/jsonl-schema.md` — entry/block schema, subagent meta sidecars, compact-marker shape.
199
+ - `references/recipes.md` — multi-step workflows (post-compact recovery, find-then-dump, deletion recovery, week-in-review journal, sibling-agent diff).
200
+
201
+ ## When the modes return empty
202
+
203
+ If a mode returns empty/unexpected output, run `--mode debug` first. It prints the entry-type distribution, content-block types, and probes for advisor + compact markers — useful when the transcript schema has drifted or when a session was truncated.