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.
- package/bin/kernel.js +499 -249
- package/config.example.yaml +17 -0
- package/knowledge_base/active_inference_foraging.md +126 -0
- package/knowledge_base/index.md +1 -1
- package/package.json +3 -1
- package/src/agent.js +355 -82
- package/src/bot.js +724 -12
- package/src/character.js +406 -0
- package/src/characters/builder.js +174 -0
- package/src/characters/builtins.js +421 -0
- package/src/conversation.js +17 -2
- package/src/dashboard/agents.css +469 -0
- package/src/dashboard/agents.html +184 -0
- package/src/dashboard/agents.js +873 -0
- package/src/dashboard/dashboard.css +281 -0
- package/src/dashboard/dashboard.js +579 -0
- package/src/dashboard/index.html +366 -0
- package/src/dashboard/server.js +521 -0
- package/src/dashboard/shared.css +700 -0
- package/src/dashboard/shared.js +218 -0
- package/src/life/engine.js +115 -26
- package/src/life/evolution.js +7 -5
- package/src/life/journal.js +5 -4
- package/src/life/memory.js +12 -9
- package/src/life/share-queue.js +7 -5
- package/src/prompts/orchestrator.js +76 -14
- package/src/prompts/workers.js +22 -0
- package/src/self.js +17 -5
- package/src/services/linkedin-api.js +190 -0
- package/src/services/stt.js +8 -2
- package/src/services/tts.js +32 -2
- package/src/services/x-api.js +141 -0
- package/src/swarm/worker-registry.js +7 -0
- package/src/tools/categories.js +4 -0
- package/src/tools/index.js +6 -0
- package/src/tools/linkedin.js +264 -0
- package/src/tools/orchestrator-tools.js +337 -2
- package/src/tools/x.js +256 -0
- package/src/utils/config.js +190 -139
- package/src/utils/display.js +165 -52
- package/src/utils/temporal-awareness.js +24 -10
package/bin/kernel.js
CHANGED
|
@@ -4,34 +4,35 @@
|
|
|
4
4
|
process.removeAllListeners('warning');
|
|
5
5
|
process.on('warning', (w) => { if (w.name !== 'DeprecationWarning' || !w.message.includes('punycode')) console.warn(w); });
|
|
6
6
|
|
|
7
|
-
import { createInterface } from 'readline';
|
|
8
7
|
import { readFileSync, existsSync } from 'fs';
|
|
9
8
|
import { join } from 'path';
|
|
10
9
|
import { homedir } from 'os';
|
|
11
10
|
import chalk from 'chalk';
|
|
12
|
-
import
|
|
11
|
+
import * as p from '@clack/prompts';
|
|
12
|
+
import { loadConfig, loadConfigInteractive, changeBrainModel, changeOrchestratorModel, saveDashboardToYaml } from '../src/utils/config.js';
|
|
13
13
|
import { createLogger, getLogger } from '../src/utils/logger.js';
|
|
14
14
|
import {
|
|
15
15
|
showLogo,
|
|
16
16
|
showStartupCheck,
|
|
17
17
|
showStartupComplete,
|
|
18
18
|
showError,
|
|
19
|
+
showCharacterCard,
|
|
20
|
+
showWelcomeScreen,
|
|
21
|
+
handleCancel,
|
|
22
|
+
formatProviderLabel,
|
|
19
23
|
} from '../src/utils/display.js';
|
|
20
24
|
import { createAuditLogger } from '../src/security/audit.js';
|
|
25
|
+
import { CharacterBuilder } from '../src/characters/builder.js';
|
|
21
26
|
import { ConversationManager } from '../src/conversation.js';
|
|
22
27
|
import { UserPersonaManager } from '../src/persona.js';
|
|
23
|
-
import { SelfManager } from '../src/self.js';
|
|
24
28
|
import { Agent } from '../src/agent.js';
|
|
25
29
|
import { JobManager } from '../src/swarm/job-manager.js';
|
|
26
30
|
import { startBot } from '../src/bot.js';
|
|
27
31
|
import { AutomationManager } from '../src/automation/index.js';
|
|
28
32
|
import { createProvider, PROVIDERS } from '../src/providers/index.js';
|
|
29
|
-
import { MemoryManager } from '../src/life/memory.js';
|
|
30
|
-
import { JournalManager } from '../src/life/journal.js';
|
|
31
|
-
import { ShareQueue } from '../src/life/share-queue.js';
|
|
32
|
-
import { EvolutionTracker } from '../src/life/evolution.js';
|
|
33
33
|
import { CodebaseKnowledge } from '../src/life/codebase.js';
|
|
34
34
|
import { LifeEngine } from '../src/life/engine.js';
|
|
35
|
+
import { CharacterManager } from '../src/character.js';
|
|
35
36
|
import {
|
|
36
37
|
loadCustomSkills,
|
|
37
38
|
getCustomSkills,
|
|
@@ -39,102 +40,34 @@ import {
|
|
|
39
40
|
deleteCustomSkill,
|
|
40
41
|
} from '../src/skills/custom.js';
|
|
41
42
|
|
|
42
|
-
function showMenu(config) {
|
|
43
|
-
const orchProviderDef = PROVIDERS[config.orchestrator.provider];
|
|
44
|
-
const orchProviderName = orchProviderDef ? orchProviderDef.name : config.orchestrator.provider;
|
|
45
|
-
const orchModelId = config.orchestrator.model;
|
|
46
|
-
|
|
47
|
-
const providerDef = PROVIDERS[config.brain.provider];
|
|
48
|
-
const providerName = providerDef ? providerDef.name : config.brain.provider;
|
|
49
|
-
const modelId = config.brain.model;
|
|
50
|
-
|
|
51
|
-
console.log('');
|
|
52
|
-
console.log(chalk.dim(` Current orchestrator: ${orchProviderName} / ${orchModelId}`));
|
|
53
|
-
console.log(chalk.dim(` Current brain: ${providerName} / ${modelId}`));
|
|
54
|
-
console.log('');
|
|
55
|
-
console.log(chalk.bold(' What would you like to do?\n'));
|
|
56
|
-
console.log(` ${chalk.cyan('1.')} Start bot`);
|
|
57
|
-
console.log(` ${chalk.cyan('2.')} Check connections`);
|
|
58
|
-
console.log(` ${chalk.cyan('3.')} View logs`);
|
|
59
|
-
console.log(` ${chalk.cyan('4.')} View audit logs`);
|
|
60
|
-
console.log(` ${chalk.cyan('5.')} Change brain model`);
|
|
61
|
-
console.log(` ${chalk.cyan('6.')} Change orchestrator model`);
|
|
62
|
-
console.log(` ${chalk.cyan('7.')} Manage custom skills`);
|
|
63
|
-
console.log(` ${chalk.cyan('8.')} Manage automations`);
|
|
64
|
-
console.log(` ${chalk.cyan('9.')} Exit`);
|
|
65
|
-
console.log('');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function ask(rl, question) {
|
|
69
|
-
return new Promise((res) => rl.question(question, res));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
43
|
/**
|
|
73
44
|
* Register SIGINT/SIGTERM handlers to shut down the bot cleanly.
|
|
74
|
-
* Stops polling, cancels running jobs, persists conversations,
|
|
75
|
-
* disarms automations, stops the life engine, and clears intervals.
|
|
76
45
|
*/
|
|
77
|
-
function setupGracefulShutdown({ bot, lifeEngine, automationManager, jobManager, conversationManager, intervals }) {
|
|
46
|
+
function setupGracefulShutdown({ bot, lifeEngine, automationManager, jobManager, conversationManager, intervals, dashboardHandle }) {
|
|
78
47
|
let shuttingDown = false;
|
|
79
48
|
|
|
80
49
|
const shutdown = async (signal) => {
|
|
81
|
-
if (shuttingDown) return;
|
|
50
|
+
if (shuttingDown) return;
|
|
82
51
|
shuttingDown = true;
|
|
83
52
|
|
|
84
53
|
const logger = getLogger();
|
|
85
54
|
logger.info(`[Shutdown] ${signal} received — shutting down gracefully...`);
|
|
86
55
|
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
|
|
90
|
-
logger.info('[Shutdown] Telegram polling stopped');
|
|
91
|
-
} catch (err) {
|
|
92
|
-
logger.error(`[Shutdown] Failed to stop polling: ${err.message}`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// 2. Stop life engine heartbeat
|
|
96
|
-
try {
|
|
97
|
-
lifeEngine.stop();
|
|
98
|
-
logger.info('[Shutdown] Life engine stopped');
|
|
99
|
-
} catch (err) {
|
|
100
|
-
logger.error(`[Shutdown] Failed to stop life engine: ${err.message}`);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// 3. Disarm all automation timers
|
|
104
|
-
try {
|
|
105
|
-
automationManager.shutdown();
|
|
106
|
-
logger.info('[Shutdown] Automation timers cancelled');
|
|
107
|
-
} catch (err) {
|
|
108
|
-
logger.error(`[Shutdown] Failed to shutdown automations: ${err.message}`);
|
|
109
|
-
}
|
|
56
|
+
try { bot.stopPolling(); logger.info('[Shutdown] Telegram polling stopped'); } catch (err) { logger.error(`[Shutdown] Failed to stop polling: ${err.message}`); }
|
|
57
|
+
try { lifeEngine.stop(); logger.info('[Shutdown] Life engine stopped'); } catch (err) { logger.error(`[Shutdown] Failed to stop life engine: ${err.message}`); }
|
|
58
|
+
try { automationManager.shutdown(); logger.info('[Shutdown] Automation timers cancelled'); } catch (err) { logger.error(`[Shutdown] Failed to shutdown automations: ${err.message}`); }
|
|
110
59
|
|
|
111
|
-
// 4. Cancel all running jobs
|
|
112
60
|
try {
|
|
113
61
|
const running = [...jobManager.jobs.values()].filter(j => !j.isTerminal);
|
|
114
|
-
for (const job of running)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (running.length > 0) {
|
|
118
|
-
logger.info(`[Shutdown] Cancelled ${running.length} running job(s)`);
|
|
119
|
-
}
|
|
120
|
-
} catch (err) {
|
|
121
|
-
logger.error(`[Shutdown] Failed to cancel jobs: ${err.message}`);
|
|
122
|
-
}
|
|
62
|
+
for (const job of running) jobManager.cancelJob(job.id);
|
|
63
|
+
if (running.length > 0) logger.info(`[Shutdown] Cancelled ${running.length} running job(s)`);
|
|
64
|
+
} catch (err) { logger.error(`[Shutdown] Failed to cancel jobs: ${err.message}`); }
|
|
123
65
|
|
|
124
|
-
|
|
125
|
-
try {
|
|
126
|
-
conversationManager.save();
|
|
127
|
-
logger.info('[Shutdown] Conversations saved');
|
|
128
|
-
} catch (err) {
|
|
129
|
-
logger.error(`[Shutdown] Failed to save conversations: ${err.message}`);
|
|
130
|
-
}
|
|
66
|
+
try { conversationManager.save(); logger.info('[Shutdown] Conversations saved'); } catch (err) { logger.error(`[Shutdown] Failed to save conversations: ${err.message}`); }
|
|
67
|
+
try { dashboardHandle?.stop(); } catch (err) { logger.error(`[Shutdown] Failed to stop dashboard: ${err.message}`); }
|
|
131
68
|
|
|
132
|
-
|
|
133
|
-
for (const id of intervals) {
|
|
134
|
-
clearInterval(id);
|
|
135
|
-
}
|
|
69
|
+
for (const id of intervals) clearInterval(id);
|
|
136
70
|
logger.info('[Shutdown] Periodic timers cleared');
|
|
137
|
-
|
|
138
71
|
logger.info('[Shutdown] Graceful shutdown complete');
|
|
139
72
|
process.exit(0);
|
|
140
73
|
};
|
|
@@ -149,33 +82,33 @@ function viewLog(filename) {
|
|
|
149
82
|
join(homedir(), '.kernelbot', filename),
|
|
150
83
|
];
|
|
151
84
|
|
|
152
|
-
for (const
|
|
153
|
-
if (existsSync(
|
|
154
|
-
const content = readFileSync(
|
|
85
|
+
for (const logPath of paths) {
|
|
86
|
+
if (existsSync(logPath)) {
|
|
87
|
+
const content = readFileSync(logPath, 'utf-8');
|
|
155
88
|
const lines = content.split('\n').filter(Boolean);
|
|
156
89
|
const recent = lines.slice(-30);
|
|
157
|
-
|
|
158
|
-
|
|
90
|
+
|
|
91
|
+
const formatted = recent.map(line => {
|
|
159
92
|
try {
|
|
160
93
|
const entry = JSON.parse(line);
|
|
161
94
|
const time = entry.timestamp || '';
|
|
162
95
|
const level = entry.level || '';
|
|
163
96
|
const msg = entry.message || '';
|
|
164
97
|
const color = level === 'error' ? chalk.red : level === 'warn' ? chalk.yellow : chalk.dim;
|
|
165
|
-
|
|
98
|
+
return `${chalk.dim(time)} ${color(level)} ${msg}`;
|
|
166
99
|
} catch {
|
|
167
|
-
|
|
100
|
+
return line;
|
|
168
101
|
}
|
|
169
|
-
}
|
|
170
|
-
|
|
102
|
+
}).join('\n');
|
|
103
|
+
|
|
104
|
+
p.note(formatted, `Last ${recent.length} entries from ${logPath}`);
|
|
171
105
|
return;
|
|
172
106
|
}
|
|
173
107
|
}
|
|
174
|
-
|
|
108
|
+
p.log.info(`No ${filename} found yet.`);
|
|
175
109
|
}
|
|
176
110
|
|
|
177
111
|
async function runCheck(config) {
|
|
178
|
-
// Orchestrator check
|
|
179
112
|
const orchProviderKey = config.orchestrator.provider || 'anthropic';
|
|
180
113
|
const orchProviderDef = PROVIDERS[orchProviderKey];
|
|
181
114
|
const orchLabel = orchProviderDef ? orchProviderDef.name : orchProviderKey;
|
|
@@ -198,7 +131,6 @@ async function runCheck(config) {
|
|
|
198
131
|
await provider.ping();
|
|
199
132
|
});
|
|
200
133
|
|
|
201
|
-
// Worker brain check
|
|
202
134
|
const providerDef = PROVIDERS[config.brain.provider];
|
|
203
135
|
const providerLabel = providerDef ? providerDef.name : config.brain.provider;
|
|
204
136
|
const envKeyLabel = providerDef ? providerDef.envKey : 'API_KEY';
|
|
@@ -217,14 +149,12 @@ async function runCheck(config) {
|
|
|
217
149
|
});
|
|
218
150
|
|
|
219
151
|
await showStartupCheck('Telegram Bot API', async () => {
|
|
220
|
-
const res = await fetch(
|
|
221
|
-
`https://api.telegram.org/bot${config.telegram.bot_token}/getMe`,
|
|
222
|
-
);
|
|
152
|
+
const res = await fetch(`https://api.telegram.org/bot${config.telegram.bot_token}/getMe`);
|
|
223
153
|
const data = await res.json();
|
|
224
154
|
if (!data.ok) throw new Error(data.description || 'Invalid token');
|
|
225
155
|
});
|
|
226
156
|
|
|
227
|
-
|
|
157
|
+
p.log.success('All checks passed.');
|
|
228
158
|
}
|
|
229
159
|
|
|
230
160
|
async function startBotFlow(config) {
|
|
@@ -236,7 +166,6 @@ async function startBotFlow(config) {
|
|
|
236
166
|
|
|
237
167
|
const checks = [];
|
|
238
168
|
|
|
239
|
-
// Orchestrator check — dynamic provider
|
|
240
169
|
const orchProviderKey = config.orchestrator.provider || 'anthropic';
|
|
241
170
|
const orchProviderDef = PROVIDERS[orchProviderKey];
|
|
242
171
|
const orchLabel = orchProviderDef ? orchProviderDef.name : orchProviderKey;
|
|
@@ -258,7 +187,6 @@ async function startBotFlow(config) {
|
|
|
258
187
|
}),
|
|
259
188
|
);
|
|
260
189
|
|
|
261
|
-
// Worker brain check
|
|
262
190
|
checks.push(
|
|
263
191
|
await showStartupCheck(`Worker (${providerLabel}) API`, async () => {
|
|
264
192
|
const provider = createProvider(config);
|
|
@@ -268,9 +196,7 @@ async function startBotFlow(config) {
|
|
|
268
196
|
|
|
269
197
|
checks.push(
|
|
270
198
|
await showStartupCheck('Telegram Bot API', async () => {
|
|
271
|
-
const res = await fetch(
|
|
272
|
-
`https://api.telegram.org/bot${config.telegram.bot_token}/getMe`,
|
|
273
|
-
);
|
|
199
|
+
const res = await fetch(`https://api.telegram.org/bot${config.telegram.bot_token}/getMe`);
|
|
274
200
|
const data = await res.json();
|
|
275
201
|
if (!data.ok) throw new Error(data.description || 'Invalid token');
|
|
276
202
|
}),
|
|
@@ -281,53 +207,90 @@ async function startBotFlow(config) {
|
|
|
281
207
|
return false;
|
|
282
208
|
}
|
|
283
209
|
|
|
284
|
-
const
|
|
210
|
+
const characterManager = new CharacterManager();
|
|
211
|
+
if (characterManager.needsOnboarding) {
|
|
212
|
+
characterManager.installAllBuiltins();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const activeCharacterId = characterManager.getActiveCharacterId();
|
|
216
|
+
const charCtx = characterManager.buildContext(activeCharacterId);
|
|
217
|
+
|
|
218
|
+
const conversationManager = new ConversationManager(config, charCtx.conversationFilePath);
|
|
285
219
|
const personaManager = new UserPersonaManager();
|
|
286
|
-
const selfManager = new SelfManager();
|
|
287
220
|
const jobManager = new JobManager({
|
|
288
221
|
jobTimeoutSeconds: config.swarm.job_timeout_seconds,
|
|
289
222
|
cleanupIntervalMinutes: config.swarm.cleanup_interval_minutes,
|
|
290
223
|
});
|
|
291
224
|
|
|
292
225
|
const automationManager = new AutomationManager();
|
|
293
|
-
|
|
294
|
-
// Life system managers
|
|
295
|
-
const memoryManager = new MemoryManager();
|
|
296
|
-
const journalManager = new JournalManager();
|
|
297
|
-
const shareQueue = new ShareQueue();
|
|
298
|
-
const evolutionTracker = new EvolutionTracker();
|
|
299
226
|
const codebaseKnowledge = new CodebaseKnowledge({ config });
|
|
300
227
|
|
|
301
|
-
const agent = new Agent({
|
|
228
|
+
const agent = new Agent({
|
|
229
|
+
config, conversationManager, personaManager,
|
|
230
|
+
selfManager: charCtx.selfManager,
|
|
231
|
+
jobManager, automationManager,
|
|
232
|
+
memoryManager: charCtx.memoryManager,
|
|
233
|
+
shareQueue: charCtx.shareQueue,
|
|
234
|
+
characterManager,
|
|
235
|
+
});
|
|
302
236
|
|
|
303
|
-
|
|
237
|
+
agent.loadCharacter(activeCharacterId);
|
|
304
238
|
codebaseKnowledge.setAgent(agent);
|
|
305
239
|
|
|
306
|
-
// Life Engine — autonomous inner life
|
|
307
240
|
const lifeEngine = new LifeEngine({
|
|
308
|
-
config, agent,
|
|
309
|
-
|
|
241
|
+
config, agent,
|
|
242
|
+
memoryManager: charCtx.memoryManager,
|
|
243
|
+
journalManager: charCtx.journalManager,
|
|
244
|
+
shareQueue: charCtx.shareQueue,
|
|
245
|
+
evolutionTracker: charCtx.evolutionTracker,
|
|
246
|
+
codebaseKnowledge,
|
|
247
|
+
selfManager: charCtx.selfManager,
|
|
248
|
+
basePath: charCtx.lifeBasePath,
|
|
249
|
+
characterId: activeCharacterId,
|
|
310
250
|
});
|
|
311
251
|
|
|
312
|
-
const
|
|
252
|
+
const dashboardDeps = {
|
|
253
|
+
config, jobManager, automationManager, lifeEngine, conversationManager, characterManager,
|
|
254
|
+
memoryManager: charCtx.memoryManager,
|
|
255
|
+
journalManager: charCtx.journalManager,
|
|
256
|
+
shareQueue: charCtx.shareQueue,
|
|
257
|
+
evolutionTracker: charCtx.evolutionTracker,
|
|
258
|
+
selfManager: charCtx.selfManager,
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
let dashboardHandle = null;
|
|
262
|
+
if (config.dashboard?.enabled) {
|
|
263
|
+
const { startDashboard } = await import('../src/dashboard/server.js');
|
|
264
|
+
dashboardHandle = startDashboard({ port: config.dashboard.port, ...dashboardDeps });
|
|
265
|
+
logger.info(`[Dashboard] Running on http://localhost:${config.dashboard.port}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const bot = startBot(config, agent, conversationManager, jobManager, automationManager, {
|
|
269
|
+
lifeEngine,
|
|
270
|
+
memoryManager: charCtx.memoryManager,
|
|
271
|
+
journalManager: charCtx.journalManager,
|
|
272
|
+
shareQueue: charCtx.shareQueue,
|
|
273
|
+
evolutionTracker: charCtx.evolutionTracker,
|
|
274
|
+
codebaseKnowledge,
|
|
275
|
+
characterManager,
|
|
276
|
+
dashboardHandle,
|
|
277
|
+
dashboardDeps,
|
|
278
|
+
});
|
|
313
279
|
|
|
314
|
-
// Periodic job cleanup and timeout enforcement
|
|
315
280
|
const cleanupMs = (config.swarm.cleanup_interval_minutes || 30) * 60 * 1000;
|
|
316
281
|
const cleanupInterval = setInterval(() => {
|
|
317
282
|
jobManager.cleanup();
|
|
318
283
|
jobManager.enforceTimeouts();
|
|
319
|
-
}, Math.min(cleanupMs, 60_000));
|
|
284
|
+
}, Math.min(cleanupMs, 60_000));
|
|
320
285
|
|
|
321
|
-
// Periodic memory pruning (daily)
|
|
322
286
|
const retentionDays = config.life?.memory_retention_days || 90;
|
|
323
287
|
const pruneInterval = setInterval(() => {
|
|
324
|
-
memoryManager.pruneOld(retentionDays);
|
|
325
|
-
shareQueue.prune(7);
|
|
288
|
+
charCtx.memoryManager.pruneOld(retentionDays);
|
|
289
|
+
charCtx.shareQueue.prune(7);
|
|
326
290
|
}, 24 * 3600_000);
|
|
327
291
|
|
|
328
292
|
showStartupComplete();
|
|
329
293
|
|
|
330
|
-
// Start life engine if enabled
|
|
331
294
|
const lifeEnabled = config.life?.enabled !== false;
|
|
332
295
|
if (lifeEnabled) {
|
|
333
296
|
logger.info('[Startup] Life engine enabled — waking up...');
|
|
@@ -336,10 +299,9 @@ async function startBotFlow(config) {
|
|
|
336
299
|
logger.info('[Startup] Life engine running');
|
|
337
300
|
}).catch(err => {
|
|
338
301
|
logger.error(`[Startup] Life engine wake-up failed: ${err.message}`);
|
|
339
|
-
lifeEngine.start();
|
|
302
|
+
lifeEngine.start();
|
|
340
303
|
});
|
|
341
304
|
|
|
342
|
-
// Initial codebase scan (background, non-blocking)
|
|
343
305
|
if (config.life?.self_coding?.enabled) {
|
|
344
306
|
codebaseKnowledge.scanChanged().then(count => {
|
|
345
307
|
if (count > 0) logger.info(`[Startup] Codebase scan: ${count} files indexed`);
|
|
@@ -351,211 +313,499 @@ async function startBotFlow(config) {
|
|
|
351
313
|
logger.info('[Startup] Life engine disabled');
|
|
352
314
|
}
|
|
353
315
|
|
|
354
|
-
// Register graceful shutdown handlers
|
|
355
316
|
setupGracefulShutdown({
|
|
356
317
|
bot, lifeEngine, automationManager, jobManager,
|
|
357
318
|
conversationManager, intervals: [cleanupInterval, pruneInterval],
|
|
319
|
+
dashboardHandle,
|
|
358
320
|
});
|
|
359
321
|
|
|
360
322
|
return true;
|
|
361
323
|
}
|
|
362
324
|
|
|
363
|
-
async function manageCustomSkills(
|
|
325
|
+
async function manageCustomSkills() {
|
|
364
326
|
loadCustomSkills();
|
|
365
327
|
|
|
366
328
|
let managing = true;
|
|
367
329
|
while (managing) {
|
|
368
330
|
const customs = getCustomSkills();
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
console.log(chalk.dim(' Empty prompt — cancelled.\n'));
|
|
395
|
-
break;
|
|
396
|
-
}
|
|
397
|
-
const skill = addCustomSkill({ name: name.trim(), systemPrompt: prompt });
|
|
398
|
-
console.log(chalk.green(`\n ✅ Created: ${skill.name} (${skill.id})\n`));
|
|
331
|
+
|
|
332
|
+
const choice = await p.select({
|
|
333
|
+
message: `Custom Skills (${customs.length})`,
|
|
334
|
+
options: [
|
|
335
|
+
{ value: 'create', label: 'Create new skill' },
|
|
336
|
+
{ value: 'list', label: `List skills`, hint: `${customs.length} total` },
|
|
337
|
+
{ value: 'delete', label: 'Delete a skill' },
|
|
338
|
+
{ value: 'back', label: 'Back' },
|
|
339
|
+
],
|
|
340
|
+
});
|
|
341
|
+
if (handleCancel(choice)) return;
|
|
342
|
+
|
|
343
|
+
switch (choice) {
|
|
344
|
+
case 'create': {
|
|
345
|
+
const name = await p.text({ message: 'Skill name' });
|
|
346
|
+
if (handleCancel(name) || !name.trim()) break;
|
|
347
|
+
|
|
348
|
+
const prompt = await p.text({
|
|
349
|
+
message: 'System prompt',
|
|
350
|
+
placeholder: 'Enter the system prompt for this skill...',
|
|
351
|
+
});
|
|
352
|
+
if (handleCancel(prompt) || !prompt.trim()) break;
|
|
353
|
+
|
|
354
|
+
const skill = addCustomSkill({ name: name.trim(), systemPrompt: prompt.trim() });
|
|
355
|
+
p.log.success(`Created: ${skill.name} (${skill.id})`);
|
|
399
356
|
break;
|
|
400
357
|
}
|
|
401
|
-
case '
|
|
402
|
-
const customs = getCustomSkills();
|
|
358
|
+
case 'list': {
|
|
403
359
|
if (!customs.length) {
|
|
404
|
-
|
|
360
|
+
p.log.info('No custom skills yet.');
|
|
405
361
|
break;
|
|
406
362
|
}
|
|
407
|
-
|
|
408
|
-
for (const s of customs) {
|
|
363
|
+
const formatted = customs.map(s => {
|
|
409
364
|
const preview = s.systemPrompt.slice(0, 60).replace(/\n/g, ' ');
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
console.log('');
|
|
365
|
+
return `${chalk.bold(s.name)} (${s.id})\n${chalk.dim(preview + (s.systemPrompt.length > 60 ? '...' : ''))}`;
|
|
366
|
+
}).join('\n\n');
|
|
367
|
+
p.note(formatted, 'Custom Skills');
|
|
414
368
|
break;
|
|
415
369
|
}
|
|
416
|
-
case '
|
|
417
|
-
const customs = getCustomSkills();
|
|
370
|
+
case 'delete': {
|
|
418
371
|
if (!customs.length) {
|
|
419
|
-
|
|
372
|
+
p.log.info('No custom skills to delete.');
|
|
420
373
|
break;
|
|
421
374
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
375
|
+
const toDelete = await p.select({
|
|
376
|
+
message: 'Select skill to delete',
|
|
377
|
+
options: [
|
|
378
|
+
...customs.map(s => ({ value: s.id, label: s.name, hint: s.id })),
|
|
379
|
+
{ value: '__back', label: 'Cancel' },
|
|
380
|
+
],
|
|
425
381
|
});
|
|
426
|
-
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
if (idx >= 0 && idx < customs.length) {
|
|
430
|
-
const deleted = deleteCustomSkill(customs[idx].id);
|
|
431
|
-
if (deleted) console.log(chalk.green(`\n 🗑️ Deleted: ${customs[idx].name}\n`));
|
|
432
|
-
else console.log(chalk.dim(' Not found.\n'));
|
|
433
|
-
} else {
|
|
434
|
-
console.log(chalk.dim(' Cancelled.\n'));
|
|
435
|
-
}
|
|
382
|
+
if (handleCancel(toDelete) || toDelete === '__back') break;
|
|
383
|
+
const deleted = deleteCustomSkill(toDelete);
|
|
384
|
+
if (deleted) p.log.success(`Deleted: ${customs.find(s => s.id === toDelete)?.name}`);
|
|
436
385
|
break;
|
|
437
386
|
}
|
|
438
|
-
case '
|
|
387
|
+
case 'back':
|
|
439
388
|
managing = false;
|
|
440
389
|
break;
|
|
441
|
-
default:
|
|
442
|
-
console.log(chalk.dim(' Invalid choice.\n'));
|
|
443
390
|
}
|
|
444
391
|
}
|
|
445
392
|
}
|
|
446
393
|
|
|
447
|
-
async function manageAutomations(
|
|
394
|
+
async function manageAutomations() {
|
|
448
395
|
const manager = new AutomationManager();
|
|
449
396
|
|
|
450
397
|
let managing = true;
|
|
451
398
|
while (managing) {
|
|
452
399
|
const autos = manager.listAll();
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
400
|
+
|
|
401
|
+
const choice = await p.select({
|
|
402
|
+
message: `Automations (${autos.length})`,
|
|
403
|
+
options: [
|
|
404
|
+
{ value: 'list', label: 'List all automations', hint: `${autos.length} total` },
|
|
405
|
+
{ value: 'delete', label: 'Delete an automation' },
|
|
406
|
+
{ value: 'back', label: 'Back' },
|
|
407
|
+
],
|
|
408
|
+
});
|
|
409
|
+
if (handleCancel(choice)) return;
|
|
410
|
+
|
|
411
|
+
switch (choice) {
|
|
412
|
+
case 'list': {
|
|
463
413
|
if (!autos.length) {
|
|
464
|
-
|
|
414
|
+
p.log.info('No automations found.');
|
|
465
415
|
break;
|
|
466
416
|
}
|
|
467
|
-
|
|
468
|
-
for (const a of autos) {
|
|
417
|
+
const formatted = autos.map(a => {
|
|
469
418
|
const status = a.enabled ? chalk.green('enabled') : chalk.yellow('paused');
|
|
470
419
|
const next = a.nextRun ? new Date(a.nextRun).toLocaleString() : 'not scheduled';
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
|
|
420
|
+
return `${chalk.bold(a.name)} (${a.id}) — chat ${a.chatId}\n` +
|
|
421
|
+
chalk.dim(`Status: ${status} | Runs: ${a.runCount} | Next: ${next}\n`) +
|
|
422
|
+
chalk.dim(`Task: ${a.description.slice(0, 80)}${a.description.length > 80 ? '...' : ''}`);
|
|
423
|
+
}).join('\n\n');
|
|
424
|
+
p.note(formatted, 'Automations');
|
|
476
425
|
break;
|
|
477
426
|
}
|
|
478
|
-
case '
|
|
427
|
+
case 'delete': {
|
|
479
428
|
if (!autos.length) {
|
|
480
|
-
|
|
429
|
+
p.log.info('No automations to delete.');
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
const toDelete = await p.select({
|
|
433
|
+
message: 'Select automation to delete',
|
|
434
|
+
options: [
|
|
435
|
+
...autos.map(a => ({ value: a.id, label: a.name, hint: `chat ${a.chatId}` })),
|
|
436
|
+
{ value: '__back', label: 'Cancel' },
|
|
437
|
+
],
|
|
438
|
+
});
|
|
439
|
+
if (handleCancel(toDelete) || toDelete === '__back') break;
|
|
440
|
+
const deleted = manager.delete(toDelete);
|
|
441
|
+
if (deleted) p.log.success(`Deleted: ${autos.find(a => a.id === toDelete)?.name}`);
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
case 'back':
|
|
445
|
+
managing = false;
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function manageCharacters(config) {
|
|
452
|
+
const charManager = new CharacterManager();
|
|
453
|
+
charManager.installAllBuiltins();
|
|
454
|
+
|
|
455
|
+
let managing = true;
|
|
456
|
+
while (managing) {
|
|
457
|
+
const characters = charManager.listCharacters();
|
|
458
|
+
const activeId = charManager.getActiveCharacterId();
|
|
459
|
+
const active = charManager.getCharacter(activeId);
|
|
460
|
+
|
|
461
|
+
const choice = await p.select({
|
|
462
|
+
message: `Characters — Active: ${active?.emoji || ''} ${active?.name || 'None'}`,
|
|
463
|
+
options: [
|
|
464
|
+
{ value: 'switch', label: 'Switch character' },
|
|
465
|
+
{ value: 'create', label: 'Create custom character' },
|
|
466
|
+
{ value: 'view', label: 'View character info' },
|
|
467
|
+
{ value: 'delete', label: 'Delete a custom character' },
|
|
468
|
+
{ value: 'back', label: 'Back' },
|
|
469
|
+
],
|
|
470
|
+
});
|
|
471
|
+
if (handleCancel(choice)) return;
|
|
472
|
+
|
|
473
|
+
switch (choice) {
|
|
474
|
+
case 'switch': {
|
|
475
|
+
const picked = await p.select({
|
|
476
|
+
message: 'Select character',
|
|
477
|
+
options: characters.map(c => ({
|
|
478
|
+
value: c.id,
|
|
479
|
+
label: `${c.emoji} ${c.name}`,
|
|
480
|
+
hint: c.id === activeId ? 'active' : undefined,
|
|
481
|
+
})),
|
|
482
|
+
});
|
|
483
|
+
if (handleCancel(picked)) break;
|
|
484
|
+
charManager.setActiveCharacter(picked);
|
|
485
|
+
const char = characters.find(c => c.id === picked);
|
|
486
|
+
p.log.success(`${char.emoji} Switched to ${char.name}`);
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
case 'create': {
|
|
490
|
+
p.log.step('Custom Character Builder');
|
|
491
|
+
|
|
492
|
+
const orchProviderKey = config.orchestrator.provider || 'anthropic';
|
|
493
|
+
const orchProviderDef = PROVIDERS[orchProviderKey];
|
|
494
|
+
const orchApiKey = config.orchestrator.api_key || (orchProviderDef && process.env[orchProviderDef.envKey]);
|
|
495
|
+
if (!orchApiKey) {
|
|
496
|
+
p.log.error('No API key configured for character generation.');
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const provider = createProvider({
|
|
501
|
+
brain: {
|
|
502
|
+
provider: orchProviderKey,
|
|
503
|
+
model: config.orchestrator.model,
|
|
504
|
+
max_tokens: config.orchestrator.max_tokens,
|
|
505
|
+
temperature: config.orchestrator.temperature,
|
|
506
|
+
api_key: orchApiKey,
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
const builder = new CharacterBuilder(provider);
|
|
511
|
+
const answers = {};
|
|
512
|
+
let cancelled = false;
|
|
513
|
+
|
|
514
|
+
let q = builder.getNextQuestion(answers);
|
|
515
|
+
while (q) {
|
|
516
|
+
const progress = builder.getProgress(answers);
|
|
517
|
+
const answer = await p.text({
|
|
518
|
+
message: `(${progress.answered + 1}/${progress.total}) ${q.question}`,
|
|
519
|
+
placeholder: q.examples,
|
|
520
|
+
});
|
|
521
|
+
if (handleCancel(answer)) { cancelled = true; break; }
|
|
522
|
+
answers[q.id] = answer.trim();
|
|
523
|
+
q = builder.getNextQuestion(answers);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (cancelled) break;
|
|
527
|
+
|
|
528
|
+
const s = p.spinner();
|
|
529
|
+
s.start('Generating character...');
|
|
530
|
+
try {
|
|
531
|
+
const result = await builder.generateCharacter(answers);
|
|
532
|
+
s.stop('Character generated');
|
|
533
|
+
const id = result.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
534
|
+
|
|
535
|
+
console.log('');
|
|
536
|
+
showCharacterCard({ ...result, id, origin: 'Custom' });
|
|
537
|
+
console.log('');
|
|
538
|
+
|
|
539
|
+
const install = await p.confirm({ message: 'Install this character?' });
|
|
540
|
+
if (handleCancel(install) || !install) {
|
|
541
|
+
p.log.info('Discarded.');
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
charManager.addCharacter(
|
|
546
|
+
{ id, type: 'custom', name: result.name, origin: 'Custom', age: result.age, emoji: result.emoji, tagline: result.tagline },
|
|
547
|
+
result.personaMd,
|
|
548
|
+
result.selfDefaults,
|
|
549
|
+
);
|
|
550
|
+
p.log.success(`${result.emoji} ${result.name} created!`);
|
|
551
|
+
} catch (err) {
|
|
552
|
+
s.stop(chalk.red(`Character generation failed: ${err.message}`));
|
|
553
|
+
}
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
case 'view': {
|
|
557
|
+
const picked = await p.select({
|
|
558
|
+
message: 'Select character to view',
|
|
559
|
+
options: characters.map(c => ({
|
|
560
|
+
value: c.id,
|
|
561
|
+
label: `${c.emoji} ${c.name}`,
|
|
562
|
+
})),
|
|
563
|
+
});
|
|
564
|
+
if (handleCancel(picked)) break;
|
|
565
|
+
const char = characters.find(c => c.id === picked);
|
|
566
|
+
showCharacterCard(char, char.id === activeId);
|
|
567
|
+
if (char.evolutionHistory?.length > 0) {
|
|
568
|
+
p.log.info(`Evolution events: ${char.evolutionHistory.length}`);
|
|
569
|
+
}
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
case 'delete': {
|
|
573
|
+
const customChars = characters.filter(c => c.type === 'custom');
|
|
574
|
+
if (customChars.length === 0) {
|
|
575
|
+
p.log.info('No custom characters to delete.');
|
|
481
576
|
break;
|
|
482
577
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
578
|
+
const picked = await p.select({
|
|
579
|
+
message: 'Select character to delete',
|
|
580
|
+
options: [
|
|
581
|
+
...customChars.map(c => ({ value: c.id, label: `${c.emoji} ${c.name}` })),
|
|
582
|
+
{ value: '__back', label: 'Cancel' },
|
|
583
|
+
],
|
|
486
584
|
});
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
} else {
|
|
495
|
-
console.log(chalk.dim(' Cancelled.\n'));
|
|
585
|
+
if (handleCancel(picked) || picked === '__back') break;
|
|
586
|
+
try {
|
|
587
|
+
const char = customChars.find(c => c.id === picked);
|
|
588
|
+
charManager.removeCharacter(picked);
|
|
589
|
+
p.log.success(`Deleted: ${char.name}`);
|
|
590
|
+
} catch (err) {
|
|
591
|
+
p.log.error(err.message);
|
|
496
592
|
}
|
|
497
593
|
break;
|
|
498
594
|
}
|
|
499
|
-
case '
|
|
595
|
+
case 'back':
|
|
500
596
|
managing = false;
|
|
501
597
|
break;
|
|
502
|
-
default:
|
|
503
|
-
console.log(chalk.dim(' Invalid choice.\n'));
|
|
504
598
|
}
|
|
505
599
|
}
|
|
506
600
|
}
|
|
507
601
|
|
|
602
|
+
async function linkLinkedInCli(config) {
|
|
603
|
+
const { saveCredential } = await import('../src/utils/config.js');
|
|
604
|
+
|
|
605
|
+
if (config.linkedin?.access_token) {
|
|
606
|
+
const truncated = `${config.linkedin.access_token.slice(0, 8)}...${config.linkedin.access_token.slice(-4)}`;
|
|
607
|
+
p.note(
|
|
608
|
+
`Token: ${truncated}${config.linkedin.person_urn ? `\nURN: ${config.linkedin.person_urn}` : ''}`,
|
|
609
|
+
'LinkedIn — Connected',
|
|
610
|
+
);
|
|
611
|
+
const relink = await p.confirm({ message: 'Re-link account?', initialValue: false });
|
|
612
|
+
if (handleCancel(relink) || !relink) return;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
p.note(
|
|
616
|
+
'1. Go to https://www.linkedin.com/developers/tools/oauth/token-generator\n' +
|
|
617
|
+
'2. Select your app, pick scopes: openid, profile, email, w_member_social\n' +
|
|
618
|
+
'3. Authorize and copy the token',
|
|
619
|
+
'Link LinkedIn Account',
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
const token = await p.text({ message: 'Paste access token' });
|
|
623
|
+
if (handleCancel(token) || !token.trim()) return;
|
|
624
|
+
|
|
625
|
+
const s = p.spinner();
|
|
626
|
+
s.start('Validating token...');
|
|
627
|
+
|
|
628
|
+
try {
|
|
629
|
+
const res = await fetch('https://api.linkedin.com/v2/userinfo', {
|
|
630
|
+
headers: { 'Authorization': `Bearer ${token.trim()}` },
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
if (res.ok) {
|
|
634
|
+
const profile = await res.json();
|
|
635
|
+
const personUrn = `urn:li:person:${profile.sub}`;
|
|
636
|
+
|
|
637
|
+
saveCredential(config, 'LINKEDIN_ACCESS_TOKEN', token.trim());
|
|
638
|
+
saveCredential(config, 'LINKEDIN_PERSON_URN', personUrn);
|
|
639
|
+
|
|
640
|
+
s.stop('LinkedIn linked');
|
|
641
|
+
p.log.info(`Name: ${profile.name}${profile.email ? ` | Email: ${profile.email}` : ''}\nURN: ${personUrn}`);
|
|
642
|
+
} else if (res.status === 401) {
|
|
643
|
+
throw new Error('Invalid or expired token.');
|
|
644
|
+
} else {
|
|
645
|
+
s.stop('Token accepted (profile scopes missing)');
|
|
646
|
+
p.log.warn(
|
|
647
|
+
'To auto-detect your URN, add "Sign in with LinkedIn using OpenID Connect"\n' +
|
|
648
|
+
'to your app at https://www.linkedin.com/developers/apps',
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
const urn = await p.text({
|
|
652
|
+
message: 'Person URN (urn:li:person:XXXXX)',
|
|
653
|
+
placeholder: 'urn:li:person:...',
|
|
654
|
+
});
|
|
655
|
+
if (handleCancel(urn) || !urn.trim()) {
|
|
656
|
+
p.log.warn('No URN provided. Token saved but LinkedIn posts will not work without a URN.');
|
|
657
|
+
saveCredential(config, 'LINKEDIN_ACCESS_TOKEN', token.trim());
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const personUrn = urn.trim().startsWith('urn:li:person:') ? urn.trim() : `urn:li:person:${urn.trim()}`;
|
|
662
|
+
saveCredential(config, 'LINKEDIN_ACCESS_TOKEN', token.trim());
|
|
663
|
+
saveCredential(config, 'LINKEDIN_PERSON_URN', personUrn);
|
|
664
|
+
|
|
665
|
+
p.log.success(`LinkedIn linked — URN: ${personUrn}`);
|
|
666
|
+
}
|
|
667
|
+
} catch (err) {
|
|
668
|
+
s.stop(chalk.red(`Token validation failed: ${err.message}`));
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async function manageDashboard(config) {
|
|
673
|
+
const dashEnabled = config.dashboard?.enabled;
|
|
674
|
+
const dashPort = config.dashboard?.port || 3000;
|
|
675
|
+
|
|
676
|
+
p.note(
|
|
677
|
+
`Auto-start: ${dashEnabled ? chalk.green('yes') : chalk.yellow('no')}\n` +
|
|
678
|
+
`Port: ${dashPort}\n` +
|
|
679
|
+
`URL: http://localhost:${dashPort}`,
|
|
680
|
+
'Dashboard',
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
const choice = await p.select({
|
|
684
|
+
message: 'Dashboard settings',
|
|
685
|
+
options: [
|
|
686
|
+
{ value: 'toggle', label: `${dashEnabled ? 'Disable' : 'Enable'} auto-start on boot` },
|
|
687
|
+
{ value: 'port', label: 'Change port', hint: String(dashPort) },
|
|
688
|
+
{ value: 'back', label: 'Back' },
|
|
689
|
+
],
|
|
690
|
+
});
|
|
691
|
+
if (handleCancel(choice) || choice === 'back') return;
|
|
692
|
+
|
|
693
|
+
if (choice === 'toggle') {
|
|
694
|
+
const newEnabled = !dashEnabled;
|
|
695
|
+
saveDashboardToYaml({ enabled: newEnabled });
|
|
696
|
+
config.dashboard.enabled = newEnabled;
|
|
697
|
+
p.log.success(`Dashboard auto-start ${newEnabled ? 'enabled' : 'disabled'}`);
|
|
698
|
+
if (newEnabled) {
|
|
699
|
+
p.log.info(`Dashboard will start at http://localhost:${dashPort} on next bot launch.`);
|
|
700
|
+
}
|
|
701
|
+
} else if (choice === 'port') {
|
|
702
|
+
const portInput = await p.text({
|
|
703
|
+
message: 'New port',
|
|
704
|
+
placeholder: String(dashPort),
|
|
705
|
+
validate: (v) => {
|
|
706
|
+
const n = parseInt(v.trim(), 10);
|
|
707
|
+
if (!n || n < 1 || n > 65535) return 'Enter a valid port (1-65535)';
|
|
708
|
+
},
|
|
709
|
+
});
|
|
710
|
+
if (handleCancel(portInput)) return;
|
|
711
|
+
const newPort = parseInt(portInput.trim(), 10);
|
|
712
|
+
saveDashboardToYaml({ port: newPort });
|
|
713
|
+
config.dashboard.port = newPort;
|
|
714
|
+
p.log.success(`Dashboard port set to ${newPort}`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
508
718
|
async function main() {
|
|
719
|
+
const headless = process.argv.includes('--start');
|
|
720
|
+
|
|
509
721
|
showLogo();
|
|
510
722
|
|
|
511
|
-
const config = await loadConfigInteractive();
|
|
723
|
+
const config = headless ? loadConfig() : await loadConfigInteractive();
|
|
512
724
|
createLogger(config);
|
|
513
725
|
|
|
514
|
-
|
|
726
|
+
// --start flag: skip menu, launch bot directly (for systemd / headless)
|
|
727
|
+
if (headless) {
|
|
728
|
+
const started = await startBotFlow(config);
|
|
729
|
+
if (!started) process.exit(1);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Show welcome screen with system info
|
|
734
|
+
const characterManager = new CharacterManager();
|
|
735
|
+
characterManager.installAllBuiltins();
|
|
736
|
+
showWelcomeScreen(config, characterManager);
|
|
515
737
|
|
|
516
738
|
let running = true;
|
|
517
739
|
while (running) {
|
|
518
|
-
|
|
519
|
-
const
|
|
740
|
+
const brainHint = formatProviderLabel(config, 'brain');
|
|
741
|
+
const orchHint = formatProviderLabel(config, 'orchestrator');
|
|
742
|
+
|
|
743
|
+
const choice = await p.select({
|
|
744
|
+
message: 'What would you like to do?',
|
|
745
|
+
options: [
|
|
746
|
+
{ value: 'start', label: 'Start bot' },
|
|
747
|
+
{ value: 'check', label: 'Check connections' },
|
|
748
|
+
{ value: 'logs', label: 'View logs' },
|
|
749
|
+
{ value: 'audit', label: 'View audit logs' },
|
|
750
|
+
{ value: 'brain', label: 'Change brain model', hint: brainHint },
|
|
751
|
+
{ value: 'orch', label: 'Change orchestrator model', hint: orchHint },
|
|
752
|
+
{ value: 'skills', label: 'Manage custom skills' },
|
|
753
|
+
{ value: 'automations', label: 'Manage automations' },
|
|
754
|
+
{ value: 'characters', label: 'Switch character' },
|
|
755
|
+
{ value: 'linkedin', label: 'Link LinkedIn account' },
|
|
756
|
+
{ value: 'dashboard', label: 'Dashboard settings' },
|
|
757
|
+
{ value: 'exit', label: 'Exit' },
|
|
758
|
+
],
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
if (handleCancel(choice)) {
|
|
762
|
+
running = false;
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
520
765
|
|
|
521
|
-
switch (choice
|
|
522
|
-
case '
|
|
523
|
-
rl.close();
|
|
766
|
+
switch (choice) {
|
|
767
|
+
case 'start': {
|
|
524
768
|
const started = await startBotFlow(config);
|
|
525
769
|
if (!started) process.exit(1);
|
|
526
|
-
return;
|
|
770
|
+
return;
|
|
527
771
|
}
|
|
528
|
-
case '
|
|
772
|
+
case 'check':
|
|
529
773
|
await runCheck(config);
|
|
530
774
|
break;
|
|
531
|
-
case '
|
|
775
|
+
case 'logs':
|
|
532
776
|
viewLog('kernel.log');
|
|
533
777
|
break;
|
|
534
|
-
case '
|
|
778
|
+
case 'audit':
|
|
535
779
|
viewLog('kernel-audit.log');
|
|
536
780
|
break;
|
|
537
|
-
case '
|
|
538
|
-
await changeBrainModel(config
|
|
781
|
+
case 'brain':
|
|
782
|
+
await changeBrainModel(config);
|
|
783
|
+
break;
|
|
784
|
+
case 'orch':
|
|
785
|
+
await changeOrchestratorModel(config);
|
|
786
|
+
break;
|
|
787
|
+
case 'skills':
|
|
788
|
+
await manageCustomSkills();
|
|
789
|
+
break;
|
|
790
|
+
case 'automations':
|
|
791
|
+
await manageAutomations();
|
|
539
792
|
break;
|
|
540
|
-
case '
|
|
541
|
-
await
|
|
793
|
+
case 'characters':
|
|
794
|
+
await manageCharacters(config);
|
|
542
795
|
break;
|
|
543
|
-
case '
|
|
544
|
-
await
|
|
796
|
+
case 'linkedin':
|
|
797
|
+
await linkLinkedInCli(config);
|
|
545
798
|
break;
|
|
546
|
-
case '
|
|
547
|
-
await
|
|
799
|
+
case 'dashboard':
|
|
800
|
+
await manageDashboard(config);
|
|
548
801
|
break;
|
|
549
|
-
case '
|
|
802
|
+
case 'exit':
|
|
550
803
|
running = false;
|
|
551
804
|
break;
|
|
552
|
-
default:
|
|
553
|
-
console.log(chalk.dim(' Invalid choice.\n'));
|
|
554
805
|
}
|
|
555
806
|
}
|
|
556
807
|
|
|
557
|
-
|
|
558
|
-
console.log(chalk.dim(' Goodbye.\n'));
|
|
808
|
+
p.outro('Goodbye.');
|
|
559
809
|
}
|
|
560
810
|
|
|
561
811
|
main().catch((err) => {
|