@averagejoeslab/puppuccino-core 0.1.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/index.cjs ADDED
@@ -0,0 +1,2026 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BUILTIN_TOOLS: () => BUILTIN_TOOLS,
24
+ BashTool: () => BashTool,
25
+ CONFIG_PATHS: () => CONFIG_PATHS,
26
+ ConfigSchema: () => ConfigSchema,
27
+ DEFAULT_SYSTEM_PROMPT: () => DEFAULT_SYSTEM_PROMPT,
28
+ EditTool: () => EditTool,
29
+ GlobTool: () => GlobTool,
30
+ GrepTool: () => GrepTool,
31
+ HookSchema: () => HookSchema,
32
+ HooksRunner: () => HooksRunner,
33
+ ListTool: () => ListTool,
34
+ MCPClient: () => MCPClient,
35
+ MCPConnection: () => MCPConnection,
36
+ MCPServerSchema: () => MCPServerSchema,
37
+ Memory: () => Memory,
38
+ PermissionChecker: () => PermissionChecker,
39
+ PermissionsSchema: () => PermissionsSchema,
40
+ Provider: () => Provider,
41
+ ReadTool: () => ReadTool,
42
+ SessionManager: () => SessionManager,
43
+ SkillsLoader: () => SkillsLoader,
44
+ ToolRegistry: () => ToolRegistry,
45
+ VERSION: () => VERSION,
46
+ WriteTool: () => WriteTool,
47
+ appendMessage: () => appendMessage,
48
+ createAgentState: () => createAgentState,
49
+ createHooksRunner: () => createHooksRunner,
50
+ createMCPClient: () => createMCPClient,
51
+ createMemory: () => createMemory,
52
+ createPermissionChecker: () => createPermissionChecker,
53
+ createProvider: () => createProvider,
54
+ createProviderInstance: () => createProviderInstance,
55
+ createSession: () => createSession,
56
+ createSessionManager: () => createSessionManager,
57
+ createSkillsLoader: () => createSkillsLoader,
58
+ createToolRegistry: () => createToolRegistry,
59
+ deleteSession: () => deleteSession,
60
+ forkSession: () => forkSession,
61
+ formatProjectContext: () => formatProjectContext,
62
+ getDataDir: () => getDataDir,
63
+ getRecentSession: () => getRecentSession,
64
+ getSessionsDir: () => getSessionsDir,
65
+ getUserConfigPath: () => getUserConfigPath,
66
+ listSessions: () => listSessions,
67
+ loadConfig: () => loadConfig,
68
+ loadProjectContext: () => loadProjectContext,
69
+ loadSession: () => loadSession,
70
+ runAgent: () => runAgent,
71
+ runAgentLoop: () => runAgentLoop,
72
+ saveConfig: () => saveConfig,
73
+ saveSession: () => saveSession
74
+ });
75
+ module.exports = __toCommonJS(index_exports);
76
+
77
+ // src/config/index.ts
78
+ var import_zod = require("zod");
79
+ var import_node_fs = require("fs");
80
+ var import_node_path = require("path");
81
+ var import_node_os = require("os");
82
+ var MCPServerSchema = import_zod.z.object({
83
+ name: import_zod.z.string(),
84
+ transport: import_zod.z.enum(["stdio", "http", "sse"]),
85
+ command: import_zod.z.string().optional(),
86
+ args: import_zod.z.array(import_zod.z.string()).optional(),
87
+ url: import_zod.z.string().optional(),
88
+ env: import_zod.z.record(import_zod.z.string()).optional(),
89
+ enabled: import_zod.z.boolean().default(true)
90
+ });
91
+ var HookSchema = import_zod.z.object({
92
+ event: import_zod.z.enum([
93
+ "SessionStart",
94
+ "UserPromptSubmit",
95
+ "PreToolUse",
96
+ "PostToolUse",
97
+ "Stop",
98
+ "PreCompact",
99
+ "SessionEnd"
100
+ ]),
101
+ matcher: import_zod.z.string().optional(),
102
+ type: import_zod.z.enum(["command", "prompt", "agent"]),
103
+ command: import_zod.z.string().optional(),
104
+ prompt: import_zod.z.string().optional(),
105
+ agent: import_zod.z.string().optional()
106
+ });
107
+ var PermissionsSchema = import_zod.z.object({
108
+ allowedTools: import_zod.z.array(import_zod.z.string()).optional(),
109
+ disallowedTools: import_zod.z.array(import_zod.z.string()).optional(),
110
+ autoApprove: import_zod.z.array(import_zod.z.string()).optional(),
111
+ yolo: import_zod.z.boolean().default(false)
112
+ });
113
+ var ConfigSchema = import_zod.z.object({
114
+ // Provider settings
115
+ provider: import_zod.z.enum(["anthropic", "openai", "groq", "local"]).default("anthropic"),
116
+ model: import_zod.z.string().default("claude-sonnet-4-20250514"),
117
+ apiKey: import_zod.z.string().optional(),
118
+ baseUrl: import_zod.z.string().optional(),
119
+ // MCP servers
120
+ mcpServers: import_zod.z.record(MCPServerSchema).optional(),
121
+ // Permissions
122
+ permissions: PermissionsSchema.optional(),
123
+ // Hooks
124
+ hooks: import_zod.z.record(import_zod.z.array(HookSchema)).optional(),
125
+ // Skills
126
+ skills: import_zod.z.object({
127
+ enabled: import_zod.z.array(import_zod.z.string()).optional(),
128
+ disabled: import_zod.z.array(import_zod.z.string()).optional()
129
+ }).optional(),
130
+ // Session settings
131
+ session: import_zod.z.object({
132
+ persistHistory: import_zod.z.boolean().default(true),
133
+ maxHistoryDays: import_zod.z.number().default(30)
134
+ }).optional(),
135
+ // UI settings
136
+ theme: import_zod.z.enum(["dark", "light", "kaldi"]).default("kaldi"),
137
+ // Agent settings
138
+ agent: import_zod.z.object({
139
+ maxTokens: import_zod.z.number().default(8192),
140
+ maxTurns: import_zod.z.number().default(100),
141
+ systemPrompt: import_zod.z.string().optional()
142
+ }).optional()
143
+ });
144
+ var CONFIG_PATHS = {
145
+ // Project-level (highest priority)
146
+ project: [".puppuccino.json", "puppuccino.json", ".puppuccino/config.json"],
147
+ // User-level
148
+ user: [
149
+ (0, import_node_path.join)((0, import_node_os.homedir)(), ".config", "puppuccino", "config.json"),
150
+ (0, import_node_path.join)((0, import_node_os.homedir)(), ".puppuccino", "config.json")
151
+ ]
152
+ };
153
+ function getDataDir() {
154
+ const platform = process.platform;
155
+ if (process.env.PUPPUCCINO_DATA_DIR) {
156
+ return process.env.PUPPUCCINO_DATA_DIR;
157
+ }
158
+ switch (platform) {
159
+ case "darwin":
160
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), "Library", "Application Support", "puppuccino");
161
+ case "win32":
162
+ return (0, import_node_path.join)(process.env.LOCALAPPDATA || (0, import_node_os.homedir)(), "puppuccino");
163
+ default:
164
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".local", "share", "puppuccino");
165
+ }
166
+ }
167
+ function getSessionsDir() {
168
+ return (0, import_node_path.join)(getDataDir(), "sessions");
169
+ }
170
+ function loadConfigFile(path) {
171
+ if (!(0, import_node_fs.existsSync)(path)) return null;
172
+ try {
173
+ const content = (0, import_node_fs.readFileSync)(path, "utf-8");
174
+ return JSON.parse(content);
175
+ } catch {
176
+ return null;
177
+ }
178
+ }
179
+ function loadConfig(projectDir) {
180
+ const configs = [];
181
+ for (const path of CONFIG_PATHS.user) {
182
+ const config = loadConfigFile(path);
183
+ if (config) {
184
+ configs.push(config);
185
+ break;
186
+ }
187
+ }
188
+ if (projectDir) {
189
+ for (const filename of CONFIG_PATHS.project) {
190
+ const path = (0, import_node_path.join)(projectDir, filename);
191
+ const config = loadConfigFile(path);
192
+ if (config) {
193
+ configs.push(config);
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ const envConfig = {};
199
+ if (process.env.ANTHROPIC_API_KEY) {
200
+ envConfig.provider = "anthropic";
201
+ envConfig.apiKey = process.env.ANTHROPIC_API_KEY;
202
+ } else if (process.env.OPENAI_API_KEY) {
203
+ envConfig.provider = "openai";
204
+ envConfig.apiKey = process.env.OPENAI_API_KEY;
205
+ }
206
+ if (process.env.PUPPUCCINO_MODEL) {
207
+ envConfig.model = process.env.PUPPUCCINO_MODEL;
208
+ }
209
+ configs.push(envConfig);
210
+ const merged = configs.reduce((acc, cfg) => ({ ...acc, ...cfg }), {});
211
+ return ConfigSchema.parse(merged);
212
+ }
213
+ function saveConfig(config, path) {
214
+ const dir = (0, import_node_path.dirname)(path);
215
+ if (!(0, import_node_fs.existsSync)(dir)) {
216
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
217
+ }
218
+ (0, import_node_fs.writeFileSync)(path, JSON.stringify(config, null, 2));
219
+ }
220
+ function getUserConfigPath() {
221
+ return CONFIG_PATHS.user[0];
222
+ }
223
+
224
+ // src/providers/index.ts
225
+ var import_anthropic = require("@ai-sdk/anthropic");
226
+ var import_openai = require("@ai-sdk/openai");
227
+ var import_ai = require("ai");
228
+ function createProvider(config) {
229
+ const { provider, apiKey, baseUrl } = config;
230
+ switch (provider) {
231
+ case "anthropic":
232
+ return (0, import_anthropic.createAnthropic)({
233
+ apiKey: apiKey || process.env.ANTHROPIC_API_KEY,
234
+ baseURL: baseUrl
235
+ });
236
+ case "openai":
237
+ return (0, import_openai.createOpenAI)({
238
+ apiKey: apiKey || process.env.OPENAI_API_KEY,
239
+ baseURL: baseUrl
240
+ });
241
+ case "groq":
242
+ return (0, import_openai.createOpenAI)({
243
+ apiKey: apiKey || process.env.GROQ_API_KEY,
244
+ baseURL: baseUrl || "https://api.groq.com/openai/v1"
245
+ });
246
+ case "local":
247
+ return (0, import_openai.createOpenAI)({
248
+ apiKey: apiKey || "local",
249
+ baseURL: baseUrl || "http://localhost:11434/v1"
250
+ });
251
+ default:
252
+ throw new Error(`Unknown provider: ${provider}`);
253
+ }
254
+ }
255
+ function convertTools(tools) {
256
+ const result = {};
257
+ for (const t of tools) {
258
+ result[t.name] = (0, import_ai.tool)({
259
+ description: t.description,
260
+ parameters: t.parameters
261
+ });
262
+ }
263
+ return result;
264
+ }
265
+ function convertMessages(messages) {
266
+ return messages.map((msg) => {
267
+ if (typeof msg.content === "string") {
268
+ return {
269
+ role: msg.role,
270
+ content: msg.content
271
+ };
272
+ }
273
+ const textParts = msg.content.filter((b) => b.type === "text").map((b) => b.text || "").join("");
274
+ return {
275
+ role: msg.role,
276
+ content: textParts
277
+ };
278
+ });
279
+ }
280
+ var Provider = class {
281
+ config;
282
+ sdk;
283
+ constructor(config) {
284
+ this.config = config;
285
+ this.sdk = createProvider(config);
286
+ }
287
+ /**
288
+ * Send a chat request and get a response
289
+ */
290
+ async chat(options) {
291
+ const {
292
+ model,
293
+ messages,
294
+ tools = [],
295
+ systemPrompt,
296
+ maxTokens = 8192,
297
+ temperature = 0.7
298
+ } = options;
299
+ const result = await (0, import_ai.generateText)({
300
+ model: this.sdk(model),
301
+ messages: convertMessages(messages),
302
+ system: systemPrompt,
303
+ tools: tools.length > 0 ? convertTools(tools) : void 0,
304
+ maxTokens,
305
+ temperature
306
+ });
307
+ const toolCalls = result.toolCalls?.map((tc) => ({
308
+ id: tc.toolCallId,
309
+ name: tc.toolName,
310
+ input: tc.args
311
+ })) || [];
312
+ return {
313
+ text: result.text,
314
+ toolCalls,
315
+ finishReason: result.finishReason,
316
+ usage: result.usage ? {
317
+ promptTokens: result.usage.promptTokens,
318
+ completionTokens: result.usage.completionTokens
319
+ } : void 0
320
+ };
321
+ }
322
+ /**
323
+ * Stream a chat response
324
+ */
325
+ async *chatStream(options) {
326
+ const {
327
+ model,
328
+ messages,
329
+ tools = [],
330
+ systemPrompt,
331
+ maxTokens = 8192,
332
+ temperature = 0.7
333
+ } = options;
334
+ const result = await (0, import_ai.streamText)({
335
+ model: this.sdk(model),
336
+ messages: convertMessages(messages),
337
+ system: systemPrompt,
338
+ tools: tools.length > 0 ? convertTools(tools) : void 0,
339
+ maxTokens,
340
+ temperature
341
+ });
342
+ for await (const chunk of result.textStream) {
343
+ yield { type: "text", text: chunk };
344
+ }
345
+ yield { type: "finish", finishReason: await result.finishReason };
346
+ }
347
+ /**
348
+ * Get current provider type
349
+ */
350
+ get providerType() {
351
+ return this.config.provider;
352
+ }
353
+ /**
354
+ * Get current model
355
+ */
356
+ get model() {
357
+ return this.config.model;
358
+ }
359
+ };
360
+ function createProviderInstance(config) {
361
+ return new Provider(config);
362
+ }
363
+
364
+ // src/tools/index.ts
365
+ var import_zod2 = require("zod");
366
+ var import_node_fs3 = require("fs");
367
+ var import_node_path3 = require("path");
368
+ var import_node_child_process = require("child_process");
369
+
370
+ // src/tools/glob.ts
371
+ var import_node_fs2 = require("fs");
372
+ var import_node_path2 = require("path");
373
+ function globToRegex(pattern) {
374
+ let regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\{\{GLOBSTAR\}\}/g, ".*");
375
+ return new RegExp(`^${regex}$`);
376
+ }
377
+ function shouldIgnore(name) {
378
+ const ignored = [
379
+ "node_modules",
380
+ ".git",
381
+ ".svn",
382
+ ".hg",
383
+ ".DS_Store",
384
+ "dist",
385
+ "build",
386
+ "coverage",
387
+ ".turbo",
388
+ ".next",
389
+ ".nuxt"
390
+ ];
391
+ return ignored.includes(name);
392
+ }
393
+ function* walkDir(dir, base) {
394
+ try {
395
+ const entries = (0, import_node_fs2.readdirSync)(dir, { withFileTypes: true });
396
+ for (const entry of entries) {
397
+ const name = entry.name;
398
+ if (entry.isDirectory() && shouldIgnore(name)) {
399
+ continue;
400
+ }
401
+ const fullPath = (0, import_node_path2.join)(dir, name);
402
+ const relativePath = (0, import_node_path2.relative)(base, fullPath);
403
+ if (entry.isDirectory()) {
404
+ yield* walkDir(fullPath, base);
405
+ } else {
406
+ yield relativePath;
407
+ }
408
+ }
409
+ } catch {
410
+ }
411
+ }
412
+ async function glob(pattern, basePath = ".") {
413
+ const absBase = (0, import_node_path2.resolve)(basePath);
414
+ const regex = globToRegex(pattern);
415
+ const results = [];
416
+ for (const file of walkDir(absBase, absBase)) {
417
+ const normalized = file.replace(/\\/g, "/");
418
+ if (regex.test(normalized)) {
419
+ results.push(normalized);
420
+ }
421
+ }
422
+ return results.sort();
423
+ }
424
+
425
+ // src/tools/index.ts
426
+ var ReadTool = {
427
+ name: "read",
428
+ description: "Read the contents of a file. Returns the file contents with line numbers.",
429
+ parameters: import_zod2.z.object({
430
+ path: import_zod2.z.string().describe("The file path to read"),
431
+ offset: import_zod2.z.number().optional().describe("Line number to start reading from (1-based)"),
432
+ limit: import_zod2.z.number().optional().describe("Maximum number of lines to read")
433
+ }),
434
+ async execute(input) {
435
+ const { path, offset = 1, limit } = input;
436
+ if (!(0, import_node_fs3.existsSync)(path)) {
437
+ return `Error: File not found: ${path}`;
438
+ }
439
+ try {
440
+ const content = (0, import_node_fs3.readFileSync)(path, "utf-8");
441
+ const lines = content.split("\n");
442
+ const startIdx = Math.max(0, offset - 1);
443
+ const endIdx = limit ? startIdx + limit : lines.length;
444
+ const selectedLines = lines.slice(startIdx, endIdx);
445
+ const formatted = selectedLines.map((line, i) => {
446
+ const lineNum = startIdx + i + 1;
447
+ const padding = String(lines.length).length;
448
+ return `${String(lineNum).padStart(padding)}\u2502${line}`;
449
+ }).join("\n");
450
+ return formatted;
451
+ } catch (err) {
452
+ return `Error reading file: ${err instanceof Error ? err.message : String(err)}`;
453
+ }
454
+ }
455
+ };
456
+ var WriteTool = {
457
+ name: "write",
458
+ description: "Write content to a file. Creates the file and parent directories if they don't exist.",
459
+ parameters: import_zod2.z.object({
460
+ path: import_zod2.z.string().describe("The file path to write to"),
461
+ content: import_zod2.z.string().describe("The content to write")
462
+ }),
463
+ async execute(input) {
464
+ const { path, content } = input;
465
+ try {
466
+ const dir = (0, import_node_path3.dirname)(path);
467
+ if (!(0, import_node_fs3.existsSync)(dir)) {
468
+ (0, import_node_fs3.mkdirSync)(dir, { recursive: true });
469
+ }
470
+ (0, import_node_fs3.writeFileSync)(path, content, "utf-8");
471
+ return `Successfully wrote ${content.length} bytes to ${path}`;
472
+ } catch (err) {
473
+ return `Error writing file: ${err instanceof Error ? err.message : String(err)}`;
474
+ }
475
+ }
476
+ };
477
+ var EditTool = {
478
+ name: "edit",
479
+ description: "Replace text in a file. The old_string must be unique in the file unless replace_all is true.",
480
+ parameters: import_zod2.z.object({
481
+ path: import_zod2.z.string().describe("The file path to edit"),
482
+ old_string: import_zod2.z.string().describe("The text to find and replace"),
483
+ new_string: import_zod2.z.string().describe("The replacement text"),
484
+ replace_all: import_zod2.z.boolean().optional().describe("Replace all occurrences (default: false)")
485
+ }),
486
+ async execute(input) {
487
+ const { path, old_string, new_string, replace_all = false } = input;
488
+ if (!(0, import_node_fs3.existsSync)(path)) {
489
+ return `Error: File not found: ${path}`;
490
+ }
491
+ try {
492
+ const content = (0, import_node_fs3.readFileSync)(path, "utf-8");
493
+ const count = content.split(old_string).length - 1;
494
+ if (count === 0) {
495
+ return `Error: old_string not found in file`;
496
+ }
497
+ if (count > 1 && !replace_all) {
498
+ return `Error: old_string found ${count} times. Use replace_all=true to replace all, or provide a more specific string.`;
499
+ }
500
+ const newContent = replace_all ? content.split(old_string).join(new_string) : content.replace(old_string, new_string);
501
+ (0, import_node_fs3.writeFileSync)(path, newContent, "utf-8");
502
+ const replacements = replace_all ? count : 1;
503
+ return `Successfully replaced ${replacements} occurrence(s) in ${path}`;
504
+ } catch (err) {
505
+ return `Error editing file: ${err instanceof Error ? err.message : String(err)}`;
506
+ }
507
+ }
508
+ };
509
+ var GlobTool = {
510
+ name: "glob",
511
+ description: "Find files matching a glob pattern. Returns a list of matching file paths.",
512
+ parameters: import_zod2.z.object({
513
+ pattern: import_zod2.z.string().describe('The glob pattern (e.g., "**/*.ts", "src/**/*.js")'),
514
+ path: import_zod2.z.string().optional().describe("The directory to search in (default: current directory)")
515
+ }),
516
+ async execute(input) {
517
+ const { pattern, path: basePath = "." } = input;
518
+ try {
519
+ const files = await glob(pattern, basePath);
520
+ if (files.length === 0) {
521
+ return "No files matched the pattern";
522
+ }
523
+ const maxResults = 100;
524
+ const limited = files.slice(0, maxResults);
525
+ const result = limited.join("\n");
526
+ if (files.length > maxResults) {
527
+ return `${result}
528
+
529
+ ... and ${files.length - maxResults} more files`;
530
+ }
531
+ return result;
532
+ } catch (err) {
533
+ return `Error searching files: ${err instanceof Error ? err.message : String(err)}`;
534
+ }
535
+ }
536
+ };
537
+ var GrepTool = {
538
+ name: "grep",
539
+ description: "Search for a pattern in files. Returns matching lines with file paths and line numbers.",
540
+ parameters: import_zod2.z.object({
541
+ pattern: import_zod2.z.string().describe("The regex pattern to search for"),
542
+ path: import_zod2.z.string().optional().describe("The directory to search in (default: current directory)"),
543
+ glob: import_zod2.z.string().optional().describe('File pattern to filter (e.g., "*.ts")')
544
+ }),
545
+ async execute(input) {
546
+ const { pattern, path: basePath = ".", glob: fileGlob } = input;
547
+ try {
548
+ const regex = new RegExp(pattern, "gi");
549
+ const results = [];
550
+ const maxResults = 50;
551
+ const searchGlob = fileGlob || "**/*";
552
+ const files = await glob(searchGlob, basePath);
553
+ outer: for (const file of files) {
554
+ const fullPath = (0, import_node_path3.resolve)(basePath, file);
555
+ try {
556
+ const stat = (0, import_node_fs3.statSync)(fullPath);
557
+ if (stat.isDirectory()) continue;
558
+ if (stat.size > 1024 * 1024) continue;
559
+ } catch {
560
+ continue;
561
+ }
562
+ try {
563
+ const content = (0, import_node_fs3.readFileSync)(fullPath, "utf-8");
564
+ const lines = content.split("\n");
565
+ for (let i = 0; i < lines.length; i++) {
566
+ if (regex.test(lines[i])) {
567
+ results.push(`${file}:${i + 1}: ${lines[i].trim()}`);
568
+ regex.lastIndex = 0;
569
+ if (results.length >= maxResults) break outer;
570
+ }
571
+ }
572
+ } catch {
573
+ continue;
574
+ }
575
+ }
576
+ if (results.length === 0) {
577
+ return "No matches found";
578
+ }
579
+ return results.join("\n");
580
+ } catch (err) {
581
+ return `Error searching: ${err instanceof Error ? err.message : String(err)}`;
582
+ }
583
+ }
584
+ };
585
+ var BashTool = {
586
+ name: "bash",
587
+ description: "Execute a shell command and return the output.",
588
+ parameters: import_zod2.z.object({
589
+ command: import_zod2.z.string().describe("The command to execute"),
590
+ timeout: import_zod2.z.number().optional().describe("Timeout in milliseconds (default: 30000)")
591
+ }),
592
+ async execute(input) {
593
+ const { command, timeout = 3e4 } = input;
594
+ return new Promise((resolve3) => {
595
+ try {
596
+ const result = (0, import_node_child_process.execSync)(command, {
597
+ encoding: "utf-8",
598
+ timeout,
599
+ maxBuffer: 10 * 1024 * 1024,
600
+ // 10MB
601
+ stdio: ["pipe", "pipe", "pipe"]
602
+ });
603
+ resolve3(result || "(no output)");
604
+ } catch (err) {
605
+ const error = err;
606
+ if (error.stdout || error.stderr) {
607
+ resolve3(`${error.stdout || ""}${error.stderr || ""}`);
608
+ } else {
609
+ resolve3(`Error: ${error.message || String(err)}`);
610
+ }
611
+ }
612
+ });
613
+ }
614
+ };
615
+ var ListTool = {
616
+ name: "ls",
617
+ description: "List the contents of a directory.",
618
+ parameters: import_zod2.z.object({
619
+ path: import_zod2.z.string().optional().describe("The directory path (default: current directory)"),
620
+ all: import_zod2.z.boolean().optional().describe("Include hidden files (default: false)")
621
+ }),
622
+ async execute(input) {
623
+ const { path: dirPath = ".", all = false } = input;
624
+ try {
625
+ if (!(0, import_node_fs3.existsSync)(dirPath)) {
626
+ return `Error: Directory not found: ${dirPath}`;
627
+ }
628
+ const entries = (0, import_node_fs3.readdirSync)(dirPath, { withFileTypes: true });
629
+ const filtered = all ? entries : entries.filter((e) => !e.name.startsWith("."));
630
+ const formatted = filtered.map((entry) => {
631
+ const suffix = entry.isDirectory() ? "/" : "";
632
+ return `${entry.name}${suffix}`;
633
+ });
634
+ return formatted.sort().join("\n");
635
+ } catch (err) {
636
+ return `Error listing directory: ${err instanceof Error ? err.message : String(err)}`;
637
+ }
638
+ }
639
+ };
640
+ var BUILTIN_TOOLS = [
641
+ ReadTool,
642
+ WriteTool,
643
+ EditTool,
644
+ GlobTool,
645
+ GrepTool,
646
+ BashTool,
647
+ ListTool
648
+ ];
649
+ var ToolRegistry = class {
650
+ tools = /* @__PURE__ */ new Map();
651
+ constructor() {
652
+ for (const tool2 of BUILTIN_TOOLS) {
653
+ this.register(tool2);
654
+ }
655
+ }
656
+ /**
657
+ * Register a tool
658
+ */
659
+ register(tool2) {
660
+ this.tools.set(tool2.name, tool2);
661
+ }
662
+ /**
663
+ * Unregister a tool
664
+ */
665
+ unregister(name) {
666
+ this.tools.delete(name);
667
+ }
668
+ /**
669
+ * Get a tool by name
670
+ */
671
+ get(name) {
672
+ return this.tools.get(name);
673
+ }
674
+ /**
675
+ * Get all registered tools
676
+ */
677
+ all() {
678
+ return Array.from(this.tools.values());
679
+ }
680
+ /**
681
+ * Check if a tool exists
682
+ */
683
+ has(name) {
684
+ return this.tools.has(name);
685
+ }
686
+ /**
687
+ * Execute a tool
688
+ */
689
+ async execute(name, input) {
690
+ const tool2 = this.get(name);
691
+ if (!tool2) {
692
+ return {
693
+ success: false,
694
+ output: "",
695
+ error: `Unknown tool: ${name}`
696
+ };
697
+ }
698
+ try {
699
+ const validated = tool2.parameters.parse(input);
700
+ const output = await tool2.execute(validated);
701
+ return {
702
+ success: true,
703
+ output
704
+ };
705
+ } catch (err) {
706
+ return {
707
+ success: false,
708
+ output: "",
709
+ error: err instanceof Error ? err.message : String(err)
710
+ };
711
+ }
712
+ }
713
+ };
714
+ function createToolRegistry() {
715
+ return new ToolRegistry();
716
+ }
717
+
718
+ // src/agent/index.ts
719
+ var DEFAULT_SYSTEM_PROMPT = `You are Kaldi, a helpful AI coding assistant. You're a Great Pyrenees who loves helping developers write great code.
720
+
721
+ You have access to tools to read, write, and edit files, search the codebase, and execute commands. Use these tools to help the user with their coding tasks.
722
+
723
+ When making changes:
724
+ - Read files before editing them to understand the context
725
+ - Make minimal, focused changes
726
+ - Explain what you're doing and why
727
+ - Be careful with destructive operations
728
+
729
+ Be friendly, helpful, and thorough in your responses.`;
730
+ async function* runAgentLoop(state) {
731
+ const {
732
+ messages,
733
+ provider,
734
+ tools,
735
+ hooks,
736
+ permissions,
737
+ systemPrompt = DEFAULT_SYSTEM_PROMPT,
738
+ maxTurns = 100,
739
+ onPermissionRequest
740
+ } = state;
741
+ let turns = 0;
742
+ while (turns < maxTurns) {
743
+ turns++;
744
+ yield { type: "thinking" };
745
+ if (hooks) {
746
+ const hookResult = await hooks.run("PreToolUse", {
747
+ messages,
748
+ turn: turns
749
+ });
750
+ if (hookResult.blocked) {
751
+ yield { type: "error", error: hookResult.reason || "Blocked by hook" };
752
+ return;
753
+ }
754
+ }
755
+ let response;
756
+ try {
757
+ response = await provider.chat({
758
+ model: provider.model,
759
+ messages,
760
+ tools: tools.all(),
761
+ systemPrompt
762
+ });
763
+ } catch (err) {
764
+ yield { type: "error", error: err instanceof Error ? err.message : String(err) };
765
+ return;
766
+ }
767
+ if (response.text) {
768
+ yield { type: "text", content: response.text };
769
+ }
770
+ if (response.toolCalls.length === 0) {
771
+ yield { type: "done", response };
772
+ return;
773
+ }
774
+ const toolResults = [];
775
+ for (const toolCall of response.toolCalls) {
776
+ yield {
777
+ type: "tool_start",
778
+ name: toolCall.name,
779
+ input: toolCall.input
780
+ };
781
+ if (permissions) {
782
+ const allowed = await permissions.check(toolCall.name, toolCall.input);
783
+ if (!allowed.permitted) {
784
+ if (onPermissionRequest) {
785
+ const granted = await onPermissionRequest(toolCall.name, toolCall.input);
786
+ if (!granted) {
787
+ yield {
788
+ type: "tool_denied",
789
+ name: toolCall.name,
790
+ reason: "Permission denied by user"
791
+ };
792
+ toolResults.push({
793
+ tool_use_id: toolCall.id,
794
+ content: "Permission denied by user"
795
+ });
796
+ continue;
797
+ }
798
+ } else {
799
+ yield {
800
+ type: "tool_denied",
801
+ name: toolCall.name,
802
+ reason: allowed.reason || "Permission denied"
803
+ };
804
+ toolResults.push({
805
+ tool_use_id: toolCall.id,
806
+ content: allowed.reason || "Permission denied"
807
+ });
808
+ continue;
809
+ }
810
+ }
811
+ }
812
+ const result = await tools.execute(toolCall.name, toolCall.input);
813
+ yield {
814
+ type: "tool_result",
815
+ name: toolCall.name,
816
+ result
817
+ };
818
+ if (hooks) {
819
+ await hooks.run("PostToolUse", {
820
+ tool: toolCall.name,
821
+ input: toolCall.input,
822
+ result
823
+ });
824
+ }
825
+ toolResults.push({
826
+ tool_use_id: toolCall.id,
827
+ content: result.success ? result.output : `Error: ${result.error}`
828
+ });
829
+ }
830
+ messages.push({
831
+ role: "assistant",
832
+ content: [
833
+ ...response.text ? [{ type: "text", text: response.text }] : [],
834
+ ...response.toolCalls.map((tc) => ({
835
+ type: "tool_use",
836
+ id: tc.id,
837
+ name: tc.name,
838
+ input: tc.input
839
+ }))
840
+ ]
841
+ });
842
+ messages.push({
843
+ role: "user",
844
+ content: toolResults.map((tr) => ({
845
+ type: "tool_result",
846
+ tool_use_id: tr.tool_use_id,
847
+ content: tr.content
848
+ }))
849
+ });
850
+ }
851
+ yield { type: "error", error: `Max turns (${maxTurns}) reached` };
852
+ }
853
+ async function runAgent(state) {
854
+ const events = [];
855
+ for await (const event of runAgentLoop(state)) {
856
+ events.push(event);
857
+ }
858
+ return {
859
+ events,
860
+ messages: state.messages
861
+ };
862
+ }
863
+ function createAgentState(provider, tools, options) {
864
+ return {
865
+ messages: [],
866
+ provider,
867
+ tools,
868
+ ...options
869
+ };
870
+ }
871
+
872
+ // src/hooks/index.ts
873
+ var import_node_child_process2 = require("child_process");
874
+ async function runCommandHook(command, context) {
875
+ try {
876
+ const input = JSON.stringify(context);
877
+ const output = (0, import_node_child_process2.execSync)(command, {
878
+ encoding: "utf-8",
879
+ input,
880
+ timeout: 3e4,
881
+ stdio: ["pipe", "pipe", "pipe"]
882
+ });
883
+ try {
884
+ const result = JSON.parse(output);
885
+ return {
886
+ blocked: result.blocked === true,
887
+ reason: result.reason,
888
+ output: result.output || output
889
+ };
890
+ } catch {
891
+ return {
892
+ blocked: false,
893
+ output
894
+ };
895
+ }
896
+ } catch (err) {
897
+ const error = err;
898
+ if (error.status === 2) {
899
+ return {
900
+ blocked: true,
901
+ reason: error.message || "Hook blocked execution"
902
+ };
903
+ }
904
+ return {
905
+ blocked: false,
906
+ output: error.message || String(err)
907
+ };
908
+ }
909
+ }
910
+ async function runPromptHook(prompt, context) {
911
+ return {
912
+ blocked: false,
913
+ output: `Prompt hook: ${prompt}`
914
+ };
915
+ }
916
+ var HooksRunner = class {
917
+ hooks = /* @__PURE__ */ new Map();
918
+ constructor(hooks) {
919
+ if (hooks) {
920
+ for (const [event, eventHooks] of Object.entries(hooks)) {
921
+ this.hooks.set(event, eventHooks);
922
+ }
923
+ }
924
+ }
925
+ /**
926
+ * Add a hook
927
+ */
928
+ add(event, hook) {
929
+ const existing = this.hooks.get(event) || [];
930
+ existing.push(hook);
931
+ this.hooks.set(event, existing);
932
+ }
933
+ /**
934
+ * Remove all hooks for an event
935
+ */
936
+ clear(event) {
937
+ this.hooks.delete(event);
938
+ }
939
+ /**
940
+ * Run all hooks for an event
941
+ */
942
+ async run(event, context = {}) {
943
+ const hooks = this.hooks.get(event) || [];
944
+ const fullContext = { event, ...context };
945
+ for (const hook of hooks) {
946
+ if (hook.matcher && context.tool) {
947
+ const pattern = new RegExp(hook.matcher);
948
+ if (!pattern.test(context.tool)) {
949
+ continue;
950
+ }
951
+ }
952
+ let result;
953
+ switch (hook.type) {
954
+ case "command":
955
+ if (hook.command) {
956
+ result = await runCommandHook(hook.command, fullContext);
957
+ } else {
958
+ result = { blocked: false };
959
+ }
960
+ break;
961
+ case "prompt":
962
+ if (hook.prompt) {
963
+ result = await runPromptHook(hook.prompt, fullContext);
964
+ } else {
965
+ result = { blocked: false };
966
+ }
967
+ break;
968
+ case "agent":
969
+ result = { blocked: false };
970
+ break;
971
+ default:
972
+ result = { blocked: false };
973
+ }
974
+ if (result.blocked) {
975
+ return result;
976
+ }
977
+ }
978
+ return { blocked: false };
979
+ }
980
+ /**
981
+ * Get all hooks for an event
982
+ */
983
+ get(event) {
984
+ return this.hooks.get(event) || [];
985
+ }
986
+ };
987
+ function createHooksRunner(hooks) {
988
+ return new HooksRunner(hooks);
989
+ }
990
+
991
+ // src/permissions/index.ts
992
+ function matchPattern(pattern, toolName, input) {
993
+ if (!pattern.includes("(")) {
994
+ return pattern === toolName || pattern === "*";
995
+ }
996
+ const match = pattern.match(/^(\w+)\((.*)\)$/);
997
+ if (!match) return false;
998
+ const [, name, argPattern] = match;
999
+ if (name !== toolName && name !== "*") {
1000
+ return false;
1001
+ }
1002
+ const argRegex = new RegExp(argPattern);
1003
+ for (const value of Object.values(input)) {
1004
+ if (typeof value === "string" && argRegex.test(value)) {
1005
+ return true;
1006
+ }
1007
+ }
1008
+ return false;
1009
+ }
1010
+ var PermissionChecker = class {
1011
+ config;
1012
+ constructor(config) {
1013
+ this.config = config || { yolo: false };
1014
+ }
1015
+ /**
1016
+ * Check if a tool execution is permitted
1017
+ */
1018
+ async check(toolName, input) {
1019
+ if (this.config.yolo) {
1020
+ return { permitted: true };
1021
+ }
1022
+ if (this.config.disallowedTools) {
1023
+ for (const pattern of this.config.disallowedTools) {
1024
+ if (matchPattern(pattern, toolName, input)) {
1025
+ return {
1026
+ permitted: false,
1027
+ reason: `Tool ${toolName} is disallowed by pattern: ${pattern}`
1028
+ };
1029
+ }
1030
+ }
1031
+ }
1032
+ if (this.config.autoApprove) {
1033
+ for (const pattern of this.config.autoApprove) {
1034
+ if (matchPattern(pattern, toolName, input)) {
1035
+ return { permitted: true };
1036
+ }
1037
+ }
1038
+ }
1039
+ if (this.config.allowedTools && this.config.allowedTools.length > 0) {
1040
+ for (const pattern of this.config.allowedTools) {
1041
+ if (matchPattern(pattern, toolName, input)) {
1042
+ return { permitted: true };
1043
+ }
1044
+ }
1045
+ return {
1046
+ permitted: false,
1047
+ reason: `Tool ${toolName} is not in allowed list`,
1048
+ requiresConfirmation: true
1049
+ };
1050
+ }
1051
+ const dangerousTools = ["bash", "write", "edit"];
1052
+ if (dangerousTools.includes(toolName)) {
1053
+ return {
1054
+ permitted: false,
1055
+ reason: `Tool ${toolName} requires confirmation`,
1056
+ requiresConfirmation: true
1057
+ };
1058
+ }
1059
+ return { permitted: true };
1060
+ }
1061
+ /**
1062
+ * Update configuration
1063
+ */
1064
+ updateConfig(config) {
1065
+ this.config = { ...this.config, ...config };
1066
+ }
1067
+ /**
1068
+ * Enable YOLO mode
1069
+ */
1070
+ enableYolo() {
1071
+ this.config.yolo = true;
1072
+ }
1073
+ /**
1074
+ * Disable YOLO mode
1075
+ */
1076
+ disableYolo() {
1077
+ this.config.yolo = false;
1078
+ }
1079
+ /**
1080
+ * Add an auto-approve pattern
1081
+ */
1082
+ addAutoApprove(pattern) {
1083
+ if (!this.config.autoApprove) {
1084
+ this.config.autoApprove = [];
1085
+ }
1086
+ this.config.autoApprove.push(pattern);
1087
+ }
1088
+ };
1089
+ function createPermissionChecker(config) {
1090
+ return new PermissionChecker(config);
1091
+ }
1092
+
1093
+ // src/session/index.ts
1094
+ var import_node_fs4 = require("fs");
1095
+ var import_node_path4 = require("path");
1096
+ var import_node_crypto = require("crypto");
1097
+ function getSessionPath(id) {
1098
+ const dir = getSessionsDir();
1099
+ return (0, import_node_path4.join)(dir, `${id}.jsonl`);
1100
+ }
1101
+ function ensureSessionsDir() {
1102
+ const dir = getSessionsDir();
1103
+ if (!(0, import_node_fs4.existsSync)(dir)) {
1104
+ (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
1105
+ }
1106
+ }
1107
+ function createSession(options) {
1108
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1109
+ return {
1110
+ id: (0, import_node_crypto.randomUUID)(),
1111
+ name: options?.name,
1112
+ createdAt: now,
1113
+ updatedAt: now,
1114
+ projectPath: options?.projectPath,
1115
+ model: options?.model,
1116
+ messageCount: 0,
1117
+ messages: []
1118
+ };
1119
+ }
1120
+ function saveSession(session) {
1121
+ ensureSessionsDir();
1122
+ const path = getSessionPath(session.id);
1123
+ const lines = [];
1124
+ const meta = {
1125
+ type: "meta",
1126
+ timestamp: session.updatedAt,
1127
+ data: {
1128
+ id: session.id,
1129
+ name: session.name,
1130
+ createdAt: session.createdAt,
1131
+ updatedAt: session.updatedAt,
1132
+ projectPath: session.projectPath,
1133
+ model: session.model,
1134
+ messageCount: session.messages.length
1135
+ }
1136
+ };
1137
+ lines.push(JSON.stringify(meta));
1138
+ for (const message of session.messages) {
1139
+ const entry = {
1140
+ type: "message",
1141
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1142
+ data: message
1143
+ };
1144
+ lines.push(JSON.stringify(entry));
1145
+ }
1146
+ (0, import_node_fs4.writeFileSync)(path, lines.join("\n") + "\n");
1147
+ }
1148
+ function appendMessage(sessionId, message) {
1149
+ ensureSessionsDir();
1150
+ const path = getSessionPath(sessionId);
1151
+ const entry = {
1152
+ type: "message",
1153
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1154
+ data: message
1155
+ };
1156
+ const line = JSON.stringify(entry) + "\n";
1157
+ if ((0, import_node_fs4.existsSync)(path)) {
1158
+ const content = (0, import_node_fs4.readFileSync)(path, "utf-8");
1159
+ (0, import_node_fs4.writeFileSync)(path, content + line);
1160
+ } else {
1161
+ (0, import_node_fs4.writeFileSync)(path, line);
1162
+ }
1163
+ }
1164
+ function loadSession(id) {
1165
+ const path = getSessionPath(id);
1166
+ if (!(0, import_node_fs4.existsSync)(path)) {
1167
+ return null;
1168
+ }
1169
+ const content = (0, import_node_fs4.readFileSync)(path, "utf-8");
1170
+ const lines = content.trim().split("\n").filter(Boolean);
1171
+ let meta = null;
1172
+ const messages = [];
1173
+ for (const line of lines) {
1174
+ try {
1175
+ const entry = JSON.parse(line);
1176
+ if (entry.type === "meta") {
1177
+ meta = entry.data;
1178
+ } else if (entry.type === "message") {
1179
+ messages.push(entry.data);
1180
+ }
1181
+ } catch {
1182
+ }
1183
+ }
1184
+ if (!meta) {
1185
+ meta = {
1186
+ id,
1187
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1188
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1189
+ messageCount: messages.length
1190
+ };
1191
+ }
1192
+ return {
1193
+ ...meta,
1194
+ messages
1195
+ };
1196
+ }
1197
+ function listSessions() {
1198
+ ensureSessionsDir();
1199
+ const dir = getSessionsDir();
1200
+ const files = (0, import_node_fs4.readdirSync)(dir).filter((f) => f.endsWith(".jsonl"));
1201
+ const sessions = [];
1202
+ for (const file of files) {
1203
+ const id = file.replace(".jsonl", "");
1204
+ const session = loadSession(id);
1205
+ if (session) {
1206
+ sessions.push({
1207
+ id: session.id,
1208
+ name: session.name,
1209
+ createdAt: session.createdAt,
1210
+ updatedAt: session.updatedAt,
1211
+ projectPath: session.projectPath,
1212
+ model: session.model,
1213
+ messageCount: session.messageCount
1214
+ });
1215
+ }
1216
+ }
1217
+ return sessions.sort(
1218
+ (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
1219
+ );
1220
+ }
1221
+ function deleteSession(id) {
1222
+ const path = getSessionPath(id);
1223
+ if ((0, import_node_fs4.existsSync)(path)) {
1224
+ (0, import_node_fs4.unlinkSync)(path);
1225
+ return true;
1226
+ }
1227
+ return false;
1228
+ }
1229
+ function getRecentSession() {
1230
+ const sessions = listSessions();
1231
+ if (sessions.length === 0) {
1232
+ return null;
1233
+ }
1234
+ return loadSession(sessions[0].id);
1235
+ }
1236
+ function forkSession(id) {
1237
+ const original = loadSession(id);
1238
+ if (!original) {
1239
+ return null;
1240
+ }
1241
+ const forked = createSession({
1242
+ name: original.name ? `${original.name} (fork)` : void 0,
1243
+ projectPath: original.projectPath,
1244
+ model: original.model
1245
+ });
1246
+ forked.messages = [...original.messages];
1247
+ forked.messageCount = original.messageCount;
1248
+ saveSession(forked);
1249
+ return forked;
1250
+ }
1251
+ var SessionManager = class {
1252
+ currentSession = null;
1253
+ /**
1254
+ * Start a new session
1255
+ */
1256
+ start(options) {
1257
+ this.currentSession = createSession(options);
1258
+ return this.currentSession;
1259
+ }
1260
+ /**
1261
+ * Resume an existing session
1262
+ */
1263
+ resume(id) {
1264
+ const session = loadSession(id);
1265
+ if (session) {
1266
+ this.currentSession = session;
1267
+ }
1268
+ return session;
1269
+ }
1270
+ /**
1271
+ * Resume the most recent session
1272
+ */
1273
+ resumeRecent() {
1274
+ const session = getRecentSession();
1275
+ if (session) {
1276
+ this.currentSession = session;
1277
+ }
1278
+ return session;
1279
+ }
1280
+ /**
1281
+ * Get current session
1282
+ */
1283
+ get current() {
1284
+ return this.currentSession;
1285
+ }
1286
+ /**
1287
+ * Add a message to current session
1288
+ */
1289
+ addMessage(message) {
1290
+ if (!this.currentSession) {
1291
+ throw new Error("No active session");
1292
+ }
1293
+ this.currentSession.messages.push(message);
1294
+ this.currentSession.messageCount = this.currentSession.messages.length;
1295
+ this.currentSession.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1296
+ }
1297
+ /**
1298
+ * Save current session
1299
+ */
1300
+ save() {
1301
+ if (this.currentSession) {
1302
+ saveSession(this.currentSession);
1303
+ }
1304
+ }
1305
+ /**
1306
+ * Clear current session messages
1307
+ */
1308
+ clear() {
1309
+ if (this.currentSession) {
1310
+ this.currentSession.messages = [];
1311
+ this.currentSession.messageCount = 0;
1312
+ this.currentSession.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1313
+ }
1314
+ }
1315
+ /**
1316
+ * End current session
1317
+ */
1318
+ end() {
1319
+ if (this.currentSession) {
1320
+ this.save();
1321
+ this.currentSession = null;
1322
+ }
1323
+ }
1324
+ };
1325
+ function createSessionManager() {
1326
+ return new SessionManager();
1327
+ }
1328
+
1329
+ // src/mcp/index.ts
1330
+ var import_node_child_process3 = require("child_process");
1331
+ var import_node_events = require("events");
1332
+ var import_zod3 = require("zod");
1333
+ var MCPConnection = class extends import_node_events.EventEmitter {
1334
+ server;
1335
+ process = null;
1336
+ messageId = 0;
1337
+ pendingRequests = /* @__PURE__ */ new Map();
1338
+ buffer = "";
1339
+ tools = [];
1340
+ initialized = false;
1341
+ constructor(server) {
1342
+ super();
1343
+ this.server = server;
1344
+ }
1345
+ /**
1346
+ * Connect to the MCP server
1347
+ */
1348
+ async connect() {
1349
+ if (this.server.transport !== "stdio") {
1350
+ throw new Error(`Transport ${this.server.transport} not yet supported`);
1351
+ }
1352
+ if (!this.server.command) {
1353
+ throw new Error("Command is required for stdio transport");
1354
+ }
1355
+ this.process = (0, import_node_child_process3.spawn)(this.server.command, this.server.args || [], {
1356
+ stdio: ["pipe", "pipe", "pipe"],
1357
+ env: {
1358
+ ...process.env,
1359
+ ...this.server.env
1360
+ }
1361
+ });
1362
+ this.process.stdout?.on("data", (data) => {
1363
+ this.handleData(data.toString());
1364
+ });
1365
+ this.process.stderr?.on("data", (data) => {
1366
+ this.emit("log", data.toString());
1367
+ });
1368
+ this.process.on("exit", (code) => {
1369
+ this.emit("exit", code);
1370
+ this.initialized = false;
1371
+ });
1372
+ await this.initialize();
1373
+ }
1374
+ /**
1375
+ * Handle incoming data
1376
+ */
1377
+ handleData(data) {
1378
+ this.buffer += data;
1379
+ const lines = this.buffer.split("\n");
1380
+ this.buffer = lines.pop() || "";
1381
+ for (const line of lines) {
1382
+ if (!line.trim()) continue;
1383
+ try {
1384
+ const message = JSON.parse(line);
1385
+ if ("id" in message && this.pendingRequests.has(message.id)) {
1386
+ const pending = this.pendingRequests.get(message.id);
1387
+ this.pendingRequests.delete(message.id);
1388
+ if (message.error) {
1389
+ pending.reject(new Error(message.error.message));
1390
+ } else {
1391
+ pending.resolve(message.result);
1392
+ }
1393
+ } else if ("method" in message && !("id" in message)) {
1394
+ this.emit("notification", message);
1395
+ }
1396
+ } catch (err) {
1397
+ this.emit("error", err);
1398
+ }
1399
+ }
1400
+ }
1401
+ /**
1402
+ * Send a request and wait for response
1403
+ */
1404
+ async request(method, params) {
1405
+ if (!this.process?.stdin) {
1406
+ throw new Error("Not connected");
1407
+ }
1408
+ const id = ++this.messageId;
1409
+ const request = {
1410
+ jsonrpc: "2.0",
1411
+ id,
1412
+ method,
1413
+ params
1414
+ };
1415
+ return new Promise((resolve3, reject) => {
1416
+ this.pendingRequests.set(id, { resolve: resolve3, reject });
1417
+ this.process.stdin.write(JSON.stringify(request) + "\n");
1418
+ setTimeout(() => {
1419
+ if (this.pendingRequests.has(id)) {
1420
+ this.pendingRequests.delete(id);
1421
+ reject(new Error("Request timeout"));
1422
+ }
1423
+ }, 3e4);
1424
+ });
1425
+ }
1426
+ /**
1427
+ * Initialize the MCP connection
1428
+ */
1429
+ async initialize() {
1430
+ await this.request("initialize", {
1431
+ protocolVersion: "2024-11-05",
1432
+ capabilities: {
1433
+ tools: {}
1434
+ },
1435
+ clientInfo: {
1436
+ name: "puppuccino",
1437
+ version: "0.1.0"
1438
+ }
1439
+ });
1440
+ this.process?.stdin?.write(JSON.stringify({
1441
+ jsonrpc: "2.0",
1442
+ method: "notifications/initialized"
1443
+ }) + "\n");
1444
+ const result = await this.request("tools/list");
1445
+ this.tools = result.tools || [];
1446
+ this.initialized = true;
1447
+ this.emit("ready");
1448
+ }
1449
+ /**
1450
+ * Get tools from this server
1451
+ */
1452
+ getTools() {
1453
+ return this.tools.map((t) => this.convertTool(t));
1454
+ }
1455
+ /**
1456
+ * Convert MCP tool to our Tool format
1457
+ */
1458
+ convertTool(mcpTool) {
1459
+ const properties = {};
1460
+ for (const [key, schema] of Object.entries(mcpTool.inputSchema.properties)) {
1461
+ const s = schema;
1462
+ let zodType;
1463
+ switch (s.type) {
1464
+ case "string":
1465
+ zodType = import_zod3.z.string();
1466
+ break;
1467
+ case "number":
1468
+ zodType = import_zod3.z.number();
1469
+ break;
1470
+ case "boolean":
1471
+ zodType = import_zod3.z.boolean();
1472
+ break;
1473
+ case "array":
1474
+ zodType = import_zod3.z.array(import_zod3.z.unknown());
1475
+ break;
1476
+ case "object":
1477
+ zodType = import_zod3.z.object({});
1478
+ break;
1479
+ default:
1480
+ zodType = import_zod3.z.unknown();
1481
+ }
1482
+ if (s.description) {
1483
+ zodType = zodType.describe(s.description);
1484
+ }
1485
+ if (!mcpTool.inputSchema.required?.includes(key)) {
1486
+ zodType = zodType.optional();
1487
+ }
1488
+ properties[key] = zodType;
1489
+ }
1490
+ const parameters = import_zod3.z.object(properties);
1491
+ return {
1492
+ name: `mcp__${this.server.name}__${mcpTool.name}`,
1493
+ description: mcpTool.description,
1494
+ parameters,
1495
+ execute: async (input) => {
1496
+ const result = await this.callTool(mcpTool.name, input);
1497
+ return typeof result === "string" ? result : JSON.stringify(result);
1498
+ }
1499
+ };
1500
+ }
1501
+ /**
1502
+ * Call a tool on this server
1503
+ */
1504
+ async callTool(name, input) {
1505
+ const result = await this.request(
1506
+ "tools/call",
1507
+ { name, arguments: input }
1508
+ );
1509
+ const textContent = result.content?.filter((c) => c.type === "text").map((c) => c.text).join("\n");
1510
+ return textContent || result;
1511
+ }
1512
+ /**
1513
+ * Disconnect from the server
1514
+ */
1515
+ disconnect() {
1516
+ if (this.process) {
1517
+ this.process.kill();
1518
+ this.process = null;
1519
+ }
1520
+ this.initialized = false;
1521
+ this.tools = [];
1522
+ }
1523
+ /**
1524
+ * Check if connected
1525
+ */
1526
+ get isConnected() {
1527
+ return this.initialized;
1528
+ }
1529
+ };
1530
+ var MCPClient = class {
1531
+ connections = /* @__PURE__ */ new Map();
1532
+ /**
1533
+ * Add and connect to an MCP server
1534
+ */
1535
+ async addServer(server) {
1536
+ if (!server.enabled) return;
1537
+ const connection = new MCPConnection(server);
1538
+ await connection.connect();
1539
+ this.connections.set(server.name, connection);
1540
+ }
1541
+ /**
1542
+ * Remove an MCP server
1543
+ */
1544
+ removeServer(name) {
1545
+ const connection = this.connections.get(name);
1546
+ if (connection) {
1547
+ connection.disconnect();
1548
+ this.connections.delete(name);
1549
+ }
1550
+ }
1551
+ /**
1552
+ * Get all tools from all servers
1553
+ */
1554
+ getTools() {
1555
+ const tools = [];
1556
+ for (const connection of this.connections.values()) {
1557
+ if (connection.isConnected) {
1558
+ tools.push(...connection.getTools());
1559
+ }
1560
+ }
1561
+ return tools;
1562
+ }
1563
+ /**
1564
+ * Call a tool (parses the mcp__server__tool format)
1565
+ */
1566
+ async callTool(name, input) {
1567
+ const parts = name.split("__");
1568
+ if (parts.length !== 3 || parts[0] !== "mcp") {
1569
+ throw new Error(`Invalid MCP tool name: ${name}`);
1570
+ }
1571
+ const [, serverName, toolName] = parts;
1572
+ const connection = this.connections.get(serverName);
1573
+ if (!connection) {
1574
+ throw new Error(`MCP server not found: ${serverName}`);
1575
+ }
1576
+ return connection.callTool(toolName, input);
1577
+ }
1578
+ /**
1579
+ * Disconnect all servers
1580
+ */
1581
+ disconnectAll() {
1582
+ for (const connection of this.connections.values()) {
1583
+ connection.disconnect();
1584
+ }
1585
+ this.connections.clear();
1586
+ }
1587
+ /**
1588
+ * Get connected server names
1589
+ */
1590
+ get servers() {
1591
+ return Array.from(this.connections.keys());
1592
+ }
1593
+ };
1594
+ function createMCPClient() {
1595
+ return new MCPClient();
1596
+ }
1597
+
1598
+ // src/skills/index.ts
1599
+ var import_node_fs5 = require("fs");
1600
+ var import_node_path5 = require("path");
1601
+ var import_node_os2 = require("os");
1602
+ var SKILL_LOCATIONS = [
1603
+ // Personal skills
1604
+ (0, import_node_path5.join)((0, import_node_os2.homedir)(), ".puppuccino", "skills"),
1605
+ (0, import_node_path5.join)((0, import_node_os2.homedir)(), ".config", "puppuccino", "skills"),
1606
+ // Project skills (will be prefixed with project path)
1607
+ ".puppuccino/skills"
1608
+ ];
1609
+ function parseFrontmatter(content) {
1610
+ const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
1611
+ const match = content.match(frontmatterRegex);
1612
+ if (!match) {
1613
+ return { meta: {}, content };
1614
+ }
1615
+ const [, yaml, rest] = match;
1616
+ const meta = {};
1617
+ for (const line of yaml.split("\n")) {
1618
+ const colonIdx = line.indexOf(":");
1619
+ if (colonIdx === -1) continue;
1620
+ const key = line.slice(0, colonIdx).trim();
1621
+ let value = line.slice(colonIdx + 1).trim();
1622
+ if (value === "true") value = true;
1623
+ else if (value === "false") value = false;
1624
+ if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
1625
+ value = value.slice(1, -1).split(",").map((s) => s.trim());
1626
+ }
1627
+ const camelKey = key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1628
+ meta[camelKey] = value;
1629
+ }
1630
+ return { meta, content: rest.trim() };
1631
+ }
1632
+ function loadSkillFromDir(dir) {
1633
+ const skillFile = (0, import_node_path5.join)(dir, "SKILL.md");
1634
+ if (!(0, import_node_fs5.existsSync)(skillFile)) {
1635
+ return null;
1636
+ }
1637
+ const content = (0, import_node_fs5.readFileSync)(skillFile, "utf-8");
1638
+ const { meta, content: skillContent } = parseFrontmatter(content);
1639
+ const name = meta.name || (0, import_node_path5.dirname)(dir).split("/").pop() || "";
1640
+ return {
1641
+ name,
1642
+ description: meta.description,
1643
+ disableModelInvocation: meta.disableModelInvocation,
1644
+ userInvocable: meta.userInvocable !== false,
1645
+ // default true
1646
+ allowedTools: meta.allowedTools,
1647
+ model: meta.model,
1648
+ context: meta.context,
1649
+ agent: meta.agent,
1650
+ content: skillContent,
1651
+ path: skillFile
1652
+ };
1653
+ }
1654
+ function loadSkillsFromDir(dir) {
1655
+ if (!(0, import_node_fs5.existsSync)(dir)) {
1656
+ return [];
1657
+ }
1658
+ const skills = [];
1659
+ try {
1660
+ const entries = (0, import_node_fs5.readdirSync)(dir, { withFileTypes: true });
1661
+ for (const entry of entries) {
1662
+ if (entry.isDirectory()) {
1663
+ const skill = loadSkillFromDir((0, import_node_path5.join)(dir, entry.name));
1664
+ if (skill) {
1665
+ skills.push(skill);
1666
+ }
1667
+ }
1668
+ }
1669
+ } catch {
1670
+ }
1671
+ return skills;
1672
+ }
1673
+ var SkillsLoader = class {
1674
+ skills = /* @__PURE__ */ new Map();
1675
+ projectPath;
1676
+ constructor(projectPath) {
1677
+ this.projectPath = projectPath;
1678
+ this.reload();
1679
+ }
1680
+ /**
1681
+ * Reload all skills
1682
+ */
1683
+ reload() {
1684
+ this.skills.clear();
1685
+ const locations = [...SKILL_LOCATIONS].reverse();
1686
+ for (const location of locations) {
1687
+ let dir;
1688
+ if (location.startsWith(".")) {
1689
+ if (!this.projectPath) continue;
1690
+ dir = (0, import_node_path5.join)(this.projectPath, location);
1691
+ } else {
1692
+ dir = location;
1693
+ }
1694
+ const skills = loadSkillsFromDir(dir);
1695
+ for (const skill of skills) {
1696
+ this.skills.set(skill.name, skill);
1697
+ }
1698
+ }
1699
+ }
1700
+ /**
1701
+ * Get a skill by name
1702
+ */
1703
+ get(name) {
1704
+ return this.skills.get(name);
1705
+ }
1706
+ /**
1707
+ * Get all skills
1708
+ */
1709
+ all() {
1710
+ return Array.from(this.skills.values());
1711
+ }
1712
+ /**
1713
+ * Get user-invocable skills
1714
+ */
1715
+ userInvocable() {
1716
+ return this.all().filter((s) => s.userInvocable !== false);
1717
+ }
1718
+ /**
1719
+ * Get model-invocable skills
1720
+ */
1721
+ modelInvocable() {
1722
+ return this.all().filter((s) => !s.disableModelInvocation);
1723
+ }
1724
+ /**
1725
+ * Check if a skill exists
1726
+ */
1727
+ has(name) {
1728
+ return this.skills.has(name);
1729
+ }
1730
+ /**
1731
+ * Expand a skill's content with arguments
1732
+ */
1733
+ expand(name, args) {
1734
+ const skill = this.get(name);
1735
+ if (!skill) return null;
1736
+ let content = skill.content;
1737
+ if (args) {
1738
+ const argParts = args.split(/\s+/);
1739
+ content = content.replace(/\$ARGUMENTS/g, args);
1740
+ content = content.replace(/\$ARGUMENTS\[(\d+)\]/g, (_, n) => argParts[parseInt(n)] || "");
1741
+ content = content.replace(/\$(\d+)/g, (_, n) => argParts[parseInt(n) - 1] || "");
1742
+ }
1743
+ return content;
1744
+ }
1745
+ };
1746
+ function createSkillsLoader(projectPath) {
1747
+ return new SkillsLoader(projectPath);
1748
+ }
1749
+
1750
+ // src/context/index.ts
1751
+ var import_node_fs6 = require("fs");
1752
+ var import_node_path6 = require("path");
1753
+ var IGNORED_DIRS = /* @__PURE__ */ new Set([
1754
+ "node_modules",
1755
+ ".git",
1756
+ ".svn",
1757
+ ".hg",
1758
+ "dist",
1759
+ "build",
1760
+ ".next",
1761
+ ".nuxt",
1762
+ ".turbo",
1763
+ "coverage",
1764
+ "__pycache__",
1765
+ ".pytest_cache",
1766
+ "venv",
1767
+ ".venv"
1768
+ ]);
1769
+ function buildTree(dir, depth = 3, current = 0) {
1770
+ if (current >= depth) return [];
1771
+ try {
1772
+ const entries = (0, import_node_fs6.readdirSync)(dir, { withFileTypes: true });
1773
+ const result = [];
1774
+ for (const entry of entries) {
1775
+ if (entry.name.startsWith(".") || IGNORED_DIRS.has(entry.name)) {
1776
+ continue;
1777
+ }
1778
+ if (entry.isDirectory()) {
1779
+ result.push({
1780
+ name: entry.name,
1781
+ type: "directory",
1782
+ children: buildTree((0, import_node_path6.join)(dir, entry.name), depth, current + 1)
1783
+ });
1784
+ } else {
1785
+ result.push({
1786
+ name: entry.name,
1787
+ type: "file"
1788
+ });
1789
+ }
1790
+ }
1791
+ return result.sort((a, b) => {
1792
+ if (a.type === b.type) return a.name.localeCompare(b.name);
1793
+ return a.type === "directory" ? -1 : 1;
1794
+ });
1795
+ } catch {
1796
+ return [];
1797
+ }
1798
+ }
1799
+ function formatTree(tree, prefix = "") {
1800
+ const lines = [];
1801
+ for (let i = 0; i < tree.length; i++) {
1802
+ const item = tree[i];
1803
+ const isLast = i === tree.length - 1;
1804
+ const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
1805
+ const childPrefix = isLast ? " " : "\u2502 ";
1806
+ const suffix = item.type === "directory" ? "/" : "";
1807
+ lines.push(`${prefix}${connector}${item.name}${suffix}`);
1808
+ if (item.children && item.children.length > 0) {
1809
+ lines.push(formatTree(item.children, prefix + childPrefix));
1810
+ }
1811
+ }
1812
+ return lines.join("\n");
1813
+ }
1814
+ function findGitRoot(dir) {
1815
+ let current = dir;
1816
+ while (current !== "/") {
1817
+ if ((0, import_node_fs6.existsSync)((0, import_node_path6.join)(current, ".git"))) {
1818
+ return current;
1819
+ }
1820
+ current = (0, import_node_path6.join)(current, "..");
1821
+ }
1822
+ return void 0;
1823
+ }
1824
+ function loadProjectContext(projectPath) {
1825
+ const name = (0, import_node_path6.basename)(projectPath);
1826
+ const gitRoot = findGitRoot(projectPath);
1827
+ let packageJson;
1828
+ const packageJsonPath = (0, import_node_path6.join)(projectPath, "package.json");
1829
+ if ((0, import_node_fs6.existsSync)(packageJsonPath)) {
1830
+ try {
1831
+ packageJson = JSON.parse((0, import_node_fs6.readFileSync)(packageJsonPath, "utf-8"));
1832
+ } catch {
1833
+ }
1834
+ }
1835
+ let readme;
1836
+ const readmeNames = ["README.md", "README.txt", "README", "readme.md"];
1837
+ for (const readmeName of readmeNames) {
1838
+ const readmePath = (0, import_node_path6.join)(projectPath, readmeName);
1839
+ if ((0, import_node_fs6.existsSync)(readmePath)) {
1840
+ try {
1841
+ readme = (0, import_node_fs6.readFileSync)(readmePath, "utf-8");
1842
+ if (readme.length > 2e3) {
1843
+ readme = readme.slice(0, 2e3) + "\n...(truncated)";
1844
+ }
1845
+ } catch {
1846
+ }
1847
+ break;
1848
+ }
1849
+ }
1850
+ const tree = buildTree(projectPath);
1851
+ const structure = formatTree(tree);
1852
+ return {
1853
+ path: projectPath,
1854
+ name,
1855
+ gitRoot,
1856
+ packageJson,
1857
+ readme,
1858
+ structure
1859
+ };
1860
+ }
1861
+ function formatProjectContext(context) {
1862
+ const sections = [];
1863
+ sections.push(`## Project: ${context.name}`);
1864
+ sections.push(`Path: ${context.path}`);
1865
+ if (context.gitRoot) {
1866
+ sections.push(`Git root: ${context.gitRoot}`);
1867
+ }
1868
+ if (context.packageJson) {
1869
+ const pkg = context.packageJson;
1870
+ if (pkg.description) {
1871
+ sections.push(`
1872
+ Description: ${pkg.description}`);
1873
+ }
1874
+ if (pkg.scripts) {
1875
+ sections.push(`
1876
+ Available scripts: ${Object.keys(pkg.scripts).join(", ")}`);
1877
+ }
1878
+ }
1879
+ sections.push(`
1880
+ ## Project Structure
1881
+ \`\`\`
1882
+ ${context.structure}
1883
+ \`\`\``);
1884
+ if (context.readme) {
1885
+ sections.push(`
1886
+ ## README
1887
+ ${context.readme}`);
1888
+ }
1889
+ return sections.join("\n");
1890
+ }
1891
+ var Memory = class {
1892
+ entries = [];
1893
+ maxEntries = 100;
1894
+ /**
1895
+ * Add an entry
1896
+ */
1897
+ add(type, content) {
1898
+ this.entries.push({
1899
+ type,
1900
+ content,
1901
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1902
+ });
1903
+ if (this.entries.length > this.maxEntries) {
1904
+ this.entries = this.entries.slice(-this.maxEntries);
1905
+ }
1906
+ }
1907
+ /**
1908
+ * Get all entries
1909
+ */
1910
+ all() {
1911
+ return [...this.entries];
1912
+ }
1913
+ /**
1914
+ * Get entries by type
1915
+ */
1916
+ byType(type) {
1917
+ return this.entries.filter((e) => e.type === type);
1918
+ }
1919
+ /**
1920
+ * Format memory for system prompt
1921
+ */
1922
+ format() {
1923
+ if (this.entries.length === 0) return "";
1924
+ const sections = ["## Memory"];
1925
+ const facts = this.byType("fact");
1926
+ if (facts.length > 0) {
1927
+ sections.push("\n### Facts");
1928
+ for (const fact of facts) {
1929
+ sections.push(`- ${fact.content}`);
1930
+ }
1931
+ }
1932
+ const preferences = this.byType("preference");
1933
+ if (preferences.length > 0) {
1934
+ sections.push("\n### User Preferences");
1935
+ for (const pref of preferences) {
1936
+ sections.push(`- ${pref.content}`);
1937
+ }
1938
+ }
1939
+ const summaries = this.byType("summary");
1940
+ if (summaries.length > 0) {
1941
+ sections.push("\n### Previous Context");
1942
+ for (const summary of summaries.slice(-3)) {
1943
+ sections.push(`- ${summary.content}`);
1944
+ }
1945
+ }
1946
+ return sections.join("\n");
1947
+ }
1948
+ /**
1949
+ * Clear all entries
1950
+ */
1951
+ clear() {
1952
+ this.entries = [];
1953
+ }
1954
+ /**
1955
+ * Export entries
1956
+ */
1957
+ export() {
1958
+ return [...this.entries];
1959
+ }
1960
+ /**
1961
+ * Import entries
1962
+ */
1963
+ import(entries) {
1964
+ this.entries = [...entries];
1965
+ }
1966
+ };
1967
+ function createMemory() {
1968
+ return new Memory();
1969
+ }
1970
+
1971
+ // src/index.ts
1972
+ var VERSION = "0.1.0";
1973
+ // Annotate the CommonJS export names for ESM import in node:
1974
+ 0 && (module.exports = {
1975
+ BUILTIN_TOOLS,
1976
+ BashTool,
1977
+ CONFIG_PATHS,
1978
+ ConfigSchema,
1979
+ DEFAULT_SYSTEM_PROMPT,
1980
+ EditTool,
1981
+ GlobTool,
1982
+ GrepTool,
1983
+ HookSchema,
1984
+ HooksRunner,
1985
+ ListTool,
1986
+ MCPClient,
1987
+ MCPConnection,
1988
+ MCPServerSchema,
1989
+ Memory,
1990
+ PermissionChecker,
1991
+ PermissionsSchema,
1992
+ Provider,
1993
+ ReadTool,
1994
+ SessionManager,
1995
+ SkillsLoader,
1996
+ ToolRegistry,
1997
+ VERSION,
1998
+ WriteTool,
1999
+ appendMessage,
2000
+ createAgentState,
2001
+ createHooksRunner,
2002
+ createMCPClient,
2003
+ createMemory,
2004
+ createPermissionChecker,
2005
+ createProvider,
2006
+ createProviderInstance,
2007
+ createSession,
2008
+ createSessionManager,
2009
+ createSkillsLoader,
2010
+ createToolRegistry,
2011
+ deleteSession,
2012
+ forkSession,
2013
+ formatProjectContext,
2014
+ getDataDir,
2015
+ getRecentSession,
2016
+ getSessionsDir,
2017
+ getUserConfigPath,
2018
+ listSessions,
2019
+ loadConfig,
2020
+ loadProjectContext,
2021
+ loadSession,
2022
+ runAgent,
2023
+ runAgentLoop,
2024
+ saveConfig,
2025
+ saveSession
2026
+ });