kernelbot 1.0.38 → 1.0.40
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 +335 -451
- package/config.example.yaml +1 -1
- package/knowledge_base/active_inference_foraging.md +126 -0
- package/knowledge_base/index.md +1 -1
- package/package.json +2 -1
- package/skills/business/business-analyst.md +32 -0
- package/skills/business/product-manager.md +32 -0
- package/skills/business/project-manager.md +32 -0
- package/skills/business/startup-advisor.md +32 -0
- package/skills/creative/music-producer.md +32 -0
- package/skills/creative/photographer.md +32 -0
- package/skills/creative/video-producer.md +32 -0
- package/skills/data/bi-analyst.md +37 -0
- package/skills/data/data-scientist.md +38 -0
- package/skills/data/ml-engineer.md +38 -0
- package/skills/design/graphic-designer.md +38 -0
- package/skills/design/product-designer.md +41 -0
- package/skills/design/ui-ux.md +38 -0
- package/skills/education/curriculum-designer.md +32 -0
- package/skills/education/language-teacher.md +32 -0
- package/skills/education/tutor.md +32 -0
- package/skills/engineering/data-eng.md +55 -0
- package/skills/engineering/devops.md +56 -0
- package/skills/engineering/mobile-dev.md +55 -0
- package/skills/engineering/security-eng.md +55 -0
- package/skills/engineering/sr-backend.md +55 -0
- package/skills/engineering/sr-frontend.md +55 -0
- package/skills/finance/accountant.md +35 -0
- package/skills/finance/crypto-defi.md +39 -0
- package/skills/finance/financial-analyst.md +35 -0
- package/skills/healthcare/health-wellness.md +32 -0
- package/skills/healthcare/medical-researcher.md +33 -0
- package/skills/legal/contract-reviewer.md +35 -0
- package/skills/legal/legal-advisor.md +36 -0
- package/skills/marketing/content-marketer.md +38 -0
- package/skills/marketing/growth.md +38 -0
- package/skills/marketing/seo.md +43 -0
- package/skills/marketing/social-media.md +43 -0
- package/skills/writing/academic-writer.md +33 -0
- package/skills/writing/copywriter.md +32 -0
- package/skills/writing/creative-writer.md +32 -0
- package/skills/writing/tech-writer.md +33 -0
- package/src/agent.js +153 -118
- package/src/automation/scheduler.js +36 -3
- package/src/bot.js +147 -64
- package/src/coder.js +30 -8
- package/src/conversation.js +96 -19
- package/src/dashboard/dashboard.css +6 -0
- package/src/dashboard/dashboard.js +28 -1
- package/src/dashboard/index.html +12 -0
- package/src/dashboard/server.js +77 -15
- package/src/dashboard/shared.js +10 -1
- package/src/life/codebase.js +2 -1
- package/src/life/daydream_engine.js +386 -0
- package/src/life/engine.js +88 -6
- package/src/life/evolution.js +4 -3
- package/src/prompts/orchestrator.js +1 -1
- package/src/prompts/system.js +1 -1
- package/src/prompts/workers.js +8 -1
- package/src/providers/anthropic.js +3 -1
- package/src/providers/base.js +33 -0
- package/src/providers/index.js +1 -1
- package/src/providers/models.js +22 -0
- package/src/providers/openai-compat.js +3 -0
- package/src/services/x-api.js +14 -3
- package/src/skills/loader.js +382 -0
- package/src/swarm/worker-registry.js +2 -2
- package/src/tools/browser.js +10 -3
- package/src/tools/coding.js +16 -0
- package/src/tools/docker.js +13 -0
- package/src/tools/git.js +31 -29
- package/src/tools/jira.js +11 -2
- package/src/tools/monitor.js +9 -1
- package/src/tools/network.js +34 -0
- package/src/tools/orchestrator-tools.js +2 -1
- package/src/tools/os.js +20 -6
- package/src/utils/config.js +87 -83
- package/src/utils/display.js +118 -66
- package/src/utils/logger.js +1 -1
- package/src/utils/timeAwareness.js +72 -0
- package/src/worker.js +26 -33
- package/src/skills/catalog.js +0 -506
- package/src/skills/custom.js +0 -128
package/bin/kernel.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
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';
|
|
11
|
+
import * as p from '@clack/prompts';
|
|
12
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 {
|
|
@@ -16,8 +16,10 @@ import {
|
|
|
16
16
|
showStartupCheck,
|
|
17
17
|
showStartupComplete,
|
|
18
18
|
showError,
|
|
19
|
-
showCharacterGallery,
|
|
20
19
|
showCharacterCard,
|
|
20
|
+
showWelcomeScreen,
|
|
21
|
+
handleCancel,
|
|
22
|
+
formatProviderLabel,
|
|
21
23
|
} from '../src/utils/display.js';
|
|
22
24
|
import { createAuditLogger } from '../src/security/audit.js';
|
|
23
25
|
import { CharacterBuilder } from '../src/characters/builder.js';
|
|
@@ -32,118 +34,40 @@ import { CodebaseKnowledge } from '../src/life/codebase.js';
|
|
|
32
34
|
import { LifeEngine } from '../src/life/engine.js';
|
|
33
35
|
import { CharacterManager } from '../src/character.js';
|
|
34
36
|
import {
|
|
35
|
-
|
|
37
|
+
loadAllSkills,
|
|
36
38
|
getCustomSkills,
|
|
37
|
-
|
|
39
|
+
saveCustomSkill,
|
|
38
40
|
deleteCustomSkill,
|
|
39
|
-
} from '../src/skills/
|
|
40
|
-
|
|
41
|
-
function showMenu(config) {
|
|
42
|
-
const orchProviderDef = PROVIDERS[config.orchestrator.provider];
|
|
43
|
-
const orchProviderName = orchProviderDef ? orchProviderDef.name : config.orchestrator.provider;
|
|
44
|
-
const orchModelId = config.orchestrator.model;
|
|
45
|
-
|
|
46
|
-
const providerDef = PROVIDERS[config.brain.provider];
|
|
47
|
-
const providerName = providerDef ? providerDef.name : config.brain.provider;
|
|
48
|
-
const modelId = config.brain.model;
|
|
49
|
-
|
|
50
|
-
console.log('');
|
|
51
|
-
console.log(chalk.dim(` Current orchestrator: ${orchProviderName} / ${orchModelId}`));
|
|
52
|
-
console.log(chalk.dim(` Current brain: ${providerName} / ${modelId}`));
|
|
53
|
-
console.log('');
|
|
54
|
-
console.log(chalk.bold(' What would you like to do?\n'));
|
|
55
|
-
console.log(` ${chalk.cyan('1.')} Start bot`);
|
|
56
|
-
console.log(` ${chalk.cyan('2.')} Check connections`);
|
|
57
|
-
console.log(` ${chalk.cyan('3.')} View logs`);
|
|
58
|
-
console.log(` ${chalk.cyan('4.')} View audit logs`);
|
|
59
|
-
console.log(` ${chalk.cyan('5.')} Change brain model`);
|
|
60
|
-
console.log(` ${chalk.cyan('6.')} Change orchestrator model`);
|
|
61
|
-
console.log(` ${chalk.cyan('7.')} Manage custom skills`);
|
|
62
|
-
console.log(` ${chalk.cyan('8.')} Manage automations`);
|
|
63
|
-
console.log(` ${chalk.cyan('9.')} Switch character`);
|
|
64
|
-
console.log(` ${chalk.cyan('10.')} Link LinkedIn account`);
|
|
65
|
-
console.log(` ${chalk.cyan('11.')} Dashboard`);
|
|
66
|
-
console.log(` ${chalk.cyan('12.')} Exit`);
|
|
67
|
-
console.log('');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function ask(rl, question) {
|
|
71
|
-
return new Promise((res) => rl.question(question, res));
|
|
72
|
-
}
|
|
41
|
+
} from '../src/skills/loader.js';
|
|
73
42
|
|
|
74
43
|
/**
|
|
75
44
|
* Register SIGINT/SIGTERM handlers to shut down the bot cleanly.
|
|
76
|
-
* Stops polling, cancels running jobs, persists conversations,
|
|
77
|
-
* disarms automations, stops the life engine, and clears intervals.
|
|
78
45
|
*/
|
|
79
46
|
function setupGracefulShutdown({ bot, lifeEngine, automationManager, jobManager, conversationManager, intervals, dashboardHandle }) {
|
|
80
47
|
let shuttingDown = false;
|
|
81
48
|
|
|
82
49
|
const shutdown = async (signal) => {
|
|
83
|
-
if (shuttingDown) return;
|
|
50
|
+
if (shuttingDown) return;
|
|
84
51
|
shuttingDown = true;
|
|
85
52
|
|
|
86
53
|
const logger = getLogger();
|
|
87
54
|
logger.info(`[Shutdown] ${signal} received — shutting down gracefully...`);
|
|
88
55
|
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
|
|
92
|
-
logger.info('[Shutdown] Telegram polling stopped');
|
|
93
|
-
} catch (err) {
|
|
94
|
-
logger.error(`[Shutdown] Failed to stop polling: ${err.message}`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// 2. Stop life engine heartbeat
|
|
98
|
-
try {
|
|
99
|
-
lifeEngine.stop();
|
|
100
|
-
logger.info('[Shutdown] Life engine stopped');
|
|
101
|
-
} catch (err) {
|
|
102
|
-
logger.error(`[Shutdown] Failed to stop life engine: ${err.message}`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 3. Disarm all automation timers
|
|
106
|
-
try {
|
|
107
|
-
automationManager.shutdown();
|
|
108
|
-
logger.info('[Shutdown] Automation timers cancelled');
|
|
109
|
-
} catch (err) {
|
|
110
|
-
logger.error(`[Shutdown] Failed to shutdown automations: ${err.message}`);
|
|
111
|
-
}
|
|
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}`); }
|
|
112
59
|
|
|
113
|
-
// 4. Cancel all running jobs
|
|
114
60
|
try {
|
|
115
61
|
const running = [...jobManager.jobs.values()].filter(j => !j.isTerminal);
|
|
116
|
-
for (const job of running)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (running.length > 0) {
|
|
120
|
-
logger.info(`[Shutdown] Cancelled ${running.length} running job(s)`);
|
|
121
|
-
}
|
|
122
|
-
} catch (err) {
|
|
123
|
-
logger.error(`[Shutdown] Failed to cancel jobs: ${err.message}`);
|
|
124
|
-
}
|
|
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}`); }
|
|
125
65
|
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
conversationManager.save();
|
|
129
|
-
logger.info('[Shutdown] Conversations saved');
|
|
130
|
-
} catch (err) {
|
|
131
|
-
logger.error(`[Shutdown] Failed to save conversations: ${err.message}`);
|
|
132
|
-
}
|
|
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}`); }
|
|
133
68
|
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
dashboardHandle?.stop();
|
|
137
|
-
} catch (err) {
|
|
138
|
-
logger.error(`[Shutdown] Failed to stop dashboard: ${err.message}`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// 7. Clear periodic intervals
|
|
142
|
-
for (const id of intervals) {
|
|
143
|
-
clearInterval(id);
|
|
144
|
-
}
|
|
69
|
+
for (const id of intervals) clearInterval(id);
|
|
145
70
|
logger.info('[Shutdown] Periodic timers cleared');
|
|
146
|
-
|
|
147
71
|
logger.info('[Shutdown] Graceful shutdown complete');
|
|
148
72
|
process.exit(0);
|
|
149
73
|
};
|
|
@@ -158,33 +82,33 @@ function viewLog(filename) {
|
|
|
158
82
|
join(homedir(), '.kernelbot', filename),
|
|
159
83
|
];
|
|
160
84
|
|
|
161
|
-
for (const
|
|
162
|
-
if (existsSync(
|
|
163
|
-
const content = readFileSync(
|
|
85
|
+
for (const logPath of paths) {
|
|
86
|
+
if (existsSync(logPath)) {
|
|
87
|
+
const content = readFileSync(logPath, 'utf-8');
|
|
164
88
|
const lines = content.split('\n').filter(Boolean);
|
|
165
89
|
const recent = lines.slice(-30);
|
|
166
|
-
|
|
167
|
-
|
|
90
|
+
|
|
91
|
+
const formatted = recent.map(line => {
|
|
168
92
|
try {
|
|
169
93
|
const entry = JSON.parse(line);
|
|
170
94
|
const time = entry.timestamp || '';
|
|
171
95
|
const level = entry.level || '';
|
|
172
96
|
const msg = entry.message || '';
|
|
173
97
|
const color = level === 'error' ? chalk.red : level === 'warn' ? chalk.yellow : chalk.dim;
|
|
174
|
-
|
|
98
|
+
return `${chalk.dim(time)} ${color(level)} ${msg}`;
|
|
175
99
|
} catch {
|
|
176
|
-
|
|
100
|
+
return line;
|
|
177
101
|
}
|
|
178
|
-
}
|
|
179
|
-
|
|
102
|
+
}).join('\n');
|
|
103
|
+
|
|
104
|
+
p.note(formatted, `Last ${recent.length} entries from ${logPath}`);
|
|
180
105
|
return;
|
|
181
106
|
}
|
|
182
107
|
}
|
|
183
|
-
|
|
108
|
+
p.log.info(`No ${filename} found yet.`);
|
|
184
109
|
}
|
|
185
110
|
|
|
186
111
|
async function runCheck(config) {
|
|
187
|
-
// Orchestrator check
|
|
188
112
|
const orchProviderKey = config.orchestrator.provider || 'anthropic';
|
|
189
113
|
const orchProviderDef = PROVIDERS[orchProviderKey];
|
|
190
114
|
const orchLabel = orchProviderDef ? orchProviderDef.name : orchProviderKey;
|
|
@@ -207,7 +131,6 @@ async function runCheck(config) {
|
|
|
207
131
|
await provider.ping();
|
|
208
132
|
});
|
|
209
133
|
|
|
210
|
-
// Worker brain check
|
|
211
134
|
const providerDef = PROVIDERS[config.brain.provider];
|
|
212
135
|
const providerLabel = providerDef ? providerDef.name : config.brain.provider;
|
|
213
136
|
const envKeyLabel = providerDef ? providerDef.envKey : 'API_KEY';
|
|
@@ -226,14 +149,12 @@ async function runCheck(config) {
|
|
|
226
149
|
});
|
|
227
150
|
|
|
228
151
|
await showStartupCheck('Telegram Bot API', async () => {
|
|
229
|
-
const res = await fetch(
|
|
230
|
-
`https://api.telegram.org/bot${config.telegram.bot_token}/getMe`,
|
|
231
|
-
);
|
|
152
|
+
const res = await fetch(`https://api.telegram.org/bot${config.telegram.bot_token}/getMe`);
|
|
232
153
|
const data = await res.json();
|
|
233
154
|
if (!data.ok) throw new Error(data.description || 'Invalid token');
|
|
234
155
|
});
|
|
235
156
|
|
|
236
|
-
|
|
157
|
+
p.log.success('All checks passed.');
|
|
237
158
|
}
|
|
238
159
|
|
|
239
160
|
async function startBotFlow(config) {
|
|
@@ -245,7 +166,6 @@ async function startBotFlow(config) {
|
|
|
245
166
|
|
|
246
167
|
const checks = [];
|
|
247
168
|
|
|
248
|
-
// Orchestrator check — dynamic provider
|
|
249
169
|
const orchProviderKey = config.orchestrator.provider || 'anthropic';
|
|
250
170
|
const orchProviderDef = PROVIDERS[orchProviderKey];
|
|
251
171
|
const orchLabel = orchProviderDef ? orchProviderDef.name : orchProviderKey;
|
|
@@ -267,7 +187,6 @@ async function startBotFlow(config) {
|
|
|
267
187
|
}),
|
|
268
188
|
);
|
|
269
189
|
|
|
270
|
-
// Worker brain check
|
|
271
190
|
checks.push(
|
|
272
191
|
await showStartupCheck(`Worker (${providerLabel}) API`, async () => {
|
|
273
192
|
const provider = createProvider(config);
|
|
@@ -277,9 +196,7 @@ async function startBotFlow(config) {
|
|
|
277
196
|
|
|
278
197
|
checks.push(
|
|
279
198
|
await showStartupCheck('Telegram Bot API', async () => {
|
|
280
|
-
const res = await fetch(
|
|
281
|
-
`https://api.telegram.org/bot${config.telegram.bot_token}/getMe`,
|
|
282
|
-
);
|
|
199
|
+
const res = await fetch(`https://api.telegram.org/bot${config.telegram.bot_token}/getMe`);
|
|
283
200
|
const data = await res.json();
|
|
284
201
|
if (!data.ok) throw new Error(data.description || 'Invalid token');
|
|
285
202
|
}),
|
|
@@ -290,11 +207,7 @@ async function startBotFlow(config) {
|
|
|
290
207
|
return false;
|
|
291
208
|
}
|
|
292
209
|
|
|
293
|
-
// Character system — manages multiple personas with isolated data
|
|
294
210
|
const characterManager = new CharacterManager();
|
|
295
|
-
|
|
296
|
-
// Install built-in characters if needed (fresh install or missing builtins).
|
|
297
|
-
// Onboarding flag stays true until user picks a character via Telegram.
|
|
298
211
|
if (characterManager.needsOnboarding) {
|
|
299
212
|
characterManager.installAllBuiltins();
|
|
300
213
|
}
|
|
@@ -310,8 +223,6 @@ async function startBotFlow(config) {
|
|
|
310
223
|
});
|
|
311
224
|
|
|
312
225
|
const automationManager = new AutomationManager();
|
|
313
|
-
|
|
314
|
-
// Life system managers — scoped to active character
|
|
315
226
|
const codebaseKnowledge = new CodebaseKnowledge({ config });
|
|
316
227
|
|
|
317
228
|
const agent = new Agent({
|
|
@@ -323,13 +234,9 @@ async function startBotFlow(config) {
|
|
|
323
234
|
characterManager,
|
|
324
235
|
});
|
|
325
236
|
|
|
326
|
-
// Load character context into agent (sets persona, name, etc.)
|
|
327
237
|
agent.loadCharacter(activeCharacterId);
|
|
328
|
-
|
|
329
|
-
// Wire codebase knowledge to agent for LLM-powered scanning
|
|
330
238
|
codebaseKnowledge.setAgent(agent);
|
|
331
239
|
|
|
332
|
-
// Life Engine — autonomous inner life (scoped to active character)
|
|
333
240
|
const lifeEngine = new LifeEngine({
|
|
334
241
|
config, agent,
|
|
335
242
|
memoryManager: charCtx.memoryManager,
|
|
@@ -351,7 +258,6 @@ async function startBotFlow(config) {
|
|
|
351
258
|
selfManager: charCtx.selfManager,
|
|
352
259
|
};
|
|
353
260
|
|
|
354
|
-
// Optional cyberpunk terminal dashboard (must init before startBot so handle is available)
|
|
355
261
|
let dashboardHandle = null;
|
|
356
262
|
if (config.dashboard?.enabled) {
|
|
357
263
|
const { startDashboard } = await import('../src/dashboard/server.js');
|
|
@@ -371,14 +277,12 @@ async function startBotFlow(config) {
|
|
|
371
277
|
dashboardDeps,
|
|
372
278
|
});
|
|
373
279
|
|
|
374
|
-
// Periodic job cleanup and timeout enforcement
|
|
375
280
|
const cleanupMs = (config.swarm.cleanup_interval_minutes || 30) * 60 * 1000;
|
|
376
281
|
const cleanupInterval = setInterval(() => {
|
|
377
282
|
jobManager.cleanup();
|
|
378
283
|
jobManager.enforceTimeouts();
|
|
379
|
-
}, Math.min(cleanupMs, 60_000));
|
|
284
|
+
}, Math.min(cleanupMs, 60_000));
|
|
380
285
|
|
|
381
|
-
// Periodic memory pruning (daily)
|
|
382
286
|
const retentionDays = config.life?.memory_retention_days || 90;
|
|
383
287
|
const pruneInterval = setInterval(() => {
|
|
384
288
|
charCtx.memoryManager.pruneOld(retentionDays);
|
|
@@ -387,7 +291,6 @@ async function startBotFlow(config) {
|
|
|
387
291
|
|
|
388
292
|
showStartupComplete();
|
|
389
293
|
|
|
390
|
-
// Start life engine if enabled
|
|
391
294
|
const lifeEnabled = config.life?.enabled !== false;
|
|
392
295
|
if (lifeEnabled) {
|
|
393
296
|
logger.info('[Startup] Life engine enabled — waking up...');
|
|
@@ -396,10 +299,9 @@ async function startBotFlow(config) {
|
|
|
396
299
|
logger.info('[Startup] Life engine running');
|
|
397
300
|
}).catch(err => {
|
|
398
301
|
logger.error(`[Startup] Life engine wake-up failed: ${err.message}`);
|
|
399
|
-
lifeEngine.start();
|
|
302
|
+
lifeEngine.start();
|
|
400
303
|
});
|
|
401
304
|
|
|
402
|
-
// Initial codebase scan (background, non-blocking)
|
|
403
305
|
if (config.life?.self_coding?.enabled) {
|
|
404
306
|
codebaseKnowledge.scanChanged().then(count => {
|
|
405
307
|
if (count > 0) logger.info(`[Startup] Codebase scan: ${count} files indexed`);
|
|
@@ -411,7 +313,6 @@ async function startBotFlow(config) {
|
|
|
411
313
|
logger.info('[Startup] Life engine disabled');
|
|
412
314
|
}
|
|
413
315
|
|
|
414
|
-
// Register graceful shutdown handlers
|
|
415
316
|
setupGracefulShutdown({
|
|
416
317
|
bot, lifeEngine, automationManager, jobManager,
|
|
417
318
|
conversationManager, intervals: [cleanupInterval, pruneInterval],
|
|
@@ -421,155 +322,134 @@ async function startBotFlow(config) {
|
|
|
421
322
|
return true;
|
|
422
323
|
}
|
|
423
324
|
|
|
424
|
-
async function manageCustomSkills(
|
|
425
|
-
|
|
325
|
+
async function manageCustomSkills() {
|
|
326
|
+
loadAllSkills();
|
|
426
327
|
|
|
427
328
|
let managing = true;
|
|
428
329
|
while (managing) {
|
|
429
330
|
const customs = getCustomSkills();
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
console.log(chalk.dim(' Empty prompt — cancelled.\n'));
|
|
456
|
-
break;
|
|
457
|
-
}
|
|
458
|
-
const skill = addCustomSkill({ name: name.trim(), systemPrompt: prompt });
|
|
459
|
-
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 = saveCustomSkill({ name: name.trim(), body: prompt.trim() });
|
|
355
|
+
p.log.success(`Created: ${skill.name} (${skill.id})`);
|
|
460
356
|
break;
|
|
461
357
|
}
|
|
462
|
-
case '
|
|
463
|
-
const customs = getCustomSkills();
|
|
358
|
+
case 'list': {
|
|
464
359
|
if (!customs.length) {
|
|
465
|
-
|
|
360
|
+
p.log.info('No custom skills yet.');
|
|
466
361
|
break;
|
|
467
362
|
}
|
|
468
|
-
|
|
469
|
-
for (const s of customs) {
|
|
363
|
+
const formatted = customs.map(s => {
|
|
470
364
|
const preview = s.systemPrompt.slice(0, 60).replace(/\n/g, ' ');
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
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');
|
|
475
368
|
break;
|
|
476
369
|
}
|
|
477
|
-
case '
|
|
478
|
-
const customs = getCustomSkills();
|
|
370
|
+
case 'delete': {
|
|
479
371
|
if (!customs.length) {
|
|
480
|
-
|
|
372
|
+
p.log.info('No custom skills to delete.');
|
|
481
373
|
break;
|
|
482
374
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
+
],
|
|
486
381
|
});
|
|
487
|
-
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
if (idx >= 0 && idx < customs.length) {
|
|
491
|
-
const deleted = deleteCustomSkill(customs[idx].id);
|
|
492
|
-
if (deleted) console.log(chalk.green(`\n 🗑️ Deleted: ${customs[idx].name}\n`));
|
|
493
|
-
else console.log(chalk.dim(' Not found.\n'));
|
|
494
|
-
} else {
|
|
495
|
-
console.log(chalk.dim(' Cancelled.\n'));
|
|
496
|
-
}
|
|
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}`);
|
|
497
385
|
break;
|
|
498
386
|
}
|
|
499
|
-
case '
|
|
387
|
+
case 'back':
|
|
500
388
|
managing = false;
|
|
501
389
|
break;
|
|
502
|
-
default:
|
|
503
|
-
console.log(chalk.dim(' Invalid choice.\n'));
|
|
504
390
|
}
|
|
505
391
|
}
|
|
506
392
|
}
|
|
507
393
|
|
|
508
|
-
async function manageAutomations(
|
|
394
|
+
async function manageAutomations() {
|
|
509
395
|
const manager = new AutomationManager();
|
|
510
396
|
|
|
511
397
|
let managing = true;
|
|
512
398
|
while (managing) {
|
|
513
399
|
const autos = manager.listAll();
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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': {
|
|
524
413
|
if (!autos.length) {
|
|
525
|
-
|
|
414
|
+
p.log.info('No automations found.');
|
|
526
415
|
break;
|
|
527
416
|
}
|
|
528
|
-
|
|
529
|
-
for (const a of autos) {
|
|
417
|
+
const formatted = autos.map(a => {
|
|
530
418
|
const status = a.enabled ? chalk.green('enabled') : chalk.yellow('paused');
|
|
531
419
|
const next = a.nextRun ? new Date(a.nextRun).toLocaleString() : 'not scheduled';
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
536
|
-
|
|
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');
|
|
537
425
|
break;
|
|
538
426
|
}
|
|
539
|
-
case '
|
|
427
|
+
case 'delete': {
|
|
540
428
|
if (!autos.length) {
|
|
541
|
-
|
|
429
|
+
p.log.info('No automations to delete.');
|
|
542
430
|
break;
|
|
543
431
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
+
],
|
|
547
438
|
});
|
|
548
|
-
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
if (idx >= 0 && idx < autos.length) {
|
|
552
|
-
const deleted = manager.delete(autos[idx].id);
|
|
553
|
-
if (deleted) console.log(chalk.green(`\n 🗑️ Deleted: ${autos[idx].name}\n`));
|
|
554
|
-
else console.log(chalk.dim(' Not found.\n'));
|
|
555
|
-
} else {
|
|
556
|
-
console.log(chalk.dim(' Cancelled.\n'));
|
|
557
|
-
}
|
|
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}`);
|
|
558
442
|
break;
|
|
559
443
|
}
|
|
560
|
-
case '
|
|
444
|
+
case 'back':
|
|
561
445
|
managing = false;
|
|
562
446
|
break;
|
|
563
|
-
default:
|
|
564
|
-
console.log(chalk.dim(' Invalid choice.\n'));
|
|
565
447
|
}
|
|
566
448
|
}
|
|
567
449
|
}
|
|
568
450
|
|
|
569
|
-
async function manageCharacters(
|
|
451
|
+
async function manageCharacters(config) {
|
|
570
452
|
const charManager = new CharacterManager();
|
|
571
|
-
|
|
572
|
-
// Ensure builtins installed
|
|
573
453
|
charManager.installAllBuiltins();
|
|
574
454
|
|
|
575
455
|
let managing = true;
|
|
@@ -578,49 +458,42 @@ async function manageCharacters(rl, config) {
|
|
|
578
458
|
const activeId = charManager.getActiveCharacterId();
|
|
579
459
|
const active = charManager.getCharacter(activeId);
|
|
580
460
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
switch (choice
|
|
594
|
-
case '
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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
|
+
})),
|
|
600
482
|
});
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
charManager.setActiveCharacter(characters[idx].id);
|
|
606
|
-
console.log(chalk.green(`\n ${characters[idx].emoji} Switched to ${characters[idx].name}\n`));
|
|
607
|
-
} else {
|
|
608
|
-
console.log(chalk.dim(' Cancelled.\n'));
|
|
609
|
-
}
|
|
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}`);
|
|
610
487
|
break;
|
|
611
488
|
}
|
|
612
|
-
case '
|
|
613
|
-
|
|
614
|
-
console.log('');
|
|
615
|
-
console.log(chalk.bold(' Custom Character Builder'));
|
|
616
|
-
console.log(chalk.dim(' Answer a few questions to create your character.\n'));
|
|
489
|
+
case 'create': {
|
|
490
|
+
p.log.step('Custom Character Builder');
|
|
617
491
|
|
|
618
|
-
// Need an LLM provider for generation
|
|
619
492
|
const orchProviderKey = config.orchestrator.provider || 'anthropic';
|
|
620
493
|
const orchProviderDef = PROVIDERS[orchProviderKey];
|
|
621
494
|
const orchApiKey = config.orchestrator.api_key || (orchProviderDef && process.env[orchProviderDef.envKey]);
|
|
622
495
|
if (!orchApiKey) {
|
|
623
|
-
|
|
496
|
+
p.log.error('No API key configured for character generation.');
|
|
624
497
|
break;
|
|
625
498
|
}
|
|
626
499
|
|
|
@@ -638,290 +511,301 @@ async function manageCharacters(rl, config) {
|
|
|
638
511
|
const answers = {};
|
|
639
512
|
let cancelled = false;
|
|
640
513
|
|
|
641
|
-
// Walk through all questions
|
|
642
514
|
let q = builder.getNextQuestion(answers);
|
|
643
515
|
while (q) {
|
|
644
516
|
const progress = builder.getProgress(answers);
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if (answer
|
|
650
|
-
cancelled = true;
|
|
651
|
-
break;
|
|
652
|
-
}
|
|
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; }
|
|
653
522
|
answers[q.id] = answer.trim();
|
|
654
523
|
q = builder.getNextQuestion(answers);
|
|
655
|
-
console.log('');
|
|
656
524
|
}
|
|
657
525
|
|
|
658
|
-
if (cancelled)
|
|
659
|
-
console.log(chalk.dim(' Character creation cancelled.\n'));
|
|
660
|
-
break;
|
|
661
|
-
}
|
|
526
|
+
if (cancelled) break;
|
|
662
527
|
|
|
663
|
-
|
|
528
|
+
const s = p.spinner();
|
|
529
|
+
s.start('Generating character...');
|
|
664
530
|
try {
|
|
665
531
|
const result = await builder.generateCharacter(answers);
|
|
532
|
+
s.stop('Character generated');
|
|
666
533
|
const id = result.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
667
534
|
|
|
668
|
-
// Show preview
|
|
669
535
|
console.log('');
|
|
670
|
-
showCharacterCard({
|
|
671
|
-
...result,
|
|
672
|
-
id,
|
|
673
|
-
origin: 'Custom',
|
|
674
|
-
});
|
|
536
|
+
showCharacterCard({ ...result, id, origin: 'Custom' });
|
|
675
537
|
console.log('');
|
|
676
538
|
|
|
677
|
-
const
|
|
678
|
-
if (
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
result.personaMd,
|
|
682
|
-
result.selfDefaults,
|
|
683
|
-
);
|
|
684
|
-
console.log(chalk.green(`\n ${result.emoji} ${result.name} created!\n`));
|
|
685
|
-
} else {
|
|
686
|
-
console.log(chalk.dim(' Discarded.\n'));
|
|
539
|
+
const install = await p.confirm({ message: 'Install this character?' });
|
|
540
|
+
if (handleCancel(install) || !install) {
|
|
541
|
+
p.log.info('Discarded.');
|
|
542
|
+
break;
|
|
687
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!`);
|
|
688
551
|
} catch (err) {
|
|
689
|
-
|
|
552
|
+
s.stop(chalk.red(`Character generation failed: ${err.message}`));
|
|
690
553
|
}
|
|
691
554
|
break;
|
|
692
555
|
}
|
|
693
|
-
case '
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
+
})),
|
|
697
563
|
});
|
|
698
|
-
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
if (
|
|
702
|
-
|
|
703
|
-
if (characters[idx].evolutionHistory?.length > 0) {
|
|
704
|
-
console.log(chalk.dim(` Evolution events: ${characters[idx].evolutionHistory.length}`));
|
|
705
|
-
}
|
|
706
|
-
console.log('');
|
|
707
|
-
} else {
|
|
708
|
-
console.log(chalk.dim(' Cancelled.\n'));
|
|
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}`);
|
|
709
569
|
}
|
|
710
570
|
break;
|
|
711
571
|
}
|
|
712
|
-
case '
|
|
572
|
+
case 'delete': {
|
|
713
573
|
const customChars = characters.filter(c => c.type === 'custom');
|
|
714
574
|
if (customChars.length === 0) {
|
|
715
|
-
|
|
575
|
+
p.log.info('No custom characters to delete.');
|
|
716
576
|
break;
|
|
717
577
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
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
|
+
],
|
|
721
584
|
});
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
} catch (err) {
|
|
730
|
-
console.log(chalk.red(`\n ${err.message}\n`));
|
|
731
|
-
}
|
|
732
|
-
} else {
|
|
733
|
-
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);
|
|
734
592
|
}
|
|
735
593
|
break;
|
|
736
594
|
}
|
|
737
|
-
case '
|
|
595
|
+
case 'back':
|
|
738
596
|
managing = false;
|
|
739
597
|
break;
|
|
740
|
-
default:
|
|
741
|
-
console.log(chalk.dim(' Invalid choice.\n'));
|
|
742
598
|
}
|
|
743
599
|
}
|
|
744
600
|
}
|
|
745
601
|
|
|
746
|
-
async function linkLinkedInCli(config
|
|
602
|
+
async function linkLinkedInCli(config) {
|
|
747
603
|
const { saveCredential } = await import('../src/utils/config.js');
|
|
748
604
|
|
|
749
|
-
// Show current status
|
|
750
605
|
if (config.linkedin?.access_token) {
|
|
751
606
|
const truncated = `${config.linkedin.access_token.slice(0, 8)}...${config.linkedin.access_token.slice(-4)}`;
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
}
|
|
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;
|
|
759
613
|
}
|
|
760
614
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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
|
+
);
|
|
767
621
|
|
|
768
|
-
const token =
|
|
769
|
-
if (
|
|
770
|
-
console.log(chalk.dim(' Cancelled.\n'));
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
622
|
+
const token = await p.text({ message: 'Paste access token' });
|
|
623
|
+
if (handleCancel(token) || !token.trim()) return;
|
|
773
624
|
|
|
774
|
-
|
|
625
|
+
const s = p.spinner();
|
|
626
|
+
s.start('Validating token...');
|
|
775
627
|
|
|
776
628
|
try {
|
|
777
|
-
// Try /v2/userinfo (requires "Sign in with LinkedIn" product → openid+profile scopes)
|
|
778
629
|
const res = await fetch('https://api.linkedin.com/v2/userinfo', {
|
|
779
|
-
headers: { 'Authorization': `Bearer ${token}` },
|
|
630
|
+
headers: { 'Authorization': `Bearer ${token.trim()}` },
|
|
780
631
|
});
|
|
781
632
|
|
|
782
633
|
if (res.ok) {
|
|
783
634
|
const profile = await res.json();
|
|
784
635
|
const personUrn = `urn:li:person:${profile.sub}`;
|
|
785
636
|
|
|
786
|
-
saveCredential(config, 'LINKEDIN_ACCESS_TOKEN', token);
|
|
637
|
+
saveCredential(config, 'LINKEDIN_ACCESS_TOKEN', token.trim());
|
|
787
638
|
saveCredential(config, 'LINKEDIN_PERSON_URN', personUrn);
|
|
788
639
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
if (profile.email) console.log(chalk.dim(` Email: ${profile.email}`));
|
|
792
|
-
console.log(chalk.dim(` URN: ${personUrn}`));
|
|
793
|
-
console.log('');
|
|
640
|
+
s.stop('LinkedIn linked');
|
|
641
|
+
p.log.info(`Name: ${profile.name}${profile.email ? ` | Email: ${profile.email}` : ''}\nURN: ${personUrn}`);
|
|
794
642
|
} else if (res.status === 401) {
|
|
795
643
|
throw new Error('Invalid or expired token.');
|
|
796
644
|
} else {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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());
|
|
809
658
|
return;
|
|
810
659
|
}
|
|
811
660
|
|
|
812
|
-
const personUrn = urn.startsWith('urn:li:person:') ? urn : `urn:li:person:${urn}`;
|
|
813
|
-
saveCredential(config, 'LINKEDIN_ACCESS_TOKEN', token);
|
|
661
|
+
const personUrn = urn.trim().startsWith('urn:li:person:') ? urn.trim() : `urn:li:person:${urn.trim()}`;
|
|
662
|
+
saveCredential(config, 'LINKEDIN_ACCESS_TOKEN', token.trim());
|
|
814
663
|
saveCredential(config, 'LINKEDIN_PERSON_URN', personUrn);
|
|
815
664
|
|
|
816
|
-
|
|
817
|
-
console.log(chalk.dim(` URN: ${personUrn}`));
|
|
818
|
-
console.log('');
|
|
665
|
+
p.log.success(`LinkedIn linked — URN: ${personUrn}`);
|
|
819
666
|
}
|
|
820
667
|
} catch (err) {
|
|
821
|
-
|
|
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}`);
|
|
822
715
|
}
|
|
823
716
|
}
|
|
824
717
|
|
|
825
718
|
async function main() {
|
|
719
|
+
const headless = process.argv.includes('--start');
|
|
720
|
+
|
|
826
721
|
showLogo();
|
|
827
722
|
|
|
828
|
-
const config = await loadConfigInteractive();
|
|
723
|
+
const config = headless ? loadConfig() : await loadConfigInteractive();
|
|
829
724
|
createLogger(config);
|
|
830
725
|
|
|
831
|
-
|
|
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);
|
|
832
737
|
|
|
833
738
|
let running = true;
|
|
834
739
|
while (running) {
|
|
835
|
-
|
|
836
|
-
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
|
+
}
|
|
837
765
|
|
|
838
|
-
switch (choice
|
|
839
|
-
case '
|
|
840
|
-
rl.close();
|
|
766
|
+
switch (choice) {
|
|
767
|
+
case 'start': {
|
|
841
768
|
const started = await startBotFlow(config);
|
|
842
769
|
if (!started) process.exit(1);
|
|
843
|
-
return;
|
|
770
|
+
return;
|
|
844
771
|
}
|
|
845
|
-
case '
|
|
772
|
+
case 'check':
|
|
846
773
|
await runCheck(config);
|
|
847
774
|
break;
|
|
848
|
-
case '
|
|
775
|
+
case 'logs':
|
|
849
776
|
viewLog('kernel.log');
|
|
850
777
|
break;
|
|
851
|
-
case '
|
|
778
|
+
case 'audit':
|
|
852
779
|
viewLog('kernel-audit.log');
|
|
853
780
|
break;
|
|
854
|
-
case '
|
|
855
|
-
await changeBrainModel(config
|
|
781
|
+
case 'brain':
|
|
782
|
+
await changeBrainModel(config);
|
|
856
783
|
break;
|
|
857
|
-
case '
|
|
858
|
-
await changeOrchestratorModel(config
|
|
784
|
+
case 'orch':
|
|
785
|
+
await changeOrchestratorModel(config);
|
|
859
786
|
break;
|
|
860
|
-
case '
|
|
861
|
-
await manageCustomSkills(
|
|
787
|
+
case 'skills':
|
|
788
|
+
await manageCustomSkills();
|
|
862
789
|
break;
|
|
863
|
-
case '
|
|
864
|
-
await manageAutomations(
|
|
790
|
+
case 'automations':
|
|
791
|
+
await manageAutomations();
|
|
865
792
|
break;
|
|
866
|
-
case '
|
|
867
|
-
await manageCharacters(
|
|
793
|
+
case 'characters':
|
|
794
|
+
await manageCharacters(config);
|
|
868
795
|
break;
|
|
869
|
-
case '
|
|
870
|
-
await linkLinkedInCli(config
|
|
796
|
+
case 'linkedin':
|
|
797
|
+
await linkLinkedInCli(config);
|
|
871
798
|
break;
|
|
872
|
-
case '
|
|
873
|
-
|
|
874
|
-
const dashPort = config.dashboard?.port || 3000;
|
|
875
|
-
console.log('');
|
|
876
|
-
console.log(chalk.bold(' Dashboard'));
|
|
877
|
-
console.log(` Auto-start on boot: ${dashEnabled ? chalk.green('yes') : chalk.yellow('no')}`);
|
|
878
|
-
console.log(` Port: ${chalk.cyan(dashPort)}`);
|
|
879
|
-
console.log(` URL: ${chalk.cyan(`http://localhost:${dashPort}`)}`);
|
|
880
|
-
console.log('');
|
|
881
|
-
console.log(` ${chalk.cyan('1.')} ${dashEnabled ? 'Disable' : 'Enable'} auto-start on boot`);
|
|
882
|
-
console.log(` ${chalk.cyan('2.')} Change port`);
|
|
883
|
-
console.log(` ${chalk.cyan('3.')} Back`);
|
|
884
|
-
console.log('');
|
|
885
|
-
const dashChoice = await ask(rl, chalk.cyan(' > '));
|
|
886
|
-
switch (dashChoice.trim()) {
|
|
887
|
-
case '1': {
|
|
888
|
-
const newEnabled = !dashEnabled;
|
|
889
|
-
saveDashboardToYaml({ enabled: newEnabled });
|
|
890
|
-
config.dashboard.enabled = newEnabled;
|
|
891
|
-
console.log(chalk.green(`\n ✔ Dashboard auto-start ${newEnabled ? 'enabled' : 'disabled'}\n`));
|
|
892
|
-
if (newEnabled) {
|
|
893
|
-
console.log(chalk.dim(` Dashboard will start at http://localhost:${dashPort} on next bot launch.`));
|
|
894
|
-
console.log(chalk.dim(' Or use /dashboard start in Telegram to start now.\n'));
|
|
895
|
-
}
|
|
896
|
-
break;
|
|
897
|
-
}
|
|
898
|
-
case '2': {
|
|
899
|
-
const portInput = await ask(rl, chalk.cyan(' New port: '));
|
|
900
|
-
const newPort = parseInt(portInput.trim(), 10);
|
|
901
|
-
if (!newPort || newPort < 1 || newPort > 65535) {
|
|
902
|
-
console.log(chalk.dim(' Invalid port.\n'));
|
|
903
|
-
break;
|
|
904
|
-
}
|
|
905
|
-
saveDashboardToYaml({ port: newPort });
|
|
906
|
-
config.dashboard.port = newPort;
|
|
907
|
-
console.log(chalk.green(`\n ✔ Dashboard port set to ${newPort}\n`));
|
|
908
|
-
break;
|
|
909
|
-
}
|
|
910
|
-
default:
|
|
911
|
-
break;
|
|
912
|
-
}
|
|
799
|
+
case 'dashboard':
|
|
800
|
+
await manageDashboard(config);
|
|
913
801
|
break;
|
|
914
|
-
|
|
915
|
-
case '12':
|
|
802
|
+
case 'exit':
|
|
916
803
|
running = false;
|
|
917
804
|
break;
|
|
918
|
-
default:
|
|
919
|
-
console.log(chalk.dim(' Invalid choice.\n'));
|
|
920
805
|
}
|
|
921
806
|
}
|
|
922
807
|
|
|
923
|
-
|
|
924
|
-
console.log(chalk.dim(' Goodbye.\n'));
|
|
808
|
+
p.outro('Goodbye.');
|
|
925
809
|
}
|
|
926
810
|
|
|
927
811
|
main().catch((err) => {
|