kiku-agent 1.0.10 → 1.0.11

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.
@@ -787,17 +787,31 @@ export const handlers = {
787
787
  const totalDur = ((sess.stats.totalDurationMs || 0) / 1000).toFixed(1);
788
788
  const createdAt = sess.createdAt ? sess.createdAt.toLocaleString() : 'unknown';
789
789
 
790
+ const lines = [
791
+ `${C.gold}Session ID:${C.reset} ${C.white}${sess.id}${C.reset}`,
792
+ `${C.gold}Created At:${C.reset} ${C.white}${createdAt}${C.reset}`,
793
+ `${C.gold}Total Messages:${C.reset} ${C.cyberCyan}${msgCount}${C.reset} total ${C.dim}(${userMsgs} user, ${assistantMsgs} assistant)${C.reset}`,
794
+ `${C.gold}Total Queries:${C.reset} ${C.cyberCyan}${sess.stats.totalQueries}${C.reset}`,
795
+ `${C.gold}Tool Calls:${C.reset} ${C.cyberCyan}${toolCalls}${C.reset}`,
796
+ `${C.gold}Total Duration:${C.reset} ${C.cyberCyan}${totalDur}s${C.reset}`,
797
+ `${C.gold}Thinking Mode:${C.reset} ${C.bold}${C.gold}${sess.thinkingMode.toUpperCase()}${C.reset}`
798
+ ];
799
+
800
+ const toolStats = typeof sess.getToolStats === 'function' ? sess.getToolStats() : {};
801
+ const toolEntries = Object.entries(toolStats);
802
+ if (toolEntries.length > 0) {
803
+ lines.push('', `${C.bold}${C.lavender}Tool Executions Breakdown:${C.reset}`);
804
+ for (const [name, s] of toolEntries) {
805
+ const dur = (s.totalDurationMs / 1000).toFixed(2);
806
+ lines.push(` ${C.cyberCyan}• ${name.padEnd(24)}${C.reset} ${C.white}${s.count} call(s)${C.reset} ${C.dim}(${s.successCount} ok, ${s.notFoundCount} empty, ${s.errorCount} err • ${dur}s)${C.reset}`);
807
+ }
808
+ }
809
+
810
+ lines.push('', `${C.dim}Commands: /session summary | /session export [markdown|json|text] | /session copy | /session clear${C.reset}`);
811
+
790
812
  printSolidPanel({
791
- title: `Active Session Details`,
792
- lines: [
793
- `${C.gold}Session ID:${C.reset} ${C.white}${sess.id}${C.reset}`,
794
- `${C.gold}Created At:${C.reset} ${C.white}${createdAt}${C.reset}`,
795
- `${C.gold}Total Messages:${C.reset} ${C.cyberCyan}${msgCount}${C.reset} total ${C.dim}(${userMsgs} user, ${assistantMsgs} assistant)${C.reset}`,
796
- `${C.gold}Total Queries:${C.reset} ${C.cyberCyan}${sess.stats.totalQueries}${C.reset}`,
797
- `${C.gold}Tool Calls:${C.reset} ${C.cyberCyan}${toolCalls}${C.reset}`,
798
- `${C.gold}Total Duration:${C.reset} ${C.cyberCyan}${totalDur}s${C.reset}`,
799
- `${C.gold}Thinking Mode:${C.reset} ${C.bold}${C.gold}${sess.thinkingMode.toUpperCase()}${C.reset}`
800
- ],
813
+ title: `Active Session Details • ${sess.id}`,
814
+ lines,
801
815
  headerBg: '\x1b[48;2;48;54;70m',
802
816
  bg: '\x1b[48;2;36;40;52m'
803
817
  });
@@ -909,19 +923,43 @@ export const handlers = {
909
923
  }
910
924
 
911
925
  if (action === 'export') {
912
- const exported = await agent.session.exportToMarkdown();
913
- console.log(`\n ${C.emerald}[OK] Session transcript exported.${C.reset}`);
914
- console.log(` ${C.slate}• File:${C.reset} ${C.white}${exported.filePath}${C.reset}\n`);
926
+ const format = (extra || '').toLowerCase().trim();
927
+ let exported;
928
+ if (format === 'json') {
929
+ exported = await agent.session.exportToJson();
930
+ } else if (format === 'text' || format === 'txt') {
931
+ exported = await agent.session.exportToText();
932
+ } else {
933
+ exported = await agent.session.exportToMarkdown();
934
+ }
935
+
936
+ printRichCard({
937
+ title: 'Session Exported Successfully',
938
+ status: 'ok',
939
+ lines: [
940
+ `• Format: ${exported.format.toUpperCase()}`,
941
+ `• File: ${exported.filePath}`,
942
+ `• Size: ${(exported.size || 0).toLocaleString()} bytes`,
943
+ ],
944
+ });
915
945
  return 'HANDLED';
916
946
  }
917
947
 
948
+ if (action === 'copy') {
949
+ if (typeof ctx.copyQueryDetails === 'function') {
950
+ ctx.copyQueryDetails(`session ${extra || ''}`.trim());
951
+ return 'HANDLED';
952
+ }
953
+ return handlers['/copy']({ ...ctx, sub: 'session', extra });
954
+ }
955
+
918
956
  if (action === 'clear' || action === 'new' || action === 'reset') {
919
957
  await agent.endSession();
920
958
  console.log(`\n ${C.emerald}[OK] Previous session archived. Started fresh.${C.reset}\n`);
921
959
  return 'HANDLED';
922
960
  }
923
961
 
924
- console.log(`\n ${C.amber}Unknown session action: "${sub}". Use /session [info|summary|list|load|export|clear]${C.reset}\n`);
962
+ console.log(`\n ${C.amber}Unknown session action: "${sub}". Use /session [info|summary|list|load|export|copy|clear]${C.reset}\n`);
925
963
  return 'HANDLED';
926
964
  },
927
965
 
@@ -988,14 +1026,28 @@ export const handlers = {
988
1026
  return 'HANDLED';
989
1027
  }
990
1028
 
991
- const requested = action === 'reset' ? 'sakura' : action;
1029
+ let requested = action === 'reset' ? 'sakura' : action;
1030
+ if (action === 'next' || action === 'cycle') {
1031
+ const currentIndex = THEME_NAMES.indexOf(current);
1032
+ const nextIndex = (currentIndex + 1) % THEME_NAMES.length;
1033
+ requested = THEME_NAMES[nextIndex];
1034
+ } else if (action === 'prev') {
1035
+ const currentIndex = THEME_NAMES.indexOf(current);
1036
+ const prevIndex = (currentIndex - 1 + THEME_NAMES.length) % THEME_NAMES.length;
1037
+ requested = THEME_NAMES[prevIndex];
1038
+ }
1039
+
992
1040
  const theme = setTheme(requested);
993
1041
  if (!theme) {
994
1042
  console.log(`\n ${C.amber}Unknown theme: "${action}".${C.reset}`);
995
- console.log(` ${C.dim}Available: ${THEME_NAMES.join(', ')}${C.reset}\n`);
1043
+ console.log(` ${C.dim}Available: ${THEME_NAMES.join(', ')}, next, prev, reset${C.reset}\n`);
996
1044
  return 'HANDLED';
997
1045
  }
998
1046
 
1047
+ if (ctx.tuiPrompt) {
1048
+ ctx.tuiPrompt.render();
1049
+ }
1050
+
999
1051
  printSolidPanel({
1000
1052
  title: 'Theme Updated',
1001
1053
  lines: [
@@ -1369,9 +1421,24 @@ export const handlers = {
1369
1421
  ctx.modelManager.client = ctx.client;
1370
1422
  ctx.modelManager.requestedModel = info.model;
1371
1423
  const resolution = await ctx.modelManager.resolve({ requestedModel: info.model });
1372
- if (resolution.connected && resolution.usedFallback) {
1373
- console.log(` ${C.amber}• Requested model unavailable; using ${resolution.selectedModel}${C.reset}`);
1424
+ if (resolution.connected && resolution.selectedModel) {
1425
+ ctx.setCatalogOnlyMode?.(false);
1426
+ if (ctx.tuiPrompt) {
1427
+ ctx.tuiPrompt.isConnected = true;
1428
+ ctx.tuiPrompt.modelName = resolution.selectedModel;
1429
+ ctx.tuiPrompt.provider = info.providerName;
1430
+ ctx.tuiPrompt.render();
1431
+ }
1432
+ if (resolution.usedFallback) {
1433
+ console.log(` ${C.amber}• Requested model unavailable; using ${resolution.selectedModel}${C.reset}`);
1434
+ }
1374
1435
  } else if (!resolution.connected) {
1436
+ ctx.setCatalogOnlyMode?.(true);
1437
+ if (ctx.tuiPrompt) {
1438
+ ctx.tuiPrompt.isConnected = false;
1439
+ ctx.tuiPrompt.provider = info.providerName;
1440
+ ctx.tuiPrompt.render();
1441
+ }
1375
1442
  console.log(` ${C.amber}• Provider is not ready: ${resolution.error}${C.reset}`);
1376
1443
  }
1377
1444
  }
@@ -1495,9 +1562,11 @@ export const handlers = {
1495
1562
  return 'HANDLED';
1496
1563
  }
1497
1564
 
1498
- if (targetProvider === ctx.client.provider) {
1499
- ctx.client.setApiKey(newKey);
1500
- if (ctx.agent?.client) ctx.agent.client.setApiKey(newKey);
1565
+ if (ctx.client?.setApiKey) {
1566
+ ctx.client.setApiKey(newKey, targetProvider);
1567
+ }
1568
+ if (ctx.agent?.client?.setApiKey) {
1569
+ ctx.agent.client.setApiKey(newKey, targetProvider);
1501
1570
  }
1502
1571
  const pDef = getProvider(targetProvider);
1503
1572
  if (pDef) pDef.apiKey = newKey;
@@ -1505,7 +1574,7 @@ export const handlers = {
1505
1574
  const masked = newKey.length > 8
1506
1575
  ? `${newKey.slice(0, 4)}...${newKey.slice(-4)}`
1507
1576
  : '****';
1508
- console.log(`\n ${C.emerald}[OK] API key updated for ${targetProvider}${C.reset} (${masked})\n`);
1577
+ console.log(`\n ${C.emerald}[OK] API key saved & persisted for ${targetProvider}${C.reset} (${masked})\n`);
1509
1578
  return 'HANDLED';
1510
1579
  }
1511
1580
 
@@ -1535,9 +1604,10 @@ export const handlers = {
1535
1604
  },
1536
1605
 
1537
1606
  '/model': async (ctx) => {
1538
- const action = (ctx.sub || 'status').toLowerCase();
1607
+ const rawSub = (ctx.sub || '').trim();
1608
+ const action = (rawSub || 'status').toLowerCase();
1539
1609
  const providerName = ctx.client.providerName || 'Model';
1540
- const { getRecommendedModels } = await import('./config.js');
1610
+ const { getRecommendedModels, CLOUD_PROVIDERS } = await import('./config.js');
1541
1611
 
1542
1612
  if (action === 'reconnect') {
1543
1613
  if (!ctx.modelManager) {
@@ -1547,8 +1617,18 @@ export const handlers = {
1547
1617
  const resolution = await ctx.modelManager.resolve();
1548
1618
  if (resolution.connected && resolution.selectedModel) {
1549
1619
  ctx.setCatalogOnlyMode?.(false);
1620
+ if (ctx.tuiPrompt) {
1621
+ ctx.tuiPrompt.isConnected = true;
1622
+ ctx.tuiPrompt.modelName = resolution.selectedModel;
1623
+ ctx.tuiPrompt.render();
1624
+ }
1550
1625
  console.log(`\n ${C.emerald}[OK] Connected using ${resolution.selectedModel}.${C.reset}\n`);
1551
1626
  } else {
1627
+ ctx.setCatalogOnlyMode?.(true);
1628
+ if (ctx.tuiPrompt) {
1629
+ ctx.tuiPrompt.isConnected = false;
1630
+ ctx.tuiPrompt.render();
1631
+ }
1552
1632
  console.log(`\n ${C.red}[MODEL ERROR] ${resolution.error || 'No chat model is ready.'}${C.reset}\n`);
1553
1633
  }
1554
1634
  return 'HANDLED';
@@ -1590,19 +1670,42 @@ export const handlers = {
1590
1670
  `${C.bold}${C.lavender}Reported by Server:${C.reset}`,
1591
1671
  ...(serverModels.length ? serverModels.map(s => ` ${s}`) : [` ${C.dim}No live model list reported by ${providerName}.${C.reset}`]),
1592
1672
  '',
1593
- `${C.dim}Select model: /model use <model_id>${C.reset}`
1673
+ `${C.dim}Select model: /model use <model_id> or /model <provider_or_model>${C.reset}`
1594
1674
  ],
1595
1675
  });
1596
1676
  return 'HANDLED';
1597
1677
  }
1598
1678
 
1599
- if (action === 'use') {
1679
+ const isDirectUse = action === 'use';
1680
+ const targetModel = isDirectUse ? (ctx.extra?.trim() || '') : (!['status', 'diag', 'diagnostics'].includes(action) ? rawSub : '');
1681
+
1682
+ if (targetModel) {
1600
1683
  try {
1601
- const targetModel = ctx.extra?.trim() || '';
1602
- if (!targetModel) {
1603
- console.log(`\n ${C.amber}Usage: /model use <model_id>${C.reset}\n`);
1604
- return 'HANDLED';
1684
+ const lowerTarget = targetModel.toLowerCase();
1685
+
1686
+
1687
+ if (CLOUD_PROVIDERS[lowerTarget]) {
1688
+ return await handlers['/provider']({ ...ctx, sub: 'use', extra: lowerTarget, parts: ['/provider', 'use', lowerTarget] });
1689
+ }
1690
+
1691
+
1692
+ let matchedProvider = null;
1693
+ if (lowerTarget.includes('gemma') || lowerTarget === 'google/gemma-4-e4b') {
1694
+ matchedProvider = 'lmstudio';
1695
+ } else {
1696
+ for (const [pId, pDef] of Object.entries(CLOUD_PROVIDERS)) {
1697
+ if (pDef.recommendedModels?.some(rm => rm.id.toLowerCase() === lowerTarget || rm.id.toLowerCase().includes(lowerTarget))) {
1698
+ matchedProvider = pId;
1699
+ break;
1700
+ }
1701
+ }
1702
+ }
1703
+
1704
+ if (matchedProvider && matchedProvider !== ctx.client.provider) {
1705
+ console.log(` ${C.cyberCyan}• Switching provider to ${CLOUD_PROVIDERS[matchedProvider].name} for model ${targetModel}...${C.reset}`);
1706
+ return await handlers['/provider']({ ...ctx, sub: 'use', extra: `${matchedProvider} ${targetModel}`, parts: ['/provider', 'use', matchedProvider, targetModel] });
1605
1707
  }
1708
+
1606
1709
  if (ctx.modelManager) {
1607
1710
  const selected = ctx.modelManager.select(targetModel, liveCheck.models || []);
1608
1711
  console.log(`\n ${C.emerald}[OK] Active model changed to ${selected}.${C.reset}\n`);
@@ -2349,20 +2452,12 @@ export const handlers = {
2349
2452
  const reference = [ctx.sub, ctx.extra].filter(Boolean).join(' ');
2350
2453
  const request = parseDetailsRequest(reference);
2351
2454
  const details = ctx.agent?.lastQueryDetails || null;
2352
- if (!details) {
2353
- printRichCard({
2354
- title: 'No Diagnostics Recorded',
2355
- status: 'warning',
2356
- lines: ['No completed model queries are available to copy.'],
2357
- });
2358
- return 'HANDLED';
2359
- }
2360
- const copyable = getCopyableContent(details, request);
2455
+ const copyable = getCopyableContent(details, request, ctx.agent?.session);
2361
2456
  if (!copyable?.text) {
2362
2457
  printRichCard({
2363
2458
  title: 'Nothing to Copy',
2364
2459
  status: 'warning',
2365
- lines: ['The requested query section is empty.'],
2460
+ lines: ['The requested query or session section is empty.'],
2366
2461
  });
2367
2462
  return 'HANDLED';
2368
2463
  }
@@ -2385,7 +2480,8 @@ export const handlers = {
2385
2480
  },
2386
2481
 
2387
2482
  '/gallery': async (ctx) => {
2388
- const displayed = typeof ctx.showTerminalImage === 'function' && ctx.showTerminalImage();
2483
+ const count = Number(ctx.sub) || 2;
2484
+ const displayed = typeof ctx.showTerminalImage === 'function' && ctx.showTerminalImage(count);
2389
2485
  if (!displayed) {
2390
2486
  printRichCard({
2391
2487
  title: 'Terminal Gallery Unavailable',
@@ -2638,6 +2734,11 @@ export const handlers = {
2638
2734
  }
2639
2735
  return 'HANDLED';
2640
2736
  },
2737
+
2738
+ '/exit': async (ctx) => {
2739
+ try { await ctx.agent?.endSession(); } catch {}
2740
+ return 'EXIT';
2741
+ },
2641
2742
  };
2642
2743
 
2643
2744
  const ALIASES = {
@@ -2676,6 +2777,9 @@ const ALIASES = {
2676
2777
  '/export': '/save',
2677
2778
  '/r': '/retry',
2678
2779
  '/kvip': '/key',
2780
+ '/switch': '/provider',
2781
+ '/providers': '/provider',
2782
+ '/fallback': '/provider',
2679
2783
  };
2680
2784
 
2681
2785
  export async function routeCommand(rawInput, ctx) {