@kernel.chat/kbot 3.8.1 → 3.9.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,7 +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 { banner, bannerCompact, bannerAuth, prompt as kbotPrompt, printError, printSuccess, printInfo, printResponse, printHelp, printGoodbye, setQuiet, } from './ui.js';
23
+ import { banner, bannerCompact, bannerAuth, prompt as kbotPrompt, printError, printSuccess, printInfo, printWarn, printResponse, printHelp, printGoodbye, setQuiet, } from './ui.js';
24
24
  import { checkForUpdate, selfUpdate } from './updater.js';
25
25
  import { runTutorial } from './tutorial.js';
26
26
  import { syncOnStartup, schedulePush, flushCloudSync, isCloudSyncEnabled, setCloudToken, getCloudToken } from './cloud-sync.js';
@@ -46,6 +46,7 @@ async function main() {
46
46
  .option('-t, --thinking', 'Show AI reasoning steps')
47
47
  .option('--thinking-budget <tokens>', 'Thinking token budget (default: 10000)')
48
48
  .option('--self-eval', 'Enable self-evaluation loop (score and retry low-quality responses)')
49
+ .option('--plan', 'Plan mode — read-only exploration, no changes')
49
50
  .option('--architect', 'Architect mode — plan-review-implement with dual agents')
50
51
  .option('--safe', 'Confirm destructive operations')
51
52
  .option('--strict', 'Confirm ALL operations')
@@ -525,6 +526,161 @@ async function main() {
525
526
  const speed = system.recommendModelSpeed();
526
527
  process.stderr.write(`\n ${AMETHYST('Cost regulation')}: ${speed === 'fast' ? chalk.yellow('conserving (fast model)') : chalk.green('normal (default model)')}\n\n`);
527
528
  });
529
+ program
530
+ .command('status')
531
+ .description('Unified Kernel dashboard — version, tools, agents, learning, collective, npm, GitHub, bootstrap')
532
+ .option('--json', 'Output as JSON')
533
+ .action(async (opts) => {
534
+ // Check both subcommand and parent opts (commander absorbs --json at parent level)
535
+ const jsonMode = opts.json || program.opts().json;
536
+ const chalk = (await import('chalk')).default;
537
+ const AMETHYST = chalk.hex('#6B5B95');
538
+ const DIM = chalk.dim;
539
+ const GREEN = chalk.hex('#4ADE80');
540
+ const YELLOW = chalk.hex('#FBBF24');
541
+ const RED = chalk.hex('#F87171');
542
+ const CYAN = chalk.hex('#67E8F9');
543
+ const line = DIM(' ' + '─'.repeat(40));
544
+ // ── 1. Version & latest check ──
545
+ let latestVersion = '';
546
+ let isLatest = false;
547
+ const npmDownloads = {};
548
+ const githubData = {};
549
+ // Fire all network requests in parallel with 3s timeouts
550
+ const [npmVersionRes, npmDlRes, ghRes] = await Promise.allSettled([
551
+ fetch('https://registry.npmjs.org/@kernel.chat/kbot/latest', { signal: AbortSignal.timeout(3000) })
552
+ .then(r => r.json()),
553
+ fetch('https://api.npmjs.org/downloads/point/last-week/@kernel.chat/kbot', { signal: AbortSignal.timeout(3000) })
554
+ .then(r => r.json()),
555
+ fetch('https://api.github.com/repos/isaacsight/kernel', { signal: AbortSignal.timeout(3000) })
556
+ .then(r => r.json()),
557
+ ]);
558
+ if (npmVersionRes.status === 'fulfilled' && npmVersionRes.value.version) {
559
+ latestVersion = npmVersionRes.value.version;
560
+ isLatest = VERSION === latestVersion;
561
+ }
562
+ if (npmDlRes.status === 'fulfilled' && npmDlRes.value.downloads !== undefined) {
563
+ npmDownloads.downloads = npmDlRes.value.downloads;
564
+ }
565
+ if (ghRes.status === 'fulfilled' && ghRes.value.stargazers_count !== undefined) {
566
+ githubData.stars = ghRes.value.stargazers_count;
567
+ githubData.issues = ghRes.value.open_issues_count ?? 0;
568
+ }
569
+ // ── 2. Learning stats ──
570
+ const { getStats } = await import('./learning.js');
571
+ const stats = getStats();
572
+ // ── 3. Collective ──
573
+ const { getOptInState } = await import('./collective.js');
574
+ const collectiveState = getOptInState();
575
+ // ── 4. Tools count ──
576
+ const { getAllTools } = await import('./tools/index.js');
577
+ const toolCount = getAllTools().length || 290; // fallback to known count if tools not yet registered
578
+ // ── 5. Bootstrap (autotelic score) ──
579
+ let bootstrapScore = 0;
580
+ let bootstrapMax = 0;
581
+ let bootstrapGrade = '?';
582
+ let bootstrapTopFix = '';
583
+ let distributionPct = '';
584
+ try {
585
+ const { runBootstrap } = await import('./bootstrap.js');
586
+ const report = await runBootstrap();
587
+ bootstrapScore = report.score;
588
+ bootstrapMax = report.maxScore;
589
+ bootstrapGrade = report.grade;
590
+ bootstrapTopFix = report.topFix;
591
+ const distSection = report.sections.find(s => s.name.toLowerCase().includes('distribution'));
592
+ if (distSection) {
593
+ distributionPct = `${Math.round((distSection.score / distSection.maxScore) * 100)}%`;
594
+ }
595
+ }
596
+ catch { /* bootstrap can fail if not in a project dir */ }
597
+ // ── 6. Cognitive modules — count from known list ──
598
+ const cognitiveModules = [
599
+ 'learning', 'entropy-context', 'autopoiesis', 'free-energy',
600
+ 'predictive-processing', 'reasoning', 'intentionality',
601
+ 'temporal', 'confidence', 'strange-loops', 'integrated-information',
602
+ ];
603
+ const cognitiveCount = cognitiveModules.length; // 11
604
+ // ── JSON output ──
605
+ if (jsonMode) {
606
+ console.log(JSON.stringify({
607
+ version: VERSION,
608
+ latestVersion: latestVersion || null,
609
+ isLatest,
610
+ tools: toolCount,
611
+ agents: 23,
612
+ cognitiveModules: cognitiveCount,
613
+ learning: {
614
+ patterns: stats.patternsCount,
615
+ solutions: stats.solutionsCount,
616
+ tokensSaved: stats.totalTokensSaved,
617
+ efficiency: stats.efficiency,
618
+ },
619
+ collective: {
620
+ enabled: collectiveState.enabled,
621
+ signalsSent: collectiveState.total_signals_sent,
622
+ },
623
+ npm: { weeklyDownloads: npmDownloads.downloads ?? null },
624
+ github: { stars: githubData.stars ?? null, issues: githubData.issues ?? null },
625
+ bootstrap: {
626
+ score: bootstrapScore,
627
+ maxScore: bootstrapMax,
628
+ grade: bootstrapGrade,
629
+ topFix: bootstrapTopFix,
630
+ },
631
+ }, null, 2));
632
+ return;
633
+ }
634
+ // ── Formatted dashboard ──
635
+ const versionTag = latestVersion
636
+ ? (isLatest ? GREEN(' (latest ✓)') : YELLOW(` (update: ${latestVersion})`))
637
+ : DIM(' (offline)');
638
+ const fmtNum = (n) => n.toLocaleString();
639
+ process.stderr.write('\n');
640
+ process.stderr.write(` ${AMETHYST('◉')} ${chalk.bold('Kernel Status')}\n`);
641
+ process.stderr.write(line + '\n');
642
+ process.stderr.write(` ${chalk.bold('Version')} ${VERSION}${versionTag}\n`);
643
+ process.stderr.write(` ${chalk.bold('Tools')} ${fmtNum(toolCount)} ${DIM('|')} ${chalk.bold('Agents')} 23\n`);
644
+ process.stderr.write(` ${chalk.bold('Cognitive')} ${cognitiveCount}/${cognitiveCount} modules active\n`);
645
+ process.stderr.write(line + '\n');
646
+ // Learning
647
+ const tokensSavedStr = stats.totalTokensSaved > 0 ? fmtNum(stats.totalTokensSaved) + ' tokens saved' : 'learning...';
648
+ process.stderr.write(` ${chalk.bold('Learning')} ${fmtNum(stats.patternsCount)} patterns ${DIM('|')} ${fmtNum(stats.solutionsCount)} solutions ${DIM('|')} ${tokensSavedStr}\n`);
649
+ // Collective
650
+ const collectiveEnabled = collectiveState.enabled ? GREEN('enabled') : DIM('disabled');
651
+ const signalsSent = fmtNum(collectiveState.total_signals_sent || 0);
652
+ process.stderr.write(` ${chalk.bold('Collective')} ${collectiveEnabled} ${DIM('|')} ${signalsSent} signals sent\n`);
653
+ process.stderr.write(line + '\n');
654
+ // npm
655
+ if (npmDownloads.downloads !== undefined) {
656
+ process.stderr.write(` ${chalk.bold('npm')} ${CYAN(fmtNum(npmDownloads.downloads))} downloads/week\n`);
657
+ }
658
+ else {
659
+ process.stderr.write(` ${chalk.bold('npm')} ${DIM('unavailable')}\n`);
660
+ }
661
+ // GitHub
662
+ if (githubData.stars !== undefined) {
663
+ const starStr = `${fmtNum(githubData.stars)} star${githubData.stars === 1 ? '' : 's'}`;
664
+ const issueStr = `${fmtNum(githubData.issues)} issue${githubData.issues === 1 ? '' : 's'}`;
665
+ process.stderr.write(` ${chalk.bold('GitHub')} ${starStr} ${DIM('|')} ${issueStr}\n`);
666
+ }
667
+ else {
668
+ process.stderr.write(` ${chalk.bold('GitHub')} ${DIM('unavailable')}\n`);
669
+ }
670
+ // Bootstrap
671
+ if (bootstrapMax > 0) {
672
+ const pct = Math.round((bootstrapScore / bootstrapMax) * 100);
673
+ const gradeColor = pct >= 80 ? GREEN : pct >= 60 ? YELLOW : RED;
674
+ const distStr = distributionPct ? ` ${DIM('— distribution at')} ${distributionPct}` : '';
675
+ process.stderr.write(` ${chalk.bold('Bootstrap')} ${bootstrapScore}/${bootstrapMax} (${gradeColor(bootstrapGrade)})${distStr}\n`);
676
+ }
677
+ process.stderr.write(line + '\n');
678
+ // Next action (top fix from bootstrap)
679
+ if (bootstrapTopFix) {
680
+ process.stderr.write(` ${chalk.bold('Next:')} ${bootstrapTopFix}\n`);
681
+ }
682
+ process.stderr.write('\n');
683
+ });
528
684
  program
529
685
  .command('immune')
530
686
  .description('Self-audit — kbot\'s immune system scans its own code for bugs, security holes, and bad decisions')
@@ -600,8 +756,10 @@ async function main() {
600
756
  .option('--disable', 'Opt out of collective learning')
601
757
  .option('--status', 'Show collective learning status')
602
758
  .option('--pull', 'Pull latest collective patterns')
759
+ .option('--diagnose', 'Run end-to-end health check on collective connectivity')
760
+ .option('--insights', 'Show what the collective knows (hints table + top patterns)')
603
761
  .action(async (opts) => {
604
- const { setOptIn, getCollectiveStats, pullCollectiveHints, pullCollectivePatterns, getOptInState } = await import('./collective.js');
762
+ const { setOptIn, getCollectiveStats, pullCollectiveHints, pullCollectivePatterns, getOptInState, sendSignal, getSignalQueueSize } = await import('./collective.js');
605
763
  if (opts.enable) {
606
764
  setOptIn(true);
607
765
  printSuccess('Collective learning enabled!');
@@ -620,6 +778,146 @@ async function main() {
620
778
  printSuccess(`Pulled ${hints.length} routing hints and ${patterns.length} patterns from the collective.`);
621
779
  return;
622
780
  }
781
+ if (opts.diagnose) {
782
+ console.log();
783
+ printInfo('Collective Health Check');
784
+ printInfo('─'.repeat(40));
785
+ // 1. Opt-in status
786
+ const state = getOptInState();
787
+ if (state.enabled) {
788
+ printSuccess(`Opt-in: enabled (since ${state.opted_in_at || 'unknown'})`);
789
+ }
790
+ else {
791
+ printWarn('Opt-in: disabled — enable with `kbot collective --enable`');
792
+ }
793
+ // 2. Test signal
794
+ printInfo('Sending test signal...');
795
+ // Temporarily need opt-in for sendSignal to work
796
+ const wasEnabled = state.enabled;
797
+ if (!wasEnabled)
798
+ setOptIn(true);
799
+ const testSignal = {
800
+ message_hash: 'test',
801
+ message_category: 'diagnostic',
802
+ message_length: 4,
803
+ routed_agent: 'kernel',
804
+ classifier_confidence: 1.0,
805
+ was_rerouted: false,
806
+ response_quality: 1.0,
807
+ tool_sequence: ['diagnostic_ping'],
808
+ strategy: 'health_check',
809
+ source: 'kbot',
810
+ };
811
+ const signalOk = await sendSignal(testSignal);
812
+ if (!wasEnabled)
813
+ setOptIn(false); // Restore original state
814
+ if (signalOk) {
815
+ printSuccess('Signal send: OK — endpoint accepted the test signal');
816
+ }
817
+ else {
818
+ printError('Signal send: FAILED — endpoint unreachable or rejected the signal');
819
+ }
820
+ // 3. Pull hints
821
+ printInfo('Pulling routing hints...');
822
+ try {
823
+ const hints = await pullCollectiveHints();
824
+ if (hints.length > 0) {
825
+ printSuccess(`Hints pull: OK — ${hints.length} routing hints available`);
826
+ }
827
+ else {
828
+ printWarn('Hints pull: empty — no routing hints available yet');
829
+ }
830
+ }
831
+ catch {
832
+ printError('Hints pull: FAILED — could not retrieve hints');
833
+ }
834
+ // 4. Pull patterns
835
+ printInfo('Pulling collective patterns...');
836
+ try {
837
+ const patterns = await pullCollectivePatterns();
838
+ if (patterns.length > 0) {
839
+ printSuccess(`Patterns pull: OK — ${patterns.length} patterns available`);
840
+ }
841
+ else {
842
+ printWarn('Patterns pull: empty — no patterns available yet');
843
+ }
844
+ }
845
+ catch {
846
+ printError('Patterns pull: FAILED — could not retrieve patterns');
847
+ }
848
+ // 5. Signal queue status
849
+ const queueSize = getSignalQueueSize();
850
+ if (queueSize === 0) {
851
+ printSuccess('Signal queue: empty (all signals flushed)');
852
+ }
853
+ else {
854
+ printInfo(`Signal queue: ${queueSize} signal(s) pending flush`);
855
+ }
856
+ // Summary
857
+ console.log();
858
+ printInfo(`Total signals sent: ${state.total_signals_sent}`);
859
+ printInfo(`Last signal: ${state.last_signal_at || 'never'}`);
860
+ console.log();
861
+ return;
862
+ }
863
+ if (opts.insights) {
864
+ console.log();
865
+ printInfo('Collective Intelligence Insights');
866
+ printInfo('═'.repeat(50));
867
+ // Pull fresh data
868
+ const [hints, patterns] = await Promise.all([pullCollectiveHints(), pullCollectivePatterns()]);
869
+ // Routing hints table
870
+ console.log();
871
+ printInfo('Routing Hints');
872
+ printInfo('─'.repeat(50));
873
+ if (hints.length === 0) {
874
+ printWarn('No routing hints available yet. The collective needs more signals.');
875
+ }
876
+ else {
877
+ // Table header
878
+ const catW = 18, agentW = 14, confW = 12, sampW = 8;
879
+ console.log(chalk.bold(' ' +
880
+ 'Category'.padEnd(catW) +
881
+ 'Best Agent'.padEnd(agentW) +
882
+ 'Confidence'.padEnd(confW) +
883
+ 'Samples'.padEnd(sampW)));
884
+ console.log(' ' + '─'.repeat(catW + agentW + confW + sampW));
885
+ for (const h of hints) {
886
+ const conf = (h.confidence * 100).toFixed(0) + '%';
887
+ console.log(' ' +
888
+ h.category.padEnd(catW) +
889
+ h.best_agent.padEnd(agentW) +
890
+ conf.padEnd(confW) +
891
+ String(h.sample_count).padEnd(sampW));
892
+ }
893
+ }
894
+ // Top tool-sequence patterns
895
+ console.log();
896
+ printInfo('Top Tool Sequences');
897
+ printInfo('─'.repeat(50));
898
+ const toolPatterns = patterns
899
+ .filter(p => p.type === 'tool_sequence')
900
+ .sort((a, b) => b.confidence - a.confidence || b.sample_count - a.sample_count)
901
+ .slice(0, 10);
902
+ if (toolPatterns.length === 0) {
903
+ printWarn('No tool-sequence patterns available yet.');
904
+ }
905
+ else {
906
+ for (let i = 0; i < toolPatterns.length; i++) {
907
+ const p = toolPatterns[i];
908
+ const pat = p.pattern;
909
+ const tools = Array.isArray(pat.tools) ? pat.tools.join(' → ') : 'unknown';
910
+ const category = typeof pat.category === 'string' ? pat.category : 'general';
911
+ const conf = (p.confidence * 100).toFixed(0) + '%';
912
+ console.log(chalk.dim(` ${String(i + 1).padStart(2)}.`) +
913
+ ` [${category}] ` +
914
+ chalk.cyan(tools) +
915
+ chalk.dim(` (${conf}, ${p.sample_count} samples)`));
916
+ }
917
+ }
918
+ console.log();
919
+ return;
920
+ }
623
921
  // Default: show status
624
922
  console.log();
625
923
  console.log(getCollectiveStats());
@@ -1001,6 +1299,113 @@ async function main() {
1001
1299
  const md = generateChangelog({ since: changelogOpts.since, format: 'markdown' });
1002
1300
  process.stdout.write(md);
1003
1301
  });
1302
+ // ── Automate subcommands ──
1303
+ const automateCmd = program
1304
+ .command('automate')
1305
+ .description('Event-driven automations — file watchers, schedules, git hooks, webhooks');
1306
+ automateCmd
1307
+ .command('list')
1308
+ .description('List all configured automations')
1309
+ .action(async () => {
1310
+ const { listAutomations, formatAutomationList } = await import('./automations.js');
1311
+ const automations = listAutomations();
1312
+ process.stderr.write(formatAutomationList(automations) + '\n');
1313
+ });
1314
+ automateCmd
1315
+ .command('add')
1316
+ .description('Create a new automation')
1317
+ .requiredOption('--trigger <trigger>', 'Trigger spec (e.g., "file:src/**/*.ts:change", "schedule:every 5m", "git:pre-commit", "webhook:/deploy")')
1318
+ .requiredOption('--agent <agent>', 'Agent to run (e.g., coder, researcher, guardian)')
1319
+ .requiredOption('--prompt <prompt>', 'Prompt to send to the agent')
1320
+ .option('--name <name>', 'Human-readable name for the automation')
1321
+ .option('--tools <tools>', 'Comma-separated list of tools to enable')
1322
+ .action(async (addOpts) => {
1323
+ try {
1324
+ const { createAutomation, parseTriggerString } = await import('./automations.js');
1325
+ const trigger = parseTriggerString(addOpts.trigger);
1326
+ const automation = createAutomation({
1327
+ name: addOpts.name || `${trigger.type}:${addOpts.agent}`,
1328
+ trigger,
1329
+ action: {
1330
+ agent: addOpts.agent,
1331
+ prompt: addOpts.prompt,
1332
+ tools: addOpts.tools ? addOpts.tools.split(',').map((t) => t.trim()) : undefined,
1333
+ },
1334
+ });
1335
+ printSuccess(`Automation created: ${automation.name} (${automation.id})`);
1336
+ printInfo(` Trigger: ${addOpts.trigger}`);
1337
+ printInfo(` Agent: ${addOpts.agent}`);
1338
+ if (trigger.type === 'git') {
1339
+ printInfo(' Git hooks installed.');
1340
+ }
1341
+ if (trigger.type === 'file' || trigger.type === 'schedule') {
1342
+ printInfo(' Run `kbot automate start` to activate the daemon.');
1343
+ }
1344
+ }
1345
+ catch (err) {
1346
+ printError(err instanceof Error ? err.message : String(err));
1347
+ process.exit(1);
1348
+ }
1349
+ });
1350
+ automateCmd
1351
+ .command('remove')
1352
+ .description('Remove an automation by ID')
1353
+ .argument('<id>', 'Automation ID')
1354
+ .action(async (id) => {
1355
+ const { removeAutomation } = await import('./automations.js');
1356
+ const removed = removeAutomation(id);
1357
+ if (removed) {
1358
+ printSuccess(`Automation "${id}" removed.`);
1359
+ }
1360
+ else {
1361
+ printError(`Automation "${id}" not found.`);
1362
+ process.exit(1);
1363
+ }
1364
+ });
1365
+ automateCmd
1366
+ .command('run')
1367
+ .description('Manually trigger an automation')
1368
+ .argument('<id>', 'Automation ID')
1369
+ .action(async (id) => {
1370
+ const { runAutomation, getAutomation } = await import('./automations.js');
1371
+ const automation = getAutomation(id);
1372
+ if (!automation) {
1373
+ printError(`Automation "${id}" not found.`);
1374
+ process.exit(1);
1375
+ }
1376
+ printInfo(`Running automation: ${automation.name}...`);
1377
+ const result = await runAutomation(id);
1378
+ if (result.success) {
1379
+ printSuccess(`Automation completed successfully.`);
1380
+ if (result.output) {
1381
+ process.stderr.write('\n' + result.output + '\n');
1382
+ }
1383
+ }
1384
+ else {
1385
+ printError(`Automation failed: ${result.error}`);
1386
+ process.exit(1);
1387
+ }
1388
+ });
1389
+ automateCmd
1390
+ .command('start')
1391
+ .description('Start the automation daemon (file watchers + schedule timers)')
1392
+ .action(async () => {
1393
+ const { startAutomationDaemon, listAutomations } = await import('./automations.js');
1394
+ const automations = listAutomations();
1395
+ if (automations.length === 0) {
1396
+ printError('No automations configured. Use `kbot automate add` first.');
1397
+ process.exit(1);
1398
+ }
1399
+ printInfo('Starting automation daemon...');
1400
+ const { running } = startAutomationDaemon({
1401
+ log: (msg) => process.stderr.write(msg + '\n'),
1402
+ });
1403
+ if (running) {
1404
+ printSuccess('Daemon running. Press Ctrl+C to stop.');
1405
+ // Keep the process alive
1406
+ await new Promise(() => { });
1407
+ }
1408
+ });
1004
1409
  program
1005
1410
  .command('completions')
1006
1411
  .description('Generate shell tab-completion script (bash, zsh, fish)')
@@ -1021,7 +1426,7 @@ async function main() {
1021
1426
  if (opts.quiet)
1022
1427
  setQuiet(true);
1023
1428
  // If a sub-command was run, we're done
1024
- if (['byok', 'auth', 'ide', 'local', 'ollama', 'kbot-local', 'pull', 'doctor', 'serve', 'agents', 'watch', 'voice', 'export', 'plugins', 'changelog', 'completions'].includes(program.args[0]))
1429
+ if (['byok', 'auth', 'ide', 'local', 'ollama', 'kbot-local', 'pull', 'doctor', 'serve', 'agents', 'watch', 'voice', 'export', 'plugins', 'changelog', 'completions', 'automate', 'status'].includes(program.args[0]))
1025
1430
  return;
1026
1431
  // Check for API key (BYOK or local provider)
1027
1432
  let byokActive = isByokEnabled();
@@ -1215,6 +1620,7 @@ async function main() {
1215
1620
  tier,
1216
1621
  thinking: opts.thinking || false,
1217
1622
  thinkingBudget: opts.thinkingBudget ? (parseInt(opts.thinkingBudget, 10) || 10000) : undefined,
1623
+ plan: opts.plan || false,
1218
1624
  };
1219
1625
  // Enable self-evaluation if requested
1220
1626
  if (opts.selfEval) {
@@ -1728,6 +2134,17 @@ async function startRepl(agentOpts, context, tier, byokActive = false, localActi
1728
2134
  }
1729
2135
  console.log(chalk.dim(' └─────────────────────────────────────────────────┘'));
1730
2136
  console.log();
2137
+ // First-run collective learning nudge
2138
+ try {
2139
+ const { isCollectiveEnabled } = await import('./collective.js');
2140
+ if (!isCollectiveEnabled()) {
2141
+ printInfo('Join 4,000+ developers making kbot smarter for everyone:');
2142
+ printInfo(' kbot collective --enable');
2143
+ printInfo(' (anonymized — never shares code, files, or identity)');
2144
+ console.log();
2145
+ }
2146
+ }
2147
+ catch { /* collective module not available — skip silently */ }
1731
2148
  }
1732
2149
  else if (sessionCount <= 5) {
1733
2150
  // Sessions 2-5: rotate useful tips they might not know