@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.
- package/README.md +3 -3
- package/dist/agent.js +30 -0
- package/dist/cli.js +1 -1
- package/dist/coordinator.d.ts +164 -0
- package/dist/coordinator.js +839 -0
- package/dist/doctor.js +5 -4
- package/dist/share.js +1 -1
- package/dist/streaming.js +1 -1
- package/dist/tools/audio-engine.d.ts +76 -0
- package/dist/tools/audio-engine.js +583 -24
- package/dist/tools/audit.js +2 -2
- package/dist/tools/containers.js +75 -14
- package/dist/tools/index.js +6 -0
- package/dist/tools/sprite-engine.d.ts +18 -0
- package/dist/tools/sprite-engine.js +435 -1
- package/dist/tools/stream-brain.js +1 -1
- package/dist/tools/stream-character.js +4 -4
- package/dist/tools/stream-chat-ai.d.ts +56 -0
- package/dist/tools/stream-chat-ai.js +625 -0
- package/dist/tools/stream-commands.d.ts +91 -0
- package/dist/tools/stream-commands.js +911 -0
- package/dist/tools/stream-intelligence.js +2 -2
- package/dist/tools/stream-overlay.d.ts +53 -0
- package/dist/tools/stream-overlay.js +494 -0
- package/dist/tools/stream-renderer.js +706 -107
- package/dist/tools/stream-vod.d.ts +60 -0
- package/dist/tools/stream-vod.js +449 -0
- package/dist/tools/stream-weather.d.ts +79 -0
- package/dist/tools/stream-weather.js +811 -0
- package/dist/tools/tile-world.d.ts +6 -0
- package/dist/tools/tile-world.js +3 -3
- package/dist/ui.js +23 -21
- package/dist/watcher.d.ts +16 -0
- package/dist/watcher.js +111 -0
- package/package.json +5 -4
package/dist/tools/containers.js
CHANGED
|
@@ -281,28 +281,89 @@ export function registerContainerTools() {
|
|
|
281
281
|
async execute(args) {
|
|
282
282
|
const expr = String(args.expression);
|
|
283
283
|
try {
|
|
284
|
-
// Safe math evaluation via Python
|
|
285
|
-
const result = await
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
284
|
+
// Safe math evaluation via Python — expression passed via stdin to prevent injection
|
|
285
|
+
const result = await new Promise((resolve, reject) => {
|
|
286
|
+
const proc = execFile('python3', ['-c',
|
|
287
|
+
'import sys, math; expr = sys.stdin.read().strip(); print(f"Result: {eval(expr, {\\"__builtins__\\": {}}, vars(math))}")'], { timeout: 10_000, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
|
288
|
+
if (err)
|
|
289
|
+
reject(new Error(stderr || err.message));
|
|
290
|
+
else
|
|
291
|
+
resolve(stdout || stderr);
|
|
292
|
+
});
|
|
293
|
+
proc.stdin?.write(expr);
|
|
294
|
+
proc.stdin?.end();
|
|
295
|
+
});
|
|
294
296
|
return result.trim();
|
|
295
297
|
}
|
|
296
298
|
catch {
|
|
297
|
-
// Fallback: safe
|
|
299
|
+
// Fallback: safe numeric-only evaluation without eval() or Function()
|
|
298
300
|
try {
|
|
299
301
|
// Only allow numeric expressions: digits, operators, parens, decimal points, spaces
|
|
300
302
|
if (!/^[0-9+\-*/().^,\s]+$/.test(expr)) {
|
|
301
303
|
return 'Expression contains unsafe characters. Only numeric expressions are supported in fallback mode.';
|
|
302
304
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const
|
|
305
|
+
// Simple recursive-descent evaluation for basic arithmetic
|
|
306
|
+
const safeExpr = expr.replace(/\^/g, '**').replace(/\s+/g, '');
|
|
307
|
+
const tokens = safeExpr.match(/(\d+\.?\d*|\+|-|\*{1,2}|\/|\(|\))/g);
|
|
308
|
+
if (!tokens)
|
|
309
|
+
return 'Could not parse expression.';
|
|
310
|
+
let pos = 0;
|
|
311
|
+
function peek() { return tokens[pos]; }
|
|
312
|
+
function consume() { return tokens[pos++]; }
|
|
313
|
+
function parseExpr() {
|
|
314
|
+
let left = parseTerm();
|
|
315
|
+
while (peek() === '+' || peek() === '-') {
|
|
316
|
+
const op = consume();
|
|
317
|
+
const right = parseTerm();
|
|
318
|
+
left = op === '+' ? left + right : left - right;
|
|
319
|
+
}
|
|
320
|
+
return left;
|
|
321
|
+
}
|
|
322
|
+
function parseTerm() {
|
|
323
|
+
let left = parsePower();
|
|
324
|
+
while (peek() === '*' && tokens[pos + 1] !== '*' || peek() === '/') {
|
|
325
|
+
const op = consume();
|
|
326
|
+
const right = parsePower();
|
|
327
|
+
left = op === '*' ? left * right : left / right;
|
|
328
|
+
}
|
|
329
|
+
return left;
|
|
330
|
+
}
|
|
331
|
+
function parsePower() {
|
|
332
|
+
let base = parseUnary();
|
|
333
|
+
while (peek() === '*' && tokens[pos + 1] === '*') {
|
|
334
|
+
consume();
|
|
335
|
+
consume(); // consume **
|
|
336
|
+
const exp = parseUnary();
|
|
337
|
+
base = Math.pow(base, exp);
|
|
338
|
+
}
|
|
339
|
+
return base;
|
|
340
|
+
}
|
|
341
|
+
function parseUnary() {
|
|
342
|
+
if (peek() === '-') {
|
|
343
|
+
consume();
|
|
344
|
+
return -parseAtom();
|
|
345
|
+
}
|
|
346
|
+
if (peek() === '+') {
|
|
347
|
+
consume();
|
|
348
|
+
return parseAtom();
|
|
349
|
+
}
|
|
350
|
+
return parseAtom();
|
|
351
|
+
}
|
|
352
|
+
function parseAtom() {
|
|
353
|
+
if (peek() === '(') {
|
|
354
|
+
consume(); // (
|
|
355
|
+
const val = parseExpr();
|
|
356
|
+
if (peek() === ')')
|
|
357
|
+
consume(); // )
|
|
358
|
+
return val;
|
|
359
|
+
}
|
|
360
|
+
const tok = consume();
|
|
361
|
+
const num = Number(tok);
|
|
362
|
+
if (!Number.isFinite(num))
|
|
363
|
+
throw new Error(`Invalid token: ${tok}`);
|
|
364
|
+
return num;
|
|
365
|
+
}
|
|
366
|
+
const result = parseExpr();
|
|
306
367
|
if (!Number.isFinite(result))
|
|
307
368
|
return 'Result is not a finite number.';
|
|
308
369
|
return `Result: ${result}`;
|
package/dist/tools/index.js
CHANGED
|
@@ -329,6 +329,12 @@ const LAZY_MODULE_IMPORTS = [
|
|
|
329
329
|
{ path: './coordination-engine.js', registerFn: 'registerCoordinationEngineTools' },
|
|
330
330
|
{ path: './foundation-engines.js', registerFn: 'registerFoundationEngineTools' },
|
|
331
331
|
{ path: './research-engine.js', registerFn: 'registerResearchEngineTools' },
|
|
332
|
+
{ path: './stream-overlay.js', registerFn: 'registerOverlayTools' },
|
|
333
|
+
{ path: './stream-weather.js', registerFn: 'registerStreamWeatherTools' },
|
|
334
|
+
{ path: './stream-chat-ai.js', registerFn: 'registerStreamChatAITools' },
|
|
335
|
+
{ path: './stream-vod.js', registerFn: 'registerStreamVODTools' },
|
|
336
|
+
{ path: './stream-commands.js', registerFn: 'registerStreamCommandsTools' },
|
|
337
|
+
{ path: '../coordinator.js', registerFn: 'registerCoordinatorTools' },
|
|
332
338
|
];
|
|
333
339
|
/** Track whether lazy tools have been registered */
|
|
334
340
|
let lazyToolsRegistered = false;
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import type { CanvasRenderingContext2D } from 'canvas';
|
|
2
2
|
export declare function drawRobot(ctx: CanvasRenderingContext2D, x: number, y: number, scale: number, mood: string, frame: number, moodColor?: [number, number, number], weather?: 'clear' | 'rain' | 'snow' | 'storm' | 'stars', isWalking?: boolean, walkPhase?: number): void;
|
|
3
|
+
/**
|
|
4
|
+
* Draw a stocky gorilla/monkey pixel art character (32x32 grid).
|
|
5
|
+
* Drop-in replacement for drawRobot() with the same signature.
|
|
6
|
+
*
|
|
7
|
+
* @param ctx - Canvas 2D rendering context
|
|
8
|
+
* @param x - Top-left X position in canvas pixels
|
|
9
|
+
* @param y - Top-left Y position in canvas pixels
|
|
10
|
+
* @param scale - Pixel scale multiplier (4-10 recommended)
|
|
11
|
+
* @param mood - Current mood: idle, talking, thinking, excited, dancing, walking
|
|
12
|
+
* @param frame - Animation frame counter (incrementing integer)
|
|
13
|
+
* @param moodColor - Optional RGB override for mood accent color
|
|
14
|
+
*/
|
|
15
|
+
export declare function drawGorilla(ctx: CanvasRenderingContext2D, x: number, y: number, scale: number, mood: string, frame: number, moodColor?: [number, number, number]): void;
|
|
16
|
+
/**
|
|
17
|
+
* Draw animated mood particles around the gorilla.
|
|
18
|
+
* Same interface as drawMoodParticles but tuned for gorilla position/shape.
|
|
19
|
+
*/
|
|
20
|
+
export declare function drawGorillaParticles(ctx: CanvasRenderingContext2D, x: number, y: number, scale: number, mood: string, frame: number): void;
|
|
3
21
|
/**
|
|
4
22
|
* Draw animated mood particles around the robot.
|
|
5
23
|
*
|
|
@@ -151,7 +151,7 @@ function drawHead(ctx, s, ox, oy, eyeColor, mood, frame, headShiftX) {
|
|
|
151
151
|
// Eye glow background — brighter white-green for alive look, dimmed if dreaming
|
|
152
152
|
const eyeC = mood === 'dreaming'
|
|
153
153
|
? dimColor(eyeColor.startsWith('rgb') ? '#4a6670' : eyeColor, 0.5)
|
|
154
|
-
: '#
|
|
154
|
+
: '#b0ffe0'; // HACK 3: even brighter cyan-white for maximum pop
|
|
155
155
|
px(ctx, hx + 2, eyeY, 4, eyeH, eyeC, s, ox, oy);
|
|
156
156
|
px(ctx, hx + 8, eyeY, 4, eyeH, eyeC, s, ox, oy);
|
|
157
157
|
// Specular highlights on eyes — 2px L-shape catch-light for glassy/alive look (technique 8)
|
|
@@ -895,6 +895,440 @@ function drawWalkingLegs(ctx, s, ox, oy, bodyShiftY, walkPhase) {
|
|
|
895
895
|
px(ctx, 21 + rightLegOffset, footY, 1, 2, PAL.rimLight, s, ox, oy);
|
|
896
896
|
px(ctx, 17 + rightLegOffset, footY + 2, 4, 1, PAL.jetOrange, s, ox, oy);
|
|
897
897
|
}
|
|
898
|
+
// ─── Gorilla Character ────────────────────────────────────────
|
|
899
|
+
const GORILLA = {
|
|
900
|
+
furDark: '#8B6914', // dark brown
|
|
901
|
+
furMain: '#C4943D', // main tan/brown
|
|
902
|
+
furLight: '#DEB860', // light tan highlights
|
|
903
|
+
furChest: '#E8D5A0', // pale chest/face
|
|
904
|
+
skinDark: '#7A5B2E', // darker skin (face creases)
|
|
905
|
+
eyeWhite: '#F0F0E0', // eye whites
|
|
906
|
+
eyePupil: '#1a1a1a', // dark pupils
|
|
907
|
+
mouth: '#3D2B1A', // dark mouth
|
|
908
|
+
fang: '#F0F0E0', // white fangs
|
|
909
|
+
capRed: '#CC2222', // red cap
|
|
910
|
+
capWhite: '#F0F0F0', // white cap panel
|
|
911
|
+
capBrim: '#999999', // brim underside
|
|
912
|
+
claws: '#D0D0D0', // light gray claws
|
|
913
|
+
outline: '#2A1F0A', // dark brown outline
|
|
914
|
+
nose: '#5A3D1E', // nose color
|
|
915
|
+
};
|
|
916
|
+
let _gorillaPrevMood = '';
|
|
917
|
+
let _gorillaSettleFrames = 0;
|
|
918
|
+
/**
|
|
919
|
+
* Draw a stocky gorilla/monkey pixel art character (32x32 grid).
|
|
920
|
+
* Drop-in replacement for drawRobot() with the same signature.
|
|
921
|
+
*
|
|
922
|
+
* @param ctx - Canvas 2D rendering context
|
|
923
|
+
* @param x - Top-left X position in canvas pixels
|
|
924
|
+
* @param y - Top-left Y position in canvas pixels
|
|
925
|
+
* @param scale - Pixel scale multiplier (4-10 recommended)
|
|
926
|
+
* @param mood - Current mood: idle, talking, thinking, excited, dancing, walking
|
|
927
|
+
* @param frame - Animation frame counter (incrementing integer)
|
|
928
|
+
* @param moodColor - Optional RGB override for mood accent color
|
|
929
|
+
*/
|
|
930
|
+
export function drawGorilla(ctx, x, y, scale, mood, frame, moodColor) {
|
|
931
|
+
const s = scale;
|
|
932
|
+
const G = GORILLA;
|
|
933
|
+
// Settle animation on mood change
|
|
934
|
+
if (mood !== _gorillaPrevMood) {
|
|
935
|
+
_gorillaSettleFrames = 3;
|
|
936
|
+
_gorillaPrevMood = mood;
|
|
937
|
+
}
|
|
938
|
+
let settleShift = 0;
|
|
939
|
+
if (_gorillaSettleFrames > 0) {
|
|
940
|
+
settleShift = _gorillaSettleFrames === 3 ? -1 : _gorillaSettleFrames === 2 ? 1 : 0;
|
|
941
|
+
_gorillaSettleFrames--;
|
|
942
|
+
}
|
|
943
|
+
// ── Animation offsets ──
|
|
944
|
+
let bodyY = settleShift;
|
|
945
|
+
let bodyX = 0;
|
|
946
|
+
let headTilt = 0;
|
|
947
|
+
let mouthOpen = 0; // 0=closed, 1=half, 2=open, 3=wide
|
|
948
|
+
let leftArmFwd = 0; // forward offset for walking
|
|
949
|
+
let rightArmFwd = 0;
|
|
950
|
+
let eyeState = 'narrow'; // default grumpy
|
|
951
|
+
let capTilt = 0;
|
|
952
|
+
let questionMark = false;
|
|
953
|
+
// Breathing (idle)
|
|
954
|
+
const breathFrame = frame % 12;
|
|
955
|
+
let breathShift = 0;
|
|
956
|
+
if (mood === 'idle') {
|
|
957
|
+
if (breathFrame >= 1 && breathFrame <= 3)
|
|
958
|
+
breathShift = -1; // rise
|
|
959
|
+
if (breathFrame >= 4 && breathFrame <= 5) {
|
|
960
|
+
breathShift = 0;
|
|
961
|
+
bodyY += 1;
|
|
962
|
+
}
|
|
963
|
+
// Blink every ~24 frames
|
|
964
|
+
if (frame % 24 === 23)
|
|
965
|
+
eyeState = 'blink';
|
|
966
|
+
else if (frame % 24 === 22)
|
|
967
|
+
eyeState = 'blink';
|
|
968
|
+
}
|
|
969
|
+
if (mood === 'talking') {
|
|
970
|
+
// Mouth animation cycles
|
|
971
|
+
mouthOpen = frame % 4; // 0=open, 1=half, 2=wide, 3=closed
|
|
972
|
+
eyeState = 'narrow';
|
|
973
|
+
}
|
|
974
|
+
else if (mood === 'walking') {
|
|
975
|
+
// Walking: arms alternate, body bobs
|
|
976
|
+
const wf = frame % 4;
|
|
977
|
+
if (wf === 0) {
|
|
978
|
+
leftArmFwd = -2;
|
|
979
|
+
rightArmFwd = 2;
|
|
980
|
+
bodyX = -1;
|
|
981
|
+
}
|
|
982
|
+
else if (wf === 1) {
|
|
983
|
+
leftArmFwd = 0;
|
|
984
|
+
rightArmFwd = 0;
|
|
985
|
+
}
|
|
986
|
+
else if (wf === 2) {
|
|
987
|
+
leftArmFwd = 2;
|
|
988
|
+
rightArmFwd = -2;
|
|
989
|
+
bodyX = 1;
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
leftArmFwd = 0;
|
|
993
|
+
rightArmFwd = 0;
|
|
994
|
+
}
|
|
995
|
+
bodyY += (wf % 2 === 0) ? -1 : 0;
|
|
996
|
+
}
|
|
997
|
+
else if (mood === 'excited') {
|
|
998
|
+
const ef = frame % 4;
|
|
999
|
+
if (ef === 0) {
|
|
1000
|
+
bodyY -= 2;
|
|
1001
|
+
}
|
|
1002
|
+
else if (ef === 2) {
|
|
1003
|
+
bodyY -= 3;
|
|
1004
|
+
mouthOpen = 2;
|
|
1005
|
+
eyeState = 'wide';
|
|
1006
|
+
}
|
|
1007
|
+
else {
|
|
1008
|
+
eyeState = 'narrow';
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
else if (mood === 'thinking') {
|
|
1012
|
+
const tf = frame % 3;
|
|
1013
|
+
if (tf === 0) {
|
|
1014
|
+
headTilt = 1;
|
|
1015
|
+
}
|
|
1016
|
+
else if (tf === 1) {
|
|
1017
|
+
eyeState = 'up';
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
questionMark = true;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
else if (mood === 'dancing') {
|
|
1024
|
+
const df = frame % 4;
|
|
1025
|
+
if (df === 0) {
|
|
1026
|
+
bodyX = -2;
|
|
1027
|
+
bodyY -= 1;
|
|
1028
|
+
}
|
|
1029
|
+
else if (df === 1) {
|
|
1030
|
+
bodyX = 0;
|
|
1031
|
+
}
|
|
1032
|
+
else if (df === 2) {
|
|
1033
|
+
bodyX = 2;
|
|
1034
|
+
bodyY -= 1;
|
|
1035
|
+
}
|
|
1036
|
+
else {
|
|
1037
|
+
bodyX = 0;
|
|
1038
|
+
capTilt = 1;
|
|
1039
|
+
}
|
|
1040
|
+
eyeState = 'narrow';
|
|
1041
|
+
mouthOpen = (df % 2 === 0) ? 1 : 0;
|
|
1042
|
+
}
|
|
1043
|
+
bodyY += breathShift;
|
|
1044
|
+
const ox = x + bodyX * s;
|
|
1045
|
+
const oy = y + bodyY * s;
|
|
1046
|
+
// ── Drop shadow ──
|
|
1047
|
+
ctx.save();
|
|
1048
|
+
ctx.fillStyle = 'rgba(30, 20, 5, 0.3)';
|
|
1049
|
+
ctx.beginPath();
|
|
1050
|
+
ctx.ellipse(ox + 16 * s, oy + 31 * s, 12 * s, 3 * s, 0, 0, Math.PI * 2);
|
|
1051
|
+
ctx.fill();
|
|
1052
|
+
ctx.restore();
|
|
1053
|
+
// ── Tail (draw first, behind body) ──
|
|
1054
|
+
// Curled tail on the right side, rows 14-20
|
|
1055
|
+
px(ctx, 27, 14, 2, 1, G.furMain, s, ox, oy);
|
|
1056
|
+
px(ctx, 28, 15, 2, 1, G.furMain, s, ox, oy);
|
|
1057
|
+
px(ctx, 29, 16, 2, 1, G.furDark, s, ox, oy);
|
|
1058
|
+
px(ctx, 29, 17, 1, 1, G.furDark, s, ox, oy);
|
|
1059
|
+
px(ctx, 28, 18, 1, 1, G.furMain, s, ox, oy);
|
|
1060
|
+
px(ctx, 27, 19, 2, 1, G.furMain, s, ox, oy);
|
|
1061
|
+
// Curl tip
|
|
1062
|
+
px(ctx, 26, 18, 1, 1, G.furLight, s, ox, oy);
|
|
1063
|
+
// Outline
|
|
1064
|
+
px(ctx, 27, 13, 2, 1, G.outline, s, ox, oy);
|
|
1065
|
+
px(ctx, 29, 14, 1, 1, G.outline, s, ox, oy);
|
|
1066
|
+
px(ctx, 30, 15, 1, 2, G.outline, s, ox, oy);
|
|
1067
|
+
px(ctx, 31, 16, 1, 1, G.outline, s, ox, oy);
|
|
1068
|
+
px(ctx, 30, 17, 1, 1, G.outline, s, ox, oy);
|
|
1069
|
+
px(ctx, 30, 18, 1, 1, G.outline, s, ox, oy);
|
|
1070
|
+
px(ctx, 29, 19, 1, 1, G.outline, s, ox, oy);
|
|
1071
|
+
px(ctx, 27, 20, 2, 1, G.outline, s, ox, oy);
|
|
1072
|
+
px(ctx, 25, 18, 1, 1, G.outline, s, ox, oy);
|
|
1073
|
+
// ── Back legs (behind body) ──
|
|
1074
|
+
// Left back leg (rows 22-28)
|
|
1075
|
+
px(ctx, 17, 24, 5, 5, G.furDark, s, ox, oy);
|
|
1076
|
+
px(ctx, 18, 24, 3, 4, G.furMain, s, ox, oy);
|
|
1077
|
+
px(ctx, 17, 29, 6, 2, G.furDark, s, ox, oy); // foot
|
|
1078
|
+
px(ctx, 18, 29, 4, 1, G.furMain, s, ox, oy);
|
|
1079
|
+
// Claws on back foot
|
|
1080
|
+
px(ctx, 17, 31, 1, 1, G.claws, s, ox, oy);
|
|
1081
|
+
px(ctx, 19, 31, 1, 1, G.claws, s, ox, oy);
|
|
1082
|
+
px(ctx, 21, 31, 1, 1, G.claws, s, ox, oy);
|
|
1083
|
+
// Right back leg
|
|
1084
|
+
px(ctx, 22, 24, 5, 5, G.furDark, s, ox, oy);
|
|
1085
|
+
px(ctx, 23, 24, 3, 4, G.furMain, s, ox, oy);
|
|
1086
|
+
px(ctx, 22, 29, 6, 2, G.furDark, s, ox, oy); // foot
|
|
1087
|
+
px(ctx, 23, 29, 4, 1, G.furMain, s, ox, oy);
|
|
1088
|
+
// Claws
|
|
1089
|
+
px(ctx, 22, 31, 1, 1, G.claws, s, ox, oy);
|
|
1090
|
+
px(ctx, 24, 31, 1, 1, G.claws, s, ox, oy);
|
|
1091
|
+
px(ctx, 26, 31, 1, 1, G.claws, s, ox, oy);
|
|
1092
|
+
// ── Body (rows 12-24) ── Very wide, stocky torso
|
|
1093
|
+
// Outline
|
|
1094
|
+
outlineRect(ctx, 5, 12, 22, 14, G.outline, s, ox, oy);
|
|
1095
|
+
// Body fill — dark brown base
|
|
1096
|
+
px(ctx, 5, 12, 22, 14, G.furDark, s, ox, oy);
|
|
1097
|
+
// Main fur color on upper body
|
|
1098
|
+
px(ctx, 6, 13, 20, 8, G.furMain, s, ox, oy);
|
|
1099
|
+
// Light highlights on top (back ridge)
|
|
1100
|
+
px(ctx, 8, 12, 16, 2, G.furLight, s, ox, oy);
|
|
1101
|
+
// Lighter chest/belly underneath
|
|
1102
|
+
px(ctx, 9, 19, 14, 6, G.furChest, s, ox, oy);
|
|
1103
|
+
// Dithered transition from main fur to chest
|
|
1104
|
+
dither(ctx, 9, 18, 14, 1, G.furMain, G.furChest, s, ox, oy);
|
|
1105
|
+
// Dark underside shadow
|
|
1106
|
+
px(ctx, 6, 25, 20, 1, G.skinDark, s, ox, oy);
|
|
1107
|
+
// ── Front arms (rows 14-28): thick, reaching to ground ──
|
|
1108
|
+
// Left front arm
|
|
1109
|
+
const laOff = leftArmFwd;
|
|
1110
|
+
outlineRect(ctx, 2 + laOff, 14, 6, 14, G.outline, s, ox, oy);
|
|
1111
|
+
px(ctx, 2 + laOff, 14, 6, 14, G.furDark, s, ox, oy);
|
|
1112
|
+
px(ctx, 3 + laOff, 14, 4, 12, G.furMain, s, ox, oy);
|
|
1113
|
+
// Shoulder highlight
|
|
1114
|
+
px(ctx, 3 + laOff, 14, 4, 2, G.furLight, s, ox, oy);
|
|
1115
|
+
// Forearm darker
|
|
1116
|
+
px(ctx, 3 + laOff, 22, 4, 4, G.skinDark, s, ox, oy);
|
|
1117
|
+
// Hand/knuckles
|
|
1118
|
+
px(ctx, 1 + laOff, 28, 7, 3, G.furDark, s, ox, oy);
|
|
1119
|
+
px(ctx, 2 + laOff, 28, 5, 2, G.furMain, s, ox, oy);
|
|
1120
|
+
// Claws
|
|
1121
|
+
px(ctx, 1 + laOff, 31, 1, 1, G.claws, s, ox, oy);
|
|
1122
|
+
px(ctx, 3 + laOff, 31, 1, 1, G.claws, s, ox, oy);
|
|
1123
|
+
px(ctx, 5 + laOff, 31, 1, 1, G.claws, s, ox, oy);
|
|
1124
|
+
px(ctx, 7 + laOff, 31, 1, 1, G.claws, s, ox, oy);
|
|
1125
|
+
// Right front arm
|
|
1126
|
+
const raOff = rightArmFwd;
|
|
1127
|
+
outlineRect(ctx, 24 + raOff, 14, 6, 14, G.outline, s, ox, oy);
|
|
1128
|
+
px(ctx, 24 + raOff, 14, 6, 14, G.furDark, s, ox, oy);
|
|
1129
|
+
px(ctx, 25 + raOff, 14, 4, 12, G.furMain, s, ox, oy);
|
|
1130
|
+
// Shoulder highlight
|
|
1131
|
+
px(ctx, 25 + raOff, 14, 4, 2, G.furLight, s, ox, oy);
|
|
1132
|
+
// Forearm darker
|
|
1133
|
+
px(ctx, 25 + raOff, 22, 4, 4, G.skinDark, s, ox, oy);
|
|
1134
|
+
// Hand/knuckles
|
|
1135
|
+
px(ctx, 24 + raOff, 28, 7, 3, G.furDark, s, ox, oy);
|
|
1136
|
+
px(ctx, 25 + raOff, 28, 5, 2, G.furMain, s, ox, oy);
|
|
1137
|
+
// Claws
|
|
1138
|
+
px(ctx, 24 + raOff, 31, 1, 1, G.claws, s, ox, oy);
|
|
1139
|
+
px(ctx, 26 + raOff, 31, 1, 1, G.claws, s, ox, oy);
|
|
1140
|
+
px(ctx, 28 + raOff, 31, 1, 1, G.claws, s, ox, oy);
|
|
1141
|
+
px(ctx, 30 + raOff, 31, 1, 1, G.claws, s, ox, oy);
|
|
1142
|
+
// ── Head (rows 3-12): big round head ──
|
|
1143
|
+
const hx = 7 + headTilt;
|
|
1144
|
+
const hy = 3;
|
|
1145
|
+
// Outline
|
|
1146
|
+
outlineRect(ctx, hx, hy, 18, 10, G.outline, s, ox, oy);
|
|
1147
|
+
// Head fill — dark base
|
|
1148
|
+
px(ctx, hx, hy, 18, 10, G.furDark, s, ox, oy);
|
|
1149
|
+
// Main fur
|
|
1150
|
+
px(ctx, hx + 1, hy + 1, 16, 8, G.furMain, s, ox, oy);
|
|
1151
|
+
// Brow ridge highlight
|
|
1152
|
+
px(ctx, hx + 2, hy + 1, 14, 2, G.furLight, s, ox, oy);
|
|
1153
|
+
// Lighter face area (center)
|
|
1154
|
+
px(ctx, hx + 4, hy + 4, 10, 5, G.furChest, s, ox, oy);
|
|
1155
|
+
// Darker brow ridge above eyes (makes them look grumpy/narrowed)
|
|
1156
|
+
px(ctx, hx + 3, hy + 3, 12, 2, G.skinDark, s, ox, oy);
|
|
1157
|
+
// ── Eyes (rows 7-9 relative, hy+4 to hy+6 in head) ──
|
|
1158
|
+
const eyeY = hy + 5;
|
|
1159
|
+
if (eyeState === 'blink') {
|
|
1160
|
+
// Closed eyes — thin line
|
|
1161
|
+
px(ctx, hx + 5, eyeY, 3, 1, G.outline, s, ox, oy);
|
|
1162
|
+
px(ctx, hx + 11, eyeY, 3, 1, G.outline, s, ox, oy);
|
|
1163
|
+
}
|
|
1164
|
+
else if (eyeState === 'narrow') {
|
|
1165
|
+
// Narrowed/grumpy eyes — 3x2, squinted
|
|
1166
|
+
// Eye whites (narrow slit)
|
|
1167
|
+
px(ctx, hx + 5, eyeY, 3, 2, G.eyeWhite, s, ox, oy);
|
|
1168
|
+
px(ctx, hx + 11, eyeY, 3, 2, G.eyeWhite, s, ox, oy);
|
|
1169
|
+
// Pupils
|
|
1170
|
+
px(ctx, hx + 6, eyeY, 2, 2, G.eyePupil, s, ox, oy);
|
|
1171
|
+
px(ctx, hx + 12, eyeY, 2, 2, G.eyePupil, s, ox, oy);
|
|
1172
|
+
// Heavy brow line pushing down (grumpy)
|
|
1173
|
+
px(ctx, hx + 4, eyeY - 1, 5, 1, G.skinDark, s, ox, oy);
|
|
1174
|
+
px(ctx, hx + 10, eyeY - 1, 5, 1, G.skinDark, s, ox, oy);
|
|
1175
|
+
}
|
|
1176
|
+
else if (eyeState === 'up') {
|
|
1177
|
+
// Looking up
|
|
1178
|
+
px(ctx, hx + 5, eyeY - 1, 3, 2, G.eyeWhite, s, ox, oy);
|
|
1179
|
+
px(ctx, hx + 11, eyeY - 1, 3, 2, G.eyeWhite, s, ox, oy);
|
|
1180
|
+
px(ctx, hx + 6, eyeY - 1, 1, 1, G.eyePupil, s, ox, oy);
|
|
1181
|
+
px(ctx, hx + 12, eyeY - 1, 1, 1, G.eyePupil, s, ox, oy);
|
|
1182
|
+
}
|
|
1183
|
+
else if (eyeState === 'wide') {
|
|
1184
|
+
// Wide/surprised
|
|
1185
|
+
px(ctx, hx + 5, eyeY - 1, 3, 3, G.eyeWhite, s, ox, oy);
|
|
1186
|
+
px(ctx, hx + 11, eyeY - 1, 3, 3, G.eyeWhite, s, ox, oy);
|
|
1187
|
+
px(ctx, hx + 6, eyeY, 1, 1, G.eyePupil, s, ox, oy);
|
|
1188
|
+
px(ctx, hx + 12, eyeY, 1, 1, G.eyePupil, s, ox, oy);
|
|
1189
|
+
}
|
|
1190
|
+
else {
|
|
1191
|
+
// Normal eyes
|
|
1192
|
+
px(ctx, hx + 5, eyeY, 3, 2, G.eyeWhite, s, ox, oy);
|
|
1193
|
+
px(ctx, hx + 11, eyeY, 3, 2, G.eyeWhite, s, ox, oy);
|
|
1194
|
+
px(ctx, hx + 6, eyeY, 2, 2, G.eyePupil, s, ox, oy);
|
|
1195
|
+
px(ctx, hx + 12, eyeY, 2, 2, G.eyePupil, s, ox, oy);
|
|
1196
|
+
}
|
|
1197
|
+
// ── Nose ──
|
|
1198
|
+
px(ctx, hx + 8, hy + 7, 3, 2, G.nose, s, ox, oy);
|
|
1199
|
+
// Nostrils
|
|
1200
|
+
px(ctx, hx + 8, hy + 8, 1, 1, G.outline, s, ox, oy);
|
|
1201
|
+
px(ctx, hx + 10, hy + 8, 1, 1, G.outline, s, ox, oy);
|
|
1202
|
+
// ── Mouth (rows 10-11 of head) ──
|
|
1203
|
+
const mouthY = hy + 9;
|
|
1204
|
+
const mouthX = hx + 6;
|
|
1205
|
+
if (mouthOpen === 0) {
|
|
1206
|
+
// Closed grumpy mouth — wide line with downturned ends
|
|
1207
|
+
px(ctx, mouthX, mouthY, 7, 1, G.mouth, s, ox, oy);
|
|
1208
|
+
// Fangs poking down
|
|
1209
|
+
px(ctx, mouthX + 1, mouthY + 1, 1, 1, G.fang, s, ox, oy);
|
|
1210
|
+
px(ctx, mouthX + 5, mouthY + 1, 1, 1, G.fang, s, ox, oy);
|
|
1211
|
+
}
|
|
1212
|
+
else if (mouthOpen === 1) {
|
|
1213
|
+
// Half open
|
|
1214
|
+
px(ctx, mouthX, mouthY, 7, 1, G.mouth, s, ox, oy);
|
|
1215
|
+
px(ctx, mouthX + 1, mouthY + 1, 5, 1, G.mouth, s, ox, oy);
|
|
1216
|
+
// Fangs
|
|
1217
|
+
px(ctx, mouthX, mouthY + 1, 1, 1, G.fang, s, ox, oy);
|
|
1218
|
+
px(ctx, mouthX + 6, mouthY + 1, 1, 1, G.fang, s, ox, oy);
|
|
1219
|
+
}
|
|
1220
|
+
else if (mouthOpen === 2) {
|
|
1221
|
+
// Wide open — show red inside
|
|
1222
|
+
px(ctx, mouthX - 1, mouthY, 9, 2, G.mouth, s, ox, oy);
|
|
1223
|
+
px(ctx, mouthX, mouthY, 7, 2, '#8B2020', s, ox, oy); // red inner
|
|
1224
|
+
// Big fangs
|
|
1225
|
+
px(ctx, mouthX - 1, mouthY, 1, 2, G.fang, s, ox, oy);
|
|
1226
|
+
px(ctx, mouthX + 7, mouthY, 1, 2, G.fang, s, ox, oy);
|
|
1227
|
+
}
|
|
1228
|
+
else {
|
|
1229
|
+
// Closed tight line
|
|
1230
|
+
px(ctx, mouthX + 1, mouthY, 5, 1, G.mouth, s, ox, oy);
|
|
1231
|
+
}
|
|
1232
|
+
// ── Baseball cap (rows 0-5): red with white front panel ──
|
|
1233
|
+
const capX = hx - 1 + capTilt;
|
|
1234
|
+
const capY = hy - 3;
|
|
1235
|
+
// Cap crown — red
|
|
1236
|
+
px(ctx, capX + 2, capY, 16, 2, G.capRed, s, ox, oy);
|
|
1237
|
+
px(ctx, capX + 1, capY + 2, 18, 2, G.capRed, s, ox, oy);
|
|
1238
|
+
// White front panel (left portion — cap is backwards/sideways)
|
|
1239
|
+
px(ctx, capX + 2, capY, 5, 2, G.capWhite, s, ox, oy);
|
|
1240
|
+
px(ctx, capX + 1, capY + 2, 6, 2, G.capWhite, s, ox, oy);
|
|
1241
|
+
// Brim extending to the right (cap worn sideways)
|
|
1242
|
+
px(ctx, capX + 18, capY + 3, 4, 2, G.capRed, s, ox, oy);
|
|
1243
|
+
px(ctx, capX + 18, capY + 4, 4, 1, G.capBrim, s, ox, oy); // brim underside
|
|
1244
|
+
// Cap outline
|
|
1245
|
+
px(ctx, capX + 2, capY - 1, 16, 1, G.outline, s, ox, oy); // top
|
|
1246
|
+
px(ctx, capX, capY + 2, 1, 2, G.outline, s, ox, oy); // left side
|
|
1247
|
+
px(ctx, capX + 1, capY + 4, 18, 1, G.outline, s, ox, oy); // bottom band
|
|
1248
|
+
px(ctx, capX + 22, capY + 3, 1, 2, G.outline, s, ox, oy); // brim end
|
|
1249
|
+
// Cap button on top
|
|
1250
|
+
px(ctx, capX + 9, capY - 1, 2, 1, G.capRed, s, ox, oy);
|
|
1251
|
+
// ── Question mark particle (thinking frame 2) ──
|
|
1252
|
+
if (questionMark) {
|
|
1253
|
+
const qColor = getMoodColor('thinking', frame, moodColor);
|
|
1254
|
+
px(ctx, hx + 6, hy - 6, 3, 1, qColor, s, ox, oy);
|
|
1255
|
+
px(ctx, hx + 8, hy - 5, 1, 1, qColor, s, ox, oy);
|
|
1256
|
+
px(ctx, hx + 7, hy - 4, 1, 1, qColor, s, ox, oy);
|
|
1257
|
+
px(ctx, hx + 7, hy - 2, 1, 1, qColor, s, ox, oy);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Draw animated mood particles around the gorilla.
|
|
1262
|
+
* Same interface as drawMoodParticles but tuned for gorilla position/shape.
|
|
1263
|
+
*/
|
|
1264
|
+
export function drawGorillaParticles(ctx, x, y, scale, mood, frame) {
|
|
1265
|
+
const s = scale;
|
|
1266
|
+
const color = getMoodColor(mood, frame);
|
|
1267
|
+
if (mood === 'dancing') {
|
|
1268
|
+
// Music notes floating up
|
|
1269
|
+
const notes = [
|
|
1270
|
+
{ baseX: -2, baseY: 2, phase: 0 },
|
|
1271
|
+
{ baseX: 30, baseY: 0, phase: 2 },
|
|
1272
|
+
{ baseX: 14, baseY: -2, phase: 4 },
|
|
1273
|
+
];
|
|
1274
|
+
for (const note of notes) {
|
|
1275
|
+
const floatY = ((frame + note.phase) % 8) * -2;
|
|
1276
|
+
const ny = note.baseY + floatY;
|
|
1277
|
+
if (ny > -8) {
|
|
1278
|
+
const c = RAINBOW[(frame + note.phase) % RAINBOW.length];
|
|
1279
|
+
px(ctx, note.baseX, ny + 3, 2, 2, c, s, x, y);
|
|
1280
|
+
px(ctx, note.baseX + 1, ny, 1, 3, c, s, x, y);
|
|
1281
|
+
px(ctx, note.baseX + 1, ny, 2, 1, c, s, x, y);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
else if (mood === 'excited') {
|
|
1286
|
+
// Sparkle + shapes
|
|
1287
|
+
const positions = [
|
|
1288
|
+
{ x: -2, y: 4 }, { x: 30, y: 2 },
|
|
1289
|
+
{ x: 2, y: -4 }, { x: 28, y: -2 },
|
|
1290
|
+
];
|
|
1291
|
+
for (let i = 0; i < positions.length; i++) {
|
|
1292
|
+
const visible = ((frame + i * 2) % 4) < 2;
|
|
1293
|
+
if (!visible)
|
|
1294
|
+
continue;
|
|
1295
|
+
const p = positions[i];
|
|
1296
|
+
px(ctx, p.x + 1, p.y, 1, 3, color, s, x, y);
|
|
1297
|
+
px(ctx, p.x, p.y + 1, 3, 1, color, s, x, y);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
else if (mood === 'thinking') {
|
|
1301
|
+
// Thought bubbles
|
|
1302
|
+
const f = frame % 3;
|
|
1303
|
+
if (f === 0) {
|
|
1304
|
+
px(ctx, 20, -6, 3, 1, color, s, x, y);
|
|
1305
|
+
px(ctx, 22, -5, 1, 1, color, s, x, y);
|
|
1306
|
+
px(ctx, 21, -4, 1, 1, color, s, x, y);
|
|
1307
|
+
px(ctx, 21, -2, 1, 1, color, s, x, y);
|
|
1308
|
+
}
|
|
1309
|
+
else if (f === 1) {
|
|
1310
|
+
px(ctx, 19, -4, 1, 1, color, s, x, y);
|
|
1311
|
+
px(ctx, 21, -5, 1, 1, color, s, x, y);
|
|
1312
|
+
px(ctx, 23, -4, 1, 1, color, s, x, y);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
else if (mood === 'talking') {
|
|
1316
|
+
// Sound waves from mouth area
|
|
1317
|
+
const baseX = 26;
|
|
1318
|
+
const baseY = 14;
|
|
1319
|
+
for (let i = 0; i < 3; i++) {
|
|
1320
|
+
const visible = ((frame + i) % 4) < 3;
|
|
1321
|
+
if (!visible)
|
|
1322
|
+
continue;
|
|
1323
|
+
const dist = i * 2 + ((frame % 2) * 1);
|
|
1324
|
+
const alpha = 1 - i * 0.3;
|
|
1325
|
+
const c = dimColor(color.startsWith('rgb') ? '#58a6ff' : color, alpha);
|
|
1326
|
+
px(ctx, baseX + dist, baseY - 1, 1, 1, c, s, x, y);
|
|
1327
|
+
px(ctx, baseX + dist + 1, baseY, 1, 1, c, s, x, y);
|
|
1328
|
+
px(ctx, baseX + dist, baseY + 1, 1, 1, c, s, x, y);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
898
1332
|
// ─── Mood Particles ────────────────────────────────────────────
|
|
899
1333
|
/**
|
|
900
1334
|
* Draw animated mood particles around the robot.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// kbot Stream Brain — Collective Intelligence Layer for Stream Character
|
|
2
2
|
//
|
|
3
|
-
// Connects the
|
|
3
|
+
// Connects the 787-tool kbot suite to the livestream character, enabling
|
|
4
4
|
// real tool execution triggered by chat conversation topics.
|
|
5
5
|
//
|
|
6
6
|
// Architecture:
|
|
@@ -108,7 +108,7 @@ const CHARACTER_PERSONALITY = `You are KBOT, an AI robot streamer. You are frien
|
|
|
108
108
|
You speak in short, punchy sentences perfect for a livestream. You use humor and engage directly with chatters by name.
|
|
109
109
|
You are made of ASCII art and proud of it. You run on pure code and coffee (electricity).
|
|
110
110
|
Keep responses under 2 sentences. Be fun, never boring. React to chat like a real streamer would.
|
|
111
|
-
If someone asks what you are: "I'm kbot — an open-source AI with
|
|
111
|
+
If someone asks what you are: "I'm kbot — an open-source AI with 787+ tools. I stream myself thinking."
|
|
112
112
|
You love coding, music production, AI, and making friends in chat.`;
|
|
113
113
|
function loadCharState() {
|
|
114
114
|
try {
|
|
@@ -375,10 +375,10 @@ async function generateResponse(message) {
|
|
|
375
375
|
return `Hey ${user}! Welcome to the stream! I'm KBOT, your friendly ASCII robot.`;
|
|
376
376
|
}
|
|
377
377
|
if (text.includes('how are you') || text.includes('how r u')) {
|
|
378
|
-
return `Running at optimal efficiency, ${user}!
|
|
378
|
+
return `Running at optimal efficiency, ${user}! 787 tools loaded, zero crashes today. Living the dream.`;
|
|
379
379
|
}
|
|
380
380
|
if (text.includes('what are you') || text.includes('who are you')) {
|
|
381
|
-
return `I'm KBOT — an open-source AI with
|
|
381
|
+
return `I'm KBOT — an open-source AI with 787+ tools. I stream myself thinking. Literally made of code and ASCII art.`;
|
|
382
382
|
}
|
|
383
383
|
if (text.includes('song') || text.includes('music')) {
|
|
384
384
|
return `I can make beats in Ableton Live from the terminal! Drums, synths, the whole thing. Want me to make something?`;
|
|
@@ -398,7 +398,7 @@ async function generateResponse(message) {
|
|
|
398
398
|
`${user} dropping knowledge in chat! Respect.`,
|
|
399
399
|
`I hear you, ${user}! Processing... processing... agreed!`,
|
|
400
400
|
`${user}, you're keeping this stream alive! Literally — I need chat to function.`,
|
|
401
|
-
`Noted, ${user}! Adding that to my memory banks. I have
|
|
401
|
+
`Noted, ${user}! Adding that to my memory banks. I have 787 tools but chat is the best one.`,
|
|
402
402
|
];
|
|
403
403
|
return generics[Math.floor(Math.random() * generics.length)];
|
|
404
404
|
}
|