@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 +2026 -0
- package/dist/index.d.cts +980 -0
- package/dist/index.d.ts +980 -0
- package/dist/index.js +1949 -0
- package/package.json +39 -0
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
|
+
});
|