kernelbot 1.0.2 → 1.0.4

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/README.md CHANGED
@@ -6,10 +6,10 @@ Send a message in Telegram, and KernelBot will read files, write code, run comma
6
6
 
7
7
  ## How It Works
8
8
 
9
- ```
9
+ ```text
10
10
  You (Telegram) → KernelBot → Claude Sonnet (Anthropic API)
11
-
12
- OS Tools (shell, files, directories)
11
+
12
+ OS Tools (shell, files, directories)
13
13
  ```
14
14
 
15
15
  KernelBot runs a **tool-use loop**: Claude decides which tools to call, KernelBot executes them on your OS, feeds results back, and Claude continues until the task is done. One message can trigger dozens of tool calls autonomously.
@@ -29,42 +29,35 @@ KernelBot runs a **tool-use loop**: Claude decides which tools to call, KernelBo
29
29
  npm install -g kernelbot
30
30
  ```
31
31
 
32
- This installs the `kernelbot` command globally.
33
-
34
32
  ## Quick Start
35
33
 
36
34
  ```bash
37
- # Interactive setup — creates .env and config.yaml
38
- kernelbot init
39
-
40
- # Verify everything works
41
- kernelbot check
42
-
43
- # Launch the bot
44
- kernelbot start
35
+ kernelbot
45
36
  ```
46
37
 
47
- ## Commands
38
+ That's it. On first run, KernelBot will:
48
39
 
49
- | Command | Description |
50
- | ------------------------- | --------------------------------------------------------- |
51
- | `kernelbot start` | Start the Telegram bot |
52
- | `kernelbot run "prompt"` | One-off agent call without Telegram (for testing/scripts) |
53
- | `kernelbot check` | Validate config and test API connections |
54
- | `kernelbot init` | Interactive setup wizard |
40
+ 1. Detect missing credentials and prompt for them
41
+ 2. Save them to `~/.kernelbot/.env`
42
+ 3. Verify API connections
43
+ 4. Launch the Telegram bot
55
44
 
56
45
  ## Configuration
57
46
 
58
- KernelBot looks for `config.yaml` in the current directory or `~/.kernelbot/`. Secrets are loaded from `.env`.
47
+ KernelBot auto-detects config from the current directory or `~/.kernelbot/`. Everything works with zero config — just provide your API keys when prompted.
59
48
 
60
- ### `.env`
49
+ ### Environment Variables
61
50
 
62
- ```
51
+ Set these in `.env` or as system environment variables:
52
+
53
+ ```text
63
54
  ANTHROPIC_API_KEY=sk-ant-...
64
55
  TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
65
56
  ```
66
57
 
67
- ### `config.yaml`
58
+ ### `config.yaml` (optional)
59
+
60
+ Drop a `config.yaml` in your working directory or `~/.kernelbot/` to customize behavior:
68
61
 
69
62
  ```yaml
70
63
  bot:
@@ -102,10 +95,10 @@ conversation:
102
95
 
103
96
  ## Project Structure
104
97
 
105
- ```
98
+ ```text
106
99
  KernelBot/
107
100
  ├── bin/
108
- │ └── kernel.js # CLI entry point
101
+ │ └── kernel.js # Entry point
109
102
  ├── src/
110
103
  │ ├── agent.js # Sonnet tool-use loop
111
104
  │ ├── bot.js # Telegram bot (polling, auth, message handling)
@@ -119,7 +112,7 @@ KernelBot/
119
112
  │ │ ├── os.js # OS tool definitions + handlers
120
113
  │ │ └── index.js # Tool registry + dispatcher
121
114
  │ └── utils/
122
- │ ├── config.js # Config loading (yaml + env + defaults)
115
+ │ ├── config.js # Config loading (auto-detect + prompt)
123
116
  │ ├── display.js # CLI display (logo, spinners, banners)
124
117
  │ └── logger.js # Winston logger
125
118
  ├── config.example.yaml
@@ -135,4 +128,4 @@ KernelBot/
135
128
 
136
129
  ## Author
137
130
 
138
- Abdullah Al-Tahrei
131
+ Abdullah Al-Taheri
package/bin/kernel.js CHANGED
@@ -4,14 +4,15 @@
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 { Command } from 'commander';
8
7
  import { createInterface } from 'readline';
9
- import { writeFileSync, existsSync } from 'fs';
10
- import { loadConfig } from '../src/utils/config.js';
8
+ import { readFileSync, existsSync } from 'fs';
9
+ import { join } from 'path';
10
+ import { homedir } from 'os';
11
+ import chalk from 'chalk';
12
+ import { loadConfig, loadConfigInteractive } from '../src/utils/config.js';
11
13
  import { createLogger, getLogger } from '../src/utils/logger.js';
12
14
  import {
13
15
  showLogo,
14
- showHelp,
15
16
  showStartupCheck,
16
17
  showStartupComplete,
17
18
  showError,
@@ -22,180 +23,163 @@ import { Agent } from '../src/agent.js';
22
23
  import { startBot } from '../src/bot.js';
23
24
  import Anthropic from '@anthropic-ai/sdk';
24
25
 
25
- const program = new Command();
26
-
27
- program
28
- .name('kernelbot')
29
- .description('KernelBot AI engineering agent with full OS control')
30
- .version('1.0.0')
31
- .action(() => {
32
- showHelp();
33
- });
34
-
35
- // ─── kernel start ────────────────────────────────────────────
36
- program
37
- .command('start')
38
- .description('Start KernelBot Telegram bot')
39
- .action(async () => {
40
- showLogo();
41
-
42
- const config = loadConfig();
43
- createLogger(config);
44
- createAuditLogger();
45
- const logger = getLogger();
46
-
47
- // Startup checks
48
- const checks = [];
49
-
50
- checks.push(
51
- await showStartupCheck('Configuration loaded', async () => {
52
- if (!config.anthropic.api_key) throw new Error('ANTHROPIC_API_KEY not set');
53
- if (!config.telegram.bot_token) throw new Error('TELEGRAM_BOT_TOKEN not set');
54
- }),
55
- );
56
-
57
- checks.push(
58
- await showStartupCheck('Anthropic API connection', async () => {
59
- const client = new Anthropic({ apiKey: config.anthropic.api_key });
60
- await client.messages.create({
61
- model: config.anthropic.model,
62
- max_tokens: 16,
63
- messages: [{ role: 'user', content: 'ping' }],
64
- });
65
- }),
66
- );
67
-
68
- if (checks.some((c) => !c)) {
69
- showError('Startup checks failed. Fix the issues above and try again.');
70
- process.exit(1);
26
+ function showMenu() {
27
+ console.log('');
28
+ console.log(chalk.bold(' What would you like to do?\n'));
29
+ console.log(` ${chalk.cyan('1.')} Start bot`);
30
+ console.log(` ${chalk.cyan('2.')} Check connections`);
31
+ console.log(` ${chalk.cyan('3.')} View logs`);
32
+ console.log(` ${chalk.cyan('4.')} View audit logs`);
33
+ console.log(` ${chalk.cyan('5.')} Exit`);
34
+ console.log('');
35
+ }
36
+
37
+ function ask(rl, question) {
38
+ return new Promise((res) => rl.question(question, res));
39
+ }
40
+
41
+ function viewLog(filename) {
42
+ const paths = [
43
+ join(process.cwd(), filename),
44
+ join(homedir(), '.kernelbot', filename),
45
+ ];
46
+
47
+ for (const p of paths) {
48
+ if (existsSync(p)) {
49
+ const content = readFileSync(p, 'utf-8');
50
+ const lines = content.split('\n').filter(Boolean);
51
+ const recent = lines.slice(-30);
52
+ console.log(chalk.dim(`\n Showing last ${recent.length} entries from ${p}\n`));
53
+ for (const line of recent) {
54
+ try {
55
+ const entry = JSON.parse(line);
56
+ const time = entry.timestamp || '';
57
+ const level = entry.level || '';
58
+ const msg = entry.message || '';
59
+ const color = level === 'error' ? chalk.red : level === 'warn' ? chalk.yellow : chalk.dim;
60
+ console.log(` ${chalk.dim(time)} ${color(level)} ${msg}`);
61
+ } catch {
62
+ console.log(` ${line}`);
63
+ }
64
+ }
65
+ console.log('');
66
+ return;
71
67
  }
68
+ }
69
+ console.log(chalk.dim(`\n No ${filename} found yet.\n`));
70
+ }
72
71
 
73
- const conversationManager = new ConversationManager(config);
74
- const agent = new Agent({ config, conversationManager });
75
-
76
- startBot(config, agent);
77
- showStartupComplete();
72
+ async function runCheck(config) {
73
+ await showStartupCheck('ANTHROPIC_API_KEY', async () => {
74
+ if (!config.anthropic.api_key) throw new Error('Not set');
78
75
  });
79
76
 
80
- // ─── kernel run ──────────────────────────────────────────────
81
- program
82
- .command('run')
83
- .description('Run a one-off prompt through the agent (no Telegram)')
84
- .argument('<prompt>', 'The prompt to send')
85
- .action(async (prompt) => {
86
- const config = loadConfig();
87
- createLogger(config);
88
- createAuditLogger();
89
-
90
- if (!config.anthropic.api_key) {
91
- showError('ANTHROPIC_API_KEY not set. Run `kernelbot init` first.');
92
- process.exit(1);
93
- }
94
-
95
- const conversationManager = new ConversationManager(config);
96
- const agent = new Agent({ config, conversationManager });
77
+ await showStartupCheck('TELEGRAM_BOT_TOKEN', async () => {
78
+ if (!config.telegram.bot_token) throw new Error('Not set');
79
+ });
97
80
 
98
- const reply = await agent.processMessage('cli', prompt, {
99
- id: 'cli',
100
- username: 'cli',
81
+ await showStartupCheck('Anthropic API connection', async () => {
82
+ const client = new Anthropic({ apiKey: config.anthropic.api_key });
83
+ await client.messages.create({
84
+ model: config.anthropic.model,
85
+ max_tokens: 16,
86
+ messages: [{ role: 'user', content: 'ping' }],
101
87
  });
102
-
103
- console.log('\n' + reply);
104
88
  });
105
89
 
106
- // ─── kernel check ────────────────────────────────────────────
107
- program
108
- .command('check')
109
- .description('Validate configuration and test API connections')
110
- .action(async () => {
111
- showLogo();
112
-
113
- const config = loadConfig();
114
- createLogger(config);
90
+ await showStartupCheck('Telegram Bot API', async () => {
91
+ const res = await fetch(
92
+ `https://api.telegram.org/bot${config.telegram.bot_token}/getMe`,
93
+ );
94
+ const data = await res.json();
95
+ if (!data.ok) throw new Error(data.description || 'Invalid token');
96
+ });
115
97
 
116
- await showStartupCheck('Configuration file', async () => {
117
- // loadConfig already succeeded if we got here
118
- });
98
+ console.log(chalk.green('\n All checks passed.\n'));
99
+ }
119
100
 
120
- await showStartupCheck('ANTHROPIC_API_KEY', async () => {
121
- if (!config.anthropic.api_key) throw new Error('Not set');
122
- });
101
+ async function startBotFlow(config) {
102
+ createAuditLogger();
103
+ const logger = getLogger();
123
104
 
124
- await showStartupCheck('TELEGRAM_BOT_TOKEN', async () => {
125
- if (!config.telegram.bot_token) throw new Error('Not set');
126
- });
105
+ const checks = [];
127
106
 
128
- await showStartupCheck('Anthropic API connection', async () => {
107
+ checks.push(
108
+ await showStartupCheck('Anthropic API', async () => {
129
109
  const client = new Anthropic({ apiKey: config.anthropic.api_key });
130
110
  await client.messages.create({
131
111
  model: config.anthropic.model,
132
112
  max_tokens: 16,
133
113
  messages: [{ role: 'user', content: 'ping' }],
134
114
  });
135
- });
115
+ }),
116
+ );
136
117
 
118
+ checks.push(
137
119
  await showStartupCheck('Telegram Bot API', async () => {
138
120
  const res = await fetch(
139
121
  `https://api.telegram.org/bot${config.telegram.bot_token}/getMe`,
140
122
  );
141
123
  const data = await res.json();
142
124
  if (!data.ok) throw new Error(data.description || 'Invalid token');
143
- });
144
-
145
- console.log('\nAll checks complete.');
146
- });
147
-
148
- // ─── kernel init ─────────────────────────────────────────────
149
- program
150
- .command('init')
151
- .description('Interactive setup: create .env and config.yaml')
152
- .action(async () => {
153
- showLogo();
154
-
155
- const rl = createInterface({ input: process.stdin, output: process.stdout });
156
- const ask = (q) => new Promise((res) => rl.question(q, res));
157
-
158
- const apiKey = await ask('Anthropic API key: ');
159
- const botToken = await ask('Telegram Bot Token: ');
160
- const userId = await ask('Your Telegram User ID (leave blank for dev mode): ');
161
-
162
- rl.close();
163
-
164
- // Write .env
165
- const envContent = `ANTHROPIC_API_KEY=${apiKey}\nTELEGRAM_BOT_TOKEN=${botToken}\n`;
166
- writeFileSync('.env', envContent);
167
-
168
- // Write config.yaml
169
- const allowedUsers =
170
- userId.trim() ? `\n allowed_users:\n - ${userId.trim()}` : '\n allowed_users: []';
171
-
172
- const configContent = `bot:
173
- name: KernelBot
174
-
175
- anthropic:
176
- model: claude-sonnet-4-20250514
177
- max_tokens: 8192
178
- temperature: 0.3
179
- max_tool_depth: 25
180
-
181
- telegram:${allowedUsers}
182
-
183
- security:
184
- blocked_paths:
185
- - /etc/shadow
186
- - /etc/passwd
187
-
188
- logging:
189
- level: info
190
- max_file_size: 5242880
191
-
192
- conversation:
193
- max_history: 50
194
- `;
195
- writeFileSync('config.yaml', configContent);
125
+ }),
126
+ );
127
+
128
+ if (checks.some((c) => !c)) {
129
+ showError('Startup failed. Fix the issues above and try again.');
130
+ return false;
131
+ }
132
+
133
+ const conversationManager = new ConversationManager(config);
134
+ const agent = new Agent({ config, conversationManager });
135
+
136
+ startBot(config, agent);
137
+ showStartupComplete();
138
+ return true;
139
+ }
140
+
141
+ async function main() {
142
+ showLogo();
143
+
144
+ const config = await loadConfigInteractive();
145
+ createLogger(config);
146
+
147
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
148
+
149
+ let running = true;
150
+ while (running) {
151
+ showMenu();
152
+ const choice = await ask(rl, chalk.cyan(' > '));
153
+
154
+ switch (choice.trim()) {
155
+ case '1': {
156
+ rl.close();
157
+ const started = await startBotFlow(config);
158
+ if (!started) process.exit(1);
159
+ return; // bot is running, don't show menu again
160
+ }
161
+ case '2':
162
+ await runCheck(config);
163
+ break;
164
+ case '3':
165
+ viewLog('kernel.log');
166
+ break;
167
+ case '4':
168
+ viewLog('kernel-audit.log');
169
+ break;
170
+ case '5':
171
+ running = false;
172
+ break;
173
+ default:
174
+ console.log(chalk.dim(' Invalid choice.\n'));
175
+ }
176
+ }
196
177
 
197
- console.log('\nCreated .env and config.yaml');
198
- console.log('Run `kernelbot check` to verify, then `kernelbot start` to launch.');
199
- });
178
+ rl.close();
179
+ console.log(chalk.dim(' Goodbye.\n'));
180
+ }
200
181
 
201
- program.parse();
182
+ main().catch((err) => {
183
+ showError(err.message);
184
+ process.exit(1);
185
+ });
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "kernelbot",
3
- "version": "1.0.002",
3
+ "version": "1.0.004",
4
4
  "description": "KernelBot — AI engineering agent with full OS control",
5
5
  "type": "module",
6
- "author": "Abdullah Al-Tahrei <abdullah@altaheri.me>",
6
+ "author": "Abdullah Al-Taheri <abdullah@altaheri.me>",
7
7
  "bin": {
8
8
  "kernelbot": "./bin/kernel.js"
9
9
  },
@@ -1,8 +1,10 @@
1
- import { readFileSync, existsSync } from 'fs';
2
- import { join } from 'path';
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
3
  import { homedir } from 'os';
4
+ import { createInterface } from 'readline';
4
5
  import yaml from 'js-yaml';
5
6
  import dotenv from 'dotenv';
7
+ import chalk from 'chalk';
6
8
 
7
9
  const DEFAULTS = {
8
10
  bot: {
@@ -54,18 +56,96 @@ function deepMerge(target, source) {
54
56
  return result;
55
57
  }
56
58
 
59
+ function getConfigDir() {
60
+ return join(homedir(), '.kernelbot');
61
+ }
62
+
63
+ function getEnvPath() {
64
+ const cwdEnv = join(process.cwd(), '.env');
65
+ if (existsSync(cwdEnv)) return cwdEnv;
66
+ return join(getConfigDir(), '.env');
67
+ }
68
+
57
69
  function findConfigFile() {
58
70
  const cwdPath = join(process.cwd(), 'config.yaml');
59
71
  if (existsSync(cwdPath)) return cwdPath;
60
72
 
61
- const homePath = join(homedir(), '.kernelbot', 'config.yaml');
73
+ const homePath = join(getConfigDir(), 'config.yaml');
62
74
  if (existsSync(homePath)) return homePath;
63
75
 
64
76
  return null;
65
77
  }
66
78
 
79
+ function ask(rl, question) {
80
+ return new Promise((res) => rl.question(question, res));
81
+ }
82
+
83
+ async function promptForMissing(config) {
84
+ const missing = [];
85
+ if (!config.anthropic.api_key) missing.push('ANTHROPIC_API_KEY');
86
+ if (!config.telegram.bot_token) missing.push('TELEGRAM_BOT_TOKEN');
87
+
88
+ if (missing.length === 0) return config;
89
+
90
+ console.log(chalk.yellow('\n Missing credentials detected. Let\'s set them up.\n'));
91
+
92
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
93
+ const mutableConfig = JSON.parse(JSON.stringify(config));
94
+ const envLines = [];
95
+
96
+ // Read existing .env if any
97
+ const envPath = getEnvPath();
98
+ let existingEnv = '';
99
+ if (existsSync(envPath)) {
100
+ existingEnv = readFileSync(envPath, 'utf-8');
101
+ }
102
+
103
+ if (!mutableConfig.anthropic.api_key) {
104
+ const key = await ask(rl, chalk.cyan(' Anthropic API key: '));
105
+ mutableConfig.anthropic.api_key = key.trim();
106
+ envLines.push(`ANTHROPIC_API_KEY=${key.trim()}`);
107
+ }
108
+
109
+ if (!mutableConfig.telegram.bot_token) {
110
+ const token = await ask(rl, chalk.cyan(' Telegram Bot Token: '));
111
+ mutableConfig.telegram.bot_token = token.trim();
112
+ envLines.push(`TELEGRAM_BOT_TOKEN=${token.trim()}`);
113
+ }
114
+
115
+ rl.close();
116
+
117
+ // Save to ~/.kernelbot/.env so it persists globally
118
+ if (envLines.length > 0) {
119
+ const configDir = getConfigDir();
120
+ mkdirSync(configDir, { recursive: true });
121
+ const savePath = join(configDir, '.env');
122
+
123
+ // Merge with existing content
124
+ let content = existingEnv ? existingEnv.trimEnd() + '\n' : '';
125
+ for (const line of envLines) {
126
+ const key = line.split('=')[0];
127
+ // Replace if exists, append if not
128
+ const regex = new RegExp(`^${key}=.*$`, 'm');
129
+ if (regex.test(content)) {
130
+ content = content.replace(regex, line);
131
+ } else {
132
+ content += line + '\n';
133
+ }
134
+ }
135
+ writeFileSync(savePath, content);
136
+ console.log(chalk.dim(`\n Saved to ${savePath}\n`));
137
+ }
138
+
139
+ return mutableConfig;
140
+ }
141
+
67
142
  export function loadConfig() {
143
+ // Load .env from CWD first, then from ~/.kernelbot/
68
144
  dotenv.config();
145
+ const globalEnv = join(getConfigDir(), '.env');
146
+ if (existsSync(globalEnv)) {
147
+ dotenv.config({ path: globalEnv });
148
+ }
69
149
 
70
150
  let fileConfig = {};
71
151
  const configPath = findConfigFile();
@@ -84,5 +164,10 @@ export function loadConfig() {
84
164
  config.telegram.bot_token = process.env.TELEGRAM_BOT_TOKEN;
85
165
  }
86
166
 
87
- return Object.freeze(config);
167
+ return config;
168
+ }
169
+
170
+ export async function loadConfigInteractive() {
171
+ const config = loadConfig();
172
+ return await promptForMissing(config);
88
173
  }
@@ -59,29 +59,6 @@ export function showError(msg) {
59
59
  );
60
60
  }
61
61
 
62
- export function showHelp() {
63
- showLogo();
64
- console.log(
65
- boxen(
66
- [
67
- chalk.bold('Commands:'),
68
- '',
69
- ` ${chalk.cyan('kernelbot start')} ${chalk.dim('Launch the Telegram bot')}`,
70
- ` ${chalk.cyan('kernelbot run')} ${chalk.yellow('"prompt"')} ${chalk.dim('One-off agent call (no Telegram)')}`,
71
- ` ${chalk.cyan('kernelbot check')} ${chalk.dim('Validate config & test APIs')}`,
72
- ` ${chalk.cyan('kernelbot init')} ${chalk.dim('Interactive setup wizard')}`,
73
- '',
74
- chalk.dim(` kernelbot --help Show all options`),
75
- ].join('\n'),
76
- {
77
- padding: 1,
78
- borderStyle: 'round',
79
- borderColor: 'cyan',
80
- },
81
- ),
82
- );
83
- }
84
-
85
62
  export function createSpinner(text) {
86
63
  return ora({ text, color: 'cyan' });
87
64
  }