@kernel.chat/kbot 3.15.1 → 3.17.0

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.
@@ -0,0 +1,274 @@
1
+ // kbot iMessage Agent — free SMS/iMessage agent via macOS Messages.app
2
+ //
3
+ // Usage:
4
+ // kbot imessage-agent start # start monitoring
5
+ // kbot imessage-agent start --numbers +1234567890 # monitor specific numbers
6
+ // kbot imessage-agent status # show status
7
+ //
8
+ // Requires:
9
+ // - macOS with Messages.app signed in to iCloud
10
+ // - Full Disk Access for Terminal (System Preferences > Privacy > Full Disk Access)
11
+ // - Ollama running locally
12
+ //
13
+ // Cost: $0 — uses local AI + Apple iMessage infrastructure
14
+ import { execSync } from 'node:child_process';
15
+ import { homedir, platform } from 'node:os';
16
+ // ── Constants ──
17
+ const DEFAULT_OLLAMA_URL = 'http://localhost:11434';
18
+ const DEFAULT_MODEL = 'qwen2.5-coder:32b';
19
+ const DEFAULT_POLL_INTERVAL = 10_000;
20
+ const SYSTEM_PROMPT = `You are a personal AI agent communicating via text message (iMessage).
21
+
22
+ Keep responses SHORT — this is texting, not email. 2-3 sentences max unless they ask for detail.
23
+ Be conversational, casual, helpful. Like texting a smart friend.
24
+ Use line breaks between ideas. No bullet points or headers — it's a text.
25
+ If they send an image, acknowledge it and help with what they're asking.
26
+ Never say "as an AI". Just help.`;
27
+ // ── iMessage via AppleScript ──
28
+ export function sendIMessage(phoneNumber, text) {
29
+ if (platform() !== 'darwin')
30
+ return false;
31
+ const escaped = text
32
+ .replace(/\\/g, '\\\\')
33
+ .replace(/"/g, '\\"')
34
+ .replace(/\n/g, '\\n');
35
+ const script = `
36
+ tell application "Messages"
37
+ set targetService to 1st account whose service type = iMessage
38
+ set targetBuddy to participant "${phoneNumber}" of targetService
39
+ send "${escaped}" to targetBuddy
40
+ end tell
41
+ `;
42
+ try {
43
+ execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, { timeout: 10000, stdio: 'pipe' });
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ export function getRecentMessages(phoneNumber, count = 5) {
51
+ if (platform() !== 'darwin')
52
+ return [];
53
+ const dbPath = `${homedir()}/Library/Messages/chat.db`;
54
+ const cleanNumber = phoneNumber.replace('+', '');
55
+ const query = `
56
+ SELECT
57
+ m.text,
58
+ m.is_from_me,
59
+ datetime(m.date/1000000000 + 978307200, 'unixepoch', 'localtime') as msg_date
60
+ FROM message m
61
+ JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
62
+ JOIN chat c ON cmj.chat_id = c.ROWID
63
+ WHERE c.chat_identifier LIKE '%${cleanNumber}%'
64
+ AND m.text IS NOT NULL
65
+ AND m.text != ''
66
+ ORDER BY m.date DESC
67
+ LIMIT ${count}
68
+ `;
69
+ try {
70
+ const result = execSync(`sqlite3 -json "${dbPath}" "${query}"`, { timeout: 5000, encoding: 'utf8' });
71
+ const messages = JSON.parse(result || '[]');
72
+ return messages.map((m) => ({
73
+ text: m.text,
74
+ isFromMe: m.is_from_me === 1,
75
+ date: m.msg_date,
76
+ })).reverse();
77
+ }
78
+ catch {
79
+ return [];
80
+ }
81
+ }
82
+ function getLastIncomingMessage(phoneNumber) {
83
+ if (platform() !== 'darwin')
84
+ return null;
85
+ const dbPath = `${homedir()}/Library/Messages/chat.db`;
86
+ const cleanNumber = phoneNumber.replace('+', '');
87
+ const query = `
88
+ SELECT
89
+ m.text,
90
+ datetime(m.date/1000000000 + 978307200, 'unixepoch', 'localtime') as msg_date
91
+ FROM message m
92
+ JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
93
+ JOIN chat c ON cmj.chat_id = c.ROWID
94
+ WHERE c.chat_identifier LIKE '%${cleanNumber}%'
95
+ AND m.is_from_me = 0
96
+ AND m.text IS NOT NULL
97
+ AND m.text != ''
98
+ ORDER BY m.date DESC
99
+ LIMIT 1
100
+ `;
101
+ try {
102
+ const result = execSync(`sqlite3 -json "${dbPath}" "${query}"`, { timeout: 5000, encoding: 'utf8' });
103
+ const messages = JSON.parse(result || '[]');
104
+ if (messages.length > 0) {
105
+ return { text: messages[0].text, date: messages[0].msg_date };
106
+ }
107
+ }
108
+ catch { /* ignore */ }
109
+ return null;
110
+ }
111
+ // ── Ollama ──
112
+ async function askOllama(messages, ollamaUrl, model) {
113
+ let prompt = SYSTEM_PROMPT + '\n\n';
114
+ for (const msg of messages) {
115
+ if (msg.role === 'user')
116
+ prompt += `Them: ${msg.content}\n\n`;
117
+ else
118
+ prompt += `You: ${msg.content}\n\n`;
119
+ }
120
+ prompt += 'You:';
121
+ try {
122
+ const res = await fetch(`${ollamaUrl}/api/generate`, {
123
+ method: 'POST',
124
+ headers: { 'Content-Type': 'application/json' },
125
+ body: JSON.stringify({
126
+ model,
127
+ prompt,
128
+ stream: false,
129
+ options: { num_predict: 500, temperature: 0.7 },
130
+ }),
131
+ });
132
+ if (!res.ok)
133
+ return '';
134
+ const data = await res.json();
135
+ return (data.response?.trim() ?? '')
136
+ .replace(/<think>[\s\S]*?<\/think>/g, '')
137
+ .replace(/<\/?think>/g, '')
138
+ .trim();
139
+ }
140
+ catch {
141
+ return '';
142
+ }
143
+ }
144
+ // ── Web Search ──
145
+ async function webSearch(query) {
146
+ try {
147
+ const encoded = encodeURIComponent(query);
148
+ const res = await fetch(`https://api.duckduckgo.com/?q=${encoded}&format=json&no_html=1&skip_disambig=1`, {
149
+ headers: { 'User-Agent': 'KernelAgent/1.0' },
150
+ signal: AbortSignal.timeout(8000),
151
+ });
152
+ const data = await res.json();
153
+ const parts = [];
154
+ if (data.AbstractText)
155
+ parts.push(data.AbstractText);
156
+ if (data.Answer)
157
+ parts.push(data.Answer);
158
+ if (data.RelatedTopics?.length) {
159
+ for (const t of data.RelatedTopics.slice(0, 3)) {
160
+ if (t.Text)
161
+ parts.push(t.Text);
162
+ }
163
+ }
164
+ return parts.join('\n').slice(0, 500);
165
+ }
166
+ catch {
167
+ return '';
168
+ }
169
+ }
170
+ // ── Agent State ──
171
+ const agentState = {
172
+ running: false,
173
+ messagesProcessed: 0,
174
+ lastCheck: '',
175
+ errors: [],
176
+ };
177
+ export function getIMessageAgentState() {
178
+ return { ...agentState };
179
+ }
180
+ let pollTimer = null;
181
+ const processedMessages = new Map();
182
+ export async function startIMessageAgent(config) {
183
+ if (platform() !== 'darwin') {
184
+ throw new Error('iMessage agent is only available on macOS');
185
+ }
186
+ if (agentState.running) {
187
+ throw new Error('iMessage agent is already running');
188
+ }
189
+ // Optional Supabase for conversation logging
190
+ let svc = null;
191
+ if (config.supabaseUrl && config.supabaseKey) {
192
+ const { createClient } = await import('@supabase/supabase-js');
193
+ svc = createClient(config.supabaseUrl, config.supabaseKey, {
194
+ auth: { persistSession: false, autoRefreshToken: false },
195
+ });
196
+ }
197
+ // Initialize with last known messages to avoid replaying old ones
198
+ for (const number of config.numbers) {
199
+ const lastMsg = getLastIncomingMessage(number);
200
+ if (lastMsg) {
201
+ processedMessages.set(number, `${lastMsg.date}:${lastMsg.text}`);
202
+ }
203
+ }
204
+ async function checkAndRespond() {
205
+ agentState.lastCheck = new Date().toISOString();
206
+ for (const number of config.numbers) {
207
+ try {
208
+ const lastMsg = getLastIncomingMessage(number);
209
+ if (!lastMsg)
210
+ continue;
211
+ const lastProcessed = processedMessages.get(number);
212
+ if (lastProcessed === `${lastMsg.date}:${lastMsg.text}`)
213
+ continue;
214
+ console.log(`[${new Date().toISOString().slice(11, 19)}] Text from ${number}: "${lastMsg.text}"`);
215
+ // Build conversation history
216
+ const recentMessages = getRecentMessages(number, 10);
217
+ const convoHistory = recentMessages.map(m => ({
218
+ role: m.isFromMe ? 'assistant' : 'user',
219
+ content: m.text,
220
+ }));
221
+ // Web search if needed
222
+ const searchTriggers = /\b(what is|how much|latest|current|price|news|who is|look up|search|find|where|when did)\b/i;
223
+ if (searchTriggers.test(lastMsg.text)) {
224
+ const results = await webSearch(lastMsg.text);
225
+ if (results)
226
+ convoHistory.push({ role: 'user', content: `[Web search context]\n${results}` });
227
+ }
228
+ // Generate response
229
+ const reply = await askOllama(convoHistory, config.ollamaUrl, config.ollamaModel);
230
+ if (!reply) {
231
+ processedMessages.set(number, `${lastMsg.date}:${lastMsg.text}`);
232
+ continue;
233
+ }
234
+ // Keep it short for texting
235
+ const shortReply = reply.length > 500 ? reply.slice(0, 497) + '...' : reply;
236
+ // Send via iMessage
237
+ const sent = sendIMessage(number, shortReply);
238
+ console.log(` ${sent ? 'Sent via iMessage' : 'FAILED to send'}`);
239
+ // Store in DB if available
240
+ if (svc) {
241
+ await svc.from('agent_conversations').insert({
242
+ email: number, name: 'iMessage User', role: 'user', content: lastMsg.text, subject: 'iMessage',
243
+ }).catch(() => { });
244
+ await svc.from('agent_conversations').insert({
245
+ email: number, name: 'Kernel Agent', role: 'assistant', content: shortReply, subject: 'iMessage',
246
+ }).catch(() => { });
247
+ }
248
+ processedMessages.set(number, `${lastMsg.date}:${lastMsg.text}`);
249
+ agentState.messagesProcessed++;
250
+ }
251
+ catch (err) {
252
+ const errMsg = err instanceof Error ? err.message : String(err);
253
+ agentState.errors.push(`${new Date().toISOString().slice(11, 19)}: ${errMsg}`);
254
+ if (agentState.errors.length > 20)
255
+ agentState.errors = agentState.errors.slice(-20);
256
+ }
257
+ }
258
+ }
259
+ agentState.running = true;
260
+ agentState.messagesProcessed = 0;
261
+ agentState.errors = [];
262
+ await checkAndRespond();
263
+ pollTimer = setInterval(checkAndRespond, config.pollInterval);
264
+ console.log(`iMessage agent polling every ${config.pollInterval / 1000}s. Ctrl+C to stop.`);
265
+ }
266
+ export function stopIMessageAgent() {
267
+ if (pollTimer) {
268
+ clearInterval(pollTimer);
269
+ pollTimer = null;
270
+ }
271
+ agentState.running = false;
272
+ }
273
+ export { DEFAULT_OLLAMA_URL, DEFAULT_MODEL, DEFAULT_POLL_INTERVAL };
274
+ //# sourceMappingURL=imessage-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imessage-agent.js","sourceRoot":"","sources":["../src/imessage-agent.ts"],"names":[],"mappings":"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"}
package/dist/init.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ export interface KbotProjectConfig {
2
+ /** Detected project name */
3
+ name: string;
4
+ /** Detected language */
5
+ language: string;
6
+ /** Detected framework (if any) */
7
+ framework?: string;
8
+ /** Package manager */
9
+ packageManager?: string;
10
+ /** Preferred default agent */
11
+ defaultAgent: string;
12
+ /** Key files kbot should know about */
13
+ keyFiles: string[];
14
+ /** Custom commands detected from package.json/Makefile/etc */
15
+ commands: Record<string, string>;
16
+ /** Forged tools created during init */
17
+ forgedTools: string[];
18
+ /** When this config was generated */
19
+ createdAt: string;
20
+ }
21
+ export declare function initProject(root: string): Promise<KbotProjectConfig>;
22
+ export declare function formatInitReport(config: KbotProjectConfig): string;
23
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,iBAAiB;IAChC,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sBAAsB;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,8BAA8B;IAC9B,YAAY,EAAE,MAAM,CAAA;IACpB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,uCAAuC;IACvC,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAA;CAClB;AAyQD,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAqD1E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAkBlE"}
package/dist/init.js ADDED
@@ -0,0 +1,355 @@
1
+ // kbot init — 60-second project onboarding
2
+ //
3
+ // Scans a repo, detects stack, generates .kbot.json config,
4
+ // creates project-specific forged tools, and prints a ready message.
5
+ //
6
+ // Usage: kbot init
7
+ //
8
+ // This is the first thing a new user runs. It must be fast,
9
+ // useful, and make kbot feel like it belongs in the project.
10
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
11
+ import { join, basename } from 'node:path';
12
+ import { execSync } from 'node:child_process';
13
+ import { homedir } from 'node:os';
14
+ // ── Detection ──
15
+ function quickExec(cmd, timeoutMs = 2000) {
16
+ try {
17
+ return execSync(cmd, { encoding: 'utf-8', timeout: timeoutMs, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
18
+ }
19
+ catch {
20
+ return '';
21
+ }
22
+ }
23
+ function detectProjectName(root) {
24
+ // Try package.json first
25
+ const pkgPath = join(root, 'package.json');
26
+ if (existsSync(pkgPath)) {
27
+ try {
28
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
29
+ if (pkg.name)
30
+ return pkg.name;
31
+ }
32
+ catch { /* ignore */ }
33
+ }
34
+ // Try Cargo.toml
35
+ const cargoPath = join(root, 'Cargo.toml');
36
+ if (existsSync(cargoPath)) {
37
+ const match = readFileSync(cargoPath, 'utf8').match(/name\s*=\s*"([^"]+)"/);
38
+ if (match)
39
+ return match[1];
40
+ }
41
+ // Try pyproject.toml
42
+ const pyPath = join(root, 'pyproject.toml');
43
+ if (existsSync(pyPath)) {
44
+ const match = readFileSync(pyPath, 'utf8').match(/name\s*=\s*"([^"]+)"/);
45
+ if (match)
46
+ return match[1];
47
+ }
48
+ // Fall back to directory name
49
+ return basename(root);
50
+ }
51
+ function detectLanguage(root) {
52
+ if (existsSync(join(root, 'tsconfig.json')))
53
+ return 'TypeScript';
54
+ if (existsSync(join(root, 'package.json')))
55
+ return 'JavaScript';
56
+ if (existsSync(join(root, 'Cargo.toml')))
57
+ return 'Rust';
58
+ if (existsSync(join(root, 'go.mod')))
59
+ return 'Go';
60
+ if (existsSync(join(root, 'pyproject.toml')) || existsSync(join(root, 'setup.py')))
61
+ return 'Python';
62
+ if (existsSync(join(root, 'build.gradle')) || existsSync(join(root, 'pom.xml')))
63
+ return 'Java';
64
+ if (existsSync(join(root, 'Package.swift')))
65
+ return 'Swift';
66
+ if (existsSync(join(root, 'mix.exs')))
67
+ return 'Elixir';
68
+ // Count file extensions in top-level src/ or root
69
+ const exts = {};
70
+ const scanDirs = [join(root, 'src'), root];
71
+ for (const dir of scanDirs) {
72
+ try {
73
+ const entries = readdirSync(dir);
74
+ for (const name of entries) {
75
+ const ext = name.split('.').pop() || '';
76
+ if (ext !== name)
77
+ exts[ext] = (exts[ext] || 0) + 1;
78
+ }
79
+ }
80
+ catch { /* dir doesn't exist */ }
81
+ }
82
+ const langMap = {
83
+ ts: 'TypeScript', tsx: 'TypeScript', js: 'JavaScript', jsx: 'JavaScript',
84
+ py: 'Python', rs: 'Rust', go: 'Go', java: 'Java', rb: 'Ruby',
85
+ swift: 'Swift', kt: 'Kotlin', cs: 'C#', cpp: 'C++', c: 'C',
86
+ };
87
+ const sorted = Object.entries(exts)
88
+ .filter(([ext]) => langMap[ext])
89
+ .sort((a, b) => b[1] - a[1]);
90
+ return sorted.length > 0 ? langMap[sorted[0][0]] : 'Unknown';
91
+ }
92
+ function detectFramework(root) {
93
+ const pkgPath = join(root, 'package.json');
94
+ if (!existsSync(pkgPath))
95
+ return undefined;
96
+ try {
97
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
98
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
99
+ if (deps.next)
100
+ return 'Next.js';
101
+ if (deps.nuxt)
102
+ return 'Nuxt';
103
+ if (deps['react-dom'])
104
+ return 'React';
105
+ if (deps.vue)
106
+ return 'Vue';
107
+ if (deps.svelte || deps['@sveltejs/kit'])
108
+ return 'Svelte';
109
+ if (deps.express)
110
+ return 'Express';
111
+ if (deps.fastify)
112
+ return 'Fastify';
113
+ if (deps.hono)
114
+ return 'Hono';
115
+ if (deps.remix || deps['@remix-run/react'])
116
+ return 'Remix';
117
+ if (deps.astro)
118
+ return 'Astro';
119
+ if (deps.nest || deps['@nestjs/core'])
120
+ return 'NestJS';
121
+ if (deps.angular || deps['@angular/core'])
122
+ return 'Angular';
123
+ if (deps.gatsby)
124
+ return 'Gatsby';
125
+ if (deps.electron)
126
+ return 'Electron';
127
+ if (deps.expo)
128
+ return 'Expo (React Native)';
129
+ if (deps['react-native'])
130
+ return 'React Native';
131
+ if (deps.vite)
132
+ return 'Vite';
133
+ }
134
+ catch { /* ignore */ }
135
+ // Python frameworks
136
+ if (existsSync(join(root, 'manage.py')))
137
+ return 'Django';
138
+ const reqPath = join(root, 'requirements.txt');
139
+ if (existsSync(reqPath)) {
140
+ const reqs = readFileSync(reqPath, 'utf8');
141
+ if (/flask/i.test(reqs))
142
+ return 'Flask';
143
+ if (/fastapi/i.test(reqs))
144
+ return 'FastAPI';
145
+ if (/django/i.test(reqs))
146
+ return 'Django';
147
+ }
148
+ // Rust frameworks
149
+ if (existsSync(join(root, 'Cargo.toml'))) {
150
+ const cargo = readFileSync(join(root, 'Cargo.toml'), 'utf8');
151
+ if (/actix/i.test(cargo))
152
+ return 'Actix';
153
+ if (/axum/i.test(cargo))
154
+ return 'Axum';
155
+ if (/rocket/i.test(cargo))
156
+ return 'Rocket';
157
+ }
158
+ return undefined;
159
+ }
160
+ function detectPackageManager(root) {
161
+ if (existsSync(join(root, 'bun.lockb')))
162
+ return 'bun';
163
+ if (existsSync(join(root, 'pnpm-lock.yaml')))
164
+ return 'pnpm';
165
+ if (existsSync(join(root, 'yarn.lock')))
166
+ return 'yarn';
167
+ if (existsSync(join(root, 'package-lock.json')))
168
+ return 'npm';
169
+ if (existsSync(join(root, 'Cargo.lock')))
170
+ return 'cargo';
171
+ if (existsSync(join(root, 'poetry.lock')))
172
+ return 'poetry';
173
+ if (existsSync(join(root, 'go.sum')))
174
+ return 'go';
175
+ if (existsSync(join(root, 'Pipfile.lock')))
176
+ return 'pipenv';
177
+ return undefined;
178
+ }
179
+ function detectKeyFiles(root) {
180
+ const candidates = [
181
+ 'package.json', 'tsconfig.json', 'Cargo.toml', 'go.mod', 'pyproject.toml',
182
+ 'Dockerfile', 'docker-compose.yml', 'Makefile',
183
+ '.env.example', '.github/workflows/ci.yml',
184
+ 'src/index.ts', 'src/main.ts', 'src/app.ts', 'src/index.js', 'src/main.js',
185
+ 'src/App.tsx', 'src/App.vue', 'src/App.svelte',
186
+ 'main.go', 'src/main.rs', 'src/lib.rs',
187
+ 'app.py', 'main.py', 'manage.py',
188
+ 'README.md', 'CLAUDE.md', '.kbot.md',
189
+ ];
190
+ return candidates.filter(f => existsSync(join(root, f)));
191
+ }
192
+ function detectCommands(root) {
193
+ const commands = {};
194
+ // From package.json scripts
195
+ const pkgPath = join(root, 'package.json');
196
+ if (existsSync(pkgPath)) {
197
+ try {
198
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
199
+ const scripts = pkg.scripts || {};
200
+ const useful = ['dev', 'start', 'build', 'test', 'lint', 'typecheck', 'deploy', 'format', 'check'];
201
+ for (const s of useful) {
202
+ if (scripts[s]) {
203
+ const pm = detectPackageManager(root) || 'npm';
204
+ commands[s] = `${pm} run ${s}`;
205
+ }
206
+ }
207
+ }
208
+ catch { /* ignore */ }
209
+ }
210
+ // From Makefile
211
+ if (existsSync(join(root, 'Makefile'))) {
212
+ try {
213
+ const makefile = readFileSync(join(root, 'Makefile'), 'utf8');
214
+ const targets = makefile.match(/^([a-zA-Z_-]+):/gm);
215
+ if (targets) {
216
+ for (const t of targets.slice(0, 8)) {
217
+ const name = t.replace(':', '');
218
+ if (!['all', 'clean', '.PHONY', 'default'].includes(name)) {
219
+ commands[name] = `make ${name}`;
220
+ }
221
+ }
222
+ }
223
+ }
224
+ catch { /* ignore */ }
225
+ }
226
+ // Cargo commands
227
+ if (existsSync(join(root, 'Cargo.toml'))) {
228
+ commands.build = commands.build || 'cargo build';
229
+ commands.test = commands.test || 'cargo test';
230
+ commands.run = commands.run || 'cargo run';
231
+ }
232
+ // Go commands
233
+ if (existsSync(join(root, 'go.mod'))) {
234
+ commands.build = commands.build || 'go build ./...';
235
+ commands.test = commands.test || 'go test ./...';
236
+ }
237
+ return commands;
238
+ }
239
+ function suggestAgent(language, framework) {
240
+ if (framework) {
241
+ const webFrameworks = ['React', 'Vue', 'Svelte', 'Next.js', 'Nuxt', 'Angular', 'Remix', 'Astro', 'Gatsby'];
242
+ if (webFrameworks.includes(framework))
243
+ return 'coder';
244
+ if (['Express', 'Fastify', 'Hono', 'NestJS'].includes(framework))
245
+ return 'coder';
246
+ if (['Django', 'Flask', 'FastAPI'].includes(framework))
247
+ return 'coder';
248
+ }
249
+ return 'kernel';
250
+ }
251
+ function generateProjectTools(config) {
252
+ const tools = [];
253
+ // Test runner
254
+ if (config.commands.test) {
255
+ tools.push({
256
+ name: 'run_tests',
257
+ description: `Run ${config.name} test suite`,
258
+ code: `const { execSync } = require('child_process'); try { return execSync('${config.commands.test}', { encoding: 'utf8', timeout: 120000, cwd: process.cwd() }).slice(-2000); } catch(e) { return 'Tests failed:\\n' + (e.stderr || e.stdout || e.message).slice(-2000); }`,
259
+ });
260
+ }
261
+ // Type checker / linter
262
+ if (config.commands.typecheck || config.commands.lint) {
263
+ const cmd = config.commands.typecheck || config.commands.lint;
264
+ tools.push({
265
+ name: 'check_code',
266
+ description: `Run type-check/lint for ${config.name}`,
267
+ code: `const { execSync } = require('child_process'); try { return execSync('${cmd}', { encoding: 'utf8', timeout: 60000, cwd: process.cwd() }).slice(-2000) || 'All checks passed.'; } catch(e) { return 'Check failed:\\n' + (e.stderr || e.stdout || e.message).slice(-2000); }`,
268
+ });
269
+ }
270
+ // Build
271
+ if (config.commands.build) {
272
+ tools.push({
273
+ name: 'build_project',
274
+ description: `Build ${config.name}`,
275
+ code: `const { execSync } = require('child_process'); try { return execSync('${config.commands.build}', { encoding: 'utf8', timeout: 120000, cwd: process.cwd() }).slice(-2000) || 'Build succeeded.'; } catch(e) { return 'Build failed:\\n' + (e.stderr || e.stdout || e.message).slice(-2000); }`,
276
+ });
277
+ }
278
+ // Dev server
279
+ if (config.commands.dev || config.commands.start) {
280
+ const cmd = config.commands.dev || config.commands.start;
281
+ tools.push({
282
+ name: 'start_dev',
283
+ description: `Start ${config.name} dev server`,
284
+ code: `const { spawn } = require('child_process'); const [bin, ...args] = '${cmd}'.split(' '); const p = spawn(bin, args, { cwd: process.cwd(), stdio: 'pipe' }); let out = ''; p.stdout?.on('data', d => out += d); p.stderr?.on('data', d => out += d); return new Promise(r => setTimeout(() => { p.kill(); r('Dev server started. Output:\\n' + out.slice(-1000)); }, 3000));`,
285
+ });
286
+ }
287
+ return tools;
288
+ }
289
+ // ── Main Init ──
290
+ export async function initProject(root) {
291
+ const name = detectProjectName(root);
292
+ const language = detectLanguage(root);
293
+ const framework = detectFramework(root);
294
+ const packageManager = detectPackageManager(root);
295
+ const keyFiles = detectKeyFiles(root);
296
+ const commands = detectCommands(root);
297
+ const defaultAgent = suggestAgent(language, framework);
298
+ const config = {
299
+ name,
300
+ language,
301
+ framework,
302
+ packageManager,
303
+ defaultAgent,
304
+ keyFiles,
305
+ commands,
306
+ forgedTools: [],
307
+ createdAt: new Date().toISOString(),
308
+ };
309
+ // Generate and save forged tools
310
+ const tools = generateProjectTools(config);
311
+ const forgeDir = join(homedir(), '.kbot', 'plugins', 'forged');
312
+ if (!existsSync(forgeDir))
313
+ mkdirSync(forgeDir, { recursive: true });
314
+ for (const tool of tools) {
315
+ const toolPath = join(forgeDir, `${tool.name}.js`);
316
+ const wrapper = `// Auto-generated by kbot init for ${name}
317
+ // ${tool.description}
318
+ module.exports = async function(args) {
319
+ ${tool.code}
320
+ };
321
+ module.exports.description = ${JSON.stringify(tool.description)};
322
+ `;
323
+ writeFileSync(toolPath, wrapper);
324
+ config.forgedTools.push(tool.name);
325
+ }
326
+ // Write .kbot.json
327
+ const configPath = join(root, '.kbot.json');
328
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
329
+ // Add to .gitignore if not already there
330
+ const gitignorePath = join(root, '.gitignore');
331
+ if (existsSync(gitignorePath)) {
332
+ const gitignore = readFileSync(gitignorePath, 'utf8');
333
+ if (!gitignore.includes('.kbot.json')) {
334
+ writeFileSync(gitignorePath, gitignore.trimEnd() + '\n.kbot.json\n');
335
+ }
336
+ }
337
+ return config;
338
+ }
339
+ export function formatInitReport(config) {
340
+ const lines = [];
341
+ lines.push(` Project: ${config.name}`);
342
+ lines.push(` Language: ${config.language}${config.framework ? ` (${config.framework})` : ''}`);
343
+ if (config.packageManager)
344
+ lines.push(` Package Mgr: ${config.packageManager}`);
345
+ lines.push(` Agent: ${config.defaultAgent}`);
346
+ lines.push(` Key files: ${config.keyFiles.length} detected`);
347
+ if (Object.keys(config.commands).length > 0) {
348
+ lines.push(` Commands: ${Object.keys(config.commands).join(', ')}`);
349
+ }
350
+ if (config.forgedTools.length > 0) {
351
+ lines.push(` Tools: ${config.forgedTools.join(', ')} (auto-forged)`);
352
+ }
353
+ return lines.join('\n');
354
+ }
355
+ //# sourceMappingURL=init.js.map