@kernel.chat/kbot 3.8.2 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -20,6 +20,7 @@ import { clearHistory, clearMemory, compactHistory, restoreHistory } from './mem
20
20
  import { saveSession, loadSession, listSessions, deleteSession, formatSessionList, } from './sessions.js';
21
21
  import { createAgent, removeAgent, getAgent, formatAgentList, formatAgentDetail, PRESETS, getMatrixAgentIds, activateMimic, listMimicProfiles, getMimicProfile, registerBuiltinAgents, formatBuiltinAgentList, formatBuiltinAgentDetail, } from './matrix.js';
22
22
  import { getExtendedStats, incrementSessions, learnFact, selfTrain, getTrainingLog, flushPendingWrites } from './learning.js';
23
+ import { maybeSynthesize, getSynthesisStats } from './memory-synthesis.js';
23
24
  import { banner, bannerCompact, bannerAuth, prompt as kbotPrompt, printError, printSuccess, printInfo, printWarn, printResponse, printHelp, printGoodbye, setQuiet, } from './ui.js';
24
25
  import { checkForUpdate, selfUpdate } from './updater.js';
25
26
  import { runTutorial } from './tutorial.js';
@@ -46,6 +47,7 @@ async function main() {
46
47
  .option('-t, --thinking', 'Show AI reasoning steps')
47
48
  .option('--thinking-budget <tokens>', 'Thinking token budget (default: 10000)')
48
49
  .option('--self-eval', 'Enable self-evaluation loop (score and retry low-quality responses)')
50
+ .option('--plan', 'Plan mode — read-only exploration, no changes')
49
51
  .option('--architect', 'Architect mode — plan-review-implement with dual agents')
50
52
  .option('--safe', 'Confirm destructive operations')
51
53
  .option('--strict', 'Confirm ALL operations')
@@ -525,6 +527,161 @@ async function main() {
525
527
  const speed = system.recommendModelSpeed();
526
528
  process.stderr.write(`\n ${AMETHYST('Cost regulation')}: ${speed === 'fast' ? chalk.yellow('conserving (fast model)') : chalk.green('normal (default model)')}\n\n`);
527
529
  });
530
+ program
531
+ .command('status')
532
+ .description('Unified Kernel dashboard — version, tools, agents, learning, collective, npm, GitHub, bootstrap')
533
+ .option('--json', 'Output as JSON')
534
+ .action(async (opts) => {
535
+ // Check both subcommand and parent opts (commander absorbs --json at parent level)
536
+ const jsonMode = opts.json || program.opts().json;
537
+ const chalk = (await import('chalk')).default;
538
+ const AMETHYST = chalk.hex('#6B5B95');
539
+ const DIM = chalk.dim;
540
+ const GREEN = chalk.hex('#4ADE80');
541
+ const YELLOW = chalk.hex('#FBBF24');
542
+ const RED = chalk.hex('#F87171');
543
+ const CYAN = chalk.hex('#67E8F9');
544
+ const line = DIM(' ' + '─'.repeat(40));
545
+ // ── 1. Version & latest check ──
546
+ let latestVersion = '';
547
+ let isLatest = false;
548
+ const npmDownloads = {};
549
+ const githubData = {};
550
+ // Fire all network requests in parallel with 3s timeouts
551
+ const [npmVersionRes, npmDlRes, ghRes] = await Promise.allSettled([
552
+ fetch('https://registry.npmjs.org/@kernel.chat/kbot/latest', { signal: AbortSignal.timeout(3000) })
553
+ .then(r => r.json()),
554
+ fetch('https://api.npmjs.org/downloads/point/last-week/@kernel.chat/kbot', { signal: AbortSignal.timeout(3000) })
555
+ .then(r => r.json()),
556
+ fetch('https://api.github.com/repos/isaacsight/kernel', { signal: AbortSignal.timeout(3000) })
557
+ .then(r => r.json()),
558
+ ]);
559
+ if (npmVersionRes.status === 'fulfilled' && npmVersionRes.value.version) {
560
+ latestVersion = npmVersionRes.value.version;
561
+ isLatest = VERSION === latestVersion;
562
+ }
563
+ if (npmDlRes.status === 'fulfilled' && npmDlRes.value.downloads !== undefined) {
564
+ npmDownloads.downloads = npmDlRes.value.downloads;
565
+ }
566
+ if (ghRes.status === 'fulfilled' && ghRes.value.stargazers_count !== undefined) {
567
+ githubData.stars = ghRes.value.stargazers_count;
568
+ githubData.issues = ghRes.value.open_issues_count ?? 0;
569
+ }
570
+ // ── 2. Learning stats ──
571
+ const { getStats } = await import('./learning.js');
572
+ const stats = getStats();
573
+ // ── 3. Collective ──
574
+ const { getOptInState } = await import('./collective.js');
575
+ const collectiveState = getOptInState();
576
+ // ── 4. Tools count ──
577
+ const { getAllTools } = await import('./tools/index.js');
578
+ const toolCount = getAllTools().length || 290; // fallback to known count if tools not yet registered
579
+ // ── 5. Bootstrap (autotelic score) ──
580
+ let bootstrapScore = 0;
581
+ let bootstrapMax = 0;
582
+ let bootstrapGrade = '?';
583
+ let bootstrapTopFix = '';
584
+ let distributionPct = '';
585
+ try {
586
+ const { runBootstrap } = await import('./bootstrap.js');
587
+ const report = await runBootstrap();
588
+ bootstrapScore = report.score;
589
+ bootstrapMax = report.maxScore;
590
+ bootstrapGrade = report.grade;
591
+ bootstrapTopFix = report.topFix;
592
+ const distSection = report.sections.find(s => s.name.toLowerCase().includes('distribution'));
593
+ if (distSection) {
594
+ distributionPct = `${Math.round((distSection.score / distSection.maxScore) * 100)}%`;
595
+ }
596
+ }
597
+ catch { /* bootstrap can fail if not in a project dir */ }
598
+ // ── 6. Cognitive modules — count from known list ──
599
+ const cognitiveModules = [
600
+ 'learning', 'entropy-context', 'autopoiesis', 'free-energy',
601
+ 'predictive-processing', 'reasoning', 'intentionality',
602
+ 'temporal', 'confidence', 'strange-loops', 'integrated-information',
603
+ ];
604
+ const cognitiveCount = cognitiveModules.length; // 11
605
+ // ── JSON output ──
606
+ if (jsonMode) {
607
+ console.log(JSON.stringify({
608
+ version: VERSION,
609
+ latestVersion: latestVersion || null,
610
+ isLatest,
611
+ tools: toolCount,
612
+ agents: 23,
613
+ cognitiveModules: cognitiveCount,
614
+ learning: {
615
+ patterns: stats.patternsCount,
616
+ solutions: stats.solutionsCount,
617
+ tokensSaved: stats.totalTokensSaved,
618
+ efficiency: stats.efficiency,
619
+ },
620
+ collective: {
621
+ enabled: collectiveState.enabled,
622
+ signalsSent: collectiveState.total_signals_sent,
623
+ },
624
+ npm: { weeklyDownloads: npmDownloads.downloads ?? null },
625
+ github: { stars: githubData.stars ?? null, issues: githubData.issues ?? null },
626
+ bootstrap: {
627
+ score: bootstrapScore,
628
+ maxScore: bootstrapMax,
629
+ grade: bootstrapGrade,
630
+ topFix: bootstrapTopFix,
631
+ },
632
+ }, null, 2));
633
+ return;
634
+ }
635
+ // ── Formatted dashboard ──
636
+ const versionTag = latestVersion
637
+ ? (isLatest ? GREEN(' (latest ✓)') : YELLOW(` (update: ${latestVersion})`))
638
+ : DIM(' (offline)');
639
+ const fmtNum = (n) => n.toLocaleString();
640
+ process.stderr.write('\n');
641
+ process.stderr.write(` ${AMETHYST('◉')} ${chalk.bold('Kernel Status')}\n`);
642
+ process.stderr.write(line + '\n');
643
+ process.stderr.write(` ${chalk.bold('Version')} ${VERSION}${versionTag}\n`);
644
+ process.stderr.write(` ${chalk.bold('Tools')} ${fmtNum(toolCount)} ${DIM('|')} ${chalk.bold('Agents')} 23\n`);
645
+ process.stderr.write(` ${chalk.bold('Cognitive')} ${cognitiveCount}/${cognitiveCount} modules active\n`);
646
+ process.stderr.write(line + '\n');
647
+ // Learning
648
+ const tokensSavedStr = stats.totalTokensSaved > 0 ? fmtNum(stats.totalTokensSaved) + ' tokens saved' : 'learning...';
649
+ process.stderr.write(` ${chalk.bold('Learning')} ${fmtNum(stats.patternsCount)} patterns ${DIM('|')} ${fmtNum(stats.solutionsCount)} solutions ${DIM('|')} ${tokensSavedStr}\n`);
650
+ // Collective
651
+ const collectiveEnabled = collectiveState.enabled ? GREEN('enabled') : DIM('disabled');
652
+ const signalsSent = fmtNum(collectiveState.total_signals_sent || 0);
653
+ process.stderr.write(` ${chalk.bold('Collective')} ${collectiveEnabled} ${DIM('|')} ${signalsSent} signals sent\n`);
654
+ process.stderr.write(line + '\n');
655
+ // npm
656
+ if (npmDownloads.downloads !== undefined) {
657
+ process.stderr.write(` ${chalk.bold('npm')} ${CYAN(fmtNum(npmDownloads.downloads))} downloads/week\n`);
658
+ }
659
+ else {
660
+ process.stderr.write(` ${chalk.bold('npm')} ${DIM('unavailable')}\n`);
661
+ }
662
+ // GitHub
663
+ if (githubData.stars !== undefined) {
664
+ const starStr = `${fmtNum(githubData.stars)} star${githubData.stars === 1 ? '' : 's'}`;
665
+ const issueStr = `${fmtNum(githubData.issues)} issue${githubData.issues === 1 ? '' : 's'}`;
666
+ process.stderr.write(` ${chalk.bold('GitHub')} ${starStr} ${DIM('|')} ${issueStr}\n`);
667
+ }
668
+ else {
669
+ process.stderr.write(` ${chalk.bold('GitHub')} ${DIM('unavailable')}\n`);
670
+ }
671
+ // Bootstrap
672
+ if (bootstrapMax > 0) {
673
+ const pct = Math.round((bootstrapScore / bootstrapMax) * 100);
674
+ const gradeColor = pct >= 80 ? GREEN : pct >= 60 ? YELLOW : RED;
675
+ const distStr = distributionPct ? ` ${DIM('— distribution at')} ${distributionPct}` : '';
676
+ process.stderr.write(` ${chalk.bold('Bootstrap')} ${bootstrapScore}/${bootstrapMax} (${gradeColor(bootstrapGrade)})${distStr}\n`);
677
+ }
678
+ process.stderr.write(line + '\n');
679
+ // Next action (top fix from bootstrap)
680
+ if (bootstrapTopFix) {
681
+ process.stderr.write(` ${chalk.bold('Next:')} ${bootstrapTopFix}\n`);
682
+ }
683
+ process.stderr.write('\n');
684
+ });
528
685
  program
529
686
  .command('immune')
530
687
  .description('Self-audit — kbot\'s immune system scans its own code for bugs, security holes, and bad decisions')
@@ -1143,6 +1300,140 @@ async function main() {
1143
1300
  const md = generateChangelog({ since: changelogOpts.since, format: 'markdown' });
1144
1301
  process.stdout.write(md);
1145
1302
  });
1303
+ // ── Automate subcommands ──
1304
+ const automateCmd = program
1305
+ .command('automate')
1306
+ .description('Event-driven automations — file watchers, schedules, git hooks, webhooks');
1307
+ automateCmd
1308
+ .command('list')
1309
+ .description('List all configured automations')
1310
+ .action(async () => {
1311
+ const { listAutomations, formatAutomationList } = await import('./automations.js');
1312
+ const automations = listAutomations();
1313
+ process.stderr.write(formatAutomationList(automations) + '\n');
1314
+ });
1315
+ automateCmd
1316
+ .command('add')
1317
+ .description('Create a new automation')
1318
+ .requiredOption('--trigger <trigger>', 'Trigger spec (e.g., "file:src/**/*.ts:change", "schedule:every 5m", "git:pre-commit", "webhook:/deploy")')
1319
+ .requiredOption('--agent <agent>', 'Agent to run (e.g., coder, researcher, guardian)')
1320
+ .requiredOption('--prompt <prompt>', 'Prompt to send to the agent')
1321
+ .option('--name <name>', 'Human-readable name for the automation')
1322
+ .option('--tools <tools>', 'Comma-separated list of tools to enable')
1323
+ .action(async (addOpts) => {
1324
+ try {
1325
+ const { createAutomation, parseTriggerString } = await import('./automations.js');
1326
+ const trigger = parseTriggerString(addOpts.trigger);
1327
+ const automation = createAutomation({
1328
+ name: addOpts.name || `${trigger.type}:${addOpts.agent}`,
1329
+ trigger,
1330
+ action: {
1331
+ agent: addOpts.agent,
1332
+ prompt: addOpts.prompt,
1333
+ tools: addOpts.tools ? addOpts.tools.split(',').map((t) => t.trim()) : undefined,
1334
+ },
1335
+ });
1336
+ printSuccess(`Automation created: ${automation.name} (${automation.id})`);
1337
+ printInfo(` Trigger: ${addOpts.trigger}`);
1338
+ printInfo(` Agent: ${addOpts.agent}`);
1339
+ if (trigger.type === 'git') {
1340
+ printInfo(' Git hooks installed.');
1341
+ }
1342
+ if (trigger.type === 'file' || trigger.type === 'schedule') {
1343
+ printInfo(' Run `kbot automate start` to activate the daemon.');
1344
+ }
1345
+ }
1346
+ catch (err) {
1347
+ printError(err instanceof Error ? err.message : String(err));
1348
+ process.exit(1);
1349
+ }
1350
+ });
1351
+ automateCmd
1352
+ .command('remove')
1353
+ .description('Remove an automation by ID')
1354
+ .argument('<id>', 'Automation ID')
1355
+ .action(async (id) => {
1356
+ const { removeAutomation } = await import('./automations.js');
1357
+ const removed = removeAutomation(id);
1358
+ if (removed) {
1359
+ printSuccess(`Automation "${id}" removed.`);
1360
+ }
1361
+ else {
1362
+ printError(`Automation "${id}" not found.`);
1363
+ process.exit(1);
1364
+ }
1365
+ });
1366
+ automateCmd
1367
+ .command('run')
1368
+ .description('Manually trigger an automation')
1369
+ .argument('<id>', 'Automation ID')
1370
+ .action(async (id) => {
1371
+ const { runAutomation, getAutomation } = await import('./automations.js');
1372
+ const automation = getAutomation(id);
1373
+ if (!automation) {
1374
+ printError(`Automation "${id}" not found.`);
1375
+ process.exit(1);
1376
+ }
1377
+ printInfo(`Running automation: ${automation.name}...`);
1378
+ const result = await runAutomation(id);
1379
+ if (result.success) {
1380
+ printSuccess(`Automation completed successfully.`);
1381
+ if (result.output) {
1382
+ process.stderr.write('\n' + result.output + '\n');
1383
+ }
1384
+ }
1385
+ else {
1386
+ printError(`Automation failed: ${result.error}`);
1387
+ process.exit(1);
1388
+ }
1389
+ });
1390
+ automateCmd
1391
+ .command('start')
1392
+ .description('Start the automation daemon (file watchers + schedule timers)')
1393
+ .action(async () => {
1394
+ const { startAutomationDaemon, listAutomations } = await import('./automations.js');
1395
+ const automations = listAutomations();
1396
+ if (automations.length === 0) {
1397
+ printError('No automations configured. Use `kbot automate add` first.');
1398
+ process.exit(1);
1399
+ }
1400
+ printInfo('Starting automation daemon...');
1401
+ const { running } = startAutomationDaemon({
1402
+ log: (msg) => process.stderr.write(msg + '\n'),
1403
+ });
1404
+ if (running) {
1405
+ printSuccess('Daemon running. Press Ctrl+C to stop.');
1406
+ // Keep the process alive
1407
+ await new Promise(() => { });
1408
+ }
1409
+ });
1410
+ program
1411
+ .command('spec <description>')
1412
+ .description('Generate a formal specification with requirements + acceptance criteria before coding')
1413
+ .option('--implement', 'Generate spec then pass to coder agent for implementation')
1414
+ .option('--agent <name>', 'Override the default architect agent', 'architect')
1415
+ .option('--output <path>', 'Custom output path for the spec file')
1416
+ .action(async (description, specOpts) => {
1417
+ const { generateSpec } = await import('./spec.js');
1418
+ const parentOpts = program.opts();
1419
+ try {
1420
+ await generateSpec(description, {
1421
+ agent: specOpts.agent,
1422
+ output: specOpts.output,
1423
+ implement: specOpts.implement,
1424
+ agentOpts: {
1425
+ model: parentOpts.model,
1426
+ stream: parentOpts.stream ?? true,
1427
+ thinking: parentOpts.thinking || false,
1428
+ thinkingBudget: parentOpts.thinkingBudget ? (parseInt(parentOpts.thinkingBudget, 10) || 10000) : undefined,
1429
+ },
1430
+ });
1431
+ }
1432
+ catch (err) {
1433
+ printError(err instanceof Error ? err.message : String(err));
1434
+ process.exit(1);
1435
+ }
1436
+ });
1146
1437
  program
1147
1438
  .command('completions')
1148
1439
  .description('Generate shell tab-completion script (bash, zsh, fish)')
@@ -1163,7 +1454,7 @@ async function main() {
1163
1454
  if (opts.quiet)
1164
1455
  setQuiet(true);
1165
1456
  // If a sub-command was run, we're done
1166
- if (['byok', 'auth', 'ide', 'local', 'ollama', 'kbot-local', 'pull', 'doctor', 'serve', 'agents', 'watch', 'voice', 'export', 'plugins', 'changelog', 'completions'].includes(program.args[0]))
1457
+ if (['byok', 'auth', 'ide', 'local', 'ollama', 'kbot-local', 'pull', 'doctor', 'serve', 'agents', 'watch', 'voice', 'export', 'plugins', 'changelog', 'completions', 'automate', 'status', 'spec'].includes(program.args[0]))
1167
1458
  return;
1168
1459
  // Check for API key (BYOK or local provider)
1169
1460
  let byokActive = isByokEnabled();
@@ -1336,7 +1627,7 @@ async function main() {
1336
1627
  printInfo(syncMsg);
1337
1628
  // Determine if we're in one-shot or REPL mode
1338
1629
  const isOneShot = promptArgs.length > 0 &&
1339
- !['byok', 'auth', 'ide', 'ollama', 'kbot-local', 'pull', 'doctor'].includes(promptArgs[0]);
1630
+ !['byok', 'auth', 'ide', 'ollama', 'kbot-local', 'pull', 'doctor', 'spec'].includes(promptArgs[0]);
1340
1631
  const isStdinOnly = !process.stdin.isTTY && promptArgs.length === 0;
1341
1632
  if (isOneShot || isStdinOnly || opts.pipe) {
1342
1633
  // One-shot / pipe mode: start lazy tools in background (non-blocking).
@@ -1357,6 +1648,7 @@ async function main() {
1357
1648
  tier,
1358
1649
  thinking: opts.thinking || false,
1359
1650
  thinkingBudget: opts.thinkingBudget ? (parseInt(opts.thinkingBudget, 10) || 10000) : undefined,
1651
+ plan: opts.plan || false,
1360
1652
  };
1361
1653
  // Enable self-evaluation if requested
1362
1654
  if (opts.selfEval) {
@@ -1476,7 +1768,7 @@ async function main() {
1476
1768
  stdinContent = Buffer.concat(chunks).toString('utf-8').trim();
1477
1769
  }
1478
1770
  // One-shot mode: kbot "fix the bug" — always stream for speed
1479
- if (promptArgs.length > 0 && !['byok', 'auth', 'ide', 'ollama', 'kbot-local', 'pull', 'doctor'].includes(promptArgs[0])) {
1771
+ if (promptArgs.length > 0 && !['byok', 'auth', 'ide', 'ollama', 'kbot-local', 'pull', 'doctor', 'spec'].includes(promptArgs[0])) {
1480
1772
  if (!opts.pipe)
1481
1773
  console.log(bannerCompact());
1482
1774
  let message = promptArgs.join(' ');
@@ -1825,9 +2117,20 @@ async function startRepl(agentOpts, context, tier, byokActive = false, localActi
1825
2117
  printInfo(`${p.name}`);
1826
2118
  }
1827
2119
  const sessionCount = incrementSessions();
2120
+ // Three-tier memory: run synthesis on every 10th session (or first time with enough data)
2121
+ if (sessionCount % 10 === 0 || sessionCount === 1) {
2122
+ try {
2123
+ const insightCount = maybeSynthesize();
2124
+ if (insightCount > 0) {
2125
+ printInfo(`Memory synthesis: ${insightCount} insights generated`);
2126
+ }
2127
+ }
2128
+ catch { /* synthesis is non-critical */ }
2129
+ }
1828
2130
  // Return-visit greeting — show kbot's growth
1829
2131
  if (sessionCount > 1) {
1830
2132
  const stats = getExtendedStats();
2133
+ const synthStats = getSynthesisStats();
1831
2134
  const parts = [];
1832
2135
  if (stats.patternsCount > 0)
1833
2136
  parts.push(`${stats.patternsCount} patterns learned`);
@@ -1835,6 +2138,8 @@ async function startRepl(agentOpts, context, tier, byokActive = false, localActi
1835
2138
  parts.push(`${stats.solutionsCount} solutions cached`);
1836
2139
  if (stats.knowledgeCount > 0)
1837
2140
  parts.push(`${stats.knowledgeCount} facts remembered`);
2141
+ if (synthStats.insightCount > 0)
2142
+ parts.push(`${synthStats.insightCount} insights synthesized`);
1838
2143
  if (parts.length > 0) {
1839
2144
  printInfo(`Session ${stats.sessions} · ${parts.join(' · ')}`);
1840
2145
  }