kernelbot 1.0.14 → 1.0.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kernelbot",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "KernelBot — AI engineering agent with full OS control",
5
5
  "type": "module",
6
6
  "author": "Abdullah Al-Taheri <abdullah@altaheri.me>",
package/src/coder.js CHANGED
@@ -1,20 +1,96 @@
1
1
  import { spawn } from 'child_process';
2
+ import { existsSync, writeFileSync, mkdirSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
2
5
  import { getLogger } from './utils/logger.js';
3
6
 
7
+ function ensureClaudeCodeSetup() {
8
+ const logger = getLogger();
9
+ const claudeJson = join(homedir(), '.claude.json');
10
+ const claudeDir = join(homedir(), '.claude');
11
+
12
+ // Create ~/.claude/ directory if missing
13
+ if (!existsSync(claudeDir)) {
14
+ mkdirSync(claudeDir, { recursive: true });
15
+ logger.info('Created ~/.claude/ directory');
16
+ }
17
+
18
+ // Create ~/.claude.json with onboarding completed if missing
19
+ if (!existsSync(claudeJson)) {
20
+ const defaults = {
21
+ hasCompletedOnboarding: true,
22
+ theme: 'dark',
23
+ shiftEnterKeyBindingInstalled: true,
24
+ };
25
+ writeFileSync(claudeJson, JSON.stringify(defaults, null, 2));
26
+ logger.info('Created ~/.claude.json with default settings (skipping setup wizard)');
27
+ }
28
+ }
29
+
30
+ function parseStreamEvent(line, onOutput, logger) {
31
+ let event;
32
+ try {
33
+ event = JSON.parse(line);
34
+ } catch {
35
+ return; // skip non-JSON lines
36
+ }
37
+
38
+ // Assistant text message
39
+ if (event.type === 'message' && event.role === 'assistant') {
40
+ const texts = (event.content || [])
41
+ .filter((b) => b.type === 'text' && b.text?.trim())
42
+ .map((b) => b.text.trim());
43
+ if (texts.length > 0) {
44
+ const msg = texts.join('\n');
45
+ logger.info(`Claude Code: ${msg.slice(0, 200)}`);
46
+ if (onOutput) onOutput(`💬 *Claude Code:*\n${msg}`).catch(() => {});
47
+ }
48
+ }
49
+
50
+ // Tool use
51
+ if (event.type === 'tool_use') {
52
+ const name = event.name || 'unknown';
53
+ const input = event.input || {};
54
+ const summary = input.command || input.file_path || input.pattern || input.query || JSON.stringify(input).slice(0, 100);
55
+ logger.info(`Claude Code tool: ${name}: ${String(summary).slice(0, 150)}`);
56
+ if (onOutput) onOutput(`🔨 \`${name}: ${String(summary).slice(0, 150)}\``).catch(() => {});
57
+ }
58
+
59
+ // Result
60
+ if (event.type === 'result') {
61
+ const status = event.status || 'done';
62
+ const duration = event.duration_ms ? `${(event.duration_ms / 1000).toFixed(1)}s` : '';
63
+ logger.info(`Claude Code finished: ${status} ${duration}`);
64
+ if (onOutput) onOutput(`✅ Claude Code finished (${status}${duration ? ` in ${duration}` : ''})`).catch(() => {});
65
+ }
66
+ }
67
+
4
68
  export class ClaudeCodeSpawner {
5
69
  constructor(config) {
6
70
  this.maxTurns = config.claude_code?.max_turns || 50;
7
71
  this.timeout = (config.claude_code?.timeout_seconds || 600) * 1000;
72
+ this.model = config.claude_code?.model || null;
8
73
  }
9
74
 
10
75
  async run({ workingDirectory, prompt, maxTurns, onOutput }) {
11
76
  const logger = getLogger();
12
77
  const turns = maxTurns || this.maxTurns;
13
78
 
79
+ // Auto-setup Claude Code if not configured
80
+ ensureClaudeCodeSetup();
81
+
14
82
  logger.info(`Spawning Claude Code in ${workingDirectory}`);
15
83
 
16
84
  return new Promise((resolve, reject) => {
17
- const args = ['-p', prompt, '--max-turns', String(turns), '--output-format', 'text'];
85
+ const args = [
86
+ '-p', prompt,
87
+ '--max-turns', String(turns),
88
+ '--output-format', 'stream-json',
89
+ '--dangerously-skip-permissions',
90
+ ];
91
+ if (this.model) {
92
+ args.push('--model', this.model);
93
+ }
18
94
 
19
95
  const child = spawn('claude', args, {
20
96
  cwd: workingDirectory,
@@ -22,25 +98,33 @@ export class ClaudeCodeSpawner {
22
98
  stdio: ['ignore', 'pipe', 'pipe'],
23
99
  });
24
100
 
25
- let stdout = '';
101
+ let fullOutput = '';
26
102
  let stderr = '';
27
103
  let buffer = '';
104
+ let resultText = '';
28
105
 
29
106
  child.stdout.on('data', (data) => {
30
- const chunk = data.toString();
31
- stdout += chunk;
32
- buffer += chunk;
107
+ buffer += data.toString();
33
108
 
34
- // Stream output in meaningful chunks (split on newlines)
109
+ // Process complete JSON lines
35
110
  const lines = buffer.split('\n');
36
- buffer = lines.pop(); // keep incomplete line in buffer
37
-
38
- if (lines.length > 0 && onOutput) {
39
- const text = lines.join('\n').trim();
40
- if (text) {
41
- logger.info(`Claude Code output: ${text.slice(0, 200)}`);
42
- onOutput(text).catch(() => {});
43
- }
111
+ buffer = lines.pop(); // keep incomplete line
112
+
113
+ for (const line of lines) {
114
+ const trimmed = line.trim();
115
+ if (!trimmed) continue;
116
+
117
+ fullOutput += trimmed + '\n';
118
+
119
+ // Extract final result text
120
+ try {
121
+ const event = JSON.parse(trimmed);
122
+ if (event.type === 'result' && event.result) {
123
+ resultText = event.result;
124
+ }
125
+ } catch {}
126
+
127
+ parseStreamEvent(trimmed, onOutput, logger);
44
128
  }
45
129
  });
46
130
 
@@ -56,16 +140,23 @@ export class ClaudeCodeSpawner {
56
140
  child.on('close', (code) => {
57
141
  clearTimeout(timer);
58
142
 
59
- // Flush remaining buffer
60
- if (buffer.trim() && onOutput) {
61
- onOutput(buffer.trim()).catch(() => {});
143
+ // Process remaining buffer
144
+ if (buffer.trim()) {
145
+ fullOutput += buffer.trim();
146
+ try {
147
+ const event = JSON.parse(buffer.trim());
148
+ if (event.type === 'result' && event.result) {
149
+ resultText = event.result;
150
+ }
151
+ } catch {}
152
+ parseStreamEvent(buffer.trim(), onOutput, logger);
62
153
  }
63
154
 
64
- if (code !== 0 && !stdout) {
155
+ if (code !== 0 && !fullOutput) {
65
156
  reject(new Error(`Claude Code exited with code ${code}: ${stderr}`));
66
157
  } else {
67
158
  resolve({
68
- output: stdout.trim(),
159
+ output: resultText || fullOutput.trim(),
69
160
  stderr: stderr.trim(),
70
161
  exitCode: code,
71
162
  });
@@ -10,12 +10,15 @@ You have full access to the operating system through your tools:
10
10
  ${toolList}
11
11
 
12
12
  ## Coding Tasks (writing code, fixing bugs, reviewing code, scaffolding projects)
13
+ IMPORTANT: You MUST NOT write code yourself using read_file/write_file. ALWAYS delegate coding to Claude Code.
13
14
  1. Use git tools to clone the repo and create a branch
14
- 2. Use spawn_claude_code to do the actual coding work inside the repo
15
+ 2. Use spawn_claude_code to do the actual coding work inside the repo — give it a clear, detailed prompt describing exactly what to build or fix
15
16
  3. After Claude Code finishes, use git tools to commit and push
16
17
  4. Use GitHub tools to create the PR
17
18
  5. Report back with the PR link
18
19
 
20
+ You are the orchestrator. Claude Code is the coder. Never use read_file + write_file to modify source code — that's Claude Code's job. You handle git, GitHub, and infrastructure. Claude Code handles all code changes.
21
+
19
22
  ## Non-Coding Tasks (monitoring, deploying, restarting services, checking status)
20
23
  - Use OS, Docker, process, network, and monitoring tools directly
21
24
  - No need to spawn Claude Code for these
@@ -41,9 +41,7 @@ export const handlers = {
41
41
  workingDirectory: params.working_directory,
42
42
  prompt: params.prompt,
43
43
  maxTurns: params.max_turns,
44
- onOutput: context.onUpdate
45
- ? (text) => context.onUpdate(`📟 \`Claude Code:\`\n${text}`)
46
- : null,
44
+ onOutput: context.onUpdate || null,
47
45
  });
48
46
  return { success: true, output: result.output };
49
47
  } catch (err) {
@@ -21,6 +21,7 @@ const DEFAULTS = {
21
21
  allowed_users: [],
22
22
  },
23
23
  claude_code: {
24
+ model: 'claude-opus-4-6',
24
25
  max_turns: 50,
25
26
  timeout_seconds: 600,
26
27
  workspace_dir: null, // defaults to ~/.kernelbot/workspaces