kernelbot 1.0.1 → 1.0.3

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
package/bin/kernel.js CHANGED
@@ -4,10 +4,12 @@
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,
@@ -21,177 +23,163 @@ import { Agent } from '../src/agent.js';
21
23
  import { startBot } from '../src/bot.js';
22
24
  import Anthropic from '@anthropic-ai/sdk';
23
25
 
24
- const program = new Command();
25
-
26
- program
27
- .name('kernelbot')
28
- .description('KernelBot AI engineering agent with full OS control')
29
- .version('1.0.0');
30
-
31
- // ─── kernel start ────────────────────────────────────────────
32
- program
33
- .command('start')
34
- .description('Start KernelBot Telegram bot')
35
- .action(async () => {
36
- showLogo();
37
-
38
- const config = loadConfig();
39
- createLogger(config);
40
- createAuditLogger();
41
- const logger = getLogger();
42
-
43
- // Startup checks
44
- const checks = [];
45
-
46
- checks.push(
47
- await showStartupCheck('Configuration loaded', async () => {
48
- if (!config.anthropic.api_key) throw new Error('ANTHROPIC_API_KEY not set');
49
- if (!config.telegram.bot_token) throw new Error('TELEGRAM_BOT_TOKEN not set');
50
- }),
51
- );
52
-
53
- checks.push(
54
- await showStartupCheck('Anthropic API connection', async () => {
55
- const client = new Anthropic({ apiKey: config.anthropic.api_key });
56
- await client.messages.create({
57
- model: config.anthropic.model,
58
- max_tokens: 16,
59
- messages: [{ role: 'user', content: 'ping' }],
60
- });
61
- }),
62
- );
63
-
64
- if (checks.some((c) => !c)) {
65
- showError('Startup checks failed. Fix the issues above and try again.');
66
- 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;
67
67
  }
68
+ }
69
+ console.log(chalk.dim(`\n No ${filename} found yet.\n`));
70
+ }
68
71
 
69
- const conversationManager = new ConversationManager(config);
70
- const agent = new Agent({ config, conversationManager });
71
-
72
- startBot(config, agent);
73
- showStartupComplete();
72
+ async function runCheck(config) {
73
+ await showStartupCheck('ANTHROPIC_API_KEY', async () => {
74
+ if (!config.anthropic.api_key) throw new Error('Not set');
74
75
  });
75
76
 
76
- // ─── kernel run ──────────────────────────────────────────────
77
- program
78
- .command('run')
79
- .description('Run a one-off prompt through the agent (no Telegram)')
80
- .argument('<prompt>', 'The prompt to send')
81
- .action(async (prompt) => {
82
- const config = loadConfig();
83
- createLogger(config);
84
- createAuditLogger();
85
-
86
- if (!config.anthropic.api_key) {
87
- showError('ANTHROPIC_API_KEY not set. Run `kernelbot init` first.');
88
- process.exit(1);
89
- }
90
-
91
- const conversationManager = new ConversationManager(config);
92
- 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
+ });
93
80
 
94
- const reply = await agent.processMessage('cli', prompt, {
95
- id: 'cli',
96
- 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' }],
97
87
  });
98
-
99
- console.log('\n' + reply);
100
88
  });
101
89
 
102
- // ─── kernel check ────────────────────────────────────────────
103
- program
104
- .command('check')
105
- .description('Validate configuration and test API connections')
106
- .action(async () => {
107
- showLogo();
108
-
109
- const config = loadConfig();
110
- 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
+ });
111
97
 
112
- await showStartupCheck('Configuration file', async () => {
113
- // loadConfig already succeeded if we got here
114
- });
98
+ console.log(chalk.green('\n All checks passed.\n'));
99
+ }
115
100
 
116
- await showStartupCheck('ANTHROPIC_API_KEY', async () => {
117
- if (!config.anthropic.api_key) throw new Error('Not set');
118
- });
101
+ async function startBotFlow(config) {
102
+ createAuditLogger();
103
+ const logger = getLogger();
119
104
 
120
- await showStartupCheck('TELEGRAM_BOT_TOKEN', async () => {
121
- if (!config.telegram.bot_token) throw new Error('Not set');
122
- });
105
+ const checks = [];
123
106
 
124
- await showStartupCheck('Anthropic API connection', async () => {
107
+ checks.push(
108
+ await showStartupCheck('Anthropic API', async () => {
125
109
  const client = new Anthropic({ apiKey: config.anthropic.api_key });
126
110
  await client.messages.create({
127
111
  model: config.anthropic.model,
128
112
  max_tokens: 16,
129
113
  messages: [{ role: 'user', content: 'ping' }],
130
114
  });
131
- });
115
+ }),
116
+ );
132
117
 
118
+ checks.push(
133
119
  await showStartupCheck('Telegram Bot API', async () => {
134
120
  const res = await fetch(
135
121
  `https://api.telegram.org/bot${config.telegram.bot_token}/getMe`,
136
122
  );
137
123
  const data = await res.json();
138
124
  if (!data.ok) throw new Error(data.description || 'Invalid token');
139
- });
140
-
141
- console.log('\nAll checks complete.');
142
- });
143
-
144
- // ─── kernel init ─────────────────────────────────────────────
145
- program
146
- .command('init')
147
- .description('Interactive setup: create .env and config.yaml')
148
- .action(async () => {
149
- showLogo();
150
-
151
- const rl = createInterface({ input: process.stdin, output: process.stdout });
152
- const ask = (q) => new Promise((res) => rl.question(q, res));
153
-
154
- const apiKey = await ask('Anthropic API key: ');
155
- const botToken = await ask('Telegram Bot Token: ');
156
- const userId = await ask('Your Telegram User ID (leave blank for dev mode): ');
157
-
158
- rl.close();
159
-
160
- // Write .env
161
- const envContent = `ANTHROPIC_API_KEY=${apiKey}\nTELEGRAM_BOT_TOKEN=${botToken}\n`;
162
- writeFileSync('.env', envContent);
163
-
164
- // Write config.yaml
165
- const allowedUsers =
166
- userId.trim() ? `\n allowed_users:\n - ${userId.trim()}` : '\n allowed_users: []';
167
-
168
- const configContent = `bot:
169
- name: KernelBot
170
-
171
- anthropic:
172
- model: claude-sonnet-4-20250514
173
- max_tokens: 8192
174
- temperature: 0.3
175
- max_tool_depth: 25
176
-
177
- telegram:${allowedUsers}
178
-
179
- security:
180
- blocked_paths:
181
- - /etc/shadow
182
- - /etc/passwd
183
-
184
- logging:
185
- level: info
186
- max_file_size: 5242880
187
-
188
- conversation:
189
- max_history: 50
190
- `;
191
- 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
+ }
192
177
 
193
- console.log('\nCreated .env and config.yaml');
194
- console.log('Run `kernelbot check` to verify, then `kernelbot start` to launch.');
195
- });
178
+ rl.close();
179
+ console.log(chalk.dim(' Goodbye.\n'));
180
+ }
196
181
 
197
- program.parse();
182
+ main().catch((err) => {
183
+ showError(err.message);
184
+ process.exit(1);
185
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kernelbot",
3
- "version": "1.0.001",
3
+ "version": "1.0.003",
4
4
  "description": "KernelBot — AI engineering agent with full OS control",
5
5
  "type": "module",
6
6
  "author": "Abdullah Al-Tahrei <abdullah@altaheri.me>",
@@ -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
  }