kernelbot 1.0.37 → 1.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/kernel.js +499 -249
- package/config.example.yaml +17 -0
- package/knowledge_base/active_inference_foraging.md +126 -0
- package/knowledge_base/index.md +1 -1
- package/package.json +3 -1
- package/src/agent.js +355 -82
- package/src/bot.js +724 -12
- package/src/character.js +406 -0
- package/src/characters/builder.js +174 -0
- package/src/characters/builtins.js +421 -0
- package/src/conversation.js +17 -2
- package/src/dashboard/agents.css +469 -0
- package/src/dashboard/agents.html +184 -0
- package/src/dashboard/agents.js +873 -0
- package/src/dashboard/dashboard.css +281 -0
- package/src/dashboard/dashboard.js +579 -0
- package/src/dashboard/index.html +366 -0
- package/src/dashboard/server.js +521 -0
- package/src/dashboard/shared.css +700 -0
- package/src/dashboard/shared.js +218 -0
- package/src/life/engine.js +115 -26
- package/src/life/evolution.js +7 -5
- package/src/life/journal.js +5 -4
- package/src/life/memory.js +12 -9
- package/src/life/share-queue.js +7 -5
- package/src/prompts/orchestrator.js +76 -14
- package/src/prompts/workers.js +22 -0
- package/src/self.js +17 -5
- package/src/services/linkedin-api.js +190 -0
- package/src/services/stt.js +8 -2
- package/src/services/tts.js +32 -2
- package/src/services/x-api.js +141 -0
- package/src/swarm/worker-registry.js +7 -0
- package/src/tools/categories.js +4 -0
- package/src/tools/index.js +6 -0
- package/src/tools/linkedin.js +264 -0
- package/src/tools/orchestrator-tools.js +337 -2
- package/src/tools/x.js +256 -0
- package/src/utils/config.js +190 -139
- package/src/utils/display.js +165 -52
- package/src/utils/temporal-awareness.js +24 -10
package/src/utils/config.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
2
|
-
import { join
|
|
2
|
+
import { join } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
|
-
import { createInterface } from 'readline';
|
|
5
4
|
import yaml from 'js-yaml';
|
|
6
5
|
import dotenv from 'dotenv';
|
|
7
6
|
import chalk from 'chalk';
|
|
7
|
+
import * as p from '@clack/prompts';
|
|
8
8
|
import { PROVIDERS } from '../providers/models.js';
|
|
9
|
+
import { handleCancel } from './display.js';
|
|
9
10
|
|
|
10
11
|
const DEFAULTS = {
|
|
11
12
|
bot: {
|
|
@@ -61,6 +62,12 @@ const DEFAULTS = {
|
|
|
61
62
|
max_history: 50,
|
|
62
63
|
recent_window: 10,
|
|
63
64
|
},
|
|
65
|
+
dashboard: {
|
|
66
|
+
enabled: false,
|
|
67
|
+
port: 3000,
|
|
68
|
+
},
|
|
69
|
+
linkedin: {},
|
|
70
|
+
x: {},
|
|
64
71
|
};
|
|
65
72
|
|
|
66
73
|
function deepMerge(target, source) {
|
|
@@ -102,10 +109,6 @@ function findConfigFile() {
|
|
|
102
109
|
return null;
|
|
103
110
|
}
|
|
104
111
|
|
|
105
|
-
function ask(rl, question) {
|
|
106
|
-
return new Promise((res) => rl.question(question, res));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
112
|
/**
|
|
110
113
|
* Migrate legacy `anthropic` config section → `brain` section.
|
|
111
114
|
*/
|
|
@@ -126,50 +129,42 @@ function migrateAnthropicConfig(config) {
|
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
/**
|
|
129
|
-
* Interactive provider → model picker.
|
|
132
|
+
* Interactive provider → model picker using @clack/prompts.
|
|
130
133
|
*/
|
|
131
|
-
export async function promptProviderSelection(
|
|
134
|
+
export async function promptProviderSelection() {
|
|
132
135
|
const providerKeys = Object.keys(PROVIDERS);
|
|
133
136
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
const providerKey = await p.select({
|
|
138
|
+
message: 'Select AI provider',
|
|
139
|
+
options: providerKeys.map(key => ({
|
|
140
|
+
value: key,
|
|
141
|
+
label: PROVIDERS[key].name,
|
|
142
|
+
})),
|
|
137
143
|
});
|
|
138
|
-
|
|
144
|
+
if (handleCancel(providerKey)) return null;
|
|
139
145
|
|
|
140
|
-
let providerIdx;
|
|
141
|
-
while (true) {
|
|
142
|
-
const input = await ask(rl, chalk.cyan(' Provider (number): '));
|
|
143
|
-
providerIdx = parseInt(input.trim(), 10) - 1;
|
|
144
|
-
if (providerIdx >= 0 && providerIdx < providerKeys.length) break;
|
|
145
|
-
console.log(chalk.dim(' Invalid choice, try again.'));
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const providerKey = providerKeys[providerIdx];
|
|
149
146
|
const provider = PROVIDERS[providerKey];
|
|
150
147
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
148
|
+
const modelId = await p.select({
|
|
149
|
+
message: `Select model for ${provider.name}`,
|
|
150
|
+
options: provider.models.map(m => ({
|
|
151
|
+
value: m.id,
|
|
152
|
+
label: m.label,
|
|
153
|
+
hint: m.id,
|
|
154
|
+
})),
|
|
154
155
|
});
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
let modelIdx;
|
|
158
|
-
while (true) {
|
|
159
|
-
const input = await ask(rl, chalk.cyan(' Model (number): '));
|
|
160
|
-
modelIdx = parseInt(input.trim(), 10) - 1;
|
|
161
|
-
if (modelIdx >= 0 && modelIdx < provider.models.length) break;
|
|
162
|
-
console.log(chalk.dim(' Invalid choice, try again.'));
|
|
163
|
-
}
|
|
156
|
+
if (handleCancel(modelId)) return null;
|
|
164
157
|
|
|
165
|
-
|
|
166
|
-
return { providerKey, modelId: model.id };
|
|
158
|
+
return { providerKey, modelId };
|
|
167
159
|
}
|
|
168
160
|
|
|
169
161
|
/**
|
|
170
|
-
*
|
|
162
|
+
* Read config.yaml, merge changes into a top-level section, and write it back.
|
|
163
|
+
* @param {string} section - The top-level YAML key to update (e.g. 'brain', 'orchestrator').
|
|
164
|
+
* @param {object} changes - Key-value pairs to merge into that section.
|
|
165
|
+
* @returns {string} The path to the written config file.
|
|
171
166
|
*/
|
|
172
|
-
|
|
167
|
+
function _patchConfigYaml(section, changes) {
|
|
173
168
|
const configDir = getConfigDir();
|
|
174
169
|
mkdirSync(configDir, { recursive: true });
|
|
175
170
|
const configPath = join(configDir, 'config.yaml');
|
|
@@ -179,16 +174,24 @@ export function saveProviderToYaml(providerKey, modelId) {
|
|
|
179
174
|
existing = yaml.load(readFileSync(configPath, 'utf-8')) || {};
|
|
180
175
|
}
|
|
181
176
|
|
|
182
|
-
existing
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
177
|
+
existing[section] = { ...(existing[section] || {}), ...changes };
|
|
178
|
+
writeFileSync(configPath, yaml.dump(existing, { lineWidth: -1 }));
|
|
179
|
+
return configPath;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Save provider and model to config.yaml.
|
|
184
|
+
*/
|
|
185
|
+
export function saveProviderToYaml(providerKey, modelId) {
|
|
186
|
+
const configPath = _patchConfigYaml('brain', { provider: providerKey, model: modelId });
|
|
187
187
|
|
|
188
188
|
// Remove legacy anthropic section if migrating
|
|
189
|
-
|
|
189
|
+
let existing = yaml.load(readFileSync(configPath, 'utf-8')) || {};
|
|
190
|
+
if (existing.anthropic) {
|
|
191
|
+
delete existing.anthropic;
|
|
192
|
+
writeFileSync(configPath, yaml.dump(existing, { lineWidth: -1 }));
|
|
193
|
+
}
|
|
190
194
|
|
|
191
|
-
writeFileSync(configPath, yaml.dump(existing, { lineWidth: -1 }));
|
|
192
195
|
return configPath;
|
|
193
196
|
}
|
|
194
197
|
|
|
@@ -196,66 +199,28 @@ export function saveProviderToYaml(providerKey, modelId) {
|
|
|
196
199
|
* Save orchestrator provider and model to config.yaml.
|
|
197
200
|
*/
|
|
198
201
|
export function saveOrchestratorToYaml(providerKey, modelId) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const configPath = join(configDir, 'config.yaml');
|
|
202
|
-
|
|
203
|
-
let existing = {};
|
|
204
|
-
if (existsSync(configPath)) {
|
|
205
|
-
existing = yaml.load(readFileSync(configPath, 'utf-8')) || {};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
existing.orchestrator = {
|
|
209
|
-
...(existing.orchestrator || {}),
|
|
210
|
-
provider: providerKey,
|
|
211
|
-
model: modelId,
|
|
212
|
-
};
|
|
202
|
+
return _patchConfigYaml('orchestrator', { provider: providerKey, model: modelId });
|
|
203
|
+
}
|
|
213
204
|
|
|
214
|
-
|
|
215
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Save dashboard config to config.yaml.
|
|
207
|
+
*/
|
|
208
|
+
export function saveDashboardToYaml(changes) {
|
|
209
|
+
return _patchConfigYaml('dashboard', changes);
|
|
216
210
|
}
|
|
217
211
|
|
|
218
212
|
/**
|
|
219
213
|
* Save Claude Code model to config.yaml.
|
|
220
214
|
*/
|
|
221
215
|
export function saveClaudeCodeModelToYaml(modelId) {
|
|
222
|
-
|
|
223
|
-
mkdirSync(configDir, { recursive: true });
|
|
224
|
-
const configPath = join(configDir, 'config.yaml');
|
|
225
|
-
|
|
226
|
-
let existing = {};
|
|
227
|
-
if (existsSync(configPath)) {
|
|
228
|
-
existing = yaml.load(readFileSync(configPath, 'utf-8')) || {};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
existing.claude_code = {
|
|
232
|
-
...(existing.claude_code || {}),
|
|
233
|
-
model: modelId,
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
writeFileSync(configPath, yaml.dump(existing, { lineWidth: -1 }));
|
|
237
|
-
return configPath;
|
|
216
|
+
return _patchConfigYaml('claude_code', { model: modelId });
|
|
238
217
|
}
|
|
239
218
|
|
|
240
219
|
/**
|
|
241
220
|
* Save Claude Code auth mode + credential to config.yaml and .env.
|
|
242
221
|
*/
|
|
243
222
|
export function saveClaudeCodeAuth(config, mode, value) {
|
|
244
|
-
|
|
245
|
-
mkdirSync(configDir, { recursive: true });
|
|
246
|
-
const configPath = join(configDir, 'config.yaml');
|
|
247
|
-
|
|
248
|
-
let existing = {};
|
|
249
|
-
if (existsSync(configPath)) {
|
|
250
|
-
existing = yaml.load(readFileSync(configPath, 'utf-8')) || {};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
existing.claude_code = {
|
|
254
|
-
...(existing.claude_code || {}),
|
|
255
|
-
auth_mode: mode,
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
writeFileSync(configPath, yaml.dump(existing, { lineWidth: -1 }));
|
|
223
|
+
_patchConfigYaml('claude_code', { auth_mode: mode });
|
|
259
224
|
|
|
260
225
|
// Update live config
|
|
261
226
|
config.claude_code.auth_mode = mode;
|
|
@@ -273,26 +238,29 @@ export function saveClaudeCodeAuth(config, mode, value) {
|
|
|
273
238
|
/**
|
|
274
239
|
* Full interactive flow: change orchestrator model + optionally enter API key.
|
|
275
240
|
*/
|
|
276
|
-
export async function changeOrchestratorModel(config
|
|
241
|
+
export async function changeOrchestratorModel(config) {
|
|
277
242
|
const { createProvider } = await import('../providers/index.js');
|
|
278
|
-
const
|
|
243
|
+
const result = await promptProviderSelection();
|
|
244
|
+
if (!result) return config;
|
|
279
245
|
|
|
246
|
+
const { providerKey, modelId } = result;
|
|
280
247
|
const providerDef = PROVIDERS[providerKey];
|
|
281
248
|
|
|
282
249
|
// Resolve API key
|
|
283
250
|
const envKey = providerDef.envKey;
|
|
284
251
|
let apiKey = process.env[envKey];
|
|
285
252
|
if (!apiKey) {
|
|
286
|
-
const key = await
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
253
|
+
const key = await p.text({
|
|
254
|
+
message: `${providerDef.name} API key (${envKey})`,
|
|
255
|
+
validate: (v) => (!v.trim() ? 'API key is required' : undefined),
|
|
256
|
+
});
|
|
257
|
+
if (handleCancel(key)) return config;
|
|
291
258
|
apiKey = key.trim();
|
|
292
259
|
}
|
|
293
260
|
|
|
294
261
|
// Validate the new provider before saving anything
|
|
295
|
-
|
|
262
|
+
const s = p.spinner();
|
|
263
|
+
s.start(`Verifying ${providerDef.name} / ${modelId}`);
|
|
296
264
|
const testConfig = {
|
|
297
265
|
brain: {
|
|
298
266
|
provider: providerKey,
|
|
@@ -305,16 +273,15 @@ export async function changeOrchestratorModel(config, rl) {
|
|
|
305
273
|
try {
|
|
306
274
|
const testProvider = createProvider(testConfig);
|
|
307
275
|
await testProvider.ping();
|
|
276
|
+
s.stop(`${providerDef.name} / ${modelId} verified`);
|
|
308
277
|
} catch (err) {
|
|
309
|
-
|
|
310
|
-
|
|
278
|
+
s.stop(chalk.red(`Verification failed: ${err.message}`));
|
|
279
|
+
p.log.warn('Orchestrator not changed. Keeping current model.');
|
|
311
280
|
return config;
|
|
312
281
|
}
|
|
313
282
|
|
|
314
283
|
// Validation passed — save everything
|
|
315
284
|
const savedPath = saveOrchestratorToYaml(providerKey, modelId);
|
|
316
|
-
console.log(chalk.dim(` Saved to ${savedPath}`));
|
|
317
|
-
|
|
318
285
|
config.orchestrator.provider = providerKey;
|
|
319
286
|
config.orchestrator.model = modelId;
|
|
320
287
|
config.orchestrator.api_key = apiKey;
|
|
@@ -322,50 +289,51 @@ export async function changeOrchestratorModel(config, rl) {
|
|
|
322
289
|
// Save the key if it was newly entered
|
|
323
290
|
if (!process.env[envKey]) {
|
|
324
291
|
saveCredential(config, envKey, apiKey);
|
|
325
|
-
console.log(chalk.dim(' API key saved.\n'));
|
|
326
292
|
}
|
|
327
293
|
|
|
328
|
-
|
|
294
|
+
p.log.success(`Orchestrator switched to ${providerDef.name} / ${modelId}`);
|
|
329
295
|
return config;
|
|
330
296
|
}
|
|
331
297
|
|
|
332
298
|
/**
|
|
333
299
|
* Full interactive flow: change brain model + optionally enter API key.
|
|
334
300
|
*/
|
|
335
|
-
export async function changeBrainModel(config
|
|
301
|
+
export async function changeBrainModel(config) {
|
|
336
302
|
const { createProvider } = await import('../providers/index.js');
|
|
337
|
-
const
|
|
303
|
+
const result = await promptProviderSelection();
|
|
304
|
+
if (!result) return config;
|
|
338
305
|
|
|
306
|
+
const { providerKey, modelId } = result;
|
|
339
307
|
const providerDef = PROVIDERS[providerKey];
|
|
340
308
|
|
|
341
309
|
// Resolve API key
|
|
342
310
|
const envKey = providerDef.envKey;
|
|
343
311
|
let apiKey = process.env[envKey];
|
|
344
312
|
if (!apiKey) {
|
|
345
|
-
const key = await
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
313
|
+
const key = await p.text({
|
|
314
|
+
message: `${providerDef.name} API key (${envKey})`,
|
|
315
|
+
validate: (v) => (!v.trim() ? 'API key is required' : undefined),
|
|
316
|
+
});
|
|
317
|
+
if (handleCancel(key)) return config;
|
|
350
318
|
apiKey = key.trim();
|
|
351
319
|
}
|
|
352
320
|
|
|
353
321
|
// Validate the new provider before saving anything
|
|
354
|
-
|
|
322
|
+
const s = p.spinner();
|
|
323
|
+
s.start(`Verifying ${providerDef.name} / ${modelId}`);
|
|
355
324
|
const testConfig = { ...config, brain: { ...config.brain, provider: providerKey, model: modelId, api_key: apiKey } };
|
|
356
325
|
try {
|
|
357
326
|
const testProvider = createProvider(testConfig);
|
|
358
327
|
await testProvider.ping();
|
|
328
|
+
s.stop(`${providerDef.name} / ${modelId} verified`);
|
|
359
329
|
} catch (err) {
|
|
360
|
-
|
|
361
|
-
|
|
330
|
+
s.stop(chalk.red(`Verification failed: ${err.message}`));
|
|
331
|
+
p.log.warn('Brain not changed. Keeping current model.');
|
|
362
332
|
return config;
|
|
363
333
|
}
|
|
364
334
|
|
|
365
335
|
// Validation passed — save everything
|
|
366
|
-
|
|
367
|
-
console.log(chalk.dim(` Saved to ${savedPath}`));
|
|
368
|
-
|
|
336
|
+
saveProviderToYaml(providerKey, modelId);
|
|
369
337
|
config.brain.provider = providerKey;
|
|
370
338
|
config.brain.model = modelId;
|
|
371
339
|
config.brain.api_key = apiKey;
|
|
@@ -373,10 +341,9 @@ export async function changeBrainModel(config, rl) {
|
|
|
373
341
|
// Save the key if it was newly entered
|
|
374
342
|
if (!process.env[envKey]) {
|
|
375
343
|
saveCredential(config, envKey, apiKey);
|
|
376
|
-
console.log(chalk.dim(' API key saved.\n'));
|
|
377
344
|
}
|
|
378
345
|
|
|
379
|
-
|
|
346
|
+
p.log.success(`Brain switched to ${providerDef.name} / ${modelId}`);
|
|
380
347
|
return config;
|
|
381
348
|
}
|
|
382
349
|
|
|
@@ -387,9 +354,8 @@ async function promptForMissing(config) {
|
|
|
387
354
|
|
|
388
355
|
if (missing.length === 0) return config;
|
|
389
356
|
|
|
390
|
-
|
|
357
|
+
p.log.warn('Missing credentials detected. Let\'s set them up.');
|
|
391
358
|
|
|
392
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
393
359
|
const mutableConfig = JSON.parse(JSON.stringify(config));
|
|
394
360
|
const envLines = [];
|
|
395
361
|
|
|
@@ -402,8 +368,11 @@ async function promptForMissing(config) {
|
|
|
402
368
|
|
|
403
369
|
if (!mutableConfig.brain.api_key) {
|
|
404
370
|
// Run brain provider selection flow
|
|
405
|
-
|
|
406
|
-
const
|
|
371
|
+
p.log.step('Worker Brain');
|
|
372
|
+
const brainResult = await promptProviderSelection();
|
|
373
|
+
if (!brainResult) { p.cancel('Setup cancelled.'); process.exit(0); }
|
|
374
|
+
|
|
375
|
+
const { providerKey, modelId } = brainResult;
|
|
407
376
|
mutableConfig.brain.provider = providerKey;
|
|
408
377
|
mutableConfig.brain.model = modelId;
|
|
409
378
|
saveProviderToYaml(providerKey, modelId);
|
|
@@ -411,36 +380,49 @@ async function promptForMissing(config) {
|
|
|
411
380
|
const providerDef = PROVIDERS[providerKey];
|
|
412
381
|
const envKey = providerDef.envKey;
|
|
413
382
|
|
|
414
|
-
const key = await
|
|
383
|
+
const key = await p.text({
|
|
384
|
+
message: `${providerDef.name} API key`,
|
|
385
|
+
validate: (v) => (!v.trim() ? 'API key is required' : undefined),
|
|
386
|
+
});
|
|
387
|
+
if (handleCancel(key)) { process.exit(0); }
|
|
415
388
|
mutableConfig.brain.api_key = key.trim();
|
|
416
389
|
envLines.push(`${envKey}=${key.trim()}`);
|
|
417
390
|
|
|
418
391
|
// Orchestrator provider selection
|
|
419
|
-
|
|
420
|
-
const sameChoice = await
|
|
421
|
-
|
|
392
|
+
p.log.step('Orchestrator');
|
|
393
|
+
const sameChoice = await p.confirm({
|
|
394
|
+
message: `Use same provider (${providerDef.name} / ${modelId}) for orchestrator?`,
|
|
395
|
+
initialValue: true,
|
|
396
|
+
});
|
|
397
|
+
if (handleCancel(sameChoice)) { process.exit(0); }
|
|
398
|
+
|
|
399
|
+
if (sameChoice) {
|
|
422
400
|
mutableConfig.orchestrator.provider = providerKey;
|
|
423
401
|
mutableConfig.orchestrator.model = modelId;
|
|
424
402
|
mutableConfig.orchestrator.api_key = key.trim();
|
|
425
403
|
saveOrchestratorToYaml(providerKey, modelId);
|
|
426
404
|
} else {
|
|
427
|
-
const orch = await promptProviderSelection(
|
|
405
|
+
const orch = await promptProviderSelection();
|
|
406
|
+
if (!orch) { p.cancel('Setup cancelled.'); process.exit(0); }
|
|
407
|
+
|
|
428
408
|
mutableConfig.orchestrator.provider = orch.providerKey;
|
|
429
409
|
mutableConfig.orchestrator.model = orch.modelId;
|
|
430
410
|
saveOrchestratorToYaml(orch.providerKey, orch.modelId);
|
|
431
411
|
|
|
432
412
|
const orchProviderDef = PROVIDERS[orch.providerKey];
|
|
433
413
|
if (orch.providerKey === providerKey) {
|
|
434
|
-
// Same provider — reuse the API key
|
|
435
414
|
mutableConfig.orchestrator.api_key = key.trim();
|
|
436
415
|
} else {
|
|
437
|
-
// Different provider — need a separate key
|
|
438
416
|
const orchEnvKey = orchProviderDef.envKey;
|
|
439
417
|
const orchExisting = process.env[orchEnvKey];
|
|
440
418
|
if (orchExisting) {
|
|
441
419
|
mutableConfig.orchestrator.api_key = orchExisting;
|
|
442
420
|
} else {
|
|
443
|
-
const orchKey = await
|
|
421
|
+
const orchKey = await p.text({
|
|
422
|
+
message: `${orchProviderDef.name} API key`,
|
|
423
|
+
validate: (v) => (!v.trim() ? 'API key is required' : undefined),
|
|
424
|
+
});
|
|
425
|
+
if (handleCancel(orchKey)) { process.exit(0); }
|
|
444
426
|
mutableConfig.orchestrator.api_key = orchKey.trim();
|
|
445
427
|
envLines.push(`${orchEnvKey}=${orchKey.trim()}`);
|
|
446
428
|
}
|
|
@@ -449,13 +431,15 @@ async function promptForMissing(config) {
|
|
|
449
431
|
}
|
|
450
432
|
|
|
451
433
|
if (!mutableConfig.telegram.bot_token) {
|
|
452
|
-
const token = await
|
|
434
|
+
const token = await p.text({
|
|
435
|
+
message: 'Telegram Bot Token',
|
|
436
|
+
validate: (v) => (!v.trim() ? 'Token is required' : undefined),
|
|
437
|
+
});
|
|
438
|
+
if (handleCancel(token)) { process.exit(0); }
|
|
453
439
|
mutableConfig.telegram.bot_token = token.trim();
|
|
454
440
|
envLines.push(`TELEGRAM_BOT_TOKEN=${token.trim()}`);
|
|
455
441
|
}
|
|
456
442
|
|
|
457
|
-
rl.close();
|
|
458
|
-
|
|
459
443
|
// Save to ~/.kernelbot/.env so it persists globally
|
|
460
444
|
if (envLines.length > 0) {
|
|
461
445
|
const configDir = getConfigDir();
|
|
@@ -465,9 +449,8 @@ async function promptForMissing(config) {
|
|
|
465
449
|
// Merge with existing content
|
|
466
450
|
let content = existingEnv ? existingEnv.trimEnd() + '\n' : '';
|
|
467
451
|
for (const line of envLines) {
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
const regex = new RegExp(`^${key}=.*$`, 'm');
|
|
452
|
+
const envKey = line.split('=')[0];
|
|
453
|
+
const regex = new RegExp(`^${envKey}=.*$`, 'm');
|
|
471
454
|
if (regex.test(content)) {
|
|
472
455
|
content = content.replace(regex, line);
|
|
473
456
|
} else {
|
|
@@ -475,7 +458,7 @@ async function promptForMissing(config) {
|
|
|
475
458
|
}
|
|
476
459
|
}
|
|
477
460
|
writeFileSync(savePath, content);
|
|
478
|
-
|
|
461
|
+
p.log.info(`Saved to ${savePath}`);
|
|
479
462
|
}
|
|
480
463
|
|
|
481
464
|
return mutableConfig;
|
|
@@ -556,6 +539,34 @@ export function loadConfig() {
|
|
|
556
539
|
config.claude_code.oauth_token = process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
557
540
|
}
|
|
558
541
|
|
|
542
|
+
// LinkedIn token-based auth from env
|
|
543
|
+
if (process.env.LINKEDIN_ACCESS_TOKEN) {
|
|
544
|
+
if (!config.linkedin) config.linkedin = {};
|
|
545
|
+
config.linkedin.access_token = process.env.LINKEDIN_ACCESS_TOKEN;
|
|
546
|
+
}
|
|
547
|
+
if (process.env.LINKEDIN_PERSON_URN) {
|
|
548
|
+
if (!config.linkedin) config.linkedin = {};
|
|
549
|
+
config.linkedin.person_urn = process.env.LINKEDIN_PERSON_URN;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// X (Twitter) OAuth 1.0a credentials from env
|
|
553
|
+
if (process.env.X_CONSUMER_KEY) {
|
|
554
|
+
if (!config.x) config.x = {};
|
|
555
|
+
config.x.consumer_key = process.env.X_CONSUMER_KEY;
|
|
556
|
+
}
|
|
557
|
+
if (process.env.X_CONSUMER_SECRET) {
|
|
558
|
+
if (!config.x) config.x = {};
|
|
559
|
+
config.x.consumer_secret = process.env.X_CONSUMER_SECRET;
|
|
560
|
+
}
|
|
561
|
+
if (process.env.X_ACCESS_TOKEN) {
|
|
562
|
+
if (!config.x) config.x = {};
|
|
563
|
+
config.x.access_token = process.env.X_ACCESS_TOKEN;
|
|
564
|
+
}
|
|
565
|
+
if (process.env.X_ACCESS_TOKEN_SECRET) {
|
|
566
|
+
if (!config.x) config.x = {};
|
|
567
|
+
config.x.access_token_secret = process.env.X_ACCESS_TOKEN_SECRET;
|
|
568
|
+
}
|
|
569
|
+
|
|
559
570
|
return config;
|
|
560
571
|
}
|
|
561
572
|
|
|
@@ -619,6 +630,30 @@ export function saveCredential(config, envKey, value) {
|
|
|
619
630
|
if (!config.jira) config.jira = {};
|
|
620
631
|
config.jira.api_token = value;
|
|
621
632
|
break;
|
|
633
|
+
case 'LINKEDIN_ACCESS_TOKEN':
|
|
634
|
+
if (!config.linkedin) config.linkedin = {};
|
|
635
|
+
config.linkedin.access_token = value;
|
|
636
|
+
break;
|
|
637
|
+
case 'LINKEDIN_PERSON_URN':
|
|
638
|
+
if (!config.linkedin) config.linkedin = {};
|
|
639
|
+
config.linkedin.person_urn = value;
|
|
640
|
+
break;
|
|
641
|
+
case 'X_CONSUMER_KEY':
|
|
642
|
+
if (!config.x) config.x = {};
|
|
643
|
+
config.x.consumer_key = value;
|
|
644
|
+
break;
|
|
645
|
+
case 'X_CONSUMER_SECRET':
|
|
646
|
+
if (!config.x) config.x = {};
|
|
647
|
+
config.x.consumer_secret = value;
|
|
648
|
+
break;
|
|
649
|
+
case 'X_ACCESS_TOKEN':
|
|
650
|
+
if (!config.x) config.x = {};
|
|
651
|
+
config.x.access_token = value;
|
|
652
|
+
break;
|
|
653
|
+
case 'X_ACCESS_TOKEN_SECRET':
|
|
654
|
+
if (!config.x) config.x = {};
|
|
655
|
+
config.x.access_token_secret = value;
|
|
656
|
+
break;
|
|
622
657
|
}
|
|
623
658
|
|
|
624
659
|
// Also set in process.env so tools pick it up
|
|
@@ -652,5 +687,21 @@ export function getMissingCredential(toolName, config) {
|
|
|
652
687
|
}
|
|
653
688
|
}
|
|
654
689
|
|
|
690
|
+
const linkedinTools = ['linkedin_create_post', 'linkedin_get_my_posts', 'linkedin_get_post', 'linkedin_comment_on_post', 'linkedin_get_comments', 'linkedin_like_post', 'linkedin_get_profile', 'linkedin_delete_post'];
|
|
691
|
+
|
|
692
|
+
if (linkedinTools.includes(toolName)) {
|
|
693
|
+
if (!config.linkedin?.access_token && !process.env.LINKEDIN_ACCESS_TOKEN) {
|
|
694
|
+
return { envKey: 'LINKEDIN_ACCESS_TOKEN', label: 'LinkedIn Access Token (from /linkedin link or https://www.linkedin.com/developers/tools/oauth/token-generator)' };
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const xTools = ['x_post_tweet', 'x_reply_to_tweet', 'x_get_my_tweets', 'x_get_tweet', 'x_search_tweets', 'x_like_tweet', 'x_retweet', 'x_delete_tweet', 'x_get_profile'];
|
|
699
|
+
|
|
700
|
+
if (xTools.includes(toolName)) {
|
|
701
|
+
if (!config.x?.consumer_key && !process.env.X_CONSUMER_KEY) {
|
|
702
|
+
return { envKey: 'X_CONSUMER_KEY', label: 'X (Twitter) Consumer Key (from /x link or X Developer Portal)' };
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
655
706
|
return null;
|
|
656
707
|
}
|