kernelbot 1.0.37 → 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 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.')} Exit`);
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. Clear periodic intervals
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
- const conversationManager = new ConversationManager(config);
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({ config, conversationManager, personaManager, selfManager, jobManager, automationManager, memoryManager, shareQueue });
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, memoryManager, journalManager, shareQueue,
309
- evolutionTracker, codebaseKnowledge, selfManager,
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 bot = startBot(config, agent, conversationManager, jobManager, automationManager, { lifeEngine, memoryManager, journalManager, shareQueue, evolutionTracker, codebaseKnowledge });
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:
@@ -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.37",
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",