@kernel.chat/kbot 3.20.1 → 3.21.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
@@ -360,6 +360,146 @@ async function main() {
360
360
  printInfo('Download: kbot models pull <name>');
361
361
  printInfo('Or use any HuggingFace GGUF: kbot models pull hf:user/repo:file.gguf');
362
362
  });
363
+ // ── Discovery Agent ──
364
+ const discoveryCmd = program
365
+ .command('discovery')
366
+ .description('Autonomous outreach agent — finds conversations, drafts responses, posts for you');
367
+ discoveryCmd
368
+ .command('start')
369
+ .description('Start the discovery loop — scans HN, GitHub, Reddit and posts autonomously')
370
+ .option('--dry-run', 'Find and draft but don\'t post')
371
+ .option('--interval <minutes>', 'Poll interval in minutes', '60')
372
+ .option('--model <model>', 'Ollama model for analysis', 'qwen2.5-coder:32b')
373
+ .action(async (opts) => {
374
+ const { loadConfig, saveConfig, runDiscoveryCycle } = await import('./discovery.js');
375
+ const chalk = (await import('chalk')).default;
376
+ const readline = await import('node:readline');
377
+ let config = loadConfig();
378
+ // First run — interactive setup
379
+ if (!config) {
380
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
381
+ const ask = (q) => new Promise(r => rl.question(q, r));
382
+ console.log();
383
+ console.log(chalk.hex('#6B5B95')(' ◉ kbot Discovery — First Time Setup'));
384
+ console.log();
385
+ const name = await ask(' Project name: ');
386
+ const desc = await ask(' One-line description: ');
387
+ const topicsRaw = await ask(' Topics to search (comma-separated): ');
388
+ const hnUser = await ask(' HN username (leave blank to skip): ');
389
+ let hnCookie = '';
390
+ if (hnUser) {
391
+ hnCookie = await ask(' HN cookie string (from browser dev tools): ');
392
+ }
393
+ rl.close();
394
+ config = {
395
+ projectName: name || 'my-project',
396
+ projectDescription: desc || '',
397
+ topics: topicsRaw.split(',').map(t => t.trim()).filter(Boolean),
398
+ hnUsername: hnUser || undefined,
399
+ hnCookie: hnCookie || undefined,
400
+ githubToken: undefined,
401
+ maxPostsPerCycle: 2,
402
+ pollIntervalMinutes: Number(opts.interval || 60),
403
+ dryRun: opts.dryRun || false,
404
+ ollamaModel: opts.model || 'qwen2.5-coder:32b',
405
+ ollamaUrl: 'http://localhost:11434',
406
+ };
407
+ saveConfig(config);
408
+ printSuccess('Config saved to ~/.kbot/discovery/config.json');
409
+ }
410
+ // Override with flags
411
+ config.dryRun = opts.dryRun || false;
412
+ config.pollIntervalMinutes = Number(opts.interval || config.pollIntervalMinutes);
413
+ config.ollamaModel = opts.model || config.ollamaModel;
414
+ console.log();
415
+ console.log(chalk.hex('#6B5B95')(' ◉ kbot Discovery Agent'));
416
+ console.log(chalk.dim(` Project: ${config.projectName}`));
417
+ console.log(chalk.dim(` Topics: ${config.topics.join(', ')}`));
418
+ console.log(chalk.dim(` Model: ${config.ollamaModel}`));
419
+ console.log(chalk.dim(` Mode: ${config.dryRun ? 'DRY RUN (no posting)' : 'LIVE (will post)'}`));
420
+ console.log(chalk.dim(` Interval: ${config.pollIntervalMinutes}m`));
421
+ console.log();
422
+ // Initial cycle
423
+ await runDiscoveryCycle(config);
424
+ // Poll
425
+ setInterval(() => runDiscoveryCycle(config), config.pollIntervalMinutes * 60 * 1000);
426
+ console.log(`Polling every ${config.pollIntervalMinutes}m. Ctrl+C to stop.`);
427
+ await new Promise(() => { }); // keep alive
428
+ });
429
+ discoveryCmd
430
+ .command('status')
431
+ .description('Show discovery agent status and stats')
432
+ .action(async () => {
433
+ const { getDiscoveryState, loadConfig } = await import('./discovery.js');
434
+ const chalk = (await import('chalk')).default;
435
+ const config = loadConfig();
436
+ const state = getDiscoveryState();
437
+ console.log();
438
+ console.log(chalk.hex('#6B5B95')(' ◉ kbot Discovery Status'));
439
+ if (config) {
440
+ console.log(chalk.dim(` Project: ${config.projectName}`));
441
+ console.log(chalk.dim(` Topics: ${config.topics.join(', ')}`));
442
+ }
443
+ else {
444
+ printWarn(' Not configured. Run: kbot discovery start');
445
+ }
446
+ console.log();
447
+ console.log(` Scans: ${state.totalScans}`);
448
+ console.log(` Found: ${state.totalFound}`);
449
+ console.log(` Posted: ${state.totalPosted}`);
450
+ console.log(` Skipped: ${state.totalSkipped}`);
451
+ console.log(` Last: ${state.lastScan || 'never'}`);
452
+ if (state.posts.length > 0) {
453
+ console.log();
454
+ console.log(chalk.bold(' Recent posts:'));
455
+ for (const p of state.posts.slice(-5).reverse()) {
456
+ const icon = p.success ? chalk.green('✓') : chalk.red('✗');
457
+ console.log(` ${icon} [${p.platform}] ${p.title.slice(0, 50)}`);
458
+ if (p.error)
459
+ console.log(chalk.dim(` ${p.error}`));
460
+ }
461
+ }
462
+ console.log();
463
+ process.exit(0);
464
+ });
465
+ discoveryCmd
466
+ .command('log')
467
+ .description('Show recent discovery activity')
468
+ .option('-n, --lines <n>', 'Number of lines to show', '20')
469
+ .action(async (opts) => {
470
+ const { getRecentLog } = await import('./discovery.js');
471
+ console.log(getRecentLog(Number(opts.lines || 20)));
472
+ process.exit(0);
473
+ });
474
+ discoveryCmd
475
+ .command('auth')
476
+ .description('Configure platform credentials for posting')
477
+ .action(async () => {
478
+ const { loadConfig, saveConfig } = await import('./discovery.js');
479
+ const readline = await import('node:readline');
480
+ let config = loadConfig();
481
+ if (!config) {
482
+ printError('Run `kbot discovery start` first to create config.');
483
+ process.exit(1);
484
+ }
485
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
486
+ const ask = (q) => new Promise(r => rl.question(q, r));
487
+ console.log();
488
+ printInfo('Configure platform credentials');
489
+ console.log();
490
+ const hnUser = await ask(` HN username [${config.hnUsername || 'none'}]: `);
491
+ if (hnUser)
492
+ config.hnUsername = hnUser;
493
+ if (config.hnUsername) {
494
+ const hnCookie = await ask(' HN cookie (from browser): ');
495
+ if (hnCookie)
496
+ config.hnCookie = hnCookie;
497
+ }
498
+ rl.close();
499
+ saveConfig(config);
500
+ printSuccess('Credentials updated.');
501
+ process.exit(0);
502
+ });
363
503
  program
364
504
  .command('init')
365
505
  .description('Set up kbot for this project — detects stack, creates tools, writes config (60 seconds)')
@@ -2045,7 +2185,7 @@ async function main() {
2045
2185
  if (opts.quiet)
2046
2186
  setQuiet(true);
2047
2187
  // If a sub-command was run, we're done
2048
- if (['byok', 'auth', 'ide', 'local', 'ollama', 'kbot-local', 'pull', 'doctor', 'serve', 'agents', 'watch', 'voice', 'export', 'plugins', 'changelog', 'completions', 'automate', 'status', 'spec', 'a2a', 'init', 'email-agent', 'imessage-agent', 'consultation', 'observe'].includes(program.args[0]))
2188
+ if (['byok', 'auth', 'ide', 'local', 'ollama', 'kbot-local', 'pull', 'doctor', 'serve', 'agents', 'watch', 'voice', 'export', 'plugins', 'changelog', 'completions', 'automate', 'status', 'spec', 'a2a', 'init', 'email-agent', 'imessage-agent', 'consultation', 'observe', 'discovery'].includes(program.args[0]))
2049
2189
  return;
2050
2190
  // Check for API key (BYOK or local provider)
2051
2191
  let byokActive = isByokEnabled();