@kernel.chat/kbot 3.95.0 → 3.97.1

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.
@@ -32,9 +32,15 @@ export declare function updateCamera(world: TileWorld, robotWorldX: number, pane
32
32
  export declare function worldXToTile(worldPixelX: number): number;
33
33
  /** Convert tile X to world pixel X */
34
34
  export declare function tileToWorldX(tileX: number): number;
35
+ /** Get a tile at absolute tile coordinates, generating chunk if needed */
36
+ export declare function getTile(world: TileWorld, tileX: number, tileY: number): BlockType;
37
+ /** Set a tile at absolute tile coordinates */
38
+ export declare function setTile(world: TileWorld, tileX: number, tileY: number, block: BlockType): boolean;
35
39
  /** Render the visible tile world */
36
40
  export declare function renderTileWorld(ctx: CanvasRenderingContext2D, world: TileWorld, panelX: number, panelY: number, panelWidth: number, panelHeight: number, robotWorldX: number, frame: number): void;
37
41
  /** Parse and handle tile world chat commands. Returns response string or null if not a tile command. */
38
42
  export declare function handleTileCommand(text: string, username: string, world: TileWorld, robotWorldPixelX: number): string | null;
43
+ /** Find the surface Y (first non-air tile from top) at a given tile X */
44
+ export declare function findSurfaceY(world: TileWorld, tileX: number): number;
39
45
  export declare function registerTileWorldTools(): void;
40
46
  //# sourceMappingURL=tile-world.d.ts.map
@@ -417,7 +417,7 @@ function tileXToLocalX(tileX) {
417
417
  }
418
418
  // ─── Tile Access Helpers ──────────────────────────────────────
419
419
  /** Get a tile at absolute tile coordinates, generating chunk if needed */
420
- function getTile(world, tileX, tileY) {
420
+ export function getTile(world, tileX, tileY) {
421
421
  if (tileY < 0 || tileY >= WORLD_HEIGHT)
422
422
  return 'air';
423
423
  const chunkIdx = tileXToChunkIndex(tileX);
@@ -430,7 +430,7 @@ function getTile(world, tileX, tileY) {
430
430
  return chunk.tiles[tileY][lx];
431
431
  }
432
432
  /** Set a tile at absolute tile coordinates */
433
- function setTile(world, tileX, tileY, block) {
433
+ export function setTile(world, tileX, tileY, block) {
434
434
  if (tileY < 0 || tileY >= WORLD_HEIGHT)
435
435
  return false;
436
436
  const chunkIdx = tileXToChunkIndex(tileX);
@@ -1009,7 +1009,7 @@ export function handleTileCommand(text, username, world, robotWorldPixelX) {
1009
1009
  return null;
1010
1010
  }
1011
1011
  /** Find the surface Y (first non-air tile from top) at a given tile X */
1012
- function findSurfaceY(world, tileX) {
1012
+ export function findSurfaceY(world, tileX) {
1013
1013
  for (let y = 0; y < WORLD_HEIGHT; y++) {
1014
1014
  const block = getTile(world, tileX, y);
1015
1015
  if (block !== 'air' && block !== 'leaves' && block !== 'water') {
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.95.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.1",
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"