kiku-agent 1.0.5 → 1.0.6

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
 
@@ -1288,7 +1473,11 @@ export const handlers = {
1288
1473
  const mcpManager = ctx.agent?.mcpManager;
1289
1474
 
1290
1475
  if (!mcpManager) {
1291
- console.log(`\n ${C.red}[MCP ERROR] MCP Client Manager is unavailable.${C.reset}\n`);
1476
+ printRichCard({
1477
+ title: 'MCP Error',
1478
+ status: 'error',
1479
+ lines: ['MCP Client Manager is unavailable.'],
1480
+ });
1292
1481
  return 'HANDLED';
1293
1482
  }
1294
1483
 
@@ -1300,9 +1489,10 @@ export const handlers = {
1300
1489
  } else {
1301
1490
  for (const [idx, t] of tools.entries()) {
1302
1491
  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';
1492
+ const toolName = sanitizeTerminalText(f.name, 128) || 'unknown_tool';
1493
+ const params = Object.keys(f.parameters?.properties || {}).map(param => sanitizeTerminalText(param, 80)).join(', ');
1494
+ lines.push(`${C.gold}${idx + 1}.${C.reset} ${C.bold}${C.cyberCyan}${toolName}${C.reset}${C.slate}(${params})${C.reset}`);
1495
+ const desc = sanitizeTerminalText(f.description || 'No description provided');
1306
1496
  const wrapped = wrapText(desc, Math.max(30, getFullWidth() - 8));
1307
1497
  for (const dLine of wrapped) {
1308
1498
  lines.push(` ${C.dim}${dLine}${C.reset}`);
@@ -1316,7 +1506,7 @@ export const handlers = {
1316
1506
  return 'HANDLED';
1317
1507
  }
1318
1508
 
1319
- if (action === 'connect') {
1509
+ if (action === 'connect' || action === 'add') {
1320
1510
  const parts = (ctx.parts || []).slice(2);
1321
1511
  const name = parts[0];
1322
1512
  const targetUrlOrType = parts[1];
@@ -1325,11 +1515,11 @@ export const handlers = {
1325
1515
  printSolidPanel({
1326
1516
  title: 'MCP Connect Usage',
1327
1517
  lines: [
1328
- `${C.bold}Connect to Remote HTTP MCP:${C.reset}`,
1518
+ `${C.bold}Connect & Save Remote HTTP MCP:${C.reset}`,
1329
1519
  ` ${C.cyberCyan}/mcp connect <name> <url>${C.reset}`,
1330
1520
  ` ${C.dim}Example: /mcp connect kiku https://jujingyi-kikuuu.vercel.app/mcp${C.reset}`,
1331
1521
  '',
1332
- `${C.bold}Connect to Stdio MCP Process:${C.reset}`,
1522
+ `${C.bold}Connect & Save Stdio MCP Process:${C.reset}`,
1333
1523
  ` ${C.cyberCyan}/mcp connect <name> stdio <command> [args...]${C.reset}`,
1334
1524
  ` ${C.dim}Example: /mcp connect local-kiku stdio node ./server/mcp/stdio.js${C.reset}`,
1335
1525
  ]
@@ -1357,11 +1547,12 @@ export const handlers = {
1357
1547
  const res = await ctx.agent.connectMcp(name, serverConfig);
1358
1548
 
1359
1549
  runner.done({
1360
- title: `MCP Server Connected • "${name}"`,
1550
+ title: `MCP Server Connected & Saved • "${name}"`,
1361
1551
  lines: [
1362
1552
  `${C.gold}Server:${C.reset} ${C.bold}${C.white}${name}${C.reset} ${C.dim}(${serverConfig.type})${C.reset}`,
1363
1553
  `${C.gold}Target:${C.reset} ${C.cyberCyan}${targetUrlOrType}${C.reset}`,
1364
1554
  `${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}`,
1555
+ `${C.gold}Persisted:${C.reset} ${C.emerald}Saved to configuration (auto-reconnects on start)${C.reset}`,
1365
1556
  `${C.gold}Integration:${C.reset} ${C.dim}Tools prefixed with ${C.cyberCyan}mcp_${name}_*${C.dim} and auto-routed to agent${C.reset}`,
1366
1557
  '',
1367
1558
  `${C.dim}Tip: Run /mcp tools to inspect all registered tools or /mcp status for overview.${C.reset}`
@@ -1375,7 +1566,81 @@ export const handlers = {
1375
1566
  return 'HANDLED';
1376
1567
  }
1377
1568
 
1378
- if (action === 'disconnect' || action === 'rm' || action === 'del') {
1569
+ if (action === 'save') {
1570
+ const target = (ctx.extra || 'global').toLowerCase().trim();
1571
+ const saveRes = await ctx.agent.saveMcpConfig(target);
1572
+ if (saveRes.success) {
1573
+ printRichCard({
1574
+ title: 'MCP Configuration Saved',
1575
+ status: 'ok',
1576
+ lines: [
1577
+ `• Target: ${target === 'workspace' ? 'Workspace (.mcp.json)' : 'Global config (mcp_servers.json)'}`,
1578
+ `• File: ${saveRes.filePath}`,
1579
+ `• Total servers persisted: ${saveRes.count}`,
1580
+ ],
1581
+ });
1582
+ } else {
1583
+ printRichCard({
1584
+ title: 'MCP Save Failed',
1585
+ status: 'error',
1586
+ lines: [saveRes.error || 'Failed to write MCP config'],
1587
+ });
1588
+ }
1589
+ return 'HANDLED';
1590
+ }
1591
+
1592
+ if (action === 'reload' || action === 'reconnect') {
1593
+ const target = (ctx.extra || '').trim();
1594
+ printRichCard({
1595
+ title: 'Reloading MCP Configuration',
1596
+ status: 'autostart',
1597
+ lines: [`Reconnecting saved MCP servers...`],
1598
+ });
1599
+ try {
1600
+ await ctx.agent.reconnectMcp(target || 'all');
1601
+ const active = ctx.agent.mcpManager.servers.size;
1602
+ printRichCard({
1603
+ title: 'MCP Servers Reconnected',
1604
+ status: 'ok',
1605
+ lines: [
1606
+ `Active live servers: ${active}`,
1607
+ `Total tools available: ${ctx.agent.mcpManager.getToolsSchema().length}`,
1608
+ ],
1609
+ });
1610
+ } catch (err) {
1611
+ printRichCard({
1612
+ title: 'MCP Reconnect Error',
1613
+ status: 'error',
1614
+ lines: [err.message],
1615
+ });
1616
+ }
1617
+ return 'HANDLED';
1618
+ }
1619
+
1620
+ if (action === 'remove' || action === 'rm' || action === 'del') {
1621
+ const name = (ctx.extra || '').trim();
1622
+ if (!name) {
1623
+ console.log(`\n ${C.amber}Usage: /mcp remove <server_name>${C.reset}\n`);
1624
+ return 'HANDLED';
1625
+ }
1626
+ const success = await ctx.agent.removeMcp(name);
1627
+ if (success) {
1628
+ printRichCard({
1629
+ title: 'MCP Server Removed',
1630
+ status: 'ok',
1631
+ lines: [`Removed "${name}" from saved configuration and disconnected.`],
1632
+ });
1633
+ } else {
1634
+ printRichCard({
1635
+ title: 'MCP Server Not Found',
1636
+ status: 'warning',
1637
+ lines: [`Server "${name}" was not found in saved configuration.`],
1638
+ });
1639
+ }
1640
+ return 'HANDLED';
1641
+ }
1642
+
1643
+ if (action === 'disconnect') {
1379
1644
  const name = (ctx.extra || '').trim();
1380
1645
  if (!name) {
1381
1646
  console.log(`\n ${C.amber}Usage: /mcp disconnect <server_name>${C.reset}\n`);
@@ -1383,45 +1648,81 @@ export const handlers = {
1383
1648
  }
1384
1649
  const success = await ctx.agent.disconnectMcp(name);
1385
1650
  if (success) {
1386
- console.log(`\n ${C.emerald}[OK] Disconnected MCP server "${name}".${C.reset}\n`);
1651
+ printRichCard({
1652
+ title: 'MCP Server Disconnected',
1653
+ status: 'info',
1654
+ lines: [
1655
+ `Disconnected session for "${name}".`,
1656
+ `Config remains saved. Run /mcp reconnect ${name} to reconnect.`,
1657
+ ],
1658
+ });
1387
1659
  } else {
1388
- console.log(`\n ${C.amber}MCP server "${name}" not found.${C.reset}\n`);
1660
+ printRichCard({
1661
+ title: 'MCP Server Not Found',
1662
+ status: 'warning',
1663
+ lines: [`MCP server "${name}" is not currently connected.`],
1664
+ });
1389
1665
  }
1390
1666
  return 'HANDLED';
1391
1667
  }
1392
1668
 
1393
1669
  const status = mcpManager.getStatus();
1394
1670
  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}`,
1671
+ `${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
1672
  ''
1397
1673
  ];
1398
1674
 
1399
1675
  if (status.servers.length > 0) {
1400
- lines.push(`${C.bold}${C.lavender}Connected Servers:${C.reset}`);
1676
+ lines.push(`${C.bold}${C.lavender}Configured MCP Servers:${C.reset}`);
1401
1677
  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`);
1678
+ const serverName = sanitizeTerminalText(s.name, 128) || 'unnamed';
1679
+ const serverType = sanitizeTerminalText(s.type, 80) || 'unknown';
1680
+ if (s.status === 'connected') {
1681
+ 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`);
1682
+ } else if (s.status === 'saved_offline') {
1683
+ 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}`);
1684
+ } else {
1685
+ 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}`);
1686
+ }
1687
+ }
1688
+
1689
+ const connectedTools = mcpManager.getToolsSchema();
1690
+ if (connectedTools.length > 0) {
1691
+ lines.push('', `${C.bold}${C.lavender}Available MCP Tool Commands (${connectedTools.length}):${C.reset}`);
1692
+ const commandList = connectedTools
1693
+ .map(tool => `@${sanitizeTerminalText(tool.function?.name, 128) || 'unknown_tool'}`)
1694
+ .join(' • ');
1695
+ for (const commandLine of wrapText(commandList, Math.max(30, getFullWidth() - 8))) {
1696
+ lines.push(` ${C.cyberCyan}${commandLine}${C.reset}`);
1697
+ }
1698
+ lines.push(`${C.dim}Use /mcp tools for parameters and full descriptions.${C.reset}`);
1403
1699
  }
1404
1700
  } 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}`);
1701
+ lines.push(`${C.dim}No MCP servers configured.${C.reset}`);
1702
+ lines.push(`Connect and save with: ${C.cyberCyan}/mcp connect <name> <url>${C.reset}`);
1407
1703
  }
1408
1704
 
1705
+ lines.push('', `${C.dim}Commands: /mcp connect <name> <url> | /mcp reconnect [all] | /mcp save | /mcp remove <name> | /mcp tools${C.reset}`);
1706
+
1409
1707
  printSolidPanel({
1410
- title: `Model Context Protocol (MCP) Client`,
1708
+ title: `Model Context Protocol (MCP) Manager • ${status.activeServersCount} Active / ${status.totalConfiguredCount} Saved`,
1411
1709
  lines,
1412
- headerBg: '\x1b[48;2;48;54;70m', bg: '\x1b[48;2;36;40;52m'
1413
1710
  });
1414
1711
  return 'HANDLED';
1415
1712
  },
1416
1713
 
1417
1714
  '/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`);
1715
+ try {
1716
+ const start = Date.now();
1717
+ const check = await ctx.client.checkConnection();
1718
+ const ms = Date.now() - start;
1719
+ if (check.connected) {
1720
+ console.log(`\n ${C.emerald}● Connected${C.reset} ${C.dim}(${ms}ms)${C.reset} — ${C.white}${check.activeModel || check.model || 'Ready'}${C.reset}\n`);
1721
+ } else {
1722
+ console.log(`\n ${C.red}● Disconnected${C.reset} ${C.dim}(${ms}ms)${C.reset} — ${C.amber}${check.error || 'Server unreachable'}${C.reset}\n`);
1723
+ }
1724
+ } catch (err) {
1725
+ console.log(`\n ${C.red}[PING ERROR] ${err.message || String(err)}${C.reset}\n`);
1425
1726
  }
1426
1727
  return 'HANDLED';
1427
1728
  },
@@ -1444,15 +1745,19 @@ export const handlers = {
1444
1745
  },
1445
1746
 
1446
1747
  '/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
- });
1748
+ try {
1749
+ const { runDoctor } = await import('./doctor.js');
1750
+ const report = await runDoctor({ client: ctx.client });
1751
+ printSolidPanel({
1752
+ title: `Kiku Doctor • ${report.ok ? 'READY' : 'ACTION REQUIRED'}`,
1753
+ lines: report.checks.map(check => {
1754
+ const marker = check.ok ? '[OK]' : check.required ? '[FAIL]' : '[OPTIONAL]';
1755
+ return `${marker} ${check.id}: ${check.detail}`;
1756
+ }),
1757
+ });
1758
+ } catch (err) {
1759
+ console.log(`\n ${C.red}[DOCTOR ERROR] ${err.message || String(err)}${C.reset}\n`);
1760
+ }
1456
1761
  return 'HANDLED';
1457
1762
  },
1458
1763
 
@@ -1460,7 +1765,8 @@ export const handlers = {
1460
1765
  printSolidPanel({
1461
1766
  title: 'Version',
1462
1767
  lines: [
1463
- `${C.gold}Version:${C.reset} ${C.white}1.0.5${C.reset}`,
1768
+ `${C.gold}Package:${C.reset} ${C.white}${PACKAGE_NAME}${C.reset}`,
1769
+ `${C.gold}Version:${C.reset} ${C.white}${PACKAGE_VERSION}${C.reset}`,
1464
1770
  `${C.gold}Node:${C.reset} ${C.white}${process.version}${C.reset}`,
1465
1771
  `${C.gold}Platform:${C.reset} ${C.white}${process.platform} ${process.arch}${C.reset}`,
1466
1772
  ],
@@ -1468,6 +1774,186 @@ export const handlers = {
1468
1774
  return 'HANDLED';
1469
1775
  },
1470
1776
 
1777
+ '/update': async (ctx) => {
1778
+ const manager = ctx.updateManager || new UpdateManager();
1779
+ const action = (ctx.sub || 'check').toLowerCase();
1780
+ const parts = ctx.parts || [];
1781
+ const allowMajor = parts.includes('--allow-major');
1782
+ const allowDeprecated = parts.includes('--allow-deprecated');
1783
+ const positional = parts.slice(2).filter(part => !part.startsWith('--'));
1784
+ const flags = parts.slice(2).filter(part => part.startsWith('--'));
1785
+
1786
+ const printUpdateHelp = () => printSolidPanel({
1787
+ title: 'Kiku Agent Update',
1788
+ lines: [
1789
+ `${C.bold}${C.white}/update${C.reset} ${C.dim}or /update check — check the configured channel${C.reset}`,
1790
+ `${C.bold}${C.white}/update install [latest|next|canary|x.y.z]${C.reset}`,
1791
+ `${C.bold}${C.white}/update channel [latest|next|canary]${C.reset}`,
1792
+ `${C.bold}${C.white}/update rollback${C.reset} ${C.dim}— reinstall the previously recorded version${C.reset}`,
1793
+ '',
1794
+ `${C.dim}Major releases require --allow-major. Deprecated releases require --allow-deprecated.${C.reset}`,
1795
+ ],
1796
+ });
1797
+
1798
+ const statusLines = status => {
1799
+ const changeLabel = status.change === 'upgrade'
1800
+ ? `${C.emerald}UPDATE AVAILABLE${C.reset}`
1801
+ : status.change === 'downgrade'
1802
+ ? `${C.amber}OLDER RELEASE${C.reset}`
1803
+ : `${C.emerald}UP TO DATE${C.reset}`;
1804
+ const lines = [
1805
+ `${C.gold}Current:${C.reset} ${C.white}${status.currentVersion}${C.reset}`,
1806
+ `${C.gold}Target:${C.reset} ${C.white}${status.targetVersion}${C.reset} ${C.dim}(${status.target})${C.reset}`,
1807
+ `${C.gold}Status:${C.reset} ${changeLabel}`,
1808
+ `${C.gold}Installation:${C.reset} ${C.white}${status.installMode.label}${C.reset}`,
1809
+ `${C.gold}Node:${C.reset} ${status.nodeCompatible ? C.emerald + 'Compatible' : C.red + 'Incompatible'}${C.reset}${status.nodeRange ? ` ${C.dim}(requires ${status.nodeRange})${C.reset}` : ''}`,
1810
+ `${C.gold}Integrity:${C.reset} ${status.integrity ? C.emerald + 'Published with npm integrity metadata' : C.amber + 'Not reported'}${C.reset}`,
1811
+ ];
1812
+ if (status.majorChange) lines.push(`${C.amber}[MAJOR] This release crosses a major-version boundary.${C.reset}`);
1813
+ if (status.deprecated) lines.push(`${C.red}[DEPRECATED] ${status.deprecated}${C.reset}`);
1814
+ lines.push('', `${C.bold}${C.lavender}What's new:${C.reset}`);
1815
+ if (status.releaseNotes.length > 0) {
1816
+ for (const note of status.releaseNotes) lines.push(` ${C.sakura}•${C.reset} ${C.white}${note}${C.reset}`);
1817
+ } else {
1818
+ lines.push(` ${C.dim}No release summary was published for this version.${C.reset}`);
1819
+ }
1820
+ if (status.releaseUrl) lines.push('', `${C.dim}Release: ${status.releaseUrl}${C.reset}`);
1821
+ return lines;
1822
+ };
1823
+
1824
+ try {
1825
+ const allowedFlags = action === 'install'
1826
+ ? ['--allow-major', '--allow-deprecated']
1827
+ : action === 'rollback'
1828
+ ? ['--allow-deprecated']
1829
+ : [];
1830
+ if (flags.some(flag => !allowedFlags.includes(flag))) {
1831
+ throw new UpdateError('Unknown option. Run /update help for supported arguments.', 'INVALID_OPTION');
1832
+ }
1833
+ if (positional.length > 1 || ((action === 'rollback' || action === 'help') && positional.length > 0)) {
1834
+ throw new UpdateError('Too many update arguments. Run /update help for usage.', 'INVALID_ARGUMENTS');
1835
+ }
1836
+
1837
+ if (action === 'help') {
1838
+ printUpdateHelp();
1839
+ return 'HANDLED';
1840
+ }
1841
+
1842
+ if (action === 'channel') {
1843
+ const requested = positional[0];
1844
+ if (!requested) {
1845
+ const channel = await manager.getChannel();
1846
+ printSolidPanel({
1847
+ title: 'Update Channel',
1848
+ lines: [
1849
+ `${C.gold}Current:${C.reset} ${C.white}${channel}${C.reset}`,
1850
+ `${C.dim}Available: ${UPDATE_CHANNELS.join(', ')}${C.reset}`,
1851
+ ],
1852
+ });
1853
+ return 'HANDLED';
1854
+ }
1855
+ const channel = await manager.setChannel(requested);
1856
+ printSolidPanel({
1857
+ title: 'Update Channel Changed',
1858
+ lines: [
1859
+ `${C.emerald}[OK] Future /update checks will use ${C.bold}${channel}${C.reset}${C.emerald}.${C.reset}`,
1860
+ `${C.dim}This changes only the release channel; it does not install anything.${C.reset}`,
1861
+ ],
1862
+ });
1863
+ return 'HANDLED';
1864
+ }
1865
+
1866
+ if (action === 'install') {
1867
+ const target = positional[0];
1868
+ console.log(`\n ${C.cyberCyan}[UPDATE] Resolving and validating the requested npm release...${C.reset}\n`);
1869
+ const result = await manager.install(target, {
1870
+ allowMajor,
1871
+ allowDeprecated,
1872
+ onProgress: message => { if (message) console.log(` ${C.dim}${message}${C.reset}`); },
1873
+ });
1874
+ if (result.outcome === 'manual') {
1875
+ printSolidPanel({
1876
+ title: 'Manual Update Required',
1877
+ lines: [
1878
+ ...statusLines(result),
1879
+ '',
1880
+ `${C.amber}This installation type is not modified automatically.${C.reset}`,
1881
+ `${C.gold}Run:${C.reset} ${C.white}${result.command}${C.reset}`,
1882
+ ],
1883
+ });
1884
+ return 'HANDLED';
1885
+ }
1886
+ if (result.outcome === 'current') {
1887
+ printSolidPanel({ title: 'Kiku Agent Is Up to Date', lines: statusLines(result) });
1888
+ return 'HANDLED';
1889
+ }
1890
+ printSolidPanel({
1891
+ title: 'Update Installed Successfully',
1892
+ lines: [
1893
+ `${C.emerald}[OK] ${PACKAGE_NAME} ${result.installedVersion} was installed and verified.${C.reset}`,
1894
+ ...(!result.historyRecorded ? [`${C.amber}[WARNING] Rollback history could not be saved.${C.reset}`] : []),
1895
+ `${C.dim}This session will close. Start kiku-agent again to load the new code.${C.reset}`,
1896
+ `${C.dim}Restart MCP hosts to load the matching kiku-mcp server version.${C.reset}`,
1897
+ ],
1898
+ });
1899
+ try { await ctx.agent?.endSession?.(); } catch {}
1900
+ return 'EXIT';
1901
+ }
1902
+
1903
+ if (action === 'rollback') {
1904
+ console.log(`\n ${C.cyberCyan}[UPDATE] Resolving the previously installed version...${C.reset}\n`);
1905
+ const result = await manager.rollback({ allowDeprecated });
1906
+ if (result.outcome === 'manual') {
1907
+ printSolidPanel({
1908
+ title: 'Manual Rollback Required',
1909
+ lines: [
1910
+ `${C.amber}Run the following command outside Kiku Agent:${C.reset}`,
1911
+ `${C.white}${result.command}${C.reset}`,
1912
+ ],
1913
+ });
1914
+ return 'HANDLED';
1915
+ }
1916
+ if (result.outcome === 'current') {
1917
+ printSolidPanel({ title: 'Rollback Not Needed', lines: statusLines(result) });
1918
+ return 'HANDLED';
1919
+ }
1920
+ printSolidPanel({
1921
+ title: 'Rollback Installed Successfully',
1922
+ lines: [
1923
+ `${C.emerald}[OK] Restored ${PACKAGE_NAME} ${result.installedVersion}.${C.reset}`,
1924
+ ...(!result.historyRecorded ? [`${C.amber}[WARNING] Rollback history could not be saved.${C.reset}`] : []),
1925
+ `${C.dim}This session will close. Restart Kiku Agent and any MCP host.${C.reset}`,
1926
+ ],
1927
+ });
1928
+ try { await ctx.agent?.endSession?.(); } catch {}
1929
+ return 'EXIT';
1930
+ }
1931
+
1932
+ if (action === 'check' || action === 'status') {
1933
+ const status = await manager.check(positional[0]);
1934
+ const lines = statusLines(status);
1935
+ if (status.updateAvailable) {
1936
+ lines.push('', `${C.cyberCyan}Run /update install${status.target === 'latest' ? '' : ` ${status.target}`} to update.${C.reset}`);
1937
+ }
1938
+ printSolidPanel({ title: 'Kiku Agent Update', lines });
1939
+ return 'HANDLED';
1940
+ }
1941
+
1942
+ printUpdateHelp();
1943
+ return 'HANDLED';
1944
+ } catch (error) {
1945
+ const safeMessage = error instanceof UpdateError ? error.message : 'The update operation failed unexpectedly.';
1946
+ printSolidPanel({
1947
+ title: 'Update Failed',
1948
+ lines: [
1949
+ `${C.red}[${error.code || 'UPDATE_ERROR'}] ${safeMessage}${C.reset}`,
1950
+ `${C.dim}No session, memory, or environment files were changed.${C.reset}`,
1951
+ ],
1952
+ });
1953
+ return 'HANDLED';
1954
+ }
1955
+ },
1956
+
1471
1957
  '/system': async () => {
1472
1958
  const os = await import('node:os');
1473
1959
  const mem = process.memoryUsage();
@@ -1564,11 +2050,16 @@ export const handlers = {
1564
2050
  '/login': async (ctx) => {
1565
2051
  console.log(`\n ${C.cyberCyan}YouTube Login${C.reset}\n`);
1566
2052
  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`);
2053
+ ctx.setIsProcessing?.(true, 'YouTube Login');
2054
+ try {
2055
+ const result = await ensureYouTubeCookies({ timeout: 180000 });
2056
+ if (result.ok) {
2057
+ console.log(` ${C.emerald}[OK] Session active (${result.source}).${C.reset}\n`);
2058
+ } else {
2059
+ console.log(` ${C.red}[ERROR] Not available: ${result.reason}${C.reset}\n`);
2060
+ }
2061
+ } finally {
2062
+ ctx.setIsProcessing?.(false);
1572
2063
  }
1573
2064
  return 'HANDLED';
1574
2065
  },
@@ -1612,19 +2103,23 @@ export const handlers = {
1612
2103
  },
1613
2104
 
1614
2105
  '/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
- });
2106
+ try {
2107
+ const result = await executeTool('get_system_stats', {});
2108
+ const s = result.stats || {};
2109
+ printSolidPanel({
2110
+ title: `Database Overview`,
2111
+ lines: [
2112
+ `${C.gold}Works:${C.reset} ${C.cyberCyan}${s.totalWorks || 0}${C.reset}`,
2113
+ `${C.gold}Songs:${C.reset} ${C.cyberCyan}${s.totalUniqueSongs || 0}${C.reset}`,
2114
+ `${C.gold}Brands:${C.reset} ${C.cyberCyan}${s.brandEndorsements || 0}${C.reset}`,
2115
+ `${C.gold}Awards:${C.reset} ${C.cyberCyan}${s.totalAwards || 0}${C.reset}`,
2116
+ `${C.gold}Timeline:${C.reset} ${C.cyberCyan}${s.timelineMilestones || 0}${C.reset}`
2117
+ ],
2118
+ headerBg: '\x1b[48;2;48;54;70m', bg: '\x1b[48;2;36;40;52m'
2119
+ });
2120
+ } catch (err) {
2121
+ console.log(`\n ${C.red}[STATS ERROR] ${err.message || String(err)}${C.reset}\n`);
2122
+ }
1628
2123
  return 'HANDLED';
1629
2124
  },
1630
2125
 
@@ -1635,6 +2130,70 @@ export const handlers = {
1635
2130
  return 'HANDLED';
1636
2131
  },
1637
2132
 
2133
+ '/cancel': async (ctx) => {
2134
+ if (typeof ctx.cancelActiveQuery === 'function') {
2135
+ const ok = ctx.cancelActiveQuery();
2136
+ if (ok) {
2137
+ printRichCard({
2138
+ title: 'Query Cancelled',
2139
+ status: 'cancel',
2140
+ lines: ['Active query and queued tasks were cancelled.'],
2141
+ });
2142
+ } else {
2143
+ printRichCard({
2144
+ title: 'No Active Query',
2145
+ status: 'info',
2146
+ lines: ['There is no query currently running.'],
2147
+ });
2148
+ }
2149
+ return 'HANDLED';
2150
+ }
2151
+ if (ctx.agent && typeof ctx.agent.cancelResearch === 'function') {
2152
+ ctx.agent.cancelResearch();
2153
+ }
2154
+ return 'HANDLED';
2155
+ },
2156
+
2157
+ '/copy': async (ctx) => {
2158
+ if (typeof ctx.copyQueryDetails === 'function') {
2159
+ const reference = [ctx.sub, ctx.extra].filter(Boolean).join(' ');
2160
+ ctx.copyQueryDetails(reference);
2161
+ return 'HANDLED';
2162
+ }
2163
+ const { getCopyableContent, copyToClipboard, parseDetailsRequest } = await import('./queryDetails.js');
2164
+ const reference = [ctx.sub, ctx.extra].filter(Boolean).join(' ');
2165
+ const request = parseDetailsRequest(reference);
2166
+ const details = ctx.agent?.lastQueryDetails || null;
2167
+ if (!details) {
2168
+ printRichCard({
2169
+ title: 'No Diagnostics Recorded',
2170
+ status: 'warning',
2171
+ lines: ['No completed model queries are available to copy.'],
2172
+ });
2173
+ return 'HANDLED';
2174
+ }
2175
+ const copyable = getCopyableContent(details, request);
2176
+ if (!copyable?.text) {
2177
+ printRichCard({
2178
+ title: 'Nothing to Copy',
2179
+ status: 'warning',
2180
+ lines: ['The requested query section is empty.'],
2181
+ });
2182
+ return 'HANDLED';
2183
+ }
2184
+ const copied = copyToClipboard(copyable.text);
2185
+ printRichCard({
2186
+ title: copied ? 'Copied to Clipboard' : 'Copy Content Ready',
2187
+ status: copied ? 'ok' : 'info',
2188
+ lines: [
2189
+ `• Target: ${copyable.title}`,
2190
+ `• Characters: ${copyable.text.length} chars (${copyable.text.split('\n').length} lines)`,
2191
+ ...(copied ? ['• Status: Copied to system clipboard (Ready to paste with Ctrl+V / Cmd+V)'] : []),
2192
+ ],
2193
+ });
2194
+ return 'HANDLED';
2195
+ },
2196
+
1638
2197
  '/ads': async (ctx) => {
1639
2198
  ctx.printAd();
1640
2199
  return 'HANDLED';
@@ -1643,6 +2202,8 @@ export const handlers = {
1643
2202
 
1644
2203
  const ALIASES = {
1645
2204
  '/quit': '/exit', '/q': '/exit',
2205
+ '/abort': '/cancel',
2206
+ '/cp': '/copy', '/clip': '/copy', '/clipboard': '/copy',
1646
2207
  '/?': '/help', '/commands': '/help', '/menu': '/help',
1647
2208
  '/hist': '/history', '/h': '/history',
1648
2209
  '/sess': '/session', '/sessions': '/session',
@@ -1650,6 +2211,7 @@ const ALIASES = {
1650
2211
  '/detail': '/details', '/d': '/details',
1651
2212
  '/language': '/lang',
1652
2213
  '/themes': '/theme',
2214
+ '/suggest': '/suggestion', '/suggestions': '/suggestion',
1653
2215
  '/exec': '/tool', '/call': '/tool',
1654
2216
  '/toollist': '/tools',
1655
2217
  '/metrics': '/stats', '/catalog': '/stats',
@@ -1690,5 +2252,9 @@ export async function routeCommand(rawInput, ctx) {
1690
2252
  return 'UNKNOWN';
1691
2253
  }
1692
2254
 
2255
+ if (sub.toLowerCase() === 'help' && rawCmd !== '/help') {
2256
+ return handlers['/help']({ ...ctx, sub: rawCmd, extra: '' });
2257
+ }
2258
+
1693
2259
  return await handler({ ...ctx, sub, extra, parts });
1694
2260
  }