kernelbot 1.0.15 → 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.15",
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,6 +1,70 @@
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;
@@ -12,10 +76,18 @@ export class ClaudeCodeSpawner {
12
76
  const logger = getLogger();
13
77
  const turns = maxTurns || this.maxTurns;
14
78
 
79
+ // Auto-setup Claude Code if not configured
80
+ ensureClaudeCodeSetup();
81
+
15
82
  logger.info(`Spawning Claude Code in ${workingDirectory}`);
16
83
 
17
84
  return new Promise((resolve, reject) => {
18
- 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
+ ];
19
91
  if (this.model) {
20
92
  args.push('--model', this.model);
21
93
  }
@@ -26,25 +98,33 @@ export class ClaudeCodeSpawner {
26
98
  stdio: ['ignore', 'pipe', 'pipe'],
27
99
  });
28
100
 
29
- let stdout = '';
101
+ let fullOutput = '';
30
102
  let stderr = '';
31
103
  let buffer = '';
104
+ let resultText = '';
32
105
 
33
106
  child.stdout.on('data', (data) => {
34
- const chunk = data.toString();
35
- stdout += chunk;
36
- buffer += chunk;
107
+ buffer += data.toString();
37
108
 
38
- // Stream output in meaningful chunks (split on newlines)
109
+ // Process complete JSON lines
39
110
  const lines = buffer.split('\n');
40
- buffer = lines.pop(); // keep incomplete line in buffer
41
-
42
- if (lines.length > 0 && onOutput) {
43
- const text = lines.join('\n').trim();
44
- if (text) {
45
- logger.info(`Claude Code output: ${text.slice(0, 200)}`);
46
- onOutput(text).catch(() => {});
47
- }
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);
48
128
  }
49
129
  });
50
130
 
@@ -60,16 +140,23 @@ export class ClaudeCodeSpawner {
60
140
  child.on('close', (code) => {
61
141
  clearTimeout(timer);
62
142
 
63
- // Flush remaining buffer
64
- if (buffer.trim() && onOutput) {
65
- 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);
66
153
  }
67
154
 
68
- if (code !== 0 && !stdout) {
155
+ if (code !== 0 && !fullOutput) {
69
156
  reject(new Error(`Claude Code exited with code ${code}: ${stderr}`));
70
157
  } else {
71
158
  resolve({
72
- output: stdout.trim(),
159
+ output: resultText || fullOutput.trim(),
73
160
  stderr: stderr.trim(),
74
161
  exitCode: code,
75
162
  });
@@ -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) {