@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.
- package/dist/agents/playtester.d.ts +8 -0
- package/dist/agents/playtester.d.ts.map +1 -0
- package/dist/agents/playtester.js +83 -0
- package/dist/agents/playtester.js.map +1 -0
- package/dist/cli.js +244 -9
- package/dist/cli.js.map +1 -1
- package/dist/consultation.test.d.ts +2 -0
- package/dist/consultation.test.d.ts.map +1 -0
- package/dist/consultation.test.js +86 -0
- package/dist/consultation.test.js.map +1 -0
- package/dist/email-agent.d.ts +33 -0
- package/dist/email-agent.d.ts.map +1 -0
- package/dist/email-agent.js +410 -0
- package/dist/email-agent.js.map +1 -0
- package/dist/imessage-agent.d.ts +28 -0
- package/dist/imessage-agent.d.ts.map +1 -0
- package/dist/imessage-agent.js +274 -0
- package/dist/imessage-agent.js.map +1 -0
- package/dist/init.d.ts +23 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +355 -0
- package/dist/init.js.map +1 -0
- package/dist/init.test.d.ts +2 -0
- package/dist/init.test.d.ts.map +1 -0
- package/dist/init.test.js +89 -0
- package/dist/init.test.js.map +1 -0
- package/dist/matrix.d.ts.map +1 -1
- package/dist/matrix.js +15 -1
- package/dist/matrix.js.map +1 -1
- package/dist/memory-synthesis.test.d.ts +2 -0
- package/dist/memory-synthesis.test.d.ts.map +1 -0
- package/dist/memory-synthesis.test.js +83 -0
- package/dist/memory-synthesis.test.js.map +1 -0
- package/dist/tools/audit.d.ts.map +1 -1
- package/dist/tools/audit.js +18 -1
- package/dist/tools/audit.js.map +1 -1
- package/package.json +2 -2
|
@@ -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
|