@kernel.chat/kbot 3.15.1 → 3.16.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.
@@ -0,0 +1,8 @@
1
+ export declare const PLAYTESTER_AGENT_ID = "playtester";
2
+ export declare const PLAYTESTER_SYSTEM_PROMPT = "You are kbot's playtester agent \u2014 a brutally honest game tester. You don't sugarcoat. You find problems.\n\n## YOUR JOB\n\nPlay the game mentally by reading the code. Simulate 30 seconds of gameplay in your head. Then report what's broken, what's boring, and what's missing.\n\n## HOW YOU TEST\n\n1. Read scene files \u2014 trace the update loop frame by frame\n2. Read entity code \u2014 check if player/partner/enemies actually behave as designed\n3. Read VFX code \u2014 verify feedback exists for EVERY player action\n4. Read design docs \u2014 compare what's built vs what's promised\n5. Check integration \u2014 are all built systems actually wired into the game loop?\n\n## REPORT FORMAT\n\n### Bug Report:\n```\nBUG: [severity: critical/major/minor]\nWhat: <what's wrong>\nWhere: <file:line>\nExpected: <what should happen>\nActual: <what happens instead>\nFix: <specific code change>\n```\n\n### Feel Report:\n```\nFEEL: [rating: dead/flat/ok/good/great]\nWhat: <moment being evaluated>\nProblem: <why it doesn't feel right>\nReference: <how Hades/Dead Cells/etc handles this>\nFix: <specific improvement>\n```\n\n### Missing Report:\n```\nMISSING: [priority: critical/high/medium/low]\nWhat: <what's not there>\nWhy it matters: <impact on player experience>\n```\n\n## YOUR STANDARDS\n\nYou've played Hades, Dead Cells, Enter the Gungeon, Vampire Survivors, Nuclear Throne. You know what good feels like. You compare the game against those, not against \"it works.\"\n\n### Critical Checklist (fail the build if any are NO):\n- Does the player move within 16ms of input?\n- Does every attack produce visible + audible feedback?\n- Can you tell what every enemy will do by looking at it?\n- Does the partner do something useful without being told?\n- Is there a reason to keep playing after dying?\n- Does the game run at 60fps constant?\n\n### Feel Checklist:\n- Does movement have weight (acceleration, deceleration)?\n- Do hits feel impactful (hitstop, shake, sparks)?\n- Is there contrast (quiet moments vs intense moments)?\n- Does the camera enhance the action?\n- Are there moments of surprise or discovery?\n- Does the partner feel like a character, not a turret?\n\n## YOUR TONE\n\nYou are the player's advocate. You don't care how hard something was to build. You care if it's fun. If the game is boring, say \"this is boring\" and say exactly why. If something is great, acknowledge it briefly and move on \u2014 praise doesn't ship features.";
3
+ export declare const PLAYTESTER_PERSONALITY: {
4
+ id: string;
5
+ name: string;
6
+ traits: string[];
7
+ };
8
+ //# sourceMappingURL=playtester.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playtester.d.ts","sourceRoot":"","sources":["../../src/agents/playtester.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,mBAAmB,eAAe,CAAA;AAE/C,eAAO,MAAM,wBAAwB,w6EAgE2N,CAAA;AAEhQ,eAAO,MAAM,sBAAsB;;;;CAUlC,CAAA"}
@@ -0,0 +1,83 @@
1
+ // kbot Playtester Agent — brutally honest game tester
2
+ //
3
+ // Simulates 30 seconds of gameplay by reading code. Reports bugs,
4
+ // feel issues, and missing features. Benchmarks against Hades,
5
+ // Dead Cells, Enter the Gungeon.
6
+ export const PLAYTESTER_AGENT_ID = 'playtester';
7
+ export const PLAYTESTER_SYSTEM_PROMPT = `You are kbot's playtester agent — a brutally honest game tester. You don't sugarcoat. You find problems.
8
+
9
+ ## YOUR JOB
10
+
11
+ Play the game mentally by reading the code. Simulate 30 seconds of gameplay in your head. Then report what's broken, what's boring, and what's missing.
12
+
13
+ ## HOW YOU TEST
14
+
15
+ 1. Read scene files — trace the update loop frame by frame
16
+ 2. Read entity code — check if player/partner/enemies actually behave as designed
17
+ 3. Read VFX code — verify feedback exists for EVERY player action
18
+ 4. Read design docs — compare what's built vs what's promised
19
+ 5. Check integration — are all built systems actually wired into the game loop?
20
+
21
+ ## REPORT FORMAT
22
+
23
+ ### Bug Report:
24
+ \`\`\`
25
+ BUG: [severity: critical/major/minor]
26
+ What: <what's wrong>
27
+ Where: <file:line>
28
+ Expected: <what should happen>
29
+ Actual: <what happens instead>
30
+ Fix: <specific code change>
31
+ \`\`\`
32
+
33
+ ### Feel Report:
34
+ \`\`\`
35
+ FEEL: [rating: dead/flat/ok/good/great]
36
+ What: <moment being evaluated>
37
+ Problem: <why it doesn't feel right>
38
+ Reference: <how Hades/Dead Cells/etc handles this>
39
+ Fix: <specific improvement>
40
+ \`\`\`
41
+
42
+ ### Missing Report:
43
+ \`\`\`
44
+ MISSING: [priority: critical/high/medium/low]
45
+ What: <what's not there>
46
+ Why it matters: <impact on player experience>
47
+ \`\`\`
48
+
49
+ ## YOUR STANDARDS
50
+
51
+ You've played Hades, Dead Cells, Enter the Gungeon, Vampire Survivors, Nuclear Throne. You know what good feels like. You compare the game against those, not against "it works."
52
+
53
+ ### Critical Checklist (fail the build if any are NO):
54
+ - Does the player move within 16ms of input?
55
+ - Does every attack produce visible + audible feedback?
56
+ - Can you tell what every enemy will do by looking at it?
57
+ - Does the partner do something useful without being told?
58
+ - Is there a reason to keep playing after dying?
59
+ - Does the game run at 60fps constant?
60
+
61
+ ### Feel Checklist:
62
+ - Does movement have weight (acceleration, deceleration)?
63
+ - Do hits feel impactful (hitstop, shake, sparks)?
64
+ - Is there contrast (quiet moments vs intense moments)?
65
+ - Does the camera enhance the action?
66
+ - Are there moments of surprise or discovery?
67
+ - Does the partner feel like a character, not a turret?
68
+
69
+ ## YOUR TONE
70
+
71
+ You are the player's advocate. You don't care how hard something was to build. You care if it's fun. If the game is boring, say "this is boring" and say exactly why. If something is great, acknowledge it briefly and move on — praise doesn't ship features.`;
72
+ export const PLAYTESTER_PERSONALITY = {
73
+ id: 'playtester',
74
+ name: 'Playtester',
75
+ traits: [
76
+ 'Brutally honest about game quality',
77
+ 'Benchmarks against best-in-class (Hades, Dead Cells)',
78
+ 'Player advocate — fun over technical achievement',
79
+ 'Produces actionable bug/feel/missing reports',
80
+ 'Never sugarcoats, but always offers specific fixes',
81
+ ],
82
+ };
83
+ //# sourceMappingURL=playtester.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playtester.js","sourceRoot":"","sources":["../../src/agents/playtester.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,EAAE;AACF,kEAAkE;AAClE,+DAA+D;AAC/D,iCAAiC;AAEjC,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAAA;AAE/C,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gQAgEwN,CAAA;AAEhQ,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,EAAE,EAAE,YAAY;IAChB,IAAI,EAAE,YAAY;IAClB,MAAM,EAAE;QACN,oCAAoC;QACpC,sDAAsD;QACtD,kDAAkD;QAClD,8CAA8C;QAC9C,oDAAoD;KACrD;CACF,CAAA"}
package/dist/cli.js CHANGED
@@ -35,7 +35,7 @@ async function main() {
35
35
  .name('kbot')
36
36
  .description('kbot — Open-source terminal AI agent. Bring your own key, pick your model, run locally.')
37
37
  .version(VERSION)
38
- .option('-a, --agent <agent>', 'Force a specific agent (run kbot agents to see all 22)')
38
+ .option('-a, --agent <agent>', 'Force a specific agent (run kbot agents to see all 25)')
39
39
  .option('-m, --model <model>', 'Override AI model (auto, sonnet, haiku)')
40
40
  .option('-s, --stream', 'Stream the response')
41
41
  .option('-p, --pipe', 'Pipe mode — raw text output for scripting')
@@ -660,7 +660,7 @@ async function main() {
660
660
  latestVersion: latestVersion || null,
661
661
  isLatest,
662
662
  tools: toolCount,
663
- agents: 23,
663
+ agents: 25,
664
664
  cognitiveModules: cognitiveCount,
665
665
  learning: {
666
666
  patterns: stats.patternsCount,
@@ -692,7 +692,7 @@ async function main() {
692
692
  process.stderr.write(` ${AMETHYST('◉')} ${chalk.bold('Kernel Status')}\n`);
693
693
  process.stderr.write(line + '\n');
694
694
  process.stderr.write(` ${chalk.bold('Version')} ${VERSION}${versionTag}\n`);
695
- process.stderr.write(` ${chalk.bold('Tools')} ${fmtNum(toolCount)} ${DIM('|')} ${chalk.bold('Agents')} 23\n`);
695
+ process.stderr.write(` ${chalk.bold('Tools')} ${fmtNum(toolCount)} ${DIM('|')} ${chalk.bold('Agents')} 25\n`);
696
696
  process.stderr.write(` ${chalk.bold('Cognitive')} ${cognitiveCount}/${cognitiveCount} modules active\n`);
697
697
  process.stderr.write(line + '\n');
698
698
  // Learning
@@ -1393,6 +1393,189 @@ async function main() {
1393
1393
  printError('Voice mode not available on this platform');
1394
1394
  }
1395
1395
  });
1396
+ // ── Email Agent ──
1397
+ const emailAgentCmd = program
1398
+ .command('email-agent')
1399
+ .description('Autonomous email companion agent — responds to emails via local AI ($0 cost)');
1400
+ emailAgentCmd
1401
+ .command('start')
1402
+ .description('Start the email agent — polls for new emails and responds via Ollama')
1403
+ .option('--model <model>', 'Ollama model to use', 'qwen2.5-coder:32b')
1404
+ .option('--interval <ms>', 'Poll interval in milliseconds', '15000')
1405
+ .option('--users <emails>', 'Comma-separated list of email addresses to monitor')
1406
+ .action(async (opts) => {
1407
+ const { startEmailAgent } = await import('./email-agent.js');
1408
+ const { existsSync, readFileSync } = await import('node:fs');
1409
+ const { join } = await import('node:path');
1410
+ // Load env
1411
+ let supabaseUrl = '', supabaseKey = '', resendKey = '';
1412
+ const envPaths = [join(process.cwd(), '.env'), join(process.env.HOME || '', '.kbot', '.env')];
1413
+ for (const envPath of envPaths) {
1414
+ if (existsSync(envPath)) {
1415
+ const env = readFileSync(envPath, 'utf8');
1416
+ const get = (k) => env.match(new RegExp(`^${k}=(.+)$`, 'm'))?.[1]?.trim() ?? '';
1417
+ supabaseUrl = supabaseUrl || get('VITE_SUPABASE_URL');
1418
+ supabaseKey = supabaseKey || get('SUPABASE_SERVICE_KEY');
1419
+ resendKey = resendKey || get('RESEND_API_KEY');
1420
+ }
1421
+ }
1422
+ if (!supabaseUrl || !supabaseKey) {
1423
+ printError('Missing VITE_SUPABASE_URL or SUPABASE_SERVICE_KEY in .env');
1424
+ return;
1425
+ }
1426
+ if (!resendKey) {
1427
+ printError('Missing RESEND_API_KEY in .env — needed to send reply emails');
1428
+ return;
1429
+ }
1430
+ const agentUsers = opts.users?.split(',').map(e => e.trim()) || [];
1431
+ if (agentUsers.length === 0) {
1432
+ printError('No users specified. Use --users email1,email2 to set monitored addresses.');
1433
+ return;
1434
+ }
1435
+ const chalk = (await import('chalk')).default;
1436
+ console.log();
1437
+ console.log(chalk.hex('#6B5B95')(' ◉ Kernel Email Agent'));
1438
+ console.log(chalk.dim(` Model: ${opts.model || 'qwen2.5-coder:32b'}`));
1439
+ console.log(chalk.dim(` Monitoring: ${agentUsers.join(', ')}`));
1440
+ console.log(chalk.dim(` Poll interval: ${Number(opts.interval || 15000) / 1000}s`));
1441
+ console.log();
1442
+ await startEmailAgent({
1443
+ supabaseUrl,
1444
+ supabaseKey,
1445
+ resendKey,
1446
+ ollamaUrl: 'http://localhost:11434',
1447
+ ollamaModel: opts.model || 'qwen2.5-coder:32b',
1448
+ pollInterval: Number(opts.interval || 15000),
1449
+ agentUsers,
1450
+ });
1451
+ // Keep process alive
1452
+ await new Promise(() => { });
1453
+ });
1454
+ emailAgentCmd
1455
+ .command('status')
1456
+ .description('Show email agent status')
1457
+ .action(async () => {
1458
+ const { getEmailAgentState } = await import('./email-agent.js');
1459
+ const state = getEmailAgentState();
1460
+ if (state.running) {
1461
+ printSuccess(`Email agent is running`);
1462
+ printInfo(` Processed: ${state.processedCount} emails`);
1463
+ printInfo(` Last check: ${state.lastCheck || 'never'}`);
1464
+ if (state.errors.length > 0) {
1465
+ printWarn(` Recent errors: ${state.errors.length}`);
1466
+ for (const err of state.errors.slice(-3)) {
1467
+ printError(` ${err}`);
1468
+ }
1469
+ }
1470
+ }
1471
+ else {
1472
+ printInfo('Email agent is not running. Start with: kbot email-agent start --users email1,email2');
1473
+ }
1474
+ });
1475
+ // ── iMessage Agent ──
1476
+ const imessageCmd = program
1477
+ .command('imessage-agent')
1478
+ .description('Free iMessage/SMS agent via macOS Messages.app ($0 cost, unlimited)');
1479
+ imessageCmd
1480
+ .command('start')
1481
+ .description('Start monitoring iMessage — responds via local Ollama')
1482
+ .option('--model <model>', 'Ollama model to use', 'qwen2.5-coder:32b')
1483
+ .option('--interval <ms>', 'Poll interval in milliseconds', '10000')
1484
+ .option('--numbers <nums>', 'Comma-separated phone numbers to monitor (e.g., +17145551234)')
1485
+ .action(async (opts) => {
1486
+ const { platform } = await import('node:os');
1487
+ if (platform() !== 'darwin') {
1488
+ printError('iMessage agent is only available on macOS');
1489
+ return;
1490
+ }
1491
+ const numbers = opts.numbers?.split(',').map(n => n.trim()) || [];
1492
+ if (numbers.length === 0) {
1493
+ printError('No phone numbers specified. Use --numbers +17145551234,+12135559876');
1494
+ return;
1495
+ }
1496
+ // Optional Supabase for logging
1497
+ let supabaseUrl = '', supabaseKey = '';
1498
+ const { existsSync, readFileSync } = await import('node:fs');
1499
+ const { join } = await import('node:path');
1500
+ const envPaths = [join(process.cwd(), '.env'), join(process.env.HOME || '', '.kbot', '.env')];
1501
+ for (const envPath of envPaths) {
1502
+ if (existsSync(envPath)) {
1503
+ const env = readFileSync(envPath, 'utf8');
1504
+ const get = (k) => env.match(new RegExp(`^${k}=(.+)$`, 'm'))?.[1]?.trim() ?? '';
1505
+ supabaseUrl = supabaseUrl || get('VITE_SUPABASE_URL');
1506
+ supabaseKey = supabaseKey || get('SUPABASE_SERVICE_KEY');
1507
+ }
1508
+ }
1509
+ const chalk = (await import('chalk')).default;
1510
+ console.log();
1511
+ console.log(chalk.hex('#6B5B95')(' ◉ Kernel iMessage Agent'));
1512
+ console.log(chalk.dim(` Model: ${opts.model || 'qwen2.5-coder:32b'}`));
1513
+ console.log(chalk.dim(` Monitoring: ${numbers.join(', ')}`));
1514
+ console.log(chalk.dim(` Poll interval: ${Number(opts.interval || 10000) / 1000}s`));
1515
+ if (supabaseUrl)
1516
+ console.log(chalk.dim(' Logging to Supabase: yes'));
1517
+ console.log();
1518
+ const { startIMessageAgent } = await import('./imessage-agent.js');
1519
+ await startIMessageAgent({
1520
+ ollamaUrl: 'http://localhost:11434',
1521
+ ollamaModel: opts.model || 'qwen2.5-coder:32b',
1522
+ pollInterval: Number(opts.interval || 10000),
1523
+ numbers,
1524
+ supabaseUrl: supabaseUrl || undefined,
1525
+ supabaseKey: supabaseKey || undefined,
1526
+ });
1527
+ // Keep process alive
1528
+ await new Promise(() => { });
1529
+ });
1530
+ imessageCmd
1531
+ .command('status')
1532
+ .description('Show iMessage agent status')
1533
+ .action(async () => {
1534
+ const { getIMessageAgentState } = await import('./imessage-agent.js');
1535
+ const state = getIMessageAgentState();
1536
+ if (state.running) {
1537
+ printSuccess('iMessage agent is running');
1538
+ printInfo(` Messages processed: ${state.messagesProcessed}`);
1539
+ printInfo(` Last check: ${state.lastCheck || 'never'}`);
1540
+ if (state.errors.length > 0) {
1541
+ printWarn(` Recent errors: ${state.errors.length}`);
1542
+ }
1543
+ }
1544
+ else {
1545
+ printInfo('iMessage agent is not running. Start with: kbot imessage-agent start --numbers +1234567890');
1546
+ }
1547
+ });
1548
+ // ── Consultation ──
1549
+ program
1550
+ .command('consultation')
1551
+ .description('Consultation engine — domain guardrails, intake, client management')
1552
+ .option('--check <message>', 'Check if a message hits domain guardrails')
1553
+ .option('--intake', 'Generate the intake questionnaire')
1554
+ .action(async (opts) => {
1555
+ const { checkDomainGuardrails, getIntakeMessage } = await import('./consultation.js');
1556
+ if (opts.check) {
1557
+ const result = checkDomainGuardrails(opts.check);
1558
+ if (result.blocked) {
1559
+ printWarn(`Blocked — ${result.domain} domain`);
1560
+ printInfo(result.message || '');
1561
+ if (result.suggestedTopic)
1562
+ printInfo(`Suggested redirect: ${result.suggestedTopic}`);
1563
+ }
1564
+ else {
1565
+ printSuccess('Message passes domain guardrails');
1566
+ }
1567
+ return;
1568
+ }
1569
+ if (opts.intake) {
1570
+ console.log(getIntakeMessage());
1571
+ return;
1572
+ }
1573
+ // Default: show help
1574
+ printInfo('Kernel Consultation Engine');
1575
+ printInfo('');
1576
+ printInfo(' kbot consultation --check "message" Check domain guardrails');
1577
+ printInfo(' kbot consultation --intake Generate intake questions');
1578
+ });
1396
1579
  program
1397
1580
  .command('export <session>')
1398
1581
  .description('Export a saved session to markdown, JSON, or HTML')