@kernel.chat/kbot 3.97.0 → 3.97.4

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/ui.js CHANGED
@@ -28,31 +28,33 @@ export const status = (...args) => { if (!_quiet)
28
28
  export const content = (...args) => console.log(...args);
29
29
  // ── Color palette ──
30
30
  // Subtle, dark-mode-friendly. One accent color, everything else is gray.
31
- const ACCENT = useColor ? chalk.hex('#A78BFA') : chalk; // soft violet (primary)
32
- const ACCENT_DIM = useColor ? chalk.hex('#7C6CB0') : chalk; // muted violet
33
- const GREEN = useColor ? chalk.hex('#4ADE80') : chalk; // success
34
- const RED = useColor ? chalk.hex('#F87171') : chalk; // error
35
- const YELLOW = useColor ? chalk.hex('#FBBF24') : chalk; // warning
36
- const CYAN = useColor ? chalk.hex('#67E8F9') : chalk; // code/paths
31
+ const noop = (s) => s;
32
+ const hex = typeof chalk.hex === 'function' ? (c) => chalk.hex(c) : () => noop;
33
+ const ACCENT = useColor ? hex('#A78BFA') : noop; // soft violet (primary)
34
+ const ACCENT_DIM = useColor ? hex('#7C6CB0') : noop; // muted violet
35
+ const GREEN = useColor ? hex('#4ADE80') : noop; // success
36
+ const RED = useColor ? hex('#F87171') : noop; // error
37
+ const YELLOW = useColor ? hex('#FBBF24') : noop; // warning
38
+ const CYAN = useColor ? hex('#67E8F9') : noop; // code/paths
37
39
  const DIM = useColor ? chalk.dim : ((s) => s); // secondary text
38
40
  /** Agent color map */
39
41
  const AGENT_COLORS = {
40
42
  kernel: ACCENT,
41
- researcher: chalk.hex('#60A5FA'),
42
- coder: chalk.hex('#4ADE80'),
43
- writer: chalk.hex('#FB923C'),
44
- analyst: chalk.hex('#F472B6'),
45
- aesthete: chalk.hex('#C4956A'),
46
- guardian: chalk.hex('#8B4513'),
47
- curator: chalk.hex('#708090'),
48
- strategist: chalk.hex('#DAA520'),
49
- creative: chalk.hex('#E879F9'),
50
- developer: chalk.hex('#38BDF8'),
43
+ researcher: hex('#60A5FA'),
44
+ coder: hex('#4ADE80'),
45
+ writer: hex('#FB923C'),
46
+ analyst: hex('#F472B6'),
47
+ aesthete: hex('#C4956A'),
48
+ guardian: hex('#8B4513'),
49
+ curator: hex('#708090'),
50
+ strategist: hex('#DAA520'),
51
+ creative: hex('#E879F9'),
52
+ developer: hex('#38BDF8'),
51
53
  local: DIM,
52
54
  };
53
55
  /** Register a custom agent's color (for matrix agents) */
54
56
  export function registerAgentVisuals(id, _icon, color) {
55
- AGENT_COLORS[id] = chalk.hex(color);
57
+ AGENT_COLORS[id] = hex(color);
56
58
  }
57
59
  export function agentColor(agentId) {
58
60
  return AGENT_COLORS[agentId] || ACCENT;
@@ -83,9 +85,9 @@ function particleGridArt() {
83
85
  // Uses the same Rubin palette: amethyst particles, mauve links, warm brown field
84
86
  const P = ACCENT; // particle core (amethyst ●)
85
87
  const L = ACCENT_DIM; // link lines (muted violet)
86
- const F = useColor ? chalk.hex('#B8875C') : chalk; // field haze (warm brown)
88
+ const F = useColor ? hex('#B8875C') : noop; // field haze (warm brown)
87
89
  const G = DIM; // grid lines
88
- const R = useColor ? chalk.hex('#E8E6DC') : chalk; // registration marks
90
+ const R = useColor ? hex('#E8E6DC') : noop; // registration marks
89
91
  const dot = P('●');
90
92
  const sm = F('◦');
91
93
  const ln = L('─');
@@ -121,7 +123,7 @@ export function banner(version) {
121
123
  ? gradient('kbot', [167, 139, 250], [103, 232, 249])
122
124
  : ACCENT('kbot');
123
125
  const title = ` ${bannerText}${v}`;
124
- const features = DIM(' Web search: free • 22 agents • 60+ tools • bring your own key');
126
+ const features = DIM(' Web search: free • 35 agents • 787+ tools • bring your own key');
125
127
  return `\n${grid}\n${title}\n${features}\n`;
126
128
  }
127
129
  export function bannerCompact() {
@@ -281,7 +283,7 @@ export function printHelp() {
281
283
  ` ${CYAN('https://kernel.chat')} ${DIM('Web companion')}`,
282
284
  ` ${CYAN('https://github.com/isaacsight/kernel')} ${DIM('GitHub')}`,
283
285
  '',
284
- ` ${DIM('25 specialist agents. 290+ tools. Type anything to get started.')}`,
286
+ ` ${DIM('35 specialist agents. 787+ tools. Type anything to get started.')}`,
285
287
  '',
286
288
  ];
287
289
  status(lines.join('\n'));
@@ -0,0 +1,16 @@
1
+ export interface WatchEvent {
2
+ type: 'file_changed' | 'build_failed' | 'test_failed' | 'new_commit' | 'new_issue';
3
+ timestamp: string;
4
+ details: Record<string, unknown>;
5
+ }
6
+ type EventCallback = (event: WatchEvent) => void;
7
+ /** Start watching a project directory for file changes and new git commits. */
8
+ export declare function startWatching(projectDir: string, callback: EventCallback): void;
9
+ /** Stop all watchers and clean up. */
10
+ export declare function stopWatching(): void;
11
+ /** Check if the watcher is currently active. */
12
+ export declare function isWatching(): boolean;
13
+ /** Manually emit a build or test failure event. */
14
+ export declare function emitBuildEvent(type: 'build_failed' | 'test_failed', details: Record<string, unknown>, callback: EventCallback): void;
15
+ export {};
16
+ //# sourceMappingURL=watcher.d.ts.map
@@ -0,0 +1,111 @@
1
+ // kbot Watcher — Filesystem and Git Event Stream
2
+ //
3
+ // Watches for changes and emits events the coordinator can act on.
4
+ // fs.watch for file changes, periodic git log for new commits.
5
+ import { watch } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { execSync } from 'node:child_process';
8
+ import chalk from 'chalk';
9
+ let fsWatcher = null;
10
+ let gitPollTimer = null;
11
+ let lastKnownCommit = null;
12
+ let active = false;
13
+ const fileDebounce = new Map();
14
+ const DEBOUNCE_MS = 500;
15
+ const GIT_POLL_MS = 30_000;
16
+ const IGNORE = ['node_modules', '.git', 'dist', 'build', '.next', '.cache', 'coverage'];
17
+ const TAG = chalk.hex('#6B5B95')('◆ watcher');
18
+ function now() { return new Date().toISOString(); }
19
+ function safeEmit(cb, ev) {
20
+ try {
21
+ cb(ev);
22
+ }
23
+ catch { /* callback errors must not crash the watcher */ }
24
+ }
25
+ function getLatestCommit(dir) {
26
+ try {
27
+ return execSync('git log -1 --format=%H', {
28
+ cwd: dir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
29
+ }).trim() || null;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ function getCommitDetails(dir, hash) {
36
+ try {
37
+ const log = execSync(`git log -1 --format="%H|%an|%s" ${hash}`, {
38
+ cwd: dir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
39
+ }).trim();
40
+ const [h, author, message] = log.split('|');
41
+ return { hash: h, author, message };
42
+ }
43
+ catch {
44
+ return { hash };
45
+ }
46
+ }
47
+ /** Start watching a project directory for file changes and new git commits. */
48
+ export function startWatching(projectDir, callback) {
49
+ if (active) {
50
+ process.stderr.write(` ${TAG} ${chalk.yellow('already active')}\n`);
51
+ return;
52
+ }
53
+ active = true;
54
+ process.stderr.write(` ${TAG} ${chalk.dim(projectDir)}\n`);
55
+ // File system watcher (recursive, debounced)
56
+ try {
57
+ fsWatcher = watch(projectDir, { recursive: true }, (_ev, filename) => {
58
+ if (!filename || IGNORE.some(p => filename.includes(p)))
59
+ return;
60
+ const prev = fileDebounce.get(filename);
61
+ if (prev)
62
+ clearTimeout(prev);
63
+ fileDebounce.set(filename, setTimeout(() => {
64
+ fileDebounce.delete(filename);
65
+ safeEmit(callback, {
66
+ type: 'file_changed', timestamp: now(),
67
+ details: { file: filename, path: join(projectDir, filename) },
68
+ });
69
+ }, DEBOUNCE_MS));
70
+ });
71
+ fsWatcher.on('error', () => { });
72
+ }
73
+ catch { /* fs.watch unavailable */ }
74
+ // Git commit polling
75
+ lastKnownCommit = getLatestCommit(projectDir);
76
+ gitPollTimer = setInterval(() => {
77
+ const latest = getLatestCommit(projectDir);
78
+ if (latest && latest !== lastKnownCommit) {
79
+ lastKnownCommit = latest;
80
+ safeEmit(callback, { type: 'new_commit', timestamp: now(), details: getCommitDetails(projectDir, latest) });
81
+ }
82
+ }, GIT_POLL_MS);
83
+ if (gitPollTimer.unref)
84
+ gitPollTimer.unref();
85
+ }
86
+ /** Stop all watchers and clean up. */
87
+ export function stopWatching() {
88
+ if (!active)
89
+ return;
90
+ if (fsWatcher) {
91
+ fsWatcher.close();
92
+ fsWatcher = null;
93
+ }
94
+ if (gitPollTimer) {
95
+ clearInterval(gitPollTimer);
96
+ gitPollTimer = null;
97
+ }
98
+ for (const t of fileDebounce.values())
99
+ clearTimeout(t);
100
+ fileDebounce.clear();
101
+ lastKnownCommit = null;
102
+ active = false;
103
+ process.stderr.write(` ${TAG} ${chalk.dim('stopped')}\n`);
104
+ }
105
+ /** Check if the watcher is currently active. */
106
+ export function isWatching() { return active; }
107
+ /** Manually emit a build or test failure event. */
108
+ export function emitBuildEvent(type, details, callback) {
109
+ safeEmit(callback, { type, timestamp: now(), details });
110
+ }
111
+ //# sourceMappingURL=watcher.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kernel.chat/kbot",
3
- "version": "3.97.0",
4
- "description": "Open-source terminal AI agent. 764+ tools, 35 agents, 20 providers. Dreams, learns, watches your system. Controls your phone. Fully local, fully sovereign. MIT.",
3
+ "version": "3.97.4",
4
+ "description": "Open-source terminal AI agent. 787+ tools, 35 agents, 20 providers. Dreams, learns, watches your system. Controls your phone. Fully local, fully sovereign. MIT.",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -39,7 +39,7 @@
39
39
  "build": "tsc && chmod +x dist/cli.js",
40
40
  "dev": "tsx src/cli.ts",
41
41
  "start": "node dist/cli.js",
42
- "test": "tsx --test src/**/*.test.ts",
42
+ "test": "vitest run",
43
43
  "typecheck": "tsc --noEmit"
44
44
  },
45
45
  "keywords": [
@@ -90,7 +90,8 @@
90
90
  "@types/node": "^22.0.0",
91
91
  "@types/nodemailer": "^7.0.11",
92
92
  "tsx": "^4.19.0",
93
- "typescript": "^5.9.0"
93
+ "typescript": "^5.9.0",
94
+ "vitest": "^4.0.0"
94
95
  },
95
96
  "engines": {
96
97
  "node": ">=20"