kiku-agent 1.0.5 → 1.0.7

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.
@@ -2,10 +2,15 @@ import { toolsSchema, executeTool } from './tools.js';
2
2
  import { audioPlayer } from './audioPlayer.js';
3
3
  import { ensureYouTubeCookies, resetCookieCheck, getCookieStatus } from './youtubeAuth.js';
4
4
  import { AgentMemory } from './memory.js';
5
- import { C, UI_ICONS, printSolidPanel, randomFanName, createProgressRunner, wrapText, getFullWidth, getWelcomeHeroLines } from './ui.js';
6
- import { COMMAND_NAMES } from './commandRegistry.js';
5
+ import {
6
+ C, UI_ICONS, printSolidPanel, formatTerminalMarkdown, formatMarkdownLines, randomFanName, createProgressRunner, wrapText, getFullWidth, getWelcomeHeroLines, sanitizeTerminalText,
7
+ formatRichCardLines, printRichCard, printRichStatusCard, formatStatusBadge, formatRichTable
8
+ } from './ui.js';
9
+ import { COMMAND_NAMES, getCommandDetails, findCommandDefinition } from './commandRegistry.js';
7
10
  import { THEMES, THEME_NAMES, ansiBackground, ansiForeground, getThemeName, setTheme } from './theme.js';
8
11
  import { getGpuMetrics, formatProgressBar } from './gpuMetrics.js';
12
+ import { PACKAGE_NAME, PACKAGE_VERSION } from './version.js';
13
+ import { UpdateError, UpdateManager, UPDATE_CHANNELS } from './updateManager.js';
9
14
 
10
15
  function levenshteinDistance(a, b) {
11
16
  const matrix = Array.from({ length: a.length + 1 }, () => Array(b.length + 1).fill(0));
@@ -87,15 +92,50 @@ export const handlers = {
87
92
  },
88
93
 
89
94
  '/help': async (ctx) => {
90
- const sub = ctx.sub || ctx.extra || '';
95
+ const sub = (ctx.sub || ctx.extra || '').trim();
91
96
  if (typeof ctx.printHelp === 'function') {
92
97
  ctx.printHelp(sub);
98
+ return 'HANDLED';
99
+ }
100
+
101
+ if (sub) {
102
+ const rawName = sub.toLowerCase();
103
+ const cmdName = rawName.startsWith('/') ? rawName : `/${rawName}`;
104
+ const definition = findCommandDefinition(cmdName);
105
+ const details = getCommandDetails(cmdName);
106
+ if (definition || details) {
107
+ const titleName = definition?.name || cmdName;
108
+ const summary = details?.summary || definition?.desc || '';
109
+ const usage = details?.usage || definition?.usage || '';
110
+ const lines = [
111
+ `${C.bold}${C.white}${summary}${C.reset}`,
112
+ '',
113
+ `${C.bold}${C.lavender}Usage:${C.reset} ${C.cyberCyan}${usage}${C.reset}`,
114
+ ];
115
+ if (details?.subcommands?.length) {
116
+ lines.push('', `${C.bold}${C.roseGlow}Subcommands & Options:${C.reset}`);
117
+ for (const s of details.subcommands) {
118
+ lines.push(` ${C.bold}${C.gold}${s.cmd.padEnd(24)}${C.reset} ${C.white}${s.desc}${C.reset}`);
119
+ if (s.example) lines.push(` ${C.dim}↳ Example: ${C.cyberCyan}${s.example}${C.reset}`);
120
+ }
121
+ }
122
+ if (details?.examples?.length) {
123
+ lines.push('', `${C.bold}${C.emerald}Quick Examples:${C.reset}`);
124
+ for (const ex of details.examples) {
125
+ lines.push(` ${C.cyberCyan}❯ ${ex}${C.reset}`);
126
+ }
127
+ }
128
+ printSolidPanel({ title: `Command Help • ${titleName}`, lines });
129
+ return 'HANDLED';
130
+ }
93
131
  }
132
+
133
+ console.log(`\n ${C.bold}Type /help <command> for detailed subcommand breakdown.${C.reset}\n`);
94
134
  return 'HANDLED';
95
135
  },
96
136
 
97
137
  '/details': async (ctx) => {
98
- ctx.printQueryDetails(ctx.sub || '');
138
+ ctx.printQueryDetails([ctx.sub, ctx.extra].filter(Boolean).join(' '));
99
139
  return 'HANDLED';
100
140
  },
101
141
 
@@ -131,7 +171,7 @@ export const handlers = {
131
171
  }
132
172
 
133
173
  lines.push(`${C.bold}${C.roseGlow}Findings Report:${C.reset}`);
134
- for (const fLine of String(latest.answer || 'No report generated.').split('\n')) {
174
+ for (const fLine of formatMarkdownLines(latest.answer || 'No report generated.')) {
135
175
  lines.push(` ${fLine}`);
136
176
  }
137
177
 
@@ -172,6 +212,7 @@ export const handlers = {
172
212
  });
173
213
 
174
214
  const startTime = Date.now();
215
+ ctx.setIsProcessing?.(true, `Subagent: ${task.slice(0, 24)}`);
175
216
  try {
176
217
  const result = await ctx.agent.runSubagent(task, {
177
218
  onProgress: event => {
@@ -200,7 +241,7 @@ export const handlers = {
200
241
  }
201
242
 
202
243
  lines.push(`${C.bold}${C.roseGlow}Synthesized Evidence:${C.reset}`);
203
- for (const line of String(result.answer || 'No findings returned.').split('\n')) {
244
+ for (const line of formatMarkdownLines(result.answer || 'No findings returned.')) {
204
245
  lines.push(` ${line}`);
205
246
  }
206
247
 
@@ -213,6 +254,8 @@ export const handlers = {
213
254
  });
214
255
  } catch (error) {
215
256
  console.log(`\n ${C.red}[SUBAGENT ERROR] ${error.message}${C.reset}\n`);
257
+ } finally {
258
+ ctx.setIsProcessing?.(false);
216
259
  }
217
260
  return 'HANDLED';
218
261
  },
@@ -306,6 +349,7 @@ export const handlers = {
306
349
  });
307
350
 
308
351
  const startTime = Date.now();
352
+ ctx.setIsProcessing?.(true, `Research: ${question.slice(0, 24)}`);
309
353
  try {
310
354
  const result = await ctx.agent.runResearch(question, {
311
355
  onProgress: event => {
@@ -319,7 +363,7 @@ export const handlers = {
319
363
  `${C.bold}${C.emerald}[DONE] Research synthesis completed in ${elapsed}s across 3 workers.${C.reset}`,
320
364
  '',
321
365
  `${C.bold}${C.roseGlow}Comprehensive Findings:${C.reset}`,
322
- ...String(result.answer).split('\n').map(l => ` ${l}`),
366
+ ...formatMarkdownLines(result.answer || 'No findings returned.').map(l => ` ${l}`),
323
367
  '',
324
368
  `${C.bold}${C.lavender}Validated Sources (${result.sources.length}):${C.reset}`,
325
369
  ...(result.sources.length > 0 ? result.sources.map((s, i) => ` [${i + 1}] ${C.cyberCyan}${s}${C.reset}`) : [` ${C.dim}Catalog sources${C.reset}`]),
@@ -331,6 +375,8 @@ export const handlers = {
331
375
  });
332
376
  } catch (error) {
333
377
  console.log(`\n ${C.red}[RESEARCH ERROR] ${error.message}${C.reset}\n`);
378
+ } finally {
379
+ ctx.setIsProcessing?.(false);
334
380
  }
335
381
  return 'HANDLED';
336
382
  },
@@ -364,6 +410,7 @@ export const handlers = {
364
410
  });
365
411
 
366
412
  const startTime = Date.now();
413
+ ctx.setIsProcessing?.(true, `Verifying: ${claim.slice(0, 24)}`);
367
414
  try {
368
415
  const result = await ctx.agent.runResearch(claim, { mode: 'verify' });
369
416
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
@@ -382,7 +429,7 @@ export const handlers = {
382
429
  `${C.bold}Verdict:${C.reset} ${verdictBadge} ${C.dim}(Checked in ${elapsed}s)${C.reset}`,
383
430
  '',
384
431
  `${C.bold}${C.roseGlow}Verification Breakdown:${C.reset}`,
385
- ...answerStr.split('\n').map(l => ` ${l}`),
432
+ ...formatMarkdownLines(answerStr).map(l => ` ${l}`),
386
433
  '',
387
434
  `${C.bold}${C.lavender}Evidence & Sources (${result.sources.length}):${C.reset}`,
388
435
  ...(result.sources.length > 0 ? result.sources.map((s, i) => ` [${i + 1}] ${C.cyberCyan}${s}${C.reset}`) : [` ${C.dim}Catalog data verification${C.reset}`])
@@ -394,6 +441,8 @@ export const handlers = {
394
441
  });
395
442
  } catch (error) {
396
443
  console.log(`\n ${C.red}[VERIFY ERROR] ${error.message}${C.reset}\n`);
444
+ } finally {
445
+ ctx.setIsProcessing?.(false);
397
446
  }
398
447
  return 'HANDLED';
399
448
  },
@@ -407,6 +456,138 @@ export const handlers = {
407
456
  return 'HANDLED';
408
457
  },
409
458
 
459
+ '/suggestion': async (ctx) => {
460
+ const rawAction = (ctx.sub || 'list').toLowerCase();
461
+ const extra = (ctx.extra || '').trim();
462
+
463
+ if (rawAction === 'run' || rawAction === 'exec' || rawAction === 'start') {
464
+ const targets = extra || 'all';
465
+
466
+ printRichCard({
467
+ title: 'Multi-Subagent Suggestions Execution',
468
+ status: 'autostart',
469
+ lines: [
470
+ `• Target Questions: ${targets}`,
471
+ '• Dispatching bounded concurrent ResearchSubagents...',
472
+ ],
473
+ });
474
+
475
+ const startTime = Date.now();
476
+ ctx.setIsProcessing?.(true, `Suggestions: ${targets.slice(0, 20)}`);
477
+ try {
478
+ const batchResult = await ctx.agent.runSuggestionBatch(targets, {
479
+ onProgress: event => {
480
+ if (event.type === 'worker-start') {
481
+ console.log(` ${C.cyberCyan}▸ Worker ${event.index} Started:${C.reset} ${C.bold}${event.label}${C.reset} — ${C.dim}${event.question}${C.reset}`);
482
+ } else if (event.type === 'tool') {
483
+ console.log(` ${C.dim}[Worker] Executing tool: ${event.name}...${C.reset}`);
484
+ } else if (event.type === 'worker-complete') {
485
+ console.log(` ${C.emerald}[✓] Worker ${event.index} Finished:${C.reset} ${event.label}`);
486
+ }
487
+ },
488
+ });
489
+
490
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
491
+ console.log('');
492
+ printRichCard({
493
+ title: `Subagent Batch Complete (${batchResult.successfulCount}/${batchResult.tasksCount} Succeeded)`,
494
+ status: 'ok',
495
+ lines: [
496
+ `Completed ${batchResult.tasksCount} research tasks concurrently in ${elapsed}s.`,
497
+ ],
498
+ });
499
+
500
+ for (const res of batchResult.results) {
501
+ if (res.answer) {
502
+ const answerLines = formatMarkdownLines(res.answer);
503
+ printSolidPanel({
504
+ title: `[Worker ${res.index}] ${res.category} • ${res.question}`,
505
+ lines: [
506
+ `${C.bold}${C.cyberCyan}Question:${C.reset} ${res.question}`,
507
+ `${C.dim}Tool calls: ${res.toolCalls?.length || 0} • Iterations: ${res.iterations || 1}${C.reset}`,
508
+ '',
509
+ `${C.bold}${C.roseGlow}Findings:${C.reset}`,
510
+ ...answerLines,
511
+ ],
512
+ });
513
+ } else {
514
+ printRichCard({
515
+ title: `Worker ${res.index} Failed: ${res.category}`,
516
+ status: 'error',
517
+ lines: [
518
+ `Question: ${res.question}`,
519
+ `Error: ${res.error || 'Failed to research'}`,
520
+ ],
521
+ });
522
+ }
523
+ }
524
+ } catch (err) {
525
+ printRichCard({
526
+ title: 'Batch Subagent Error',
527
+ status: 'error',
528
+ lines: [err.message],
529
+ });
530
+ } finally {
531
+ ctx.setIsProcessing?.(false);
532
+ }
533
+ return 'HANDLED';
534
+ }
535
+
536
+ if (rawAction === 'auto') {
537
+ console.log(`\n ${C.cyberCyan}Generating fresh suggestions and launching concurrent subagents...${C.reset}\n`);
538
+ ctx.setIsProcessing?.(true, 'Auto Suggestions');
539
+ try {
540
+ await ctx.agent.generateSuggestions({ limit: 3 });
541
+ } finally {
542
+ ctx.setIsProcessing?.(false);
543
+ }
544
+ return handlers['/suggestion']({ ...ctx, sub: 'run', extra: 'all' });
545
+ }
546
+
547
+ const isTopic = rawAction === 'topic';
548
+ const topicName = isTopic ? extra : (rawAction !== 'list' && rawAction !== 'refresh' ? rawAction : '');
549
+
550
+ try {
551
+ console.log(`\n ${C.cyberCyan}✦ Fetching AI research suggestions...${C.reset}`);
552
+ ctx.setIsProcessing?.(true, 'Fetching Suggestions');
553
+ const suggestions = await ctx.agent.generateSuggestions({
554
+ topic: topicName,
555
+ limit: 5,
556
+ });
557
+
558
+ const cardLines = [
559
+ `${C.dim}Explore exciting Ju Jingyi topics with concurrent subagents:${C.reset}`,
560
+ '',
561
+ ...suggestions.flatMap((s) => [
562
+ ` ${C.bold}${C.gold}[${s.id}]${C.reset} ${C.bold}${C.cyberCyan}[${s.category}]${C.reset} ${C.white}${s.question}${C.reset}`,
563
+ ` ${C.dim}↳ ${s.description}${C.reset}`,
564
+ ]),
565
+ '',
566
+ `${C.bold}${C.roseGlow}Quick Execution Tips:${C.reset}`,
567
+ ` • Run one task: ${C.white}/suggestion run 1${C.reset}`,
568
+ ` • Run parallel batch: ${C.white}/suggestion run 1,2,3${C.reset}`,
569
+ ` • Run all concurrently: ${C.white}/suggestion run all${C.reset}`,
570
+ ` • Auto-generate & run: ${C.white}/suggestion auto${C.reset}`,
571
+ ` • Filter by topic: ${C.white}/suggestion topic drama | music | brand | 2026${C.reset}`,
572
+ ];
573
+
574
+ printRichCard({
575
+ title: `AI Research Suggestions (${suggestions.length} Questions Generated)`,
576
+ status: 'info',
577
+ lines: cardLines,
578
+ });
579
+ } catch (err) {
580
+ printRichCard({
581
+ title: 'Suggestion Error',
582
+ status: 'error',
583
+ lines: [err.message],
584
+ });
585
+ } finally {
586
+ ctx.setIsProcessing?.(false);
587
+ }
588
+ return 'HANDLED';
589
+ },
590
+
410
591
  '/skill': async (ctx) => {
411
592
  const action = (ctx.sub || 'list').toLowerCase();
412
593
  const memory = ctx.agent.memory;
@@ -976,8 +1157,12 @@ export const handlers = {
976
1157
  },
977
1158
 
978
1159
  '/save': async (ctx) => {
979
- const exported = await ctx.agent.session.exportToMarkdown();
980
- console.log(`\n ${C.emerald}[OK] Exported.${C.reset} ${C.white}${exported.filePath}${C.reset}\n`);
1160
+ try {
1161
+ const exported = await ctx.agent.session.exportToMarkdown();
1162
+ console.log(`\n ${C.emerald}[OK] Exported.${C.reset} ${C.white}${exported.filePath}${C.reset}\n`);
1163
+ } catch (err) {
1164
+ console.log(`\n ${C.red}[SAVE ERROR] ${err.message || String(err)}${C.reset}\n`);
1165
+ }
981
1166
  return 'HANDLED';
982
1167
  },
983
1168
 
@@ -1013,7 +1198,7 @@ export const handlers = {
1013
1198
 
1014
1199
  if (!targetProvider) {
1015
1200
  console.log(`\n ${C.amber}Usage: /provider use <provider_name> [model_id]${C.reset}`);
1016
- console.log(` ${C.dim}Example: /provider use groq llama-3.1-8b-instant${C.reset}\n`);
1201
+ console.log(` ${C.dim}Example: /provider use groq openai/gpt-oss-120b${C.reset}\n`);
1017
1202
  return 'HANDLED';
1018
1203
  }
1019
1204
 
@@ -1021,10 +1206,16 @@ export const handlers = {
1021
1206
  const options = targetModel ? { model: targetModel } : {};
1022
1207
  const info = ctx.client.switchProvider(targetProvider, options);
1023
1208
  if (ctx.agent?.client) ctx.agent.client.switchProvider(targetProvider, options);
1024
- if (ctx.modelManager) {
1025
- ctx.modelManager.client = ctx.client;
1026
- ctx.modelManager.requestedModel = info.model;
1027
- }
1209
+ if (ctx.modelManager) {
1210
+ ctx.modelManager.client = ctx.client;
1211
+ ctx.modelManager.requestedModel = info.model;
1212
+ const resolution = await ctx.modelManager.resolve({ requestedModel: info.model });
1213
+ if (resolution.connected && resolution.usedFallback) {
1214
+ console.log(` ${C.amber}• Requested model unavailable; using ${resolution.selectedModel}${C.reset}`);
1215
+ } else if (!resolution.connected) {
1216
+ console.log(` ${C.amber}• Provider is not ready: ${resolution.error}${C.reset}`);
1217
+ }
1218
+ }
1028
1219
  ctx.baseUrl = info.baseUrl;
1029
1220
 
1030
1221
  console.log(`\n ${C.emerald}[OK] Switched provider to ${C.bold}${info.providerName}${C.reset} (${info.provider})`);
@@ -1288,7 +1479,11 @@ export const handlers = {
1288
1479
  const mcpManager = ctx.agent?.mcpManager;
1289
1480
 
1290
1481
  if (!mcpManager) {
1291
- console.log(`\n ${C.red}[MCP ERROR] MCP Client Manager is unavailable.${C.reset}\n`);
1482
+ printRichCard({
1483
+ title: 'MCP Error',
1484
+ status: 'error',
1485
+ lines: ['MCP Client Manager is unavailable.'],
1486
+ });
1292
1487
  return 'HANDLED';
1293
1488
  }
1294
1489
 
@@ -1300,9 +1495,10 @@ export const handlers = {
1300
1495
  } else {
1301
1496
  for (const [idx, t] of tools.entries()) {
1302
1497
  const f = t.function;
1303
- const params = Object.keys(f.parameters?.properties || {}).join(', ');
1304
- lines.push(`${C.gold}${idx + 1}.${C.reset} ${C.bold}${C.cyberCyan}${f.name}${C.reset}${C.slate}(${params})${C.reset}`);
1305
- const desc = f.description || 'No description provided';
1498
+ const toolName = sanitizeTerminalText(f.name, 128) || 'unknown_tool';
1499
+ const params = Object.keys(f.parameters?.properties || {}).map(param => sanitizeTerminalText(param, 80)).join(', ');
1500
+ lines.push(`${C.gold}${idx + 1}.${C.reset} ${C.bold}${C.cyberCyan}${toolName}${C.reset}${C.slate}(${params})${C.reset}`);
1501
+ const desc = sanitizeTerminalText(f.description || 'No description provided');
1306
1502
  const wrapped = wrapText(desc, Math.max(30, getFullWidth() - 8));
1307
1503
  for (const dLine of wrapped) {
1308
1504
  lines.push(` ${C.dim}${dLine}${C.reset}`);
@@ -1316,7 +1512,7 @@ export const handlers = {
1316
1512
  return 'HANDLED';
1317
1513
  }
1318
1514
 
1319
- if (action === 'connect') {
1515
+ if (action === 'connect' || action === 'add') {
1320
1516
  const parts = (ctx.parts || []).slice(2);
1321
1517
  const name = parts[0];
1322
1518
  const targetUrlOrType = parts[1];
@@ -1325,11 +1521,11 @@ export const handlers = {
1325
1521
  printSolidPanel({
1326
1522
  title: 'MCP Connect Usage',
1327
1523
  lines: [
1328
- `${C.bold}Connect to Remote HTTP MCP:${C.reset}`,
1524
+ `${C.bold}Connect & Save Remote HTTP MCP:${C.reset}`,
1329
1525
  ` ${C.cyberCyan}/mcp connect <name> <url>${C.reset}`,
1330
1526
  ` ${C.dim}Example: /mcp connect kiku https://jujingyi-kikuuu.vercel.app/mcp${C.reset}`,
1331
1527
  '',
1332
- `${C.bold}Connect to Stdio MCP Process:${C.reset}`,
1528
+ `${C.bold}Connect & Save Stdio MCP Process:${C.reset}`,
1333
1529
  ` ${C.cyberCyan}/mcp connect <name> stdio <command> [args...]${C.reset}`,
1334
1530
  ` ${C.dim}Example: /mcp connect local-kiku stdio node ./server/mcp/stdio.js${C.reset}`,
1335
1531
  ]
@@ -1357,11 +1553,12 @@ export const handlers = {
1357
1553
  const res = await ctx.agent.connectMcp(name, serverConfig);
1358
1554
 
1359
1555
  runner.done({
1360
- title: `MCP Server Connected • "${name}"`,
1556
+ title: `MCP Server Connected & Saved • "${name}"`,
1361
1557
  lines: [
1362
1558
  `${C.gold}Server:${C.reset} ${C.bold}${C.white}${name}${C.reset} ${C.dim}(${serverConfig.type})${C.reset}`,
1363
1559
  `${C.gold}Target:${C.reset} ${C.cyberCyan}${targetUrlOrType}${C.reset}`,
1364
1560
  `${C.gold}Capabilities:${C.reset} ${C.emerald}${res.toolsCount} Tools${C.reset} ${C.slate}•${C.reset} ${C.cyberCyan}${res.resourcesCount} Resources${C.reset} ${C.slate}•${C.reset} ${C.lavender}${res.promptsCount} Prompts${C.reset}`,
1561
+ `${C.gold}Persisted:${C.reset} ${C.emerald}Saved to configuration (auto-reconnects on start)${C.reset}`,
1365
1562
  `${C.gold}Integration:${C.reset} ${C.dim}Tools prefixed with ${C.cyberCyan}mcp_${name}_*${C.dim} and auto-routed to agent${C.reset}`,
1366
1563
  '',
1367
1564
  `${C.dim}Tip: Run /mcp tools to inspect all registered tools or /mcp status for overview.${C.reset}`
@@ -1375,7 +1572,81 @@ export const handlers = {
1375
1572
  return 'HANDLED';
1376
1573
  }
1377
1574
 
1378
- if (action === 'disconnect' || action === 'rm' || action === 'del') {
1575
+ if (action === 'save') {
1576
+ const target = (ctx.extra || 'global').toLowerCase().trim();
1577
+ const saveRes = await ctx.agent.saveMcpConfig(target);
1578
+ if (saveRes.success) {
1579
+ printRichCard({
1580
+ title: 'MCP Configuration Saved',
1581
+ status: 'ok',
1582
+ lines: [
1583
+ `• Target: ${target === 'workspace' ? 'Workspace (.mcp.json)' : 'Global config (mcp_servers.json)'}`,
1584
+ `• File: ${saveRes.filePath}`,
1585
+ `• Total servers persisted: ${saveRes.count}`,
1586
+ ],
1587
+ });
1588
+ } else {
1589
+ printRichCard({
1590
+ title: 'MCP Save Failed',
1591
+ status: 'error',
1592
+ lines: [saveRes.error || 'Failed to write MCP config'],
1593
+ });
1594
+ }
1595
+ return 'HANDLED';
1596
+ }
1597
+
1598
+ if (action === 'reload' || action === 'reconnect') {
1599
+ const target = (ctx.extra || '').trim();
1600
+ printRichCard({
1601
+ title: 'Reloading MCP Configuration',
1602
+ status: 'autostart',
1603
+ lines: [`Reconnecting saved MCP servers...`],
1604
+ });
1605
+ try {
1606
+ await ctx.agent.reconnectMcp(target || 'all');
1607
+ const active = ctx.agent.mcpManager.servers.size;
1608
+ printRichCard({
1609
+ title: 'MCP Servers Reconnected',
1610
+ status: 'ok',
1611
+ lines: [
1612
+ `Active live servers: ${active}`,
1613
+ `Total tools available: ${ctx.agent.mcpManager.getToolsSchema().length}`,
1614
+ ],
1615
+ });
1616
+ } catch (err) {
1617
+ printRichCard({
1618
+ title: 'MCP Reconnect Error',
1619
+ status: 'error',
1620
+ lines: [err.message],
1621
+ });
1622
+ }
1623
+ return 'HANDLED';
1624
+ }
1625
+
1626
+ if (action === 'remove' || action === 'rm' || action === 'del') {
1627
+ const name = (ctx.extra || '').trim();
1628
+ if (!name) {
1629
+ console.log(`\n ${C.amber}Usage: /mcp remove <server_name>${C.reset}\n`);
1630
+ return 'HANDLED';
1631
+ }
1632
+ const success = await ctx.agent.removeMcp(name);
1633
+ if (success) {
1634
+ printRichCard({
1635
+ title: 'MCP Server Removed',
1636
+ status: 'ok',
1637
+ lines: [`Removed "${name}" from saved configuration and disconnected.`],
1638
+ });
1639
+ } else {
1640
+ printRichCard({
1641
+ title: 'MCP Server Not Found',
1642
+ status: 'warning',
1643
+ lines: [`Server "${name}" was not found in saved configuration.`],
1644
+ });
1645
+ }
1646
+ return 'HANDLED';
1647
+ }
1648
+
1649
+ if (action === 'disconnect') {
1379
1650
  const name = (ctx.extra || '').trim();
1380
1651
  if (!name) {
1381
1652
  console.log(`\n ${C.amber}Usage: /mcp disconnect <server_name>${C.reset}\n`);
@@ -1383,45 +1654,81 @@ export const handlers = {
1383
1654
  }
1384
1655
  const success = await ctx.agent.disconnectMcp(name);
1385
1656
  if (success) {
1386
- console.log(`\n ${C.emerald}[OK] Disconnected MCP server "${name}".${C.reset}\n`);
1657
+ printRichCard({
1658
+ title: 'MCP Server Disconnected',
1659
+ status: 'info',
1660
+ lines: [
1661
+ `Disconnected session for "${name}".`,
1662
+ `Config remains saved. Run /mcp reconnect ${name} to reconnect.`,
1663
+ ],
1664
+ });
1387
1665
  } else {
1388
- console.log(`\n ${C.amber}MCP server "${name}" not found.${C.reset}\n`);
1666
+ printRichCard({
1667
+ title: 'MCP Server Not Found',
1668
+ status: 'warning',
1669
+ lines: [`MCP server "${name}" is not currently connected.`],
1670
+ });
1389
1671
  }
1390
1672
  return 'HANDLED';
1391
1673
  }
1392
1674
 
1393
1675
  const status = mcpManager.getStatus();
1394
1676
  const lines = [
1395
- `${C.gold}Active Servers:${C.reset} ${C.cyberCyan}${status.activeServersCount}${C.reset} • ${C.gold}Total MCP Tools:${C.reset} ${C.cyberCyan}${status.totalToolsCount}${C.reset}`,
1677
+ `${C.gold}Active Live Servers:${C.reset} ${C.cyberCyan}${status.activeServersCount}${C.reset} • ${C.gold}Total Configured:${C.reset} ${C.white}${status.totalConfiguredCount}${C.reset} • ${C.gold}Total Tools:${C.reset} ${C.cyberCyan}${status.totalToolsCount}${C.reset}`,
1396
1678
  ''
1397
1679
  ];
1398
1680
 
1399
1681
  if (status.servers.length > 0) {
1400
- lines.push(`${C.bold}${C.lavender}Connected Servers:${C.reset}`);
1682
+ lines.push(`${C.bold}${C.lavender}Configured MCP Servers:${C.reset}`);
1401
1683
  for (const s of status.servers) {
1402
- lines.push(` ${C.emerald}●${C.reset} ${C.bold}${C.white}${s.name}${C.reset} ${C.dim}(${s.type})${C.reset} — ${s.toolsCount} tools, ${s.resourcesCount} resources`);
1684
+ const serverName = sanitizeTerminalText(s.name, 128) || 'unnamed';
1685
+ const serverType = sanitizeTerminalText(s.type, 80) || 'unknown';
1686
+ if (s.status === 'connected') {
1687
+ lines.push(` ${C.emerald}● Connected:${C.reset} ${C.bold}${C.white}${serverName}${C.reset} ${C.dim}(${serverType})${C.reset} — ${C.emerald}${s.toolsCount} tools${C.reset}, ${s.resourcesCount} resources`);
1688
+ } else if (s.status === 'saved_offline') {
1689
+ lines.push(` ${C.slate}○ Saved (Offline):${C.reset} ${C.bold}${C.white}${serverName}${C.reset} ${C.dim}(${serverType})${C.reset} — ${C.dim}type /mcp reconnect ${serverName} to start${C.reset}`);
1690
+ } else {
1691
+ lines.push(` ${C.red}✕ Error:${C.reset} ${C.bold}${C.white}${serverName}${C.reset} ${C.dim}(${serverType})${C.reset} — ${C.red}${s.error || 'Connection failed'}${C.reset}`);
1692
+ }
1693
+ }
1694
+
1695
+ const connectedTools = mcpManager.getToolsSchema();
1696
+ if (connectedTools.length > 0) {
1697
+ lines.push('', `${C.bold}${C.lavender}Available MCP Tool Commands (${connectedTools.length}):${C.reset}`);
1698
+ const commandList = connectedTools
1699
+ .map(tool => `@${sanitizeTerminalText(tool.function?.name, 128) || 'unknown_tool'}`)
1700
+ .join(' • ');
1701
+ for (const commandLine of wrapText(commandList, Math.max(30, getFullWidth() - 8))) {
1702
+ lines.push(` ${C.cyberCyan}${commandLine}${C.reset}`);
1703
+ }
1704
+ lines.push(`${C.dim}Use /mcp tools for parameters and full descriptions.${C.reset}`);
1403
1705
  }
1404
1706
  } else {
1405
- lines.push(`${C.dim}No active MCP servers connected.${C.reset}`);
1406
- lines.push(`Connect with: ${C.cyberCyan}/mcp connect <name> <url>${C.reset}`);
1707
+ lines.push(`${C.dim}No MCP servers configured.${C.reset}`);
1708
+ lines.push(`Connect and save with: ${C.cyberCyan}/mcp connect <name> <url>${C.reset}`);
1407
1709
  }
1408
1710
 
1711
+ lines.push('', `${C.dim}Commands: /mcp connect <name> <url> | /mcp reconnect [all] | /mcp save | /mcp remove <name> | /mcp tools${C.reset}`);
1712
+
1409
1713
  printSolidPanel({
1410
- title: `Model Context Protocol (MCP) Client`,
1714
+ title: `Model Context Protocol (MCP) Manager • ${status.activeServersCount} Active / ${status.totalConfiguredCount} Saved`,
1411
1715
  lines,
1412
- headerBg: '\x1b[48;2;48;54;70m', bg: '\x1b[48;2;36;40;52m'
1413
1716
  });
1414
1717
  return 'HANDLED';
1415
1718
  },
1416
1719
 
1417
1720
  '/ping': async (ctx) => {
1418
- const start = Date.now();
1419
- const check = await ctx.client.checkConnection();
1420
- const ms = Date.now() - start;
1421
- if (check.connected) {
1422
- console.log(`\n ${C.emerald}● Connected${C.reset} ${C.dim}(${ms}ms)${C.reset} — ${C.white}${check.activeModel}${C.reset}\n`);
1423
- } else {
1424
- console.log(`\n ${C.red}● Disconnected${C.reset} ${C.dim}(${ms}ms)${C.reset}\n`);
1721
+ try {
1722
+ const start = Date.now();
1723
+ const check = await ctx.client.checkConnection();
1724
+ const ms = Date.now() - start;
1725
+ if (check.connected) {
1726
+ console.log(`\n ${C.emerald}● Connected${C.reset} ${C.dim}(${ms}ms)${C.reset} — ${C.white}${check.activeModel || check.model || 'Ready'}${C.reset}\n`);
1727
+ } else {
1728
+ console.log(`\n ${C.red}● Disconnected${C.reset} ${C.dim}(${ms}ms)${C.reset} — ${C.amber}${check.error || 'Server unreachable'}${C.reset}\n`);
1729
+ }
1730
+ } catch (err) {
1731
+ console.log(`\n ${C.red}[PING ERROR] ${err.message || String(err)}${C.reset}\n`);
1425
1732
  }
1426
1733
  return 'HANDLED';
1427
1734
  },
@@ -1444,15 +1751,19 @@ export const handlers = {
1444
1751
  },
1445
1752
 
1446
1753
  '/doctor': async (ctx) => {
1447
- const { runDoctor } = await import('./doctor.js');
1448
- const report = await runDoctor({ client: ctx.client });
1449
- printSolidPanel({
1450
- title: `Kiku Doctor • ${report.ok ? 'READY' : 'ACTION REQUIRED'}`,
1451
- lines: report.checks.map(check => {
1452
- const marker = check.ok ? '[OK]' : check.required ? '[FAIL]' : '[OPTIONAL]';
1453
- return `${marker} ${check.id}: ${check.detail}`;
1454
- }),
1455
- });
1754
+ try {
1755
+ const { runDoctor } = await import('./doctor.js');
1756
+ const report = await runDoctor({ client: ctx.client });
1757
+ printSolidPanel({
1758
+ title: `Kiku Doctor • ${report.ok ? 'READY' : 'ACTION REQUIRED'}`,
1759
+ lines: report.checks.map(check => {
1760
+ const marker = check.ok ? '[OK]' : check.required ? '[FAIL]' : '[OPTIONAL]';
1761
+ return `${marker} ${check.id}: ${check.detail}`;
1762
+ }),
1763
+ });
1764
+ } catch (err) {
1765
+ console.log(`\n ${C.red}[DOCTOR ERROR] ${err.message || String(err)}${C.reset}\n`);
1766
+ }
1456
1767
  return 'HANDLED';
1457
1768
  },
1458
1769
 
@@ -1460,7 +1771,8 @@ export const handlers = {
1460
1771
  printSolidPanel({
1461
1772
  title: 'Version',
1462
1773
  lines: [
1463
- `${C.gold}Version:${C.reset} ${C.white}1.0.5${C.reset}`,
1774
+ `${C.gold}Package:${C.reset} ${C.white}${PACKAGE_NAME}${C.reset}`,
1775
+ `${C.gold}Version:${C.reset} ${C.white}${PACKAGE_VERSION}${C.reset}`,
1464
1776
  `${C.gold}Node:${C.reset} ${C.white}${process.version}${C.reset}`,
1465
1777
  `${C.gold}Platform:${C.reset} ${C.white}${process.platform} ${process.arch}${C.reset}`,
1466
1778
  ],
@@ -1468,6 +1780,186 @@ export const handlers = {
1468
1780
  return 'HANDLED';
1469
1781
  },
1470
1782
 
1783
+ '/update': async (ctx) => {
1784
+ const manager = ctx.updateManager || new UpdateManager();
1785
+ const action = (ctx.sub || 'check').toLowerCase();
1786
+ const parts = ctx.parts || [];
1787
+ const allowMajor = parts.includes('--allow-major');
1788
+ const allowDeprecated = parts.includes('--allow-deprecated');
1789
+ const positional = parts.slice(2).filter(part => !part.startsWith('--'));
1790
+ const flags = parts.slice(2).filter(part => part.startsWith('--'));
1791
+
1792
+ const printUpdateHelp = () => printSolidPanel({
1793
+ title: 'Kiku Agent Update',
1794
+ lines: [
1795
+ `${C.bold}${C.white}/update${C.reset} ${C.dim}or /update check — check the configured channel${C.reset}`,
1796
+ `${C.bold}${C.white}/update install [latest|next|canary|x.y.z]${C.reset}`,
1797
+ `${C.bold}${C.white}/update channel [latest|next|canary]${C.reset}`,
1798
+ `${C.bold}${C.white}/update rollback${C.reset} ${C.dim}— reinstall the previously recorded version${C.reset}`,
1799
+ '',
1800
+ `${C.dim}Major releases require --allow-major. Deprecated releases require --allow-deprecated.${C.reset}`,
1801
+ ],
1802
+ });
1803
+
1804
+ const statusLines = status => {
1805
+ const changeLabel = status.change === 'upgrade'
1806
+ ? `${C.emerald}UPDATE AVAILABLE${C.reset}`
1807
+ : status.change === 'downgrade'
1808
+ ? `${C.amber}OLDER RELEASE${C.reset}`
1809
+ : `${C.emerald}UP TO DATE${C.reset}`;
1810
+ const lines = [
1811
+ `${C.gold}Current:${C.reset} ${C.white}${status.currentVersion}${C.reset}`,
1812
+ `${C.gold}Target:${C.reset} ${C.white}${status.targetVersion}${C.reset} ${C.dim}(${status.target})${C.reset}`,
1813
+ `${C.gold}Status:${C.reset} ${changeLabel}`,
1814
+ `${C.gold}Installation:${C.reset} ${C.white}${status.installMode.label}${C.reset}`,
1815
+ `${C.gold}Node:${C.reset} ${status.nodeCompatible ? C.emerald + 'Compatible' : C.red + 'Incompatible'}${C.reset}${status.nodeRange ? ` ${C.dim}(requires ${status.nodeRange})${C.reset}` : ''}`,
1816
+ `${C.gold}Integrity:${C.reset} ${status.integrity ? C.emerald + 'Published with npm integrity metadata' : C.amber + 'Not reported'}${C.reset}`,
1817
+ ];
1818
+ if (status.majorChange) lines.push(`${C.amber}[MAJOR] This release crosses a major-version boundary.${C.reset}`);
1819
+ if (status.deprecated) lines.push(`${C.red}[DEPRECATED] ${status.deprecated}${C.reset}`);
1820
+ lines.push('', `${C.bold}${C.lavender}What's new:${C.reset}`);
1821
+ if (status.releaseNotes.length > 0) {
1822
+ for (const note of status.releaseNotes) lines.push(` ${C.sakura}•${C.reset} ${C.white}${note}${C.reset}`);
1823
+ } else {
1824
+ lines.push(` ${C.dim}No release summary was published for this version.${C.reset}`);
1825
+ }
1826
+ if (status.releaseUrl) lines.push('', `${C.dim}Release: ${status.releaseUrl}${C.reset}`);
1827
+ return lines;
1828
+ };
1829
+
1830
+ try {
1831
+ const allowedFlags = action === 'install'
1832
+ ? ['--allow-major', '--allow-deprecated']
1833
+ : action === 'rollback'
1834
+ ? ['--allow-deprecated']
1835
+ : [];
1836
+ if (flags.some(flag => !allowedFlags.includes(flag))) {
1837
+ throw new UpdateError('Unknown option. Run /update help for supported arguments.', 'INVALID_OPTION');
1838
+ }
1839
+ if (positional.length > 1 || ((action === 'rollback' || action === 'help') && positional.length > 0)) {
1840
+ throw new UpdateError('Too many update arguments. Run /update help for usage.', 'INVALID_ARGUMENTS');
1841
+ }
1842
+
1843
+ if (action === 'help') {
1844
+ printUpdateHelp();
1845
+ return 'HANDLED';
1846
+ }
1847
+
1848
+ if (action === 'channel') {
1849
+ const requested = positional[0];
1850
+ if (!requested) {
1851
+ const channel = await manager.getChannel();
1852
+ printSolidPanel({
1853
+ title: 'Update Channel',
1854
+ lines: [
1855
+ `${C.gold}Current:${C.reset} ${C.white}${channel}${C.reset}`,
1856
+ `${C.dim}Available: ${UPDATE_CHANNELS.join(', ')}${C.reset}`,
1857
+ ],
1858
+ });
1859
+ return 'HANDLED';
1860
+ }
1861
+ const channel = await manager.setChannel(requested);
1862
+ printSolidPanel({
1863
+ title: 'Update Channel Changed',
1864
+ lines: [
1865
+ `${C.emerald}[OK] Future /update checks will use ${C.bold}${channel}${C.reset}${C.emerald}.${C.reset}`,
1866
+ `${C.dim}This changes only the release channel; it does not install anything.${C.reset}`,
1867
+ ],
1868
+ });
1869
+ return 'HANDLED';
1870
+ }
1871
+
1872
+ if (action === 'install') {
1873
+ const target = positional[0];
1874
+ console.log(`\n ${C.cyberCyan}[UPDATE] Resolving and validating the requested npm release...${C.reset}\n`);
1875
+ const result = await manager.install(target, {
1876
+ allowMajor,
1877
+ allowDeprecated,
1878
+ onProgress: message => { if (message) console.log(` ${C.dim}${message}${C.reset}`); },
1879
+ });
1880
+ if (result.outcome === 'manual') {
1881
+ printSolidPanel({
1882
+ title: 'Manual Update Required',
1883
+ lines: [
1884
+ ...statusLines(result),
1885
+ '',
1886
+ `${C.amber}This installation type is not modified automatically.${C.reset}`,
1887
+ `${C.gold}Run:${C.reset} ${C.white}${result.command}${C.reset}`,
1888
+ ],
1889
+ });
1890
+ return 'HANDLED';
1891
+ }
1892
+ if (result.outcome === 'current') {
1893
+ printSolidPanel({ title: 'Kiku Agent Is Up to Date', lines: statusLines(result) });
1894
+ return 'HANDLED';
1895
+ }
1896
+ printSolidPanel({
1897
+ title: 'Update Installed Successfully',
1898
+ lines: [
1899
+ `${C.emerald}[OK] ${PACKAGE_NAME} ${result.installedVersion} was installed and verified.${C.reset}`,
1900
+ ...(!result.historyRecorded ? [`${C.amber}[WARNING] Rollback history could not be saved.${C.reset}`] : []),
1901
+ `${C.dim}This session will close. Start kiku-agent again to load the new code.${C.reset}`,
1902
+ `${C.dim}Restart MCP hosts to load the matching kiku-mcp server version.${C.reset}`,
1903
+ ],
1904
+ });
1905
+ try { await ctx.agent?.endSession?.(); } catch {}
1906
+ return 'EXIT';
1907
+ }
1908
+
1909
+ if (action === 'rollback') {
1910
+ console.log(`\n ${C.cyberCyan}[UPDATE] Resolving the previously installed version...${C.reset}\n`);
1911
+ const result = await manager.rollback({ allowDeprecated });
1912
+ if (result.outcome === 'manual') {
1913
+ printSolidPanel({
1914
+ title: 'Manual Rollback Required',
1915
+ lines: [
1916
+ `${C.amber}Run the following command outside Kiku Agent:${C.reset}`,
1917
+ `${C.white}${result.command}${C.reset}`,
1918
+ ],
1919
+ });
1920
+ return 'HANDLED';
1921
+ }
1922
+ if (result.outcome === 'current') {
1923
+ printSolidPanel({ title: 'Rollback Not Needed', lines: statusLines(result) });
1924
+ return 'HANDLED';
1925
+ }
1926
+ printSolidPanel({
1927
+ title: 'Rollback Installed Successfully',
1928
+ lines: [
1929
+ `${C.emerald}[OK] Restored ${PACKAGE_NAME} ${result.installedVersion}.${C.reset}`,
1930
+ ...(!result.historyRecorded ? [`${C.amber}[WARNING] Rollback history could not be saved.${C.reset}`] : []),
1931
+ `${C.dim}This session will close. Restart Kiku Agent and any MCP host.${C.reset}`,
1932
+ ],
1933
+ });
1934
+ try { await ctx.agent?.endSession?.(); } catch {}
1935
+ return 'EXIT';
1936
+ }
1937
+
1938
+ if (action === 'check' || action === 'status') {
1939
+ const status = await manager.check(positional[0]);
1940
+ const lines = statusLines(status);
1941
+ if (status.updateAvailable) {
1942
+ lines.push('', `${C.cyberCyan}Run /update install${status.target === 'latest' ? '' : ` ${status.target}`} to update.${C.reset}`);
1943
+ }
1944
+ printSolidPanel({ title: 'Kiku Agent Update', lines });
1945
+ return 'HANDLED';
1946
+ }
1947
+
1948
+ printUpdateHelp();
1949
+ return 'HANDLED';
1950
+ } catch (error) {
1951
+ const safeMessage = error instanceof UpdateError ? error.message : 'The update operation failed unexpectedly.';
1952
+ printSolidPanel({
1953
+ title: 'Update Failed',
1954
+ lines: [
1955
+ `${C.red}[${error.code || 'UPDATE_ERROR'}] ${safeMessage}${C.reset}`,
1956
+ `${C.dim}No session, memory, or environment files were changed.${C.reset}`,
1957
+ ],
1958
+ });
1959
+ return 'HANDLED';
1960
+ }
1961
+ },
1962
+
1471
1963
  '/system': async () => {
1472
1964
  const os = await import('node:os');
1473
1965
  const mem = process.memoryUsage();
@@ -1564,11 +2056,16 @@ export const handlers = {
1564
2056
  '/login': async (ctx) => {
1565
2057
  console.log(`\n ${C.cyberCyan}YouTube Login${C.reset}\n`);
1566
2058
  resetCookieCheck();
1567
- const result = await ensureYouTubeCookies({ timeout: 180000 });
1568
- if (result.ok) {
1569
- console.log(` ${C.emerald}[OK] Session active (${result.source}).${C.reset}\n`);
1570
- } else {
1571
- console.log(` ${C.red}[ERROR] Not available: ${result.reason}${C.reset}\n`);
2059
+ ctx.setIsProcessing?.(true, 'YouTube Login');
2060
+ try {
2061
+ const result = await ensureYouTubeCookies({ timeout: 180000 });
2062
+ if (result.ok) {
2063
+ console.log(` ${C.emerald}[OK] Session active (${result.source}).${C.reset}\n`);
2064
+ } else {
2065
+ console.log(` ${C.red}[ERROR] Not available: ${result.reason}${C.reset}\n`);
2066
+ }
2067
+ } finally {
2068
+ ctx.setIsProcessing?.(false);
1572
2069
  }
1573
2070
  return 'HANDLED';
1574
2071
  },
@@ -1612,19 +2109,23 @@ export const handlers = {
1612
2109
  },
1613
2110
 
1614
2111
  '/stats': async () => {
1615
- const result = await executeTool('get_system_stats', {});
1616
- const s = result.stats;
1617
- printSolidPanel({
1618
- title: `Database Overview`,
1619
- lines: [
1620
- `${C.gold}Works:${C.reset} ${C.cyberCyan}${s.totalWorks}${C.reset}`,
1621
- `${C.gold}Songs:${C.reset} ${C.cyberCyan}${s.totalUniqueSongs}${C.reset}`,
1622
- `${C.gold}Brands:${C.reset} ${C.cyberCyan}${s.brandEndorsements}${C.reset}`,
1623
- `${C.gold}Awards:${C.reset} ${C.cyberCyan}${s.totalAwards}${C.reset}`,
1624
- `${C.gold}Timeline:${C.reset} ${C.cyberCyan}${s.timelineMilestones}${C.reset}`
1625
- ],
1626
- headerBg: '\x1b[48;2;48;54;70m', bg: '\x1b[48;2;36;40;52m'
1627
- });
2112
+ try {
2113
+ const result = await executeTool('get_system_stats', {});
2114
+ const s = result.stats || {};
2115
+ printSolidPanel({
2116
+ title: `Database Overview`,
2117
+ lines: [
2118
+ `${C.gold}Works:${C.reset} ${C.cyberCyan}${s.totalWorks || 0}${C.reset}`,
2119
+ `${C.gold}Songs:${C.reset} ${C.cyberCyan}${s.totalUniqueSongs || 0}${C.reset}`,
2120
+ `${C.gold}Brands:${C.reset} ${C.cyberCyan}${s.brandEndorsements || 0}${C.reset}`,
2121
+ `${C.gold}Awards:${C.reset} ${C.cyberCyan}${s.totalAwards || 0}${C.reset}`,
2122
+ `${C.gold}Timeline:${C.reset} ${C.cyberCyan}${s.timelineMilestones || 0}${C.reset}`
2123
+ ],
2124
+ headerBg: '\x1b[48;2;48;54;70m', bg: '\x1b[48;2;36;40;52m'
2125
+ });
2126
+ } catch (err) {
2127
+ console.log(`\n ${C.red}[STATS ERROR] ${err.message || String(err)}${C.reset}\n`);
2128
+ }
1628
2129
  return 'HANDLED';
1629
2130
  },
1630
2131
 
@@ -1635,6 +2136,70 @@ export const handlers = {
1635
2136
  return 'HANDLED';
1636
2137
  },
1637
2138
 
2139
+ '/cancel': async (ctx) => {
2140
+ if (typeof ctx.cancelActiveQuery === 'function') {
2141
+ const ok = ctx.cancelActiveQuery();
2142
+ if (ok) {
2143
+ printRichCard({
2144
+ title: 'Query Cancelled',
2145
+ status: 'cancel',
2146
+ lines: ['Active query and queued tasks were cancelled.'],
2147
+ });
2148
+ } else {
2149
+ printRichCard({
2150
+ title: 'No Active Query',
2151
+ status: 'info',
2152
+ lines: ['There is no query currently running.'],
2153
+ });
2154
+ }
2155
+ return 'HANDLED';
2156
+ }
2157
+ if (ctx.agent && typeof ctx.agent.cancelResearch === 'function') {
2158
+ ctx.agent.cancelResearch();
2159
+ }
2160
+ return 'HANDLED';
2161
+ },
2162
+
2163
+ '/copy': async (ctx) => {
2164
+ if (typeof ctx.copyQueryDetails === 'function') {
2165
+ const reference = [ctx.sub, ctx.extra].filter(Boolean).join(' ');
2166
+ ctx.copyQueryDetails(reference);
2167
+ return 'HANDLED';
2168
+ }
2169
+ const { getCopyableContent, copyToClipboard, parseDetailsRequest } = await import('./queryDetails.js');
2170
+ const reference = [ctx.sub, ctx.extra].filter(Boolean).join(' ');
2171
+ const request = parseDetailsRequest(reference);
2172
+ const details = ctx.agent?.lastQueryDetails || null;
2173
+ if (!details) {
2174
+ printRichCard({
2175
+ title: 'No Diagnostics Recorded',
2176
+ status: 'warning',
2177
+ lines: ['No completed model queries are available to copy.'],
2178
+ });
2179
+ return 'HANDLED';
2180
+ }
2181
+ const copyable = getCopyableContent(details, request);
2182
+ if (!copyable?.text) {
2183
+ printRichCard({
2184
+ title: 'Nothing to Copy',
2185
+ status: 'warning',
2186
+ lines: ['The requested query section is empty.'],
2187
+ });
2188
+ return 'HANDLED';
2189
+ }
2190
+ const copied = copyToClipboard(copyable.text);
2191
+ printRichCard({
2192
+ title: copied ? 'Copied to Clipboard' : 'Copy Content Ready',
2193
+ status: copied ? 'ok' : 'info',
2194
+ lines: [
2195
+ `• Target: ${copyable.title}`,
2196
+ `• Characters: ${copyable.text.length} chars (${copyable.text.split('\n').length} lines)`,
2197
+ ...(copied ? ['• Status: Copied to system clipboard (Ready to paste with Ctrl+V / Cmd+V)'] : []),
2198
+ ],
2199
+ });
2200
+ return 'HANDLED';
2201
+ },
2202
+
1638
2203
  '/ads': async (ctx) => {
1639
2204
  ctx.printAd();
1640
2205
  return 'HANDLED';
@@ -1643,6 +2208,8 @@ export const handlers = {
1643
2208
 
1644
2209
  const ALIASES = {
1645
2210
  '/quit': '/exit', '/q': '/exit',
2211
+ '/abort': '/cancel',
2212
+ '/cp': '/copy', '/clip': '/copy', '/clipboard': '/copy',
1646
2213
  '/?': '/help', '/commands': '/help', '/menu': '/help',
1647
2214
  '/hist': '/history', '/h': '/history',
1648
2215
  '/sess': '/session', '/sessions': '/session',
@@ -1650,6 +2217,7 @@ const ALIASES = {
1650
2217
  '/detail': '/details', '/d': '/details',
1651
2218
  '/language': '/lang',
1652
2219
  '/themes': '/theme',
2220
+ '/suggest': '/suggestion', '/suggestions': '/suggestion',
1653
2221
  '/exec': '/tool', '/call': '/tool',
1654
2222
  '/toollist': '/tools',
1655
2223
  '/metrics': '/stats', '/catalog': '/stats',
@@ -1690,5 +2258,9 @@ export async function routeCommand(rawInput, ctx) {
1690
2258
  return 'UNKNOWN';
1691
2259
  }
1692
2260
 
2261
+ if (sub.toLowerCase() === 'help' && rawCmd !== '/help') {
2262
+ return handlers['/help']({ ...ctx, sub: rawCmd, extra: '' });
2263
+ }
2264
+
1693
2265
  return await handler({ ...ctx, sub, extra, parts });
1694
2266
  }