kernelbot 1.0.37 → 1.0.39

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.
Files changed (41) hide show
  1. package/bin/kernel.js +499 -249
  2. package/config.example.yaml +17 -0
  3. package/knowledge_base/active_inference_foraging.md +126 -0
  4. package/knowledge_base/index.md +1 -1
  5. package/package.json +3 -1
  6. package/src/agent.js +355 -82
  7. package/src/bot.js +724 -12
  8. package/src/character.js +406 -0
  9. package/src/characters/builder.js +174 -0
  10. package/src/characters/builtins.js +421 -0
  11. package/src/conversation.js +17 -2
  12. package/src/dashboard/agents.css +469 -0
  13. package/src/dashboard/agents.html +184 -0
  14. package/src/dashboard/agents.js +873 -0
  15. package/src/dashboard/dashboard.css +281 -0
  16. package/src/dashboard/dashboard.js +579 -0
  17. package/src/dashboard/index.html +366 -0
  18. package/src/dashboard/server.js +521 -0
  19. package/src/dashboard/shared.css +700 -0
  20. package/src/dashboard/shared.js +218 -0
  21. package/src/life/engine.js +115 -26
  22. package/src/life/evolution.js +7 -5
  23. package/src/life/journal.js +5 -4
  24. package/src/life/memory.js +12 -9
  25. package/src/life/share-queue.js +7 -5
  26. package/src/prompts/orchestrator.js +76 -14
  27. package/src/prompts/workers.js +22 -0
  28. package/src/self.js +17 -5
  29. package/src/services/linkedin-api.js +190 -0
  30. package/src/services/stt.js +8 -2
  31. package/src/services/tts.js +32 -2
  32. package/src/services/x-api.js +141 -0
  33. package/src/swarm/worker-registry.js +7 -0
  34. package/src/tools/categories.js +4 -0
  35. package/src/tools/index.js +6 -0
  36. package/src/tools/linkedin.js +264 -0
  37. package/src/tools/orchestrator-tools.js +337 -2
  38. package/src/tools/x.js +256 -0
  39. package/src/utils/config.js +190 -139
  40. package/src/utils/display.js +165 -52
  41. package/src/utils/temporal-awareness.js +24 -10
@@ -1,65 +1,66 @@
1
- import { readFileSync } from 'fs';
2
- import { join, dirname } from 'path';
3
- import { fileURLToPath } from 'url';
4
- import chalk from 'chalk';
5
- import ora from 'ora';
6
- import boxen from 'boxen';
7
- import gradient from 'gradient-string';
1
+ import { readFileSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { networkInterfaces } from "os";
5
+ import chalk from "chalk";
6
+ import ora from "ora";
7
+ import boxen from "boxen";
8
+ import gradient from "gradient-string";
9
+ import * as p from "@clack/prompts";
10
+ import { PROVIDERS } from "../providers/models.js";
11
+
12
+ export { p };
13
+
14
+ function getLocalIp() {
15
+ const nets = networkInterfaces();
16
+ for (const iface of Object.values(nets)) {
17
+ for (const info of iface) {
18
+ if (info.family === "IPv4" && !info.internal) return info.address;
19
+ }
20
+ }
21
+ return "localhost";
22
+ }
8
23
 
9
24
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
25
 
11
26
  function getVersion() {
12
27
  try {
13
- const pkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8'));
28
+ const pkg = JSON.parse(readFileSync(join(__dirname, "../../package.json"), "utf-8"));
14
29
  return pkg.version;
15
30
  } catch {
16
- return 'unknown';
31
+ return "unknown";
17
32
  }
18
33
  }
19
34
 
20
35
  const LOGO = `
21
- ██╗ ██╗███████╗██████╗ ███╗ ██╗███████╗██╗ ██████╗ ██████╗ ████████╗
22
- ██║ ██╔╝██╔════╝██╔══██╗████╗ ██║██╔════╝██║ ██╔══██╗██╔═══██╗╚══██╔══╝
23
- █████╔╝ █████╗ ██████╔╝██╔██╗ ██║█████╗ ██║ ██████╔╝██║ ██║ ██║
24
- ██╔═██╗ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ██║ ██╔══██╗██║ ██║ ██║
25
- ██║ ██╗███████╗██║ ██║██║ ╚████║███████╗███████╗██████╔╝╚██████╔╝ ██║
26
- ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝╚═════╝ ╚═════╝ ╚═╝
36
+ ▄▄████████▄▄
37
+ ▄██▀▀ ▀▀██▄
38
+ ▄█▀ ▄██████▄ ▀█▄
39
+ █▀ ▄██▀ ▀██▄ ▀█
40
+ █▀ ▄█▀ ▄████▄ ▀█▄ ▀█
41
+ ▐█ ██ ▄█▀ ▀█▄ ██ █▌
42
+ ▐█ █▌ █▀ ██ ▀█ ▐█ █▌
43
+ ▐█ █▌ ▀█▄ ▀▀██▀ ▐█ █▌
44
+ █▄ ██ ▀▀████▀ ██ ▄█
45
+ █▄ ▀██▄ ▄██▀ ▄█
46
+ ▀█▄ ▀██████▀ ▄█▀
47
+ ▀██▄▄ ▄▄██▀
48
+ ▀▀████████▀▀
49
+
50
+ █▄▀ █▀▀ █▀█ █▄ █ █▀▀ █ █▀▄ █▀█ ▀█▀
51
+ █▀▄ █▀▀ █▄▀ █ ██ █▀▀ █ ██▀ █ █ █
52
+ █ █ █▄▄ █ █ █ ▀█ █▄▄ █▄▄ █▄▀ █▄█ █
27
53
  `;
28
54
 
29
- // White to ~70% black gradient
30
- const monoGradient = gradient([
31
- '#FFFFFF',
32
- '#D0D0D0',
33
- '#A0A0A0',
34
- '#707070',
35
- '#4D4D4D',
36
- ]);
55
+ // Green terminal gradient
56
+ const monoGradient = gradient(["#00ff41", "#00cc33", "#009926", "#006619"]);
37
57
 
38
58
  export function showLogo() {
39
59
  console.log(monoGradient.multiline(LOGO));
40
- console.log(chalk.dim(` AI Engineering Agent — v${getVersion()}\n`));
41
- console.log(
42
- boxen(
43
- chalk.yellow.bold('WARNING') +
44
- chalk.yellow(
45
- '\n\nKernelBot has full access to your operating system.\n' +
46
- 'It can execute commands, read/write files, manage processes,\n' +
47
- 'and interact with external services on your behalf.\n\n' +
48
- 'Only run this on machines you control.\n' +
49
- 'Set OWNER_TELEGRAM_ID in .env or allowed_users in config.yaml.',
50
- ),
51
- {
52
- padding: 1,
53
- borderStyle: 'round',
54
- borderColor: 'yellow',
55
- },
56
- ),
57
- );
58
- console.log('');
59
60
  }
60
61
 
61
62
  export async function showStartupCheck(label, checkFn) {
62
- const spinner = ora({ text: label, color: 'cyan' }).start();
63
+ const spinner = ora({ text: label, color: "cyan" }).start();
63
64
  try {
64
65
  await checkFn();
65
66
  spinner.succeed(chalk.green(label));
@@ -72,11 +73,11 @@ export async function showStartupCheck(label, checkFn) {
72
73
 
73
74
  export function showStartupComplete() {
74
75
  console.log(
75
- boxen(chalk.green.bold('KernelBot is live'), {
76
+ boxen(chalk.green.bold("KernelBot is live"), {
76
77
  padding: 1,
77
78
  margin: { top: 1 },
78
- borderStyle: 'round',
79
- borderColor: 'green',
79
+ borderStyle: "round",
80
+ borderColor: "green",
80
81
  }),
81
82
  );
82
83
  }
@@ -85,8 +86,8 @@ export function showSuccess(msg) {
85
86
  console.log(
86
87
  boxen(chalk.green(msg), {
87
88
  padding: 1,
88
- borderStyle: 'round',
89
- borderColor: 'green',
89
+ borderStyle: "round",
90
+ borderColor: "green",
90
91
  }),
91
92
  );
92
93
  }
@@ -95,12 +96,124 @@ export function showError(msg) {
95
96
  console.log(
96
97
  boxen(chalk.red(msg), {
97
98
  padding: 1,
98
- borderStyle: 'round',
99
- borderColor: 'red',
99
+ borderStyle: "round",
100
+ borderColor: "red",
100
101
  }),
101
102
  );
102
103
  }
103
104
 
104
105
  export function createSpinner(text) {
105
- return ora({ text, color: 'cyan' });
106
- }
106
+ return ora({ text, color: "cyan" });
107
+ }
108
+
109
+ /**
110
+ * Display a single character card in the CLI.
111
+ * @param {object} character — character profile with name, emoji, tagline, origin, age, asciiArt
112
+ * @param {boolean} isActive — whether this is the currently active character
113
+ */
114
+ export function showCharacterCard(character, isActive = false) {
115
+ const art = character.asciiArt || "";
116
+ const activeTag = isActive ? chalk.green(" (active)") : "";
117
+ const content = [
118
+ `${character.emoji} ${chalk.bold(character.name)}${activeTag}`,
119
+ chalk.dim(`"${character.tagline}"`),
120
+ "",
121
+ ...(art ? art.split("\n").map((line) => chalk.cyan(line)) : []),
122
+ "",
123
+ chalk.dim(`Origin: ${character.origin || "Unknown"}`),
124
+ chalk.dim(`Style: ${character.age || "Unknown"}`),
125
+ ].join("\n");
126
+
127
+ console.log(
128
+ boxen(content, {
129
+ padding: 1,
130
+ borderStyle: "round",
131
+ borderColor: isActive ? "green" : "cyan",
132
+ }),
133
+ );
134
+ }
135
+
136
+ /**
137
+ * Format "Provider / model" label for a config section.
138
+ * @param {object} config — full config
139
+ * @param {'brain'|'orchestrator'} section
140
+ */
141
+ export function formatProviderLabel(config, section) {
142
+ const sec = config[section];
143
+ const providerDef = PROVIDERS[sec.provider];
144
+ const name = providerDef ? providerDef.name : sec.provider;
145
+ return `${name} / ${sec.model}`;
146
+ }
147
+
148
+ /**
149
+ * Centralized cancel handler for @clack/prompts.
150
+ * Call after every prompt — exits gracefully on Ctrl+C.
151
+ */
152
+ export function handleCancel(value) {
153
+ if (p.isCancel(value)) {
154
+ p.cancel("Cancelled.");
155
+ return true;
156
+ }
157
+ return false;
158
+ }
159
+
160
+ /**
161
+ * Claude Code-inspired info box shown after the logo.
162
+ */
163
+ export function showWelcomeScreen(config, characterManager) {
164
+ const version = getVersion();
165
+
166
+ const orchLabel = formatProviderLabel(config, "orchestrator");
167
+ const brainLabel = formatProviderLabel(config, "brain");
168
+
169
+ let charLabel = "None";
170
+ if (characterManager) {
171
+ const activeId = characterManager.getActiveCharacterId();
172
+ const active = characterManager.getCharacter(activeId);
173
+ if (active) charLabel = `${active.emoji} ${active.name}`;
174
+ }
175
+
176
+ const lifeEnabled = config.life?.enabled !== false;
177
+ const dashPort = config.dashboard?.port || 3000;
178
+ const dashEnabled = config.dashboard?.enabled;
179
+
180
+ const pad = (label, width = 18) => label.padEnd(width);
181
+
182
+ const lines = [
183
+ "",
184
+ ` ${chalk.dim(pad("Orchestrator"))}${orchLabel}`,
185
+ ` ${chalk.dim(pad("Brain"))}${brainLabel}`,
186
+ ` ${chalk.dim(pad("Character"))}${charLabel}`,
187
+ ` ${chalk.dim(pad("Life Engine"))}${lifeEnabled ? chalk.green("enabled") : chalk.yellow("disabled")}`,
188
+ ` ${chalk.dim(pad("Dashboard"))}${dashEnabled ? chalk.green(`http://${getLocalIp()}:${dashPort}/`) : chalk.yellow("off")}`,
189
+ "",
190
+ chalk.dim(" ↑↓ Navigate · Enter Select · Ctrl+C Cancel"),
191
+ ];
192
+
193
+ console.log(
194
+ boxen(lines.join("\n"), {
195
+ title: `KERNEL Bot v${version}`,
196
+ titleAlignment: "left",
197
+ padding: { top: 0, bottom: 0, left: 0, right: 2 },
198
+ borderStyle: "round",
199
+ borderColor: "green",
200
+ }),
201
+ );
202
+ }
203
+
204
+ export function showCharacterGallery(characters, activeId = null) {
205
+ console.log("");
206
+ console.log(
207
+ gradient(["#ff6b6b", "#feca57", "#48dbfb", "#ff9ff3"]).multiline(
208
+ " ═══════════════════════════════\n" + " CHOOSE YOUR CHARACTER\n" + " ═══════════════════════════════",
209
+ ),
210
+ );
211
+ console.log("");
212
+ console.log(chalk.dim(" Each character has their own personality,"));
213
+ console.log(chalk.dim(" memories, and story that evolves with you."));
214
+ console.log("");
215
+
216
+ for (const c of characters) {
217
+ showCharacterCard(c, c.id === activeId);
218
+ }
219
+ }
@@ -117,17 +117,30 @@ function determineStatus(hour, dayOfWeek, workingHours) {
117
117
  }
118
118
 
119
119
  /**
120
- * Determine the likely activity period for more nuanced awareness.
120
+ * Activity period definitions with behavioral tone hints.
121
+ * Each period carries a label and a short guidance string that gets
122
+ * injected into the system prompt so the LLM adapts its personality
123
+ * to the owner's real-time situation.
124
+ */
125
+ const ACTIVITY_PERIODS = [
126
+ { start: 0, end: 5, label: 'late_night', tone: 'Keep it calm and gentle — they may be winding down or unable to sleep. Avoid high-energy openers.' },
127
+ { start: 5, end: 7, label: 'early_morning', tone: 'They are just waking up. Be warm but concise — ease them into the day without overwhelming detail.' },
128
+ { start: 7, end: 12, label: 'morning', tone: 'Good energy window. Match an upbeat, productive tone — they are likely starting their day.' },
129
+ { start: 12, end: 14, label: 'midday', tone: 'Lunch break energy — keep things light and conversational unless they initiate something serious.' },
130
+ { start: 14, end: 17, label: 'afternoon', tone: 'Afternoon focus. Be direct and helpful — they may be deep in work.' },
131
+ { start: 17, end: 20, label: 'evening', tone: 'Winding down from the day. Be relaxed and friendly — match a casual, end-of-day vibe.' },
132
+ { start: 20, end: 23, label: 'night', tone: 'Late evening — be warm and unhurried. They are likely relaxing, so keep the mood easy-going.' },
133
+ ];
134
+
135
+ /**
136
+ * Determine the likely activity period and its behavioral tone hint.
137
+ * Returns { label, tone } for richer LLM context.
121
138
  */
122
139
  function determineActivityPeriod(hour) {
123
- if (hour >= 0 && hour < 5) return 'late_night';
124
- if (hour >= 5 && hour < 7) return 'early_morning';
125
- if (hour >= 7 && hour < 12) return 'morning';
126
- if (hour >= 12 && hour < 14) return 'midday';
127
- if (hour >= 14 && hour < 17) return 'afternoon';
128
- if (hour >= 17 && hour < 20) return 'evening';
129
- if (hour >= 20 && hour < 23) return 'night';
130
- return 'late_night';
140
+ const period = ACTIVITY_PERIODS.find(p => hour >= p.start && hour < p.end);
141
+ if (period) return { label: period.label, tone: period.tone };
142
+ // hour 23 falls outside all ranges treat as late_night
143
+ return { label: 'late_night', tone: ACTIVITY_PERIODS[0].tone };
131
144
  }
132
145
 
133
146
  /**
@@ -163,7 +176,7 @@ export function buildTemporalAwareness() {
163
176
  const currentHour = getCurrentHour(now, timezone);
164
177
  const currentDay = getCurrentDayOfWeek(now, timezone);
165
178
  const { status, detail } = determineStatus(currentHour, currentDay, owner.working_hours);
166
- const period = determineActivityPeriod(currentHour);
179
+ const { label: period, tone: periodTone } = determineActivityPeriod(currentHour);
167
180
 
168
181
  const lines = [
169
182
  `## Owner's Real-Time Context`,
@@ -190,6 +203,7 @@ export function buildTemporalAwareness() {
190
203
  lines.push('IMPORTANT: Be aware of the owner\'s current local time and status.');
191
204
  lines.push('Do NOT assume they are at work during off-hours, or sleeping during work hours.');
192
205
  lines.push('Adjust greetings, tone, and context to match their real-time situation.');
206
+ lines.push(`Tone hint: ${periodTone}`);
193
207
 
194
208
  const block = lines.join('\n');
195
209