@aurora-foundation/obsidian-next 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3736 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import React7 from "react";
5
+ import { render } from "ink";
6
+
7
+ // src/ui/Root.tsx
8
+ import { useState as useState4, useEffect, useCallback as useCallback3 } from "react";
9
+ import { Box as Box7, Text as Text7, useApp, useInput as useInput3 } from "ink";
10
+ import TextInput from "ink-text-input";
11
+
12
+ // src/core/bus.ts
13
+ import { EventEmitter } from "events";
14
+ var EventBus = class extends EventEmitter {
15
+ constructor() {
16
+ super();
17
+ this.setMaxListeners(20);
18
+ }
19
+ emitAgent(e) {
20
+ this.emit("agent", e);
21
+ }
22
+ emitUser(e) {
23
+ this.emit("user", e);
24
+ }
25
+ };
26
+ var bus = new EventBus();
27
+
28
+ // src/components/AgentLine.tsx
29
+ import React from "react";
30
+ import { Box, Text } from "ink";
31
+
32
+ // src/utils/syntax.ts
33
+ import chalk from "chalk";
34
+ var KEYWORDS = {
35
+ typescript: ["const", "let", "var", "function", "class", "interface", "type", "import", "export", "from", "return", "if", "else", "for", "while", "switch", "case", "break", "continue", "try", "catch", "throw", "async", "await", "new", "this", "super", "extends", "implements", "static", "public", "private", "protected", "readonly", "enum", "namespace", "module", "declare", "as", "is", "in", "of", "typeof", "instanceof", "void", "null", "undefined", "true", "false"],
36
+ javascript: ["const", "let", "var", "function", "class", "import", "export", "from", "return", "if", "else", "for", "while", "switch", "case", "break", "continue", "try", "catch", "throw", "async", "await", "new", "this", "super", "extends", "static", "typeof", "instanceof", "void", "null", "undefined", "true", "false"],
37
+ python: ["def", "class", "import", "from", "return", "if", "elif", "else", "for", "while", "try", "except", "finally", "with", "as", "pass", "break", "continue", "raise", "yield", "lambda", "and", "or", "not", "in", "is", "None", "True", "False", "self", "async", "await"],
38
+ bash: ["if", "then", "else", "elif", "fi", "for", "while", "do", "done", "case", "esac", "function", "return", "exit", "export", "local", "readonly", "declare", "echo", "cd", "ls", "rm", "cp", "mv", "mkdir", "cat", "grep", "sed", "awk", "npm", "npx", "node", "git"],
39
+ json: [],
40
+ default: ["const", "let", "var", "function", "class", "return", "if", "else", "for", "while", "import", "export", "true", "false", "null"]
41
+ };
42
+ var TYPES = ["string", "number", "boolean", "object", "any", "void", "never", "unknown", "Array", "Promise", "Map", "Set", "Record", "Partial", "Required", "Readonly"];
43
+ function highlightCodeLine(line, lang) {
44
+ const keywords = KEYWORDS[lang] || KEYWORDS.default;
45
+ if (!line.trim()) return line;
46
+ let result = line;
47
+ const commentMatch = result.match(/(\/\/.*|#.*)$/);
48
+ if (commentMatch) {
49
+ const commentStart = result.indexOf(commentMatch[0]);
50
+ const before = result.slice(0, commentStart);
51
+ const comment = chalk.gray(commentMatch[0]);
52
+ result = before + comment;
53
+ line = before;
54
+ }
55
+ result = result.replace(/(["'`])(?:(?!\1)[^\\]|\\.)*\1/g, (match) => {
56
+ return chalk.green(match);
57
+ });
58
+ result = result.replace(/\b(\d+\.?\d*)\b/g, (match) => {
59
+ return chalk.yellow(match);
60
+ });
61
+ for (const kw of keywords) {
62
+ const regex = new RegExp(`\\b(${kw})\\b`, "g");
63
+ result = result.replace(regex, chalk.magenta("$1"));
64
+ }
65
+ if (lang === "typescript" || lang === "ts") {
66
+ for (const type of TYPES) {
67
+ const regex = new RegExp(`\\b(${type})\\b`, "g");
68
+ result = result.replace(regex, chalk.cyan("$1"));
69
+ }
70
+ }
71
+ result = result.replace(/\b([a-zA-Z_]\w*)\s*\(/g, (match, fn) => {
72
+ return chalk.blue(fn) + "(";
73
+ });
74
+ return result;
75
+ }
76
+ function highlightCodeBlock(code, lang = "default") {
77
+ const normalizedLang = lang.toLowerCase().replace("shell", "bash").replace("sh", "bash").replace("ts", "typescript").replace("js", "javascript").replace("py", "python");
78
+ const lines = code.split("\n");
79
+ return lines.map((line) => highlightCodeLine(line, normalizedLang)).join("\n");
80
+ }
81
+ function parseMarkdown(content) {
82
+ const segments = [];
83
+ const lines = content.split("\n");
84
+ let inCodeBlock = false;
85
+ let codeBlockLang = "";
86
+ let codeBlockContent = [];
87
+ for (const line of lines) {
88
+ if (line.startsWith("```")) {
89
+ if (inCodeBlock) {
90
+ segments.push({
91
+ type: "code",
92
+ content: codeBlockContent.join("\n"),
93
+ lang: codeBlockLang
94
+ });
95
+ codeBlockContent = [];
96
+ inCodeBlock = false;
97
+ codeBlockLang = "";
98
+ } else {
99
+ inCodeBlock = true;
100
+ codeBlockLang = line.slice(3).trim() || "default";
101
+ }
102
+ continue;
103
+ }
104
+ if (inCodeBlock) {
105
+ codeBlockContent.push(line);
106
+ continue;
107
+ }
108
+ if (line.startsWith("#")) {
109
+ segments.push({ type: "heading", content: line });
110
+ continue;
111
+ }
112
+ if (line.match(/^\s*[-*]\s/) || line.match(/^\s*\d+\.\s/)) {
113
+ segments.push({ type: "list", content: line });
114
+ continue;
115
+ }
116
+ segments.push({ type: "text", content: line });
117
+ }
118
+ if (inCodeBlock && codeBlockContent.length > 0) {
119
+ segments.push({
120
+ type: "code",
121
+ content: codeBlockContent.join("\n"),
122
+ lang: codeBlockLang
123
+ });
124
+ }
125
+ return segments;
126
+ }
127
+ function renderMarkdown(content) {
128
+ const segments = parseMarkdown(content);
129
+ const output = [];
130
+ for (const seg of segments) {
131
+ switch (seg.type) {
132
+ case "code":
133
+ output.push(chalk.bgGray.black(" " + (seg.lang || "code") + " "));
134
+ const highlighted = highlightCodeBlock(seg.content, seg.lang);
135
+ for (const line2 of highlighted.split("\n")) {
136
+ output.push(" " + line2);
137
+ }
138
+ break;
139
+ case "heading":
140
+ const level = (seg.content.match(/^#+/) || [""])[0].length;
141
+ const text = seg.content.replace(/^#+\s*/, "");
142
+ if (level === 1) {
143
+ output.push(chalk.bold.white(text));
144
+ } else if (level === 2) {
145
+ output.push(chalk.bold.cyan(text));
146
+ } else {
147
+ output.push(chalk.bold.gray(text));
148
+ }
149
+ break;
150
+ case "list":
151
+ const listText = seg.content.replace(/^(\s*)([-*]|\d+\.)\s/, (_, indent, bullet) => {
152
+ return indent + chalk.cyan(bullet) + " ";
153
+ });
154
+ output.push(listText);
155
+ break;
156
+ case "text":
157
+ let line = seg.content;
158
+ line = line.replace(/`([^`]+)`/g, (_, code) => {
159
+ return chalk.bgGray.white(" " + code + " ");
160
+ });
161
+ line = line.replace(/\*\*([^*]+)\*\*/g, (_, text2) => {
162
+ return chalk.bold(text2);
163
+ });
164
+ line = line.replace(/\*([^*]+)\*/g, (_, text2) => {
165
+ return chalk.italic(text2);
166
+ });
167
+ output.push(line);
168
+ break;
169
+ default:
170
+ output.push(seg.content);
171
+ }
172
+ }
173
+ return output.join("\n");
174
+ }
175
+
176
+ // src/components/AgentLine.tsx
177
+ import { jsx, jsxs } from "react/jsx-runtime";
178
+ var flareAnim = ["\xB7", "\u25AA", "\u259A", "\u2756", "\u2726", "\u2739", "\u2726", "\u25AA"];
179
+ var isProcessingMessage = (content) => {
180
+ const lower = content.toLowerCase().trim();
181
+ if (content.length > 100) return false;
182
+ return lower.endsWith("...") || lower.startsWith("[safe]") || lower.startsWith("[plan]") || lower.startsWith("[auto]") || lower === "generating plan..." || lower === "executing plan...";
183
+ };
184
+ var AgentLine = ({ content, isStreaming }) => {
185
+ const isProcessing = isStreaming || isProcessingMessage(content);
186
+ const [frame, setFrame] = React.useState(0);
187
+ React.useEffect(() => {
188
+ if (!isProcessing) return;
189
+ const interval = setInterval(() => {
190
+ setFrame((prev) => (prev + 1) % flareAnim.length);
191
+ }, 100);
192
+ return () => clearInterval(interval);
193
+ }, [isProcessing]);
194
+ const hasMarkdown = content.includes("```") || content.includes("# ") || content.match(/^\s*[-*]\s/m) || content.includes("`");
195
+ const renderedContent = hasMarkdown && !isProcessing ? renderMarkdown(content) : content;
196
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
197
+ /* @__PURE__ */ jsx(Box, { marginRight: 1, children: isProcessing ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: flareAnim[frame] }) : /* @__PURE__ */ jsx(Text, { color: "cyan", children: "*" }) }),
198
+ /* @__PURE__ */ jsx(Box, { flexGrow: 1, flexDirection: "column", children: renderedContent.split("\n").map((line, i) => /* @__PURE__ */ jsx(Text, { color: isProcessing ? "gray" : void 0, children: line }, i)) })
199
+ ] }) });
200
+ };
201
+
202
+ // src/components/ToolOutput.tsx
203
+ import { useState } from "react";
204
+ import { Box as Box2, Text as Text2 } from "ink";
205
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
206
+ var MAX_VISIBLE_LINES = 8;
207
+ var colorizeLine = (line, isError) => {
208
+ if (isError) {
209
+ return /* @__PURE__ */ jsx2(Text2, { color: "red", children: line });
210
+ }
211
+ if (line.match(/^\s*\d+\s*\+/) || line.startsWith("+") && !line.startsWith("+++")) {
212
+ return /* @__PURE__ */ jsx2(Text2, { color: "green", children: line });
213
+ }
214
+ if (line.match(/^\s*\d+\s*-/) || line.startsWith("-") && !line.startsWith("---")) {
215
+ return /* @__PURE__ */ jsx2(Text2, { color: "red", children: line });
216
+ }
217
+ if (line.startsWith("@@")) {
218
+ return /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: line });
219
+ }
220
+ if (line.startsWith(">") || line.startsWith("$")) {
221
+ return /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: line });
222
+ }
223
+ if (line.includes("success") || line.includes("Success") || line.includes("\u2713") || line.includes("Build success")) {
224
+ return /* @__PURE__ */ jsx2(Text2, { color: "green", children: line });
225
+ }
226
+ if (line.includes("warning") || line.includes("Warning") || line.includes("WARN")) {
227
+ return /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: line });
228
+ }
229
+ if (line.includes("error") || line.includes("Error") || line.includes("ERR") || line.includes("failed")) {
230
+ return /* @__PURE__ */ jsx2(Text2, { color: "red", children: line });
231
+ }
232
+ if (line.match(/^[a-zA-Z].*\.(ts|js|tsx|jsx|json|md|css|html)/) || line.includes("dist/") || line.includes("src/")) {
233
+ return /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: line });
234
+ }
235
+ if (line.match(/\d+(\.\d+)?\s*(KB|MB|ms|s)\b/)) {
236
+ return /* @__PURE__ */ jsx2(Text2, { color: "magenta", children: line });
237
+ }
238
+ return /* @__PURE__ */ jsx2(Text2, { color: "white", children: line });
239
+ };
240
+ var ToolOutput = ({ tool, output, isError }) => {
241
+ const [expanded, setExpanded] = useState(false);
242
+ const lines = output.split("\n").filter((l) => l.trim());
243
+ const needsCollapse = lines.length > MAX_VISIBLE_LINES;
244
+ const visibleLines = expanded ? lines : lines.slice(0, MAX_VISIBLE_LINES);
245
+ const hiddenCount = lines.length - MAX_VISIBLE_LINES;
246
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
247
+ /* @__PURE__ */ jsxs2(Box2, { children: [
248
+ /* @__PURE__ */ jsx2(Text2, { color: isError ? "red" : "green", children: isError ? " \u2717 " : " \u23BF " }),
249
+ colorizeLine(visibleLines[0] || "", isError)
250
+ ] }),
251
+ visibleLines.slice(1).map((line, i) => /* @__PURE__ */ jsxs2(Box2, { children: [
252
+ /* @__PURE__ */ jsx2(Text2, { children: " " }),
253
+ colorizeLine(line, isError)
254
+ ] }, i)),
255
+ needsCollapse && !expanded && /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", dimColor: true, children: [
256
+ " ... +",
257
+ hiddenCount,
258
+ " lines"
259
+ ] }) })
260
+ ] });
261
+ };
262
+
263
+ // src/components/ApprovalPrompt.tsx
264
+ import { useState as useState2, useCallback } from "react";
265
+ import { Box as Box3, Text as Text3, useInput } from "ink";
266
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
267
+ var ApprovalPrompt = ({
268
+ requestId,
269
+ context: context2,
270
+ diff,
271
+ onResolve
272
+ }) => {
273
+ const [resolved, setResolved] = useState2(false);
274
+ const handleApproval = useCallback((approved) => {
275
+ if (resolved) return;
276
+ setResolved(true);
277
+ bus.emitUser({
278
+ type: "approval_response",
279
+ approved,
280
+ requestId
281
+ });
282
+ onResolve();
283
+ }, [resolved, requestId, onResolve]);
284
+ useInput((input, key) => {
285
+ if (resolved) return;
286
+ if (input.toLowerCase() === "y" || key.return && !key.shift) {
287
+ handleApproval(true);
288
+ }
289
+ if (input.toLowerCase() === "n" || key.escape) {
290
+ handleApproval(false);
291
+ }
292
+ });
293
+ if (resolved) {
294
+ return null;
295
+ }
296
+ return /* @__PURE__ */ jsxs3(
297
+ Box3,
298
+ {
299
+ flexDirection: "column",
300
+ borderStyle: "round",
301
+ borderColor: "yellow",
302
+ paddingX: 1,
303
+ paddingY: 0,
304
+ marginY: 1,
305
+ children: [
306
+ /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: "[!] Approval Required" }) }),
307
+ /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, children: /* @__PURE__ */ jsx3(Text3, { color: "white", children: context2 }) }),
308
+ diff && /* @__PURE__ */ jsxs3(
309
+ Box3,
310
+ {
311
+ flexDirection: "column",
312
+ borderStyle: "single",
313
+ borderColor: "gray",
314
+ paddingX: 1,
315
+ marginBottom: 1,
316
+ children: [
317
+ /* @__PURE__ */ jsx3(Text3, { color: "gray", dimColor: true, children: "Preview:" }),
318
+ diff.split("\n").slice(0, 10).map((line, i) => /* @__PURE__ */ jsx3(
319
+ Text3,
320
+ {
321
+ color: line.startsWith("+") ? "green" : line.startsWith("-") ? "red" : "gray",
322
+ children: line
323
+ },
324
+ i
325
+ )),
326
+ diff.split("\n").length > 10 && /* @__PURE__ */ jsxs3(Text3, { color: "gray", dimColor: true, children: [
327
+ "... (",
328
+ diff.split("\n").length - 10,
329
+ " more lines)"
330
+ ] })
331
+ ]
332
+ }
333
+ ),
334
+ /* @__PURE__ */ jsxs3(Box3, { children: [
335
+ /* @__PURE__ */ jsx3(Text3, { color: "green", bold: true, children: "[Y]" }),
336
+ /* @__PURE__ */ jsx3(Text3, { color: "white", children: " Approve " }),
337
+ /* @__PURE__ */ jsx3(Text3, { color: "red", bold: true, children: "[N]" }),
338
+ /* @__PURE__ */ jsx3(Text3, { color: "white", children: " Deny" })
339
+ ] })
340
+ ]
341
+ }
342
+ );
343
+ };
344
+
345
+ // src/components/ChoicePrompt.tsx
346
+ import { useState as useState3, useCallback as useCallback2 } from "react";
347
+ import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
348
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
349
+ var ChoicePrompt = ({
350
+ question,
351
+ options,
352
+ onResolve
353
+ }) => {
354
+ const [selectedIndex, setSelectedIndex] = useState3(0);
355
+ const [resolved, setResolved] = useState3(false);
356
+ const handleSelect = useCallback2(() => {
357
+ if (resolved) return;
358
+ const selected = options[selectedIndex];
359
+ if (!selected) return;
360
+ setResolved(true);
361
+ bus.emitUser({
362
+ type: "user_choice",
363
+ selectionId: selected.id
364
+ });
365
+ onResolve();
366
+ }, [resolved, selectedIndex, options, onResolve]);
367
+ useInput2((input, key) => {
368
+ if (resolved) return;
369
+ if (key.upArrow) {
370
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : options.length - 1);
371
+ }
372
+ if (key.downArrow) {
373
+ setSelectedIndex((prev) => prev < options.length - 1 ? prev + 1 : 0);
374
+ }
375
+ const num = parseInt(input, 10);
376
+ if (num >= 1 && num <= options.length) {
377
+ setSelectedIndex(num - 1);
378
+ }
379
+ if (key.return) {
380
+ handleSelect();
381
+ }
382
+ if (key.escape) {
383
+ setResolved(true);
384
+ bus.emitUser({
385
+ type: "user_choice",
386
+ selectionId: "cancel"
387
+ });
388
+ onResolve();
389
+ }
390
+ });
391
+ if (resolved) {
392
+ return null;
393
+ }
394
+ return /* @__PURE__ */ jsxs4(
395
+ Box4,
396
+ {
397
+ flexDirection: "column",
398
+ borderStyle: "round",
399
+ borderColor: "cyan",
400
+ paddingX: 1,
401
+ paddingY: 0,
402
+ marginY: 1,
403
+ children: [
404
+ /* @__PURE__ */ jsx4(Box4, { marginBottom: 1, children: /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "cyan", children: [
405
+ "[?] ",
406
+ question
407
+ ] }) }),
408
+ /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", marginBottom: 1, children: options.map((option, index) => {
409
+ const isSelected = index === selectedIndex;
410
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
411
+ /* @__PURE__ */ jsx4(Text4, { color: isSelected ? "cyan" : "gray", children: isSelected ? ">" : " " }),
412
+ /* @__PURE__ */ jsxs4(Text4, { color: isSelected ? "white" : "gray", bold: isSelected, children: [
413
+ "[",
414
+ index + 1,
415
+ "] ",
416
+ option.label
417
+ ] })
418
+ ] }, option.id);
419
+ }) }),
420
+ /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: "Use arrows or numbers to select, Enter to confirm, Esc to cancel" }) })
421
+ ]
422
+ }
423
+ );
424
+ };
425
+
426
+ // src/ui/Dashboard.tsx
427
+ import React5 from "react";
428
+ import { Box as Box5, Text as Text5 } from "ink";
429
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
430
+ var flareAnim2 = ["\xB7", "\u25AA", "\u259A", "\u2756", "\u2726", "\u2739", "\u2726", "\u25AA"];
431
+ var owlSprites = {
432
+ idle: `\u2590\u259B\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u259C\u258C
433
+ \u2590\u2588\u2588\u2580 \u2580\u2588\u2588\u258C
434
+ \u2590\u2588\u2588 \u2584 \u2588\u2588\u258C
435
+ \u2590\u2599\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u259F\u258C`,
436
+ blink_half: `\u2590\u259B\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u259C\u258C
437
+ \u2590\u2588\u2588\u2584 \u2584\u2588\u2588\u258C
438
+ \u2590\u2588\u2588 \u2584 \u2588\u2588\u258C
439
+ \u2590\u2599\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u259F\u258C`,
440
+ blink_full: `\u2590\u259B\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u259C\u258C
441
+ \u2590\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C
442
+ \u2590\u2588\u2588\u2588\u2588\u2584\u2588\u2588\u2588\u2588\u2588\u2588\u258C
443
+ \u2590\u2599\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u259F\u258C`,
444
+ suspicious: `\u2590\u259B\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u259C\u258C
445
+ \u2590\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C
446
+ \u2590\u2588\u2588\u2580 \u2584 \u2580\u2588\u2588\u258C
447
+ \u2590\u2599\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u259F\u258C`,
448
+ look_right: `\u2590\u259B\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u259C\u258C
449
+ \u2590\u2588\u2588\u2580 \u2590\u2588\u258C
450
+ \u2590\u2588\u2588 \u2584 \u2590\u2588\u258C
451
+ \u2590\u2599\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u259F\u258C`,
452
+ look_left: `\u2590\u259B\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u259C\u258C
453
+ \u2590\u2588\u258C \u2580\u2588\u2588\u258C
454
+ \u2590\u2588\u258C \u2584 \u2588\u2588\u258C
455
+ \u2590\u2599\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\u259F\u258C`
456
+ };
457
+ var Dashboard = ({
458
+ username = "User",
459
+ model = "Claude Sonnet 4.5",
460
+ workspace = process.cwd()
461
+ }) => {
462
+ const [flareFrame, setFlareFrame] = React5.useState(0);
463
+ const [owlState, setOwlState] = React5.useState("idle");
464
+ const [columns, setColumns] = React5.useState(process.stdout.columns);
465
+ React5.useEffect(() => {
466
+ const onResize = () => setColumns(process.stdout.columns);
467
+ process.stdout.on("resize", onResize);
468
+ return () => {
469
+ process.stdout.off("resize", onResize);
470
+ };
471
+ }, []);
472
+ React5.useEffect(() => {
473
+ const interval = setInterval(() => {
474
+ setFlareFrame((prev) => (prev + 1) % flareAnim2.length);
475
+ }, 100);
476
+ return () => clearInterval(interval);
477
+ }, []);
478
+ React5.useEffect(() => {
479
+ let isActive = true;
480
+ const loop = async () => {
481
+ while (isActive) {
482
+ const delay = Math.random() * 5e3 + 3e3;
483
+ await new Promise((r) => setTimeout(r, delay));
484
+ if (!isActive) break;
485
+ setOwlState("blink_half");
486
+ await new Promise((r) => setTimeout(r, 50));
487
+ setOwlState("blink_full");
488
+ await new Promise((r) => setTimeout(r, 100));
489
+ setOwlState("blink_half");
490
+ await new Promise((r) => setTimeout(r, 50));
491
+ setOwlState("idle");
492
+ }
493
+ };
494
+ loop();
495
+ return () => {
496
+ isActive = false;
497
+ };
498
+ }, []);
499
+ const showRightColumn = columns >= 100;
500
+ return /* @__PURE__ */ jsxs5(
501
+ Box5,
502
+ {
503
+ borderStyle: "round",
504
+ borderColor: "red",
505
+ flexDirection: "row",
506
+ paddingX: 1,
507
+ paddingY: 0,
508
+ children: [
509
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", width: showRightColumn ? "60%" : "100%", paddingRight: showRightColumn ? 1 : 0, children: [
510
+ /* @__PURE__ */ jsx5(Box5, { justifyContent: "center", marginBottom: 1, children: /* @__PURE__ */ jsxs5(Text5, { bold: true, color: "white", children: [
511
+ "Welcome back, ",
512
+ username,
513
+ "!"
514
+ ] }) }),
515
+ /* @__PURE__ */ jsx5(Box5, { justifyContent: "center", marginBottom: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "red", children: owlSprites[owlState] }) }),
516
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", alignItems: "center", children: [
517
+ /* @__PURE__ */ jsxs5(Text5, { color: "white", children: [
518
+ model,
519
+ " ",
520
+ /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: flareAnim2[flareFrame] }),
521
+ " Obsidian Next"
522
+ ] }),
523
+ /* @__PURE__ */ jsx5(Text5, { color: "gray", children: workspace })
524
+ ] })
525
+ ] }),
526
+ showRightColumn && /* @__PURE__ */ jsx5(Box5, { borderStyle: "single", borderTop: false, borderBottom: false, borderLeft: false, borderColor: "red", marginX: 1 }),
527
+ showRightColumn && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", width: "40%", paddingLeft: 1, children: [
528
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
529
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: "red", children: "Commands" }),
530
+ /* @__PURE__ */ jsxs5(Text5, { color: "white", children: [
531
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "/help" }),
532
+ " Show all commands"
533
+ ] }),
534
+ /* @__PURE__ */ jsxs5(Text5, { color: "white", children: [
535
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "/tool" }),
536
+ " Execute tools"
537
+ ] }),
538
+ /* @__PURE__ */ jsxs5(Text5, { color: "white", children: [
539
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "/clear" }),
540
+ " Clear history"
541
+ ] })
542
+ ] }),
543
+ /* @__PURE__ */ jsx5(Box5, { borderStyle: "single", borderTop: false, borderLeft: false, borderRight: false, borderColor: "red", marginBottom: 1 }),
544
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
545
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: "red", children: "Quick Start" }),
546
+ /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Ask me to read, edit, or" }),
547
+ /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "run commands in your code." })
548
+ ] })
549
+ ] })
550
+ ]
551
+ }
552
+ );
553
+ };
554
+
555
+ // src/ui/CommandPopup.tsx
556
+ import { Box as Box6, Text as Text6 } from "ink";
557
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
558
+ var COMMANDS = [
559
+ { name: "/help", desc: "Show available commands" },
560
+ { name: "/init", desc: "Initialize configuration" },
561
+ { name: "/config", desc: "View/edit configuration" },
562
+ { name: "/models", desc: "Select AI model" },
563
+ { name: "/mode", desc: "Set mode (auto/plan/safe)" },
564
+ { name: "/clear", desc: "Clear conversation" },
565
+ { name: "/cost", desc: "Show session cost" },
566
+ { name: "/usage", desc: "Show historical usage" },
567
+ { name: "/status", desc: "Show system status" },
568
+ { name: "/task", desc: "View current task" },
569
+ { name: "/tool", desc: "Execute tools manually" },
570
+ { name: "/sandbox", desc: "Toggle sandbox mode" },
571
+ { name: "/undo", desc: "Undo file changes" },
572
+ { name: "/doctor", desc: "Run diagnostics" },
573
+ { name: "/settings", desc: "View/edit settings" },
574
+ { name: "/exit", desc: "Exit the CLI" }
575
+ ];
576
+ var CommandPopup = ({ matches, selectedIndex }) => {
577
+ if (matches.length === 0) return null;
578
+ return /* @__PURE__ */ jsx6(
579
+ Box6,
580
+ {
581
+ flexDirection: "column",
582
+ borderStyle: "round",
583
+ borderColor: "gray",
584
+ paddingX: 1,
585
+ marginBottom: 0,
586
+ width: "100%",
587
+ children: matches.map((cmd, i) => {
588
+ const isSelected = i === selectedIndex;
589
+ return /* @__PURE__ */ jsxs6(Box6, { justifyContent: "space-between", children: [
590
+ /* @__PURE__ */ jsxs6(Text6, { color: isSelected ? "cyan" : "red", bold: isSelected, children: [
591
+ isSelected ? "> " : " ",
592
+ cmd.name
593
+ ] }),
594
+ /* @__PURE__ */ jsx6(Text6, { color: isSelected ? "white" : "gray", children: cmd.desc })
595
+ ] }, cmd.name);
596
+ })
597
+ }
598
+ );
599
+ };
600
+
601
+ // src/core/history.ts
602
+ import fs from "fs/promises";
603
+ import path from "path";
604
+ import os from "os";
605
+ var HistoryManager = class {
606
+ historyPath;
607
+ saveTimer = null;
608
+ constructor(customPath) {
609
+ this.historyPath = customPath || path.join(os.homedir(), ".obsidian", "history.json");
610
+ }
611
+ async load() {
612
+ try {
613
+ const data = await fs.readFile(this.historyPath, "utf-8");
614
+ const events = JSON.parse(data);
615
+ return Array.isArray(events) ? events : [];
616
+ } catch {
617
+ return [];
618
+ }
619
+ }
620
+ async save(events) {
621
+ if (this.saveTimer) clearTimeout(this.saveTimer);
622
+ this.saveTimer = setTimeout(async () => {
623
+ try {
624
+ const dir = path.dirname(this.historyPath);
625
+ await fs.mkdir(dir, { recursive: true });
626
+ await fs.writeFile(this.historyPath, JSON.stringify(events, null, 2));
627
+ } catch (error) {
628
+ console.error("Failed to save history:", error);
629
+ }
630
+ }, 500);
631
+ }
632
+ async clear() {
633
+ if (this.saveTimer) clearTimeout(this.saveTimer);
634
+ try {
635
+ await fs.writeFile(this.historyPath, JSON.stringify([], null, 2));
636
+ } catch {
637
+ }
638
+ }
639
+ };
640
+ var history = new HistoryManager();
641
+
642
+ // src/core/usage.ts
643
+ import fs2 from "fs/promises";
644
+ import path2 from "path";
645
+ import os2 from "os";
646
+ import { z } from "zod";
647
+ var UsageSchema = z.object({
648
+ totalSessions: z.number().default(0),
649
+ totalRequests: z.number().default(0),
650
+ totalInputTokens: z.number().default(0),
651
+ totalOutputTokens: z.number().default(0),
652
+ totalCost: z.number().default(0)
653
+ });
654
+ var MODEL_PRICES = {
655
+ // Claude 4.5 Family (2025-2026)
656
+ "claude-sonnet-4-5-20250929": { input: 3, output: 15 },
657
+ "claude-haiku-4-5-20251001": { input: 1, output: 5 },
658
+ "claude-opus-4-5-20251101": { input: 5, output: 25 },
659
+ // Claude 3.5 Family (backward compatibility)
660
+ "claude-3-5-sonnet": { input: 3, output: 15 },
661
+ "claude-3-5-haiku": { input: 0.25, output: 1.25 },
662
+ "claude-3-haiku": { input: 0.25, output: 1.25 },
663
+ "claude-3-opus": { input: 15, output: 75 },
664
+ "claude-3-sonnet": { input: 3, output: 15 }
665
+ };
666
+ var UsageTracker = class {
667
+ usagePath;
668
+ stats;
669
+ sessionCost = 0;
670
+ constructor(customPath) {
671
+ this.usagePath = customPath || path2.join(os2.homedir(), ".obsidian", "usage.json");
672
+ this.stats = UsageSchema.parse({});
673
+ }
674
+ async init() {
675
+ try {
676
+ const data = await fs2.readFile(this.usagePath, "utf-8");
677
+ this.stats = UsageSchema.parse(JSON.parse(data));
678
+ } catch {
679
+ await this.save();
680
+ }
681
+ }
682
+ async track(model, input, output) {
683
+ let prices = MODEL_PRICES[model];
684
+ if (!prices) {
685
+ const key = Object.keys(MODEL_PRICES).find((k) => model.includes(k));
686
+ prices = key ? MODEL_PRICES[key] : { input: 0, output: 0 };
687
+ }
688
+ const cost = input / 1e6 * prices.input + output / 1e6 * prices.output;
689
+ this.stats.totalRequests++;
690
+ this.stats.totalInputTokens += input;
691
+ this.stats.totalOutputTokens += output;
692
+ this.stats.totalCost += cost;
693
+ this.sessionCost += cost;
694
+ await this.save();
695
+ }
696
+ getSessionCost() {
697
+ return this.sessionCost;
698
+ }
699
+ async trackSession() {
700
+ this.stats.totalSessions++;
701
+ await this.save();
702
+ }
703
+ getStats() {
704
+ return this.stats;
705
+ }
706
+ async save() {
707
+ const dir = path2.dirname(this.usagePath);
708
+ await fs2.mkdir(dir, { recursive: true });
709
+ await fs2.writeFile(this.usagePath, JSON.stringify(this.stats, null, 2));
710
+ }
711
+ };
712
+ var usage = new UsageTracker();
713
+
714
+ // src/core/config.ts
715
+ import fs3 from "fs/promises";
716
+ import path3 from "path";
717
+ import os3 from "os";
718
+ import { z as z2 } from "zod";
719
+ import dotenv from "dotenv";
720
+ dotenv.config();
721
+ var ConfigSchema = z2.object({
722
+ apiKey: z2.string().optional(),
723
+ model: z2.string().default("claude-sonnet-4-5-20250929"),
724
+ workspaceRoot: z2.string().default(process.cwd()),
725
+ maxTokens: z2.number().default(8192),
726
+ language: z2.string().default("en")
727
+ });
728
+ var DEFAULT_CONFIG = {
729
+ model: "claude-3-5-sonnet",
730
+ maxTokens: 4096,
731
+ language: "en",
732
+ workspaceRoot: process.cwd()
733
+ };
734
+ var ConfigManager = class {
735
+ configPath;
736
+ cachedConfig = null;
737
+ constructor(customPath) {
738
+ this.configPath = customPath || path3.join(os3.homedir(), ".obsidian", "config.json");
739
+ }
740
+ async load() {
741
+ if (this.cachedConfig) return this.cachedConfig;
742
+ return this.reload();
743
+ }
744
+ async reload() {
745
+ let loadedConfig = DEFAULT_CONFIG;
746
+ try {
747
+ const data = await fs3.readFile(this.configPath, "utf-8");
748
+ const parsed = JSON.parse(data);
749
+ loadedConfig = { ...DEFAULT_CONFIG, ...parsed };
750
+ } catch {
751
+ }
752
+ const envKey = process.env.ANTHROPIC_API_KEY;
753
+ const finalConfig = ConfigSchema.parse({
754
+ ...loadedConfig,
755
+ apiKey: envKey || loadedConfig.apiKey
756
+ });
757
+ this.cachedConfig = finalConfig;
758
+ return finalConfig;
759
+ }
760
+ clearCache() {
761
+ this.cachedConfig = null;
762
+ }
763
+ async save(config2) {
764
+ const dir = path3.dirname(this.configPath);
765
+ await fs3.mkdir(dir, { recursive: true });
766
+ await fs3.writeFile(this.configPath, JSON.stringify(config2, null, 2));
767
+ this.clearCache();
768
+ }
769
+ async exists() {
770
+ try {
771
+ await fs3.access(this.configPath);
772
+ return true;
773
+ } catch {
774
+ return false;
775
+ }
776
+ }
777
+ getPath() {
778
+ return this.configPath;
779
+ }
780
+ };
781
+ var config = new ConfigManager();
782
+
783
+ // src/core/context.ts
784
+ import fs5 from "fs/promises";
785
+ import path5 from "path";
786
+
787
+ // src/core/settings.ts
788
+ import fs4 from "fs/promises";
789
+ import path4 from "path";
790
+ import { z as z3 } from "zod";
791
+ var SETTINGS_DIR = ".obsidian";
792
+ var SETTINGS_FILE = "settings.json";
793
+ var SettingsSchema = z3.object({
794
+ // Execution mode
795
+ mode: z3.enum(["auto", "plan", "safe"]).default("safe"),
796
+ // Auto-accept settings
797
+ autoAccept: z3.object({
798
+ enabled: z3.boolean().default(false),
799
+ readOperations: z3.boolean().default(true),
800
+ safeCommands: z3.boolean().default(true)
801
+ }).default({}),
802
+ // Tool permissions
803
+ permissions: z3.object({
804
+ // Patterns always allowed without prompt: "tool:pattern"
805
+ allow: z3.array(z3.string()).default([]),
806
+ // Patterns always blocked
807
+ deny: z3.array(z3.string()).default([])
808
+ }).default({}),
809
+ // UI preferences
810
+ ui: z3.object({
811
+ syntaxHighlight: z3.boolean().default(true),
812
+ diffColors: z3.boolean().default(true),
813
+ showLineNumbers: z3.boolean().default(true)
814
+ }).default({})
815
+ });
816
+ var DEFAULT_SETTINGS = {
817
+ mode: "safe",
818
+ autoAccept: {
819
+ enabled: false,
820
+ readOperations: false,
821
+ safeCommands: false
822
+ },
823
+ permissions: {
824
+ allow: [],
825
+ // Empty - user builds their own allow list
826
+ deny: []
827
+ },
828
+ ui: {
829
+ syntaxHighlight: true,
830
+ diffColors: true,
831
+ showLineNumbers: true
832
+ }
833
+ };
834
+ var SettingsManager = class {
835
+ settingsPath;
836
+ cached = null;
837
+ constructor() {
838
+ this.settingsPath = path4.join(process.cwd(), SETTINGS_DIR, SETTINGS_FILE);
839
+ }
840
+ async load() {
841
+ if (this.cached) return this.cached;
842
+ return this.reload();
843
+ }
844
+ async reload() {
845
+ try {
846
+ const data = await fs4.readFile(this.settingsPath, "utf-8");
847
+ const parsed = JSON.parse(data);
848
+ this.cached = SettingsSchema.parse({ ...DEFAULT_SETTINGS, ...parsed });
849
+ } catch {
850
+ this.cached = DEFAULT_SETTINGS;
851
+ await this.save(DEFAULT_SETTINGS);
852
+ }
853
+ return this.cached;
854
+ }
855
+ /**
856
+ * Add a permission to the allow list (called when user approves a command)
857
+ */
858
+ async addAllowedPermission(tool, command) {
859
+ const s = await this.load();
860
+ const pattern = `${tool}:${command}`;
861
+ if (!s.permissions.allow.includes(pattern)) {
862
+ s.permissions.allow.push(pattern);
863
+ await this.save({ permissions: s.permissions });
864
+ }
865
+ }
866
+ /**
867
+ * Add a permission to the deny list
868
+ */
869
+ async addDeniedPermission(tool, command) {
870
+ const s = await this.load();
871
+ const pattern = `${tool}:${command}`;
872
+ if (!s.permissions.deny.includes(pattern)) {
873
+ s.permissions.deny.push(pattern);
874
+ await this.save({ permissions: s.permissions });
875
+ }
876
+ }
877
+ async save(newSettings) {
878
+ const current = await this.load();
879
+ const merged = { ...current, ...newSettings };
880
+ if (newSettings.autoAccept) {
881
+ merged.autoAccept = { ...current.autoAccept, ...newSettings.autoAccept };
882
+ }
883
+ if (newSettings.permissions) {
884
+ merged.permissions = { ...current.permissions, ...newSettings.permissions };
885
+ }
886
+ if (newSettings.ui) {
887
+ merged.ui = { ...current.ui, ...newSettings.ui };
888
+ }
889
+ const validated = SettingsSchema.parse(merged);
890
+ const dir = path4.dirname(this.settingsPath);
891
+ await fs4.mkdir(dir, { recursive: true });
892
+ await fs4.writeFile(this.settingsPath, JSON.stringify(validated, null, 2));
893
+ this.cached = validated;
894
+ }
895
+ async get(key) {
896
+ const s = await this.load();
897
+ return s[key];
898
+ }
899
+ async set(key, value) {
900
+ await this.save({ [key]: value });
901
+ }
902
+ /**
903
+ * Check if a tool:command pattern is allowed
904
+ */
905
+ async isAllowed(tool, command) {
906
+ const s = await this.load();
907
+ const pattern = `${tool}:${command}`;
908
+ for (const deny of s.permissions.deny) {
909
+ if (this.matchPattern(pattern, deny)) {
910
+ return false;
911
+ }
912
+ }
913
+ for (const allow of s.permissions.allow) {
914
+ if (this.matchPattern(pattern, allow)) {
915
+ return true;
916
+ }
917
+ }
918
+ return false;
919
+ }
920
+ /**
921
+ * Check if a tool:command pattern is explicitly denied
922
+ */
923
+ async isDenied(tool, command) {
924
+ const s = await this.load();
925
+ const pattern = `${tool}:${command}`;
926
+ for (const deny of s.permissions.deny) {
927
+ if (this.matchPattern(pattern, deny)) {
928
+ return true;
929
+ }
930
+ }
931
+ return false;
932
+ }
933
+ /**
934
+ * Simple glob-like pattern matching
935
+ * Supports * as wildcard
936
+ */
937
+ matchPattern(value, pattern) {
938
+ const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
939
+ return new RegExp(`^${regex}$`).test(value);
940
+ }
941
+ clearCache() {
942
+ this.cached = null;
943
+ }
944
+ getPath() {
945
+ return this.settingsPath;
946
+ }
947
+ };
948
+ var settings = new SettingsManager();
949
+
950
+ // src/core/context.ts
951
+ var CONTEXT_DIR = ".obsidian";
952
+ var CONTEXT_FILE = "context.json";
953
+ function generateSessionId() {
954
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
955
+ }
956
+ function createEmptyContext() {
957
+ const now = (/* @__PURE__ */ new Date()).toISOString();
958
+ return {
959
+ session_id: generateSessionId(),
960
+ mode: "safe",
961
+ current_task: null,
962
+ files_read: [],
963
+ files_modified: [],
964
+ working_set: [],
965
+ last_action: null,
966
+ created_at: now,
967
+ updated_at: now
968
+ };
969
+ }
970
+ var ContextManager = class {
971
+ ctx = createEmptyContext();
972
+ contextPath;
973
+ constructor() {
974
+ this.contextPath = path5.join(process.cwd(), CONTEXT_DIR, CONTEXT_FILE);
975
+ }
976
+ async init() {
977
+ const dir = path5.join(process.cwd(), CONTEXT_DIR);
978
+ try {
979
+ await fs5.mkdir(dir, { recursive: true });
980
+ await this.load();
981
+ } catch {
982
+ this.ctx = createEmptyContext();
983
+ await this.save();
984
+ }
985
+ }
986
+ async load() {
987
+ try {
988
+ const data = await fs5.readFile(this.contextPath, "utf-8");
989
+ this.ctx = JSON.parse(data);
990
+ } catch {
991
+ this.ctx = createEmptyContext();
992
+ }
993
+ }
994
+ async save() {
995
+ this.ctx.updated_at = (/* @__PURE__ */ new Date()).toISOString();
996
+ await fs5.writeFile(this.contextPath, JSON.stringify(this.ctx, null, 2));
997
+ }
998
+ // Getters
999
+ get() {
1000
+ return { ...this.ctx };
1001
+ }
1002
+ getMode() {
1003
+ return this.ctx.mode;
1004
+ }
1005
+ async syncModeFromSettings() {
1006
+ const s = await settings.load();
1007
+ this.ctx.mode = s.mode;
1008
+ }
1009
+ getCurrentTask() {
1010
+ return this.ctx.current_task;
1011
+ }
1012
+ getWorkingSet() {
1013
+ return [...this.ctx.working_set];
1014
+ }
1015
+ // Setters
1016
+ async setMode(mode) {
1017
+ this.ctx.mode = mode;
1018
+ await settings.set("mode", mode);
1019
+ await this.save();
1020
+ }
1021
+ async setTask(task) {
1022
+ this.ctx.current_task = task;
1023
+ await this.save();
1024
+ }
1025
+ // Tracking
1026
+ async trackRead(filePath) {
1027
+ const normalized = path5.relative(process.cwd(), path5.resolve(filePath));
1028
+ if (!this.ctx.files_read.includes(normalized)) {
1029
+ this.ctx.files_read.push(normalized);
1030
+ }
1031
+ if (!this.ctx.working_set.includes(normalized)) {
1032
+ this.ctx.working_set.push(normalized);
1033
+ }
1034
+ await this.save();
1035
+ }
1036
+ async trackModified(filePath) {
1037
+ const normalized = path5.relative(process.cwd(), path5.resolve(filePath));
1038
+ if (!this.ctx.files_modified.includes(normalized)) {
1039
+ this.ctx.files_modified.push(normalized);
1040
+ }
1041
+ if (!this.ctx.working_set.includes(normalized)) {
1042
+ this.ctx.working_set.push(normalized);
1043
+ }
1044
+ await this.save();
1045
+ }
1046
+ async setLastAction(action) {
1047
+ this.ctx.last_action = action;
1048
+ await this.save();
1049
+ }
1050
+ // Reset
1051
+ async reset() {
1052
+ this.ctx = createEmptyContext();
1053
+ await this.save();
1054
+ }
1055
+ async clearWorkingSet() {
1056
+ this.ctx.working_set = [];
1057
+ await this.save();
1058
+ }
1059
+ // Summary for LLM
1060
+ getSummary() {
1061
+ const lines = [];
1062
+ if (this.ctx.current_task) {
1063
+ lines.push(`Task: ${this.ctx.current_task}`);
1064
+ }
1065
+ if (this.ctx.working_set.length > 0) {
1066
+ lines.push(`Working set: ${this.ctx.working_set.slice(-5).join(", ")}`);
1067
+ }
1068
+ if (this.ctx.files_modified.length > 0) {
1069
+ lines.push(`Modified: ${this.ctx.files_modified.slice(-3).join(", ")}`);
1070
+ }
1071
+ return lines.join("\n");
1072
+ }
1073
+ };
1074
+ var context = new ContextManager();
1075
+
1076
+ // src/core/llm.ts
1077
+ import Anthropic from "@anthropic-ai/sdk";
1078
+
1079
+ // src/core/tools.ts
1080
+ import { exec as exec2 } from "child_process";
1081
+ import { promisify as promisify2 } from "util";
1082
+ import fs8 from "fs/promises";
1083
+ import path8 from "path";
1084
+
1085
+ // src/core/auditor.ts
1086
+ import path6 from "path";
1087
+ import fs6 from "fs/promises";
1088
+ var BLOCKED_PATTERNS = [
1089
+ "rm -rf /",
1090
+ "rm -fr /",
1091
+ ":(){:|:&};:",
1092
+ // Fork bomb
1093
+ "> /dev/sda",
1094
+ // Disk overwrite
1095
+ "mkfs",
1096
+ "dd if=",
1097
+ "chmod -R 777",
1098
+ ":(){ :|:& };:",
1099
+ "curl | sh",
1100
+ // Pipe to shell
1101
+ "wget | sh",
1102
+ "curl | bash",
1103
+ "wget | bash"
1104
+ ];
1105
+ var APPROVAL_PATTERNS = [
1106
+ { pattern: "rm -rf", reason: "Recursive delete operation" },
1107
+ { pattern: "rm -r", reason: "Recursive delete operation" },
1108
+ { pattern: "git push --force", reason: "Force push to remote" },
1109
+ { pattern: "git reset --hard", reason: "Hard reset (loses changes)" },
1110
+ { pattern: "npm publish", reason: "Publishing to npm registry" },
1111
+ { pattern: "docker rm", reason: "Removing Docker containers" },
1112
+ { pattern: "DROP TABLE", reason: "SQL table deletion" },
1113
+ { pattern: "DROP DATABASE", reason: "SQL database deletion" },
1114
+ { pattern: "truncate", reason: "Truncating data" }
1115
+ ];
1116
+ var Auditor = class {
1117
+ workspaceRoot;
1118
+ constructor(root = process.cwd()) {
1119
+ this.workspaceRoot = path6.resolve(root);
1120
+ }
1121
+ async checkCommand(command) {
1122
+ const lowerCommand = command.toLowerCase();
1123
+ if (BLOCKED_PATTERNS.some((p) => command.includes(p))) {
1124
+ return {
1125
+ approved: false,
1126
+ reason: "Detected destructive command pattern",
1127
+ isCritical: true
1128
+ };
1129
+ }
1130
+ if (await settings.isDenied("bash", command)) {
1131
+ return {
1132
+ approved: false,
1133
+ reason: "Command blocked by settings",
1134
+ isCritical: false
1135
+ };
1136
+ }
1137
+ if (await settings.isAllowed("bash", command)) {
1138
+ return {
1139
+ approved: true,
1140
+ autoApproved: true
1141
+ };
1142
+ }
1143
+ for (const { pattern, reason } of APPROVAL_PATTERNS) {
1144
+ if (lowerCommand.includes(pattern.toLowerCase())) {
1145
+ return {
1146
+ approved: true,
1147
+ requiresApproval: true,
1148
+ reason
1149
+ };
1150
+ }
1151
+ }
1152
+ const s = await settings.load();
1153
+ if (s.mode === "safe") {
1154
+ return {
1155
+ approved: true,
1156
+ requiresApproval: true,
1157
+ reason: "Safe mode requires approval for all commands"
1158
+ };
1159
+ }
1160
+ return { approved: true };
1161
+ }
1162
+ checkPath(filePath) {
1163
+ const resolved = path6.resolve(this.workspaceRoot, filePath);
1164
+ if (!resolved.startsWith(this.workspaceRoot)) {
1165
+ return { approved: false, reason: `Path outside workspace: ${filePath}`, isCritical: true };
1166
+ }
1167
+ return { approved: true };
1168
+ }
1169
+ async checkFileEdit(filePath) {
1170
+ const pathCheck = this.checkPath(filePath);
1171
+ if (!pathCheck.approved) return pathCheck;
1172
+ try {
1173
+ await fs6.access(filePath);
1174
+ return { approved: true };
1175
+ } catch {
1176
+ return { approved: false, reason: `File not found: ${filePath}`, isCritical: false };
1177
+ }
1178
+ }
1179
+ };
1180
+ var auditor = new Auditor();
1181
+
1182
+ // src/core/sandbox.ts
1183
+ import { exec } from "child_process";
1184
+ import { promisify } from "util";
1185
+ import os4 from "os";
1186
+ var execAsync = promisify(exec);
1187
+ var DEFAULT_SANDBOX_CONFIG = {
1188
+ mode: "local",
1189
+ allowedDomains: [
1190
+ "*.github.com",
1191
+ "*.npmjs.org",
1192
+ "*.npmjs.com",
1193
+ "api.anthropic.com",
1194
+ "registry.npmjs.org"
1195
+ ],
1196
+ deniedDomains: [],
1197
+ denyRead: [
1198
+ "~/.ssh",
1199
+ "~/.aws",
1200
+ "~/.config/gcloud",
1201
+ "~/.kube",
1202
+ "~/.gnupg"
1203
+ ],
1204
+ allowWrite: [
1205
+ ".",
1206
+ // Current workspace
1207
+ "/tmp"
1208
+ ],
1209
+ denyWrite: [
1210
+ ".env",
1211
+ ".env.*",
1212
+ "*.key",
1213
+ "*.pem",
1214
+ ".git/config"
1215
+ ]
1216
+ };
1217
+ var SandboxExecutor = class {
1218
+ initialized = false;
1219
+ mode = "local";
1220
+ sandboxManager = null;
1221
+ config = DEFAULT_SANDBOX_CONFIG;
1222
+ /**
1223
+ * Initialize sandbox with configuration
1224
+ */
1225
+ async initialize() {
1226
+ try {
1227
+ const cfg = await config.load();
1228
+ this.mode = cfg.executionMode || "local";
1229
+ this.config = {
1230
+ ...DEFAULT_SANDBOX_CONFIG,
1231
+ ...cfg.sandbox || {}
1232
+ };
1233
+ if (this.mode !== "sandbox") {
1234
+ this.initialized = true;
1235
+ return true;
1236
+ }
1237
+ try {
1238
+ const { SandboxManager } = await import("@anthropic-ai/sandbox-runtime");
1239
+ const runtimeConfig = {
1240
+ network: {
1241
+ allowedDomains: this.config.allowedDomains,
1242
+ deniedDomains: this.config.deniedDomains
1243
+ },
1244
+ filesystem: {
1245
+ denyRead: this.config.denyRead,
1246
+ allowWrite: this.config.allowWrite,
1247
+ denyWrite: this.config.denyWrite
1248
+ }
1249
+ };
1250
+ await SandboxManager.initialize(runtimeConfig);
1251
+ this.sandboxManager = SandboxManager;
1252
+ this.initialized = true;
1253
+ bus.emitAgent({
1254
+ type: "thought",
1255
+ content: "[SANDBOX] Initialized with OS-level isolation"
1256
+ });
1257
+ return true;
1258
+ } catch (importError) {
1259
+ bus.emitAgent({
1260
+ type: "error",
1261
+ message: `Sandbox runtime unavailable: ${importError.message}. Using local mode.`
1262
+ });
1263
+ this.mode = "local";
1264
+ this.initialized = true;
1265
+ return true;
1266
+ }
1267
+ } catch (error) {
1268
+ bus.emitAgent({
1269
+ type: "error",
1270
+ message: `Sandbox initialization failed: ${error.message}`
1271
+ });
1272
+ return false;
1273
+ }
1274
+ }
1275
+ /**
1276
+ * Wrap a command with sandbox protection
1277
+ */
1278
+ async wrapCommand(command) {
1279
+ if (!this.initialized) {
1280
+ await this.initialize();
1281
+ }
1282
+ if (this.mode === "local") {
1283
+ return command;
1284
+ }
1285
+ if (this.sandboxManager) {
1286
+ try {
1287
+ return await this.sandboxManager.wrapWithSandbox(command);
1288
+ } catch (error) {
1289
+ }
1290
+ }
1291
+ return this.wrapWithNativeSandbox(command);
1292
+ }
1293
+ /**
1294
+ * Wrap command with native OS sandbox (macOS sandbox-exec or Linux firejail)
1295
+ */
1296
+ async wrapWithNativeSandbox(command) {
1297
+ const platform = os4.platform();
1298
+ if (platform === "darwin") {
1299
+ const profile = `
1300
+ (version 1)
1301
+ (allow default)
1302
+ (deny network*)
1303
+ (allow network-outbound (remote tcp "*:80" "*:443"))
1304
+ (deny file-read* (subpath "${os4.homedir()}/.ssh"))
1305
+ (deny file-read* (subpath "${os4.homedir()}/.aws"))
1306
+ (deny file-read* (subpath "${os4.homedir()}/.gnupg"))
1307
+ (deny file-write* (literal "${process.cwd()}/.env"))
1308
+ `.trim();
1309
+ const escapedCommand = command.replace(/"/g, '\\"');
1310
+ return `sandbox-exec -p '${profile}' bash -c "${escapedCommand}"`;
1311
+ }
1312
+ if (platform === "linux") {
1313
+ try {
1314
+ await execAsync("which firejail");
1315
+ const escapedCommand = command.replace(/'/g, "'\\''");
1316
+ return `firejail --net=none --blacklist=~/.ssh --blacklist=~/.aws --blacklist=~/.gnupg bash -c '${escapedCommand}'`;
1317
+ } catch {
1318
+ }
1319
+ }
1320
+ return command;
1321
+ }
1322
+ /**
1323
+ * Get current execution mode
1324
+ */
1325
+ getMode() {
1326
+ return this.mode;
1327
+ }
1328
+ /**
1329
+ * Set execution mode
1330
+ */
1331
+ async setMode(mode) {
1332
+ this.mode = mode;
1333
+ if (mode === "sandbox" && !this.sandboxManager) {
1334
+ await this.initialize();
1335
+ }
1336
+ bus.emitAgent({
1337
+ type: "thought",
1338
+ content: `[SANDBOX] Execution mode set to: ${mode}`
1339
+ });
1340
+ }
1341
+ /**
1342
+ * Get current sandbox configuration
1343
+ */
1344
+ getConfig() {
1345
+ return { ...this.config };
1346
+ }
1347
+ /**
1348
+ * Update sandbox configuration
1349
+ */
1350
+ updateConfig(updates) {
1351
+ this.config = { ...this.config, ...updates };
1352
+ }
1353
+ /**
1354
+ * Reset and cleanup sandbox resources
1355
+ */
1356
+ async reset() {
1357
+ if (this.sandboxManager) {
1358
+ try {
1359
+ await this.sandboxManager.reset();
1360
+ } catch {
1361
+ }
1362
+ }
1363
+ this.initialized = false;
1364
+ this.sandboxManager = null;
1365
+ }
1366
+ /**
1367
+ * Check if sandbox mode is available on this system
1368
+ */
1369
+ async isAvailable() {
1370
+ try {
1371
+ await import("@anthropic-ai/sandbox-runtime");
1372
+ return true;
1373
+ } catch {
1374
+ return false;
1375
+ }
1376
+ }
1377
+ };
1378
+ var sandbox = new SandboxExecutor();
1379
+
1380
+ // src/core/undo.ts
1381
+ import { PrismaClient } from "@prisma/client";
1382
+ import fs7 from "fs/promises";
1383
+ import path7 from "path";
1384
+ var UndoManager = class {
1385
+ prisma = null;
1386
+ sessionId = null;
1387
+ changes = [];
1388
+ // In-memory stack for fast access
1389
+ dbEnabled = false;
1390
+ async init(sessionId) {
1391
+ this.sessionId = sessionId;
1392
+ try {
1393
+ this.prisma = new PrismaClient();
1394
+ await this.prisma.$connect();
1395
+ this.dbEnabled = true;
1396
+ const dbChanges = await this.prisma.fileChange.findMany({
1397
+ where: { sessionId, undone: false },
1398
+ orderBy: { createdAt: "desc" },
1399
+ take: 50
1400
+ });
1401
+ this.changes = dbChanges.map((c) => ({
1402
+ id: c.id,
1403
+ filePath: c.filePath,
1404
+ operation: c.operation,
1405
+ beforeContent: c.beforeContent,
1406
+ afterContent: c.afterContent,
1407
+ timestamp: c.createdAt,
1408
+ undone: c.undone
1409
+ }));
1410
+ } catch {
1411
+ this.dbEnabled = false;
1412
+ this.changes = [];
1413
+ }
1414
+ }
1415
+ async recordChange(filePath, operation, beforeContent, afterContent) {
1416
+ const id = `chg_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
1417
+ const change = {
1418
+ id,
1419
+ filePath: path7.relative(process.cwd(), path7.resolve(filePath)),
1420
+ operation,
1421
+ beforeContent,
1422
+ afterContent,
1423
+ timestamp: /* @__PURE__ */ new Date(),
1424
+ undone: false
1425
+ };
1426
+ this.changes.unshift(change);
1427
+ if (this.dbEnabled && this.prisma && this.sessionId) {
1428
+ try {
1429
+ await this.prisma.fileChange.create({
1430
+ data: {
1431
+ id,
1432
+ sessionId: this.sessionId,
1433
+ filePath: change.filePath,
1434
+ operation,
1435
+ beforeContent,
1436
+ afterContent
1437
+ }
1438
+ });
1439
+ } catch {
1440
+ }
1441
+ }
1442
+ if (this.changes.length > 100) {
1443
+ this.changes = this.changes.slice(0, 100);
1444
+ }
1445
+ return id;
1446
+ }
1447
+ async undo(count = 1) {
1448
+ const toUndo = this.changes.filter((c) => !c.undone).slice(0, count);
1449
+ if (toUndo.length === 0) {
1450
+ return { success: false, message: "Nothing to undo" };
1451
+ }
1452
+ const results = [];
1453
+ for (const change of toUndo) {
1454
+ try {
1455
+ const fullPath = path7.resolve(process.cwd(), change.filePath);
1456
+ switch (change.operation) {
1457
+ case "create":
1458
+ await fs7.unlink(fullPath);
1459
+ results.push(`Deleted: ${change.filePath}`);
1460
+ break;
1461
+ case "edit":
1462
+ if (change.beforeContent !== null) {
1463
+ await fs7.writeFile(fullPath, change.beforeContent, "utf-8");
1464
+ results.push(`Restored: ${change.filePath}`);
1465
+ }
1466
+ break;
1467
+ case "delete":
1468
+ if (change.beforeContent !== null) {
1469
+ await fs7.mkdir(path7.dirname(fullPath), { recursive: true });
1470
+ await fs7.writeFile(fullPath, change.beforeContent, "utf-8");
1471
+ results.push(`Restored: ${change.filePath}`);
1472
+ }
1473
+ break;
1474
+ }
1475
+ change.undone = true;
1476
+ if (this.dbEnabled && this.prisma) {
1477
+ await this.prisma.fileChange.update({
1478
+ where: { id: change.id },
1479
+ data: { undone: true }
1480
+ }).catch(() => {
1481
+ });
1482
+ }
1483
+ } catch (error) {
1484
+ results.push(`Failed: ${change.filePath} - ${error.message}`);
1485
+ }
1486
+ }
1487
+ return {
1488
+ success: true,
1489
+ message: results.join("\n")
1490
+ };
1491
+ }
1492
+ getHistory(limit = 10) {
1493
+ return this.changes.filter((c) => !c.undone).slice(0, limit);
1494
+ }
1495
+ async close() {
1496
+ if (this.prisma) {
1497
+ await this.prisma.$disconnect();
1498
+ }
1499
+ }
1500
+ };
1501
+ var undo = new UndoManager();
1502
+
1503
+ // src/core/tools.ts
1504
+ var execAsync2 = promisify2(exec2);
1505
+ var MAX_OUTPUT_LENGTH = 1e4;
1506
+ var MAX_FILE_READ_LINES = 500;
1507
+ var IGNORED_DIRS = ["node_modules", ".git", "dist", ".next", "__pycache__", ".cache", "coverage"];
1508
+ var APPROVAL_TIMEOUT = 3e4;
1509
+ function truncateOutput(output, maxLength = MAX_OUTPUT_LENGTH) {
1510
+ if (output.length <= maxLength) return output;
1511
+ const truncated = output.slice(0, maxLength);
1512
+ const remaining = output.length - maxLength;
1513
+ return `${truncated}
1514
+
1515
+ ... [TRUNCATED: ${remaining} more characters]`;
1516
+ }
1517
+ var pendingApprovals = /* @__PURE__ */ new Map();
1518
+ bus.on("user", (event) => {
1519
+ if (event.type === "approval_response") {
1520
+ const pending = pendingApprovals.get(event.requestId);
1521
+ if (pending) {
1522
+ clearTimeout(pending.timeout);
1523
+ pendingApprovals.delete(event.requestId);
1524
+ pending.resolve(event.approved);
1525
+ }
1526
+ }
1527
+ });
1528
+ async function requestApproval(command, reason) {
1529
+ const requestId = `approval_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1530
+ return new Promise((resolve) => {
1531
+ const timeout = setTimeout(() => {
1532
+ pendingApprovals.delete(requestId);
1533
+ bus.emitAgent({
1534
+ type: "error",
1535
+ message: "Approval request timed out. Command denied."
1536
+ });
1537
+ resolve(false);
1538
+ }, APPROVAL_TIMEOUT);
1539
+ pendingApprovals.set(requestId, { resolve, timeout });
1540
+ bus.emitAgent({
1541
+ type: "approval_request",
1542
+ requestId,
1543
+ context: `Command: ${command}
1544
+ Reason: ${reason}`
1545
+ });
1546
+ });
1547
+ }
1548
+ var BashTool = {
1549
+ name: "bash",
1550
+ description: "Execute shell commands in the workspace",
1551
+ async execute(args) {
1552
+ const command = args.command;
1553
+ if (!command) {
1554
+ return { success: false, error: "No command provided" };
1555
+ }
1556
+ const audit = await auditor.checkCommand(command);
1557
+ if (!audit.approved) {
1558
+ return {
1559
+ success: false,
1560
+ error: `Security violation: ${audit.reason}`
1561
+ };
1562
+ }
1563
+ if (audit.requiresApproval && !audit.autoApproved) {
1564
+ const approved = await requestApproval(command, audit.reason || "Potentially dangerous operation");
1565
+ if (!approved) {
1566
+ await settings.addDeniedPermission("bash", command);
1567
+ return {
1568
+ success: false,
1569
+ error: "Command rejected by user"
1570
+ };
1571
+ }
1572
+ await settings.addAllowedPermission("bash", command);
1573
+ }
1574
+ try {
1575
+ const execCommand = await sandbox.wrapCommand(command);
1576
+ const { stdout, stderr } = await execAsync2(execCommand, {
1577
+ cwd: process.cwd(),
1578
+ timeout: 3e4,
1579
+ // 30 second timeout
1580
+ maxBuffer: 1024 * 1024
1581
+ // 1MB buffer (reduced from 10MB)
1582
+ });
1583
+ const output = stdout || stderr || "Command executed successfully";
1584
+ return {
1585
+ success: true,
1586
+ output: truncateOutput(output)
1587
+ };
1588
+ } catch (error) {
1589
+ return {
1590
+ success: false,
1591
+ error: error.message || "Command execution failed"
1592
+ };
1593
+ }
1594
+ }
1595
+ };
1596
+ var ReadTool = {
1597
+ name: "read",
1598
+ description: "Read file contents from the workspace",
1599
+ async execute(args) {
1600
+ const filePath = args.path;
1601
+ if (!filePath) {
1602
+ return { success: false, error: "No file path provided" };
1603
+ }
1604
+ const pathCheck = auditor.checkPath(filePath);
1605
+ if (!pathCheck.approved) {
1606
+ return {
1607
+ success: false,
1608
+ error: pathCheck.reason
1609
+ };
1610
+ }
1611
+ if (IGNORED_DIRS.some((dir) => filePath.includes(`/${dir}/`) || filePath.startsWith(`${dir}/`))) {
1612
+ return {
1613
+ success: false,
1614
+ error: `Cannot read from ignored directory. Paths containing ${IGNORED_DIRS.join(", ")} are blocked to prevent context explosion.`
1615
+ };
1616
+ }
1617
+ try {
1618
+ const fullPath = path8.resolve(process.cwd(), filePath);
1619
+ const content = await fs8.readFile(fullPath, "utf-8");
1620
+ const lines = content.split("\n");
1621
+ const limitedLines = lines.slice(0, MAX_FILE_READ_LINES);
1622
+ const numbered = limitedLines.map((line, i) => `${String(i + 1).padStart(4)} | ${line}`).join("\n");
1623
+ const truncationNote = lines.length > MAX_FILE_READ_LINES ? `
1624
+
1625
+ ... [TRUNCATED: ${lines.length - MAX_FILE_READ_LINES} more lines. Use offset parameter to read more.]` : "";
1626
+ await context.trackRead(filePath);
1627
+ return {
1628
+ success: true,
1629
+ output: truncateOutput(`File: ${filePath} (${lines.length} lines)
1630
+ ${"=".repeat(60)}
1631
+ ${numbered}${truncationNote}`)
1632
+ };
1633
+ } catch (error) {
1634
+ return {
1635
+ success: false,
1636
+ error: `Failed to read file: ${error.message}`
1637
+ };
1638
+ }
1639
+ }
1640
+ };
1641
+ var WriteTool = {
1642
+ name: "write",
1643
+ description: "Create new files in the workspace",
1644
+ async execute(args) {
1645
+ const filePath = args.path;
1646
+ const content = args.content;
1647
+ if (!filePath) {
1648
+ return { success: false, error: "No file path provided" };
1649
+ }
1650
+ if (content === void 0) {
1651
+ return { success: false, error: "No content provided" };
1652
+ }
1653
+ const pathCheck = auditor.checkPath(filePath);
1654
+ if (!pathCheck.approved) {
1655
+ return {
1656
+ success: false,
1657
+ error: pathCheck.reason
1658
+ };
1659
+ }
1660
+ try {
1661
+ const fullPath = path8.resolve(process.cwd(), filePath);
1662
+ try {
1663
+ await fs8.access(fullPath);
1664
+ return {
1665
+ success: false,
1666
+ error: `File already exists: ${filePath}. Use 'edit' tool to modify.`
1667
+ };
1668
+ } catch {
1669
+ }
1670
+ await fs8.mkdir(path8.dirname(fullPath), { recursive: true });
1671
+ await fs8.writeFile(fullPath, content, "utf-8");
1672
+ await context.trackModified(filePath);
1673
+ await undo.recordChange(filePath, "create", null, content);
1674
+ return {
1675
+ success: true,
1676
+ output: `Created file: ${filePath} (${content.length} bytes)`
1677
+ };
1678
+ } catch (error) {
1679
+ return {
1680
+ success: false,
1681
+ error: `Failed to write file: ${error.message}`
1682
+ };
1683
+ }
1684
+ }
1685
+ };
1686
+ var EditTool = {
1687
+ name: "edit",
1688
+ description: "Edit existing files using search and replace",
1689
+ async execute(args) {
1690
+ const filePath = args.path;
1691
+ const search = args.search;
1692
+ const replace = args.replace;
1693
+ if (!filePath) {
1694
+ return { success: false, error: "No file path provided" };
1695
+ }
1696
+ if (!search) {
1697
+ return { success: false, error: "No search string provided" };
1698
+ }
1699
+ if (replace === void 0) {
1700
+ return { success: false, error: "No replacement string provided" };
1701
+ }
1702
+ const fileCheck = await auditor.checkFileEdit(filePath);
1703
+ if (!fileCheck.approved) {
1704
+ return {
1705
+ success: false,
1706
+ error: fileCheck.reason
1707
+ };
1708
+ }
1709
+ try {
1710
+ const fullPath = path8.resolve(process.cwd(), filePath);
1711
+ const original = await fs8.readFile(fullPath, "utf-8");
1712
+ if (!original.includes(search)) {
1713
+ return {
1714
+ success: false,
1715
+ error: `Search string not found in ${filePath}`
1716
+ };
1717
+ }
1718
+ const modified = original.replace(search, replace);
1719
+ const diffPreview = generateDiffPreview(search, replace);
1720
+ await fs8.writeFile(fullPath, modified, "utf-8");
1721
+ const originalLines = original.split("\n").length;
1722
+ const modifiedLines = modified.split("\n").length;
1723
+ const delta = modifiedLines - originalLines;
1724
+ await context.trackModified(filePath);
1725
+ await undo.recordChange(filePath, "edit", original, modified);
1726
+ return {
1727
+ success: true,
1728
+ output: `Edited ${filePath}:
1729
+ ${diffPreview}
1730
+ Lines: ${originalLines} -> ${modifiedLines} (${delta >= 0 ? "+" : ""}${delta})`
1731
+ };
1732
+ } catch (error) {
1733
+ return {
1734
+ success: false,
1735
+ error: `Failed to edit file: ${error.message}`
1736
+ };
1737
+ }
1738
+ }
1739
+ };
1740
+ function generateDiffPreview(search, replace) {
1741
+ const searchLines = search.split("\n").slice(0, 5);
1742
+ const replaceLines = replace.split("\n").slice(0, 5);
1743
+ let preview = "";
1744
+ for (const line of searchLines) {
1745
+ preview += `- ${line}
1746
+ `;
1747
+ }
1748
+ if (search.split("\n").length > 5) {
1749
+ preview += `- ... (${search.split("\n").length - 5} more lines)
1750
+ `;
1751
+ }
1752
+ for (const line of replaceLines) {
1753
+ preview += `+ ${line}
1754
+ `;
1755
+ }
1756
+ if (replace.split("\n").length > 5) {
1757
+ preview += `+ ... (${replace.split("\n").length - 5} more lines)
1758
+ `;
1759
+ }
1760
+ return preview.trim();
1761
+ }
1762
+ var ListTool = {
1763
+ name: "list",
1764
+ description: "List files and directories in the workspace",
1765
+ async execute(args) {
1766
+ const dirPath = args.path || ".";
1767
+ const pathCheck = auditor.checkPath(dirPath);
1768
+ if (!pathCheck.approved) {
1769
+ return {
1770
+ success: false,
1771
+ error: pathCheck.reason
1772
+ };
1773
+ }
1774
+ try {
1775
+ const fullPath = path8.resolve(process.cwd(), dirPath);
1776
+ const entries = await fs8.readdir(fullPath, { withFileTypes: true });
1777
+ const filtered = entries.filter(
1778
+ (entry) => !IGNORED_DIRS.includes(entry.name) && !entry.name.startsWith(".")
1779
+ );
1780
+ const formatted = filtered.map((entry) => {
1781
+ const prefix = entry.isDirectory() ? "[DIR]" : "[FILE]";
1782
+ return `${prefix} ${entry.name}`;
1783
+ }).join("\n");
1784
+ const hiddenCount = entries.length - filtered.length;
1785
+ const hiddenNote = hiddenCount > 0 ? `
1786
+
1787
+ (${hiddenCount} hidden: node_modules, .git, etc.)` : "";
1788
+ return {
1789
+ success: true,
1790
+ output: `Directory: ${dirPath}
1791
+ ${"=".repeat(60)}
1792
+ ${formatted}${hiddenNote}`
1793
+ };
1794
+ } catch (error) {
1795
+ return {
1796
+ success: false,
1797
+ error: `Failed to list directory: ${error.message}`
1798
+ };
1799
+ }
1800
+ }
1801
+ };
1802
+ var GrepTool = {
1803
+ name: "grep",
1804
+ description: "Search for patterns in files using regex",
1805
+ async execute(args) {
1806
+ const pattern = args.pattern;
1807
+ const searchPath = args.path || ".";
1808
+ const maxResults = args.limit || 50;
1809
+ if (!pattern) {
1810
+ return { success: false, error: "No search pattern provided" };
1811
+ }
1812
+ const pathCheck = auditor.checkPath(searchPath);
1813
+ if (!pathCheck.approved) {
1814
+ return {
1815
+ success: false,
1816
+ error: pathCheck.reason
1817
+ };
1818
+ }
1819
+ try {
1820
+ const fullPath = path8.resolve(process.cwd(), searchPath);
1821
+ const results = [];
1822
+ await searchDirectory(fullPath, pattern, results, maxResults);
1823
+ if (results.length === 0) {
1824
+ return {
1825
+ success: true,
1826
+ output: `No matches found for: ${pattern}`
1827
+ };
1828
+ }
1829
+ return {
1830
+ success: true,
1831
+ output: truncateOutput(`Found ${results.length} matches for "${pattern}":
1832
+ ${"=".repeat(60)}
1833
+ ${results.join("\n")}`)
1834
+ };
1835
+ } catch (error) {
1836
+ return {
1837
+ success: false,
1838
+ error: `Search failed: ${error.message}`
1839
+ };
1840
+ }
1841
+ }
1842
+ };
1843
+ async function searchDirectory(dir, pattern, results, maxResults, depth = 0) {
1844
+ if (results.length >= maxResults || depth > 10) return;
1845
+ try {
1846
+ const entries = await fs8.readdir(dir, { withFileTypes: true });
1847
+ const regex = new RegExp(pattern, "gi");
1848
+ for (const entry of entries) {
1849
+ if (results.length >= maxResults) break;
1850
+ const fullPath = path8.join(dir, entry.name);
1851
+ const relativePath = path8.relative(process.cwd(), fullPath);
1852
+ if (entry.name.startsWith(".") || IGNORED_DIRS.includes(entry.name)) {
1853
+ continue;
1854
+ }
1855
+ if (entry.isDirectory()) {
1856
+ await searchDirectory(fullPath, pattern, results, maxResults, depth + 1);
1857
+ } else if (entry.isFile()) {
1858
+ const ext = path8.extname(entry.name).toLowerCase();
1859
+ const textExtensions = [".ts", ".tsx", ".js", ".jsx", ".json", ".md", ".txt", ".yaml", ".yml", ".css", ".html", ".sh"];
1860
+ if (textExtensions.includes(ext) || ext === "") {
1861
+ try {
1862
+ const content = await fs8.readFile(fullPath, "utf-8");
1863
+ const lines = content.split("\n");
1864
+ for (let i = 0; i < lines.length && results.length < maxResults; i++) {
1865
+ if (regex.test(lines[i])) {
1866
+ results.push(`${relativePath}:${i + 1}: ${lines[i].trim().slice(0, 100)}`);
1867
+ }
1868
+ regex.lastIndex = 0;
1869
+ }
1870
+ } catch {
1871
+ }
1872
+ }
1873
+ }
1874
+ }
1875
+ } catch {
1876
+ }
1877
+ }
1878
+ var GlobTool = {
1879
+ name: "glob",
1880
+ description: "Find files matching a glob pattern (e.g., **/*.ts, src/**/*.tsx)",
1881
+ async execute(args) {
1882
+ const pattern = args.pattern;
1883
+ const basePath = args.path || ".";
1884
+ if (!pattern) {
1885
+ return { success: false, error: "No pattern provided" };
1886
+ }
1887
+ try {
1888
+ const results = [];
1889
+ const fullBase = path8.resolve(process.cwd(), basePath);
1890
+ await globSearch(fullBase, pattern, results, 100);
1891
+ if (results.length === 0) {
1892
+ return {
1893
+ success: true,
1894
+ output: `No files matching: ${pattern}`
1895
+ };
1896
+ }
1897
+ return {
1898
+ success: true,
1899
+ output: truncateOutput(`Found ${results.length} files:
1900
+ ${results.join("\n")}`)
1901
+ };
1902
+ } catch (error) {
1903
+ return {
1904
+ success: false,
1905
+ error: `Glob failed: ${error.message}`
1906
+ };
1907
+ }
1908
+ }
1909
+ };
1910
+ async function globSearch(dir, pattern, results, maxResults, depth = 0) {
1911
+ if (results.length >= maxResults || depth > 15) return;
1912
+ try {
1913
+ const entries = await fs8.readdir(dir, { withFileTypes: true });
1914
+ const regexPattern = pattern.replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/{{GLOBSTAR}}/g, ".*");
1915
+ const regex = new RegExp(`^${regexPattern}$`);
1916
+ for (const entry of entries) {
1917
+ if (results.length >= maxResults) break;
1918
+ if (entry.name.startsWith(".") || IGNORED_DIRS.includes(entry.name)) {
1919
+ continue;
1920
+ }
1921
+ const fullPath = path8.join(dir, entry.name);
1922
+ const relativePath = path8.relative(process.cwd(), fullPath);
1923
+ if (entry.isDirectory()) {
1924
+ if (pattern.includes("**") || pattern.includes("/")) {
1925
+ await globSearch(fullPath, pattern, results, maxResults, depth + 1);
1926
+ }
1927
+ } else if (entry.isFile()) {
1928
+ if (regex.test(relativePath) || regex.test(entry.name)) {
1929
+ results.push(relativePath);
1930
+ }
1931
+ }
1932
+ }
1933
+ } catch {
1934
+ }
1935
+ }
1936
+ var WebFetchTool = {
1937
+ name: "web_fetch",
1938
+ description: "Fetch content from a URL (for documentation, APIs, etc.)",
1939
+ async execute(args) {
1940
+ const url = args.url;
1941
+ if (!url) {
1942
+ return { success: false, error: "No URL provided" };
1943
+ }
1944
+ try {
1945
+ new URL(url);
1946
+ } catch {
1947
+ return { success: false, error: "Invalid URL format" };
1948
+ }
1949
+ const blockedDomains = ["localhost", "127.0.0.1", "0.0.0.0", "169.254"];
1950
+ const urlObj = new URL(url);
1951
+ if (blockedDomains.some((d) => urlObj.hostname.includes(d))) {
1952
+ return {
1953
+ success: false,
1954
+ error: "Cannot fetch from local/private addresses"
1955
+ };
1956
+ }
1957
+ try {
1958
+ const controller = new AbortController();
1959
+ const timeoutId = setTimeout(() => controller.abort(), 1e4);
1960
+ const response = await fetch(url, {
1961
+ signal: controller.signal,
1962
+ headers: {
1963
+ "User-Agent": "Obsidian-Next/1.0 (AI Agent CLI)",
1964
+ "Accept": "text/html,application/json,text/plain,*/*"
1965
+ }
1966
+ });
1967
+ clearTimeout(timeoutId);
1968
+ if (!response.ok) {
1969
+ return {
1970
+ success: false,
1971
+ error: `HTTP ${response.status}: ${response.statusText}`
1972
+ };
1973
+ }
1974
+ const contentType = response.headers.get("content-type") || "";
1975
+ let content = await response.text();
1976
+ if (content.length > MAX_OUTPUT_LENGTH) {
1977
+ content = content.slice(0, MAX_OUTPUT_LENGTH) + `
1978
+
1979
+ ... [TRUNCATED: ${content.length - MAX_OUTPUT_LENGTH} more characters]`;
1980
+ }
1981
+ if (contentType.includes("text/html")) {
1982
+ content = content.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
1983
+ }
1984
+ return {
1985
+ success: true,
1986
+ output: truncateOutput(`URL: ${url}
1987
+ Content-Type: ${contentType}
1988
+ ${"=".repeat(60)}
1989
+ ${content}`)
1990
+ };
1991
+ } catch (error) {
1992
+ if (error.name === "AbortError") {
1993
+ return { success: false, error: "Request timed out after 10 seconds" };
1994
+ }
1995
+ return {
1996
+ success: false,
1997
+ error: `Fetch failed: ${error.message}`
1998
+ };
1999
+ }
2000
+ }
2001
+ };
2002
+ var ToolRegistry = class {
2003
+ tools = /* @__PURE__ */ new Map();
2004
+ constructor() {
2005
+ this.register(BashTool);
2006
+ this.register(ReadTool);
2007
+ this.register(WriteTool);
2008
+ this.register(EditTool);
2009
+ this.register(ListTool);
2010
+ this.register(GrepTool);
2011
+ this.register(GlobTool);
2012
+ this.register(WebFetchTool);
2013
+ }
2014
+ register(tool) {
2015
+ this.tools.set(tool.name, tool);
2016
+ }
2017
+ has(name) {
2018
+ return this.tools.has(name);
2019
+ }
2020
+ get(name) {
2021
+ return this.tools.get(name);
2022
+ }
2023
+ list() {
2024
+ return Array.from(this.tools.values());
2025
+ }
2026
+ async execute(name, args) {
2027
+ const tool = this.tools.get(name);
2028
+ if (!tool) {
2029
+ return {
2030
+ success: false,
2031
+ error: `Unknown tool: ${name}`
2032
+ };
2033
+ }
2034
+ bus.emitAgent({
2035
+ type: "tool_start",
2036
+ tool: name,
2037
+ args: JSON.stringify(args, null, 2)
2038
+ });
2039
+ const result = await tool.execute(args);
2040
+ bus.emitAgent({
2041
+ type: "tool_result",
2042
+ tool: name,
2043
+ output: result.success ? result.output || "Success" : result.error || "Failed",
2044
+ isError: !result.success
2045
+ });
2046
+ return result;
2047
+ }
2048
+ };
2049
+ var tools = new ToolRegistry();
2050
+
2051
+ // src/core/llm.ts
2052
+ var MAX_TOOL_ITERATIONS = 10;
2053
+ var LLMClient = class {
2054
+ client = null;
2055
+ lastConfig = null;
2056
+ conversationHistory = [];
2057
+ toolIterations = 0;
2058
+ accumulatedInputTokens = 0;
2059
+ accumulatedOutputTokens = 0;
2060
+ async initialize() {
2061
+ const cfg = await config.load();
2062
+ await usage.init();
2063
+ if (!cfg.apiKey) {
2064
+ bus.emitAgent({
2065
+ type: "error",
2066
+ message: "Missing ANTHROPIC_API_KEY. Please set it in .env or via /init."
2067
+ });
2068
+ return false;
2069
+ }
2070
+ this.client = new Anthropic({
2071
+ apiKey: cfg.apiKey
2072
+ });
2073
+ this.lastConfig = cfg;
2074
+ return true;
2075
+ }
2076
+ async streamChat(userMessage) {
2077
+ if (!this.client) {
2078
+ const initialized = await this.initialize();
2079
+ if (!initialized || !this.client) return null;
2080
+ }
2081
+ try {
2082
+ const modelMap = {
2083
+ // New 4.5 Aliases
2084
+ "claude-sonnet-4-5": "claude-sonnet-4-5-20250929",
2085
+ "claude-haiku-4-5": "claude-haiku-4-5-20251001",
2086
+ "claude-opus-4-5": "claude-opus-4-5-20251101",
2087
+ "ollama": "llama3"
2088
+ };
2089
+ const requestedModel = this.lastConfig?.model || "claude-sonnet-4-5-20250929";
2090
+ let apiModel = modelMap[requestedModel] || requestedModel;
2091
+ if (userMessage.trim()) {
2092
+ this.toolIterations = 0;
2093
+ this.accumulatedInputTokens = 0;
2094
+ this.accumulatedOutputTokens = 0;
2095
+ this.conversationHistory.push({
2096
+ role: "user",
2097
+ content: userMessage
2098
+ });
2099
+ } else {
2100
+ this.toolIterations++;
2101
+ if (this.toolIterations > MAX_TOOL_ITERATIONS) {
2102
+ bus.emitAgent({
2103
+ type: "error",
2104
+ message: `Tool iteration limit (${MAX_TOOL_ITERATIONS}) exceeded. Stopping to prevent infinite loop.`
2105
+ });
2106
+ return null;
2107
+ }
2108
+ }
2109
+ const toolDefinitions = tools.list().map((tool) => ({
2110
+ name: tool.name,
2111
+ description: tool.description,
2112
+ input_schema: {
2113
+ type: "object",
2114
+ properties: this.getToolSchema(tool.name),
2115
+ required: this.getRequiredParams(tool.name)
2116
+ }
2117
+ }));
2118
+ const toolList = tools.list().map((t) => `- ${t.name}: ${t.description}`).join("\n");
2119
+ const systemPrompt = `You are a CLI coding agent. You have tools. Use them.
2120
+
2121
+ Available: ${tools.list().map((t) => t.name).join(", ")}
2122
+
2123
+ - Dont explain. Just do it.
2124
+ - No markdown. No ** or \` or #. Plain text.
2125
+ - Read files before editing.
2126
+ - Grep to find code. Read to understand. Edit to change.
2127
+ - One thought, then act. No preamble.
2128
+ - If asked to do something, do it. Dont ask for confirmation.
2129
+ - Errors: fix them, dont apologize.
2130
+ - Never read node_modules or .git.
2131
+
2132
+ cwd: ${process.cwd()}`;
2133
+ const createMessage = async (model) => {
2134
+ return await this.client.messages.create({
2135
+ model,
2136
+ max_tokens: this.lastConfig?.maxTokens || 8192,
2137
+ system: systemPrompt,
2138
+ messages: [...this.conversationHistory],
2139
+ tools: toolDefinitions,
2140
+ stream: true
2141
+ });
2142
+ };
2143
+ let stream;
2144
+ let currentModel = apiModel;
2145
+ let inputTokens = 0;
2146
+ let outputTokens = 0;
2147
+ try {
2148
+ stream = await createMessage(apiModel);
2149
+ } catch (error) {
2150
+ const isNotFound = error.status === 404 || error.message && error.message.includes("not_found_error") || error.error && error.error.type === "not_found_error";
2151
+ if (isNotFound) {
2152
+ bus.emitAgent({
2153
+ type: "error",
2154
+ message: `Model ${apiModel} not available. Falling back to claude-haiku-4-5.`
2155
+ });
2156
+ currentModel = "claude-haiku-4-5-20251001";
2157
+ stream = await createMessage(currentModel);
2158
+ } else {
2159
+ throw error;
2160
+ }
2161
+ }
2162
+ let fullResponse = "";
2163
+ let buffer = "";
2164
+ let toolUses = [];
2165
+ let currentToolUse = null;
2166
+ for await (const chunk of stream) {
2167
+ if (chunk.type === "message_start" && chunk.message && chunk.message.usage) {
2168
+ inputTokens += chunk.message.usage.input_tokens || 0;
2169
+ this.accumulatedInputTokens += chunk.message.usage.input_tokens || 0;
2170
+ }
2171
+ if (chunk.type === "message_delta" && chunk.usage) {
2172
+ outputTokens += chunk.usage.output_tokens || 0;
2173
+ this.accumulatedOutputTokens += chunk.usage.output_tokens || 0;
2174
+ }
2175
+ if (chunk.type === "content_block_start" && chunk.content_block.type === "tool_use") {
2176
+ currentToolUse = {
2177
+ id: chunk.content_block.id,
2178
+ name: chunk.content_block.name,
2179
+ input: ""
2180
+ };
2181
+ }
2182
+ if (chunk.type === "content_block_delta" && chunk.delta.type === "input_json_delta") {
2183
+ if (currentToolUse) {
2184
+ currentToolUse.input += chunk.delta.partial_json;
2185
+ }
2186
+ }
2187
+ if (chunk.type === "content_block_stop" && currentToolUse) {
2188
+ try {
2189
+ currentToolUse.input = JSON.parse(currentToolUse.input);
2190
+ toolUses.push(currentToolUse);
2191
+ currentToolUse = null;
2192
+ } catch (e) {
2193
+ bus.emitAgent({
2194
+ type: "error",
2195
+ message: `Failed to parse tool input: ${e}`
2196
+ });
2197
+ }
2198
+ }
2199
+ if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
2200
+ const text = chunk.delta.text;
2201
+ fullResponse += text;
2202
+ buffer += text;
2203
+ const shouldEmit = buffer.length >= 50 || buffer.match(/[.!?]\s*$/) || buffer.match(/\n/);
2204
+ if (shouldEmit) {
2205
+ bus.emitAgent({
2206
+ type: "thought",
2207
+ content: fullResponse
2208
+ });
2209
+ buffer = "";
2210
+ }
2211
+ }
2212
+ }
2213
+ if (buffer.length > 0) {
2214
+ bus.emitAgent({
2215
+ type: "thought",
2216
+ content: fullResponse
2217
+ });
2218
+ }
2219
+ if (toolUses.length > 0) {
2220
+ const toolResults = [];
2221
+ for (const toolUse of toolUses) {
2222
+ const result = await tools.execute(toolUse.name, toolUse.input);
2223
+ toolResults.push({
2224
+ type: "tool_result",
2225
+ tool_use_id: toolUse.id,
2226
+ content: result.success ? result.output || "Success" : result.error || "Failed",
2227
+ is_error: !result.success
2228
+ });
2229
+ }
2230
+ this.conversationHistory.push({
2231
+ role: "assistant",
2232
+ content: [
2233
+ ...fullResponse ? [{ type: "text", text: fullResponse }] : [],
2234
+ ...toolUses.map((tu) => ({
2235
+ type: "tool_use",
2236
+ id: tu.id,
2237
+ name: tu.name,
2238
+ input: tu.input
2239
+ }))
2240
+ ]
2241
+ });
2242
+ this.conversationHistory.push({
2243
+ role: "user",
2244
+ content: toolResults
2245
+ });
2246
+ return await this.streamChat("");
2247
+ }
2248
+ if (fullResponse) {
2249
+ this.conversationHistory.push({
2250
+ role: "assistant",
2251
+ content: fullResponse
2252
+ });
2253
+ }
2254
+ await usage.track(currentModel, this.accumulatedInputTokens, this.accumulatedOutputTokens);
2255
+ return fullResponse;
2256
+ } catch (error) {
2257
+ bus.emitAgent({
2258
+ type: "error",
2259
+ message: `LLM Error: ${error.message}`
2260
+ });
2261
+ return null;
2262
+ }
2263
+ }
2264
+ getToolSchema(toolName) {
2265
+ const schemas = {
2266
+ bash: {
2267
+ command: {
2268
+ type: "string",
2269
+ description: "The shell command to execute"
2270
+ }
2271
+ },
2272
+ read: {
2273
+ path: {
2274
+ type: "string",
2275
+ description: "Path to the file to read (relative to workspace)"
2276
+ }
2277
+ },
2278
+ write: {
2279
+ path: {
2280
+ type: "string",
2281
+ description: "Path where to create the new file"
2282
+ },
2283
+ content: {
2284
+ type: "string",
2285
+ description: "Content to write to the file"
2286
+ }
2287
+ },
2288
+ edit: {
2289
+ path: {
2290
+ type: "string",
2291
+ description: "Path to the file to edit"
2292
+ },
2293
+ search: {
2294
+ type: "string",
2295
+ description: "Text to search for (must match exactly)"
2296
+ },
2297
+ replace: {
2298
+ type: "string",
2299
+ description: "Text to replace with"
2300
+ }
2301
+ },
2302
+ list: {
2303
+ path: {
2304
+ type: "string",
2305
+ description: "Directory path to list (defaults to current directory)"
2306
+ }
2307
+ },
2308
+ grep: {
2309
+ pattern: {
2310
+ type: "string",
2311
+ description: "Regex pattern to search for"
2312
+ },
2313
+ path: {
2314
+ type: "string",
2315
+ description: "Directory to search in (defaults to current directory)"
2316
+ },
2317
+ limit: {
2318
+ type: "number",
2319
+ description: "Maximum number of results (default: 50)"
2320
+ }
2321
+ },
2322
+ glob: {
2323
+ pattern: {
2324
+ type: "string",
2325
+ description: "Glob pattern like **/*.ts or src/**/*.tsx"
2326
+ },
2327
+ path: {
2328
+ type: "string",
2329
+ description: "Base directory (defaults to current directory)"
2330
+ }
2331
+ },
2332
+ web_fetch: {
2333
+ url: {
2334
+ type: "string",
2335
+ description: "URL to fetch content from"
2336
+ }
2337
+ }
2338
+ };
2339
+ return schemas[toolName] || {};
2340
+ }
2341
+ getRequiredParams(toolName) {
2342
+ const required = {
2343
+ bash: ["command"],
2344
+ read: ["path"],
2345
+ write: ["path", "content"],
2346
+ edit: ["path", "search", "replace"],
2347
+ list: [],
2348
+ grep: ["pattern"],
2349
+ glob: ["pattern"],
2350
+ web_fetch: ["url"]
2351
+ };
2352
+ return required[toolName] || [];
2353
+ }
2354
+ clearHistory() {
2355
+ this.conversationHistory = [];
2356
+ }
2357
+ };
2358
+ var llm = new LLMClient();
2359
+
2360
+ // src/core/tasks.ts
2361
+ import fs9 from "fs/promises";
2362
+ import path9 from "path";
2363
+ var TASKS_DIR = ".obsidian";
2364
+ var TASKS_FILE = "tasks.md";
2365
+ var TaskTracker = class {
2366
+ task = null;
2367
+ tasksPath;
2368
+ constructor() {
2369
+ this.tasksPath = path9.join(process.cwd(), TASKS_DIR, TASKS_FILE);
2370
+ }
2371
+ async init() {
2372
+ const dir = path9.join(process.cwd(), TASKS_DIR);
2373
+ await fs9.mkdir(dir, { recursive: true });
2374
+ await this.load();
2375
+ }
2376
+ async load() {
2377
+ try {
2378
+ const content = await fs9.readFile(this.tasksPath, "utf-8");
2379
+ this.task = this.parse(content);
2380
+ } catch {
2381
+ this.task = null;
2382
+ }
2383
+ }
2384
+ parse(content) {
2385
+ const lines = content.split("\n");
2386
+ let title = "";
2387
+ let status = "pending";
2388
+ const subtasks = [];
2389
+ const context2 = [];
2390
+ for (const line of lines) {
2391
+ if (line.startsWith("# ")) {
2392
+ title = line.slice(2).trim();
2393
+ continue;
2394
+ }
2395
+ if (line.startsWith("Status: ")) {
2396
+ const s = line.slice(8).trim().toLowerCase();
2397
+ if (["pending", "in_progress", "blocked", "done"].includes(s)) {
2398
+ status = s;
2399
+ }
2400
+ continue;
2401
+ }
2402
+ const subtaskMatch = line.match(/^- \[([ x])\] (.+)$/);
2403
+ if (subtaskMatch) {
2404
+ subtasks.push({
2405
+ done: subtaskMatch[1] === "x",
2406
+ text: subtaskMatch[2]
2407
+ });
2408
+ continue;
2409
+ }
2410
+ if (line.startsWith("- Modified: ") || line.startsWith("- Read: ")) {
2411
+ context2.push(line.slice(2));
2412
+ }
2413
+ }
2414
+ if (!title) return null;
2415
+ return {
2416
+ id: Date.now().toString(36),
2417
+ title,
2418
+ status,
2419
+ subtasks,
2420
+ context: context2,
2421
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
2422
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
2423
+ };
2424
+ }
2425
+ serialize() {
2426
+ if (!this.task) return "# No active task\n";
2427
+ const lines = [
2428
+ `# ${this.task.title}`,
2429
+ "",
2430
+ `Status: ${this.task.status}`,
2431
+ "",
2432
+ "## Progress"
2433
+ ];
2434
+ for (const st of this.task.subtasks) {
2435
+ lines.push(`- [${st.done ? "x" : " "}] ${st.text}`);
2436
+ }
2437
+ if (this.task.context.length > 0) {
2438
+ lines.push("", "## Context");
2439
+ for (const c of this.task.context) {
2440
+ lines.push(`- ${c}`);
2441
+ }
2442
+ }
2443
+ lines.push("", `Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
2444
+ return lines.join("\n");
2445
+ }
2446
+ async save() {
2447
+ const content = this.serialize();
2448
+ await fs9.writeFile(this.tasksPath, content);
2449
+ }
2450
+ // Task management
2451
+ async create(title) {
2452
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2453
+ this.task = {
2454
+ id: Date.now().toString(36),
2455
+ title,
2456
+ status: "in_progress",
2457
+ subtasks: [],
2458
+ context: [],
2459
+ created_at: now,
2460
+ updated_at: now
2461
+ };
2462
+ await this.save();
2463
+ return this.task;
2464
+ }
2465
+ async addSubtask(text) {
2466
+ if (!this.task) return;
2467
+ this.task.subtasks.push({ text, done: false });
2468
+ this.task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
2469
+ await this.save();
2470
+ }
2471
+ async completeSubtask(index) {
2472
+ if (!this.task || index >= this.task.subtasks.length) return;
2473
+ this.task.subtasks[index].done = true;
2474
+ this.task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
2475
+ await this.save();
2476
+ }
2477
+ async setStatus(status) {
2478
+ if (!this.task) return;
2479
+ this.task.status = status;
2480
+ this.task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
2481
+ await this.save();
2482
+ }
2483
+ async addContext(ctx) {
2484
+ if (!this.task) return;
2485
+ if (!this.task.context.includes(ctx)) {
2486
+ this.task.context.push(ctx);
2487
+ await this.save();
2488
+ }
2489
+ }
2490
+ async complete() {
2491
+ if (!this.task) return;
2492
+ this.task.status = "done";
2493
+ for (const st of this.task.subtasks) {
2494
+ st.done = true;
2495
+ }
2496
+ await this.save();
2497
+ }
2498
+ async clear() {
2499
+ this.task = null;
2500
+ await this.save();
2501
+ }
2502
+ // Getters
2503
+ get() {
2504
+ return this.task ? { ...this.task } : null;
2505
+ }
2506
+ getProgress() {
2507
+ if (!this.task) return "No active task";
2508
+ const done = this.task.subtasks.filter((s) => s.done).length;
2509
+ const total = this.task.subtasks.length;
2510
+ return `${this.task.title} [${done}/${total}]`;
2511
+ }
2512
+ hasActiveTask() {
2513
+ return this.task !== null && this.task.status !== "done";
2514
+ }
2515
+ };
2516
+ var tasks = new TaskTracker();
2517
+
2518
+ // src/core/agent.ts
2519
+ var Agent = class {
2520
+ initialized = false;
2521
+ pendingPlan = null;
2522
+ async init() {
2523
+ if (this.initialized) return;
2524
+ await context.init();
2525
+ await tasks.init();
2526
+ this.initialized = true;
2527
+ }
2528
+ async run(input) {
2529
+ await this.init();
2530
+ const mode = context.getMode();
2531
+ if (this.isNewTask(input)) {
2532
+ await tasks.create(this.extractTaskTitle(input));
2533
+ await context.setTask(tasks.getProgress());
2534
+ }
2535
+ bus.emitAgent({ type: "thought", content: `[${mode}] Processing...` });
2536
+ if (mode === "plan") {
2537
+ await this.runPlanMode(input);
2538
+ } else {
2539
+ await this.runDirectMode(input);
2540
+ }
2541
+ }
2542
+ isNewTask(input) {
2543
+ const actionWords = ["create", "add", "implement", "fix", "update", "refactor", "build", "make", "write"];
2544
+ const lower = input.toLowerCase();
2545
+ return input.length > 20 && actionWords.some((w) => lower.includes(w));
2546
+ }
2547
+ extractTaskTitle(input) {
2548
+ const firstSentence = input.split(/[.!?\n]/)[0];
2549
+ return firstSentence.slice(0, 50) + (firstSentence.length > 50 ? "..." : "");
2550
+ }
2551
+ async runPlanMode(input) {
2552
+ bus.emitAgent({ type: "thought", content: "Generating plan (read-only)..." });
2553
+ const planPrompt = `Analyze this request and create a plan.
2554
+
2555
+ IMPORTANT: You are in PLANNING mode. You may ONLY use read operations (read, list, grep, glob) to understand the codebase. Do NOT execute any writes or modifications yet.
2556
+
2557
+ OUTPUT a structured plan:
2558
+
2559
+ REQUEST: ${input}
2560
+
2561
+ FORMAT:
2562
+ TASK: <one line summary>
2563
+ STEPS:
2564
+ 1. <step>
2565
+ 2. <step>
2566
+ FILES_READ: <comma separated paths or "none">
2567
+ FILES_MODIFY: <comma separated paths or "none">
2568
+ APPROVAL: <yes if destructive, no otherwise>`;
2569
+ const planResponse = await llm.streamChat(planPrompt);
2570
+ if (!planResponse) {
2571
+ bus.emitAgent({ type: "error", message: "Failed to generate plan" });
2572
+ return;
2573
+ }
2574
+ const plan = this.parsePlan(planResponse);
2575
+ this.pendingPlan = { plan, originalInput: input };
2576
+ bus.emitAgent({
2577
+ type: "thought",
2578
+ content: this.formatPlan(plan)
2579
+ });
2580
+ bus.emitAgent({
2581
+ type: "approval_request",
2582
+ requestId: `plan_${Date.now()}`,
2583
+ context: `Execute this plan?
2584
+
2585
+ ${this.formatPlan(plan)}`
2586
+ });
2587
+ }
2588
+ async runDirectMode(input) {
2589
+ const startTime = Date.now();
2590
+ const ctxSummary = context.getSummary();
2591
+ const taskProgress = tasks.getProgress();
2592
+ let enhancedInput = input;
2593
+ if (ctxSummary || taskProgress !== "No active task") {
2594
+ enhancedInput = `${input}
2595
+
2596
+ [Context: ${ctxSummary}]
2597
+ [${taskProgress}]`;
2598
+ }
2599
+ const response = await llm.streamChat(enhancedInput);
2600
+ if (response) {
2601
+ await context.setLastAction(input.slice(0, 50));
2602
+ const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
2603
+ bus.emitAgent({ type: "done", summary: `Completed in ${duration}s` });
2604
+ } else {
2605
+ bus.emitAgent({ type: "error", message: "Failed to get response" });
2606
+ }
2607
+ }
2608
+ async handleApprovalResponse(approved, requestId) {
2609
+ if (!this.pendingPlan) {
2610
+ return;
2611
+ }
2612
+ if (!approved) {
2613
+ bus.emitAgent({ type: "thought", content: "Plan rejected. Awaiting new instructions." });
2614
+ this.pendingPlan = null;
2615
+ return;
2616
+ }
2617
+ const { plan, originalInput } = this.pendingPlan;
2618
+ this.pendingPlan = null;
2619
+ await this.executePlan(plan, originalInput);
2620
+ }
2621
+ async executePlan(plan, originalInput) {
2622
+ const startTime = Date.now();
2623
+ const previousMode = context.getMode();
2624
+ await context.setMode("auto");
2625
+ bus.emitAgent({ type: "thought", content: "Executing approved plan (auto-accept enabled)..." });
2626
+ const executionPrompt = `Execute this plan step by step:
2627
+
2628
+ ORIGINAL REQUEST: ${originalInput}
2629
+
2630
+ PLAN:
2631
+ ${this.formatPlan(plan)}
2632
+
2633
+ Execute each step carefully. Use available tools as needed.`;
2634
+ try {
2635
+ const response = await llm.streamChat(executionPrompt);
2636
+ if (response) {
2637
+ await context.setLastAction(`Executed: ${plan.task.slice(0, 40)}`);
2638
+ const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
2639
+ bus.emitAgent({ type: "done", summary: `Plan executed in ${duration}s` });
2640
+ } else {
2641
+ bus.emitAgent({ type: "error", message: "Failed to execute plan" });
2642
+ }
2643
+ } finally {
2644
+ await context.setMode(previousMode);
2645
+ bus.emitAgent({ type: "thought", content: `Mode restored to: ${previousMode}` });
2646
+ }
2647
+ }
2648
+ parsePlan(response) {
2649
+ const lines = response.split("\n");
2650
+ const plan = {
2651
+ task: "",
2652
+ steps: [],
2653
+ files_to_read: [],
2654
+ files_to_modify: [],
2655
+ requires_approval: false
2656
+ };
2657
+ for (const line of lines) {
2658
+ if (line.startsWith("TASK:")) {
2659
+ plan.task = line.slice(5).trim();
2660
+ } else if (line.match(/^\d+\./)) {
2661
+ plan.steps.push(line.replace(/^\d+\.\s*/, "").trim());
2662
+ } else if (line.startsWith("FILES_READ:")) {
2663
+ const files = line.slice(11).trim();
2664
+ if (files !== "none") {
2665
+ plan.files_to_read = files.split(",").map((f) => f.trim());
2666
+ }
2667
+ } else if (line.startsWith("FILES_MODIFY:")) {
2668
+ const files = line.slice(13).trim();
2669
+ if (files !== "none") {
2670
+ plan.files_to_modify = files.split(",").map((f) => f.trim());
2671
+ }
2672
+ } else if (line.startsWith("APPROVAL:")) {
2673
+ plan.requires_approval = line.toLowerCase().includes("yes");
2674
+ }
2675
+ }
2676
+ return plan;
2677
+ }
2678
+ formatPlan(plan) {
2679
+ const lines = [`Task: ${plan.task}`, "", "Steps:"];
2680
+ plan.steps.forEach((s, i) => lines.push(` ${i + 1}. ${s}`));
2681
+ if (plan.files_to_read.length > 0) {
2682
+ lines.push("", `Read: ${plan.files_to_read.join(", ")}`);
2683
+ }
2684
+ if (plan.files_to_modify.length > 0) {
2685
+ lines.push(`Modify: ${plan.files_to_modify.join(", ")}`);
2686
+ }
2687
+ return lines.join("\n");
2688
+ }
2689
+ // Mode control
2690
+ async setMode(mode) {
2691
+ await context.setMode(mode);
2692
+ bus.emitAgent({ type: "thought", content: `Mode: ${mode}` });
2693
+ }
2694
+ getMode() {
2695
+ return context.getMode();
2696
+ }
2697
+ };
2698
+ var agent = new Agent();
2699
+
2700
+ // src/ui/Root.tsx
2701
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2702
+ var MAX_EVENTS = 50;
2703
+ var Root = () => {
2704
+ const [events, setEvents] = useState4([]);
2705
+ const [input, setInput] = useState4("");
2706
+ const [pendingPrompt, setPendingPrompt] = useState4(null);
2707
+ const { exit } = useApp();
2708
+ const [stats, setStats] = useState4({ cost: 0, model: "Loading...", mode: "safe" });
2709
+ const handlePromptResolve = useCallback3(() => {
2710
+ setPendingPrompt(null);
2711
+ }, []);
2712
+ useEffect(() => {
2713
+ const updateStats = async () => {
2714
+ const cfg = await config.load();
2715
+ setStats({
2716
+ cost: usage.getSessionCost(),
2717
+ model: cfg.model,
2718
+ mode: context.getMode()
2719
+ });
2720
+ };
2721
+ updateStats();
2722
+ const statHandler = (event) => {
2723
+ if (event.type === "done" || event.type === "tool_result" || event.type === "thought") {
2724
+ updateStats();
2725
+ }
2726
+ };
2727
+ bus.on("agent", statHandler);
2728
+ return () => {
2729
+ bus.off("agent", statHandler);
2730
+ };
2731
+ }, []);
2732
+ useEffect(() => {
2733
+ history.load().then((loadedEvents) => {
2734
+ if (loadedEvents.length > 0) {
2735
+ setEvents(loadedEvents);
2736
+ }
2737
+ });
2738
+ }, []);
2739
+ useEffect(() => {
2740
+ if (events.length > 0) {
2741
+ history.save(events);
2742
+ }
2743
+ }, [events]);
2744
+ useEffect(() => {
2745
+ const handler = (event) => {
2746
+ if (event.type === "clear_history") {
2747
+ setEvents([]);
2748
+ history.clear();
2749
+ setPendingPrompt(null);
2750
+ return;
2751
+ }
2752
+ if (event.type === "approval_request") {
2753
+ setPendingPrompt({
2754
+ type: "approval",
2755
+ requestId: event.requestId,
2756
+ context: event.context,
2757
+ diff: event.diff
2758
+ });
2759
+ return;
2760
+ }
2761
+ if (event.type === "choice_request") {
2762
+ setPendingPrompt({
2763
+ type: "choice",
2764
+ question: event.question,
2765
+ options: event.options
2766
+ });
2767
+ return;
2768
+ }
2769
+ setEvents((prev) => {
2770
+ const last = prev[prev.length - 1];
2771
+ if (event.type === "thought" && last && last.type === "thought") {
2772
+ if (last.content === event.content) return prev;
2773
+ const newEvents = [...prev];
2774
+ newEvents[newEvents.length - 1] = event;
2775
+ return newEvents;
2776
+ }
2777
+ return [...prev, event];
2778
+ });
2779
+ };
2780
+ const userHandler = (event) => {
2781
+ if (event.type === "user_input") {
2782
+ setEvents((prev) => [...prev, { type: "user_input", content: event.content }]);
2783
+ }
2784
+ };
2785
+ bus.on("agent", handler);
2786
+ bus.on("user", userHandler);
2787
+ return () => {
2788
+ bus.off("agent", handler);
2789
+ bus.off("user", userHandler);
2790
+ };
2791
+ }, []);
2792
+ const [inputKey, setInputKey] = useState4(0);
2793
+ const [selectedIndex, setSelectedIndex] = useState4(0);
2794
+ const query = input.toLowerCase();
2795
+ const isCommand = input.startsWith("/");
2796
+ const matches = isCommand ? COMMANDS.filter((c) => c.name.startsWith(query)) : [];
2797
+ useEffect(() => {
2798
+ setSelectedIndex(0);
2799
+ }, [input]);
2800
+ const cycleMode = useCallback3(async () => {
2801
+ const modes = ["auto", "plan", "safe"];
2802
+ const currentIndex = modes.indexOf(stats.mode);
2803
+ const nextMode = modes[(currentIndex + 1) % modes.length];
2804
+ await agent.setMode(nextMode);
2805
+ setStats((prev) => ({ ...prev, mode: nextMode }));
2806
+ }, [stats.mode]);
2807
+ useInput3((input2, key) => {
2808
+ if (key.shift && key.tab) {
2809
+ cycleMode();
2810
+ return;
2811
+ }
2812
+ if (matches.length === 0) return;
2813
+ if (key.upArrow) {
2814
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : matches.length - 1);
2815
+ }
2816
+ if (key.downArrow) {
2817
+ setSelectedIndex((prev) => prev < matches.length - 1 ? prev + 1 : 0);
2818
+ }
2819
+ if (key.return || key.tab) {
2820
+ const selected = matches[selectedIndex];
2821
+ if (selected && input2 !== selected.name) {
2822
+ setInput(selected.name);
2823
+ setInputKey((prev) => prev + 1);
2824
+ }
2825
+ }
2826
+ });
2827
+ const handleSubmit = (value) => {
2828
+ if (!value.trim()) return;
2829
+ if (matches.length > 0 && value !== matches[selectedIndex]?.name) {
2830
+ const selected = matches[selectedIndex];
2831
+ if (selected && selected.name.startsWith(value) && selected.name !== value) {
2832
+ return;
2833
+ }
2834
+ }
2835
+ if (value.trim() === "/exit") {
2836
+ exit();
2837
+ return;
2838
+ }
2839
+ bus.emitUser({ type: "user_input", content: value });
2840
+ setInput("");
2841
+ };
2842
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", height: "100%", children: [
2843
+ /* @__PURE__ */ jsx7(Dashboard, {}),
2844
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", flexGrow: 1, marginY: 1, overflowY: "hidden", justifyContent: "flex-end", children: events.slice(-MAX_EVENTS).map((event, i) => {
2845
+ let content = null;
2846
+ if (event.type === "user_input") {
2847
+ content = /* @__PURE__ */ jsx7(Box7, { flexDirection: "row", paddingX: 1, marginBottom: 0, children: /* @__PURE__ */ jsxs7(Text7, { backgroundColor: "#222222", dimColor: true, children: [
2848
+ /* @__PURE__ */ jsx7(Text7, { color: "gray", children: " > " }),
2849
+ /* @__PURE__ */ jsx7(Text7, { color: "white", children: event.content }),
2850
+ /* @__PURE__ */ jsx7(Text7, { children: " " })
2851
+ ] }) }, i);
2852
+ } else if (event.type === "thought") {
2853
+ content = /* @__PURE__ */ jsx7(AgentLine, { content: event.content }, i);
2854
+ } else if (event.type === "tool_start") {
2855
+ let argsSummary = "";
2856
+ try {
2857
+ const args = JSON.parse(event.args);
2858
+ const firstVal = Object.values(args)[0];
2859
+ if (typeof firstVal === "string") {
2860
+ argsSummary = firstVal.length > 60 ? firstVal.slice(0, 60) + "..." : firstVal;
2861
+ }
2862
+ } catch {
2863
+ }
2864
+ content = /* @__PURE__ */ jsxs7(Box7, { children: [
2865
+ /* @__PURE__ */ jsx7(Text7, { backgroundColor: "#1a1a2e", color: "cyan", children: " \u23FA " }),
2866
+ /* @__PURE__ */ jsxs7(Text7, { backgroundColor: "#1a1a2e", color: "white", bold: true, children: [
2867
+ " ",
2868
+ event.tool
2869
+ ] }),
2870
+ /* @__PURE__ */ jsxs7(Text7, { backgroundColor: "#1a1a2e", color: "gray", children: [
2871
+ "(",
2872
+ argsSummary,
2873
+ ") "
2874
+ ] })
2875
+ ] }, i);
2876
+ } else if (event.type === "tool_result") {
2877
+ content = /* @__PURE__ */ jsx7(ToolOutput, { tool: event.tool, output: event.output, isError: event.isError }, i);
2878
+ } else if (event.type === "done") {
2879
+ content = /* @__PURE__ */ jsxs7(Text7, { color: "green", children: [
2880
+ "[OK] ",
2881
+ event.summary
2882
+ ] }, i);
2883
+ } else if (event.type === "error") {
2884
+ content = /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
2885
+ "[ERR] ",
2886
+ event.message
2887
+ ] }, i);
2888
+ } else if (event.type === "clear_history") {
2889
+ content = /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "[SYS] History cleared" }, i);
2890
+ }
2891
+ if (!content) return null;
2892
+ return /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: content }, i);
2893
+ }) }),
2894
+ pendingPrompt?.type === "approval" && /* @__PURE__ */ jsx7(
2895
+ ApprovalPrompt,
2896
+ {
2897
+ requestId: pendingPrompt.requestId,
2898
+ context: pendingPrompt.context,
2899
+ diff: pendingPrompt.diff,
2900
+ onResolve: handlePromptResolve
2901
+ }
2902
+ ),
2903
+ pendingPrompt?.type === "choice" && /* @__PURE__ */ jsx7(
2904
+ ChoicePrompt,
2905
+ {
2906
+ question: pendingPrompt.question,
2907
+ options: pendingPrompt.options,
2908
+ onResolve: handlePromptResolve
2909
+ }
2910
+ ),
2911
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2912
+ /* @__PURE__ */ jsx7(
2913
+ CommandPopup,
2914
+ {
2915
+ matches,
2916
+ selectedIndex
2917
+ }
2918
+ ),
2919
+ /* @__PURE__ */ jsxs7(Box7, { borderStyle: "round", borderColor: pendingPrompt ? "gray" : "gray", paddingX: 1, children: [
2920
+ /* @__PURE__ */ jsx7(Text7, { color: "red", bold: true, children: "> " }),
2921
+ /* @__PURE__ */ jsx7(
2922
+ TextInput,
2923
+ {
2924
+ value: input,
2925
+ onChange: pendingPrompt ? () => {
2926
+ } : setInput,
2927
+ onSubmit: pendingPrompt ? () => {
2928
+ } : handleSubmit,
2929
+ placeholder: pendingPrompt ? "Respond to prompt above..." : "Type a message or command..."
2930
+ },
2931
+ inputKey
2932
+ )
2933
+ ] })
2934
+ ] }),
2935
+ /* @__PURE__ */ jsxs7(
2936
+ Box7,
2937
+ {
2938
+ borderStyle: "single",
2939
+ borderTop: false,
2940
+ borderLeft: false,
2941
+ borderRight: false,
2942
+ borderColor: "gray",
2943
+ marginTop: 0,
2944
+ paddingX: 1,
2945
+ flexDirection: "row",
2946
+ justifyContent: "space-between",
2947
+ children: [
2948
+ /* @__PURE__ */ jsx7(Box7, { minWidth: 22, children: /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
2949
+ "[ ",
2950
+ /* @__PURE__ */ jsx7(Text7, { color: stats.mode === "plan" ? "yellow" : stats.mode === "auto" ? "green" : "white", children: stats.mode === "auto" ? "auto-accept ON" : stats.mode === "plan" ? "plan mode" : "default" }),
2951
+ " ]"
2952
+ ] }) }),
2953
+ /* @__PURE__ */ jsx7(Box7, { minWidth: 20, children: /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "[ Context: 0 files ]" }) }),
2954
+ /* @__PURE__ */ jsx7(Box7, { minWidth: 25, justifyContent: "center", children: /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
2955
+ "[ Model: ",
2956
+ /* @__PURE__ */ jsx7(Text7, { color: "white", children: stats.model }),
2957
+ " ]"
2958
+ ] }) }),
2959
+ /* @__PURE__ */ jsx7(Box7, { minWidth: 15, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
2960
+ "[ Cost: ",
2961
+ /* @__PURE__ */ jsxs7(Text7, { color: "green", children: [
2962
+ "$",
2963
+ stats.cost.toFixed(4)
2964
+ ] }),
2965
+ " ]"
2966
+ ] }) })
2967
+ ]
2968
+ }
2969
+ )
2970
+ ] });
2971
+ };
2972
+
2973
+ // src/commands/init.ts
2974
+ var initCommand = async (_args) => {
2975
+ bus.emitAgent({ type: "thought", content: "Checking configuration..." });
2976
+ if (await config.exists()) {
2977
+ bus.emitAgent({
2978
+ type: "tool_result",
2979
+ tool: "System",
2980
+ output: `Configuration already exists at ${config.getPath()}`
2981
+ });
2982
+ return;
2983
+ }
2984
+ bus.emitAgent({ type: "thought", content: "Creating default configuration..." });
2985
+ await config.save({
2986
+ model: "claude-3-5-sonnet",
2987
+ maxTokens: 8192,
2988
+ language: "en"
2989
+ });
2990
+ bus.emitAgent({
2991
+ type: "tool_result",
2992
+ tool: "System",
2993
+ output: `Initialized configuration at ${config.getPath()}`
2994
+ });
2995
+ };
2996
+
2997
+ // src/commands/clear.ts
2998
+ var clearCommand = async (_args) => {
2999
+ bus.emitAgent({
3000
+ type: "clear_history"
3001
+ });
3002
+ llm.clearHistory();
3003
+ await context.reset();
3004
+ await new Promise((resolve) => setTimeout(resolve, 100));
3005
+ bus.emitAgent({
3006
+ type: "done",
3007
+ summary: "History, conversation, and context cleared."
3008
+ });
3009
+ };
3010
+
3011
+ // src/commands/cost.ts
3012
+ var costCommand = async (_args) => {
3013
+ await usage.init();
3014
+ const stats = usage.getStats();
3015
+ bus.emitAgent({
3016
+ type: "tool_result",
3017
+ tool: "Cost Tracker",
3018
+ output: `Session Cost Report:
3019
+ Input tokens: ${stats.totalInputTokens.toLocaleString()}
3020
+ Output tokens: ${stats.totalOutputTokens.toLocaleString()}
3021
+ Total cost: $${stats.totalCost.toFixed(4)}`
3022
+ });
3023
+ };
3024
+
3025
+ // src/commands/usage.ts
3026
+ var usageCommand = async (_args) => {
3027
+ await usage.init();
3028
+ const stats = usage.getStats();
3029
+ bus.emitAgent({
3030
+ type: "tool_result",
3031
+ tool: "Usage Tracker",
3032
+ output: `Historical Usage Report:
3033
+ Total sessions: ${stats.totalSessions}
3034
+ Total messages: ${stats.totalRequests}
3035
+ Total tokens: ${(stats.totalInputTokens + stats.totalOutputTokens).toLocaleString()}
3036
+ Total cost: $${stats.totalCost.toFixed(4)}`
3037
+ });
3038
+ };
3039
+
3040
+ // src/commands/models.ts
3041
+ var modelsCommand = async (args) => {
3042
+ const currentConfig = await config.load();
3043
+ if (args.length === 0) {
3044
+ bus.emitAgent({
3045
+ type: "tool_result",
3046
+ tool: "Model Selector",
3047
+ output: `Available AI Models:
3048
+ 1. claude-sonnet-4-5 [Current/Default]
3049
+ 2. claude-opus-4-5 [Powerful]
3050
+ 3. claude-haiku-4-5 [Fast/Cheap]
3051
+ 4. ollama [Local]
3052
+
3053
+ Current model: ${currentConfig.model}
3054
+
3055
+ Usage: /models <number> to select a model`
3056
+ });
3057
+ return;
3058
+ }
3059
+ const selection = args[0];
3060
+ let newModel;
3061
+ switch (selection) {
3062
+ case "1":
3063
+ case "sonnet":
3064
+ case "claude-sonnet-4-5":
3065
+ newModel = "claude-sonnet-4-5-20250929";
3066
+ break;
3067
+ case "2":
3068
+ case "opus":
3069
+ case "claude-opus-4-5":
3070
+ newModel = "claude-opus-4-5-20251101";
3071
+ break;
3072
+ case "3":
3073
+ case "haiku":
3074
+ case "claude-haiku-4-5":
3075
+ newModel = "claude-haiku-4-5-20251001";
3076
+ break;
3077
+ case "4":
3078
+ case "ollama":
3079
+ newModel = "ollama";
3080
+ break;
3081
+ default:
3082
+ bus.emitAgent({
3083
+ type: "error",
3084
+ message: `Invalid model selection: ${selection}. Use /models to see available options.`
3085
+ });
3086
+ return;
3087
+ }
3088
+ await config.save({
3089
+ ...currentConfig,
3090
+ model: newModel
3091
+ });
3092
+ bus.emitAgent({
3093
+ type: "done",
3094
+ summary: `Model switched to ${newModel}`
3095
+ });
3096
+ };
3097
+
3098
+ // src/commands/tool.ts
3099
+ var toolCommand = async (args) => {
3100
+ if (args.length === 0) {
3101
+ const toolList = tools.list();
3102
+ const formatted = toolList.map((t) => ` \u2022 ${t.name}: ${t.description}`).join("\n");
3103
+ bus.emitAgent({
3104
+ type: "thought",
3105
+ content: `Available tools:
3106
+ ${formatted}
3107
+
3108
+ Usage: /tool <name> <json-args>`
3109
+ });
3110
+ bus.emitAgent({
3111
+ type: "done",
3112
+ summary: "Tools listed"
3113
+ });
3114
+ return;
3115
+ }
3116
+ const toolName = args[0];
3117
+ const jsonArgs = args.slice(1).join(" ");
3118
+ if (!tools.has(toolName)) {
3119
+ bus.emitAgent({
3120
+ type: "error",
3121
+ message: `Unknown tool: ${toolName}`
3122
+ });
3123
+ return;
3124
+ }
3125
+ try {
3126
+ const parsedArgs = jsonArgs ? JSON.parse(jsonArgs) : {};
3127
+ const result = await tools.execute(toolName, parsedArgs);
3128
+ bus.emitAgent({
3129
+ type: "done",
3130
+ summary: result.success ? "Tool executed successfully" : "Tool execution failed"
3131
+ });
3132
+ } catch (error) {
3133
+ bus.emitAgent({
3134
+ type: "error",
3135
+ message: `Failed to execute tool: ${error.message}`
3136
+ });
3137
+ }
3138
+ };
3139
+
3140
+ // src/commands/status.ts
3141
+ import os5 from "os";
3142
+ import path10 from "path";
3143
+ var statusCommand = async (_args) => {
3144
+ const cfg = await config.load();
3145
+ const stats = usage.getStats();
3146
+ const platform = `${os5.type()} ${os5.release()}`;
3147
+ const nodeVersion = process.version;
3148
+ const workspace = process.cwd();
3149
+ const workspaceName = path10.basename(workspace);
3150
+ const sessionCost = usage.getSessionCost();
3151
+ const toolList = tools.list().map((t) => t.name).join(", ");
3152
+ const statusLines = [
3153
+ "=".repeat(50),
3154
+ "OBSIDIAN NEXT - System Status",
3155
+ "=".repeat(50),
3156
+ "",
3157
+ "[System]",
3158
+ ` Platform: ${platform}`,
3159
+ ` Node.js: ${nodeVersion}`,
3160
+ ` Workspace: ${workspaceName}`,
3161
+ ` Path: ${workspace}`,
3162
+ "",
3163
+ "[Configuration]",
3164
+ ` Model: ${cfg.model || "claude-sonnet-4-5"}`,
3165
+ ` Max Tokens: ${cfg.maxTokens || 8192}`,
3166
+ ` API Key: ${cfg.apiKey ? "[SET]" : "[NOT SET]"}`,
3167
+ "",
3168
+ "[Session]",
3169
+ ` Cost: $${sessionCost.toFixed(4)}`,
3170
+ ` Input: ${stats.totalInputTokens.toLocaleString()} tokens`,
3171
+ ` Output: ${stats.totalOutputTokens.toLocaleString()} tokens`,
3172
+ "",
3173
+ "[Tools]",
3174
+ ` Available: ${toolList}`,
3175
+ "",
3176
+ "=".repeat(50)
3177
+ ];
3178
+ bus.emitAgent({
3179
+ type: "thought",
3180
+ content: statusLines.join("\n")
3181
+ });
3182
+ bus.emitAgent({
3183
+ type: "done",
3184
+ summary: "Status displayed"
3185
+ });
3186
+ };
3187
+
3188
+ // src/commands/sandbox.ts
3189
+ var sandboxCommand = async (args) => {
3190
+ const subcommand = args[0]?.toLowerCase();
3191
+ if (!subcommand) {
3192
+ const mode = sandbox.getMode();
3193
+ const available = await sandbox.isAvailable();
3194
+ const statusLines = [
3195
+ "=".repeat(50),
3196
+ "SANDBOX EXECUTION MODE",
3197
+ "=".repeat(50),
3198
+ "",
3199
+ `Current Mode: ${mode.toUpperCase()}`,
3200
+ `Runtime: ${available ? "Available" : "Not installed"}`,
3201
+ "",
3202
+ "Modes:",
3203
+ " local - Direct execution with auditor checks",
3204
+ " sandbox - OS-level isolation (filesystem/network)",
3205
+ "",
3206
+ "Usage:",
3207
+ " /sandbox local - Switch to local mode",
3208
+ " /sandbox sandbox - Switch to sandbox mode",
3209
+ " /sandbox config - Show sandbox configuration",
3210
+ "",
3211
+ "=".repeat(50)
3212
+ ];
3213
+ bus.emitAgent({
3214
+ type: "thought",
3215
+ content: statusLines.join("\n")
3216
+ });
3217
+ bus.emitAgent({
3218
+ type: "done",
3219
+ summary: "Sandbox status displayed"
3220
+ });
3221
+ return;
3222
+ }
3223
+ if (subcommand === "local" || subcommand === "sandbox") {
3224
+ const available = await sandbox.isAvailable();
3225
+ if (subcommand === "sandbox" && !available) {
3226
+ bus.emitAgent({
3227
+ type: "error",
3228
+ message: "Sandbox runtime not available. Install with: npm install @anthropic-ai/sandbox-runtime"
3229
+ });
3230
+ return;
3231
+ }
3232
+ await sandbox.setMode(subcommand);
3233
+ bus.emitAgent({
3234
+ type: "done",
3235
+ summary: `Execution mode: ${subcommand.toUpperCase()}`
3236
+ });
3237
+ return;
3238
+ }
3239
+ if (subcommand === "config") {
3240
+ const config2 = sandbox.getConfig();
3241
+ const configLines = [
3242
+ "=".repeat(50),
3243
+ "SANDBOX CONFIGURATION",
3244
+ "=".repeat(50),
3245
+ "",
3246
+ "[Network]",
3247
+ ` Allowed: ${config2.allowedDomains.join(", ") || "none"}`,
3248
+ ` Denied: ${config2.deniedDomains.join(", ") || "none"}`,
3249
+ "",
3250
+ "[Filesystem]",
3251
+ ` Deny Read: ${config2.denyRead.join(", ") || "none"}`,
3252
+ ` Allow Write: ${config2.allowWrite.join(", ") || "none"}`,
3253
+ ` Deny Write: ${config2.denyWrite.join(", ") || "none"}`,
3254
+ "",
3255
+ "=".repeat(50)
3256
+ ];
3257
+ bus.emitAgent({
3258
+ type: "thought",
3259
+ content: configLines.join("\n")
3260
+ });
3261
+ bus.emitAgent({
3262
+ type: "done",
3263
+ summary: "Sandbox config displayed"
3264
+ });
3265
+ return;
3266
+ }
3267
+ bus.emitAgent({
3268
+ type: "error",
3269
+ message: `Unknown sandbox command: ${subcommand}. Use: local, sandbox, or config`
3270
+ });
3271
+ };
3272
+
3273
+ // src/commands/mode.ts
3274
+ var MODES = ["auto", "plan", "safe"];
3275
+ async function modeCommand(args) {
3276
+ const mode = args[0]?.toLowerCase();
3277
+ if (!mode) {
3278
+ const current = agent.getMode();
3279
+ bus.emitAgent({
3280
+ type: "thought",
3281
+ content: `Current mode: ${current}
3282
+
3283
+ Available modes:
3284
+ - auto: Execute without confirmation
3285
+ - plan: Think -> Approve -> Execute
3286
+ - safe: Auto reads, approve writes (default)`
3287
+ });
3288
+ return;
3289
+ }
3290
+ if (!MODES.includes(mode)) {
3291
+ bus.emitAgent({
3292
+ type: "error",
3293
+ message: `Invalid mode: ${mode}. Use: auto, plan, safe`
3294
+ });
3295
+ return;
3296
+ }
3297
+ await agent.setMode(mode);
3298
+ }
3299
+
3300
+ // src/commands/task.ts
3301
+ async function taskCommand(args) {
3302
+ await tasks.init();
3303
+ const subcommand = args[0]?.toLowerCase();
3304
+ if (!subcommand || subcommand === "show") {
3305
+ const task = tasks.get();
3306
+ if (!task) {
3307
+ bus.emitAgent({ type: "thought", content: "No active task" });
3308
+ return;
3309
+ }
3310
+ const lines = [
3311
+ `Task: ${task.title}`,
3312
+ `Status: ${task.status}`,
3313
+ "",
3314
+ "Progress:"
3315
+ ];
3316
+ task.subtasks.forEach((st, i) => {
3317
+ lines.push(` ${st.done ? "[x]" : "[ ]"} ${st.text}`);
3318
+ });
3319
+ if (task.context.length > 0) {
3320
+ lines.push("", "Context:");
3321
+ task.context.forEach((c) => lines.push(` - ${c}`));
3322
+ }
3323
+ bus.emitAgent({ type: "thought", content: lines.join("\n") });
3324
+ return;
3325
+ }
3326
+ if (subcommand === "new") {
3327
+ const title = args.slice(1).join(" ");
3328
+ if (!title) {
3329
+ bus.emitAgent({ type: "error", message: "Usage: /task new <title>" });
3330
+ return;
3331
+ }
3332
+ await tasks.create(title);
3333
+ bus.emitAgent({ type: "thought", content: `Task created: ${title}` });
3334
+ return;
3335
+ }
3336
+ if (subcommand === "add") {
3337
+ const text = args.slice(1).join(" ");
3338
+ if (!text) {
3339
+ bus.emitAgent({ type: "error", message: "Usage: /task add <subtask>" });
3340
+ return;
3341
+ }
3342
+ await tasks.addSubtask(text);
3343
+ bus.emitAgent({ type: "thought", content: `Added: ${text}` });
3344
+ return;
3345
+ }
3346
+ if (subcommand === "done") {
3347
+ const index = parseInt(args[1]);
3348
+ if (isNaN(index)) {
3349
+ await tasks.complete();
3350
+ bus.emitAgent({ type: "thought", content: "Task completed" });
3351
+ } else {
3352
+ await tasks.completeSubtask(index - 1);
3353
+ bus.emitAgent({ type: "thought", content: `Subtask ${index} done` });
3354
+ }
3355
+ return;
3356
+ }
3357
+ if (subcommand === "clear") {
3358
+ await tasks.clear();
3359
+ bus.emitAgent({ type: "thought", content: "Task cleared" });
3360
+ return;
3361
+ }
3362
+ bus.emitAgent({
3363
+ type: "thought",
3364
+ content: `Usage:
3365
+ /task - Show current task
3366
+ /task new <t> - Create new task
3367
+ /task add <st> - Add subtask
3368
+ /task done [n] - Complete task or subtask n
3369
+ /task clear - Clear task`
3370
+ });
3371
+ }
3372
+
3373
+ // src/commands/undo.ts
3374
+ async function undoCommand(args) {
3375
+ const count = parseInt(args[0]) || 1;
3376
+ const history2 = undo.getHistory(10);
3377
+ if (args[0] === "list" || args[0] === "history") {
3378
+ if (history2.length === 0) {
3379
+ bus.emitAgent({ type: "thought", content: "No changes to undo" });
3380
+ return;
3381
+ }
3382
+ const lines = ["Recent changes:", ""];
3383
+ history2.forEach((c, i) => {
3384
+ const op = c.operation === "create" ? "+" : c.operation === "delete" ? "-" : "~";
3385
+ lines.push(` ${i + 1}. [${op}] ${c.filePath}`);
3386
+ });
3387
+ bus.emitAgent({ type: "thought", content: lines.join("\n") });
3388
+ return;
3389
+ }
3390
+ const result = await undo.undo(count);
3391
+ if (result.success) {
3392
+ bus.emitAgent({ type: "thought", content: result.message });
3393
+ } else {
3394
+ bus.emitAgent({ type: "error", message: result.message });
3395
+ }
3396
+ }
3397
+
3398
+ // src/commands/config.ts
3399
+ async function configCommand(args) {
3400
+ const subcommand = args[0];
3401
+ const cfg = await config.load();
3402
+ if (!subcommand) {
3403
+ const display = [
3404
+ "Current Configuration:",
3405
+ "=".repeat(40),
3406
+ ` model: ${cfg.model}`,
3407
+ ` maxTokens: ${cfg.maxTokens}`,
3408
+ ` apiKey: ${cfg.apiKey ? "***" + cfg.apiKey.slice(-4) : "not set"}`,
3409
+ "",
3410
+ "Usage:",
3411
+ " /config set <key> <value>",
3412
+ " /config reset",
3413
+ "",
3414
+ "Keys: model, maxTokens"
3415
+ ];
3416
+ bus.emitAgent({ type: "thought", content: display.join("\n") });
3417
+ return;
3418
+ }
3419
+ if (subcommand === "set") {
3420
+ const key = args[1];
3421
+ const value = args.slice(2).join(" ");
3422
+ if (!key || !value) {
3423
+ bus.emitAgent({ type: "error", message: "Usage: /config set <key> <value>" });
3424
+ return;
3425
+ }
3426
+ const validKeys = ["model", "maxTokens"];
3427
+ if (!validKeys.includes(key)) {
3428
+ bus.emitAgent({ type: "error", message: `Invalid key. Valid keys: ${validKeys.join(", ")}` });
3429
+ return;
3430
+ }
3431
+ const updates = {};
3432
+ if (key === "maxTokens") {
3433
+ const num = parseInt(value);
3434
+ if (isNaN(num) || num < 100 || num > 2e5) {
3435
+ bus.emitAgent({ type: "error", message: "maxTokens must be between 100 and 200000" });
3436
+ return;
3437
+ }
3438
+ updates.maxTokens = num;
3439
+ } else {
3440
+ updates[key] = value;
3441
+ }
3442
+ await config.save(updates);
3443
+ bus.emitAgent({ type: "thought", content: `Config updated: ${key} = ${value}` });
3444
+ return;
3445
+ }
3446
+ if (subcommand === "reset") {
3447
+ await config.save({
3448
+ model: "claude-sonnet-4-5-20250929",
3449
+ maxTokens: 8192
3450
+ });
3451
+ bus.emitAgent({ type: "thought", content: "Config reset to defaults" });
3452
+ return;
3453
+ }
3454
+ bus.emitAgent({ type: "error", message: `Unknown subcommand: ${subcommand}` });
3455
+ }
3456
+
3457
+ // src/commands/doctor.ts
3458
+ import Anthropic2 from "@anthropic-ai/sdk";
3459
+ async function doctorCommand(args) {
3460
+ const results = [];
3461
+ bus.emitAgent({ type: "thought", content: "Running diagnostics..." });
3462
+ try {
3463
+ const cfg = await config.load();
3464
+ if (cfg.apiKey) {
3465
+ results.push({
3466
+ name: "API Key",
3467
+ status: "ok",
3468
+ message: `Configured (***${cfg.apiKey.slice(-4)})`
3469
+ });
3470
+ } else {
3471
+ results.push({
3472
+ name: "API Key",
3473
+ status: "error",
3474
+ message: "Not set. Run /init or set ANTHROPIC_API_KEY"
3475
+ });
3476
+ }
3477
+ results.push({
3478
+ name: "Model",
3479
+ status: "ok",
3480
+ message: cfg.model
3481
+ });
3482
+ } catch (e) {
3483
+ results.push({
3484
+ name: "Config",
3485
+ status: "error",
3486
+ message: `Failed to load: ${e}`
3487
+ });
3488
+ }
3489
+ const toolList = tools.list();
3490
+ results.push({
3491
+ name: "Tools",
3492
+ status: "ok",
3493
+ message: `${toolList.length} registered: ${toolList.map((t) => t.name).join(", ")}`
3494
+ });
3495
+ const sandboxAvailable = await sandbox.isAvailable();
3496
+ results.push({
3497
+ name: "Sandbox",
3498
+ status: sandboxAvailable ? "ok" : "warn",
3499
+ message: sandboxAvailable ? "Available" : "Not available (commands run directly)"
3500
+ });
3501
+ try {
3502
+ const cfg = await config.load();
3503
+ if (cfg.apiKey) {
3504
+ const client = new Anthropic2({ apiKey: cfg.apiKey });
3505
+ results.push({
3506
+ name: "API Client",
3507
+ status: "ok",
3508
+ message: "Anthropic SDK initialized"
3509
+ });
3510
+ }
3511
+ } catch (e) {
3512
+ results.push({
3513
+ name: "API Client",
3514
+ status: "error",
3515
+ message: e.message || "Failed to initialize"
3516
+ });
3517
+ }
3518
+ const cwd = process.cwd();
3519
+ results.push({
3520
+ name: "Workspace",
3521
+ status: "ok",
3522
+ message: cwd
3523
+ });
3524
+ const nodeVersion = process.version;
3525
+ const majorVersion = parseInt(nodeVersion.slice(1).split(".")[0]);
3526
+ results.push({
3527
+ name: "Node.js",
3528
+ status: majorVersion >= 18 ? "ok" : "warn",
3529
+ message: nodeVersion + (majorVersion < 18 ? " (recommend v18+)" : "")
3530
+ });
3531
+ const statusIcons = {
3532
+ ok: "[OK]",
3533
+ warn: "[!!]",
3534
+ error: "[X]"
3535
+ };
3536
+ const statusColors = {
3537
+ ok: "green",
3538
+ warn: "yellow",
3539
+ error: "red"
3540
+ };
3541
+ const output = [
3542
+ "System Diagnostics",
3543
+ "=".repeat(50),
3544
+ "",
3545
+ ...results.map((r) => {
3546
+ const icon = statusIcons[r.status];
3547
+ return `${icon.padEnd(6)} ${r.name.padEnd(12)} ${r.message}`;
3548
+ }),
3549
+ "",
3550
+ "=".repeat(50),
3551
+ `Total: ${results.filter((r) => r.status === "ok").length} OK, ${results.filter((r) => r.status === "warn").length} warnings, ${results.filter((r) => r.status === "error").length} errors`
3552
+ ];
3553
+ bus.emitAgent({ type: "thought", content: output.join("\n") });
3554
+ }
3555
+
3556
+ // src/commands/settings.ts
3557
+ var settingsCommand = async (args) => {
3558
+ const s = await settings.load();
3559
+ if (args.length === 0) {
3560
+ bus.emitAgent({
3561
+ type: "thought",
3562
+ content: `Settings (${settings.getPath()}):
3563
+ ${JSON.stringify(s, null, 2)}`
3564
+ });
3565
+ return;
3566
+ }
3567
+ const [key, ...valueParts] = args;
3568
+ const value = valueParts.join(" ");
3569
+ if (!value) {
3570
+ const keys = key.split(".");
3571
+ let current = s;
3572
+ for (const k of keys) {
3573
+ if (current && typeof current === "object" && k in current) {
3574
+ current = current[k];
3575
+ } else {
3576
+ bus.emitAgent({
3577
+ type: "error",
3578
+ message: `Unknown setting: ${key}`
3579
+ });
3580
+ return;
3581
+ }
3582
+ }
3583
+ bus.emitAgent({
3584
+ type: "thought",
3585
+ content: `${key} = ${JSON.stringify(current, null, 2)}`
3586
+ });
3587
+ return;
3588
+ }
3589
+ try {
3590
+ const keys = key.split(".");
3591
+ let parsed;
3592
+ if (value === "true") parsed = true;
3593
+ else if (value === "false") parsed = false;
3594
+ else if (!isNaN(Number(value))) parsed = Number(value);
3595
+ else if (value.startsWith("[") || value.startsWith("{")) parsed = JSON.parse(value);
3596
+ else parsed = value;
3597
+ if (keys.length === 1) {
3598
+ await settings.save({ [keys[0]]: parsed });
3599
+ } else if (keys.length === 2) {
3600
+ const current = await settings.load();
3601
+ const topKey = keys[0];
3602
+ const subKey = keys[1];
3603
+ if (typeof current[topKey] === "object" && current[topKey] !== null) {
3604
+ await settings.save({
3605
+ [topKey]: {
3606
+ ...current[topKey],
3607
+ [subKey]: parsed
3608
+ }
3609
+ });
3610
+ }
3611
+ }
3612
+ const updated = await settings.reload();
3613
+ bus.emitAgent({
3614
+ type: "done",
3615
+ summary: `Set ${key} = ${JSON.stringify(parsed)}`
3616
+ });
3617
+ } catch (err) {
3618
+ bus.emitAgent({
3619
+ type: "error",
3620
+ message: `Failed to set ${key}: ${err.message}`
3621
+ });
3622
+ }
3623
+ };
3624
+
3625
+ // src/core/commands.ts
3626
+ var CommandRegistry = class {
3627
+ commands = /* @__PURE__ */ new Map();
3628
+ constructor() {
3629
+ this.register("help", "Show available commands", async () => {
3630
+ const validCommands = Array.from(this.commands.values()).map((c) => ` /${c.name.padEnd(10)} - ${c.description}`).join("\n");
3631
+ bus.emitAgent({
3632
+ type: "thought",
3633
+ content: `Available Commands:
3634
+ ${validCommands}`
3635
+ });
3636
+ });
3637
+ this.register("init", "Initialize configuration", initCommand);
3638
+ this.register("clear", "Clear conversation history", clearCommand);
3639
+ this.register("cost", "Show session cost", costCommand);
3640
+ this.register("usage", "Show historical usage", usageCommand);
3641
+ this.register("models", "Select AI model", modelsCommand);
3642
+ this.register("tool", "Execute tools manually", toolCommand);
3643
+ this.register("status", "Show system status", statusCommand);
3644
+ this.register("sandbox", "Toggle sandbox mode", sandboxCommand);
3645
+ this.register("mode", "Set execution mode (auto/plan/safe)", modeCommand);
3646
+ this.register("task", "View/manage current task", taskCommand);
3647
+ this.register("undo", "Undo recent file changes", undoCommand);
3648
+ this.register("config", "View/edit configuration", configCommand);
3649
+ this.register("doctor", "Run system diagnostics", doctorCommand);
3650
+ this.register("settings", "View/edit settings (mode, permissions, ui)", settingsCommand);
3651
+ }
3652
+ register(name, description, handler) {
3653
+ this.commands.set(name, { name, description, handler });
3654
+ }
3655
+ has(name) {
3656
+ return this.commands.has(name);
3657
+ }
3658
+ async execute(name, args) {
3659
+ const cmd = this.commands.get(name);
3660
+ if (!cmd) {
3661
+ bus.emitAgent({
3662
+ type: "error",
3663
+ message: `Unknown command: /${name}`
3664
+ });
3665
+ return;
3666
+ }
3667
+ try {
3668
+ await cmd.handler(args);
3669
+ } catch (error) {
3670
+ bus.emitAgent({
3671
+ type: "error",
3672
+ message: `Command /${name} failed: ${error instanceof Error ? error.message : String(error)}`
3673
+ });
3674
+ }
3675
+ }
3676
+ };
3677
+ var commands = new CommandRegistry();
3678
+
3679
+ // src/agents/supervisor.ts
3680
+ var Supervisor = class {
3681
+ constructor() {
3682
+ this.setupListeners();
3683
+ }
3684
+ setupListeners() {
3685
+ bus.on("user", async (event) => {
3686
+ if (event.type === "user_input") {
3687
+ await this.handleInput(event.content);
3688
+ }
3689
+ if (event.type === "approval_response") {
3690
+ await agent.handleApprovalResponse(event.approved, event.requestId);
3691
+ }
3692
+ });
3693
+ }
3694
+ async handleInput(input) {
3695
+ if (input.startsWith("/")) {
3696
+ const [cmdName, ...args] = input.slice(1).split(" ");
3697
+ if (commands.has(cmdName)) {
3698
+ await commands.execute(cmdName, args);
3699
+ return;
3700
+ }
3701
+ bus.emitAgent({
3702
+ type: "error",
3703
+ message: `Unknown command: /${cmdName}. Try /help`
3704
+ });
3705
+ return;
3706
+ }
3707
+ await agent.run(input);
3708
+ }
3709
+ };
3710
+ var supervisor = new Supervisor();
3711
+
3712
+ // src/index.ts
3713
+ async function main() {
3714
+ process.stdout.write("\x1B[?1049h");
3715
+ process.stdout.write("\x1Bc");
3716
+ const { waitUntilExit, cleanup } = render(React7.createElement(Root), {
3717
+ patchConsole: false,
3718
+ exitOnCtrlC: true
3719
+ });
3720
+ try {
3721
+ await waitUntilExit();
3722
+ } catch (error) {
3723
+ console.error("Runtime Error:", error);
3724
+ } finally {
3725
+ process.stdout.write("\x1B[?1049l");
3726
+ process.exit(0);
3727
+ }
3728
+ }
3729
+ if (!supervisor) {
3730
+ console.error("Fatal: Supervisor failed to initialize");
3731
+ process.exit(1);
3732
+ }
3733
+ main().catch((err) => {
3734
+ console.error("Fatal Error:", err);
3735
+ process.exit(1);
3736
+ });