@gemdoq/codi 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/cli.js ADDED
@@ -0,0 +1,4569 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // node_modules/tsup/assets/esm_shims.js
13
+ import path from "path";
14
+ import { fileURLToPath } from "url";
15
+ var init_esm_shims = __esm({
16
+ "node_modules/tsup/assets/esm_shims.js"() {
17
+ "use strict";
18
+ }
19
+ });
20
+
21
+ // src/ui/renderer.ts
22
+ var renderer_exports = {};
23
+ __export(renderer_exports, {
24
+ renderAssistantPrefix: () => renderAssistantPrefix,
25
+ renderCodeBlock: () => renderCodeBlock,
26
+ renderDiff: () => renderDiff,
27
+ renderError: () => renderError,
28
+ renderInfo: () => renderInfo,
29
+ renderMarkdown: () => renderMarkdown,
30
+ renderPrompt: () => renderPrompt,
31
+ renderSuccess: () => renderSuccess,
32
+ renderToolCall: () => renderToolCall,
33
+ renderToolResult: () => renderToolResult,
34
+ renderUserMessage: () => renderUserMessage,
35
+ renderWarning: () => renderWarning
36
+ });
37
+ import chalk2 from "chalk";
38
+ import { marked } from "marked";
39
+ import TerminalRenderer from "marked-terminal";
40
+ import { highlight } from "cli-highlight";
41
+ import { createTwoFilesPatch } from "diff";
42
+ function renderMarkdown(text) {
43
+ try {
44
+ const rendered = marked.parse(text);
45
+ return (typeof rendered === "string" ? rendered : "").trimEnd();
46
+ } catch {
47
+ return text;
48
+ }
49
+ }
50
+ function renderDiff(filePath, oldContent, newContent) {
51
+ if (!filePath && !oldContent && newContent) {
52
+ return colorDiffLines(newContent);
53
+ }
54
+ const patch = createTwoFilesPatch(
55
+ `a/${filePath}`,
56
+ `b/${filePath}`,
57
+ oldContent,
58
+ newContent,
59
+ "",
60
+ "",
61
+ { context: 3 }
62
+ );
63
+ return colorDiffLines(patch);
64
+ }
65
+ function colorDiffLines(text) {
66
+ return text.split("\n").map((line) => {
67
+ if (line.startsWith("+++") || line.startsWith("---")) {
68
+ return chalk2.bold(line);
69
+ }
70
+ if (line.startsWith("+")) {
71
+ return chalk2.green(line);
72
+ }
73
+ if (line.startsWith("-")) {
74
+ return chalk2.red(line);
75
+ }
76
+ if (line.startsWith("@@")) {
77
+ return chalk2.cyan(line);
78
+ }
79
+ return chalk2.dim(line);
80
+ }).join("\n");
81
+ }
82
+ function renderCodeBlock(code, language) {
83
+ try {
84
+ return highlight(code, { language, ignoreIllegals: true });
85
+ } catch {
86
+ return chalk2.cyan(code);
87
+ }
88
+ }
89
+ function renderToolCall(toolName, args) {
90
+ const header = chalk2.yellow(`\u26A1 ${toolName}`);
91
+ const argStr = Object.entries(args).map(([k, v]) => {
92
+ const val = typeof v === "string" && v.length > 100 ? v.slice(0, 100) + "..." : String(v);
93
+ return chalk2.dim(` ${k}: `) + val;
94
+ }).join("\n");
95
+ return `${header}
96
+ ${argStr}`;
97
+ }
98
+ function renderToolResult(toolName, result, isError) {
99
+ const icon = isError ? chalk2.red("\u2717") : chalk2.green("\u2713");
100
+ const header = `${icon} ${chalk2.yellow(toolName)}`;
101
+ const content = isError ? chalk2.red(result) : chalk2.dim(result);
102
+ const maxLen = 500;
103
+ const truncated = content.length > maxLen ? content.slice(0, maxLen) + chalk2.dim("\n... (truncated)") : content;
104
+ return `${header}
105
+ ${truncated}`;
106
+ }
107
+ function renderError(message) {
108
+ return chalk2.red(`\u2717 ${message}`);
109
+ }
110
+ function renderWarning(message) {
111
+ return chalk2.yellow(`\u26A0 ${message}`);
112
+ }
113
+ function renderInfo(message) {
114
+ return chalk2.blue(`\u2139 ${message}`);
115
+ }
116
+ function renderSuccess(message) {
117
+ return chalk2.green(`\u2713 ${message}`);
118
+ }
119
+ function renderUserMessage(message) {
120
+ return chalk2.white(message);
121
+ }
122
+ function renderAssistantPrefix() {
123
+ return chalk2.green.bold("codi");
124
+ }
125
+ function renderPrompt() {
126
+ return chalk2.cyan.bold("codi > ");
127
+ }
128
+ var renderer;
129
+ var init_renderer = __esm({
130
+ "src/ui/renderer.ts"() {
131
+ "use strict";
132
+ init_esm_shims();
133
+ renderer = new TerminalRenderer({
134
+ codespan: chalk2.cyan,
135
+ strong: chalk2.bold,
136
+ em: chalk2.italic,
137
+ heading: chalk2.green.bold,
138
+ firstHeading: chalk2.magenta.underline.bold,
139
+ code: chalk2.yellow,
140
+ link: chalk2.blue,
141
+ href: chalk2.blue.underline,
142
+ unescape: true,
143
+ emoji: false,
144
+ width: process.stdout.columns || 100,
145
+ tab: 2
146
+ });
147
+ marked.setOptions({ renderer });
148
+ }
149
+ });
150
+
151
+ // src/tools/tool.ts
152
+ function makeToolResult(output3, metadata) {
153
+ return { success: true, output: output3, metadata };
154
+ }
155
+ function makeToolError(error, metadata) {
156
+ return { success: false, output: error, error, metadata };
157
+ }
158
+ var init_tool = __esm({
159
+ "src/tools/tool.ts"() {
160
+ "use strict";
161
+ init_esm_shims();
162
+ }
163
+ });
164
+
165
+ // src/tools/task-tools.ts
166
+ var task_tools_exports = {};
167
+ __export(task_tools_exports, {
168
+ taskCreateTool: () => taskCreateTool,
169
+ taskGetTool: () => taskGetTool,
170
+ taskListTool: () => taskListTool,
171
+ taskManager: () => taskManager,
172
+ taskUpdateTool: () => taskUpdateTool
173
+ });
174
+ function formatTask(task) {
175
+ const lines = [
176
+ `#${task.id} [${task.status}] ${task.subject}`
177
+ ];
178
+ if (task.description) lines.push(` ${task.description}`);
179
+ if (task.owner) lines.push(` Owner: ${task.owner}`);
180
+ if (task.blocks.length) lines.push(` Blocks: ${task.blocks.join(", ")}`);
181
+ if (task.blockedBy.length) lines.push(` Blocked by: ${task.blockedBy.join(", ")}`);
182
+ return lines.join("\n");
183
+ }
184
+ var TaskManager, taskManager, taskCreateTool, taskUpdateTool, taskListTool, taskGetTool;
185
+ var init_task_tools = __esm({
186
+ "src/tools/task-tools.ts"() {
187
+ "use strict";
188
+ init_esm_shims();
189
+ init_tool();
190
+ TaskManager = class {
191
+ tasks = /* @__PURE__ */ new Map();
192
+ nextId = 1;
193
+ create(subject, description, activeForm) {
194
+ const id = String(this.nextId++);
195
+ const task = {
196
+ id,
197
+ subject,
198
+ description,
199
+ activeForm,
200
+ status: "pending",
201
+ blocks: [],
202
+ blockedBy: [],
203
+ metadata: {},
204
+ createdAt: Date.now(),
205
+ updatedAt: Date.now()
206
+ };
207
+ this.tasks.set(id, task);
208
+ return task;
209
+ }
210
+ get(id) {
211
+ return this.tasks.get(id);
212
+ }
213
+ update(id, updates) {
214
+ const task = this.tasks.get(id);
215
+ if (!task) return void 0;
216
+ if (updates.status) task.status = updates.status;
217
+ if (updates.subject) task.subject = updates.subject;
218
+ if (updates.description) task.description = updates.description;
219
+ if (updates.activeForm) task.activeForm = updates.activeForm;
220
+ if (updates.owner) task.owner = updates.owner;
221
+ if (updates.metadata) Object.assign(task.metadata, updates.metadata);
222
+ if (updates.addBlocks) {
223
+ task.blocks = [.../* @__PURE__ */ new Set([...task.blocks, ...updates.addBlocks])];
224
+ }
225
+ if (updates.addBlockedBy) {
226
+ task.blockedBy = [.../* @__PURE__ */ new Set([...task.blockedBy, ...updates.addBlockedBy])];
227
+ }
228
+ if (updates.status === "deleted") {
229
+ this.tasks.delete(id);
230
+ }
231
+ task.updatedAt = Date.now();
232
+ return task;
233
+ }
234
+ list() {
235
+ return [...this.tasks.values()].filter((t) => t.status !== "deleted");
236
+ }
237
+ };
238
+ taskManager = new TaskManager();
239
+ taskCreateTool = {
240
+ name: "task_create",
241
+ description: `Create a new task to track work.`,
242
+ inputSchema: {
243
+ type: "object",
244
+ properties: {
245
+ subject: { type: "string", description: "Brief task title" },
246
+ description: { type: "string", description: "Detailed description" },
247
+ activeForm: { type: "string", description: 'Present continuous form (e.g., "Running tests")' }
248
+ },
249
+ required: ["subject", "description"]
250
+ },
251
+ dangerous: false,
252
+ readOnly: false,
253
+ async execute(input3) {
254
+ const task = taskManager.create(
255
+ String(input3["subject"]),
256
+ String(input3["description"]),
257
+ input3["activeForm"] ? String(input3["activeForm"]) : void 0
258
+ );
259
+ return makeToolResult(`Task #${task.id} created: ${task.subject}`);
260
+ }
261
+ };
262
+ taskUpdateTool = {
263
+ name: "task_update",
264
+ description: `Update a task's status, description, or dependencies.`,
265
+ inputSchema: {
266
+ type: "object",
267
+ properties: {
268
+ taskId: { type: "string", description: "Task ID" },
269
+ status: { type: "string", enum: ["pending", "in_progress", "completed", "deleted"] },
270
+ subject: { type: "string" },
271
+ description: { type: "string" },
272
+ activeForm: { type: "string" },
273
+ owner: { type: "string" },
274
+ addBlocks: { type: "array", items: { type: "string" } },
275
+ addBlockedBy: { type: "array", items: { type: "string" } }
276
+ },
277
+ required: ["taskId"]
278
+ },
279
+ dangerous: false,
280
+ readOnly: false,
281
+ async execute(input3) {
282
+ const task = taskManager.update(String(input3["taskId"]), input3);
283
+ if (!task) return makeToolError(`Task not found: ${input3["taskId"]}`);
284
+ return makeToolResult(`Task #${task.id} updated: ${formatTask(task)}`);
285
+ }
286
+ };
287
+ taskListTool = {
288
+ name: "task_list",
289
+ description: `List all tasks with their status.`,
290
+ inputSchema: {
291
+ type: "object",
292
+ properties: {},
293
+ required: []
294
+ },
295
+ dangerous: false,
296
+ readOnly: true,
297
+ async execute() {
298
+ const tasks = taskManager.list();
299
+ if (tasks.length === 0) {
300
+ return makeToolResult("No tasks.");
301
+ }
302
+ return makeToolResult(tasks.map(formatTask).join("\n\n"));
303
+ }
304
+ };
305
+ taskGetTool = {
306
+ name: "task_get",
307
+ description: `Get full details of a specific task.`,
308
+ inputSchema: {
309
+ type: "object",
310
+ properties: {
311
+ taskId: { type: "string", description: "Task ID" }
312
+ },
313
+ required: ["taskId"]
314
+ },
315
+ dangerous: false,
316
+ readOnly: true,
317
+ async execute(input3) {
318
+ const task = taskManager.get(String(input3["taskId"]));
319
+ if (!task) return makeToolError(`Task not found: ${input3["taskId"]}`);
320
+ return makeToolResult(formatTask(task));
321
+ }
322
+ };
323
+ }
324
+ });
325
+
326
+ // src/cli.ts
327
+ init_esm_shims();
328
+ import chalk13 from "chalk";
329
+
330
+ // src/setup-wizard.ts
331
+ init_esm_shims();
332
+ import * as fs from "fs";
333
+ import * as path2 from "path";
334
+ import chalk from "chalk";
335
+ import * as readline from "readline/promises";
336
+ import { stdin as input, stdout as output } from "process";
337
+ var SETTINGS_DIR = path2.join(
338
+ process.env["HOME"] || process.env["USERPROFILE"] || "~",
339
+ ".codi"
340
+ );
341
+ var SETTINGS_PATH = path2.join(SETTINGS_DIR, "settings.json");
342
+ async function needsSetup() {
343
+ if (process.env["GEMINI_API_KEY"]) return false;
344
+ if (process.env["OPENAI_API_KEY"]) return false;
345
+ if (process.env["ANTHROPIC_API_KEY"]) return false;
346
+ if (fs.existsSync(SETTINGS_PATH)) {
347
+ try {
348
+ const content = fs.readFileSync(SETTINGS_PATH, "utf-8");
349
+ const settings = JSON.parse(content);
350
+ if (settings.apiKeys?.openai || settings.apiKeys?.anthropic) {
351
+ return false;
352
+ }
353
+ } catch {
354
+ }
355
+ }
356
+ return true;
357
+ }
358
+ async function runSetupWizard() {
359
+ const rl = readline.createInterface({ input, output });
360
+ console.log("");
361
+ console.log(chalk.cyan.bold(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
362
+ console.log(chalk.cyan.bold(" \u2502") + chalk.white.bold(" Codi (\uCF54\uB514) - First Time Setup ") + chalk.cyan.bold("\u2502"));
363
+ console.log(chalk.cyan.bold(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
364
+ console.log("");
365
+ console.log(chalk.dim(" API key not found. Let's set one up!"));
366
+ console.log("");
367
+ console.log(chalk.bold(" Which AI provider would you like to use?"));
368
+ console.log("");
369
+ console.log(` ${chalk.cyan("1.")} Google Gemini ${chalk.green("(Free tier available)")}`);
370
+ console.log(` ${chalk.cyan("2.")} OpenAI (GPT-4o, etc.)`);
371
+ console.log(` ${chalk.cyan("3.")} Anthropic (Claude)`);
372
+ console.log(` ${chalk.cyan("4.")} Ollama ${chalk.green("(Free, local)")}`);
373
+ console.log("");
374
+ const choice = await rl.question(chalk.cyan(" Choice [1]: "));
375
+ const providerChoice = choice.trim() || "1";
376
+ let provider = "openai";
377
+ let envVarName = "GEMINI_API_KEY";
378
+ let keyName = "openai";
379
+ let signupUrl = "";
380
+ let model = "gemini-2.5-flash";
381
+ switch (providerChoice) {
382
+ case "1":
383
+ provider = "openai";
384
+ envVarName = "GEMINI_API_KEY";
385
+ keyName = "openai";
386
+ signupUrl = "https://aistudio.google.com/apikey";
387
+ model = "gemini-2.5-flash";
388
+ break;
389
+ case "2":
390
+ provider = "openai";
391
+ envVarName = "OPENAI_API_KEY";
392
+ keyName = "openai";
393
+ signupUrl = "https://platform.openai.com/api-keys";
394
+ model = "gpt-4o";
395
+ break;
396
+ case "3":
397
+ provider = "anthropic";
398
+ envVarName = "ANTHROPIC_API_KEY";
399
+ keyName = "anthropic";
400
+ signupUrl = "https://console.anthropic.com/settings/keys";
401
+ model = "claude-sonnet-4-20250514";
402
+ break;
403
+ case "4":
404
+ console.log("");
405
+ console.log(chalk.green(" \u2713 Ollama selected! No API key needed."));
406
+ console.log(chalk.dim(" Make sure Ollama is running: ollama serve"));
407
+ console.log(chalk.dim(" And pull a model: ollama pull llama3.1"));
408
+ console.log("");
409
+ console.log(chalk.dim(" Start Codi with:"));
410
+ console.log(chalk.cyan(" codi --provider ollama --model llama3.1"));
411
+ console.log("");
412
+ rl.close();
413
+ return null;
414
+ default:
415
+ console.log(chalk.yellow(" Invalid choice. Using Gemini (default)."));
416
+ break;
417
+ }
418
+ if (signupUrl) {
419
+ console.log("");
420
+ console.log(chalk.bold(" Get your API key:"));
421
+ console.log(chalk.cyan(` \u2192 ${signupUrl}`));
422
+ console.log("");
423
+ }
424
+ const apiKey = await rl.question(chalk.cyan(" Paste your API key: "));
425
+ rl.close();
426
+ if (!apiKey.trim()) {
427
+ console.log(chalk.yellow("\n No API key provided. Setup cancelled."));
428
+ console.log(chalk.dim(` You can set it later: export ${envVarName}=your-key
429
+ `));
430
+ return null;
431
+ }
432
+ const rl2 = readline.createInterface({ input, output });
433
+ console.log("");
434
+ console.log(chalk.bold(" How would you like to save it?"));
435
+ console.log("");
436
+ console.log(` ${chalk.cyan("1.")} Save to ~/.codi/settings.json ${chalk.green("(Recommended)")}`);
437
+ console.log(` ${chalk.cyan("2.")} Show export command (manual setup)`);
438
+ console.log("");
439
+ const saveChoice = await rl2.question(chalk.cyan(" Choice [1]: "));
440
+ rl2.close();
441
+ const trimmedKey = apiKey.trim();
442
+ if (saveChoice.trim() === "2") {
443
+ console.log("");
444
+ console.log(chalk.bold(" Add this to your shell profile (~/.zshrc or ~/.bashrc):"));
445
+ console.log("");
446
+ console.log(chalk.cyan(` export ${envVarName}=${trimmedKey}`));
447
+ console.log("");
448
+ console.log(chalk.dim(" Then restart your terminal or run: source ~/.zshrc"));
449
+ console.log("");
450
+ return { apiKey: trimmedKey, provider };
451
+ }
452
+ try {
453
+ if (!fs.existsSync(SETTINGS_DIR)) {
454
+ fs.mkdirSync(SETTINGS_DIR, { recursive: true });
455
+ }
456
+ let settings = {};
457
+ if (fs.existsSync(SETTINGS_PATH)) {
458
+ try {
459
+ settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, "utf-8"));
460
+ } catch {
461
+ settings = {};
462
+ }
463
+ }
464
+ if (!settings["apiKeys"] || typeof settings["apiKeys"] !== "object") {
465
+ settings["apiKeys"] = {};
466
+ }
467
+ settings["apiKeys"][keyName] = trimmedKey;
468
+ if (providerChoice === "2") {
469
+ settings["provider"] = "openai";
470
+ settings["model"] = model;
471
+ settings["baseUrls"] = {};
472
+ } else if (providerChoice === "3") {
473
+ settings["provider"] = "anthropic";
474
+ settings["model"] = model;
475
+ }
476
+ fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8");
477
+ console.log("");
478
+ console.log(chalk.green(" \u2713 Settings saved to ~/.codi/settings.json"));
479
+ console.log(chalk.dim(` Provider: ${provider} | Model: ${model}`));
480
+ console.log("");
481
+ } catch (err) {
482
+ console.log(chalk.red(`
483
+ Failed to save settings: ${err}`));
484
+ console.log(chalk.dim(` Set manually: export ${envVarName}=${trimmedKey}
485
+ `));
486
+ }
487
+ return { apiKey: trimmedKey, provider };
488
+ }
489
+
490
+ // src/config/config.ts
491
+ init_esm_shims();
492
+ import * as fs2 from "fs";
493
+ import * as path3 from "path";
494
+ var DEFAULT_CONFIG = {
495
+ provider: "openai",
496
+ model: "gemini-2.5-flash",
497
+ maxTokens: 8192,
498
+ apiKeys: {},
499
+ baseUrls: {
500
+ openai: "https://generativelanguage.googleapis.com/v1beta/openai"
501
+ },
502
+ permissions: {
503
+ allow: ["read_file", "glob", "grep", "list_dir", "ask_user"],
504
+ deny: [],
505
+ ask: ["write_file", "edit_file", "multi_edit", "bash", "git", "web_fetch", "web_search", "notebook_edit"]
506
+ },
507
+ hooks: {},
508
+ mcpServers: {},
509
+ customCommands: [],
510
+ sandbox: false,
511
+ autoCompactThreshold: 0.7,
512
+ memoryEnabled: true
513
+ };
514
+ var ConfigManager = class {
515
+ config;
516
+ configPaths = [];
517
+ constructor() {
518
+ this.config = { ...DEFAULT_CONFIG };
519
+ this.loadAll();
520
+ }
521
+ loadAll() {
522
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
523
+ this.loadFile(path3.join(home, ".codi", "settings.json"));
524
+ this.loadFile(path3.join(process.cwd(), ".codi", "settings.json"));
525
+ this.loadFile(path3.join(process.cwd(), ".codi", "settings.local.json"));
526
+ if (process.env["GEMINI_API_KEY"]) {
527
+ this.config.apiKeys.openai = process.env["GEMINI_API_KEY"];
528
+ }
529
+ if (process.env["ANTHROPIC_API_KEY"]) {
530
+ this.config.apiKeys.anthropic = process.env["ANTHROPIC_API_KEY"];
531
+ }
532
+ if (process.env["OPENAI_API_KEY"]) {
533
+ this.config.apiKeys.openai = process.env["OPENAI_API_KEY"];
534
+ }
535
+ if (process.env["CODI_MODEL"]) {
536
+ this.config.model = process.env["CODI_MODEL"];
537
+ }
538
+ if (process.env["CODI_PROVIDER"]) {
539
+ this.config.provider = process.env["CODI_PROVIDER"];
540
+ }
541
+ }
542
+ loadFile(filePath) {
543
+ try {
544
+ if (!fs2.existsSync(filePath)) return;
545
+ const content = fs2.readFileSync(filePath, "utf-8");
546
+ const parsed = JSON.parse(content);
547
+ this.configPaths.push(filePath);
548
+ this.mergeConfig(parsed);
549
+ } catch {
550
+ }
551
+ }
552
+ mergeConfig(partial) {
553
+ for (const [key, value] of Object.entries(partial)) {
554
+ if (key === "permissions" && typeof value === "object" && value !== null) {
555
+ const perms = value;
556
+ if (Array.isArray(perms["allow"])) {
557
+ this.config.permissions.allow = [
558
+ .../* @__PURE__ */ new Set([...this.config.permissions.allow, ...perms["allow"]])
559
+ ];
560
+ }
561
+ if (Array.isArray(perms["deny"])) {
562
+ this.config.permissions.deny = [
563
+ .../* @__PURE__ */ new Set([...this.config.permissions.deny, ...perms["deny"]])
564
+ ];
565
+ }
566
+ if (Array.isArray(perms["ask"])) {
567
+ this.config.permissions.ask = [
568
+ .../* @__PURE__ */ new Set([...this.config.permissions.ask, ...perms["ask"]])
569
+ ];
570
+ }
571
+ } else if (key === "hooks" && typeof value === "object" && value !== null) {
572
+ Object.assign(this.config.hooks, value);
573
+ } else if (key === "mcpServers" && typeof value === "object" && value !== null) {
574
+ Object.assign(this.config.mcpServers, value);
575
+ } else if (key === "apiKeys" && typeof value === "object" && value !== null) {
576
+ Object.assign(this.config.apiKeys, value);
577
+ } else if (key === "baseUrls" && typeof value === "object" && value !== null) {
578
+ Object.assign(this.config.baseUrls, value);
579
+ } else if (key in this.config) {
580
+ this.config[key] = value;
581
+ }
582
+ }
583
+ }
584
+ get() {
585
+ return this.config;
586
+ }
587
+ reload() {
588
+ this.config = { ...DEFAULT_CONFIG };
589
+ this.configPaths = [];
590
+ this.loadAll();
591
+ }
592
+ set(key, value) {
593
+ this.config[key] = value;
594
+ }
595
+ getConfigPaths() {
596
+ return this.configPaths;
597
+ }
598
+ save(scope) {
599
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
600
+ let filePath;
601
+ switch (scope) {
602
+ case "user":
603
+ filePath = path3.join(home, ".codi", "settings.json");
604
+ break;
605
+ case "project":
606
+ filePath = path3.join(process.cwd(), ".codi", "settings.json");
607
+ break;
608
+ case "local":
609
+ filePath = path3.join(process.cwd(), ".codi", "settings.local.json");
610
+ break;
611
+ }
612
+ const dir = path3.dirname(filePath);
613
+ if (!fs2.existsSync(dir)) {
614
+ fs2.mkdirSync(dir, { recursive: true });
615
+ }
616
+ fs2.writeFileSync(filePath, JSON.stringify(this.config, null, 2), "utf-8");
617
+ }
618
+ };
619
+ var configManager = new ConfigManager();
620
+
621
+ // src/repl.ts
622
+ init_esm_shims();
623
+ import * as readline2 from "readline/promises";
624
+ import { stdin as input2, stdout as output2 } from "process";
625
+ import chalk4 from "chalk";
626
+ import { execSync } from "child_process";
627
+ import { edit } from "external-editor";
628
+
629
+ // src/ui/keybindings.ts
630
+ init_esm_shims();
631
+ var KeyBindingManager = class {
632
+ bindings = /* @__PURE__ */ new Map();
633
+ register(binding) {
634
+ const id = this.makeId(binding);
635
+ this.bindings.set(id, binding);
636
+ }
637
+ makeId(binding) {
638
+ const parts = [];
639
+ if (binding.ctrl) parts.push("ctrl");
640
+ if (binding.meta) parts.push("meta");
641
+ if (binding.shift) parts.push("shift");
642
+ parts.push(binding.key);
643
+ return parts.join("+");
644
+ }
645
+ async handle(str, key) {
646
+ const id = this.makeId({
647
+ key: key.name ?? str,
648
+ ctrl: key.ctrl,
649
+ meta: key.meta,
650
+ shift: key.shift
651
+ });
652
+ const binding = this.bindings.get(id);
653
+ if (binding) {
654
+ await binding.handler();
655
+ return true;
656
+ }
657
+ return false;
658
+ }
659
+ listBindings() {
660
+ return [...this.bindings.values()];
661
+ }
662
+ };
663
+
664
+ // src/repl.ts
665
+ init_renderer();
666
+
667
+ // src/ui/status-line.ts
668
+ init_esm_shims();
669
+ import chalk3 from "chalk";
670
+ var StatusLine = class {
671
+ info = {
672
+ model: "",
673
+ provider: "",
674
+ inputTokens: 0,
675
+ outputTokens: 0,
676
+ cost: 0
677
+ };
678
+ enabled = true;
679
+ update(partial) {
680
+ Object.assign(this.info, partial);
681
+ }
682
+ setEnabled(enabled) {
683
+ this.enabled = enabled;
684
+ }
685
+ render() {
686
+ if (!this.enabled) return "";
687
+ const { model, inputTokens, outputTokens, cost, mode } = this.info;
688
+ const parts = [];
689
+ if (model) parts.push(chalk3.cyan(`[${model}]`));
690
+ if (mode === "plan") parts.push(chalk3.yellow("[PLAN]"));
691
+ parts.push(chalk3.dim(`in:${this.formatTokens(inputTokens)}`));
692
+ parts.push(chalk3.dim(`out:${this.formatTokens(outputTokens)}`));
693
+ if (cost > 0) parts.push(chalk3.green(`$${cost.toFixed(4)}`));
694
+ return parts.join(chalk3.dim(" | "));
695
+ }
696
+ formatTokens(n) {
697
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
698
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
699
+ return String(n);
700
+ }
701
+ getInfo() {
702
+ return { ...this.info };
703
+ }
704
+ };
705
+ var statusLine = new StatusLine();
706
+
707
+ // src/ui/completer.ts
708
+ init_esm_shims();
709
+ import * as fs3 from "fs";
710
+ import * as path4 from "path";
711
+ var SLASH_COMMANDS = [
712
+ "/help",
713
+ "/quit",
714
+ "/exit",
715
+ "/clear",
716
+ "/reset",
717
+ "/new",
718
+ "/model",
719
+ "/compact",
720
+ "/cost",
721
+ "/config",
722
+ "/permissions",
723
+ "/diff",
724
+ "/save",
725
+ "/resume",
726
+ "/continue",
727
+ "/fork",
728
+ "/plan",
729
+ "/memory",
730
+ "/init",
731
+ "/export",
732
+ "/tasks",
733
+ "/status",
734
+ "/context",
735
+ "/rewind",
736
+ "/mcp"
737
+ ];
738
+ function completer(line) {
739
+ if (line.startsWith("/")) {
740
+ const matches = SLASH_COMMANDS.filter((cmd) => cmd.startsWith(line));
741
+ return [matches, line];
742
+ }
743
+ if (line.includes("@")) {
744
+ const atIndex = line.lastIndexOf("@");
745
+ const partial = line.slice(atIndex + 1);
746
+ const dir = path4.dirname(partial) || ".";
747
+ const base = path4.basename(partial);
748
+ try {
749
+ const entries = fs3.readdirSync(dir === "" ? "." : dir);
750
+ const matches = entries.filter((e) => e.startsWith(base)).map((e) => {
751
+ const full = dir === "." ? e : path4.join(dir, e);
752
+ try {
753
+ return fs3.statSync(full).isDirectory() ? full + "/" : full;
754
+ } catch {
755
+ return full;
756
+ }
757
+ }).map((p) => line.slice(0, atIndex + 1) + p);
758
+ return [matches, line];
759
+ } catch {
760
+ return [[], line];
761
+ }
762
+ }
763
+ return [[], line];
764
+ }
765
+
766
+ // src/repl.ts
767
+ var Repl = class {
768
+ rl = null;
769
+ keyBindings = new KeyBindingManager();
770
+ running = false;
771
+ multilineBuffer = [];
772
+ inMultiline = false;
773
+ pasteMode = false;
774
+ options;
775
+ lastInterruptTime = 0;
776
+ constructor(options) {
777
+ this.options = options;
778
+ this.setupKeyBindings();
779
+ }
780
+ setupKeyBindings() {
781
+ this.keyBindings.register({
782
+ key: "l",
783
+ ctrl: true,
784
+ handler: () => {
785
+ process.stdout.write("\x1B[2J\x1B[0f");
786
+ },
787
+ description: "Clear screen"
788
+ });
789
+ }
790
+ async start() {
791
+ this.running = true;
792
+ this.rl = readline2.createInterface({
793
+ input: input2,
794
+ output: output2,
795
+ prompt: renderPrompt(),
796
+ completer: (line) => completer(line),
797
+ terminal: true
798
+ });
799
+ if (process.stdin.isTTY) {
800
+ process.stdout.write("\x1B[?2004h");
801
+ }
802
+ this.printWelcome();
803
+ while (this.running) {
804
+ try {
805
+ const statusStr = statusLine.render();
806
+ if (statusStr) {
807
+ output2.write(chalk4.dim(statusStr) + "\n");
808
+ }
809
+ this.rl.setPrompt(renderPrompt());
810
+ this.rl.prompt();
811
+ const line = await new Promise((resolve10, reject) => {
812
+ const onLine = (data) => {
813
+ cleanup();
814
+ resolve10(data);
815
+ };
816
+ const onClose = () => {
817
+ cleanup();
818
+ reject(new Error("closed"));
819
+ };
820
+ const onSigint = () => {
821
+ cleanup();
822
+ const now = Date.now();
823
+ if (now - this.lastInterruptTime < 2e3) {
824
+ this.gracefulExit().catch(() => process.exit(1));
825
+ return;
826
+ }
827
+ this.lastInterruptTime = now;
828
+ this.options.onInterrupt();
829
+ console.log(chalk4.dim("\n(Press Ctrl+C again to exit)"));
830
+ resolve10("");
831
+ };
832
+ const cleanup = () => {
833
+ this.rl.removeListener("line", onLine);
834
+ this.rl.removeListener("close", onClose);
835
+ this.rl.removeListener("SIGINT", onSigint);
836
+ };
837
+ this.rl.on("line", onLine);
838
+ this.rl.on("close", onClose);
839
+ this.rl.on("SIGINT", onSigint);
840
+ });
841
+ const trimmed = line.trim();
842
+ if (!trimmed) continue;
843
+ if (trimmed.endsWith("\\")) {
844
+ this.multilineBuffer.push(trimmed.slice(0, -1));
845
+ this.inMultiline = true;
846
+ this.rl.setPrompt(chalk4.dim("... "));
847
+ continue;
848
+ }
849
+ let fullInput;
850
+ if (this.inMultiline) {
851
+ this.multilineBuffer.push(trimmed);
852
+ fullInput = this.multilineBuffer.join("\n");
853
+ this.multilineBuffer = [];
854
+ this.inMultiline = false;
855
+ } else {
856
+ fullInput = trimmed;
857
+ }
858
+ await this.processInput(fullInput);
859
+ } catch (err) {
860
+ if (err instanceof Error && err.message === "closed") {
861
+ await this.gracefulExit();
862
+ return;
863
+ }
864
+ }
865
+ }
866
+ if (process.stdin.isTTY) {
867
+ process.stdout.write("\x1B[?2004l");
868
+ }
869
+ }
870
+ async processInput(input3) {
871
+ const lower = input3.toLowerCase();
872
+ if (lower === "exit" || lower === "quit" || lower === "q") {
873
+ await this.gracefulExit();
874
+ return;
875
+ }
876
+ if (input3.startsWith("/")) {
877
+ const spaceIdx = input3.indexOf(" ");
878
+ const command = spaceIdx === -1 ? input3 : input3.slice(0, spaceIdx);
879
+ const args = spaceIdx === -1 ? "" : input3.slice(spaceIdx + 1).trim();
880
+ const handled = await this.options.onSlashCommand(command, args);
881
+ if (handled) return;
882
+ console.log(renderError(`Unknown command: ${command}. Type /help for available commands.`));
883
+ return;
884
+ }
885
+ if (input3.startsWith("!")) {
886
+ const cmd = input3.slice(1).trim();
887
+ if (!cmd) return;
888
+ try {
889
+ const result = execSync(cmd, {
890
+ encoding: "utf-8",
891
+ stdio: ["inherit", "pipe", "pipe"],
892
+ timeout: 3e4
893
+ });
894
+ console.log(result);
895
+ } catch (err) {
896
+ if (err.stderr) console.error(chalk4.red(err.stderr));
897
+ else if (err.stdout) console.log(err.stdout);
898
+ else console.error(renderError(String(err.message)));
899
+ }
900
+ return;
901
+ }
902
+ let message = input3;
903
+ const atMatches = input3.match(/@([\w./-]+)/g);
904
+ if (atMatches) {
905
+ for (const match of atMatches) {
906
+ const filePath = match.slice(1);
907
+ try {
908
+ const { readFileSync: readFileSync12 } = await import("fs");
909
+ const content = readFileSync12(filePath, "utf-8");
910
+ message = message.replace(match, `
911
+ [File: ${filePath}]
912
+ \`\`\`
913
+ ${content}
914
+ \`\`\`
915
+ `);
916
+ } catch {
917
+ }
918
+ }
919
+ }
920
+ await this.options.onMessage(message);
921
+ }
922
+ openEditor() {
923
+ try {
924
+ const text = edit("", { postfix: ".md" });
925
+ return text.trim() || null;
926
+ } catch {
927
+ return null;
928
+ }
929
+ }
930
+ stop() {
931
+ this.running = false;
932
+ if (this.rl) {
933
+ this.rl.close();
934
+ this.rl = null;
935
+ }
936
+ }
937
+ async gracefulExit() {
938
+ this.stop();
939
+ if (this.options.onExit) {
940
+ await this.options.onExit();
941
+ }
942
+ console.log(chalk4.dim("\nGoodbye!\n"));
943
+ process.exit(0);
944
+ }
945
+ printWelcome() {
946
+ console.log("");
947
+ console.log(chalk4.cyan.bold(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
948
+ console.log(chalk4.cyan.bold(" \u2502") + chalk4.white.bold(" Codi (\uCF54\uB514) v0.1.0 ") + chalk4.cyan.bold("\u2502"));
949
+ console.log(chalk4.cyan.bold(" \u2502") + chalk4.dim(" AI Code Agent for Terminal ") + chalk4.cyan.bold("\u2502"));
950
+ console.log(chalk4.cyan.bold(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
951
+ console.log("");
952
+ console.log(chalk4.dim(" Type /help for commands, Ctrl+D to quit"));
953
+ console.log(chalk4.dim(" Use \\ at end of line for multiline input"));
954
+ console.log("");
955
+ }
956
+ };
957
+
958
+ // src/agent/agent-loop.ts
959
+ init_esm_shims();
960
+
961
+ // src/agent/conversation.ts
962
+ init_esm_shims();
963
+ var Conversation = class _Conversation {
964
+ messages = [];
965
+ systemPrompt = "";
966
+ setSystemPrompt(prompt) {
967
+ this.systemPrompt = prompt;
968
+ }
969
+ getSystemPrompt() {
970
+ return this.systemPrompt;
971
+ }
972
+ addUserMessage(content) {
973
+ this.messages.push({ role: "user", content });
974
+ }
975
+ addAssistantMessage(content) {
976
+ this.messages.push({ role: "assistant", content });
977
+ }
978
+ addToolResults(results) {
979
+ const blocks = results.map((r) => ({
980
+ type: "tool_result",
981
+ tool_use_id: r.tool_use_id,
982
+ content: r.content,
983
+ is_error: r.is_error
984
+ }));
985
+ this.messages.push({ role: "user", content: blocks });
986
+ }
987
+ getMessages() {
988
+ return [...this.messages];
989
+ }
990
+ getLastMessage() {
991
+ return this.messages[this.messages.length - 1];
992
+ }
993
+ getMessageCount() {
994
+ return this.messages.length;
995
+ }
996
+ clear() {
997
+ this.messages = [];
998
+ }
999
+ /**
1000
+ * Replace old messages with a summary, keeping recent messages intact.
1001
+ */
1002
+ compact(summary, keepRecent = 4) {
1003
+ if (this.messages.length <= keepRecent) return;
1004
+ const recent = this.messages.slice(-keepRecent);
1005
+ this.messages = [
1006
+ { role: "user", content: `[Previous conversation summary]
1007
+ ${summary}` },
1008
+ { role: "assistant", content: "Understood. I have the context from our previous conversation." },
1009
+ ...recent
1010
+ ];
1011
+ }
1012
+ /**
1013
+ * Fork the conversation at the current point.
1014
+ */
1015
+ fork() {
1016
+ const forked = new _Conversation();
1017
+ forked.systemPrompt = this.systemPrompt;
1018
+ forked.messages = [...this.messages.map((m) => ({ ...m }))];
1019
+ return forked;
1020
+ }
1021
+ /**
1022
+ * Truncate to a specific number of messages from the end.
1023
+ */
1024
+ truncateTo(count) {
1025
+ if (this.messages.length > count) {
1026
+ this.messages = this.messages.slice(-count);
1027
+ }
1028
+ }
1029
+ /**
1030
+ * Serialize for session saving.
1031
+ */
1032
+ serialize() {
1033
+ return {
1034
+ systemPrompt: this.systemPrompt,
1035
+ messages: this.messages
1036
+ };
1037
+ }
1038
+ /**
1039
+ * Restore from serialized data.
1040
+ */
1041
+ static deserialize(data) {
1042
+ const conv = new _Conversation();
1043
+ conv.systemPrompt = data.systemPrompt;
1044
+ conv.messages = data.messages;
1045
+ return conv;
1046
+ }
1047
+ /**
1048
+ * Estimate rough token count (very approximate).
1049
+ */
1050
+ estimateTokens() {
1051
+ let chars = this.systemPrompt.length;
1052
+ for (const msg of this.messages) {
1053
+ if (typeof msg.content === "string") {
1054
+ chars += msg.content.length;
1055
+ } else {
1056
+ for (const block of msg.content) {
1057
+ if (block.type === "text") chars += block.text.length;
1058
+ else if (block.type === "tool_use") chars += JSON.stringify(block.input).length;
1059
+ else if (block.type === "tool_result") {
1060
+ chars += typeof block.content === "string" ? block.content.length : JSON.stringify(block.content).length;
1061
+ }
1062
+ }
1063
+ }
1064
+ }
1065
+ return Math.ceil(chars / 4);
1066
+ }
1067
+ };
1068
+
1069
+ // src/tools/executor.ts
1070
+ init_esm_shims();
1071
+ init_tool();
1072
+ init_renderer();
1073
+ import chalk5 from "chalk";
1074
+ var ToolExecutor = class {
1075
+ constructor(registry, options = {}) {
1076
+ this.registry = registry;
1077
+ this.options = options;
1078
+ }
1079
+ async executeOne(toolCall) {
1080
+ const tool = this.registry.get(toolCall.name);
1081
+ if (!tool) {
1082
+ return {
1083
+ toolUseId: toolCall.id,
1084
+ toolName: toolCall.name,
1085
+ result: makeToolError(`Unknown tool: ${toolCall.name}. Available tools: ${this.registry.listNames().join(", ")}`)
1086
+ };
1087
+ }
1088
+ if (this.options.planMode && !tool.readOnly) {
1089
+ return {
1090
+ toolUseId: toolCall.id,
1091
+ toolName: toolCall.name,
1092
+ result: makeToolError(`Tool '${toolCall.name}' is not available in plan mode (read-only). Use only read-only tools.`)
1093
+ };
1094
+ }
1095
+ if (tool.dangerous && this.options.permissionCheck) {
1096
+ const allowed = await this.options.permissionCheck(tool, toolCall.input);
1097
+ if (!allowed) {
1098
+ return {
1099
+ toolUseId: toolCall.id,
1100
+ toolName: toolCall.name,
1101
+ result: makeToolError(`Permission denied for tool: ${toolCall.name}`)
1102
+ };
1103
+ }
1104
+ }
1105
+ let input3 = toolCall.input;
1106
+ if (this.options.preHook) {
1107
+ try {
1108
+ const hookResult = await this.options.preHook(toolCall.name, input3);
1109
+ if (!hookResult.proceed) {
1110
+ return {
1111
+ toolUseId: toolCall.id,
1112
+ toolName: toolCall.name,
1113
+ result: makeToolError(`Tool execution blocked by hook for: ${toolCall.name}`)
1114
+ };
1115
+ }
1116
+ if (hookResult.updatedInput) {
1117
+ input3 = hookResult.updatedInput;
1118
+ }
1119
+ } catch (err) {
1120
+ console.error(chalk5.yellow(`Hook error for ${toolCall.name}: ${err}`));
1121
+ }
1122
+ }
1123
+ if (this.options.showToolCalls) {
1124
+ console.log(renderToolCall(toolCall.name, input3));
1125
+ }
1126
+ let result;
1127
+ try {
1128
+ result = await tool.execute(input3);
1129
+ } catch (err) {
1130
+ result = makeToolError(
1131
+ `Tool '${toolCall.name}' threw an error: ${err instanceof Error ? err.message : String(err)}`
1132
+ );
1133
+ }
1134
+ if (this.options.showToolCalls) {
1135
+ console.log(renderToolResult(toolCall.name, result.output, !result.success));
1136
+ }
1137
+ if (this.options.postHook) {
1138
+ try {
1139
+ await this.options.postHook(toolCall.name, input3, result);
1140
+ } catch {
1141
+ }
1142
+ }
1143
+ return {
1144
+ toolUseId: toolCall.id,
1145
+ toolName: toolCall.name,
1146
+ result
1147
+ };
1148
+ }
1149
+ async executeMany(toolCalls) {
1150
+ const safeCalls = [];
1151
+ const dangerousCalls = [];
1152
+ for (const tc of toolCalls) {
1153
+ const tool = this.registry.get(tc.name);
1154
+ if (tool?.dangerous) {
1155
+ dangerousCalls.push(tc);
1156
+ } else {
1157
+ safeCalls.push(tc);
1158
+ }
1159
+ }
1160
+ const safePromises = safeCalls.map((tc) => this.executeOne(tc));
1161
+ const dangerousResults = [];
1162
+ for (const tc of dangerousCalls) {
1163
+ const result = await this.executeOne(tc);
1164
+ dangerousResults.push(result);
1165
+ }
1166
+ const safeResults = await Promise.allSettled(safePromises);
1167
+ const results = [];
1168
+ for (let i = 0; i < safeResults.length; i++) {
1169
+ const r = safeResults[i];
1170
+ if (r.status === "fulfilled") {
1171
+ results.push(r.value);
1172
+ } else {
1173
+ results.push({
1174
+ toolUseId: safeCalls[i].id,
1175
+ toolName: safeCalls[i].name,
1176
+ result: makeToolError(`Tool execution failed: ${r.reason}`)
1177
+ });
1178
+ }
1179
+ }
1180
+ results.push(...dangerousResults);
1181
+ const orderMap = new Map(toolCalls.map((tc, i) => [tc.id, i]));
1182
+ results.sort((a, b) => (orderMap.get(a.toolUseId) ?? 0) - (orderMap.get(b.toolUseId) ?? 0));
1183
+ return results;
1184
+ }
1185
+ setOptions(options) {
1186
+ Object.assign(this.options, options);
1187
+ }
1188
+ };
1189
+
1190
+ // src/agent/token-tracker.ts
1191
+ init_esm_shims();
1192
+
1193
+ // src/llm/types.ts
1194
+ init_esm_shims();
1195
+ var MODEL_COSTS = {
1196
+ "claude-sonnet-4-20250514": { input: 3e-3, output: 0.015 },
1197
+ "claude-opus-4-20250514": { input: 0.015, output: 0.075 },
1198
+ "claude-haiku-3-5-20241022": { input: 8e-4, output: 4e-3 },
1199
+ "gpt-4o": { input: 25e-4, output: 0.01 },
1200
+ "gpt-4o-mini": { input: 15e-5, output: 6e-4 },
1201
+ "gpt-4.1": { input: 2e-3, output: 8e-3 },
1202
+ "gpt-4.1-mini": { input: 4e-4, output: 16e-4 },
1203
+ "gpt-4.1-nano": { input: 1e-4, output: 4e-4 }
1204
+ };
1205
+ function getModelCost(model) {
1206
+ return MODEL_COSTS[model] ?? { input: 0, output: 0 };
1207
+ }
1208
+
1209
+ // src/agent/token-tracker.ts
1210
+ var TokenTracker = class {
1211
+ inputTokens = 0;
1212
+ outputTokens = 0;
1213
+ requests = 0;
1214
+ model = "";
1215
+ setModel(model) {
1216
+ this.model = model;
1217
+ }
1218
+ track(usage) {
1219
+ this.inputTokens += usage.input_tokens;
1220
+ this.outputTokens += usage.output_tokens;
1221
+ this.requests++;
1222
+ }
1223
+ getStats() {
1224
+ const costs = getModelCost(this.model);
1225
+ const cost = this.inputTokens / 1e3 * costs.input + this.outputTokens / 1e3 * costs.output;
1226
+ return {
1227
+ inputTokens: this.inputTokens,
1228
+ outputTokens: this.outputTokens,
1229
+ totalTokens: this.inputTokens + this.outputTokens,
1230
+ cost,
1231
+ requests: this.requests
1232
+ };
1233
+ }
1234
+ getCost() {
1235
+ return this.getStats().cost;
1236
+ }
1237
+ reset() {
1238
+ this.inputTokens = 0;
1239
+ this.outputTokens = 0;
1240
+ this.requests = 0;
1241
+ }
1242
+ format() {
1243
+ const stats = this.getStats();
1244
+ const parts = [
1245
+ `Requests: ${stats.requests}`,
1246
+ `Input: ${this.formatTokens(stats.inputTokens)}`,
1247
+ `Output: ${this.formatTokens(stats.outputTokens)}`,
1248
+ `Total: ${this.formatTokens(stats.totalTokens)}`
1249
+ ];
1250
+ if (stats.cost > 0) {
1251
+ parts.push(`Cost: $${stats.cost.toFixed(4)}`);
1252
+ }
1253
+ return parts.join(" | ");
1254
+ }
1255
+ formatTokens(n) {
1256
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
1257
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
1258
+ return String(n);
1259
+ }
1260
+ };
1261
+ var tokenTracker = new TokenTracker();
1262
+
1263
+ // src/ui/spinner.ts
1264
+ init_esm_shims();
1265
+ import ora from "ora";
1266
+ import chalk6 from "chalk";
1267
+ var currentSpinner = null;
1268
+ function startSpinner(text) {
1269
+ stopSpinner();
1270
+ currentSpinner = ora({
1271
+ text: chalk6.dim(text),
1272
+ spinner: "dots",
1273
+ color: "cyan"
1274
+ }).start();
1275
+ return currentSpinner;
1276
+ }
1277
+ function updateSpinner(text) {
1278
+ if (currentSpinner) {
1279
+ currentSpinner.text = chalk6.dim(text);
1280
+ }
1281
+ }
1282
+ function stopSpinner(symbol) {
1283
+ if (currentSpinner) {
1284
+ if (symbol) {
1285
+ currentSpinner.stopAndPersist({ symbol });
1286
+ } else {
1287
+ currentSpinner.stop();
1288
+ }
1289
+ currentSpinner = null;
1290
+ }
1291
+ }
1292
+
1293
+ // src/agent/agent-loop.ts
1294
+ init_renderer();
1295
+ import chalk7 from "chalk";
1296
+ var MAX_RETRIES = 3;
1297
+ var RETRY_DELAYS = [1e3, 2e3, 4e3];
1298
+ async function agentLoop(userMessage, options) {
1299
+ const {
1300
+ provider,
1301
+ registry,
1302
+ maxIterations = 25,
1303
+ stream = true,
1304
+ showOutput = true
1305
+ } = options;
1306
+ const conversation = options.conversation ?? new Conversation();
1307
+ if (options.systemPrompt) {
1308
+ conversation.setSystemPrompt(options.systemPrompt);
1309
+ }
1310
+ const executor = new ToolExecutor(registry, {
1311
+ permissionCheck: options.permissionCheck,
1312
+ preHook: options.preHook,
1313
+ postHook: options.postHook,
1314
+ planMode: options.planMode,
1315
+ showToolCalls: showOutput
1316
+ });
1317
+ conversation.addUserMessage(userMessage);
1318
+ let iterations = 0;
1319
+ let finalText = "";
1320
+ while (iterations < maxIterations) {
1321
+ iterations++;
1322
+ let response;
1323
+ const spinner = showOutput ? startSpinner("Thinking...") : null;
1324
+ try {
1325
+ response = await callLlmWithRetry(provider, conversation, registry, stream, options, showOutput);
1326
+ } catch (err) {
1327
+ stopSpinner();
1328
+ const errMsg = err instanceof Error ? err.message : String(err);
1329
+ if (showOutput) {
1330
+ console.error(chalk7.red(`
1331
+ LLM Error: ${errMsg}`));
1332
+ }
1333
+ return `Error communicating with LLM: ${errMsg}`;
1334
+ }
1335
+ stopSpinner();
1336
+ if (response.usage) {
1337
+ tokenTracker.track(response.usage);
1338
+ const stats = tokenTracker.getStats();
1339
+ statusLine.update({
1340
+ inputTokens: stats.inputTokens,
1341
+ outputTokens: stats.outputTokens,
1342
+ cost: stats.cost
1343
+ });
1344
+ }
1345
+ conversation.addAssistantMessage(response.content);
1346
+ const wasStreamed = response._streamed === true;
1347
+ if (response.text) {
1348
+ finalText = response.text;
1349
+ }
1350
+ if (response.stopReason === "end_turn" || !response.toolCalls || response.toolCalls.length === 0) {
1351
+ if (showOutput && finalText && !wasStreamed) {
1352
+ console.log("");
1353
+ console.log(renderAssistantPrefix());
1354
+ console.log(renderMarkdown(finalText));
1355
+ console.log("");
1356
+ }
1357
+ if (wasStreamed && showOutput) {
1358
+ console.log("");
1359
+ }
1360
+ break;
1361
+ }
1362
+ if (response.stopReason === "max_tokens") {
1363
+ if (showOutput) {
1364
+ console.log(chalk7.yellow("\n\u26A0 Response truncated (max tokens reached)"));
1365
+ }
1366
+ }
1367
+ if (response.toolCalls && response.toolCalls.length > 0) {
1368
+ if (showOutput) {
1369
+ console.log("");
1370
+ }
1371
+ const results = await executor.executeMany(response.toolCalls);
1372
+ const toolResults = results.map((r) => ({
1373
+ tool_use_id: r.toolUseId,
1374
+ content: r.result.output,
1375
+ is_error: !r.result.success
1376
+ }));
1377
+ conversation.addToolResults(toolResults);
1378
+ }
1379
+ }
1380
+ if (iterations >= maxIterations) {
1381
+ if (showOutput) {
1382
+ console.log(chalk7.yellow(`
1383
+ \u26A0 Agent loop reached maximum iterations (${maxIterations})`));
1384
+ }
1385
+ }
1386
+ return finalText;
1387
+ }
1388
+ async function callLlmWithRetry(provider, conversation, registry, stream, options, showOutput) {
1389
+ let lastError = null;
1390
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
1391
+ try {
1392
+ let streamedText = "";
1393
+ const response = await provider.chat({
1394
+ messages: conversation.getMessages(),
1395
+ systemPrompt: conversation.getSystemPrompt(),
1396
+ tools: registry.getToolDefinitions(options.planMode ? { readOnly: true } : void 0),
1397
+ stream,
1398
+ callbacks: stream ? {
1399
+ onToken: (text) => {
1400
+ stopSpinner();
1401
+ if (showOutput && !streamedText) {
1402
+ process.stdout.write("\n" + renderAssistantPrefix() + "\n");
1403
+ }
1404
+ streamedText += text;
1405
+ if (showOutput) {
1406
+ process.stdout.write(text);
1407
+ }
1408
+ options.onToken?.(text);
1409
+ }
1410
+ } : void 0
1411
+ });
1412
+ if (streamedText) {
1413
+ if (showOutput) {
1414
+ process.stdout.write("\n");
1415
+ }
1416
+ return { ...response, _streamed: true };
1417
+ }
1418
+ return response;
1419
+ } catch (err) {
1420
+ lastError = err instanceof Error ? err : new Error(String(err));
1421
+ const status = err.status || err.statusCode;
1422
+ if (status === 429 || status >= 500 && status < 600) {
1423
+ const delay = RETRY_DELAYS[attempt] || 4e3;
1424
+ if (showOutput) {
1425
+ updateSpinner(`API error (${status}), retrying in ${delay / 1e3}s...`);
1426
+ }
1427
+ await sleep(delay);
1428
+ continue;
1429
+ }
1430
+ throw lastError;
1431
+ }
1432
+ }
1433
+ throw lastError || new Error("Max retries exceeded");
1434
+ }
1435
+ function sleep(ms) {
1436
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
1437
+ }
1438
+
1439
+ // src/agent/system-prompt.ts
1440
+ init_esm_shims();
1441
+ import * as os from "os";
1442
+ import { execSync as execSync2 } from "child_process";
1443
+ var ROLE_DEFINITION = `You are Codi (\uCF54\uB514), a terminal-based AI coding agent. You help users with software engineering tasks including writing code, debugging, refactoring, and explaining code. You have access to tools for file manipulation, code search, shell execution, and more.`;
1444
+ var TOOL_HIERARCHY = `# Tool Usage Rules
1445
+ - Use read_file instead of bash cat/head/tail
1446
+ - Use edit_file instead of bash sed/awk
1447
+ - Use write_file instead of bash echo/cat heredoc
1448
+ - Use glob instead of bash find/ls for file search
1449
+ - Use grep instead of bash grep/rg for content search
1450
+ - Reserve bash for system commands that have no dedicated tool
1451
+ - Use sub_agent for complex multi-step exploration tasks
1452
+ - Call multiple tools in parallel when they are independent`;
1453
+ var CODE_RULES = `# Code Modification Rules
1454
+ - ALWAYS read a file before editing it
1455
+ - Prefer edit_file over write_file for existing files
1456
+ - Make only the changes that are directly requested
1457
+ - Do NOT add unnecessary docstrings, comments, type annotations, or error handling
1458
+ - Do NOT over-engineer or add features beyond what was asked
1459
+ - Be careful about security vulnerabilities (XSS, SQL injection, command injection)
1460
+ - Avoid backwards-compatibility hacks for removed code`;
1461
+ var GIT_SAFETY = `# Git Safety
1462
+ - NEVER amend existing commits - create new commits
1463
+ - NEVER force push
1464
+ - NEVER skip hooks (--no-verify)
1465
+ - NEVER use interactive mode (-i)
1466
+ - NEVER use destructive operations (reset --hard, clean -f, checkout .) without explicit user request
1467
+ - Always create new commits rather than amending
1468
+ - Stage specific files instead of using 'git add -A'
1469
+ - Only commit when explicitly asked`;
1470
+ var RESPONSE_STYLE = `# Response Style
1471
+ - Keep responses short and concise
1472
+ - Reference code with file_path:line_number format
1473
+ - Do NOT use emojis unless the user requests them
1474
+ - Do NOT create documentation files unless requested
1475
+ - Do NOT give time estimates
1476
+ - If blocked, try alternative approaches rather than retrying the same thing`;
1477
+ var SAFETY_RULES = `# Safety & Caution
1478
+ - For irreversible or destructive operations, confirm with the user first
1479
+ - Do NOT brute-force solutions - if something fails, try a different approach
1480
+ - Be careful with operations that affect shared state (push, PR creation, etc.)
1481
+ - Investigate unexpected state before deleting or overwriting
1482
+ - Measure twice, cut once`;
1483
+ function buildSystemPrompt(context) {
1484
+ const fragments = [];
1485
+ fragments.push(ROLE_DEFINITION);
1486
+ fragments.push(buildEnvironmentInfo(context));
1487
+ fragments.push(TOOL_HIERARCHY);
1488
+ fragments.push(CODE_RULES);
1489
+ fragments.push(GIT_SAFETY);
1490
+ fragments.push(RESPONSE_STYLE);
1491
+ fragments.push(SAFETY_RULES);
1492
+ if (context.planMode) {
1493
+ fragments.push(`# Plan Mode
1494
+ You are in PLAN MODE (read-only). You can only use read-only tools (read_file, glob, grep, list_dir, ask_user).
1495
+ You CANNOT modify files, run commands, or make any changes.
1496
+ Analyze the codebase and create a detailed plan for the user to approve.`);
1497
+ }
1498
+ if (context.codiMd) {
1499
+ fragments.push(`# Project Instructions (CODI.md)
1500
+ ${context.codiMd}`);
1501
+ }
1502
+ if (context.memory) {
1503
+ fragments.push(`# Auto Memory
1504
+ ${context.memory}`);
1505
+ }
1506
+ if (context.gitStatus) {
1507
+ fragments.push(`# Current Git Status
1508
+ ${context.gitStatus}`);
1509
+ }
1510
+ return fragments.join("\n\n---\n\n");
1511
+ }
1512
+ function buildEnvironmentInfo(context) {
1513
+ const lines = [
1514
+ "# Environment",
1515
+ `- Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
1516
+ `- OS: ${os.platform()} ${os.release()}`,
1517
+ `- Shell: ${process.env["SHELL"] || "unknown"}`,
1518
+ `- Working Directory: ${context.cwd}`,
1519
+ `- Model: ${context.model}`,
1520
+ `- Provider: ${context.provider}`
1521
+ ];
1522
+ try {
1523
+ execSync2("git rev-parse --is-inside-work-tree", { cwd: context.cwd, stdio: "pipe" });
1524
+ const branch = execSync2("git branch --show-current", { cwd: context.cwd, encoding: "utf-8" }).trim();
1525
+ lines.push(`- Git Branch: ${branch}`);
1526
+ lines.push(`- Is Git Repo: true`);
1527
+ } catch {
1528
+ lines.push(`- Is Git Repo: false`);
1529
+ }
1530
+ return lines.join("\n");
1531
+ }
1532
+ var EXPLORE_SYSTEM_PROMPT = `You are an Explore agent - a fast, read-only agent specialized for codebase exploration. Your job is to quickly find files, search code, and answer questions about the codebase. You have access to: read_file, glob, grep, list_dir. Be thorough but efficient. Return your findings concisely.`;
1533
+ var PLAN_SYSTEM_PROMPT = `You are a Plan agent - a software architect agent for designing implementation plans. Analyze the codebase, identify critical files, consider trade-offs, and return a step-by-step implementation plan. You have access to: read_file, glob, grep, list_dir, web_fetch. Be thorough in your analysis.`;
1534
+ var GENERAL_SYSTEM_PROMPT = `You are a General sub-agent handling a specific task autonomously. Complete the task fully and return a concise summary of what you did. You have access to all tools except creating more sub-agents.`;
1535
+
1536
+ // src/agent/context-compressor.ts
1537
+ init_esm_shims();
1538
+ var DEFAULT_OPTIONS = {
1539
+ threshold: 0.7,
1540
+ maxContextTokens: 2e5,
1541
+ keepRecentMessages: 6
1542
+ };
1543
+ var ContextCompressor = class {
1544
+ options;
1545
+ constructor(options) {
1546
+ this.options = { ...DEFAULT_OPTIONS, ...options };
1547
+ }
1548
+ shouldCompress(conversation) {
1549
+ const estimatedTokens = conversation.estimateTokens();
1550
+ return estimatedTokens > this.options.maxContextTokens * this.options.threshold;
1551
+ }
1552
+ async compress(conversation, provider, focusHint) {
1553
+ const messages = conversation.getMessages();
1554
+ if (messages.length <= this.options.keepRecentMessages) return;
1555
+ const toSummarize = messages.slice(0, -this.options.keepRecentMessages);
1556
+ const summaryContent = toSummarize.map((m) => {
1557
+ const role = m.role;
1558
+ const content = typeof m.content === "string" ? m.content : m.content.map((b) => {
1559
+ if (b.type === "text") return b.text;
1560
+ if (b.type === "tool_use") return `[Tool: ${b.name}]`;
1561
+ if (b.type === "tool_result") return `[Result: ${typeof b.content === "string" ? b.content.slice(0, 200) : "..."}]`;
1562
+ return "";
1563
+ }).filter(Boolean).join("\n");
1564
+ return `${role}: ${content}`;
1565
+ }).join("\n\n");
1566
+ const prompt = focusHint ? `Summarize this conversation concisely, focusing on: ${focusHint}
1567
+
1568
+ ${summaryContent}` : `Summarize this conversation concisely, preserving key decisions, code changes, file paths, and important context:
1569
+
1570
+ ${summaryContent}`;
1571
+ try {
1572
+ const response = await provider.chat({
1573
+ messages: [{ role: "user", content: prompt }],
1574
+ systemPrompt: "You are a conversation summarizer. Create a concise but complete summary that preserves all important technical details, file paths, decisions made, and context needed to continue the conversation.",
1575
+ maxTokens: 2e3
1576
+ });
1577
+ const summary = response.text || "Previous conversation context.";
1578
+ conversation.compact(summary, this.options.keepRecentMessages);
1579
+ } catch {
1580
+ conversation.truncateTo(this.options.keepRecentMessages + 2);
1581
+ }
1582
+ }
1583
+ };
1584
+
1585
+ // src/agent/memory.ts
1586
+ init_esm_shims();
1587
+ import * as fs4 from "fs";
1588
+ import * as path5 from "path";
1589
+ import * as crypto from "crypto";
1590
+ var MemoryManager = class {
1591
+ memoryDir;
1592
+ constructor() {
1593
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
1594
+ const projectHash = crypto.createHash("md5").update(process.cwd()).digest("hex").slice(0, 8);
1595
+ const projectName = path5.basename(process.cwd());
1596
+ this.memoryDir = path5.join(home, ".codi", "projects", `${projectName}-${projectHash}`, "memory");
1597
+ }
1598
+ ensureDir() {
1599
+ if (!fs4.existsSync(this.memoryDir)) {
1600
+ fs4.mkdirSync(this.memoryDir, { recursive: true });
1601
+ }
1602
+ }
1603
+ getMemoryDir() {
1604
+ return this.memoryDir;
1605
+ }
1606
+ loadIndex() {
1607
+ const indexPath = path5.join(this.memoryDir, "MEMORY.md");
1608
+ if (!fs4.existsSync(indexPath)) return "";
1609
+ const content = fs4.readFileSync(indexPath, "utf-8");
1610
+ const lines = content.split("\n");
1611
+ return lines.slice(0, 200).join("\n");
1612
+ }
1613
+ saveIndex(content) {
1614
+ this.ensureDir();
1615
+ const indexPath = path5.join(this.memoryDir, "MEMORY.md");
1616
+ fs4.writeFileSync(indexPath, content, "utf-8");
1617
+ }
1618
+ loadTopic(name) {
1619
+ const topicPath = path5.join(this.memoryDir, `${name}.md`);
1620
+ if (!fs4.existsSync(topicPath)) return null;
1621
+ return fs4.readFileSync(topicPath, "utf-8");
1622
+ }
1623
+ saveTopic(name, content) {
1624
+ this.ensureDir();
1625
+ const topicPath = path5.join(this.memoryDir, `${name}.md`);
1626
+ fs4.writeFileSync(topicPath, content, "utf-8");
1627
+ }
1628
+ listTopics() {
1629
+ if (!fs4.existsSync(this.memoryDir)) return [];
1630
+ return fs4.readdirSync(this.memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").map((f) => f.replace(".md", ""));
1631
+ }
1632
+ buildMemoryPrompt() {
1633
+ const index = this.loadIndex();
1634
+ if (!index) return "";
1635
+ const lines = [
1636
+ `You have a persistent memory directory at ${this.memoryDir}.`,
1637
+ "Use write_file/edit_file to update memory files as you learn patterns.",
1638
+ "",
1639
+ "Current MEMORY.md:",
1640
+ index
1641
+ ];
1642
+ return lines.join("\n");
1643
+ }
1644
+ };
1645
+ var memoryManager = new MemoryManager();
1646
+
1647
+ // src/agent/session.ts
1648
+ init_esm_shims();
1649
+ import * as fs5 from "fs";
1650
+ import * as path6 from "path";
1651
+ import * as crypto2 from "crypto";
1652
+ var SessionManager = class {
1653
+ sessionsDir;
1654
+ constructor() {
1655
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
1656
+ this.sessionsDir = path6.join(home, ".codi", "sessions");
1657
+ }
1658
+ ensureDir() {
1659
+ if (!fs5.existsSync(this.sessionsDir)) {
1660
+ fs5.mkdirSync(this.sessionsDir, { recursive: true });
1661
+ }
1662
+ }
1663
+ save(conversation, name, model) {
1664
+ this.ensureDir();
1665
+ const id = name || crypto2.randomUUID().slice(0, 8);
1666
+ const filePath = path6.join(this.sessionsDir, `${id}.jsonl`);
1667
+ const data = conversation.serialize();
1668
+ const meta = {
1669
+ id,
1670
+ name,
1671
+ createdAt: Date.now(),
1672
+ updatedAt: Date.now(),
1673
+ messageCount: data.messages.length,
1674
+ cwd: process.cwd(),
1675
+ model: model || "unknown"
1676
+ };
1677
+ const lines = [
1678
+ JSON.stringify({ type: "meta", ...meta }),
1679
+ JSON.stringify({ type: "system", content: data.systemPrompt }),
1680
+ ...data.messages.map((m) => JSON.stringify({ type: "message", ...m }))
1681
+ ];
1682
+ fs5.writeFileSync(filePath, lines.join("\n") + "\n", "utf-8");
1683
+ return id;
1684
+ }
1685
+ load(id) {
1686
+ const filePath = path6.join(this.sessionsDir, `${id}.jsonl`);
1687
+ if (!fs5.existsSync(filePath)) return null;
1688
+ const content = fs5.readFileSync(filePath, "utf-8");
1689
+ const lines = content.trim().split("\n").filter(Boolean);
1690
+ let meta = null;
1691
+ let systemPrompt = "";
1692
+ const messages = [];
1693
+ for (const line of lines) {
1694
+ try {
1695
+ const obj = JSON.parse(line);
1696
+ if (obj.type === "meta") {
1697
+ meta = obj;
1698
+ } else if (obj.type === "system") {
1699
+ systemPrompt = obj.content;
1700
+ } else if (obj.type === "message") {
1701
+ messages.push({ role: obj.role, content: obj.content });
1702
+ }
1703
+ } catch {
1704
+ continue;
1705
+ }
1706
+ }
1707
+ if (!meta) return null;
1708
+ const conversation = Conversation.deserialize({ systemPrompt, messages });
1709
+ return { conversation, meta };
1710
+ }
1711
+ list() {
1712
+ this.ensureDir();
1713
+ const files = fs5.readdirSync(this.sessionsDir).filter((f) => f.endsWith(".jsonl"));
1714
+ const sessions = [];
1715
+ for (const file of files) {
1716
+ const filePath = path6.join(this.sessionsDir, file);
1717
+ try {
1718
+ const firstLine = fs5.readFileSync(filePath, "utf-8").split("\n")[0];
1719
+ if (firstLine) {
1720
+ const meta = JSON.parse(firstLine);
1721
+ if (meta.type === "meta") {
1722
+ sessions.push(meta);
1723
+ }
1724
+ }
1725
+ } catch {
1726
+ continue;
1727
+ }
1728
+ }
1729
+ return sessions.sort((a, b) => b.updatedAt - a.updatedAt);
1730
+ }
1731
+ getLatest() {
1732
+ const sessions = this.list();
1733
+ return sessions[0] ?? null;
1734
+ }
1735
+ delete(id) {
1736
+ const filePath = path6.join(this.sessionsDir, `${id}.jsonl`);
1737
+ if (fs5.existsSync(filePath)) {
1738
+ fs5.unlinkSync(filePath);
1739
+ return true;
1740
+ }
1741
+ return false;
1742
+ }
1743
+ cleanup(maxAgeDays = 30) {
1744
+ const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
1745
+ const sessions = this.list();
1746
+ let deleted = 0;
1747
+ for (const session of sessions) {
1748
+ if (session.updatedAt < cutoff) {
1749
+ this.delete(session.id);
1750
+ deleted++;
1751
+ }
1752
+ }
1753
+ return deleted;
1754
+ }
1755
+ };
1756
+ var sessionManager = new SessionManager();
1757
+
1758
+ // src/agent/checkpoint.ts
1759
+ init_esm_shims();
1760
+ import { execSync as execSync3 } from "child_process";
1761
+ var CheckpointManager = class {
1762
+ checkpoints = [];
1763
+ nextId = 1;
1764
+ isGitRepo;
1765
+ constructor() {
1766
+ try {
1767
+ execSync3("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
1768
+ this.isGitRepo = true;
1769
+ } catch {
1770
+ this.isGitRepo = false;
1771
+ }
1772
+ }
1773
+ create(conversation, description) {
1774
+ const checkpoint = {
1775
+ id: this.nextId++,
1776
+ timestamp: Date.now(),
1777
+ conversation: conversation.serialize(),
1778
+ description
1779
+ };
1780
+ if (this.isGitRepo) {
1781
+ try {
1782
+ execSync3('git stash push -m "codi-checkpoint" --include-untracked', {
1783
+ stdio: "pipe",
1784
+ encoding: "utf-8"
1785
+ });
1786
+ const ref = execSync3("git stash list --format=%H", {
1787
+ encoding: "utf-8"
1788
+ }).split("\n")[0]?.trim();
1789
+ if (ref) {
1790
+ checkpoint.gitRef = ref;
1791
+ }
1792
+ try {
1793
+ execSync3("git stash pop", { stdio: "pipe" });
1794
+ } catch {
1795
+ }
1796
+ } catch {
1797
+ }
1798
+ }
1799
+ this.checkpoints.push(checkpoint);
1800
+ if (this.checkpoints.length > 20) {
1801
+ this.checkpoints = this.checkpoints.slice(-20);
1802
+ }
1803
+ return checkpoint.id;
1804
+ }
1805
+ rewind(id) {
1806
+ let checkpoint;
1807
+ if (id !== void 0) {
1808
+ checkpoint = this.checkpoints.find((cp) => cp.id === id);
1809
+ } else {
1810
+ checkpoint = this.checkpoints[this.checkpoints.length - 2];
1811
+ }
1812
+ if (!checkpoint) return null;
1813
+ if (checkpoint.gitRef && this.isGitRepo) {
1814
+ try {
1815
+ execSync3(`git checkout ${checkpoint.gitRef} -- .`, { stdio: "pipe" });
1816
+ } catch {
1817
+ }
1818
+ }
1819
+ const idx = this.checkpoints.indexOf(checkpoint);
1820
+ if (idx >= 0) {
1821
+ this.checkpoints = this.checkpoints.slice(0, idx + 1);
1822
+ }
1823
+ return {
1824
+ conversation: Conversation.deserialize(checkpoint.conversation),
1825
+ description: checkpoint.description
1826
+ };
1827
+ }
1828
+ list() {
1829
+ return this.checkpoints.map((cp) => ({
1830
+ id: cp.id,
1831
+ timestamp: cp.timestamp,
1832
+ description: cp.description
1833
+ }));
1834
+ }
1835
+ };
1836
+ var checkpointManager = new CheckpointManager();
1837
+
1838
+ // src/agent/codi-md.ts
1839
+ init_esm_shims();
1840
+ import * as fs6 from "fs";
1841
+ import * as path7 from "path";
1842
+ function loadCodiMd() {
1843
+ const fragments = [];
1844
+ let dir = process.cwd();
1845
+ const root = path7.parse(dir).root;
1846
+ while (dir !== root) {
1847
+ loadFromDir(dir, fragments);
1848
+ const parent = path7.dirname(dir);
1849
+ if (parent === dir) break;
1850
+ dir = parent;
1851
+ }
1852
+ return fragments.join("\n\n---\n\n");
1853
+ }
1854
+ function loadFromDir(dir, fragments) {
1855
+ const codiPath = path7.join(dir, "CODI.md");
1856
+ if (fs6.existsSync(codiPath)) {
1857
+ try {
1858
+ let content = fs6.readFileSync(codiPath, "utf-8");
1859
+ content = processImports(content, dir);
1860
+ fragments.push(`[CODI.md from ${dir}]
1861
+ ${content}`);
1862
+ } catch {
1863
+ }
1864
+ }
1865
+ const localPath = path7.join(dir, "CODI.local.md");
1866
+ if (fs6.existsSync(localPath)) {
1867
+ try {
1868
+ const content = fs6.readFileSync(localPath, "utf-8");
1869
+ fragments.push(`[CODI.local.md from ${dir}]
1870
+ ${content}`);
1871
+ } catch {
1872
+ }
1873
+ }
1874
+ const rulesDir = path7.join(dir, ".codi", "rules");
1875
+ if (fs6.existsSync(rulesDir)) {
1876
+ try {
1877
+ const files = fs6.readdirSync(rulesDir).filter((f) => f.endsWith(".md")).sort();
1878
+ for (const file of files) {
1879
+ const content = fs6.readFileSync(path7.join(rulesDir, file), "utf-8");
1880
+ fragments.push(`[Rule: ${file}]
1881
+ ${content}`);
1882
+ }
1883
+ } catch {
1884
+ }
1885
+ }
1886
+ }
1887
+ function processImports(content, baseDir) {
1888
+ return content.replace(/@([\w./-]+)/g, (match, importPath) => {
1889
+ const resolved = path7.resolve(baseDir, importPath);
1890
+ if (fs6.existsSync(resolved)) {
1891
+ try {
1892
+ return fs6.readFileSync(resolved, "utf-8");
1893
+ } catch {
1894
+ return match;
1895
+ }
1896
+ }
1897
+ return match;
1898
+ });
1899
+ }
1900
+
1901
+ // src/agent/mode-manager.ts
1902
+ init_esm_shims();
1903
+ var currentMode = "execute";
1904
+ function getMode() {
1905
+ return currentMode;
1906
+ }
1907
+ function setMode(mode) {
1908
+ currentMode = mode;
1909
+ }
1910
+
1911
+ // src/tools/registry.ts
1912
+ init_esm_shims();
1913
+ var ToolRegistry = class _ToolRegistry {
1914
+ tools = /* @__PURE__ */ new Map();
1915
+ register(tool) {
1916
+ this.tools.set(tool.name, tool);
1917
+ }
1918
+ registerAll(tools) {
1919
+ for (const tool of tools) {
1920
+ this.register(tool);
1921
+ }
1922
+ }
1923
+ get(name) {
1924
+ return this.tools.get(name);
1925
+ }
1926
+ has(name) {
1927
+ return this.tools.has(name);
1928
+ }
1929
+ remove(name) {
1930
+ return this.tools.delete(name);
1931
+ }
1932
+ list() {
1933
+ return [...this.tools.values()];
1934
+ }
1935
+ listNames() {
1936
+ return [...this.tools.keys()];
1937
+ }
1938
+ getToolDefinitions(options) {
1939
+ let tools = [...this.tools.values()];
1940
+ if (options?.readOnly) {
1941
+ tools = tools.filter((t) => t.readOnly);
1942
+ }
1943
+ if (options?.names) {
1944
+ const nameSet = new Set(options.names);
1945
+ tools = tools.filter((t) => nameSet.has(t.name));
1946
+ }
1947
+ return tools.map((t) => ({
1948
+ name: t.name,
1949
+ description: t.description,
1950
+ input_schema: t.inputSchema
1951
+ }));
1952
+ }
1953
+ clone() {
1954
+ const newRegistry = new _ToolRegistry();
1955
+ for (const [, tool] of this.tools) {
1956
+ newRegistry.register(tool);
1957
+ }
1958
+ return newRegistry;
1959
+ }
1960
+ subset(names) {
1961
+ const newRegistry = new _ToolRegistry();
1962
+ for (const name of names) {
1963
+ const tool = this.tools.get(name);
1964
+ if (tool) newRegistry.register(tool);
1965
+ }
1966
+ return newRegistry;
1967
+ }
1968
+ };
1969
+
1970
+ // src/security/permission-manager.ts
1971
+ init_esm_shims();
1972
+ import * as readline3 from "readline/promises";
1973
+ import chalk8 from "chalk";
1974
+
1975
+ // src/config/permissions.ts
1976
+ init_esm_shims();
1977
+ function parseRule(rule) {
1978
+ const match = rule.match(/^(\w+)(?:\((.+)\))?$/);
1979
+ if (match) {
1980
+ return { tool: match[1], pattern: match[2] };
1981
+ }
1982
+ return { tool: rule };
1983
+ }
1984
+ function matchesRule(rule, toolName, input3) {
1985
+ if (rule.tool !== toolName) return false;
1986
+ if (!rule.pattern) return true;
1987
+ const pattern = new RegExp(rule.pattern.replace(/\*/g, ".*"));
1988
+ for (const value of Object.values(input3)) {
1989
+ if (typeof value === "string" && pattern.test(value)) {
1990
+ return true;
1991
+ }
1992
+ }
1993
+ return false;
1994
+ }
1995
+ function evaluatePermission(toolName, input3, rules) {
1996
+ for (const rule of rules.deny) {
1997
+ if (matchesRule(parseRule(rule), toolName, input3)) {
1998
+ return "deny";
1999
+ }
2000
+ }
2001
+ for (const rule of rules.ask) {
2002
+ if (matchesRule(parseRule(rule), toolName, input3)) {
2003
+ return "ask";
2004
+ }
2005
+ }
2006
+ for (const rule of rules.allow) {
2007
+ if (matchesRule(parseRule(rule), toolName, input3)) {
2008
+ return "allow";
2009
+ }
2010
+ }
2011
+ return "ask";
2012
+ }
2013
+
2014
+ // src/security/permission-manager.ts
2015
+ var sessionAllowed = /* @__PURE__ */ new Set();
2016
+ var sessionDenied = /* @__PURE__ */ new Set();
2017
+ var currentMode2 = "default";
2018
+ function setPermissionMode(mode) {
2019
+ currentMode2 = mode;
2020
+ }
2021
+ async function checkPermission(tool, input3) {
2022
+ if (!tool.dangerous) return true;
2023
+ if (currentMode2 === "yolo") return true;
2024
+ if (currentMode2 === "acceptEdits" && ["write_file", "edit_file", "multi_edit"].includes(tool.name)) {
2025
+ return true;
2026
+ }
2027
+ if (currentMode2 === "plan" && !tool.readOnly) {
2028
+ return false;
2029
+ }
2030
+ const config = configManager.get();
2031
+ const decision = evaluatePermission(tool.name, input3, config.permissions);
2032
+ if (decision === "allow") return true;
2033
+ if (decision === "deny") {
2034
+ console.log(chalk8.red(`\u2717 Permission denied for ${tool.name} (denied by rule)`));
2035
+ return false;
2036
+ }
2037
+ const key = `${tool.name}:${JSON.stringify(input3)}`;
2038
+ if (sessionAllowed.has(tool.name)) return true;
2039
+ if (sessionDenied.has(tool.name)) return false;
2040
+ return promptUser(tool, input3);
2041
+ }
2042
+ async function promptUser(tool, input3) {
2043
+ console.log("");
2044
+ console.log(chalk8.yellow.bold(`\u26A0 Permission Required: ${tool.name}`));
2045
+ const relevantParams = Object.entries(input3).filter(([, v]) => v !== void 0);
2046
+ for (const [key, value] of relevantParams) {
2047
+ const displayValue = typeof value === "string" && value.length > 200 ? value.slice(0, 200) + "..." : String(value);
2048
+ console.log(chalk8.dim(` ${key}: `) + displayValue);
2049
+ }
2050
+ console.log("");
2051
+ const rl = readline3.createInterface({
2052
+ input: process.stdin,
2053
+ output: process.stdout
2054
+ });
2055
+ try {
2056
+ const answer = await rl.question(
2057
+ chalk8.yellow(`Allow? [${chalk8.bold("Y")}es / ${chalk8.bold("n")}o / ${chalk8.bold("a")}lways for this tool] `)
2058
+ );
2059
+ rl.close();
2060
+ const choice = answer.trim().toLowerCase();
2061
+ if (choice === "a" || choice === "always") {
2062
+ sessionAllowed.add(tool.name);
2063
+ return true;
2064
+ }
2065
+ if (choice === "n" || choice === "no") {
2066
+ return false;
2067
+ }
2068
+ return true;
2069
+ } catch {
2070
+ rl.close();
2071
+ return false;
2072
+ }
2073
+ }
2074
+
2075
+ // src/hooks/hook-manager.ts
2076
+ init_esm_shims();
2077
+ import { exec } from "child_process";
2078
+ var HookManager = class {
2079
+ async runHooks(event, context) {
2080
+ const config = configManager.get();
2081
+ const hookConfigs = config.hooks[event];
2082
+ if (!hookConfigs || hookConfigs.length === 0) {
2083
+ return { proceed: true };
2084
+ }
2085
+ for (const hookConfig of hookConfigs) {
2086
+ if (context.tool && hookConfig.matcher) {
2087
+ const matcher = new RegExp(hookConfig.matcher);
2088
+ if (!matcher.test(context.tool)) continue;
2089
+ }
2090
+ for (const hook of hookConfig.hooks) {
2091
+ if (hook.type === "command" && hook.command) {
2092
+ const result = await this.runCommandHook(hook.command, context, hook.timeout);
2093
+ if (!result.proceed) return result;
2094
+ if (result.updatedInput) {
2095
+ context.args = result.updatedInput;
2096
+ }
2097
+ }
2098
+ }
2099
+ }
2100
+ return { proceed: true };
2101
+ }
2102
+ async runCommandHook(command, context, timeout) {
2103
+ return new Promise((resolve10) => {
2104
+ const stdinData = JSON.stringify({
2105
+ tool: context["tool"],
2106
+ args: context["args"],
2107
+ session_id: context["sessionId"],
2108
+ cwd: context["cwd"] || process.cwd()
2109
+ });
2110
+ const proc = exec(command, {
2111
+ timeout: timeout || 5e3,
2112
+ cwd: process.cwd(),
2113
+ env: { ...process.env }
2114
+ }, (err, stdout, stderr) => {
2115
+ if (err) {
2116
+ if (err.code === 2) {
2117
+ resolve10({
2118
+ proceed: false,
2119
+ reason: stderr || stdout || "Blocked by hook"
2120
+ });
2121
+ return;
2122
+ }
2123
+ resolve10({ proceed: true });
2124
+ return;
2125
+ }
2126
+ try {
2127
+ const output3 = JSON.parse(stdout);
2128
+ resolve10({
2129
+ proceed: output3.decision !== "block",
2130
+ reason: output3.reason,
2131
+ updatedInput: output3.updatedInput
2132
+ });
2133
+ } catch {
2134
+ resolve10({ proceed: true });
2135
+ }
2136
+ });
2137
+ if (proc.stdin) {
2138
+ proc.stdin.write(stdinData);
2139
+ proc.stdin.end();
2140
+ }
2141
+ });
2142
+ }
2143
+ };
2144
+ var hookManager = new HookManager();
2145
+
2146
+ // src/mcp/mcp-manager.ts
2147
+ init_esm_shims();
2148
+ init_tool();
2149
+ import * as fs7 from "fs";
2150
+ import * as path8 from "path";
2151
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2152
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
2153
+ import chalk9 from "chalk";
2154
+ var McpManager = class {
2155
+ servers = /* @__PURE__ */ new Map();
2156
+ async initialize(registry) {
2157
+ const config = configManager.get();
2158
+ const mcpConfigs = this.loadMcpConfigs();
2159
+ const allServers = { ...mcpConfigs, ...config.mcpServers };
2160
+ for (const [name, serverConfig] of Object.entries(allServers)) {
2161
+ try {
2162
+ await this.connectServer(name, serverConfig, registry);
2163
+ } catch (err) {
2164
+ console.error(chalk9.yellow(` \u26A0 Failed to connect MCP server '${name}': ${err instanceof Error ? err.message : String(err)}`));
2165
+ }
2166
+ }
2167
+ }
2168
+ loadMcpConfigs() {
2169
+ const configs = {};
2170
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2171
+ const paths = [
2172
+ path8.join(home, ".codi", "mcp.json"),
2173
+ path8.join(process.cwd(), ".codi", "mcp.json")
2174
+ ];
2175
+ for (const p of paths) {
2176
+ try {
2177
+ if (fs7.existsSync(p)) {
2178
+ const content = JSON.parse(fs7.readFileSync(p, "utf-8"));
2179
+ if (content.mcpServers) {
2180
+ Object.assign(configs, content.mcpServers);
2181
+ }
2182
+ }
2183
+ } catch {
2184
+ }
2185
+ }
2186
+ return configs;
2187
+ }
2188
+ async connectServer(name, config, registry) {
2189
+ const transport = new StdioClientTransport({
2190
+ command: config.command,
2191
+ args: config.args,
2192
+ env: { ...process.env, ...config.env || {} }
2193
+ });
2194
+ const client = new Client({
2195
+ name: "codi",
2196
+ version: "0.1.0"
2197
+ });
2198
+ await client.connect(transport);
2199
+ const toolsResult = await client.listTools();
2200
+ const toolNames = [];
2201
+ for (const mcpTool of toolsResult.tools) {
2202
+ const toolName = `mcp__${name}__${mcpTool.name}`;
2203
+ toolNames.push(toolName);
2204
+ const tool = {
2205
+ name: toolName,
2206
+ description: `[MCP:${name}] ${mcpTool.description || mcpTool.name}`,
2207
+ inputSchema: mcpTool.inputSchema || { type: "object", properties: {} },
2208
+ dangerous: true,
2209
+ readOnly: false,
2210
+ async execute(input3) {
2211
+ try {
2212
+ const result = await client.callTool({
2213
+ name: mcpTool.name,
2214
+ arguments: input3
2215
+ });
2216
+ const contentArr = Array.isArray(result.content) ? result.content : [];
2217
+ const text = contentArr.map((c) => {
2218
+ if (c.type === "text") return c.text;
2219
+ return JSON.stringify(c);
2220
+ }).join("\n") || "";
2221
+ return makeToolResult(text);
2222
+ } catch (err) {
2223
+ return makeToolError(`MCP tool error: ${err instanceof Error ? err.message : String(err)}`);
2224
+ }
2225
+ }
2226
+ };
2227
+ registry.register(tool);
2228
+ }
2229
+ this.servers.set(name, { name, client, transport, tools: toolNames });
2230
+ console.log(chalk9.dim(` \u2713 MCP server '${name}' connected (${toolNames.length} tools)`));
2231
+ }
2232
+ async disconnect(name) {
2233
+ const server = this.servers.get(name);
2234
+ if (!server) return;
2235
+ try {
2236
+ await server.transport.close();
2237
+ } catch {
2238
+ }
2239
+ this.servers.delete(name);
2240
+ }
2241
+ async disconnectAll() {
2242
+ for (const [name] of this.servers) {
2243
+ await this.disconnect(name);
2244
+ }
2245
+ }
2246
+ listServers() {
2247
+ return [...this.servers.values()].map((s) => ({
2248
+ name: s.name,
2249
+ tools: s.tools
2250
+ }));
2251
+ }
2252
+ getServerCount() {
2253
+ return this.servers.size;
2254
+ }
2255
+ };
2256
+ var mcpManager = new McpManager();
2257
+
2258
+ // src/agent/sub-agent.ts
2259
+ init_esm_shims();
2260
+ init_tool();
2261
+ import chalk10 from "chalk";
2262
+ var AGENT_PRESETS = {
2263
+ explore: {
2264
+ tools: ["read_file", "glob", "grep", "list_dir"],
2265
+ systemPrompt: EXPLORE_SYSTEM_PROMPT,
2266
+ maxIterations: 15
2267
+ },
2268
+ plan: {
2269
+ tools: ["read_file", "glob", "grep", "list_dir", "web_fetch"],
2270
+ systemPrompt: PLAN_SYSTEM_PROMPT,
2271
+ maxIterations: 20
2272
+ },
2273
+ general: {
2274
+ tools: [],
2275
+ // Empty means all tools except sub_agent
2276
+ systemPrompt: GENERAL_SYSTEM_PROMPT,
2277
+ maxIterations: 25
2278
+ }
2279
+ };
2280
+ var backgroundAgents = /* @__PURE__ */ new Map();
2281
+ var bgCounter = 0;
2282
+ function createSubAgentHandler(provider, mainRegistry) {
2283
+ return async (input3) => {
2284
+ const type = String(input3["type"]);
2285
+ const task = String(input3["task"]);
2286
+ const background = input3["background"] === true;
2287
+ const preset = AGENT_PRESETS[type];
2288
+ if (!preset) {
2289
+ return makeToolError(`Unknown agent type: ${type}. Use 'explore', 'plan', or 'general'.`);
2290
+ }
2291
+ let registry;
2292
+ if (preset.tools.length > 0) {
2293
+ registry = mainRegistry.subset(preset.tools);
2294
+ } else {
2295
+ registry = mainRegistry.clone();
2296
+ registry.remove("sub_agent");
2297
+ }
2298
+ const conversation = new Conversation();
2299
+ const maxIterations = input3["maxIterations"] || preset.maxIterations;
2300
+ console.log(chalk10.dim(`
2301
+ \u25B8 Launching ${type} agent: ${task.slice(0, 80)}...`));
2302
+ const runAgent = async () => {
2303
+ const result = await agentLoop(task, {
2304
+ provider,
2305
+ conversation,
2306
+ registry,
2307
+ systemPrompt: preset.systemPrompt,
2308
+ maxIterations,
2309
+ stream: false,
2310
+ showOutput: false
2311
+ });
2312
+ return result;
2313
+ };
2314
+ if (background) {
2315
+ const taskId = `agent_${++bgCounter}`;
2316
+ const entry = { promise: runAgent(), status: "running", result: void 0 };
2317
+ backgroundAgents.set(taskId, entry);
2318
+ entry.promise.then((result) => {
2319
+ entry.status = "done";
2320
+ entry.result = result;
2321
+ }).catch((err) => {
2322
+ entry.status = "error";
2323
+ entry.result = String(err);
2324
+ });
2325
+ return makeToolResult(`Background agent started with ID: ${taskId}. Use task output to check results.`);
2326
+ }
2327
+ try {
2328
+ const result = await runAgent();
2329
+ console.log(chalk10.dim(` \u25B8 ${type} agent completed.`));
2330
+ return makeToolResult(result);
2331
+ } catch (err) {
2332
+ return makeToolError(`Sub-agent failed: ${err instanceof Error ? err.message : String(err)}`);
2333
+ }
2334
+ };
2335
+ }
2336
+
2337
+ // src/tools/sub-agent-tool.ts
2338
+ init_esm_shims();
2339
+ init_tool();
2340
+ var subAgentHandler = null;
2341
+ function setSubAgentHandler(handler) {
2342
+ subAgentHandler = handler;
2343
+ }
2344
+ var subAgentTool = {
2345
+ name: "sub_agent",
2346
+ description: `Launch a sub-agent to handle complex tasks autonomously. Types: 'explore' (read-only codebase exploration), 'plan' (architecture planning), 'general' (full capabilities). Sub-agents run in independent contexts.`,
2347
+ inputSchema: {
2348
+ type: "object",
2349
+ properties: {
2350
+ type: {
2351
+ type: "string",
2352
+ enum: ["explore", "plan", "general"],
2353
+ description: "Agent type: explore (fast, read-only), plan (analysis), general (full access)"
2354
+ },
2355
+ task: { type: "string", description: "Detailed task description for the agent" },
2356
+ model: { type: "string", description: "Optional model override" },
2357
+ background: { type: "boolean", description: "Run in background and return task ID" }
2358
+ },
2359
+ required: ["type", "task"]
2360
+ },
2361
+ dangerous: false,
2362
+ readOnly: true,
2363
+ async execute(input3) {
2364
+ if (!subAgentHandler) {
2365
+ return makeToolError("Sub-agent system not initialized. This feature will be available after setup.");
2366
+ }
2367
+ return subAgentHandler(input3);
2368
+ }
2369
+ };
2370
+
2371
+ // src/config/slash-commands.ts
2372
+ init_esm_shims();
2373
+ import * as fs8 from "fs";
2374
+ import * as path9 from "path";
2375
+ import chalk11 from "chalk";
2376
+ function createBuiltinCommands() {
2377
+ return [
2378
+ {
2379
+ name: "/help",
2380
+ description: "Show available commands",
2381
+ handler: async () => {
2382
+ const commands = createBuiltinCommands();
2383
+ console.log(chalk11.bold("\nAvailable Commands:\n"));
2384
+ for (const cmd of commands) {
2385
+ const aliases = cmd.aliases ? chalk11.dim(` (${cmd.aliases.join(", ")})`) : "";
2386
+ console.log(` ${chalk11.cyan(cmd.name)}${aliases} - ${cmd.description}`);
2387
+ }
2388
+ console.log("");
2389
+ console.log(chalk11.dim(" Prefixes: ! (bash), @ (file reference)"));
2390
+ console.log(chalk11.dim(" Use \\ at end of line for multiline input"));
2391
+ console.log("");
2392
+ return true;
2393
+ }
2394
+ },
2395
+ {
2396
+ name: "/quit",
2397
+ aliases: ["/exit"],
2398
+ description: "Exit Codi",
2399
+ handler: async (_args, ctx) => {
2400
+ if (ctx.exitFn) {
2401
+ await ctx.exitFn();
2402
+ }
2403
+ console.log(chalk11.dim("\nGoodbye!\n"));
2404
+ process.exit(0);
2405
+ }
2406
+ },
2407
+ {
2408
+ name: "/clear",
2409
+ aliases: ["/reset", "/new"],
2410
+ description: "Clear conversation history",
2411
+ handler: async (_args, ctx) => {
2412
+ ctx.conversation.clear();
2413
+ ctx.reloadSystemPrompt();
2414
+ console.log(chalk11.green("\u2713 Conversation cleared"));
2415
+ return true;
2416
+ }
2417
+ },
2418
+ {
2419
+ name: "/model",
2420
+ description: "Switch model or provider (e.g., /model gpt-4o, /model ollama:llama3.1)",
2421
+ handler: async (args, ctx) => {
2422
+ if (!args) {
2423
+ const info = statusLine.getInfo();
2424
+ console.log(chalk11.cyan(`Current model: ${info.model} (${info.provider})`));
2425
+ return true;
2426
+ }
2427
+ if (args.includes(":")) {
2428
+ const [provider, model] = args.split(":");
2429
+ ctx.setProvider(provider, model);
2430
+ } else {
2431
+ ctx.setProvider("", args);
2432
+ }
2433
+ console.log(chalk11.green(`\u2713 Model switched to: ${args}`));
2434
+ return true;
2435
+ }
2436
+ },
2437
+ {
2438
+ name: "/compact",
2439
+ description: "Compress conversation history (optional: focus hint)",
2440
+ handler: async (args, ctx) => {
2441
+ console.log(chalk11.dim("Compressing conversation..."));
2442
+ await ctx.compressor.compress(ctx.conversation, ctx.provider, args || void 0);
2443
+ ctx.reloadSystemPrompt();
2444
+ console.log(chalk11.green("\u2713 Conversation compressed"));
2445
+ return true;
2446
+ }
2447
+ },
2448
+ {
2449
+ name: "/cost",
2450
+ description: "Show token usage and cost",
2451
+ handler: async () => {
2452
+ console.log(chalk11.cyan(`
2453
+ ${tokenTracker.format()}
2454
+ `));
2455
+ return true;
2456
+ }
2457
+ },
2458
+ {
2459
+ name: "/config",
2460
+ description: "Show current configuration",
2461
+ handler: async () => {
2462
+ const config = configManager.get();
2463
+ console.log(chalk11.bold("\nConfiguration:\n"));
2464
+ console.log(chalk11.dim(JSON.stringify(config, null, 2)));
2465
+ console.log(chalk11.dim(`
2466
+ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
2467
+ console.log("");
2468
+ return true;
2469
+ }
2470
+ },
2471
+ {
2472
+ name: "/permissions",
2473
+ description: "Show permission rules",
2474
+ handler: async () => {
2475
+ const config = configManager.get();
2476
+ console.log(chalk11.bold("\nPermission Rules:\n"));
2477
+ console.log(chalk11.green(" Allow: ") + config.permissions.allow.join(", "));
2478
+ console.log(chalk11.red(" Deny: ") + (config.permissions.deny.join(", ") || "(none)"));
2479
+ console.log(chalk11.yellow(" Ask: ") + config.permissions.ask.join(", "));
2480
+ console.log("");
2481
+ return true;
2482
+ }
2483
+ },
2484
+ {
2485
+ name: "/save",
2486
+ description: "Save current session",
2487
+ handler: async (args, ctx) => {
2488
+ const name = args || void 0;
2489
+ const id = sessionManager.save(ctx.conversation, name, statusLine.getInfo().model);
2490
+ console.log(chalk11.green(`\u2713 Session saved: ${id}`));
2491
+ return true;
2492
+ }
2493
+ },
2494
+ {
2495
+ name: "/resume",
2496
+ aliases: ["/continue"],
2497
+ description: "Resume a saved session",
2498
+ handler: async (args, ctx) => {
2499
+ const id = args || sessionManager.getLatest()?.id;
2500
+ if (!id) {
2501
+ console.log(chalk11.yellow("No sessions found. Use /save to save a session first."));
2502
+ return true;
2503
+ }
2504
+ const session = sessionManager.load(id);
2505
+ if (!session) {
2506
+ console.log(chalk11.red(`Session not found: ${id}`));
2507
+ return true;
2508
+ }
2509
+ const data = session.conversation.serialize();
2510
+ ctx.conversation.clear();
2511
+ ctx.conversation.setSystemPrompt(data.systemPrompt);
2512
+ for (const msg of data.messages) {
2513
+ if (msg.role === "user") ctx.conversation.addUserMessage(msg.content);
2514
+ else if (msg.role === "assistant") ctx.conversation.addAssistantMessage(msg.content);
2515
+ }
2516
+ console.log(chalk11.green(`\u2713 Resumed session: ${id} (${session.meta.messageCount} messages)`));
2517
+ return true;
2518
+ }
2519
+ },
2520
+ {
2521
+ name: "/fork",
2522
+ description: "Fork current conversation into a new session",
2523
+ handler: async (args, ctx) => {
2524
+ const name = args || `fork-${Date.now()}`;
2525
+ const id = sessionManager.save(ctx.conversation, name, statusLine.getInfo().model);
2526
+ console.log(chalk11.green(`\u2713 Conversation forked: ${id}`));
2527
+ return true;
2528
+ }
2529
+ },
2530
+ {
2531
+ name: "/plan",
2532
+ description: "Toggle plan mode (read-only analysis)",
2533
+ handler: async () => {
2534
+ const current = getMode();
2535
+ const newMode = current === "plan" ? "execute" : "plan";
2536
+ setMode(newMode);
2537
+ statusLine.update({ mode: newMode });
2538
+ console.log(chalk11.cyan(`Mode: ${newMode === "plan" ? "PLAN (read-only)" : "EXECUTE"}`));
2539
+ return true;
2540
+ }
2541
+ },
2542
+ {
2543
+ name: "/memory",
2544
+ description: "Show or edit auto memory",
2545
+ handler: async () => {
2546
+ const index = memoryManager.loadIndex();
2547
+ const topics = memoryManager.listTopics();
2548
+ console.log(chalk11.bold("\nAuto Memory:\n"));
2549
+ console.log(chalk11.dim(`Directory: ${memoryManager.getMemoryDir()}`));
2550
+ if (index) {
2551
+ console.log(chalk11.dim("\nMEMORY.md:"));
2552
+ console.log(index);
2553
+ } else {
2554
+ console.log(chalk11.dim("\nNo memory saved yet."));
2555
+ }
2556
+ if (topics.length > 0) {
2557
+ console.log(chalk11.dim(`
2558
+ Topics: ${topics.join(", ")}`));
2559
+ }
2560
+ console.log("");
2561
+ return true;
2562
+ }
2563
+ },
2564
+ {
2565
+ name: "/init",
2566
+ description: "Initialize CODI.md in the current project",
2567
+ handler: async () => {
2568
+ const codiPath = path9.join(process.cwd(), "CODI.md");
2569
+ if (fs8.existsSync(codiPath)) {
2570
+ console.log(chalk11.yellow("CODI.md already exists"));
2571
+ return true;
2572
+ }
2573
+ const content = `# Project: ${path9.basename(process.cwd())}
2574
+
2575
+ ## Overview
2576
+ <!-- Describe your project here -->
2577
+
2578
+ ## Architecture
2579
+ <!-- Key architectural decisions -->
2580
+
2581
+ ## Development
2582
+ <!-- Development guidelines, commands, etc. -->
2583
+
2584
+ ## Conventions
2585
+ <!-- Code style, naming conventions, etc. -->
2586
+ `;
2587
+ fs8.writeFileSync(codiPath, content, "utf-8");
2588
+ console.log(chalk11.green("\u2713 Created CODI.md"));
2589
+ return true;
2590
+ }
2591
+ },
2592
+ {
2593
+ name: "/export",
2594
+ description: "Export conversation to a file",
2595
+ handler: async (args, ctx) => {
2596
+ const filePath = args || `conversation-${Date.now()}.md`;
2597
+ const messages = ctx.conversation.getMessages();
2598
+ let md = `# Codi Conversation Export
2599
+
2600
+ Date: ${(/* @__PURE__ */ new Date()).toISOString()}
2601
+
2602
+ ---
2603
+
2604
+ `;
2605
+ for (const msg of messages) {
2606
+ const role = msg.role === "user" ? "User" : msg.role === "assistant" ? "Codi" : "System";
2607
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content, null, 2);
2608
+ md += `## ${role}
2609
+
2610
+ ${content}
2611
+
2612
+ ---
2613
+
2614
+ `;
2615
+ }
2616
+ fs8.writeFileSync(filePath, md, "utf-8");
2617
+ console.log(chalk11.green(`\u2713 Exported to ${filePath}`));
2618
+ return true;
2619
+ }
2620
+ },
2621
+ {
2622
+ name: "/tasks",
2623
+ description: "Show task list",
2624
+ handler: async () => {
2625
+ const { taskManager: taskManager2 } = await Promise.resolve().then(() => (init_task_tools(), task_tools_exports));
2626
+ const tasks = taskManager2.list();
2627
+ if (tasks.length === 0) {
2628
+ console.log(chalk11.dim("\nNo tasks.\n"));
2629
+ return true;
2630
+ }
2631
+ console.log(chalk11.bold("\nTasks:\n"));
2632
+ for (const task of tasks) {
2633
+ const statusIcon = task.status === "completed" ? chalk11.green("\u2713") : task.status === "in_progress" ? chalk11.yellow("\u27F3") : chalk11.dim("\u25CB");
2634
+ console.log(` ${statusIcon} #${task.id} ${task.subject} [${task.status}]`);
2635
+ }
2636
+ console.log("");
2637
+ return true;
2638
+ }
2639
+ },
2640
+ {
2641
+ name: "/status",
2642
+ description: "Show system status",
2643
+ handler: async () => {
2644
+ const config = configManager.get();
2645
+ const info = statusLine.getInfo();
2646
+ const stats = tokenTracker.getStats();
2647
+ const mcpServers = mcpManager.listServers();
2648
+ console.log(chalk11.bold("\nCodi Status:\n"));
2649
+ console.log(` Version: 0.1.0`);
2650
+ console.log(` Model: ${info.model}`);
2651
+ console.log(` Provider: ${config.provider}`);
2652
+ console.log(` Mode: ${getMode()}`);
2653
+ console.log(` Tokens: ${tokenTracker.format()}`);
2654
+ console.log(` MCP: ${mcpServers.length} server(s)`);
2655
+ for (const s of mcpServers) {
2656
+ console.log(` - ${s.name} (${s.tools.length} tools)`);
2657
+ }
2658
+ console.log("");
2659
+ return true;
2660
+ }
2661
+ },
2662
+ {
2663
+ name: "/context",
2664
+ description: "Show context window usage",
2665
+ handler: async (_args, ctx) => {
2666
+ const estimated = ctx.conversation.estimateTokens();
2667
+ const max = 2e5;
2668
+ const pct = Math.round(estimated / max * 100);
2669
+ const bar = "\u2588".repeat(Math.round(pct / 5)) + "\u2591".repeat(20 - Math.round(pct / 5));
2670
+ console.log(chalk11.bold("\nContext Window:\n"));
2671
+ console.log(` ${bar} ${pct}%`);
2672
+ console.log(` ~${estimated.toLocaleString()} / ${max.toLocaleString()} tokens`);
2673
+ console.log(` Messages: ${ctx.conversation.getMessageCount()}`);
2674
+ console.log("");
2675
+ return true;
2676
+ }
2677
+ },
2678
+ {
2679
+ name: "/rewind",
2680
+ description: "Rewind to a previous checkpoint",
2681
+ handler: async (_args, ctx) => {
2682
+ const checkpoints = checkpointManager.list();
2683
+ if (checkpoints.length === 0) {
2684
+ console.log(chalk11.yellow("No checkpoints available."));
2685
+ return true;
2686
+ }
2687
+ const result = checkpointManager.rewind();
2688
+ if (!result) {
2689
+ console.log(chalk11.yellow("No checkpoint to rewind to."));
2690
+ return true;
2691
+ }
2692
+ const data = result.conversation.serialize();
2693
+ ctx.conversation.clear();
2694
+ ctx.conversation.setSystemPrompt(data.systemPrompt);
2695
+ for (const msg of data.messages) {
2696
+ if (msg.role === "user") ctx.conversation.addUserMessage(msg.content);
2697
+ else if (msg.role === "assistant") ctx.conversation.addAssistantMessage(msg.content);
2698
+ }
2699
+ console.log(chalk11.green(`\u2713 Rewound to checkpoint${result.description ? `: ${result.description}` : ""}`));
2700
+ return true;
2701
+ }
2702
+ },
2703
+ {
2704
+ name: "/diff",
2705
+ description: "Show git diff of current changes",
2706
+ handler: async () => {
2707
+ try {
2708
+ const { execSync: execSync5 } = await import("child_process");
2709
+ const diff = execSync5("git diff", { encoding: "utf-8", cwd: process.cwd() });
2710
+ if (!diff.trim()) {
2711
+ console.log(chalk11.dim("\nNo changes.\n"));
2712
+ } else {
2713
+ const { renderDiff: renderDiff2 } = await Promise.resolve().then(() => (init_renderer(), renderer_exports));
2714
+ console.log("\n" + renderDiff2("", "", diff) + "\n");
2715
+ }
2716
+ } catch {
2717
+ console.log(chalk11.yellow("Not a git repository or git not available."));
2718
+ }
2719
+ return true;
2720
+ }
2721
+ },
2722
+ {
2723
+ name: "/mcp",
2724
+ description: "Show MCP server status",
2725
+ handler: async () => {
2726
+ const servers = mcpManager.listServers();
2727
+ if (servers.length === 0) {
2728
+ console.log(chalk11.dim("\nNo MCP servers connected.\n"));
2729
+ console.log(chalk11.dim("Add servers in .codi/mcp.json or ~/.codi/mcp.json"));
2730
+ return true;
2731
+ }
2732
+ console.log(chalk11.bold("\nMCP Servers:\n"));
2733
+ for (const s of servers) {
2734
+ console.log(` ${chalk11.green("\u25CF")} ${s.name}`);
2735
+ for (const t of s.tools) {
2736
+ console.log(chalk11.dim(` - ${t}`));
2737
+ }
2738
+ }
2739
+ console.log("");
2740
+ return true;
2741
+ }
2742
+ }
2743
+ ];
2744
+ }
2745
+ function loadCustomCommands() {
2746
+ const commands = [];
2747
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2748
+ const dirs = [
2749
+ path9.join(home, ".codi", "commands"),
2750
+ path9.join(process.cwd(), ".codi", "commands")
2751
+ ];
2752
+ for (const dir of dirs) {
2753
+ if (!fs8.existsSync(dir)) continue;
2754
+ const files = fs8.readdirSync(dir).filter((f) => f.endsWith(".md"));
2755
+ for (const file of files) {
2756
+ const name = "/" + file.replace(".md", "");
2757
+ const filePath = path9.join(dir, file);
2758
+ commands.push({
2759
+ name,
2760
+ description: `Custom command from ${path9.relative(process.cwd(), filePath)}`,
2761
+ handler: async (_args, ctx) => {
2762
+ let content = fs8.readFileSync(filePath, "utf-8");
2763
+ content = content.replace(/\{\{cwd\}\}/g, process.cwd()).replace(/\{\{date\}\}/g, (/* @__PURE__ */ new Date()).toISOString().split("T")[0]).replace(/\{\{file_path\}\}/g, _args || "");
2764
+ await ctx.conversation.addUserMessage(content);
2765
+ return false;
2766
+ }
2767
+ });
2768
+ }
2769
+ }
2770
+ return commands;
2771
+ }
2772
+
2773
+ // src/tools/file-read.ts
2774
+ init_esm_shims();
2775
+ init_tool();
2776
+ import * as fs9 from "fs";
2777
+ import * as path10 from "path";
2778
+ var fileReadTool = {
2779
+ name: "read_file",
2780
+ description: `Read a file from the filesystem. Supports text files with line numbers (cat -n format), PDF files, images (returns base64 for multimodal), and Jupyter notebooks (.ipynb). Use offset/limit for large files.`,
2781
+ inputSchema: {
2782
+ type: "object",
2783
+ properties: {
2784
+ file_path: { type: "string", description: "Absolute or relative path to the file" },
2785
+ offset: { type: "number", description: "Line number to start reading from (1-based)" },
2786
+ limit: { type: "number", description: "Number of lines to read" },
2787
+ pages: { type: "string", description: 'Page range for PDF files (e.g., "1-5")' }
2788
+ },
2789
+ required: ["file_path"]
2790
+ },
2791
+ dangerous: false,
2792
+ readOnly: true,
2793
+ async execute(input3) {
2794
+ const filePath = String(input3["file_path"]);
2795
+ const offset = input3["offset"];
2796
+ const limit = input3["limit"];
2797
+ const resolved = path10.resolve(filePath);
2798
+ if (!fs9.existsSync(resolved)) {
2799
+ return makeToolError(`File not found: ${resolved}`);
2800
+ }
2801
+ const stat = fs9.statSync(resolved);
2802
+ if (stat.isDirectory()) {
2803
+ return makeToolError(`Path is a directory, not a file: ${resolved}. Use list_dir instead.`);
2804
+ }
2805
+ const ext = path10.extname(resolved).toLowerCase();
2806
+ if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"].includes(ext)) {
2807
+ const data = fs9.readFileSync(resolved);
2808
+ const base64 = data.toString("base64");
2809
+ const mimeMap = {
2810
+ ".png": "image/png",
2811
+ ".jpg": "image/jpeg",
2812
+ ".jpeg": "image/jpeg",
2813
+ ".gif": "image/gif",
2814
+ ".webp": "image/webp",
2815
+ ".bmp": "image/bmp",
2816
+ ".svg": "image/svg+xml"
2817
+ };
2818
+ return makeToolResult(`[Image: ${path10.basename(resolved)}]`, {
2819
+ filePath: resolved,
2820
+ isImage: true,
2821
+ imageData: base64,
2822
+ imageMimeType: mimeMap[ext] || "image/png"
2823
+ });
2824
+ }
2825
+ if (ext === ".pdf") {
2826
+ try {
2827
+ const pdfModule = await import("pdf-parse");
2828
+ const pdfParse = pdfModule.default || pdfModule;
2829
+ const buffer = fs9.readFileSync(resolved);
2830
+ const data = await pdfParse(buffer);
2831
+ const pages = input3["pages"];
2832
+ let text = data.text;
2833
+ if (pages) {
2834
+ const allPages = text.split("\f");
2835
+ const [start, end] = pages.split("-").map(Number);
2836
+ const s = (start || 1) - 1;
2837
+ const e = end || start || allPages.length;
2838
+ text = allPages.slice(s, e).join("\n\n--- Page Break ---\n\n");
2839
+ }
2840
+ return makeToolResult(text, { filePath: resolved });
2841
+ } catch (err) {
2842
+ return makeToolError(`Failed to parse PDF: ${err instanceof Error ? err.message : String(err)}`);
2843
+ }
2844
+ }
2845
+ if (ext === ".ipynb") {
2846
+ try {
2847
+ const content = fs9.readFileSync(resolved, "utf-8");
2848
+ const nb = JSON.parse(content);
2849
+ const output3 = [];
2850
+ for (let i = 0; i < (nb.cells || []).length; i++) {
2851
+ const cell = nb.cells[i];
2852
+ const cellType = cell.cell_type || "code";
2853
+ const source = Array.isArray(cell.source) ? cell.source.join("") : cell.source || "";
2854
+ output3.push(`--- Cell ${i + 1} [${cellType}] ---`);
2855
+ output3.push(source);
2856
+ if (cell.outputs && cell.outputs.length > 0) {
2857
+ output3.push("--- Output ---");
2858
+ for (const out of cell.outputs) {
2859
+ if (out.text) {
2860
+ output3.push(Array.isArray(out.text) ? out.text.join("") : out.text);
2861
+ } else if (out.data?.["text/plain"]) {
2862
+ const plain = out.data["text/plain"];
2863
+ output3.push(Array.isArray(plain) ? plain.join("") : plain);
2864
+ }
2865
+ }
2866
+ }
2867
+ output3.push("");
2868
+ }
2869
+ return makeToolResult(output3.join("\n"), { filePath: resolved });
2870
+ } catch (err) {
2871
+ return makeToolError(`Failed to parse notebook: ${err instanceof Error ? err.message : String(err)}`);
2872
+ }
2873
+ }
2874
+ try {
2875
+ const content = fs9.readFileSync(resolved, "utf-8");
2876
+ const lines = content.split("\n");
2877
+ const totalLines = lines.length;
2878
+ const startLine = Math.max(1, offset || 1);
2879
+ const endLine = limit ? Math.min(startLine + limit - 1, totalLines) : Math.min(startLine + 1999, totalLines);
2880
+ const selectedLines = lines.slice(startLine - 1, endLine);
2881
+ const numbered = selectedLines.map((line, i) => {
2882
+ const lineNum = startLine + i;
2883
+ const numStr = String(lineNum).padStart(String(endLine).length, " ");
2884
+ return `${numStr} ${line}`;
2885
+ });
2886
+ let result = numbered.join("\n");
2887
+ if (endLine < totalLines) {
2888
+ result += `
2889
+
2890
+ ... (${totalLines - endLine} more lines)`;
2891
+ }
2892
+ return makeToolResult(result, { filePath: resolved });
2893
+ } catch (err) {
2894
+ return makeToolError(`Failed to read file: ${err instanceof Error ? err.message : String(err)}`);
2895
+ }
2896
+ }
2897
+ };
2898
+
2899
+ // src/tools/file-write.ts
2900
+ init_esm_shims();
2901
+ init_tool();
2902
+ import * as fs10 from "fs";
2903
+ import * as path11 from "path";
2904
+ var fileWriteTool = {
2905
+ name: "write_file",
2906
+ description: `Create a new file or overwrite an existing file. Creates parent directories if needed. For modifying existing files, prefer edit_file instead.`,
2907
+ inputSchema: {
2908
+ type: "object",
2909
+ properties: {
2910
+ file_path: { type: "string", description: "Absolute or relative path to the file" },
2911
+ content: { type: "string", description: "The content to write to the file" }
2912
+ },
2913
+ required: ["file_path", "content"]
2914
+ },
2915
+ dangerous: true,
2916
+ readOnly: false,
2917
+ async execute(input3) {
2918
+ const filePath = String(input3["file_path"]);
2919
+ const content = String(input3["content"]);
2920
+ const resolved = path11.resolve(filePath);
2921
+ try {
2922
+ const dir = path11.dirname(resolved);
2923
+ if (!fs10.existsSync(dir)) {
2924
+ fs10.mkdirSync(dir, { recursive: true });
2925
+ }
2926
+ const existed = fs10.existsSync(resolved);
2927
+ fs10.writeFileSync(resolved, content, "utf-8");
2928
+ const lines = content.split("\n").length;
2929
+ const action = existed ? "Overwrote" : "Created";
2930
+ return makeToolResult(`${action} ${resolved} (${lines} lines)`, {
2931
+ filePath: resolved,
2932
+ linesChanged: lines
2933
+ });
2934
+ } catch (err) {
2935
+ return makeToolError(`Failed to write file: ${err instanceof Error ? err.message : String(err)}`);
2936
+ }
2937
+ }
2938
+ };
2939
+
2940
+ // src/tools/file-edit.ts
2941
+ init_esm_shims();
2942
+ init_tool();
2943
+ import * as fs11 from "fs";
2944
+ import * as path12 from "path";
2945
+ var fileEditTool = {
2946
+ name: "edit_file",
2947
+ description: `Perform exact string replacement in a file. The old_string must be unique in the file unless replace_all is true. Preserves indentation exactly.`,
2948
+ inputSchema: {
2949
+ type: "object",
2950
+ properties: {
2951
+ file_path: { type: "string", description: "Path to the file to edit" },
2952
+ old_string: { type: "string", description: "The exact text to find and replace" },
2953
+ new_string: { type: "string", description: "The replacement text" },
2954
+ replace_all: { type: "boolean", description: "Replace all occurrences (default: false)", default: false }
2955
+ },
2956
+ required: ["file_path", "old_string", "new_string"]
2957
+ },
2958
+ dangerous: true,
2959
+ readOnly: false,
2960
+ async execute(input3) {
2961
+ const filePath = String(input3["file_path"]);
2962
+ const oldString = String(input3["old_string"]);
2963
+ const newString = String(input3["new_string"]);
2964
+ const replaceAll = input3["replace_all"] === true;
2965
+ const resolved = path12.resolve(filePath);
2966
+ if (!fs11.existsSync(resolved)) {
2967
+ return makeToolError(`File not found: ${resolved}`);
2968
+ }
2969
+ try {
2970
+ let content = fs11.readFileSync(resolved, "utf-8");
2971
+ if (oldString === newString) {
2972
+ return makeToolError("old_string and new_string are identical. No changes needed.");
2973
+ }
2974
+ if (!content.includes(oldString)) {
2975
+ const lines = content.split("\n");
2976
+ const oldLines = oldString.split("\n");
2977
+ const firstOldLine = oldLines[0]?.trim();
2978
+ const similar = firstOldLine ? lines.find((l) => l.trim().includes(firstOldLine)) : void 0;
2979
+ let errMsg = `old_string not found in ${resolved}.`;
2980
+ if (similar) {
2981
+ errMsg += `
2982
+ Did you mean this line?
2983
+ ${similar.trim()}`;
2984
+ }
2985
+ errMsg += "\nMake sure old_string matches exactly including whitespace and indentation.";
2986
+ return makeToolError(errMsg);
2987
+ }
2988
+ if (!replaceAll) {
2989
+ const count = content.split(oldString).length - 1;
2990
+ if (count > 1) {
2991
+ return makeToolError(
2992
+ `old_string appears ${count} times in the file. Use replace_all: true to replace all, or provide more context to make it unique.`
2993
+ );
2994
+ }
2995
+ }
2996
+ if (replaceAll) {
2997
+ content = content.split(oldString).join(newString);
2998
+ } else {
2999
+ const idx = content.indexOf(oldString);
3000
+ content = content.slice(0, idx) + newString + content.slice(idx + oldString.length);
3001
+ }
3002
+ fs11.writeFileSync(resolved, content, "utf-8");
3003
+ const linesChanged = Math.max(
3004
+ oldString.split("\n").length,
3005
+ newString.split("\n").length
3006
+ );
3007
+ return makeToolResult(`Edited ${resolved}`, {
3008
+ filePath: resolved,
3009
+ linesChanged
3010
+ });
3011
+ } catch (err) {
3012
+ return makeToolError(`Failed to edit file: ${err instanceof Error ? err.message : String(err)}`);
3013
+ }
3014
+ }
3015
+ };
3016
+
3017
+ // src/tools/file-multi-edit.ts
3018
+ init_esm_shims();
3019
+ init_tool();
3020
+ import * as fs12 from "fs";
3021
+ import * as path13 from "path";
3022
+ var fileMultiEditTool = {
3023
+ name: "multi_edit",
3024
+ description: `Apply multiple edits to a single file atomically. Each edit is an old_string \u2192 new_string replacement. All edits are validated before any are applied.`,
3025
+ inputSchema: {
3026
+ type: "object",
3027
+ properties: {
3028
+ file_path: { type: "string", description: "Path to the file to edit" },
3029
+ edits: {
3030
+ type: "array",
3031
+ description: "Array of edits to apply",
3032
+ items: {
3033
+ type: "object",
3034
+ properties: {
3035
+ old_string: { type: "string", description: "Text to find" },
3036
+ new_string: { type: "string", description: "Replacement text" }
3037
+ },
3038
+ required: ["old_string", "new_string"]
3039
+ }
3040
+ }
3041
+ },
3042
+ required: ["file_path", "edits"]
3043
+ },
3044
+ dangerous: true,
3045
+ readOnly: false,
3046
+ async execute(input3) {
3047
+ const filePath = String(input3["file_path"]);
3048
+ const edits = input3["edits"];
3049
+ const resolved = path13.resolve(filePath);
3050
+ if (!fs12.existsSync(resolved)) {
3051
+ return makeToolError(`File not found: ${resolved}`);
3052
+ }
3053
+ if (!Array.isArray(edits) || edits.length === 0) {
3054
+ return makeToolError("edits must be a non-empty array of {old_string, new_string} objects");
3055
+ }
3056
+ try {
3057
+ let content = fs12.readFileSync(resolved, "utf-8");
3058
+ for (let i = 0; i < edits.length; i++) {
3059
+ const edit2 = edits[i];
3060
+ if (!content.includes(edit2.old_string)) {
3061
+ return makeToolError(
3062
+ `Edit ${i + 1}/${edits.length}: old_string not found in file. No edits applied.
3063
+ Searching for: ${edit2.old_string.slice(0, 100)}...`
3064
+ );
3065
+ }
3066
+ }
3067
+ let totalLinesChanged = 0;
3068
+ for (const edit2 of edits) {
3069
+ const idx = content.indexOf(edit2.old_string);
3070
+ if (idx === -1) {
3071
+ return makeToolError(`Edit validation passed but old_string disappeared during application. This may happen if edits overlap.`);
3072
+ }
3073
+ content = content.slice(0, idx) + edit2.new_string + content.slice(idx + edit2.old_string.length);
3074
+ totalLinesChanged += Math.max(
3075
+ edit2.old_string.split("\n").length,
3076
+ edit2.new_string.split("\n").length
3077
+ );
3078
+ }
3079
+ fs12.writeFileSync(resolved, content, "utf-8");
3080
+ return makeToolResult(`Applied ${edits.length} edits to ${resolved}`, {
3081
+ filePath: resolved,
3082
+ linesChanged: totalLinesChanged
3083
+ });
3084
+ } catch (err) {
3085
+ return makeToolError(`Failed to multi-edit: ${err instanceof Error ? err.message : String(err)}`);
3086
+ }
3087
+ }
3088
+ };
3089
+
3090
+ // src/tools/glob.ts
3091
+ init_esm_shims();
3092
+ init_tool();
3093
+ import * as fs13 from "fs";
3094
+ import * as path14 from "path";
3095
+ import { globby } from "globby";
3096
+ var globTool = {
3097
+ name: "glob",
3098
+ description: `Fast file pattern matching. Supports glob patterns like "**/*.ts". Respects .gitignore. Returns matching file paths sorted by modification time.`,
3099
+ inputSchema: {
3100
+ type: "object",
3101
+ properties: {
3102
+ pattern: { type: "string", description: 'Glob pattern to match files (e.g., "**/*.ts", "src/**/*.tsx")' },
3103
+ path: { type: "string", description: "Directory to search in. Defaults to current working directory." }
3104
+ },
3105
+ required: ["pattern"]
3106
+ },
3107
+ dangerous: false,
3108
+ readOnly: true,
3109
+ async execute(input3) {
3110
+ const pattern = String(input3["pattern"]);
3111
+ const searchPath = input3["path"] ? String(input3["path"]) : process.cwd();
3112
+ const resolved = path14.resolve(searchPath);
3113
+ try {
3114
+ const files = await globby(pattern, {
3115
+ cwd: resolved,
3116
+ gitignore: true,
3117
+ ignore: ["node_modules/**", ".git/**"],
3118
+ absolute: true,
3119
+ onlyFiles: true
3120
+ });
3121
+ const withStats = files.map((f) => {
3122
+ try {
3123
+ const stat = fs13.statSync(f);
3124
+ return { path: f, mtime: stat.mtimeMs };
3125
+ } catch {
3126
+ return { path: f, mtime: 0 };
3127
+ }
3128
+ });
3129
+ withStats.sort((a, b) => b.mtime - a.mtime);
3130
+ const result = withStats.map((f) => f.path);
3131
+ if (result.length === 0) {
3132
+ return makeToolResult(`No files matched pattern: ${pattern} in ${resolved}`);
3133
+ }
3134
+ return makeToolResult(
3135
+ `Found ${result.length} file(s):
3136
+ ${result.join("\n")}`
3137
+ );
3138
+ } catch (err) {
3139
+ return makeToolError(`Glob failed: ${err instanceof Error ? err.message : String(err)}`);
3140
+ }
3141
+ }
3142
+ };
3143
+
3144
+ // src/tools/grep.ts
3145
+ init_esm_shims();
3146
+ init_tool();
3147
+ import { execFile } from "child_process";
3148
+ import * as path15 from "path";
3149
+ var grepTool = {
3150
+ name: "grep",
3151
+ description: `Search file contents using regex patterns. Uses ripgrep (rg) if available, falls back to grep. Supports context lines, file type filters, and multiple output modes.`,
3152
+ inputSchema: {
3153
+ type: "object",
3154
+ properties: {
3155
+ pattern: { type: "string", description: "Regex pattern to search for" },
3156
+ path: { type: "string", description: "File or directory to search in" },
3157
+ glob: { type: "string", description: 'Glob pattern to filter files (e.g., "*.ts")' },
3158
+ type: { type: "string", description: 'File type filter (e.g., "ts", "py", "js")' },
3159
+ output_mode: {
3160
+ type: "string",
3161
+ enum: ["content", "files_with_matches", "count"],
3162
+ description: "Output mode: content (matching lines), files_with_matches (file paths only), count (match counts)"
3163
+ },
3164
+ "-A": { type: "number", description: "Lines to show after match" },
3165
+ "-B": { type: "number", description: "Lines to show before match" },
3166
+ "-C": { type: "number", description: "Context lines (before and after)" },
3167
+ "-i": { type: "boolean", description: "Case insensitive search" },
3168
+ "-n": { type: "boolean", description: "Show line numbers" },
3169
+ multiline: { type: "boolean", description: "Enable multiline matching" },
3170
+ head_limit: { type: "number", description: "Limit output to first N entries" }
3171
+ },
3172
+ required: ["pattern"]
3173
+ },
3174
+ dangerous: false,
3175
+ readOnly: true,
3176
+ async execute(input3) {
3177
+ const pattern = String(input3["pattern"]);
3178
+ const searchPath = path15.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
3179
+ const outputMode = input3["output_mode"] || "files_with_matches";
3180
+ const headLimit = input3["head_limit"] || 0;
3181
+ const args = [];
3182
+ const useRg = await hasCommand("rg");
3183
+ const cmd = useRg ? "rg" : "grep";
3184
+ if (!useRg) {
3185
+ args.push("-r");
3186
+ }
3187
+ if (outputMode === "files_with_matches") {
3188
+ args.push(useRg ? "-l" : "-l");
3189
+ } else if (outputMode === "count") {
3190
+ args.push(useRg ? "-c" : "-c");
3191
+ }
3192
+ if (input3["-i"]) args.push("-i");
3193
+ if (input3["-n"] !== false && outputMode === "content") {
3194
+ args.push("-n");
3195
+ }
3196
+ if (input3["-C"]) args.push("-C", String(input3["-C"]));
3197
+ else if (input3["-A"]) args.push("-A", String(input3["-A"]));
3198
+ if (input3["-B"]) args.push("-B", String(input3["-B"]));
3199
+ if (input3["multiline"] && useRg) {
3200
+ args.push("-U", "--multiline-dotall");
3201
+ }
3202
+ if (input3["type"] && useRg) {
3203
+ args.push("--type", String(input3["type"]));
3204
+ }
3205
+ if (input3["glob"] && useRg) {
3206
+ args.push("--glob", String(input3["glob"]));
3207
+ }
3208
+ if (useRg) {
3209
+ args.push("--no-ignore-vcs");
3210
+ args.push("-g", "!node_modules");
3211
+ args.push("-g", "!.git");
3212
+ }
3213
+ args.push(pattern, searchPath);
3214
+ try {
3215
+ const result = await runCommand(cmd, args);
3216
+ if (!result.trim()) {
3217
+ return makeToolResult(`No matches found for pattern: ${pattern}`);
3218
+ }
3219
+ let output3 = result;
3220
+ if (headLimit > 0) {
3221
+ const lines = output3.split("\n");
3222
+ output3 = lines.slice(0, headLimit).join("\n");
3223
+ if (lines.length > headLimit) {
3224
+ output3 += `
3225
+ ... (${lines.length - headLimit} more results)`;
3226
+ }
3227
+ }
3228
+ return makeToolResult(output3);
3229
+ } catch (err) {
3230
+ if (err.code === 1) {
3231
+ return makeToolResult(`No matches found for pattern: ${pattern}`);
3232
+ }
3233
+ return makeToolError(`Search failed: ${err.message || String(err)}`);
3234
+ }
3235
+ }
3236
+ };
3237
+ function hasCommand(cmd) {
3238
+ return new Promise((resolve10) => {
3239
+ execFile("which", [cmd], (err) => resolve10(!err));
3240
+ });
3241
+ }
3242
+ function runCommand(cmd, args) {
3243
+ return new Promise((resolve10, reject) => {
3244
+ execFile(cmd, args, { maxBuffer: 10 * 1024 * 1024, timeout: 3e4 }, (err, stdout, stderr) => {
3245
+ if (err) {
3246
+ err.code = err.code;
3247
+ reject(err);
3248
+ return;
3249
+ }
3250
+ resolve10(stdout);
3251
+ });
3252
+ });
3253
+ }
3254
+
3255
+ // src/tools/bash.ts
3256
+ init_esm_shims();
3257
+ init_tool();
3258
+ import { exec as exec2, spawn } from "child_process";
3259
+ var backgroundTasks = /* @__PURE__ */ new Map();
3260
+ var taskCounter = 0;
3261
+ var bashTool = {
3262
+ name: "bash",
3263
+ description: `Execute a bash command. Supports timeout (max 600s, default 120s) and background execution. The working directory persists between calls.`,
3264
+ inputSchema: {
3265
+ type: "object",
3266
+ properties: {
3267
+ command: { type: "string", description: "The bash command to execute" },
3268
+ description: { type: "string", description: "Brief description of what the command does" },
3269
+ timeout: { type: "number", description: "Timeout in milliseconds (max 600000, default 120000)" },
3270
+ run_in_background: { type: "boolean", description: "Run in background and return a task ID" }
3271
+ },
3272
+ required: ["command"]
3273
+ },
3274
+ dangerous: true,
3275
+ readOnly: false,
3276
+ async execute(input3) {
3277
+ const command = String(input3["command"]);
3278
+ const timeout = Math.min(Number(input3["timeout"]) || 12e4, 6e5);
3279
+ const runInBackground = input3["run_in_background"] === true;
3280
+ if (!command.trim()) {
3281
+ return makeToolError("Command cannot be empty");
3282
+ }
3283
+ if (runInBackground) {
3284
+ return runBackgroundTask(command);
3285
+ }
3286
+ return new Promise((resolve10) => {
3287
+ exec2(command, {
3288
+ timeout,
3289
+ maxBuffer: 10 * 1024 * 1024,
3290
+ shell: process.env["SHELL"] || "/bin/bash",
3291
+ cwd: process.cwd(),
3292
+ env: { ...process.env }
3293
+ }, (err, stdout, stderr) => {
3294
+ if (err) {
3295
+ const exitCode = err.code;
3296
+ const output4 = [
3297
+ stdout ? `stdout:
3298
+ ${stdout}` : "",
3299
+ stderr ? `stderr:
3300
+ ${stderr}` : "",
3301
+ `Exit code: ${exitCode}`
3302
+ ].filter(Boolean).join("\n\n");
3303
+ if (err.killed) {
3304
+ resolve10(makeToolError(`Command timed out after ${timeout / 1e3}s
3305
+ ${output4}`));
3306
+ } else {
3307
+ resolve10(makeToolResult(output4 || `Command failed with exit code ${exitCode}`));
3308
+ }
3309
+ return;
3310
+ }
3311
+ const output3 = [
3312
+ stdout ? stdout : "",
3313
+ stderr ? `stderr:
3314
+ ${stderr}` : ""
3315
+ ].filter(Boolean).join("\n");
3316
+ resolve10(makeToolResult(output3 || "(no output)"));
3317
+ });
3318
+ });
3319
+ }
3320
+ };
3321
+ function runBackgroundTask(command) {
3322
+ const taskId = `bg_${++taskCounter}`;
3323
+ const proc = spawn(command, {
3324
+ shell: process.env["SHELL"] || "/bin/bash",
3325
+ cwd: process.cwd(),
3326
+ env: { ...process.env },
3327
+ stdio: ["ignore", "pipe", "pipe"],
3328
+ detached: false
3329
+ });
3330
+ const task = { process: proc, output: "", status: "running", exitCode: void 0 };
3331
+ backgroundTasks.set(taskId, task);
3332
+ proc.stdout?.on("data", (data) => {
3333
+ task.output += data.toString();
3334
+ });
3335
+ proc.stderr?.on("data", (data) => {
3336
+ task.output += data.toString();
3337
+ });
3338
+ proc.on("close", (code) => {
3339
+ task.status = code === 0 ? "done" : "error";
3340
+ task.exitCode = code ?? 1;
3341
+ });
3342
+ proc.on("error", (err) => {
3343
+ task.status = "error";
3344
+ task.output += `
3345
+ Process error: ${err.message}`;
3346
+ });
3347
+ return makeToolResult(`Background task started with ID: ${taskId}
3348
+ Use task_output tool to check results.`);
3349
+ }
3350
+
3351
+ // src/tools/list-dir.ts
3352
+ init_esm_shims();
3353
+ init_tool();
3354
+ import * as fs14 from "fs";
3355
+ import * as path16 from "path";
3356
+ var listDirTool = {
3357
+ name: "list_dir",
3358
+ description: `List directory contents with file/folder distinction and basic metadata.`,
3359
+ inputSchema: {
3360
+ type: "object",
3361
+ properties: {
3362
+ path: { type: "string", description: "Directory path to list (defaults to cwd)" }
3363
+ },
3364
+ required: []
3365
+ },
3366
+ dangerous: false,
3367
+ readOnly: true,
3368
+ async execute(input3) {
3369
+ const dirPath = path16.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
3370
+ if (!fs14.existsSync(dirPath)) {
3371
+ return makeToolError(`Directory not found: ${dirPath}`);
3372
+ }
3373
+ const stat = fs14.statSync(dirPath);
3374
+ if (!stat.isDirectory()) {
3375
+ return makeToolError(`Not a directory: ${dirPath}`);
3376
+ }
3377
+ try {
3378
+ const entries = fs14.readdirSync(dirPath, { withFileTypes: true });
3379
+ const IGNORE = /* @__PURE__ */ new Set([".git", "node_modules", ".DS_Store", "__pycache__", ".next", "dist", "build"]);
3380
+ const lines = [];
3381
+ const dirs = [];
3382
+ const files = [];
3383
+ for (const entry of entries) {
3384
+ if (IGNORE.has(entry.name)) continue;
3385
+ if (entry.isDirectory()) {
3386
+ dirs.push(`${entry.name}/`);
3387
+ } else if (entry.isSymbolicLink()) {
3388
+ try {
3389
+ const target = fs14.readlinkSync(path16.join(dirPath, entry.name));
3390
+ files.push(`${entry.name} -> ${target}`);
3391
+ } catch {
3392
+ files.push(`${entry.name} -> (broken link)`);
3393
+ }
3394
+ } else {
3395
+ files.push(entry.name);
3396
+ }
3397
+ }
3398
+ dirs.sort();
3399
+ files.sort();
3400
+ lines.push(...dirs, ...files);
3401
+ if (lines.length === 0) {
3402
+ return makeToolResult(`Directory is empty: ${dirPath}`);
3403
+ }
3404
+ return makeToolResult(`${dirPath}
3405
+ ${lines.join("\n")}`);
3406
+ } catch (err) {
3407
+ return makeToolError(`Failed to list directory: ${err instanceof Error ? err.message : String(err)}`);
3408
+ }
3409
+ }
3410
+ };
3411
+
3412
+ // src/tools/git.ts
3413
+ init_esm_shims();
3414
+ init_tool();
3415
+ import { execSync as execSync4 } from "child_process";
3416
+ var gitTool = {
3417
+ name: "git",
3418
+ description: `Execute git commands with safety checks. Blocks dangerous operations like force push, hard reset, and amend. Use for status, diff, log, commit, branch operations.`,
3419
+ inputSchema: {
3420
+ type: "object",
3421
+ properties: {
3422
+ command: { type: "string", description: 'Git subcommand and arguments (e.g., "status", "diff HEAD", "log --oneline -10")' }
3423
+ },
3424
+ required: ["command"]
3425
+ },
3426
+ dangerous: true,
3427
+ readOnly: false,
3428
+ async execute(input3) {
3429
+ const command = String(input3["command"]).trim();
3430
+ const blocked = [
3431
+ { pattern: /push\s+.*--force/, msg: "Force push is blocked for safety. Use regular push." },
3432
+ { pattern: /push\s+.*-f\b/, msg: "Force push (-f) is blocked for safety." },
3433
+ { pattern: /reset\s+--hard/, msg: "Hard reset is blocked. Use soft reset or create a new commit." },
3434
+ { pattern: /clean\s+-f/, msg: "git clean -f is blocked. Manually review files to remove." },
3435
+ { pattern: /checkout\s+\.\s*$/, msg: "git checkout . discards all changes. Use more specific paths." },
3436
+ { pattern: /branch\s+-D/, msg: "Force branch deletion is blocked. Use -d for safe deletion." },
3437
+ { pattern: /\b-i\b/, msg: "Interactive mode (-i) is not supported in non-interactive context." },
3438
+ { pattern: /commit\s+.*--amend/, msg: "Amending commits is blocked. Create a new commit instead." },
3439
+ { pattern: /--no-verify/, msg: "Skipping hooks (--no-verify) is blocked." }
3440
+ ];
3441
+ for (const check of blocked) {
3442
+ if (check.pattern.test(command)) {
3443
+ return makeToolError(check.msg);
3444
+ }
3445
+ }
3446
+ const readOnlyPrefixes = ["status", "diff", "log", "show", "branch", "tag", "remote", "stash list", "ls-files"];
3447
+ const isReadOnly = readOnlyPrefixes.some((p) => command.startsWith(p));
3448
+ try {
3449
+ const result = execSync4(`git ${command}`, {
3450
+ encoding: "utf-8",
3451
+ maxBuffer: 10 * 1024 * 1024,
3452
+ timeout: 3e4,
3453
+ cwd: process.cwd()
3454
+ });
3455
+ return makeToolResult(result || "(no output)");
3456
+ } catch (err) {
3457
+ const output3 = [err.stdout, err.stderr].filter(Boolean).join("\n");
3458
+ return makeToolError(`git ${command} failed:
3459
+ ${output3 || err.message}`);
3460
+ }
3461
+ }
3462
+ };
3463
+
3464
+ // src/tools/web-fetch.ts
3465
+ init_esm_shims();
3466
+ init_tool();
3467
+ var cache = /* @__PURE__ */ new Map();
3468
+ var CACHE_TTL = 15 * 60 * 1e3;
3469
+ var webFetchTool = {
3470
+ name: "web_fetch",
3471
+ description: `Fetch content from a URL, convert HTML to text, and return the content. Includes a 15-minute cache. HTTP URLs are upgraded to HTTPS.`,
3472
+ inputSchema: {
3473
+ type: "object",
3474
+ properties: {
3475
+ url: { type: "string", description: "URL to fetch" },
3476
+ prompt: { type: "string", description: "What information to extract from the page" }
3477
+ },
3478
+ required: ["url", "prompt"]
3479
+ },
3480
+ dangerous: true,
3481
+ readOnly: true,
3482
+ async execute(input3) {
3483
+ let url = String(input3["url"]);
3484
+ const prompt = String(input3["prompt"] || "");
3485
+ if (url.startsWith("http://")) {
3486
+ url = url.replace("http://", "https://");
3487
+ }
3488
+ const cached = cache.get(url);
3489
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
3490
+ return makeToolResult(`[Cached] ${prompt ? `Query: ${prompt}
3491
+
3492
+ ` : ""}${cached.content}`);
3493
+ }
3494
+ try {
3495
+ const response = await fetch(url, {
3496
+ headers: {
3497
+ "User-Agent": "Codi/0.1 (AI Code Agent)",
3498
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
3499
+ },
3500
+ redirect: "follow",
3501
+ signal: AbortSignal.timeout(3e4)
3502
+ });
3503
+ if (!response.ok) {
3504
+ return makeToolError(`HTTP ${response.status}: ${response.statusText}`);
3505
+ }
3506
+ const contentType = response.headers.get("content-type") || "";
3507
+ let text;
3508
+ if (contentType.includes("text/html") || contentType.includes("application/xhtml")) {
3509
+ const html = await response.text();
3510
+ const { load } = await import("cheerio");
3511
+ const $ = load(html);
3512
+ $("script, style, nav, header, footer, iframe, noscript").remove();
3513
+ const main2 = $("main, article, .content, #content, .main").first();
3514
+ text = (main2.length ? main2.text() : $("body").text()).replace(/\s+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
3515
+ } else {
3516
+ text = await response.text();
3517
+ }
3518
+ const MAX_LEN = 5e4;
3519
+ if (text.length > MAX_LEN) {
3520
+ text = text.slice(0, MAX_LEN) + "\n\n... (truncated)";
3521
+ }
3522
+ cache.set(url, { content: text, timestamp: Date.now() });
3523
+ return makeToolResult(prompt ? `URL: ${url}
3524
+ Query: ${prompt}
3525
+
3526
+ ${text}` : text);
3527
+ } catch (err) {
3528
+ return makeToolError(`Failed to fetch URL: ${err instanceof Error ? err.message : String(err)}`);
3529
+ }
3530
+ }
3531
+ };
3532
+
3533
+ // src/tools/web-search.ts
3534
+ init_esm_shims();
3535
+ init_tool();
3536
+ var webSearchTool = {
3537
+ name: "web_search",
3538
+ description: `Search the web for information. Returns search results with titles, URLs, and snippets. Uses DuckDuckGo as the search engine.`,
3539
+ inputSchema: {
3540
+ type: "object",
3541
+ properties: {
3542
+ query: { type: "string", description: "Search query" }
3543
+ },
3544
+ required: ["query"]
3545
+ },
3546
+ dangerous: true,
3547
+ readOnly: true,
3548
+ async execute(input3) {
3549
+ const query = String(input3["query"]);
3550
+ try {
3551
+ const encoded = encodeURIComponent(query);
3552
+ const url = `https://html.duckduckgo.com/html/?q=${encoded}`;
3553
+ const response = await fetch(url, {
3554
+ headers: {
3555
+ "User-Agent": "Mozilla/5.0 (compatible; Codi/0.1)"
3556
+ },
3557
+ signal: AbortSignal.timeout(15e3)
3558
+ });
3559
+ if (!response.ok) {
3560
+ return makeToolError(`Search failed: HTTP ${response.status}`);
3561
+ }
3562
+ const html = await response.text();
3563
+ const { load } = await import("cheerio");
3564
+ const $ = load(html);
3565
+ const results = [];
3566
+ $(".result").each((i, el) => {
3567
+ if (i >= 10) return false;
3568
+ const $el = $(el);
3569
+ const title = $el.find(".result__title a").text().trim();
3570
+ const href = $el.find(".result__title a").attr("href") || "";
3571
+ const snippet = $el.find(".result__snippet").text().trim();
3572
+ if (title && href) {
3573
+ let actualUrl = href;
3574
+ try {
3575
+ const urlObj = new URL(href, "https://duckduckgo.com");
3576
+ actualUrl = urlObj.searchParams.get("uddg") || href;
3577
+ } catch {
3578
+ actualUrl = href;
3579
+ }
3580
+ results.push({ title, url: actualUrl, snippet });
3581
+ }
3582
+ });
3583
+ if (results.length === 0) {
3584
+ return makeToolResult(`No results found for: ${query}`);
3585
+ }
3586
+ const formatted = results.map((r, i) => `${i + 1}. ${r.title}
3587
+ ${r.url}
3588
+ ${r.snippet}`).join("\n\n");
3589
+ return makeToolResult(`Search results for: ${query}
3590
+
3591
+ ${formatted}`);
3592
+ } catch (err) {
3593
+ return makeToolError(`Search failed: ${err instanceof Error ? err.message : String(err)}`);
3594
+ }
3595
+ }
3596
+ };
3597
+
3598
+ // src/tools/notebook-edit.ts
3599
+ init_esm_shims();
3600
+ init_tool();
3601
+ import * as fs15 from "fs";
3602
+ import * as path17 from "path";
3603
+ var notebookEditTool = {
3604
+ name: "notebook_edit",
3605
+ description: `Edit Jupyter notebook (.ipynb) cells. Supports replacing, inserting, and deleting cells.`,
3606
+ inputSchema: {
3607
+ type: "object",
3608
+ properties: {
3609
+ notebook_path: { type: "string", description: "Path to the .ipynb file" },
3610
+ cell_number: { type: "number", description: "Cell index (0-based)" },
3611
+ new_source: { type: "string", description: "New source content for the cell" },
3612
+ cell_type: { type: "string", enum: ["code", "markdown"], description: "Cell type (for insert)" },
3613
+ edit_mode: { type: "string", enum: ["replace", "insert", "delete"], description: "Edit mode (default: replace)" }
3614
+ },
3615
+ required: ["notebook_path", "new_source"]
3616
+ },
3617
+ dangerous: true,
3618
+ readOnly: false,
3619
+ async execute(input3) {
3620
+ const nbPath = path17.resolve(String(input3["notebook_path"]));
3621
+ const cellNumber = input3["cell_number"];
3622
+ const newSource = String(input3["new_source"]);
3623
+ const cellType = input3["cell_type"] || "code";
3624
+ const editMode = input3["edit_mode"] || "replace";
3625
+ if (!fs15.existsSync(nbPath)) {
3626
+ return makeToolError(`Notebook not found: ${nbPath}`);
3627
+ }
3628
+ try {
3629
+ const content = fs15.readFileSync(nbPath, "utf-8");
3630
+ const nb = JSON.parse(content);
3631
+ if (!nb.cells || !Array.isArray(nb.cells)) {
3632
+ return makeToolError("Invalid notebook format: no cells array");
3633
+ }
3634
+ const sourceLines = newSource.split("\n").map(
3635
+ (l, i, arr) => i < arr.length - 1 ? l + "\n" : l
3636
+ );
3637
+ switch (editMode) {
3638
+ case "replace": {
3639
+ const idx = cellNumber ?? 0;
3640
+ if (idx < 0 || idx >= nb.cells.length) {
3641
+ return makeToolError(`Cell index ${idx} out of range (0-${nb.cells.length - 1})`);
3642
+ }
3643
+ nb.cells[idx].source = sourceLines;
3644
+ if (input3["cell_type"]) {
3645
+ nb.cells[idx].cell_type = cellType;
3646
+ }
3647
+ break;
3648
+ }
3649
+ case "insert": {
3650
+ const idx = cellNumber !== void 0 ? cellNumber + 1 : nb.cells.length;
3651
+ const newCell = {
3652
+ cell_type: cellType,
3653
+ source: sourceLines,
3654
+ metadata: {}
3655
+ };
3656
+ if (cellType === "code") {
3657
+ newCell["execution_count"] = null;
3658
+ newCell["outputs"] = [];
3659
+ }
3660
+ nb.cells.splice(idx, 0, newCell);
3661
+ break;
3662
+ }
3663
+ case "delete": {
3664
+ const idx = cellNumber ?? 0;
3665
+ if (idx < 0 || idx >= nb.cells.length) {
3666
+ return makeToolError(`Cell index ${idx} out of range (0-${nb.cells.length - 1})`);
3667
+ }
3668
+ nb.cells.splice(idx, 1);
3669
+ break;
3670
+ }
3671
+ default:
3672
+ return makeToolError(`Unknown edit_mode: ${editMode}`);
3673
+ }
3674
+ fs15.writeFileSync(nbPath, JSON.stringify(nb, null, 1), "utf-8");
3675
+ return makeToolResult(`Notebook ${editMode}d cell in ${nbPath} (${nb.cells.length} cells total)`);
3676
+ } catch (err) {
3677
+ return makeToolError(`Notebook edit failed: ${err instanceof Error ? err.message : String(err)}`);
3678
+ }
3679
+ }
3680
+ };
3681
+
3682
+ // src/cli.ts
3683
+ init_task_tools();
3684
+
3685
+ // src/tools/ask-user.ts
3686
+ init_esm_shims();
3687
+ init_tool();
3688
+ import * as readline4 from "readline/promises";
3689
+ import chalk12 from "chalk";
3690
+ var askUserTool = {
3691
+ name: "ask_user",
3692
+ description: `Ask the user a question with optional choices. Use to gather preferences, clarify requirements, or get decisions.`,
3693
+ inputSchema: {
3694
+ type: "object",
3695
+ properties: {
3696
+ question: { type: "string", description: "The question to ask" },
3697
+ options: {
3698
+ type: "array",
3699
+ description: "Optional choices for the user",
3700
+ items: {
3701
+ type: "object",
3702
+ properties: {
3703
+ label: { type: "string", description: "Option label" },
3704
+ description: { type: "string", description: "Option description" }
3705
+ },
3706
+ required: ["label"]
3707
+ }
3708
+ },
3709
+ multiSelect: { type: "boolean", description: "Allow multiple selections" }
3710
+ },
3711
+ required: ["question"]
3712
+ },
3713
+ dangerous: false,
3714
+ readOnly: true,
3715
+ async execute(input3) {
3716
+ const question = String(input3["question"]);
3717
+ const options = input3["options"];
3718
+ const multiSelect = input3["multiSelect"] === true;
3719
+ console.log("");
3720
+ console.log(chalk12.cyan.bold("? ") + chalk12.bold(question));
3721
+ if (options && options.length > 0) {
3722
+ console.log("");
3723
+ for (let i = 0; i < options.length; i++) {
3724
+ const opt = options[i];
3725
+ console.log(chalk12.cyan(` ${i + 1}.`) + ` ${opt.label}${opt.description ? chalk12.dim(` - ${opt.description}`) : ""}`);
3726
+ }
3727
+ console.log(chalk12.dim(` ${options.length + 1}. Other (type custom response)`));
3728
+ console.log("");
3729
+ const rl2 = readline4.createInterface({
3730
+ input: process.stdin,
3731
+ output: process.stdout
3732
+ });
3733
+ try {
3734
+ const prompt = multiSelect ? chalk12.dim("Enter numbers separated by commas: ") : chalk12.dim("Enter number or type response: ");
3735
+ const answer = await rl2.question(prompt);
3736
+ rl2.close();
3737
+ if (multiSelect) {
3738
+ const indices = answer.split(",").map((s) => parseInt(s.trim()) - 1);
3739
+ const selected = indices.filter((i) => i >= 0 && i < options.length).map((i) => options[i].label);
3740
+ if (selected.length === 0) {
3741
+ return makeToolResult(`User response: ${answer}`);
3742
+ }
3743
+ return makeToolResult(`User selected: ${selected.join(", ")}`);
3744
+ }
3745
+ const idx = parseInt(answer) - 1;
3746
+ if (idx >= 0 && idx < options.length) {
3747
+ return makeToolResult(`User selected: ${options[idx].label}`);
3748
+ }
3749
+ return makeToolResult(`User response: ${answer}`);
3750
+ } catch {
3751
+ rl2.close();
3752
+ return makeToolError("Failed to get user input");
3753
+ }
3754
+ }
3755
+ const rl = readline4.createInterface({
3756
+ input: process.stdin,
3757
+ output: process.stdout
3758
+ });
3759
+ try {
3760
+ const answer = await rl.question(chalk12.dim("> "));
3761
+ rl.close();
3762
+ return makeToolResult(`User response: ${answer}`);
3763
+ } catch {
3764
+ rl.close();
3765
+ return makeToolError("Failed to get user input");
3766
+ }
3767
+ }
3768
+ };
3769
+
3770
+ // src/llm/anthropic.ts
3771
+ init_esm_shims();
3772
+ import Anthropic from "@anthropic-ai/sdk";
3773
+ var AnthropicProvider = class {
3774
+ name = "anthropic";
3775
+ model;
3776
+ client;
3777
+ maxTokens;
3778
+ constructor(config) {
3779
+ this.client = new Anthropic({
3780
+ apiKey: config.apiKey || process.env["ANTHROPIC_API_KEY"],
3781
+ ...config.baseUrl ? { baseURL: config.baseUrl } : {}
3782
+ });
3783
+ this.model = config.model || "claude-sonnet-4-20250514";
3784
+ this.maxTokens = config.maxTokens || 8192;
3785
+ }
3786
+ setModel(model) {
3787
+ this.model = model;
3788
+ }
3789
+ async listModels() {
3790
+ return [
3791
+ "claude-opus-4-20250514",
3792
+ "claude-sonnet-4-20250514",
3793
+ "claude-haiku-3-5-20241022"
3794
+ ];
3795
+ }
3796
+ async chat(options) {
3797
+ const messages = this.convertMessages(options.messages);
3798
+ const tools = options.tools?.map((t) => ({
3799
+ name: t.name,
3800
+ description: t.description,
3801
+ input_schema: t.input_schema
3802
+ }));
3803
+ if (options.stream && options.callbacks) {
3804
+ return this.streamChat(messages, options);
3805
+ }
3806
+ const response = await this.client.messages.create({
3807
+ model: this.model,
3808
+ max_tokens: options.maxTokens || this.maxTokens,
3809
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
3810
+ ...options.systemPrompt ? { system: options.systemPrompt } : {},
3811
+ messages,
3812
+ ...tools && tools.length > 0 ? { tools } : {}
3813
+ });
3814
+ return this.parseResponse(response);
3815
+ }
3816
+ async streamChat(messages, options) {
3817
+ const tools = options.tools?.map((t) => ({
3818
+ name: t.name,
3819
+ description: t.description,
3820
+ input_schema: t.input_schema
3821
+ }));
3822
+ const stream = this.client.messages.stream({
3823
+ model: this.model,
3824
+ max_tokens: options.maxTokens || this.maxTokens,
3825
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
3826
+ ...options.systemPrompt ? { system: options.systemPrompt } : {},
3827
+ messages,
3828
+ ...tools && tools.length > 0 ? { tools } : {}
3829
+ });
3830
+ stream.on("text", (text) => {
3831
+ options.callbacks?.onToken?.(text);
3832
+ });
3833
+ const response = await stream.finalMessage();
3834
+ return this.parseResponse(response);
3835
+ }
3836
+ convertMessages(messages) {
3837
+ return messages.filter((m) => m.role !== "system").map((m) => {
3838
+ if (typeof m.content === "string") {
3839
+ return { role: m.role, content: m.content };
3840
+ }
3841
+ const blocks = m.content.map((block) => {
3842
+ switch (block.type) {
3843
+ case "text":
3844
+ return { type: "text", text: block.text };
3845
+ case "image":
3846
+ return {
3847
+ type: "image",
3848
+ source: {
3849
+ type: "base64",
3850
+ media_type: block.source.media_type,
3851
+ data: block.source.data
3852
+ }
3853
+ };
3854
+ case "tool_use":
3855
+ return {
3856
+ type: "tool_use",
3857
+ id: block.id,
3858
+ name: block.name,
3859
+ input: block.input
3860
+ };
3861
+ case "tool_result":
3862
+ return {
3863
+ type: "tool_result",
3864
+ tool_use_id: block.tool_use_id,
3865
+ content: typeof block.content === "string" ? block.content : JSON.stringify(block.content),
3866
+ ...block.is_error ? { is_error: true } : {}
3867
+ };
3868
+ default:
3869
+ return { type: "text", text: JSON.stringify(block) };
3870
+ }
3871
+ });
3872
+ return { role: m.role, content: blocks };
3873
+ });
3874
+ }
3875
+ parseResponse(response) {
3876
+ const content = [];
3877
+ const toolCalls = [];
3878
+ let text = "";
3879
+ for (const block of response.content) {
3880
+ if (block.type === "text") {
3881
+ content.push({ type: "text", text: block.text });
3882
+ text += block.text;
3883
+ } else if (block.type === "tool_use") {
3884
+ const tc = {
3885
+ id: block.id,
3886
+ name: block.name,
3887
+ input: block.input
3888
+ };
3889
+ content.push({
3890
+ type: "tool_use",
3891
+ id: block.id,
3892
+ name: block.name,
3893
+ input: block.input
3894
+ });
3895
+ toolCalls.push(tc);
3896
+ }
3897
+ }
3898
+ return {
3899
+ content,
3900
+ text: text || void 0,
3901
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
3902
+ usage: {
3903
+ input_tokens: response.usage.input_tokens,
3904
+ output_tokens: response.usage.output_tokens
3905
+ },
3906
+ stopReason: response.stop_reason
3907
+ };
3908
+ }
3909
+ };
3910
+
3911
+ // src/llm/openai.ts
3912
+ init_esm_shims();
3913
+ import OpenAI from "openai";
3914
+ var OpenAIProvider = class {
3915
+ name = "openai";
3916
+ model;
3917
+ client;
3918
+ maxTokens;
3919
+ constructor(config) {
3920
+ this.client = new OpenAI({
3921
+ apiKey: config.apiKey || process.env["OPENAI_API_KEY"],
3922
+ ...config.baseUrl ? { baseURL: config.baseUrl } : {}
3923
+ });
3924
+ this.model = config.model || "gpt-4o";
3925
+ this.maxTokens = config.maxTokens || 8192;
3926
+ }
3927
+ setModel(model) {
3928
+ this.model = model;
3929
+ }
3930
+ async listModels() {
3931
+ try {
3932
+ const models = await this.client.models.list();
3933
+ return models.data.filter((m) => m.id.startsWith("gpt-")).map((m) => m.id).sort();
3934
+ } catch {
3935
+ return ["gpt-4o", "gpt-4o-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano"];
3936
+ }
3937
+ }
3938
+ async chat(options) {
3939
+ const messages = this.convertMessages(options.messages, options.systemPrompt);
3940
+ const tools = options.tools?.map((t) => ({
3941
+ type: "function",
3942
+ function: {
3943
+ name: t.name,
3944
+ description: t.description,
3945
+ parameters: t.input_schema
3946
+ }
3947
+ }));
3948
+ if (options.stream && options.callbacks) {
3949
+ return this.streamChat(messages, tools, options);
3950
+ }
3951
+ const response = await this.client.chat.completions.create({
3952
+ model: this.model,
3953
+ messages,
3954
+ max_tokens: options.maxTokens || this.maxTokens,
3955
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
3956
+ ...tools && tools.length > 0 ? { tools } : {}
3957
+ });
3958
+ return this.parseResponse(response);
3959
+ }
3960
+ async streamChat(messages, tools, options) {
3961
+ const stream = await this.client.chat.completions.create({
3962
+ model: this.model,
3963
+ messages,
3964
+ max_tokens: options.maxTokens || this.maxTokens,
3965
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
3966
+ ...tools && tools.length > 0 ? { tools } : {},
3967
+ stream: true
3968
+ });
3969
+ const content = [];
3970
+ const toolCalls = [];
3971
+ let text = "";
3972
+ const toolCallAccumulator = /* @__PURE__ */ new Map();
3973
+ for await (const chunk of stream) {
3974
+ const delta = chunk.choices[0]?.delta;
3975
+ if (!delta) continue;
3976
+ if (delta.content) {
3977
+ text += delta.content;
3978
+ options.callbacks?.onToken?.(delta.content);
3979
+ }
3980
+ if (delta.tool_calls) {
3981
+ for (const tc of delta.tool_calls) {
3982
+ const idx = tc.index;
3983
+ if (!toolCallAccumulator.has(idx)) {
3984
+ toolCallAccumulator.set(idx, { id: tc.id || "", name: tc.function?.name || "", args: "" });
3985
+ }
3986
+ const acc = toolCallAccumulator.get(idx);
3987
+ if (tc.id) acc.id = tc.id;
3988
+ if (tc.function?.name) acc.name = tc.function.name;
3989
+ if (tc.function?.arguments) acc.args += tc.function.arguments;
3990
+ }
3991
+ }
3992
+ }
3993
+ if (text) {
3994
+ content.push({ type: "text", text });
3995
+ }
3996
+ for (const [, acc] of toolCallAccumulator) {
3997
+ let input3 = {};
3998
+ try {
3999
+ input3 = JSON.parse(acc.args);
4000
+ } catch {
4001
+ }
4002
+ const tc = { id: acc.id, name: acc.name, input: input3 };
4003
+ toolCalls.push(tc);
4004
+ content.push({ type: "tool_use", id: acc.id, name: acc.name, input: input3 });
4005
+ }
4006
+ return {
4007
+ content,
4008
+ text: text || void 0,
4009
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
4010
+ stopReason: toolCalls.length > 0 ? "tool_use" : "end_turn"
4011
+ };
4012
+ }
4013
+ convertMessages(messages, systemPrompt) {
4014
+ const result = [];
4015
+ if (systemPrompt) {
4016
+ result.push({ role: "system", content: systemPrompt });
4017
+ }
4018
+ for (const m of messages) {
4019
+ if (m.role === "system") {
4020
+ result.push({ role: "system", content: typeof m.content === "string" ? m.content : JSON.stringify(m.content) });
4021
+ continue;
4022
+ }
4023
+ if (typeof m.content === "string") {
4024
+ result.push({ role: m.role, content: m.content });
4025
+ continue;
4026
+ }
4027
+ const hasToolResults = m.content.some((b) => b.type === "tool_result");
4028
+ if (hasToolResults) {
4029
+ for (const block of m.content) {
4030
+ if (block.type === "tool_result") {
4031
+ result.push({
4032
+ role: "tool",
4033
+ tool_call_id: block.tool_use_id,
4034
+ content: typeof block.content === "string" ? block.content : JSON.stringify(block.content)
4035
+ });
4036
+ }
4037
+ }
4038
+ continue;
4039
+ }
4040
+ const hasToolUse = m.content.some((b) => b.type === "tool_use");
4041
+ if (hasToolUse) {
4042
+ const toolCalls = [];
4043
+ let textContent = "";
4044
+ for (const block of m.content) {
4045
+ if (block.type === "tool_use") {
4046
+ toolCalls.push({
4047
+ id: block.id,
4048
+ type: "function",
4049
+ function: {
4050
+ name: block.name,
4051
+ arguments: JSON.stringify(block.input)
4052
+ }
4053
+ });
4054
+ } else if (block.type === "text") {
4055
+ textContent += block.text;
4056
+ }
4057
+ }
4058
+ result.push({
4059
+ role: "assistant",
4060
+ content: textContent || null,
4061
+ tool_calls: toolCalls
4062
+ });
4063
+ continue;
4064
+ }
4065
+ const parts = m.content.filter(
4066
+ (b) => b.type === "text" || b.type === "image"
4067
+ ).map((b) => {
4068
+ if (b.type === "text") return { type: "text", text: b.text };
4069
+ return {
4070
+ type: "image_url",
4071
+ image_url: { url: `data:${b.source.media_type};base64,${b.source.data}` }
4072
+ };
4073
+ });
4074
+ if (m.role === "user") {
4075
+ result.push({ role: "user", content: parts });
4076
+ } else {
4077
+ const textContent = parts.filter((p) => p.type === "text").map((p) => p.text).join("");
4078
+ result.push({ role: "assistant", content: textContent || null });
4079
+ }
4080
+ }
4081
+ return result;
4082
+ }
4083
+ parseResponse(response) {
4084
+ const choice = response.choices[0];
4085
+ if (!choice) {
4086
+ return { content: [], stopReason: "end_turn" };
4087
+ }
4088
+ const content = [];
4089
+ const toolCalls = [];
4090
+ let text = "";
4091
+ if (choice.message.content) {
4092
+ text = choice.message.content;
4093
+ content.push({ type: "text", text });
4094
+ }
4095
+ if (choice.message.tool_calls) {
4096
+ for (const tc of choice.message.tool_calls) {
4097
+ if (tc.type !== "function") continue;
4098
+ let input3 = {};
4099
+ try {
4100
+ input3 = JSON.parse(tc.function.arguments);
4101
+ } catch {
4102
+ }
4103
+ toolCalls.push({ id: tc.id, name: tc.function.name, input: input3 });
4104
+ content.push({ type: "tool_use", id: tc.id, name: tc.function.name, input: input3 });
4105
+ }
4106
+ }
4107
+ return {
4108
+ content,
4109
+ text: text || void 0,
4110
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
4111
+ usage: response.usage ? {
4112
+ input_tokens: response.usage.prompt_tokens,
4113
+ output_tokens: response.usage.completion_tokens
4114
+ } : void 0,
4115
+ stopReason: choice.finish_reason === "tool_calls" ? "tool_use" : choice.finish_reason === "length" ? "max_tokens" : "end_turn"
4116
+ };
4117
+ }
4118
+ };
4119
+
4120
+ // src/llm/ollama.ts
4121
+ init_esm_shims();
4122
+ import { Ollama } from "ollama";
4123
+ var OllamaProvider = class {
4124
+ name = "ollama";
4125
+ model;
4126
+ client;
4127
+ maxTokens;
4128
+ constructor(config) {
4129
+ this.client = new Ollama({
4130
+ host: config.baseUrl || process.env["OLLAMA_HOST"] || "http://localhost:11434"
4131
+ });
4132
+ this.model = config.model || "llama3.1";
4133
+ this.maxTokens = config.maxTokens || 4096;
4134
+ }
4135
+ setModel(model) {
4136
+ this.model = model;
4137
+ }
4138
+ async listModels() {
4139
+ try {
4140
+ const models = await this.client.list();
4141
+ return models.models.map((m) => m.name);
4142
+ } catch {
4143
+ return [];
4144
+ }
4145
+ }
4146
+ async chat(options) {
4147
+ const messages = this.convertMessages(options.messages, options.systemPrompt);
4148
+ const tools = options.tools?.map((t) => ({
4149
+ type: "function",
4150
+ function: {
4151
+ name: t.name,
4152
+ description: t.description,
4153
+ parameters: t.input_schema
4154
+ }
4155
+ }));
4156
+ if (options.stream && options.callbacks) {
4157
+ return this.streamChat(messages, tools, options);
4158
+ }
4159
+ const response = await this.client.chat({
4160
+ model: this.model,
4161
+ messages,
4162
+ ...tools && tools.length > 0 ? { tools } : {},
4163
+ options: {
4164
+ num_predict: options.maxTokens || this.maxTokens,
4165
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {}
4166
+ }
4167
+ });
4168
+ return this.parseResponse(response);
4169
+ }
4170
+ async streamChat(messages, tools, options) {
4171
+ const response = await this.client.chat({
4172
+ model: this.model,
4173
+ messages,
4174
+ ...tools && tools.length > 0 ? { tools } : {},
4175
+ stream: true,
4176
+ options: {
4177
+ num_predict: options.maxTokens || this.maxTokens,
4178
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {}
4179
+ }
4180
+ });
4181
+ let text = "";
4182
+ const toolCalls = [];
4183
+ for await (const chunk of response) {
4184
+ if (chunk.message?.content) {
4185
+ text += chunk.message.content;
4186
+ options.callbacks?.onToken?.(chunk.message.content);
4187
+ }
4188
+ if (chunk.message?.tool_calls) {
4189
+ for (const tc of chunk.message.tool_calls) {
4190
+ toolCalls.push({
4191
+ id: `ollama_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
4192
+ name: tc.function.name,
4193
+ input: tc.function.arguments
4194
+ });
4195
+ }
4196
+ }
4197
+ }
4198
+ const content = [];
4199
+ if (text) content.push({ type: "text", text });
4200
+ for (const tc of toolCalls) {
4201
+ content.push({ type: "tool_use", id: tc.id, name: tc.name, input: tc.input });
4202
+ }
4203
+ return {
4204
+ content,
4205
+ text: text || void 0,
4206
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
4207
+ stopReason: toolCalls.length > 0 ? "tool_use" : "end_turn"
4208
+ };
4209
+ }
4210
+ convertMessages(messages, systemPrompt) {
4211
+ const result = [];
4212
+ if (systemPrompt) {
4213
+ result.push({ role: "system", content: systemPrompt });
4214
+ }
4215
+ for (const m of messages) {
4216
+ if (typeof m.content === "string") {
4217
+ result.push({ role: m.role, content: m.content });
4218
+ continue;
4219
+ }
4220
+ const textParts = [];
4221
+ const images = [];
4222
+ for (const block of m.content) {
4223
+ if (block.type === "text") {
4224
+ textParts.push(block.text);
4225
+ } else if (block.type === "image") {
4226
+ images.push(block.source.data);
4227
+ } else if (block.type === "tool_result") {
4228
+ textParts.push(`[Tool Result: ${typeof block.content === "string" ? block.content : JSON.stringify(block.content)}]`);
4229
+ } else if (block.type === "tool_use") {
4230
+ textParts.push(`[Tool Call: ${block.name}(${JSON.stringify(block.input)})]`);
4231
+ }
4232
+ }
4233
+ result.push({
4234
+ role: m.role,
4235
+ content: textParts.join("\n"),
4236
+ ...images.length > 0 ? { images } : {}
4237
+ });
4238
+ }
4239
+ return result;
4240
+ }
4241
+ parseResponse(response) {
4242
+ const content = [];
4243
+ const toolCalls = [];
4244
+ let text = "";
4245
+ if (response.message?.content) {
4246
+ text = response.message.content;
4247
+ content.push({ type: "text", text });
4248
+ }
4249
+ if (response.message?.tool_calls) {
4250
+ for (const tc of response.message.tool_calls) {
4251
+ const toolCall = {
4252
+ id: `ollama_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
4253
+ name: tc.function.name,
4254
+ input: tc.function.arguments
4255
+ };
4256
+ toolCalls.push(toolCall);
4257
+ content.push({ type: "tool_use", ...toolCall });
4258
+ }
4259
+ }
4260
+ return {
4261
+ content,
4262
+ text: text || void 0,
4263
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
4264
+ usage: response.eval_count ? { input_tokens: response.prompt_eval_count ?? 0, output_tokens: response.eval_count } : void 0,
4265
+ stopReason: toolCalls.length > 0 ? "tool_use" : "end_turn"
4266
+ };
4267
+ }
4268
+ };
4269
+
4270
+ // src/cli.ts
4271
+ function parseArgs(argv) {
4272
+ const args = {};
4273
+ let i = 2;
4274
+ while (i < argv.length) {
4275
+ const arg = argv[i];
4276
+ switch (arg) {
4277
+ case "--model":
4278
+ case "-m":
4279
+ args.model = argv[++i];
4280
+ break;
4281
+ case "--provider":
4282
+ args.provider = argv[++i];
4283
+ break;
4284
+ case "-p":
4285
+ args.prompt = argv.slice(i + 1).join(" ");
4286
+ i = argv.length;
4287
+ break;
4288
+ case "-c":
4289
+ case "--continue":
4290
+ args.continue = true;
4291
+ break;
4292
+ case "-r":
4293
+ case "--resume":
4294
+ args.resume = argv[++i];
4295
+ break;
4296
+ case "--help":
4297
+ case "-h":
4298
+ args.help = true;
4299
+ break;
4300
+ case "--version":
4301
+ case "-v":
4302
+ args.version = true;
4303
+ break;
4304
+ case "--plan":
4305
+ args.plan = true;
4306
+ break;
4307
+ case "--yolo":
4308
+ args.yolo = true;
4309
+ break;
4310
+ default:
4311
+ if (!arg.startsWith("-")) {
4312
+ args.prompt = argv.slice(i).join(" ");
4313
+ i = argv.length;
4314
+ }
4315
+ break;
4316
+ }
4317
+ i++;
4318
+ }
4319
+ return args;
4320
+ }
4321
+ function printHelp() {
4322
+ console.log(`
4323
+ ${chalk13.cyan.bold("Codi (\uCF54\uB514)")} - AI Code Agent for Terminal
4324
+
4325
+ ${chalk13.bold("Usage:")}
4326
+ codi [options] [prompt]
4327
+
4328
+ ${chalk13.bold("Options:")}
4329
+ -m, --model <model> Set the model (default: gemini-2.5-flash)
4330
+ --provider <name> Set the provider (openai, anthropic, ollama)
4331
+ -p <prompt> Run a single prompt and exit
4332
+ -c, --continue Continue the last session
4333
+ -r, --resume <id> Resume a specific session
4334
+ --plan Start in plan mode (read-only)
4335
+ --yolo Skip all permission checks
4336
+ -h, --help Show this help
4337
+ -v, --version Show version
4338
+
4339
+ ${chalk13.bold("Environment:")}
4340
+ GEMINI_API_KEY Google Gemini API key (default provider)
4341
+ OPENAI_API_KEY OpenAI API key
4342
+ ANTHROPIC_API_KEY Anthropic API key
4343
+
4344
+ ${chalk13.bold("Examples:")}
4345
+ codi # Start interactive session
4346
+ codi -p "explain main.ts" # Single prompt
4347
+ codi --provider anthropic # Use Anthropic Claude
4348
+ codi --model gpt-4o --provider openai # Use OpenAI GPT-4o
4349
+ codi -c # Continue last session
4350
+ `);
4351
+ }
4352
+ async function main() {
4353
+ const args = parseArgs(process.argv);
4354
+ if (args.help) {
4355
+ printHelp();
4356
+ process.exit(0);
4357
+ }
4358
+ if (args.version) {
4359
+ console.log("codi v0.1.0");
4360
+ process.exit(0);
4361
+ }
4362
+ if (await needsSetup()) {
4363
+ const result = await runSetupWizard();
4364
+ if (result) {
4365
+ configManager.reload();
4366
+ }
4367
+ }
4368
+ const config = configManager.get();
4369
+ const providerName = args.provider || config.provider;
4370
+ const modelName = args.model || config.model;
4371
+ let provider;
4372
+ switch (providerName) {
4373
+ case "anthropic":
4374
+ provider = new AnthropicProvider({
4375
+ apiKey: config.apiKeys.anthropic,
4376
+ model: modelName,
4377
+ maxTokens: config.maxTokens,
4378
+ baseUrl: config.baseUrls.anthropic
4379
+ });
4380
+ break;
4381
+ case "ollama":
4382
+ provider = new OllamaProvider({
4383
+ model: modelName,
4384
+ maxTokens: config.maxTokens,
4385
+ baseUrl: config.baseUrls.ollama
4386
+ });
4387
+ break;
4388
+ case "openai":
4389
+ default:
4390
+ provider = new OpenAIProvider({
4391
+ apiKey: config.apiKeys.openai || process.env["GEMINI_API_KEY"],
4392
+ model: modelName,
4393
+ maxTokens: config.maxTokens,
4394
+ baseUrl: config.baseUrls.openai
4395
+ });
4396
+ break;
4397
+ }
4398
+ tokenTracker.setModel(modelName);
4399
+ statusLine.update({ model: modelName, provider: providerName });
4400
+ const registry = new ToolRegistry();
4401
+ registry.registerAll([
4402
+ fileReadTool,
4403
+ fileWriteTool,
4404
+ fileEditTool,
4405
+ fileMultiEditTool,
4406
+ globTool,
4407
+ grepTool,
4408
+ bashTool,
4409
+ listDirTool,
4410
+ gitTool,
4411
+ webFetchTool,
4412
+ webSearchTool,
4413
+ notebookEditTool,
4414
+ subAgentTool,
4415
+ taskCreateTool,
4416
+ taskUpdateTool,
4417
+ taskListTool,
4418
+ taskGetTool,
4419
+ askUserTool
4420
+ ]);
4421
+ const subAgentHandler2 = createSubAgentHandler(provider, registry);
4422
+ setSubAgentHandler(subAgentHandler2);
4423
+ try {
4424
+ await mcpManager.initialize(registry);
4425
+ } catch {
4426
+ }
4427
+ const conversation = new Conversation();
4428
+ const compressor = new ContextCompressor({
4429
+ threshold: config.autoCompactThreshold
4430
+ });
4431
+ const codiMd = loadCodiMd();
4432
+ const memory = config.memoryEnabled ? memoryManager.buildMemoryPrompt() : "";
4433
+ function buildPrompt() {
4434
+ const context = {
4435
+ model: provider.model,
4436
+ provider: provider.name,
4437
+ cwd: process.cwd(),
4438
+ codiMd: codiMd || void 0,
4439
+ memory: memory || void 0,
4440
+ planMode: getMode() === "plan"
4441
+ };
4442
+ return buildSystemPrompt(context);
4443
+ }
4444
+ conversation.setSystemPrompt(buildPrompt());
4445
+ if (args.yolo) {
4446
+ setPermissionMode("yolo");
4447
+ } else if (args.plan) {
4448
+ setPermissionMode("plan");
4449
+ setMode("plan");
4450
+ statusLine.update({ mode: "plan" });
4451
+ }
4452
+ if (args.continue || args.resume) {
4453
+ const id = args.resume || sessionManager.getLatest()?.id;
4454
+ if (id) {
4455
+ const session = sessionManager.load(id);
4456
+ if (session) {
4457
+ const data = session.conversation.serialize();
4458
+ for (const msg of data.messages) {
4459
+ if (msg.role === "user") conversation.addUserMessage(msg.content);
4460
+ else if (msg.role === "assistant") conversation.addAssistantMessage(msg.content);
4461
+ }
4462
+ console.log(chalk13.dim(`Resumed session: ${id}`));
4463
+ }
4464
+ }
4465
+ }
4466
+ await hookManager.runHooks("SessionStart", { cwd: process.cwd() });
4467
+ if (args.prompt) {
4468
+ await agentLoop(args.prompt, {
4469
+ provider,
4470
+ conversation,
4471
+ registry,
4472
+ systemPrompt: conversation.getSystemPrompt(),
4473
+ permissionCheck: checkPermission,
4474
+ preHook: async (toolName, input3) => hookManager.runHooks("PreToolUse", { tool: toolName, args: input3 }),
4475
+ postHook: async (toolName, input3, result) => {
4476
+ await hookManager.runHooks("PostToolUse", { tool: toolName, args: input3, result });
4477
+ },
4478
+ planMode: getMode() === "plan"
4479
+ });
4480
+ await hookManager.runHooks("SessionEnd", {});
4481
+ await mcpManager.disconnectAll();
4482
+ process.exit(0);
4483
+ }
4484
+ const slashCommands = [...createBuiltinCommands(), ...loadCustomCommands()];
4485
+ const cmdCtx = {
4486
+ conversation,
4487
+ provider,
4488
+ compressor,
4489
+ exitFn: async () => {
4490
+ stopSpinner();
4491
+ console.log(chalk13.dim("\nSaving session..."));
4492
+ sessionManager.save(conversation, void 0, provider.model);
4493
+ await hookManager.runHooks("SessionEnd", {});
4494
+ await mcpManager.disconnectAll();
4495
+ },
4496
+ setProvider: (name, model) => {
4497
+ const newProvider = name || providerName;
4498
+ switch (newProvider) {
4499
+ case "openai":
4500
+ provider = new OpenAIProvider({ model, maxTokens: config.maxTokens });
4501
+ break;
4502
+ case "ollama":
4503
+ provider = new OllamaProvider({ model, maxTokens: config.maxTokens });
4504
+ break;
4505
+ default:
4506
+ provider = new AnthropicProvider({ model, maxTokens: config.maxTokens });
4507
+ break;
4508
+ }
4509
+ tokenTracker.setModel(model);
4510
+ statusLine.update({ model, provider: newProvider });
4511
+ },
4512
+ reloadSystemPrompt: () => {
4513
+ conversation.setSystemPrompt(buildPrompt());
4514
+ }
4515
+ };
4516
+ const repl = new Repl({
4517
+ onMessage: async (message) => {
4518
+ checkpointManager.create(conversation, message.slice(0, 50));
4519
+ if (compressor.shouldCompress(conversation)) {
4520
+ console.log(chalk13.dim("Auto-compacting conversation..."));
4521
+ await compressor.compress(conversation, provider);
4522
+ conversation.setSystemPrompt(buildPrompt());
4523
+ }
4524
+ await agentLoop(message, {
4525
+ provider,
4526
+ conversation,
4527
+ registry,
4528
+ systemPrompt: conversation.getSystemPrompt(),
4529
+ permissionCheck: checkPermission,
4530
+ preHook: async (toolName, input3) => hookManager.runHooks("PreToolUse", { tool: toolName, args: input3 }),
4531
+ postHook: async (toolName, input3, result) => {
4532
+ await hookManager.runHooks("PostToolUse", { tool: toolName, args: input3, result });
4533
+ },
4534
+ planMode: getMode() === "plan"
4535
+ });
4536
+ },
4537
+ onSlashCommand: async (command, args2) => {
4538
+ const cmd = slashCommands.find(
4539
+ (c) => c.name === command || c.aliases?.includes(command)
4540
+ );
4541
+ if (!cmd) return false;
4542
+ return cmd.handler(args2, cmdCtx);
4543
+ },
4544
+ onInterrupt: () => {
4545
+ stopSpinner();
4546
+ },
4547
+ onExit: async () => {
4548
+ stopSpinner();
4549
+ console.log(chalk13.dim("\nSaving session..."));
4550
+ sessionManager.save(conversation, void 0, provider.model);
4551
+ await hookManager.runHooks("SessionEnd", {});
4552
+ await mcpManager.disconnectAll();
4553
+ }
4554
+ });
4555
+ process.on("SIGTERM", async () => {
4556
+ stopSpinner();
4557
+ sessionManager.save(conversation, void 0, provider.model);
4558
+ await hookManager.runHooks("SessionEnd", {});
4559
+ await mcpManager.disconnectAll();
4560
+ process.exit(0);
4561
+ });
4562
+ await repl.start();
4563
+ }
4564
+ main().catch((err) => {
4565
+ console.error(chalk13.red(`Fatal error: ${err.message}`));
4566
+ console.error(err.stack);
4567
+ process.exit(1);
4568
+ });
4569
+ //# sourceMappingURL=cli.js.map