kernelbot 1.0.36 → 1.0.38
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 +389 -23
- package/config.example.yaml +17 -0
- package/package.json +2 -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 +209 -0
- package/src/life/engine.js +28 -20
- 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/security/auth.js +42 -1
- 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 +104 -57
- package/src/utils/display.js +73 -12
- package/src/utils/temporal-awareness.js +24 -10
package/bin/kernel.js
CHANGED
|
@@ -9,29 +9,28 @@ import { readFileSync, existsSync } from 'fs';
|
|
|
9
9
|
import { join } from 'path';
|
|
10
10
|
import { homedir } from 'os';
|
|
11
11
|
import chalk from 'chalk';
|
|
12
|
-
import { loadConfig, loadConfigInteractive, changeBrainModel, changeOrchestratorModel } from '../src/utils/config.js';
|
|
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
|
+
showCharacterGallery,
|
|
20
|
+
showCharacterCard,
|
|
19
21
|
} from '../src/utils/display.js';
|
|
20
22
|
import { createAuditLogger } from '../src/security/audit.js';
|
|
23
|
+
import { CharacterBuilder } from '../src/characters/builder.js';
|
|
21
24
|
import { ConversationManager } from '../src/conversation.js';
|
|
22
25
|
import { UserPersonaManager } from '../src/persona.js';
|
|
23
|
-
import { SelfManager } from '../src/self.js';
|
|
24
26
|
import { Agent } from '../src/agent.js';
|
|
25
27
|
import { JobManager } from '../src/swarm/job-manager.js';
|
|
26
28
|
import { startBot } from '../src/bot.js';
|
|
27
29
|
import { AutomationManager } from '../src/automation/index.js';
|
|
28
30
|
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
31
|
import { CodebaseKnowledge } from '../src/life/codebase.js';
|
|
34
32
|
import { LifeEngine } from '../src/life/engine.js';
|
|
33
|
+
import { CharacterManager } from '../src/character.js';
|
|
35
34
|
import {
|
|
36
35
|
loadCustomSkills,
|
|
37
36
|
getCustomSkills,
|
|
@@ -61,7 +60,10 @@ function showMenu(config) {
|
|
|
61
60
|
console.log(` ${chalk.cyan('6.')} Change orchestrator model`);
|
|
62
61
|
console.log(` ${chalk.cyan('7.')} Manage custom skills`);
|
|
63
62
|
console.log(` ${chalk.cyan('8.')} Manage automations`);
|
|
64
|
-
console.log(` ${chalk.cyan('9.')}
|
|
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`);
|
|
65
67
|
console.log('');
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -74,7 +76,7 @@ function ask(rl, question) {
|
|
|
74
76
|
* Stops polling, cancels running jobs, persists conversations,
|
|
75
77
|
* disarms automations, stops the life engine, and clears intervals.
|
|
76
78
|
*/
|
|
77
|
-
function setupGracefulShutdown({ bot, lifeEngine, automationManager, jobManager, conversationManager, intervals }) {
|
|
79
|
+
function setupGracefulShutdown({ bot, lifeEngine, automationManager, jobManager, conversationManager, intervals, dashboardHandle }) {
|
|
78
80
|
let shuttingDown = false;
|
|
79
81
|
|
|
80
82
|
const shutdown = async (signal) => {
|
|
@@ -129,7 +131,14 @@ function setupGracefulShutdown({ bot, lifeEngine, automationManager, jobManager,
|
|
|
129
131
|
logger.error(`[Shutdown] Failed to save conversations: ${err.message}`);
|
|
130
132
|
}
|
|
131
133
|
|
|
132
|
-
// 6.
|
|
134
|
+
// 6. Stop dashboard
|
|
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
|
|
133
142
|
for (const id of intervals) {
|
|
134
143
|
clearInterval(id);
|
|
135
144
|
}
|
|
@@ -281,9 +290,20 @@ async function startBotFlow(config) {
|
|
|
281
290
|
return false;
|
|
282
291
|
}
|
|
283
292
|
|
|
284
|
-
|
|
293
|
+
// Character system — manages multiple personas with isolated data
|
|
294
|
+
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
|
+
if (characterManager.needsOnboarding) {
|
|
299
|
+
characterManager.installAllBuiltins();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const activeCharacterId = characterManager.getActiveCharacterId();
|
|
303
|
+
const charCtx = characterManager.buildContext(activeCharacterId);
|
|
304
|
+
|
|
305
|
+
const conversationManager = new ConversationManager(config, charCtx.conversationFilePath);
|
|
285
306
|
const personaManager = new UserPersonaManager();
|
|
286
|
-
const selfManager = new SelfManager();
|
|
287
307
|
const jobManager = new JobManager({
|
|
288
308
|
jobTimeoutSeconds: config.swarm.job_timeout_seconds,
|
|
289
309
|
cleanupIntervalMinutes: config.swarm.cleanup_interval_minutes,
|
|
@@ -291,25 +311,65 @@ async function startBotFlow(config) {
|
|
|
291
311
|
|
|
292
312
|
const automationManager = new AutomationManager();
|
|
293
313
|
|
|
294
|
-
// Life system managers
|
|
295
|
-
const memoryManager = new MemoryManager();
|
|
296
|
-
const journalManager = new JournalManager();
|
|
297
|
-
const shareQueue = new ShareQueue();
|
|
298
|
-
const evolutionTracker = new EvolutionTracker();
|
|
314
|
+
// Life system managers — scoped to active character
|
|
299
315
|
const codebaseKnowledge = new CodebaseKnowledge({ config });
|
|
300
316
|
|
|
301
|
-
const agent = new Agent({
|
|
317
|
+
const agent = new Agent({
|
|
318
|
+
config, conversationManager, personaManager,
|
|
319
|
+
selfManager: charCtx.selfManager,
|
|
320
|
+
jobManager, automationManager,
|
|
321
|
+
memoryManager: charCtx.memoryManager,
|
|
322
|
+
shareQueue: charCtx.shareQueue,
|
|
323
|
+
characterManager,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Load character context into agent (sets persona, name, etc.)
|
|
327
|
+
agent.loadCharacter(activeCharacterId);
|
|
302
328
|
|
|
303
329
|
// Wire codebase knowledge to agent for LLM-powered scanning
|
|
304
330
|
codebaseKnowledge.setAgent(agent);
|
|
305
331
|
|
|
306
|
-
// Life Engine — autonomous inner life
|
|
332
|
+
// Life Engine — autonomous inner life (scoped to active character)
|
|
307
333
|
const lifeEngine = new LifeEngine({
|
|
308
|
-
config, agent,
|
|
309
|
-
|
|
334
|
+
config, agent,
|
|
335
|
+
memoryManager: charCtx.memoryManager,
|
|
336
|
+
journalManager: charCtx.journalManager,
|
|
337
|
+
shareQueue: charCtx.shareQueue,
|
|
338
|
+
evolutionTracker: charCtx.evolutionTracker,
|
|
339
|
+
codebaseKnowledge,
|
|
340
|
+
selfManager: charCtx.selfManager,
|
|
341
|
+
basePath: charCtx.lifeBasePath,
|
|
342
|
+
characterId: activeCharacterId,
|
|
310
343
|
});
|
|
311
344
|
|
|
312
|
-
const
|
|
345
|
+
const dashboardDeps = {
|
|
346
|
+
config, jobManager, automationManager, lifeEngine, conversationManager, characterManager,
|
|
347
|
+
memoryManager: charCtx.memoryManager,
|
|
348
|
+
journalManager: charCtx.journalManager,
|
|
349
|
+
shareQueue: charCtx.shareQueue,
|
|
350
|
+
evolutionTracker: charCtx.evolutionTracker,
|
|
351
|
+
selfManager: charCtx.selfManager,
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// Optional cyberpunk terminal dashboard (must init before startBot so handle is available)
|
|
355
|
+
let dashboardHandle = null;
|
|
356
|
+
if (config.dashboard?.enabled) {
|
|
357
|
+
const { startDashboard } = await import('../src/dashboard/server.js');
|
|
358
|
+
dashboardHandle = startDashboard({ port: config.dashboard.port, ...dashboardDeps });
|
|
359
|
+
logger.info(`[Dashboard] Running on http://localhost:${config.dashboard.port}`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const bot = startBot(config, agent, conversationManager, jobManager, automationManager, {
|
|
363
|
+
lifeEngine,
|
|
364
|
+
memoryManager: charCtx.memoryManager,
|
|
365
|
+
journalManager: charCtx.journalManager,
|
|
366
|
+
shareQueue: charCtx.shareQueue,
|
|
367
|
+
evolutionTracker: charCtx.evolutionTracker,
|
|
368
|
+
codebaseKnowledge,
|
|
369
|
+
characterManager,
|
|
370
|
+
dashboardHandle,
|
|
371
|
+
dashboardDeps,
|
|
372
|
+
});
|
|
313
373
|
|
|
314
374
|
// Periodic job cleanup and timeout enforcement
|
|
315
375
|
const cleanupMs = (config.swarm.cleanup_interval_minutes || 30) * 60 * 1000;
|
|
@@ -321,8 +381,8 @@ async function startBotFlow(config) {
|
|
|
321
381
|
// Periodic memory pruning (daily)
|
|
322
382
|
const retentionDays = config.life?.memory_retention_days || 90;
|
|
323
383
|
const pruneInterval = setInterval(() => {
|
|
324
|
-
memoryManager.pruneOld(retentionDays);
|
|
325
|
-
shareQueue.prune(7);
|
|
384
|
+
charCtx.memoryManager.pruneOld(retentionDays);
|
|
385
|
+
charCtx.shareQueue.prune(7);
|
|
326
386
|
}, 24 * 3600_000);
|
|
327
387
|
|
|
328
388
|
showStartupComplete();
|
|
@@ -355,6 +415,7 @@ async function startBotFlow(config) {
|
|
|
355
415
|
setupGracefulShutdown({
|
|
356
416
|
bot, lifeEngine, automationManager, jobManager,
|
|
357
417
|
conversationManager, intervals: [cleanupInterval, pruneInterval],
|
|
418
|
+
dashboardHandle,
|
|
358
419
|
});
|
|
359
420
|
|
|
360
421
|
return true;
|
|
@@ -505,6 +566,262 @@ async function manageAutomations(rl) {
|
|
|
505
566
|
}
|
|
506
567
|
}
|
|
507
568
|
|
|
569
|
+
async function manageCharacters(rl, config) {
|
|
570
|
+
const charManager = new CharacterManager();
|
|
571
|
+
|
|
572
|
+
// Ensure builtins installed
|
|
573
|
+
charManager.installAllBuiltins();
|
|
574
|
+
|
|
575
|
+
let managing = true;
|
|
576
|
+
while (managing) {
|
|
577
|
+
const characters = charManager.listCharacters();
|
|
578
|
+
const activeId = charManager.getActiveCharacterId();
|
|
579
|
+
const active = charManager.getCharacter(activeId);
|
|
580
|
+
|
|
581
|
+
console.log('');
|
|
582
|
+
console.log(chalk.bold(' Character Management'));
|
|
583
|
+
console.log(chalk.dim(` Active: ${active?.emoji || ''} ${active?.name || 'None'}`));
|
|
584
|
+
console.log('');
|
|
585
|
+
console.log(` ${chalk.cyan('1.')} Switch character`);
|
|
586
|
+
console.log(` ${chalk.cyan('2.')} Create custom character`);
|
|
587
|
+
console.log(` ${chalk.cyan('3.')} View character info`);
|
|
588
|
+
console.log(` ${chalk.cyan('4.')} Delete a custom character`);
|
|
589
|
+
console.log(` ${chalk.cyan('5.')} Back`);
|
|
590
|
+
console.log('');
|
|
591
|
+
|
|
592
|
+
const choice = await ask(rl, chalk.cyan(' > '));
|
|
593
|
+
switch (choice.trim()) {
|
|
594
|
+
case '1': {
|
|
595
|
+
showCharacterGallery(characters, activeId);
|
|
596
|
+
console.log('');
|
|
597
|
+
characters.forEach((c, i) => {
|
|
598
|
+
const marker = c.id === activeId ? chalk.green(' ✓') : '';
|
|
599
|
+
console.log(` ${chalk.cyan(`${i + 1}.`)} ${c.emoji} ${c.name}${marker}`);
|
|
600
|
+
});
|
|
601
|
+
console.log('');
|
|
602
|
+
const pick = await ask(rl, chalk.cyan(' Select #: '));
|
|
603
|
+
const idx = parseInt(pick, 10) - 1;
|
|
604
|
+
if (idx >= 0 && idx < characters.length) {
|
|
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
|
+
}
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
case '2': {
|
|
613
|
+
// Create custom character via Q&A
|
|
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'));
|
|
617
|
+
|
|
618
|
+
// Need an LLM provider for generation
|
|
619
|
+
const orchProviderKey = config.orchestrator.provider || 'anthropic';
|
|
620
|
+
const orchProviderDef = PROVIDERS[orchProviderKey];
|
|
621
|
+
const orchApiKey = config.orchestrator.api_key || (orchProviderDef && process.env[orchProviderDef.envKey]);
|
|
622
|
+
if (!orchApiKey) {
|
|
623
|
+
console.log(chalk.red(' No API key configured for character generation.\n'));
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const provider = createProvider({
|
|
628
|
+
brain: {
|
|
629
|
+
provider: orchProviderKey,
|
|
630
|
+
model: config.orchestrator.model,
|
|
631
|
+
max_tokens: config.orchestrator.max_tokens,
|
|
632
|
+
temperature: config.orchestrator.temperature,
|
|
633
|
+
api_key: orchApiKey,
|
|
634
|
+
},
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
const builder = new CharacterBuilder(provider);
|
|
638
|
+
const answers = {};
|
|
639
|
+
let cancelled = false;
|
|
640
|
+
|
|
641
|
+
// Walk through all questions
|
|
642
|
+
let q = builder.getNextQuestion(answers);
|
|
643
|
+
while (q) {
|
|
644
|
+
const progress = builder.getProgress(answers);
|
|
645
|
+
console.log(chalk.bold(` Question ${progress.answered + 1}/${progress.total}`));
|
|
646
|
+
console.log(` ${q.question}`);
|
|
647
|
+
console.log(chalk.dim(` Examples: ${q.examples}`));
|
|
648
|
+
const answer = await ask(rl, chalk.cyan(' > '));
|
|
649
|
+
if (answer.trim().toLowerCase() === 'cancel') {
|
|
650
|
+
cancelled = true;
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
answers[q.id] = answer.trim();
|
|
654
|
+
q = builder.getNextQuestion(answers);
|
|
655
|
+
console.log('');
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (cancelled) {
|
|
659
|
+
console.log(chalk.dim(' Character creation cancelled.\n'));
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
console.log(chalk.dim(' Generating character...'));
|
|
664
|
+
try {
|
|
665
|
+
const result = await builder.generateCharacter(answers);
|
|
666
|
+
const id = result.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
667
|
+
|
|
668
|
+
// Show preview
|
|
669
|
+
console.log('');
|
|
670
|
+
showCharacterCard({
|
|
671
|
+
...result,
|
|
672
|
+
id,
|
|
673
|
+
origin: 'Custom',
|
|
674
|
+
});
|
|
675
|
+
console.log('');
|
|
676
|
+
|
|
677
|
+
const confirm = await ask(rl, chalk.cyan(' Install this character? (y/n): '));
|
|
678
|
+
if (confirm.trim().toLowerCase() === 'y') {
|
|
679
|
+
charManager.addCharacter(
|
|
680
|
+
{ id, type: 'custom', name: result.name, origin: 'Custom', age: result.age, emoji: result.emoji, tagline: result.tagline },
|
|
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'));
|
|
687
|
+
}
|
|
688
|
+
} catch (err) {
|
|
689
|
+
console.log(chalk.red(`\n Character generation failed: ${err.message}\n`));
|
|
690
|
+
}
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
case '3': {
|
|
694
|
+
console.log('');
|
|
695
|
+
characters.forEach((c, i) => {
|
|
696
|
+
console.log(` ${chalk.cyan(`${i + 1}.`)} ${c.emoji} ${c.name}`);
|
|
697
|
+
});
|
|
698
|
+
console.log('');
|
|
699
|
+
const pick = await ask(rl, chalk.cyan(' View #: '));
|
|
700
|
+
const idx = parseInt(pick, 10) - 1;
|
|
701
|
+
if (idx >= 0 && idx < characters.length) {
|
|
702
|
+
showCharacterCard(characters[idx], characters[idx].id === activeId);
|
|
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'));
|
|
709
|
+
}
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
case '4': {
|
|
713
|
+
const customChars = characters.filter(c => c.type === 'custom');
|
|
714
|
+
if (customChars.length === 0) {
|
|
715
|
+
console.log(chalk.dim('\n No custom characters to delete.\n'));
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
console.log('');
|
|
719
|
+
customChars.forEach((c, i) => {
|
|
720
|
+
console.log(` ${chalk.cyan(`${i + 1}.`)} ${c.emoji} ${c.name}`);
|
|
721
|
+
});
|
|
722
|
+
console.log('');
|
|
723
|
+
const pick = await ask(rl, chalk.cyan(' Delete #: '));
|
|
724
|
+
const idx = parseInt(pick, 10) - 1;
|
|
725
|
+
if (idx >= 0 && idx < customChars.length) {
|
|
726
|
+
try {
|
|
727
|
+
charManager.removeCharacter(customChars[idx].id);
|
|
728
|
+
console.log(chalk.green(`\n Deleted: ${customChars[idx].name}\n`));
|
|
729
|
+
} catch (err) {
|
|
730
|
+
console.log(chalk.red(`\n ${err.message}\n`));
|
|
731
|
+
}
|
|
732
|
+
} else {
|
|
733
|
+
console.log(chalk.dim(' Cancelled.\n'));
|
|
734
|
+
}
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
case '5':
|
|
738
|
+
managing = false;
|
|
739
|
+
break;
|
|
740
|
+
default:
|
|
741
|
+
console.log(chalk.dim(' Invalid choice.\n'));
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async function linkLinkedInCli(config, rl) {
|
|
747
|
+
const { saveCredential } = await import('../src/utils/config.js');
|
|
748
|
+
|
|
749
|
+
// Show current status
|
|
750
|
+
if (config.linkedin?.access_token) {
|
|
751
|
+
const truncated = `${config.linkedin.access_token.slice(0, 8)}...${config.linkedin.access_token.slice(-4)}`;
|
|
752
|
+
console.log(chalk.dim(`\n Currently connected — token: ${truncated}`));
|
|
753
|
+
if (config.linkedin.person_urn) console.log(chalk.dim(` URN: ${config.linkedin.person_urn}`));
|
|
754
|
+
const relink = (await ask(rl, chalk.cyan('\n Re-link? [y/N]: '))).trim().toLowerCase();
|
|
755
|
+
if (relink !== 'y') {
|
|
756
|
+
console.log(chalk.dim(' Cancelled.\n'));
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
console.log('');
|
|
762
|
+
console.log(chalk.bold(' Link LinkedIn Account\n'));
|
|
763
|
+
console.log(chalk.dim(' 1. Go to https://www.linkedin.com/developers/tools/oauth/token-generator'));
|
|
764
|
+
console.log(chalk.dim(' 2. Select your app, pick scopes: openid, profile, email, w_member_social'));
|
|
765
|
+
console.log(chalk.dim(' 3. Authorize and copy the token'));
|
|
766
|
+
console.log('');
|
|
767
|
+
|
|
768
|
+
const token = (await ask(rl, chalk.cyan(' Paste token (or "cancel"): '))).trim();
|
|
769
|
+
if (!token || token.toLowerCase() === 'cancel') {
|
|
770
|
+
console.log(chalk.dim(' Cancelled.\n'));
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
console.log(chalk.dim('\n Validating token...'));
|
|
775
|
+
|
|
776
|
+
try {
|
|
777
|
+
// Try /v2/userinfo (requires "Sign in with LinkedIn" product → openid+profile scopes)
|
|
778
|
+
const res = await fetch('https://api.linkedin.com/v2/userinfo', {
|
|
779
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
if (res.ok) {
|
|
783
|
+
const profile = await res.json();
|
|
784
|
+
const personUrn = `urn:li:person:${profile.sub}`;
|
|
785
|
+
|
|
786
|
+
saveCredential(config, 'LINKEDIN_ACCESS_TOKEN', token);
|
|
787
|
+
saveCredential(config, 'LINKEDIN_PERSON_URN', personUrn);
|
|
788
|
+
|
|
789
|
+
console.log(chalk.green(`\n ✔ LinkedIn linked`));
|
|
790
|
+
console.log(chalk.dim(` Name: ${profile.name}`));
|
|
791
|
+
if (profile.email) console.log(chalk.dim(` Email: ${profile.email}`));
|
|
792
|
+
console.log(chalk.dim(` URN: ${personUrn}`));
|
|
793
|
+
console.log('');
|
|
794
|
+
} else if (res.status === 401) {
|
|
795
|
+
throw new Error('Invalid or expired token.');
|
|
796
|
+
} else {
|
|
797
|
+
// 403 = token works but no profile scopes → save token, ask for URN
|
|
798
|
+
console.log(chalk.yellow('\n Token accepted but profile scopes missing (openid+profile).'));
|
|
799
|
+
console.log(chalk.dim(' To auto-detect your URN, add "Sign in with LinkedIn using OpenID Connect"'));
|
|
800
|
+
console.log(chalk.dim(' to your app at https://www.linkedin.com/developers/apps\n'));
|
|
801
|
+
console.log(chalk.dim(' For now, enter your person URN manually.'));
|
|
802
|
+
console.log(chalk.dim(' Find it: LinkedIn profile → URL contains /in/yourname'));
|
|
803
|
+
console.log(chalk.dim(' Or: Developer Portal → Your App → Auth → Your member sub value\n'));
|
|
804
|
+
|
|
805
|
+
const urn = (await ask(rl, chalk.cyan(' Person URN (urn:li:person:XXXXX): '))).trim();
|
|
806
|
+
if (!urn) {
|
|
807
|
+
console.log(chalk.yellow(' No URN provided. Token saved but LinkedIn posts will not work without a URN.\n'));
|
|
808
|
+
saveCredential(config, 'LINKEDIN_ACCESS_TOKEN', token);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const personUrn = urn.startsWith('urn:li:person:') ? urn : `urn:li:person:${urn}`;
|
|
813
|
+
saveCredential(config, 'LINKEDIN_ACCESS_TOKEN', token);
|
|
814
|
+
saveCredential(config, 'LINKEDIN_PERSON_URN', personUrn);
|
|
815
|
+
|
|
816
|
+
console.log(chalk.green(`\n ✔ LinkedIn linked`));
|
|
817
|
+
console.log(chalk.dim(` URN: ${personUrn}`));
|
|
818
|
+
console.log('');
|
|
819
|
+
}
|
|
820
|
+
} catch (err) {
|
|
821
|
+
console.log(chalk.red(`\n ✖ Token validation failed: ${err.message}\n`));
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
508
825
|
async function main() {
|
|
509
826
|
showLogo();
|
|
510
827
|
|
|
@@ -547,6 +864,55 @@ async function main() {
|
|
|
547
864
|
await manageAutomations(rl);
|
|
548
865
|
break;
|
|
549
866
|
case '9':
|
|
867
|
+
await manageCharacters(rl, config);
|
|
868
|
+
break;
|
|
869
|
+
case '10':
|
|
870
|
+
await linkLinkedInCli(config, rl);
|
|
871
|
+
break;
|
|
872
|
+
case '11': {
|
|
873
|
+
const dashEnabled = config.dashboard?.enabled;
|
|
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
|
+
}
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
case '12':
|
|
550
916
|
running = false;
|
|
551
917
|
break;
|
|
552
918
|
default:
|
package/config.example.yaml
CHANGED
|
@@ -32,6 +32,15 @@ jira:
|
|
|
32
32
|
# email: you@company.com # JIRA account email (Cloud) or username (Server)
|
|
33
33
|
# api_token: your-api-token # API token from https://id.atlassian.net/manage-profile/security/api-tokens
|
|
34
34
|
|
|
35
|
+
# LinkedIn — token-based integration for posting, reading, and engaging
|
|
36
|
+
# 1. Go to https://www.linkedin.com/developers/tools/oauth/token-generator
|
|
37
|
+
# 2. Select your app, pick scopes: openid, profile, email, w_member_social
|
|
38
|
+
# 3. Authorize and copy the token
|
|
39
|
+
# 4. Run /linkedin link <token> in Telegram, or set in .env:
|
|
40
|
+
# LINKEDIN_ACCESS_TOKEN=your-token
|
|
41
|
+
# LINKEDIN_PERSON_URN is auto-detected when you use /linkedin link
|
|
42
|
+
linkedin:
|
|
43
|
+
|
|
35
44
|
telegram:
|
|
36
45
|
# List Telegram user IDs allowed to interact. Empty = deny all.
|
|
37
46
|
# Set OWNER_TELEGRAM_ID in .env or add IDs here.
|
|
@@ -72,6 +81,9 @@ life:
|
|
|
72
81
|
self_code: 10 # Self-evolution proposals (requires self_coding.enabled)
|
|
73
82
|
code_review: 5 # Codebase scanning + PR status checks
|
|
74
83
|
reflect: 8 # Read logs, analyze interactions, find improvement patterns
|
|
84
|
+
cooldown_hours: # Per-activity cooldowns (hours) before the activity can repeat
|
|
85
|
+
journal: 4 # Hours between journal entries
|
|
86
|
+
reflect: 4 # Hours between reflection activities
|
|
75
87
|
proactive_sharing: true # Share discoveries with users proactively
|
|
76
88
|
proactive_max_per_day: 3 # Max proactive messages per day
|
|
77
89
|
memory_retention_days: 90 # Days to keep episodic memories
|
|
@@ -88,3 +100,8 @@ life:
|
|
|
88
100
|
quiet_hours:
|
|
89
101
|
start: 2 # Hour to start quiet period (no activities)
|
|
90
102
|
end: 6 # Hour to end quiet period
|
|
103
|
+
|
|
104
|
+
# Dashboard — optional cyberpunk terminal monitoring UI
|
|
105
|
+
# dashboard:
|
|
106
|
+
# enabled: false # Auto-start dashboard on boot (default: false)
|
|
107
|
+
# port: 3000 # HTTP port for the dashboard
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kernelbot",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.38",
|
|
4
4
|
"description": "KernelBot — AI engineering agent with full OS control",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Abdullah Al-Taheri <abdullah@altaheri.me>",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"gradient-string": "^3.0.0",
|
|
42
42
|
"js-yaml": "^4.1.0",
|
|
43
43
|
"node-telegram-bot-api": "^0.66.0",
|
|
44
|
+
"oauth-1.0a": "^2.2.6",
|
|
44
45
|
"openai": "^4.82.0",
|
|
45
46
|
"ora": "^8.1.1",
|
|
46
47
|
"puppeteer": "^24.37.3",
|