@wundr.io/cli 1.0.1 → 1.0.2-dev.20260530174250.ef0ec927

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 (69) hide show
  1. package/bin/wundr.js +13 -5
  2. package/package.json +30 -9
  3. package/src/ai/ai-service.ts +6 -4
  4. package/src/ai/claude-client.ts +6 -2
  5. package/src/ai/conversation-manager.ts +12 -5
  6. package/src/cli.ts +42 -13
  7. package/src/commands/ai.ts +340 -64
  8. package/src/commands/alignment.ts +1212 -0
  9. package/src/commands/analyze-optimized.ts +371 -33
  10. package/src/commands/analyze.ts +8 -6
  11. package/src/commands/batch.ts +166 -26
  12. package/src/commands/chat.ts +20 -10
  13. package/src/commands/claude-init.ts +31 -27
  14. package/src/commands/claude-setup.ts +761 -81
  15. package/src/commands/computer-setup.ts +524 -12
  16. package/src/commands/create-command.ts +3 -3
  17. package/src/commands/create.ts +9 -6
  18. package/src/commands/dashboard.ts +11 -6
  19. package/src/commands/govern.ts +11 -6
  20. package/src/commands/governance.ts +1005 -0
  21. package/src/commands/guardian.ts +887 -0
  22. package/src/commands/init.ts +104 -11
  23. package/src/commands/orchestrator.ts +789 -0
  24. package/src/commands/performance-optimizer.ts +15 -10
  25. package/src/commands/plugins.ts +8 -5
  26. package/src/commands/project-update.ts +1156 -0
  27. package/src/commands/rag.ts +1011 -0
  28. package/src/commands/session.ts +631 -0
  29. package/src/commands/setup.ts +42 -344
  30. package/src/commands/test-init.ts +3 -2
  31. package/src/commands/test.ts +3 -2
  32. package/src/commands/watch.ts +21 -11
  33. package/src/commands/worktree.ts +1057 -0
  34. package/src/context/context-manager.ts +5 -2
  35. package/src/context/session-manager.ts +18 -7
  36. package/src/framework/command-interface.ts +520 -0
  37. package/src/framework/command-registry.ts +942 -0
  38. package/src/framework/completion-exporter.ts +383 -0
  39. package/src/framework/debug-logger.ts +519 -0
  40. package/src/framework/error-handler.ts +867 -0
  41. package/src/framework/help-generator.ts +540 -0
  42. package/src/framework/index.ts +169 -0
  43. package/src/framework/interactive-repl.ts +703 -0
  44. package/src/framework/output-formatter.ts +834 -0
  45. package/src/framework/progress-manager.ts +539 -0
  46. package/src/index.ts +3 -2
  47. package/src/interactive/interactive-mode.ts +14 -7
  48. package/src/lib/conflict-resolution.ts +818 -0
  49. package/src/lib/merge-strategy.ts +550 -0
  50. package/src/lib/safety-mechanisms.ts +451 -0
  51. package/src/lib/state-detection.ts +1030 -0
  52. package/src/nlp/command-mapper.ts +8 -3
  53. package/src/nlp/command-parser.ts +5 -2
  54. package/src/nlp/intent-parser.ts +23 -9
  55. package/src/plugins/plugin-manager.ts +50 -24
  56. package/src/tests/computer-setup-integration.test.ts +46 -15
  57. package/src/types/index.ts +1 -1
  58. package/src/types/modules.d.ts +425 -1
  59. package/src/utils/backup-rollback-manager.ts +19 -14
  60. package/src/utils/claude-config-installer.ts +119 -28
  61. package/src/utils/config-manager.ts +9 -6
  62. package/src/utils/error-handler.ts +3 -1
  63. package/src/utils/logger.ts +35 -12
  64. package/templates/batch/ci-cd.yaml +7 -7
  65. package/test-suites/api/health.spec.ts +20 -23
  66. package/test-suites/helpers/test-config.ts +14 -13
  67. package/test-suites/ui/accessibility.spec.ts +27 -22
  68. package/test-suites/ui/smoke.spec.ts +26 -21
  69. package/src/commands/computer-setup-commands.ts +0 -869
@@ -0,0 +1,631 @@
1
+ /**
2
+ * Session Management CLI Commands
3
+ * Manages coding agent sessions including listing, pausing, resuming, and killing sessions.
4
+ */
5
+
6
+ import * as fs from 'fs/promises';
7
+ import * as os from 'os';
8
+ import * as path from 'path';
9
+
10
+ import chalk from 'chalk';
11
+ import { Command } from 'commander';
12
+ import ora from 'ora';
13
+
14
+ // Constants
15
+ const SESSIONS_BASE_DIR = path.join(os.homedir(), '.wundr', 'sessions');
16
+ const SESSIONS_STATE_FILE = path.join(SESSIONS_BASE_DIR, 'state.json');
17
+
18
+ // Types
19
+ export type SessionStatus =
20
+ | 'active'
21
+ | 'paused'
22
+ | 'completed'
23
+ | 'error'
24
+ | 'terminated';
25
+
26
+ export interface AgentSession {
27
+ sessionId: string;
28
+ status: SessionStatus;
29
+ startedAt: string;
30
+ pausedAt?: string;
31
+ slotId: string;
32
+ worktreePath: string;
33
+ memoryBankPath?: string;
34
+ subAgents?: SubAgentInfo[];
35
+ taskDescription?: string;
36
+ lastActivity?: string;
37
+ metrics?: SessionMetrics;
38
+ }
39
+
40
+ export interface SubAgentInfo {
41
+ agentId: string;
42
+ type: string;
43
+ status: 'active' | 'idle' | 'terminated';
44
+ taskCount: number;
45
+ lastActivity?: string;
46
+ }
47
+
48
+ export interface SessionMetrics {
49
+ tasksCompleted: number;
50
+ tasksTotal: number;
51
+ tokensUsed: number;
52
+ duration: number;
53
+ errors: number;
54
+ }
55
+
56
+ export interface SessionsState {
57
+ version: string;
58
+ lastUpdated: string;
59
+ sessions: AgentSession[];
60
+ }
61
+
62
+ // Utility functions
63
+ function getTimestamp(): string {
64
+ return new Date().toISOString();
65
+ }
66
+
67
+ function formatDuration(ms: number): string {
68
+ const seconds = Math.floor(ms / 1000);
69
+ const minutes = Math.floor(seconds / 60);
70
+ const hours = Math.floor(minutes / 60);
71
+
72
+ if (hours > 0) {
73
+ return `${hours}h ${minutes % 60}m`;
74
+ }
75
+ if (minutes > 0) {
76
+ return `${minutes}m ${seconds % 60}s`;
77
+ }
78
+ return `${seconds}s`;
79
+ }
80
+
81
+ function padRight(str: string, length: number): string {
82
+ return str.length >= length
83
+ ? str.substring(0, length)
84
+ : str + ' '.repeat(length - str.length);
85
+ }
86
+
87
+ function truncate(str: string, length: number): string {
88
+ if (str.length <= length) {
89
+ return str;
90
+ }
91
+ return str.substring(0, length - 3) + '...';
92
+ }
93
+
94
+ async function ensureSessionsDir(): Promise<void> {
95
+ await fs.mkdir(SESSIONS_BASE_DIR, { recursive: true });
96
+ }
97
+
98
+ async function loadSessionsState(): Promise<SessionsState> {
99
+ try {
100
+ await ensureSessionsDir();
101
+ const content = await fs.readFile(SESSIONS_STATE_FILE, 'utf-8');
102
+ return JSON.parse(content) as SessionsState;
103
+ } catch {
104
+ // Return empty state if file doesn't exist
105
+ return {
106
+ version: '1.0.0',
107
+ lastUpdated: getTimestamp(),
108
+ sessions: [],
109
+ };
110
+ }
111
+ }
112
+
113
+ async function saveSessionsState(state: SessionsState): Promise<void> {
114
+ await ensureSessionsDir();
115
+ state.lastUpdated = getTimestamp();
116
+ await fs.writeFile(SESSIONS_STATE_FILE, JSON.stringify(state, null, 2));
117
+ }
118
+
119
+ function getStatusColor(status: SessionStatus): (str: string) => string {
120
+ switch (status) {
121
+ case 'active':
122
+ return chalk.green;
123
+ case 'paused':
124
+ return chalk.yellow;
125
+ case 'completed':
126
+ return chalk.blue;
127
+ case 'error':
128
+ return chalk.red;
129
+ case 'terminated':
130
+ return chalk.gray;
131
+ default:
132
+ return chalk.white;
133
+ }
134
+ }
135
+
136
+ function getStatusIcon(status: SessionStatus): string {
137
+ switch (status) {
138
+ case 'active':
139
+ return '[ACTIVE]';
140
+ case 'paused':
141
+ return '[PAUSED]';
142
+ case 'completed':
143
+ return '[DONE]';
144
+ case 'error':
145
+ return '[ERROR]';
146
+ case 'terminated':
147
+ return '[KILLED]';
148
+ default:
149
+ return '[UNKNOWN]';
150
+ }
151
+ }
152
+
153
+ // Create session command
154
+ export function createSessionCommand(): Command {
155
+ const command = new Command('session')
156
+ .description('Manage coding agent sessions')
157
+ .addHelpText(
158
+ 'after',
159
+ chalk.gray(`
160
+ Examples:
161
+ ${chalk.green('wundr session list')} List all active sessions
162
+ ${chalk.green('wundr session list --all')} List all sessions including completed
163
+ ${chalk.green('wundr session info <sessionId>')} Get detailed session information
164
+ ${chalk.green('wundr session pause <sessionId>')} Pause a running session
165
+ ${chalk.green('wundr session resume <sessionId>')}Resume a paused session
166
+ ${chalk.green('wundr session kill <sessionId>')} Terminate a session
167
+ `)
168
+ );
169
+
170
+ // List command (default)
171
+ command
172
+ .command('list', { isDefault: true })
173
+ .description('List sessions')
174
+ .option('-a, --all', 'Show all sessions including completed and terminated')
175
+ .option(
176
+ '-s, --status <status>',
177
+ 'Filter by status (active, paused, completed, error, terminated)'
178
+ )
179
+ .option('-f, --format <format>', 'Output format (table, json)', 'table')
180
+ .action(async options => {
181
+ await listSessions(options);
182
+ });
183
+
184
+ // Info command
185
+ command
186
+ .command('info <sessionId>')
187
+ .description('Get detailed information about a session')
188
+ .option('-f, --format <format>', 'Output format (table, json)', 'table')
189
+ .action(async (sessionId, options) => {
190
+ await showSessionInfo(sessionId, options);
191
+ });
192
+
193
+ // Pause command
194
+ command
195
+ .command('pause <sessionId>')
196
+ .description('Pause a running session')
197
+ .action(async sessionId => {
198
+ await pauseSession(sessionId);
199
+ });
200
+
201
+ // Resume command
202
+ command
203
+ .command('resume <sessionId>')
204
+ .description('Resume a paused session')
205
+ .action(async sessionId => {
206
+ await resumeSession(sessionId);
207
+ });
208
+
209
+ // Kill command
210
+ command
211
+ .command('kill <sessionId>')
212
+ .description('Terminate a session')
213
+ .option('--force', 'Force termination without confirmation')
214
+ .action(async (sessionId, options) => {
215
+ await killSession(sessionId, options);
216
+ });
217
+
218
+ return command;
219
+ }
220
+
221
+ // Command implementations
222
+ async function listSessions(options: {
223
+ all?: boolean;
224
+ status?: SessionStatus;
225
+ format?: 'table' | 'json';
226
+ }): Promise<void> {
227
+ const spinner = ora('Loading sessions...').start();
228
+
229
+ try {
230
+ const state = await loadSessionsState();
231
+ let sessions = state.sessions;
232
+
233
+ // Filter by status if provided
234
+ if (options.status) {
235
+ sessions = sessions.filter(s => s.status === options.status);
236
+ } else if (!options.all) {
237
+ // By default, only show active and paused sessions
238
+ sessions = sessions.filter(
239
+ s => s.status === 'active' || s.status === 'paused'
240
+ );
241
+ }
242
+
243
+ spinner.stop();
244
+
245
+ if (options.format === 'json') {
246
+ console.log(
247
+ JSON.stringify(
248
+ {
249
+ timestamp: getTimestamp(),
250
+ count: sessions.length,
251
+ sessions: sessions,
252
+ },
253
+ null,
254
+ 2
255
+ )
256
+ );
257
+ return;
258
+ }
259
+
260
+ console.log(chalk.cyan('\nSession List'));
261
+ console.log(chalk.gray('='.repeat(100)));
262
+
263
+ if (sessions.length === 0) {
264
+ console.log(chalk.yellow('\nNo sessions found.'));
265
+ if (!options.all && !options.status) {
266
+ console.log(
267
+ chalk.gray('Use --all to show completed and terminated sessions.')
268
+ );
269
+ }
270
+ console.log('');
271
+ return;
272
+ }
273
+
274
+ // Table header
275
+ console.log(
276
+ chalk.cyan(
277
+ padRight('Session ID', 20) +
278
+ padRight('Status', 12) +
279
+ padRight('Started At', 22) +
280
+ padRight('Slot ID', 10) +
281
+ padRight('Worktree Path', 36)
282
+ )
283
+ );
284
+ console.log(chalk.gray('-'.repeat(100)));
285
+
286
+ // Table rows
287
+ for (const session of sessions) {
288
+ const statusColor = getStatusColor(session.status);
289
+ const startedAt = new Date(session.startedAt).toLocaleString();
290
+ const worktreePath = truncate(session.worktreePath, 34);
291
+
292
+ console.log(
293
+ padRight(session.sessionId, 20) +
294
+ statusColor(padRight(getStatusIcon(session.status), 12)) +
295
+ padRight(startedAt, 22) +
296
+ padRight(session.slotId, 10) +
297
+ chalk.gray(padRight(worktreePath, 36))
298
+ );
299
+ }
300
+
301
+ console.log(chalk.gray('-'.repeat(100)));
302
+ console.log(chalk.gray(`Total: ${sessions.length} session(s)`));
303
+ console.log('');
304
+ } catch (error) {
305
+ spinner.fail('Failed to load sessions');
306
+ console.error(
307
+ chalk.red(error instanceof Error ? error.message : String(error))
308
+ );
309
+ }
310
+ }
311
+
312
+ async function showSessionInfo(
313
+ sessionId: string,
314
+ options: { format?: 'table' | 'json' }
315
+ ): Promise<void> {
316
+ const spinner = ora(`Loading session ${sessionId}...`).start();
317
+
318
+ try {
319
+ const state = await loadSessionsState();
320
+ const session = state.sessions.find(s => s.sessionId === sessionId);
321
+
322
+ if (!session) {
323
+ spinner.fail(`Session not found: ${sessionId}`);
324
+ return;
325
+ }
326
+
327
+ spinner.stop();
328
+
329
+ if (options.format === 'json') {
330
+ console.log(JSON.stringify(session, null, 2));
331
+ return;
332
+ }
333
+
334
+ console.log(chalk.cyan('\nSession Details'));
335
+ console.log(chalk.gray('='.repeat(60)));
336
+
337
+ // Basic info
338
+ const statusColor = getStatusColor(session.status);
339
+ console.log(
340
+ chalk.white('Session ID: ') + chalk.green(session.sessionId)
341
+ );
342
+ console.log(
343
+ chalk.white('Status: ') +
344
+ statusColor(getStatusIcon(session.status))
345
+ );
346
+ console.log(chalk.white('Slot ID: ') + session.slotId);
347
+ console.log(
348
+ chalk.white('Worktree Path: ') + chalk.gray(session.worktreePath)
349
+ );
350
+ console.log(
351
+ chalk.white('Started At: ') +
352
+ new Date(session.startedAt).toLocaleString()
353
+ );
354
+
355
+ if (session.pausedAt) {
356
+ console.log(
357
+ chalk.white('Paused At: ') +
358
+ new Date(session.pausedAt).toLocaleString()
359
+ );
360
+ }
361
+
362
+ if (session.lastActivity) {
363
+ console.log(
364
+ chalk.white('Last Activity: ') +
365
+ new Date(session.lastActivity).toLocaleString()
366
+ );
367
+ }
368
+
369
+ if (session.taskDescription) {
370
+ console.log(chalk.white('Task: ') + session.taskDescription);
371
+ }
372
+
373
+ // Memory bank status
374
+ console.log(chalk.gray('\n' + '-'.repeat(60)));
375
+ console.log(chalk.cyan('Memory Bank Status'));
376
+
377
+ if (session.memoryBankPath) {
378
+ console.log(chalk.white('Path: ') + chalk.gray(session.memoryBankPath));
379
+ // Check if memory bank exists
380
+ try {
381
+ await fs.access(session.memoryBankPath);
382
+ console.log(chalk.white('Status: ') + chalk.green('[AVAILABLE]'));
383
+ } catch {
384
+ console.log(chalk.white('Status: ') + chalk.yellow('[NOT FOUND]'));
385
+ }
386
+ } else {
387
+ console.log(chalk.gray('No memory bank configured.'));
388
+ }
389
+
390
+ // Sub-agents
391
+ console.log(chalk.gray('\n' + '-'.repeat(60)));
392
+ console.log(chalk.cyan('Sub-Agents'));
393
+
394
+ if (session.subAgents && session.subAgents.length > 0) {
395
+ console.log(
396
+ chalk.cyan(
397
+ padRight('Agent ID', 15) +
398
+ padRight('Type', 15) +
399
+ padRight('Status', 12) +
400
+ padRight('Tasks', 8)
401
+ )
402
+ );
403
+ console.log(chalk.gray('-'.repeat(50)));
404
+
405
+ for (const agent of session.subAgents) {
406
+ const agentStatusColor =
407
+ agent.status === 'active'
408
+ ? chalk.green
409
+ : agent.status === 'idle'
410
+ ? chalk.yellow
411
+ : chalk.gray;
412
+ console.log(
413
+ padRight(agent.agentId, 15) +
414
+ padRight(agent.type, 15) +
415
+ agentStatusColor(padRight(`[${agent.status.toUpperCase()}]`, 12)) +
416
+ padRight(String(agent.taskCount), 8)
417
+ );
418
+ }
419
+ } else {
420
+ console.log(chalk.gray('No sub-agents spawned.'));
421
+ }
422
+
423
+ // Metrics
424
+ if (session.metrics) {
425
+ console.log(chalk.gray('\n' + '-'.repeat(60)));
426
+ console.log(chalk.cyan('Session Metrics'));
427
+ console.log(
428
+ chalk.white('Tasks: ') +
429
+ `${session.metrics.tasksCompleted}/${session.metrics.tasksTotal}`
430
+ );
431
+ console.log(
432
+ chalk.white('Duration: ') + formatDuration(session.metrics.duration)
433
+ );
434
+ console.log(
435
+ chalk.white('Tokens: ') + session.metrics.tokensUsed.toLocaleString()
436
+ );
437
+ console.log(
438
+ chalk.white('Errors: ') +
439
+ (session.metrics.errors > 0 ? chalk.red(session.metrics.errors) : '0')
440
+ );
441
+ }
442
+
443
+ console.log(chalk.gray('='.repeat(60)));
444
+ console.log('');
445
+ } catch (error) {
446
+ spinner.fail('Failed to load session info');
447
+ console.error(
448
+ chalk.red(error instanceof Error ? error.message : String(error))
449
+ );
450
+ }
451
+ }
452
+
453
+ async function pauseSession(sessionId: string): Promise<void> {
454
+ const spinner = ora(`Pausing session ${sessionId}...`).start();
455
+
456
+ try {
457
+ const state = await loadSessionsState();
458
+ const sessionIndex = state.sessions.findIndex(
459
+ s => s.sessionId === sessionId
460
+ );
461
+
462
+ if (sessionIndex === -1) {
463
+ spinner.fail(`Session not found: ${sessionId}`);
464
+ return;
465
+ }
466
+
467
+ const session = state.sessions[sessionIndex];
468
+
469
+ if (!session) {
470
+ spinner.fail(`Session not found: ${sessionId}`);
471
+ return;
472
+ }
473
+
474
+ if (session.status !== 'active') {
475
+ spinner.fail(`Cannot pause session with status: ${session.status}`);
476
+ console.log(chalk.yellow('Only active sessions can be paused.'));
477
+ return;
478
+ }
479
+
480
+ // Update session status
481
+ session.status = 'paused';
482
+ session.pausedAt = getTimestamp();
483
+ session.lastActivity = getTimestamp();
484
+
485
+ await saveSessionsState(state);
486
+
487
+ spinner.succeed(`Session paused: ${sessionId}`);
488
+ console.log(
489
+ chalk.gray('Use "wundr session resume ' + sessionId + '" to resume.')
490
+ );
491
+ console.log('');
492
+ } catch (error) {
493
+ spinner.fail('Failed to pause session');
494
+ console.error(
495
+ chalk.red(error instanceof Error ? error.message : String(error))
496
+ );
497
+ }
498
+ }
499
+
500
+ async function resumeSession(sessionId: string): Promise<void> {
501
+ const spinner = ora(`Resuming session ${sessionId}...`).start();
502
+
503
+ try {
504
+ const state = await loadSessionsState();
505
+ const sessionIndex = state.sessions.findIndex(
506
+ s => s.sessionId === sessionId
507
+ );
508
+
509
+ if (sessionIndex === -1) {
510
+ spinner.fail(`Session not found: ${sessionId}`);
511
+ return;
512
+ }
513
+
514
+ const session = state.sessions[sessionIndex];
515
+
516
+ if (!session) {
517
+ spinner.fail(`Session not found: ${sessionId}`);
518
+ return;
519
+ }
520
+
521
+ if (session.status !== 'paused') {
522
+ spinner.fail(`Cannot resume session with status: ${session.status}`);
523
+ console.log(chalk.yellow('Only paused sessions can be resumed.'));
524
+ return;
525
+ }
526
+
527
+ // Update session status
528
+ session.status = 'active';
529
+ session.pausedAt = undefined;
530
+ session.lastActivity = getTimestamp();
531
+
532
+ await saveSessionsState(state);
533
+
534
+ spinner.succeed(`Session resumed: ${sessionId}`);
535
+ console.log(chalk.green('Session is now active.'));
536
+ console.log('');
537
+ } catch (error) {
538
+ spinner.fail('Failed to resume session');
539
+ console.error(
540
+ chalk.red(error instanceof Error ? error.message : String(error))
541
+ );
542
+ }
543
+ }
544
+
545
+ async function killSession(
546
+ sessionId: string,
547
+ options: { force?: boolean }
548
+ ): Promise<void> {
549
+ const spinner = ora(`Terminating session ${sessionId}...`).start();
550
+
551
+ try {
552
+ const state = await loadSessionsState();
553
+ const sessionIndex = state.sessions.findIndex(
554
+ s => s.sessionId === sessionId
555
+ );
556
+
557
+ if (sessionIndex === -1) {
558
+ spinner.fail(`Session not found: ${sessionId}`);
559
+ return;
560
+ }
561
+
562
+ const session = state.sessions[sessionIndex];
563
+
564
+ if (!session) {
565
+ spinner.fail(`Session not found: ${sessionId}`);
566
+ return;
567
+ }
568
+
569
+ if (session.status === 'terminated' || session.status === 'completed') {
570
+ spinner.fail(`Session already ${session.status}: ${sessionId}`);
571
+ return;
572
+ }
573
+
574
+ // Confirm if not forced
575
+ if (!options.force) {
576
+ spinner.stop();
577
+ const inquirer = await import('inquirer');
578
+ const answers = await inquirer.default.prompt([
579
+ {
580
+ type: 'confirm',
581
+ name: 'confirm',
582
+ message: `Are you sure you want to terminate session "${sessionId}"? This cannot be undone.`,
583
+ default: false,
584
+ },
585
+ ]);
586
+
587
+ if (!answers.confirm) {
588
+ console.log(chalk.yellow('Cancelled.'));
589
+ return;
590
+ }
591
+ spinner.start();
592
+ }
593
+
594
+ // Update session status
595
+ session.status = 'terminated';
596
+ session.lastActivity = getTimestamp();
597
+
598
+ // Terminate sub-agents
599
+ if (session.subAgents) {
600
+ for (const agent of session.subAgents) {
601
+ agent.status = 'terminated';
602
+ }
603
+ }
604
+
605
+ await saveSessionsState(state);
606
+
607
+ spinner.succeed(`Session terminated: ${sessionId}`);
608
+
609
+ // Show cleanup info
610
+ console.log(chalk.gray('\nCleanup completed:'));
611
+ if (session.subAgents && session.subAgents.length > 0) {
612
+ console.log(
613
+ chalk.gray(` - Terminated ${session.subAgents.length} sub-agent(s)`)
614
+ );
615
+ }
616
+ if (session.memoryBankPath) {
617
+ console.log(
618
+ chalk.gray(` - Memory bank preserved at: ${session.memoryBankPath}`)
619
+ );
620
+ }
621
+ console.log('');
622
+ } catch (error) {
623
+ spinner.fail('Failed to terminate session');
624
+ console.error(
625
+ chalk.red(error instanceof Error ? error.message : String(error))
626
+ );
627
+ }
628
+ }
629
+
630
+ // Export for registration
631
+ export default createSessionCommand;