claudedash 0.5.4 → 0.6.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.
Files changed (50) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +43 -3
  3. package/dist/cli.js +497 -7
  4. package/dist/cli.js.map +1 -1
  5. package/dist/core/contextHealth.d.ts +8 -2
  6. package/dist/core/contextHealth.d.ts.map +1 -1
  7. package/dist/core/contextHealth.js +27 -5
  8. package/dist/core/contextHealth.js.map +1 -1
  9. package/dist/public/404/index.html +1 -1
  10. package/dist/public/404.html +1 -1
  11. package/dist/public/__next.__PAGE__.txt +2 -2
  12. package/dist/public/__next._full.txt +4 -4
  13. package/dist/public/__next._head.txt +1 -1
  14. package/dist/public/__next._index.txt +2 -2
  15. package/dist/public/__next._tree.txt +2 -2
  16. package/dist/public/_next/static/chunks/db0169666466ef04.js +69 -0
  17. package/dist/public/_next/static/chunks/fa2cff080f66a65c.css +2 -0
  18. package/dist/public/_not-found/__next._full.txt +3 -3
  19. package/dist/public/_not-found/__next._head.txt +1 -1
  20. package/dist/public/_not-found/__next._index.txt +2 -2
  21. package/dist/public/_not-found/__next._not-found.__PAGE__.txt +1 -1
  22. package/dist/public/_not-found/__next._not-found.txt +1 -1
  23. package/dist/public/_not-found/__next._tree.txt +2 -2
  24. package/dist/public/_not-found/index.html +1 -1
  25. package/dist/public/_not-found/index.txt +3 -3
  26. package/dist/public/index.html +1 -1
  27. package/dist/public/index.txt +4 -4
  28. package/dist/server/routes/live.d.ts.map +1 -1
  29. package/dist/server/routes/live.js +22 -4
  30. package/dist/server/routes/live.js.map +1 -1
  31. package/dist/server/routes/observability.d.ts.map +1 -1
  32. package/dist/server/routes/observability.js +18 -0
  33. package/dist/server/routes/observability.js.map +1 -1
  34. package/dist/server/routes/plan.d.ts +2 -0
  35. package/dist/server/routes/plan.d.ts.map +1 -1
  36. package/dist/server/routes/plan.js +66 -3
  37. package/dist/server/routes/plan.js.map +1 -1
  38. package/dist/server/server.d.ts +2 -0
  39. package/dist/server/server.d.ts.map +1 -1
  40. package/dist/server/server.js +48 -5
  41. package/dist/server/server.js.map +1 -1
  42. package/dist/server/watcher.d.ts +1 -1
  43. package/dist/server/watcher.js +3 -3
  44. package/dist/server/watcher.js.map +1 -1
  45. package/package.json +14 -6
  46. package/dist/public/_next/static/chunks/02c545b35c5a7a0d.js +0 -2
  47. package/dist/public/_next/static/chunks/bfa1988f6340e536.css +0 -2
  48. /package/dist/public/_next/static/{36m05Da62vzqVee_ifGeq → 1zNqkaO9LSa-UBCfTki_G}/_buildManifest.js +0 -0
  49. /package/dist/public/_next/static/{36m05Da62vzqVee_ifGeq → 1zNqkaO9LSa-UBCfTki_G}/_clientMiddlewareManifest.json +0 -0
  50. /package/dist/public/_next/static/{36m05Da62vzqVee_ifGeq → 1zNqkaO9LSa-UBCfTki_G}/_ssgManifest.js +0 -0
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 agent-scope contributors
3
+ Copyright (c) 2026 claudedash contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -93,9 +93,46 @@ Then:
93
93
  claudedash start # Auto-detect modes, open dashboard
94
94
  claudedash start --claude-dir /path # Custom Claude directory
95
95
  claudedash start -p 3000 # Custom port
96
+ claudedash start --host 0.0.0.0 # Expose to network (shows warning)
97
+ claudedash start --token <secret> # Enable token auth for sharing
96
98
  claudedash init # Init plan mode in current dir
99
+ claudedash recover # Summarize last session after /clear
100
+ claudedash spec # Create spec-mode templates
101
+ claudedash worktree create <branch> # Create isolated worktree
97
102
  ```
98
103
 
104
+ ## Sharing with your team
105
+
106
+ By default, claudedash only listens on `127.0.0.1` (localhost). To share the dashboard with a teammate:
107
+
108
+ ```bash
109
+ # 1. Start with a secret token
110
+ claudedash start --token mysecret123
111
+
112
+ # Or use an environment variable
113
+ CLAUDEDASH_TOKEN=mysecret123 claudedash start
114
+ ```
115
+
116
+ Your teammate can then open the dashboard with the token in the URL:
117
+
118
+ ```
119
+ http://your-host:4317?token=mysecret123
120
+ ```
121
+
122
+ Or pass it as a header:
123
+
124
+ ```bash
125
+ curl -H "Authorization: Bearer mysecret123" http://your-host:4317/sessions
126
+ ```
127
+
128
+ > **Security:** Never commit your token to git. Use `.env` and add it to `.gitignore`. Consider using a local tunnel like [ngrok](https://ngrok.com) or [cloudflared](https://github.com/cloudflare/cloudflared) rather than exposing `--host 0.0.0.0` directly.
129
+ >
130
+ > ```bash
131
+ > # Share via tunnel without network exposure
132
+ > claudedash start --token $(openssl rand -hex 16)
133
+ > ngrok http 4317
134
+ > ```
135
+
99
136
  ## Queue format (Plan mode)
100
137
 
101
138
  ```markdown
@@ -174,13 +211,16 @@ When running agents across multiple git worktrees in parallel, the new **Worktre
174
211
 
175
212
  | Endpoint | Description |
176
213
  |---|---|
177
- | `GET /health` | Status + available modes |
214
+ | `GET /health` | Status + available modes + `connectedClients` + `lastSessions` |
178
215
  | `GET /sessions` | All Claude Code sessions (includes `contextHealth`) |
179
216
  | `GET /sessions/:id` | Tasks for a session |
180
217
  | `GET /events` | SSE stream |
181
218
  | `GET /snapshot` | Plan mode state |
182
- | `GET /quality-timeline` | Quality check events (filter with `?taskId=`) |
219
+ | `GET /quality-timeline` | Quality check events (filter with `?taskId=` or `?file=`) |
183
220
  | `GET /worktrees` | Git worktrees with task associations |
221
+ | `GET /claude-insights` | Claude usage report (sandboxed HTML) |
222
+ | `POST /plan/task` | Add a task to queue.md |
223
+ | `PATCH /plan/task/:id` | Update task status (DONE / BLOCKED / FAILED) |
184
224
 
185
225
  ## Development
186
226
 
@@ -190,7 +230,7 @@ cd claudedash && npm install
190
230
  cd dashboard && npm install && cd ..
191
231
 
192
232
  npm run build # Build core + dashboard
193
- npm test # 163 tests
233
+ npm test # 165 tests
194
234
  npm run dev # Dev server with watch
195
235
  ```
196
236
 
package/dist/cli.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import { mkdirSync, writeFileSync, existsSync } from 'fs';
3
+ import { mkdirSync, writeFileSync, existsSync, readdirSync, readFileSync, statSync, watch } from 'fs';
4
4
  import { join } from 'path';
5
- import { exec } from 'child_process';
5
+ import { execFile } from 'child_process';
6
6
  import { startServer } from './server/server.js';
7
7
  const program = new Command();
8
8
  program
9
9
  .name('claudedash')
10
10
  .description('Live Kanban, quality gates and context health monitoring for Claude Code agents')
11
- .version('0.5.4');
11
+ .version('0.6.0');
12
12
  program
13
13
  .command('init')
14
14
  .description('Initialize claudedash in current directory')
@@ -172,9 +172,180 @@ Run \`npx -y claudedash@latest start\` to view progress.
172
172
  `;
173
173
  writeFileSync(join(claudeWatchDir, 'CLAUDE.md'), claudeMdContent);
174
174
  console.log('✓ Created CLAUDE.md');
175
+ // Create hooks/ directory with lifecycle templates
176
+ mkdirSync(join(claudeWatchDir, 'hooks'), { recursive: true });
177
+ const preCompactHook = `# Pre-Compact Hook
178
+
179
+ Run this before Claude Code compacts the conversation.
180
+ Add to your project's CLAUDE.md or register as a PreCompact hook.
181
+
182
+ ## Purpose
183
+ Save the current plan state so it can be restored after compaction.
184
+
185
+ ## Instructions for the agent
186
+
187
+ Before context compaction, write a brief state snapshot:
188
+ 1. Current task ID from .claudedash/queue.md (the one in_progress)
189
+ 2. How many tasks are DONE (count lines in execution.log)
190
+ 3. Any important decisions or blockers from the last few messages
191
+
192
+ Write the snapshot to .claudedash/compact-state.md:
193
+ \`\`\`
194
+ # Compact State
195
+ Task: S1-T3
196
+ Completed: 2 (S1-T1, S1-T2)
197
+ Status: In progress — editing src/server/server.ts, adding CORS restriction
198
+ Blocker: none
199
+ \`\`\`
200
+ `;
201
+ const postCompactHook = `# Post-Compact Hook
202
+
203
+ Run this after Claude Code compacts the conversation.
204
+ Add to your project's CLAUDE.md or register as a PostCompact hook.
205
+
206
+ ## Purpose
207
+ Re-inject plan context after compaction so the agent resumes correctly.
208
+
209
+ ## Instructions for the agent
210
+
211
+ After context compaction, immediately:
212
+ 1. Read .claudedash/compact-state.md (if it exists)
213
+ 2. Read .claudedash/execution.log to verify completed tasks
214
+ 3. Read .claudedash/queue.md to find the current task
215
+ 4. Resume from exactly where the snapshot says
216
+
217
+ Then delete .claudedash/compact-state.md to avoid stale state.
218
+ `;
219
+ const stopHook = `# Stop Hook
220
+
221
+ Prevents the agent from stopping mid-task.
222
+ Register as a Stop hook in Claude Code settings.
223
+
224
+ ## Purpose
225
+ If there are pending tasks in the queue, remind the agent to continue.
226
+
227
+ ## Logic (loop_limit: 3)
228
+
229
+ \`\`\`json
230
+ {
231
+ "hook": "Stop",
232
+ "loop_limit": 3,
233
+ "condition": "pending tasks remain in .claudedash/queue.md not in execution.log",
234
+ "followup_message": "There are still pending tasks in .claudedash/queue.md. Check .claudedash/workflow.md and continue with the next READY task. Do not stop until all tasks are DONE or BLOCKED."
235
+ }
236
+ \`\`\`
237
+
238
+ ## How to Install
239
+
240
+ Add to your Claude Code hooks configuration:
241
+ 1. Open Claude Code settings
242
+ 2. Navigate to Hooks → Stop
243
+ 3. Paste the JSON block above
244
+ 4. Set loop_limit to 3 to prevent infinite loops
245
+ `;
246
+ const postToolUseHook = `# PostToolUse Hook — Quality Gate
247
+
248
+ Runs automatically after Bash, Edit, or Write tool calls.
249
+ Register in Claude Code settings under Hooks → PostToolUse.
250
+
251
+ ## Purpose
252
+ Run lint and typecheck after every code change and record results
253
+ in execution.log so the Quality Gates dashboard panel can display them.
254
+
255
+ ## Hook Configuration
256
+
257
+ \`\`\`json
258
+ {
259
+ "hook": "PostToolUse",
260
+ "matcher": { "tool_name": ["Bash", "Edit", "Write"] },
261
+ "command": "npm run lint --silent 2>/dev/null && npx tsc --noEmit 2>/dev/null",
262
+ "on_success": {
263
+ "append_to": ".claudedash/execution.log",
264
+ "line": {"task_id":"{{task_id}}","status":"QUALITY","timestamp":"{{iso}}","agent":"claude","meta":{"quality":{"lint":true,"typecheck":true}}}
265
+ },
266
+ "on_failure": {
267
+ "append_to": ".claudedash/execution.log",
268
+ "line": {"task_id":"{{task_id}}","status":"QUALITY","timestamp":"{{iso}}","agent":"claude","meta":{"quality":{"lint":false,"typecheck":false}}}
269
+ }
270
+ }
271
+ \`\`\`
272
+
273
+ ## Manual Usage
274
+
275
+ After any code change, run:
276
+ \`\`\`bash
277
+ npm run lint && npx tsc --noEmit
278
+ \`\`\`
279
+ Then append to execution.log:
280
+ \`\`\`json
281
+ {"task_id":"S1-T1","status":"QUALITY","timestamp":"2026-01-15T10:30:00Z","agent":"claude","meta":{"quality":{"lint":true,"typecheck":true}}}
282
+ \`\`\`
283
+ `;
284
+ const tddHook = `# TDD Enforcement Hook
285
+
286
+ Warns when a new source file is created without a corresponding test file.
287
+
288
+ ## Naming Conventions Checked
289
+
290
+ | Source file | Expected test file |
291
+ |----------------------|-------------------------------------|
292
+ | src/foo.ts | tests/foo.test.ts or foo.spec.ts |
293
+ | src/core/bar.ts | tests/core/bar.test.ts |
294
+ | lib/baz.py | test_baz.py or baz_test.py |
295
+ | pkg/qux.go | qux_test.go |
296
+
297
+ ## Skip List (configure in .claudedash/config.json)
298
+
299
+ \`\`\`json
300
+ {
301
+ "tddHook": {
302
+ "skipPatterns": ["src/cli.ts", "src/server/server.ts", "**/*.d.ts"]
303
+ }
304
+ }
305
+ \`\`\`
306
+
307
+ ## Hook Configuration
308
+
309
+ \`\`\`json
310
+ {
311
+ "hook": "PostToolUse",
312
+ "matcher": { "tool_name": ["Write"], "file_pattern": "src/**/*.ts" },
313
+ "script": ".claudedash/hooks/tdd-check.sh"
314
+ }
315
+ \`\`\`
316
+
317
+ ## Behavior
318
+
319
+ - If a matching test file exists → silent pass
320
+ - If no test file found → prints a warning (does NOT block)
321
+ - Agent should create the test file before marking the task DONE
322
+ `;
323
+ writeFileSync(join(claudeWatchDir, 'hooks', 'pre-compact.md'), preCompactHook);
324
+ writeFileSync(join(claudeWatchDir, 'hooks', 'post-compact.md'), postCompactHook);
325
+ writeFileSync(join(claudeWatchDir, 'hooks', 'stop.md'), stopHook);
326
+ writeFileSync(join(claudeWatchDir, 'hooks', 'post-tool-use.md'), postToolUseHook);
327
+ writeFileSync(join(claudeWatchDir, 'hooks', 'tdd-enforcement.md'), tddHook);
328
+ console.log('✓ Created hooks/ (pre-compact, post-compact, stop, post-tool-use, tdd-enforcement)');
329
+ // Auto-inject TodoWrite directive into project CLAUDE.md
330
+ const projectClaudeMdPath = join(process.cwd(), 'CLAUDE.md');
331
+ const todoWriteMarker = 'TodoWrite tool to track your work';
332
+ const todoWriteDirective = `\n## Task Tracking (MANDATORY)\n\nYou MUST use the TodoWrite tool to track your work. This is not optional.\nThe user monitors your progress via a live dashboard that reads TodoWrite output.\n\nRules:\n- At the START of any multi-step task, create a todo list with all steps.\n- Mark each task as \`in_progress\` BEFORE you start working on it.\n- Mark each task as \`completed\` IMMEDIATELY after finishing it.\n- Keep exactly ONE task as \`in_progress\` at any time.\n- Update the todo list as you discover new subtasks.\n\nIf you skip TodoWrite, the user cannot see what you are doing.\n`;
333
+ if (existsSync(projectClaudeMdPath)) {
334
+ const existing = readFileSync(projectClaudeMdPath, 'utf-8');
335
+ if (!existing.includes(todoWriteMarker)) {
336
+ writeFileSync(projectClaudeMdPath, existing + todoWriteDirective, 'utf-8');
337
+ console.log('✓ Added TodoWrite directive to CLAUDE.md');
338
+ }
339
+ else {
340
+ console.log('✓ CLAUDE.md already has TodoWrite directive');
341
+ }
342
+ }
343
+ else {
344
+ writeFileSync(projectClaudeMdPath, `# CLAUDE.md${todoWriteDirective}`, 'utf-8');
345
+ console.log('✓ Created CLAUDE.md with TodoWrite directive');
346
+ }
175
347
  console.log('\n✓ Ready! Next steps:');
176
348
  console.log(' 1. Edit .claudedash/queue.md with your tasks');
177
- console.log(' 2. Copy .claudedash/CLAUDE.md contents into your project CLAUDE.md');
178
349
  console.log(' 3. Tell your agent: "follow .claudedash/workflow.md, start with S1-T1"');
179
350
  console.log(' 4. Run: npx -y claudedash@latest start');
180
351
  }
@@ -188,6 +359,9 @@ program
188
359
  .description('Start the claudedash server and dashboard')
189
360
  .option('--claude-dir <path>', 'Path to Claude directory', join(process.env.HOME || '~', '.claude'))
190
361
  .option('-p, --port <number>', 'Port number', '4317')
362
+ .option('--host <host>', 'Bind host', '127.0.0.1')
363
+ .option('--no-bell', 'Disable terminal bell on task alerts')
364
+ .option('--token <secret>', 'Require Bearer token for all API requests (reads CLAUDEDASH_TOKEN env var if not provided)')
191
365
  .action(async (opts) => {
192
366
  const claudeDir = opts.claudeDir;
193
367
  const claudeWatchDir = join(process.cwd(), '.claudedash');
@@ -215,12 +389,22 @@ program
215
389
  catch { /* use default port */ }
216
390
  }
217
391
  }
218
- const url = `http://localhost:${port}`;
392
+ const host = opts.host;
393
+ const isLocalhost = host === '127.0.0.1' || host === 'localhost' || host === '::1';
394
+ const url = `http://${isLocalhost ? 'localhost' : host}:${port}`;
395
+ if (!isLocalhost) {
396
+ console.log(`⚠️ Server exposed to network on ${host}:${port}`);
397
+ }
398
+ const token = opts.token ?? process.env.CLAUDEDASH_TOKEN;
399
+ if (token)
400
+ console.log('🔒 Token authentication enabled');
219
401
  try {
220
402
  await startServer({
221
403
  claudeDir,
222
404
  port,
223
- agentScopeDir: hasPlan ? claudeWatchDir : undefined
405
+ host,
406
+ agentScopeDir: hasPlan ? claudeWatchDir : undefined,
407
+ token,
224
408
  });
225
409
  console.log(`✓ Server running on ${url}`);
226
410
  if (hasLive)
@@ -232,16 +416,322 @@ program
232
416
  const openCommand = platform === 'darwin' ? 'open' :
233
417
  platform === 'win32' ? 'start' :
234
418
  'xdg-open';
235
- exec(`${openCommand} ${url}`, (error) => {
419
+ execFile(openCommand, [url], (error) => {
236
420
  if (error) {
237
421
  console.log('Could not auto-open browser. Please visit:', url);
238
422
  }
239
423
  });
424
+ // Watch execution.log for FAILED/BLOCKED events and alert in terminal
425
+ if (hasPlan) {
426
+ const logPath = join(claudeWatchDir, 'execution.log');
427
+ const useBell = opts.bell !== false;
428
+ let lastSize = existsSync(logPath) ? statSync(logPath).size : 0;
429
+ watch(logPath, () => {
430
+ try {
431
+ if (!existsSync(logPath))
432
+ return;
433
+ const currentSize = statSync(logPath).size;
434
+ if (currentSize <= lastSize)
435
+ return;
436
+ const content = readFileSync(logPath, 'utf-8');
437
+ const newContent = content.slice(lastSize);
438
+ lastSize = currentSize;
439
+ for (const line of newContent.split('\n').filter(Boolean)) {
440
+ try {
441
+ const event = JSON.parse(line);
442
+ if (event.status === 'FAILED') {
443
+ if (useBell)
444
+ process.stdout.write('\u0007');
445
+ console.error(`\x1b[31m⚠ TASK FAILED: ${event.task_id}\x1b[0m`);
446
+ }
447
+ else if (event.status === 'BLOCKED') {
448
+ if (useBell)
449
+ process.stdout.write('\u0007');
450
+ const reason = event.reason ? ` — ${String(event.reason)}` : '';
451
+ console.warn(`\x1b[33m⚠ TASK BLOCKED: ${event.task_id}${reason}\x1b[0m`);
452
+ }
453
+ }
454
+ catch { /* skip malformed lines */ }
455
+ }
456
+ }
457
+ catch { /* skip watch errors */ }
458
+ });
459
+ }
240
460
  }
241
461
  catch (error) {
242
462
  console.error('❌ Failed to start server:', error);
243
463
  process.exit(1);
244
464
  }
245
465
  });
466
+ program
467
+ .command('spec')
468
+ .description('Initialize spec-mode templates (.claudedash/spec/)')
469
+ .action(() => {
470
+ const specDir = join(process.cwd(), '.claudedash', 'spec');
471
+ if (!existsSync(join(process.cwd(), '.claudedash'))) {
472
+ console.error('❌ Run "claudedash init" first.');
473
+ process.exit(1);
474
+ }
475
+ if (existsSync(specDir)) {
476
+ console.log('⚠️ .claudedash/spec/ already exists');
477
+ process.exit(1);
478
+ }
479
+ try {
480
+ mkdirSync(specDir, { recursive: true });
481
+ writeFileSync(join(specDir, 'plan.md'), `# Spec: Plan Phase
482
+
483
+ ## Goal
484
+ [One-sentence feature description]
485
+
486
+ ## Exploration Checklist
487
+ - [ ] Read existing code affected by this change
488
+ - [ ] Identify integration points and edge cases
489
+ - [ ] Propose approach (max 3 options, choose one)
490
+ - [ ] Get user approval before moving to implement phase
491
+
492
+ ## Decision
493
+ Chosen approach: [write here]
494
+ Approved: yes / no
495
+ `);
496
+ writeFileSync(join(specDir, 'implement.md'), `# Spec: Implement Phase
497
+
498
+ ## Acceptance Criteria
499
+ [Copy from queue.md task AC]
500
+
501
+ ## TDD Checklist
502
+ - [ ] Write failing test first
503
+ - [ ] Implement minimum code to pass
504
+ - [ ] Refactor if needed (tests still green)
505
+ - [ ] Run npm test — all pass
506
+
507
+ ## Files Changed
508
+ [List here]
509
+ `);
510
+ writeFileSync(join(specDir, 'verify.md'), `# Spec: Verify Phase
511
+
512
+ ## Review Checklist
513
+ - [ ] All AC items satisfied
514
+ - [ ] No regressions (full test suite passes)
515
+ - [ ] Lint clean
516
+ - [ ] Edge cases handled
517
+ - [ ] Peer/self review complete
518
+
519
+ ## Result
520
+ Status: DONE / FAILED
521
+ Notes: [write here]
522
+ `);
523
+ console.log('✓ Created .claudedash/spec/ (plan.md, implement.md, verify.md)');
524
+ console.log('\nUsage:');
525
+ console.log(' 1. Fill in spec/plan.md and get approval');
526
+ console.log(' 2. Follow spec/implement.md (TDD)');
527
+ console.log(' 3. Complete spec/verify.md checklist');
528
+ console.log(' 4. Log result to .claudedash/execution.log');
529
+ }
530
+ catch (error) {
531
+ console.error('❌ Failed to create spec templates:', error);
532
+ process.exit(1);
533
+ }
534
+ });
535
+ program
536
+ .command('worktree')
537
+ .description('Manage isolated worktrees for parallel agent runs')
538
+ .argument('<action>', 'Action: create')
539
+ .argument('[branch]', 'Branch name for create action')
540
+ .action((action, branch) => {
541
+ if (action !== 'create') {
542
+ console.error(`❌ Unknown action: ${action}. Supported: create`);
543
+ process.exit(1);
544
+ }
545
+ if (!branch) {
546
+ console.error('❌ Branch name required: claudedash worktree create <branch>');
547
+ process.exit(1);
548
+ }
549
+ const worktreePath = join(process.cwd(), '..', branch.replace(/\//g, '-'));
550
+ execFile('git', ['worktree', 'add', '-b', branch, worktreePath], (err, _stdout, stderr) => {
551
+ if (err) {
552
+ console.error('❌ Failed to create worktree:', stderr || err.message);
553
+ process.exit(1);
554
+ }
555
+ console.log(`✓ Worktree created at: ${worktreePath}`);
556
+ console.log(` Branch: ${branch}`);
557
+ console.log(`\nNext steps:`);
558
+ console.log(` cd ${worktreePath}`);
559
+ console.log(` claudedash init`);
560
+ console.log(` claudedash start`);
561
+ console.log(`\nThe dashboard Worktrees tab will show sessions for this branch.`);
562
+ });
563
+ });
564
+ program
565
+ .command('recover')
566
+ .description('Summarize the last Claude Code session after /clear')
567
+ .option('--claude-dir <path>', 'Path to Claude directory', join(process.env.HOME || '~', '.claude'))
568
+ .action((opts) => {
569
+ const claudeDir = opts.claudeDir;
570
+ const projectsDir = join(claudeDir, 'projects');
571
+ if (!existsSync(projectsDir)) {
572
+ console.error('❌ No projects directory found at', projectsDir);
573
+ process.exit(1);
574
+ }
575
+ // Map cwd to Claude project dir name (slashes → hyphens, no leading -)
576
+ // Walk up from cwd to find a matching project directory
577
+ let projectDir = '';
578
+ let searchPath = process.cwd();
579
+ while (searchPath !== '/') {
580
+ const candidate = join(projectsDir, searchPath.replace(/\//g, '-'));
581
+ if (existsSync(candidate)) {
582
+ projectDir = candidate;
583
+ break;
584
+ }
585
+ searchPath = join(searchPath, '..');
586
+ }
587
+ if (!projectDir) {
588
+ console.error(`❌ No session history found for this directory or any parent.`);
589
+ process.exit(1);
590
+ }
591
+ // Find most recently modified JSONL file
592
+ const jsonlFiles = readdirSync(projectDir)
593
+ .filter(f => f.endsWith('.jsonl'))
594
+ .map(f => ({ name: f, mtime: statSync(join(projectDir, f)).mtime.getTime() }))
595
+ .sort((a, b) => b.mtime - a.mtime);
596
+ if (jsonlFiles.length === 0) {
597
+ console.error('❌ No session files found.');
598
+ process.exit(1);
599
+ }
600
+ const latestFile = join(projectDir, jsonlFiles[0].name);
601
+ const lines = readFileSync(latestFile, 'utf-8').trim().split('\n').filter(Boolean);
602
+ // Extract key info from JSONL
603
+ let lastUserMsg = '';
604
+ let lastAssistantText = '';
605
+ let sessionId = '';
606
+ let sessionStart = '';
607
+ for (const line of lines) {
608
+ try {
609
+ const entry = JSON.parse(line);
610
+ if (entry.sessionId && !sessionId) {
611
+ sessionId = String(entry.sessionId);
612
+ }
613
+ if (!sessionStart && entry.type === 'user') {
614
+ const msg = entry.message;
615
+ if (msg?.role === 'user') {
616
+ const ts = entry.timestamp ?? entry.ts;
617
+ if (ts)
618
+ sessionStart = String(ts);
619
+ }
620
+ }
621
+ if (entry.type === 'user') {
622
+ const msg = entry.message;
623
+ const content = msg?.content;
624
+ if (typeof content === 'string')
625
+ lastUserMsg = content.slice(0, 200);
626
+ }
627
+ if (entry.type === 'assistant') {
628
+ const msg = entry.message;
629
+ const content = msg?.content;
630
+ if (Array.isArray(content)) {
631
+ for (const block of content) {
632
+ const b = block;
633
+ if (b.type === 'text' && typeof b.text === 'string') {
634
+ lastAssistantText = b.text.slice(0, 400);
635
+ }
636
+ }
637
+ }
638
+ }
639
+ }
640
+ catch { /* skip malformed lines */ }
641
+ }
642
+ console.log('\n📋 Session Recovery Summary');
643
+ console.log('─'.repeat(50));
644
+ console.log(`Session file : ${jsonlFiles[0].name}`);
645
+ console.log(`Total events : ${lines.length}`);
646
+ if (sessionStart)
647
+ console.log(`Started : ${sessionStart}`);
648
+ if (lastUserMsg) {
649
+ console.log('\n💬 Last user message:');
650
+ console.log(` ${lastUserMsg.replace(/\n/g, '\n ')}`);
651
+ }
652
+ if (lastAssistantText) {
653
+ console.log('\n🤖 Last assistant response (excerpt):');
654
+ console.log(` ${lastAssistantText.replace(/\n/g, '\n ')}`);
655
+ }
656
+ // Plan mode state
657
+ const claudeWatchDir = join(process.cwd(), '.claudedash');
658
+ const logPath = join(claudeWatchDir, 'execution.log');
659
+ const queuePath = join(claudeWatchDir, 'queue.md');
660
+ if (existsSync(logPath) && existsSync(queuePath)) {
661
+ console.log('\n📊 Plan Mode State:');
662
+ const logLines = readFileSync(logPath, 'utf-8').trim().split('\n').filter(Boolean);
663
+ const doneTasks = [];
664
+ let lastTask = '';
665
+ for (const l of logLines) {
666
+ try {
667
+ const e = JSON.parse(l);
668
+ const tid = String(e.task_id ?? '');
669
+ const status = String(e.status ?? '');
670
+ if (status === 'DONE')
671
+ doneTasks.push(tid);
672
+ lastTask = tid;
673
+ }
674
+ catch { /* skip */ }
675
+ }
676
+ console.log(` Completed tasks : ${doneTasks.length} (${doneTasks.join(', ') || 'none'})`);
677
+ if (lastTask)
678
+ console.log(` Last task : ${lastTask}`);
679
+ // Find next READY task
680
+ const queueContent = readFileSync(queuePath, 'utf-8');
681
+ const taskIds = [...queueContent.matchAll(/^## (S\d+-T\d+)/gm)].map(m => m[1]);
682
+ const doneSet = new Set(doneTasks);
683
+ const nextTask = taskIds.find(id => !doneSet.has(id));
684
+ if (nextTask)
685
+ console.log(` ➡ Next task : ${nextTask}`);
686
+ }
687
+ console.log('\n─'.repeat(50));
688
+ console.log('Run `claudedash start` to view the live dashboard.\n');
689
+ });
690
+ program
691
+ .command('doctor')
692
+ .description('Check claudedash environment and configuration')
693
+ .option('--claude-dir <path>', 'Path to Claude directory', join(process.env.HOME || '~', '.claude'))
694
+ .action(async (opts) => {
695
+ const claudeDir = opts.claudeDir;
696
+ const claudeWatchDir = join(process.cwd(), '.claudedash');
697
+ const checks = [];
698
+ // Node.js version
699
+ const nodeVer = process.version;
700
+ const major = parseInt(nodeVer.slice(1));
701
+ checks.push({ label: `Node.js ${nodeVer}`, ok: major >= 18, note: major < 18 ? 'Requires Node.js 18+' : undefined });
702
+ // git available
703
+ try {
704
+ const { execFileSync } = await import('child_process').then(m => m);
705
+ execFileSync('git', ['--version'], { stdio: 'ignore' });
706
+ checks.push({ label: 'git available', ok: true });
707
+ }
708
+ catch {
709
+ checks.push({ label: 'git available', ok: false, note: 'git not found in PATH' });
710
+ }
711
+ // ~/.claude/ directory
712
+ const claudeDirExists = existsSync(claudeDir);
713
+ checks.push({ label: `~/.claude/ directory (${claudeDir})`, ok: claudeDirExists, note: claudeDirExists ? undefined : 'Not found — Claude Code may not be installed' });
714
+ // ~/.claude/tasks/ (live mode)
715
+ const tasksDir = join(claudeDir, 'tasks');
716
+ checks.push({ label: '~/.claude/tasks/ (Live mode data)', ok: existsSync(tasksDir) });
717
+ // .claudedash/ (plan mode)
718
+ checks.push({ label: '.claudedash/ (Plan mode)', ok: existsSync(claudeWatchDir) });
719
+ // CLAUDE.md in cwd
720
+ const claudeMdPath = join(process.cwd(), 'CLAUDE.md');
721
+ const claudeMdExists = existsSync(claudeMdPath);
722
+ const hasTodoWrite = claudeMdExists && readFileSync(claudeMdPath, 'utf-8').includes('TodoWrite');
723
+ checks.push({ label: 'CLAUDE.md with TodoWrite directive', ok: hasTodoWrite, note: !claudeMdExists ? 'Not found — run claudedash init' : !hasTodoWrite ? 'TodoWrite missing — run claudedash init' : undefined });
724
+ // Port 4317 availability (simple check)
725
+ checks.push({ label: 'Default port 4317', ok: true, note: 'Cannot test without binding' });
726
+ // Print results
727
+ console.log('\n🩺 claudedash doctor\n');
728
+ for (const check of checks) {
729
+ const icon = check.ok ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
730
+ const note = check.note ? ` \x1b[33m(${check.note})\x1b[0m` : '';
731
+ console.log(` ${icon} ${check.label}${note}`);
732
+ }
733
+ const failed = checks.filter(c => !c.ok).length;
734
+ console.log(`\n${failed === 0 ? '\x1b[32mAll checks passed.\x1b[0m' : `\x1b[31m${failed} check(s) failed.\x1b[0m`}\n`);
735
+ });
246
736
  program.parse();
247
737
  //# sourceMappingURL=cli.js.map