@alia-codea/cli 1.1.0 → 2.0.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 +1263 -487
- package/package.json +5 -5
- package/src/app.tsx +281 -0
- package/src/commands/auth.ts +11 -1
- package/src/commands/repl.ts +11 -299
- package/src/commands/run.ts +103 -126
- package/src/commands/sessions.ts +5 -6
- package/src/components/ApprovalPrompt.tsx +60 -0
- package/src/components/Header.tsx +39 -0
- package/src/components/InputBar.tsx +36 -0
- package/src/components/MessageList.tsx +81 -0
- package/src/components/ThinkingIndicator.tsx +28 -0
- package/src/components/ToolCallCard.tsx +68 -0
- package/src/index.ts +20 -6
- package/src/tools/executor.ts +140 -14
- package/src/tools/patch.ts +167 -0
- package/src/utils/api.ts +22 -3
- package/src/utils/approval.ts +31 -0
- package/src/utils/context.ts +65 -4
- package/src/utils/conversation.ts +141 -0
- package/dist/api-X2G5QROW.js +0 -10
- package/dist/chunk-SVPL4GNV.js +0 -230
- package/dist/index.d.ts +0 -1
- package/src/utils/ui.ts +0 -153
package/dist/index.js
CHANGED
|
@@ -1,40 +1,610 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
config,
|
|
4
|
-
createSession,
|
|
5
|
-
getSession,
|
|
6
|
-
getSessions,
|
|
7
|
-
saveSession,
|
|
8
|
-
streamChat
|
|
9
|
-
} from "./chunk-SVPL4GNV.js";
|
|
10
2
|
|
|
11
3
|
// src/index.ts
|
|
12
4
|
import { Command } from "commander";
|
|
13
5
|
|
|
6
|
+
// src/utils/config.ts
|
|
7
|
+
import Conf from "conf";
|
|
8
|
+
var config = new Conf({
|
|
9
|
+
projectName: "alia-codea-cli",
|
|
10
|
+
defaults: {
|
|
11
|
+
apiKey: "",
|
|
12
|
+
apiBaseUrl: "https://api.alia.onl",
|
|
13
|
+
defaultModel: "alia-v1-codea",
|
|
14
|
+
sessions: [],
|
|
15
|
+
currentSessionId: null
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
function saveSession(session) {
|
|
19
|
+
const sessions = config.get("sessions") || [];
|
|
20
|
+
const existingIndex = sessions.findIndex((s) => s.id === session.id);
|
|
21
|
+
if (existingIndex >= 0) {
|
|
22
|
+
sessions[existingIndex] = session;
|
|
23
|
+
} else {
|
|
24
|
+
sessions.unshift(session);
|
|
25
|
+
}
|
|
26
|
+
if (sessions.length > 50) {
|
|
27
|
+
sessions.splice(50);
|
|
28
|
+
}
|
|
29
|
+
config.set("sessions", sessions);
|
|
30
|
+
}
|
|
31
|
+
function getSession(id) {
|
|
32
|
+
const sessions = config.get("sessions") || [];
|
|
33
|
+
return sessions.find((s) => s.id === id);
|
|
34
|
+
}
|
|
35
|
+
function getSessions() {
|
|
36
|
+
return config.get("sessions") || [];
|
|
37
|
+
}
|
|
38
|
+
function createSession() {
|
|
39
|
+
const session = {
|
|
40
|
+
id: Date.now().toString(),
|
|
41
|
+
title: "New conversation",
|
|
42
|
+
messages: [],
|
|
43
|
+
createdAt: Date.now(),
|
|
44
|
+
updatedAt: Date.now(),
|
|
45
|
+
cwd: process.cwd()
|
|
46
|
+
};
|
|
47
|
+
saveSession(session);
|
|
48
|
+
config.set("currentSessionId", session.id);
|
|
49
|
+
return session;
|
|
50
|
+
}
|
|
51
|
+
|
|
14
52
|
// src/commands/repl.ts
|
|
15
|
-
import
|
|
16
|
-
import
|
|
53
|
+
import React4 from "react";
|
|
54
|
+
import { render } from "ink";
|
|
55
|
+
|
|
56
|
+
// src/app.tsx
|
|
57
|
+
import { useState as useState3, useCallback, useRef, useEffect as useEffect2 } from "react";
|
|
58
|
+
import { Box as Box7, useApp, useInput as useInput2 } from "ink";
|
|
59
|
+
|
|
60
|
+
// src/components/Header.tsx
|
|
61
|
+
import { Box, Text } from "ink";
|
|
62
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
63
|
+
function shortenPath(p) {
|
|
64
|
+
const home = process.env.HOME || "";
|
|
65
|
+
if (home && p.startsWith(home)) {
|
|
66
|
+
return "~" + p.slice(home.length);
|
|
67
|
+
}
|
|
68
|
+
return p;
|
|
69
|
+
}
|
|
70
|
+
var MODE_COLORS = {
|
|
71
|
+
"suggest": "yellow",
|
|
72
|
+
"auto-edit": "cyan",
|
|
73
|
+
"full-auto": "green"
|
|
74
|
+
};
|
|
75
|
+
function Header({ cwd, model, approvalMode, contextPercent }) {
|
|
76
|
+
return /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, justifyContent: "space-between", children: [
|
|
77
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: shortenPath(cwd) }),
|
|
78
|
+
/* @__PURE__ */ jsxs(Box, { gap: 1, children: [
|
|
79
|
+
/* @__PURE__ */ jsx(Text, { color: "magenta", children: model }),
|
|
80
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "|" }),
|
|
81
|
+
/* @__PURE__ */ jsx(Text, { color: MODE_COLORS[approvalMode], children: approvalMode }),
|
|
82
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "|" }),
|
|
83
|
+
/* @__PURE__ */ jsxs(Text, { color: contextPercent < 20 ? "red" : "gray", children: [
|
|
84
|
+
contextPercent,
|
|
85
|
+
"% left"
|
|
86
|
+
] })
|
|
87
|
+
] })
|
|
88
|
+
] });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/components/MessageList.tsx
|
|
92
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
93
|
+
import { Marked } from "marked";
|
|
94
|
+
import { markedTerminal } from "marked-terminal";
|
|
95
|
+
|
|
96
|
+
// src/components/ToolCallCard.tsx
|
|
97
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
98
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
99
|
+
function formatArgs(tool, args) {
|
|
100
|
+
switch (tool) {
|
|
101
|
+
case "read_file":
|
|
102
|
+
case "write_file":
|
|
103
|
+
case "edit_file":
|
|
104
|
+
return args.path || "";
|
|
105
|
+
case "apply_patch":
|
|
106
|
+
return "applying patch...";
|
|
107
|
+
case "list_files":
|
|
108
|
+
return args.path || ".";
|
|
109
|
+
case "search_files":
|
|
110
|
+
return `"${args.pattern}" in ${args.path || "."}`;
|
|
111
|
+
case "run_command":
|
|
112
|
+
return args.command || "";
|
|
113
|
+
default:
|
|
114
|
+
return JSON.stringify(args).slice(0, 60);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function ToolCallCard({ execution }) {
|
|
118
|
+
const { tool, args, result, success, approved } = execution;
|
|
119
|
+
const argStr = formatArgs(tool, args);
|
|
120
|
+
const isDone = result !== void 0;
|
|
121
|
+
if (approved === false) {
|
|
122
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 2, children: [
|
|
123
|
+
/* @__PURE__ */ jsxs2(Box2, { gap: 1, children: [
|
|
124
|
+
/* @__PURE__ */ jsx2(Text2, { color: "red", children: "\u2717" }),
|
|
125
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "gray", strikethrough: true, children: tool }),
|
|
126
|
+
/* @__PURE__ */ jsx2(Text2, { color: "gray", children: argStr })
|
|
127
|
+
] }),
|
|
128
|
+
/* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsx2(Text2, { color: "yellow", dimColor: true, children: "Declined by user" }) })
|
|
129
|
+
] });
|
|
130
|
+
}
|
|
131
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 2, children: [
|
|
132
|
+
/* @__PURE__ */ jsxs2(Box2, { gap: 1, children: [
|
|
133
|
+
isDone ? /* @__PURE__ */ jsx2(Text2, { color: success ? "green" : "red", children: success ? "\u2713" : "\u2717" }) : /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: "\u2192" }),
|
|
134
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: tool }),
|
|
135
|
+
/* @__PURE__ */ jsx2(Text2, { color: "gray", children: argStr })
|
|
136
|
+
] }),
|
|
137
|
+
isDone && result && /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", wrap: "truncate-end", children: [
|
|
138
|
+
result.slice(0, 120).replace(/\n/g, " "),
|
|
139
|
+
result.length > 120 ? "..." : ""
|
|
140
|
+
] }) })
|
|
141
|
+
] });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/components/MessageList.tsx
|
|
145
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
146
|
+
var marked = new Marked(markedTerminal());
|
|
147
|
+
function renderMarkdown(text) {
|
|
148
|
+
try {
|
|
149
|
+
const rendered = marked.parse(text);
|
|
150
|
+
if (typeof rendered === "string") {
|
|
151
|
+
return rendered.trimEnd();
|
|
152
|
+
}
|
|
153
|
+
return text;
|
|
154
|
+
} catch {
|
|
155
|
+
return text;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function MessageBlock({ message }) {
|
|
159
|
+
switch (message.type) {
|
|
160
|
+
case "user":
|
|
161
|
+
return /* @__PURE__ */ jsxs3(Box3, { paddingLeft: 1, paddingY: 0, children: [
|
|
162
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "\u276F " }),
|
|
163
|
+
/* @__PURE__ */ jsx3(Text3, { children: message.content })
|
|
164
|
+
] });
|
|
165
|
+
case "assistant":
|
|
166
|
+
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", paddingLeft: 1, children: /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
167
|
+
/* @__PURE__ */ jsx3(Text3, { color: "magenta", children: "\u2726 " }),
|
|
168
|
+
/* @__PURE__ */ jsx3(Text3, { children: message.streaming ? message.content : renderMarkdown(message.content) })
|
|
169
|
+
] }) });
|
|
170
|
+
case "tool":
|
|
171
|
+
if (message.toolExecution) {
|
|
172
|
+
return /* @__PURE__ */ jsx3(ToolCallCard, { execution: message.toolExecution });
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
case "info":
|
|
176
|
+
return /* @__PURE__ */ jsxs3(Box3, { paddingLeft: 1, children: [
|
|
177
|
+
/* @__PURE__ */ jsx3(Text3, { color: "blue", children: "\u2139 " }),
|
|
178
|
+
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: message.content })
|
|
179
|
+
] });
|
|
180
|
+
default:
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function MessageList({ messages }) {
|
|
185
|
+
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", flexGrow: 1, gap: 0, children: messages.map((msg) => /* @__PURE__ */ jsx3(MessageBlock, { message: msg }, msg.id)) });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/components/InputBar.tsx
|
|
189
|
+
import { useState as useState2 } from "react";
|
|
190
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
191
|
+
import TextInput from "ink-text-input";
|
|
192
|
+
|
|
193
|
+
// src/components/ThinkingIndicator.tsx
|
|
194
|
+
import { useState, useEffect } from "react";
|
|
195
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
196
|
+
import Spinner from "ink-spinner";
|
|
197
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
198
|
+
function ThinkingIndicator({ label = "Thinking" }) {
|
|
199
|
+
const [elapsed, setElapsed] = useState(0);
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
const interval = setInterval(() => {
|
|
202
|
+
setElapsed((e) => e + 1);
|
|
203
|
+
}, 1e3);
|
|
204
|
+
return () => clearInterval(interval);
|
|
205
|
+
}, []);
|
|
206
|
+
return /* @__PURE__ */ jsxs4(Box4, { gap: 1, children: [
|
|
207
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: /* @__PURE__ */ jsx4(Spinner, { type: "dots" }) }),
|
|
208
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: label }),
|
|
209
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "gray", children: [
|
|
210
|
+
"(",
|
|
211
|
+
elapsed,
|
|
212
|
+
"s)"
|
|
213
|
+
] })
|
|
214
|
+
] });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/components/InputBar.tsx
|
|
218
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
219
|
+
function InputBar({ onSubmit, isProcessing, thinkingLabel }) {
|
|
220
|
+
const [value, setValue] = useState2("");
|
|
221
|
+
const handleSubmit = (text) => {
|
|
222
|
+
const trimmed = text.trim();
|
|
223
|
+
if (!trimmed) return;
|
|
224
|
+
setValue("");
|
|
225
|
+
onSubmit(trimmed);
|
|
226
|
+
};
|
|
227
|
+
if (isProcessing) {
|
|
228
|
+
return /* @__PURE__ */ jsx5(Box5, { paddingX: 1, children: /* @__PURE__ */ jsx5(ThinkingIndicator, { label: thinkingLabel || "Thinking" }) });
|
|
229
|
+
}
|
|
230
|
+
return /* @__PURE__ */ jsxs5(Box5, { paddingX: 1, children: [
|
|
231
|
+
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u276F " }),
|
|
232
|
+
/* @__PURE__ */ jsx5(TextInput, { value, onChange: setValue, onSubmit: handleSubmit })
|
|
233
|
+
] });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/components/ApprovalPrompt.tsx
|
|
237
|
+
import { Box as Box6, Text as Text6, useInput } from "ink";
|
|
238
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
239
|
+
function formatArgs2(tool, args) {
|
|
240
|
+
switch (tool) {
|
|
241
|
+
case "write_file":
|
|
242
|
+
return `Write to ${args.path}`;
|
|
243
|
+
case "edit_file":
|
|
244
|
+
return `Edit ${args.path}`;
|
|
245
|
+
case "apply_patch":
|
|
246
|
+
return `Apply patch`;
|
|
247
|
+
case "run_command":
|
|
248
|
+
return `Run: ${args.command}`;
|
|
249
|
+
default:
|
|
250
|
+
return `${tool}: ${JSON.stringify(args).slice(0, 80)}`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function ApprovalPrompt({ execution, onResolve }) {
|
|
254
|
+
useInput((input, key) => {
|
|
255
|
+
if (input === "y" || input === "Y") {
|
|
256
|
+
onResolve(true);
|
|
257
|
+
} else if (input === "n" || input === "N" || key.escape) {
|
|
258
|
+
onResolve(false);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
const description = formatArgs2(execution.tool, execution.args);
|
|
262
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingLeft: 2, paddingY: 0, children: [
|
|
263
|
+
/* @__PURE__ */ jsxs6(Box6, { gap: 1, children: [
|
|
264
|
+
/* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "\u26A0" }),
|
|
265
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: description })
|
|
266
|
+
] }),
|
|
267
|
+
execution.tool === "run_command" && execution.args.command && /* @__PURE__ */ jsx6(Box6, { paddingLeft: 2, children: /* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
|
|
268
|
+
"$ ",
|
|
269
|
+
execution.args.command
|
|
270
|
+
] }) }),
|
|
271
|
+
execution.tool === "write_file" && execution.args.content && /* @__PURE__ */ jsx6(Box6, { paddingLeft: 2, children: /* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
|
|
272
|
+
execution.args.content.split("\n").length,
|
|
273
|
+
" lines"
|
|
274
|
+
] }) }),
|
|
275
|
+
/* @__PURE__ */ jsxs6(Box6, { paddingLeft: 2, gap: 1, children: [
|
|
276
|
+
/* @__PURE__ */ jsx6(Text6, { color: "green", children: "[y]" }),
|
|
277
|
+
/* @__PURE__ */ jsx6(Text6, { children: "approve" }),
|
|
278
|
+
/* @__PURE__ */ jsx6(Text6, { color: "red", children: "[n]" }),
|
|
279
|
+
/* @__PURE__ */ jsx6(Text6, { children: "deny" })
|
|
280
|
+
] })
|
|
281
|
+
] });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/utils/api.ts
|
|
285
|
+
import OpenAI from "openai";
|
|
286
|
+
var fileTools = [
|
|
287
|
+
{
|
|
288
|
+
type: "function",
|
|
289
|
+
function: {
|
|
290
|
+
name: "read_file",
|
|
291
|
+
description: "Read the contents of a file",
|
|
292
|
+
parameters: {
|
|
293
|
+
type: "object",
|
|
294
|
+
properties: {
|
|
295
|
+
path: { type: "string", description: "The file path to read" }
|
|
296
|
+
},
|
|
297
|
+
required: ["path"]
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
type: "function",
|
|
303
|
+
function: {
|
|
304
|
+
name: "write_file",
|
|
305
|
+
description: "Write content to a file (creates or overwrites)",
|
|
306
|
+
parameters: {
|
|
307
|
+
type: "object",
|
|
308
|
+
properties: {
|
|
309
|
+
path: { type: "string", description: "The file path to write to" },
|
|
310
|
+
content: { type: "string", description: "The content to write" }
|
|
311
|
+
},
|
|
312
|
+
required: ["path", "content"]
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
type: "function",
|
|
318
|
+
function: {
|
|
319
|
+
name: "edit_file",
|
|
320
|
+
description: "Make targeted edits to a file by replacing specific text. For small single-location changes.",
|
|
321
|
+
parameters: {
|
|
322
|
+
type: "object",
|
|
323
|
+
properties: {
|
|
324
|
+
path: { type: "string", description: "The file path to edit" },
|
|
325
|
+
old_text: { type: "string", description: "The text to find and replace" },
|
|
326
|
+
new_text: { type: "string", description: "The replacement text" }
|
|
327
|
+
},
|
|
328
|
+
required: ["path", "old_text", "new_text"]
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
type: "function",
|
|
334
|
+
function: {
|
|
335
|
+
name: "apply_patch",
|
|
336
|
+
description: "Apply a unified diff patch to one or more files. Preferred for multi-line or multi-file changes. Uses standard unified diff format with fuzzy line matching (\xB120 line drift).",
|
|
337
|
+
parameters: {
|
|
338
|
+
type: "object",
|
|
339
|
+
properties: {
|
|
340
|
+
patch: {
|
|
341
|
+
type: "string",
|
|
342
|
+
description: "The unified diff patch text. Must include --- a/file and +++ b/file headers and @@ hunk headers."
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
required: ["patch"]
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
type: "function",
|
|
351
|
+
function: {
|
|
352
|
+
name: "list_files",
|
|
353
|
+
description: "List files in a directory",
|
|
354
|
+
parameters: {
|
|
355
|
+
type: "object",
|
|
356
|
+
properties: {
|
|
357
|
+
path: { type: "string", description: "The directory path (default: current directory)" },
|
|
358
|
+
recursive: { type: "boolean", description: "Whether to list recursively" }
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
type: "function",
|
|
365
|
+
function: {
|
|
366
|
+
name: "search_files",
|
|
367
|
+
description: "Search for text patterns across files. Uses ripgrep when available for fast results with context lines.",
|
|
368
|
+
parameters: {
|
|
369
|
+
type: "object",
|
|
370
|
+
properties: {
|
|
371
|
+
pattern: { type: "string", description: "The search pattern (regex supported)" },
|
|
372
|
+
path: { type: "string", description: "Directory to search in (default: current)" },
|
|
373
|
+
file_pattern: { type: "string", description: 'File glob pattern (e.g., "*.ts")' },
|
|
374
|
+
context_lines: { type: "number", description: "Number of context lines around matches (default: 2)" },
|
|
375
|
+
max_results: { type: "number", description: "Maximum number of matches to return (default: 50)" }
|
|
376
|
+
},
|
|
377
|
+
required: ["pattern"]
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
type: "function",
|
|
383
|
+
function: {
|
|
384
|
+
name: "run_command",
|
|
385
|
+
description: "Execute a shell command",
|
|
386
|
+
parameters: {
|
|
387
|
+
type: "object",
|
|
388
|
+
properties: {
|
|
389
|
+
command: { type: "string", description: "The command to execute" },
|
|
390
|
+
cwd: { type: "string", description: "Working directory (default: current)" }
|
|
391
|
+
},
|
|
392
|
+
required: ["command"]
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
];
|
|
397
|
+
async function streamChat(messages, systemMessage, model, callbacks) {
|
|
398
|
+
const apiKey = config.get("apiKey");
|
|
399
|
+
const baseUrl = config.get("apiBaseUrl") || "https://api.alia.onl";
|
|
400
|
+
const openai = new OpenAI({
|
|
401
|
+
apiKey,
|
|
402
|
+
baseURL: `${baseUrl}/v1`
|
|
403
|
+
});
|
|
404
|
+
const allMessages = [
|
|
405
|
+
{ role: "system", content: systemMessage },
|
|
406
|
+
...messages.map((m) => {
|
|
407
|
+
if (m.role === "tool") {
|
|
408
|
+
return { role: "tool", tool_call_id: m.tool_call_id, content: m.content };
|
|
409
|
+
} else if (m.tool_calls) {
|
|
410
|
+
return { role: "assistant", content: m.content || "", tool_calls: m.tool_calls };
|
|
411
|
+
}
|
|
412
|
+
return { role: m.role, content: m.content };
|
|
413
|
+
})
|
|
414
|
+
];
|
|
415
|
+
try {
|
|
416
|
+
const stream = await openai.chat.completions.create({
|
|
417
|
+
model,
|
|
418
|
+
messages: allMessages,
|
|
419
|
+
tools: fileTools,
|
|
420
|
+
stream: true
|
|
421
|
+
});
|
|
422
|
+
let fullContent = "";
|
|
423
|
+
const toolCalls = [];
|
|
424
|
+
const toolCallsMap = /* @__PURE__ */ new Map();
|
|
425
|
+
for await (const chunk of stream) {
|
|
426
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
427
|
+
if (!delta) continue;
|
|
428
|
+
if (delta.content) {
|
|
429
|
+
fullContent += delta.content;
|
|
430
|
+
callbacks.onContent(delta.content);
|
|
431
|
+
}
|
|
432
|
+
if (delta.tool_calls) {
|
|
433
|
+
for (const tc of delta.tool_calls) {
|
|
434
|
+
const index = tc.index ?? 0;
|
|
435
|
+
if (!toolCallsMap.has(index)) {
|
|
436
|
+
const newToolCall = {
|
|
437
|
+
id: tc.id || "",
|
|
438
|
+
type: "function",
|
|
439
|
+
function: {
|
|
440
|
+
name: tc.function?.name || "",
|
|
441
|
+
arguments: tc.function?.arguments || ""
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
toolCallsMap.set(index, newToolCall);
|
|
445
|
+
toolCalls.push(newToolCall);
|
|
446
|
+
} else {
|
|
447
|
+
const existingToolCall = toolCallsMap.get(index);
|
|
448
|
+
if (tc.function?.name) {
|
|
449
|
+
existingToolCall.function.name = tc.function.name;
|
|
450
|
+
}
|
|
451
|
+
if (tc.function?.arguments) {
|
|
452
|
+
existingToolCall.function.arguments += tc.function.arguments;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
callbacks.onDone(fullContent, toolCalls.length > 0 ? toolCalls : void 0);
|
|
459
|
+
} catch (error) {
|
|
460
|
+
callbacks.onError(error);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
17
463
|
|
|
18
464
|
// src/tools/executor.ts
|
|
19
|
-
import * as
|
|
20
|
-
import * as
|
|
465
|
+
import * as fs2 from "fs/promises";
|
|
466
|
+
import * as path2 from "path";
|
|
21
467
|
import { exec } from "child_process";
|
|
22
468
|
import { promisify } from "util";
|
|
23
469
|
import chalk from "chalk";
|
|
470
|
+
|
|
471
|
+
// src/tools/patch.ts
|
|
472
|
+
import * as fs from "fs/promises";
|
|
473
|
+
import * as path from "path";
|
|
474
|
+
function parsePatch(patchText) {
|
|
475
|
+
const files = [];
|
|
476
|
+
const lines = patchText.split("\n");
|
|
477
|
+
let currentFile = null;
|
|
478
|
+
let currentHunk = null;
|
|
479
|
+
for (let i = 0; i < lines.length; i++) {
|
|
480
|
+
const line = lines[i];
|
|
481
|
+
if (line.startsWith("--- ")) {
|
|
482
|
+
const nextLine = lines[i + 1];
|
|
483
|
+
if (nextLine && nextLine.startsWith("+++ ")) {
|
|
484
|
+
let filePath = nextLine.slice(4).trim();
|
|
485
|
+
if (filePath.startsWith("b/")) {
|
|
486
|
+
filePath = filePath.slice(2);
|
|
487
|
+
}
|
|
488
|
+
currentFile = { filePath, hunks: [] };
|
|
489
|
+
files.push(currentFile);
|
|
490
|
+
i++;
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const hunkMatch = line.match(/^@@ -(\d+)(?:,\d+)? \+\d+(?:,\d+)? @@/);
|
|
495
|
+
if (hunkMatch && currentFile) {
|
|
496
|
+
currentHunk = {
|
|
497
|
+
oldStart: parseInt(hunkMatch[1], 10),
|
|
498
|
+
oldLines: [],
|
|
499
|
+
newLines: []
|
|
500
|
+
};
|
|
501
|
+
currentFile.hunks.push(currentHunk);
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (currentHunk) {
|
|
505
|
+
if (line.startsWith("-")) {
|
|
506
|
+
currentHunk.oldLines.push(line.slice(1));
|
|
507
|
+
} else if (line.startsWith("+")) {
|
|
508
|
+
currentHunk.newLines.push(line.slice(1));
|
|
509
|
+
} else if (line.startsWith(" ")) {
|
|
510
|
+
currentHunk.oldLines.push(line.slice(1));
|
|
511
|
+
currentHunk.newLines.push(line.slice(1));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return files;
|
|
516
|
+
}
|
|
517
|
+
function findHunkPosition(fileLines, hunkOldLines, expectedStart, drift = 20) {
|
|
518
|
+
const start = expectedStart - 1;
|
|
519
|
+
if (matchesAt(fileLines, hunkOldLines, start)) {
|
|
520
|
+
return start;
|
|
521
|
+
}
|
|
522
|
+
for (let offset = 1; offset <= drift; offset++) {
|
|
523
|
+
if (matchesAt(fileLines, hunkOldLines, start + offset)) {
|
|
524
|
+
return start + offset;
|
|
525
|
+
}
|
|
526
|
+
if (matchesAt(fileLines, hunkOldLines, start - offset)) {
|
|
527
|
+
return start - offset;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return -1;
|
|
531
|
+
}
|
|
532
|
+
function matchesAt(fileLines, hunkOldLines, position) {
|
|
533
|
+
if (position < 0 || position + hunkOldLines.length > fileLines.length) {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
for (let i = 0; i < hunkOldLines.length; i++) {
|
|
537
|
+
const fileLine = fileLines[position + i].trimEnd();
|
|
538
|
+
const hunkLine = hunkOldLines[i].trimEnd();
|
|
539
|
+
if (fileLine !== hunkLine) {
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
async function applyPatch(patchText, basePath) {
|
|
546
|
+
const filePatches = parsePatch(patchText);
|
|
547
|
+
const results = [];
|
|
548
|
+
let allSuccess = true;
|
|
549
|
+
for (const filePatch of filePatches) {
|
|
550
|
+
const absolutePath = path.resolve(basePath, filePatch.filePath);
|
|
551
|
+
try {
|
|
552
|
+
let content;
|
|
553
|
+
try {
|
|
554
|
+
content = await fs.readFile(absolutePath, "utf-8");
|
|
555
|
+
} catch {
|
|
556
|
+
const allAdditions = filePatch.hunks.every((h) => h.oldLines.length === 0);
|
|
557
|
+
if (allAdditions) {
|
|
558
|
+
const newContent = filePatch.hunks.map((h) => h.newLines.join("\n")).join("\n");
|
|
559
|
+
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
560
|
+
await fs.writeFile(absolutePath, newContent, "utf-8");
|
|
561
|
+
results.push({ file: filePatch.filePath, success: true, message: "Created new file" });
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
throw new Error(`File not found: ${filePatch.filePath}`);
|
|
565
|
+
}
|
|
566
|
+
let fileLines = content.split("\n");
|
|
567
|
+
const sortedHunks = [...filePatch.hunks].sort((a, b) => b.oldStart - a.oldStart);
|
|
568
|
+
for (const hunk of sortedHunks) {
|
|
569
|
+
const position = findHunkPosition(fileLines, hunk.oldLines, hunk.oldStart);
|
|
570
|
+
if (position === -1) {
|
|
571
|
+
throw new Error(
|
|
572
|
+
`Could not find match for hunk at line ${hunk.oldStart} in ${filePatch.filePath}`
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
fileLines.splice(position, hunk.oldLines.length, ...hunk.newLines);
|
|
576
|
+
}
|
|
577
|
+
await fs.writeFile(absolutePath, fileLines.join("\n"), "utf-8");
|
|
578
|
+
results.push({
|
|
579
|
+
file: filePatch.filePath,
|
|
580
|
+
success: true,
|
|
581
|
+
message: `Applied ${filePatch.hunks.length} hunk(s)`
|
|
582
|
+
});
|
|
583
|
+
} catch (error) {
|
|
584
|
+
allSuccess = false;
|
|
585
|
+
results.push({ file: filePatch.filePath, success: false, message: error.message });
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return { success: allSuccess, results };
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/tools/executor.ts
|
|
24
592
|
var execAsync = promisify(exec);
|
|
25
593
|
async function executeTool(name, args) {
|
|
26
594
|
try {
|
|
27
595
|
switch (name) {
|
|
28
596
|
case "read_file":
|
|
29
|
-
return await
|
|
597
|
+
return await readFile3(args.path);
|
|
30
598
|
case "write_file":
|
|
31
|
-
return await
|
|
599
|
+
return await writeFile3(args.path, args.content);
|
|
32
600
|
case "edit_file":
|
|
33
601
|
return await editFile(args.path, args.old_text, args.new_text);
|
|
602
|
+
case "apply_patch":
|
|
603
|
+
return await applyPatchTool(args.patch);
|
|
34
604
|
case "list_files":
|
|
35
605
|
return await listFiles(args.path, args.recursive);
|
|
36
606
|
case "search_files":
|
|
37
|
-
return await searchFiles(args.pattern, args.path, args.file_pattern);
|
|
607
|
+
return await searchFiles(args.pattern, args.path, args.file_pattern, args.context_lines, args.max_results);
|
|
38
608
|
case "run_command":
|
|
39
609
|
return await runCommand(args.command, args.cwd);
|
|
40
610
|
default:
|
|
@@ -44,36 +614,61 @@ async function executeTool(name, args) {
|
|
|
44
614
|
return { success: false, result: error.message };
|
|
45
615
|
}
|
|
46
616
|
}
|
|
47
|
-
async function
|
|
48
|
-
const absolutePath =
|
|
49
|
-
const content = await
|
|
617
|
+
async function readFile3(filePath) {
|
|
618
|
+
const absolutePath = path2.resolve(process.cwd(), filePath);
|
|
619
|
+
const content = await fs2.readFile(absolutePath, "utf-8");
|
|
50
620
|
return { success: true, result: content };
|
|
51
621
|
}
|
|
52
|
-
async function
|
|
53
|
-
const absolutePath =
|
|
54
|
-
await
|
|
55
|
-
await
|
|
622
|
+
async function writeFile3(filePath, content) {
|
|
623
|
+
const absolutePath = path2.resolve(process.cwd(), filePath);
|
|
624
|
+
await fs2.mkdir(path2.dirname(absolutePath), { recursive: true });
|
|
625
|
+
await fs2.writeFile(absolutePath, content, "utf-8");
|
|
56
626
|
return { success: true, result: `File written: ${filePath}` };
|
|
57
627
|
}
|
|
58
628
|
async function editFile(filePath, oldText, newText) {
|
|
59
|
-
const absolutePath =
|
|
60
|
-
const content = await
|
|
61
|
-
if (
|
|
62
|
-
|
|
629
|
+
const absolutePath = path2.resolve(process.cwd(), filePath);
|
|
630
|
+
const content = await fs2.readFile(absolutePath, "utf-8");
|
|
631
|
+
if (content.includes(oldText)) {
|
|
632
|
+
const newContent = content.replace(oldText, newText);
|
|
633
|
+
await fs2.writeFile(absolutePath, newContent, "utf-8");
|
|
634
|
+
return { success: true, result: `File edited: ${filePath}` };
|
|
635
|
+
}
|
|
636
|
+
const normalizedOld = oldText.replace(/\s+/g, " ").trim();
|
|
637
|
+
const lines = content.split("\n");
|
|
638
|
+
let matchStart = -1;
|
|
639
|
+
let matchEnd = -1;
|
|
640
|
+
for (let i = 0; i < lines.length; i++) {
|
|
641
|
+
for (let j = i; j < lines.length; j++) {
|
|
642
|
+
const block = lines.slice(i, j + 1).join("\n");
|
|
643
|
+
if (block.replace(/\s+/g, " ").trim() === normalizedOld) {
|
|
644
|
+
matchStart = i;
|
|
645
|
+
matchEnd = j;
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (matchStart >= 0) break;
|
|
63
650
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
651
|
+
if (matchStart >= 0) {
|
|
652
|
+
const newLines = [...lines.slice(0, matchStart), ...newText.split("\n"), ...lines.slice(matchEnd + 1)];
|
|
653
|
+
await fs2.writeFile(absolutePath, newLines.join("\n"), "utf-8");
|
|
654
|
+
return { success: true, result: `File edited (fuzzy match): ${filePath}` };
|
|
655
|
+
}
|
|
656
|
+
return { success: false, result: `Text not found in file: "${oldText.slice(0, 50)}..."` };
|
|
657
|
+
}
|
|
658
|
+
async function applyPatchTool(patchText) {
|
|
659
|
+
const result = await applyPatch(patchText, process.cwd());
|
|
660
|
+
const summary = result.results.map((r) => `${r.success ? "\u2713" : "\u2717"} ${r.file}: ${r.message}`).join("\n");
|
|
661
|
+
return { success: result.success, result: summary };
|
|
67
662
|
}
|
|
68
663
|
async function listFiles(dirPath = ".", recursive = false) {
|
|
69
|
-
const absolutePath =
|
|
664
|
+
const absolutePath = path2.resolve(process.cwd(), dirPath);
|
|
70
665
|
if (recursive) {
|
|
71
666
|
const files = [];
|
|
72
667
|
async function walk(dir) {
|
|
73
|
-
const entries = await
|
|
668
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
74
669
|
for (const entry of entries) {
|
|
75
|
-
const fullPath =
|
|
76
|
-
const relativePath =
|
|
670
|
+
const fullPath = path2.join(dir, entry.name);
|
|
671
|
+
const relativePath = path2.relative(absolutePath, fullPath);
|
|
77
672
|
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") {
|
|
78
673
|
continue;
|
|
79
674
|
}
|
|
@@ -88,19 +683,62 @@ async function listFiles(dirPath = ".", recursive = false) {
|
|
|
88
683
|
await walk(absolutePath);
|
|
89
684
|
return { success: true, result: files.join("\n") };
|
|
90
685
|
} else {
|
|
91
|
-
const entries = await
|
|
686
|
+
const entries = await fs2.readdir(absolutePath, { withFileTypes: true });
|
|
92
687
|
const files = entries.map((e) => e.name + (e.isDirectory() ? "/" : ""));
|
|
93
688
|
return { success: true, result: files.join("\n") };
|
|
94
689
|
}
|
|
95
690
|
}
|
|
96
|
-
async function searchFiles(pattern, dirPath = ".", filePattern) {
|
|
97
|
-
const absolutePath =
|
|
691
|
+
async function searchFiles(pattern, dirPath = ".", filePattern, contextLines = 2, maxResults = 50) {
|
|
692
|
+
const absolutePath = path2.resolve(process.cwd(), dirPath);
|
|
693
|
+
try {
|
|
694
|
+
const rgArgs = [
|
|
695
|
+
"--json",
|
|
696
|
+
"-C",
|
|
697
|
+
String(contextLines),
|
|
698
|
+
"-m",
|
|
699
|
+
String(maxResults),
|
|
700
|
+
"--no-heading"
|
|
701
|
+
];
|
|
702
|
+
if (filePattern) {
|
|
703
|
+
rgArgs.push("-g", filePattern);
|
|
704
|
+
}
|
|
705
|
+
rgArgs.push("--", pattern, absolutePath);
|
|
706
|
+
const { stdout } = await execAsync(`rg ${rgArgs.map((a) => `'${a}'`).join(" ")}`, {
|
|
707
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
708
|
+
timeout: 3e4
|
|
709
|
+
});
|
|
710
|
+
const results2 = [];
|
|
711
|
+
const lines = stdout.trim().split("\n");
|
|
712
|
+
for (const line of lines) {
|
|
713
|
+
try {
|
|
714
|
+
const data = JSON.parse(line);
|
|
715
|
+
if (data.type === "match") {
|
|
716
|
+
const relPath = path2.relative(absolutePath, data.data.path.text);
|
|
717
|
+
const lineNum = data.data.line_number;
|
|
718
|
+
const text = data.data.lines.text.trimEnd();
|
|
719
|
+
results2.push(`${relPath}:${lineNum}: ${text}`);
|
|
720
|
+
} else if (data.type === "context") {
|
|
721
|
+
const relPath = path2.relative(absolutePath, data.data.path.text);
|
|
722
|
+
const lineNum = data.data.line_number;
|
|
723
|
+
const text = data.data.lines.text.trimEnd();
|
|
724
|
+
results2.push(`${relPath}:${lineNum} ${text}`);
|
|
725
|
+
}
|
|
726
|
+
} catch {
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (results2.length === 0) {
|
|
730
|
+
return { success: true, result: "No matches found." };
|
|
731
|
+
}
|
|
732
|
+
return { success: true, result: results2.join("\n") };
|
|
733
|
+
} catch {
|
|
734
|
+
}
|
|
98
735
|
const regex = new RegExp(pattern, "gi");
|
|
99
736
|
const results = [];
|
|
737
|
+
const fileMatchCounts = /* @__PURE__ */ new Map();
|
|
100
738
|
async function searchDir(dir) {
|
|
101
|
-
const entries = await
|
|
739
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
102
740
|
for (const entry of entries) {
|
|
103
|
-
const fullPath =
|
|
741
|
+
const fullPath = path2.join(dir, entry.name);
|
|
104
742
|
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") {
|
|
105
743
|
continue;
|
|
106
744
|
}
|
|
@@ -108,21 +746,39 @@ async function searchFiles(pattern, dirPath = ".", filePattern) {
|
|
|
108
746
|
await searchDir(fullPath);
|
|
109
747
|
} else {
|
|
110
748
|
if (filePattern) {
|
|
111
|
-
const ext =
|
|
749
|
+
const ext = path2.extname(entry.name);
|
|
112
750
|
const patternExt = filePattern.replace("*", "");
|
|
113
751
|
if (ext !== patternExt && !entry.name.match(filePattern.replace("*", ".*"))) {
|
|
114
752
|
continue;
|
|
115
753
|
}
|
|
116
754
|
}
|
|
117
755
|
try {
|
|
118
|
-
const content = await
|
|
756
|
+
const content = await fs2.readFile(fullPath, "utf-8");
|
|
757
|
+
if (content.includes("\0")) continue;
|
|
119
758
|
const lines = content.split("\n");
|
|
759
|
+
const matchIndices = [];
|
|
120
760
|
lines.forEach((line, index) => {
|
|
761
|
+
regex.lastIndex = 0;
|
|
121
762
|
if (regex.test(line)) {
|
|
122
|
-
|
|
123
|
-
results.push(`${relativePath}:${index + 1}: ${line.trim()}`);
|
|
763
|
+
matchIndices.push(index);
|
|
124
764
|
}
|
|
125
765
|
});
|
|
766
|
+
if (matchIndices.length > 0) {
|
|
767
|
+
const relativePath = path2.relative(absolutePath, fullPath);
|
|
768
|
+
fileMatchCounts.set(relativePath, matchIndices.length);
|
|
769
|
+
for (const idx of matchIndices) {
|
|
770
|
+
const start = Math.max(0, idx - contextLines);
|
|
771
|
+
const end = Math.min(lines.length - 1, idx + contextLines);
|
|
772
|
+
for (let i = start; i <= end; i++) {
|
|
773
|
+
results.push({
|
|
774
|
+
file: relativePath,
|
|
775
|
+
line: i + 1,
|
|
776
|
+
text: lines[i].trimEnd(),
|
|
777
|
+
isMatch: i === idx
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
126
782
|
} catch {
|
|
127
783
|
}
|
|
128
784
|
}
|
|
@@ -132,11 +788,14 @@ async function searchFiles(pattern, dirPath = ".", filePattern) {
|
|
|
132
788
|
if (results.length === 0) {
|
|
133
789
|
return { success: true, result: "No matches found." };
|
|
134
790
|
}
|
|
135
|
-
|
|
136
|
-
|
|
791
|
+
const formatted = results.slice(0, maxResults * (1 + contextLines * 2)).map((r) => `${r.file}:${r.line}${r.isMatch ? ":" : " "} ${r.text}`).join("\n");
|
|
792
|
+
const totalMatches = Array.from(fileMatchCounts.values()).reduce((a, b) => a + b, 0);
|
|
793
|
+
const footer = `
|
|
794
|
+
(${totalMatches} matches in ${fileMatchCounts.size} files)`;
|
|
795
|
+
return { success: true, result: formatted + footer };
|
|
137
796
|
}
|
|
138
797
|
async function runCommand(command, cwd) {
|
|
139
|
-
const workingDir = cwd ?
|
|
798
|
+
const workingDir = cwd ? path2.resolve(process.cwd(), cwd) : process.cwd();
|
|
140
799
|
try {
|
|
141
800
|
const { stdout, stderr } = await execAsync(command, {
|
|
142
801
|
cwd: workingDir,
|
|
@@ -156,113 +815,123 @@ ${error.stderr}` : "") || error.message
|
|
|
156
815
|
};
|
|
157
816
|
}
|
|
158
817
|
}
|
|
159
|
-
function formatToolCall(name, args) {
|
|
160
|
-
const labels = {
|
|
161
|
-
read_file: "Reading file",
|
|
162
|
-
write_file: "Writing file",
|
|
163
|
-
edit_file: "Editing file",
|
|
164
|
-
list_files: "Listing files",
|
|
165
|
-
search_files: "Searching files",
|
|
166
|
-
run_command: "Running command"
|
|
167
|
-
};
|
|
168
|
-
const label = labels[name] || name;
|
|
169
|
-
const argStr = Object.entries(args).map(([k, v]) => `${k}: ${typeof v === "string" ? v.slice(0, 50) : v}`).join(", ");
|
|
170
|
-
return `${chalk.cyan("\u2192")} ${chalk.bold(label)}: ${chalk.gray(argStr)}`;
|
|
171
|
-
}
|
|
172
818
|
|
|
173
|
-
// src/utils/
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
function
|
|
184
|
-
|
|
819
|
+
// src/utils/approval.ts
|
|
820
|
+
var TOOL_CATEGORIES = {
|
|
821
|
+
read_file: "read_only",
|
|
822
|
+
list_files: "read_only",
|
|
823
|
+
search_files: "read_only",
|
|
824
|
+
write_file: "file_write",
|
|
825
|
+
edit_file: "file_write",
|
|
826
|
+
apply_patch: "file_write",
|
|
827
|
+
run_command: "shell"
|
|
828
|
+
};
|
|
829
|
+
function categorize(toolName) {
|
|
830
|
+
return TOOL_CATEGORIES[toolName] || "shell";
|
|
185
831
|
}
|
|
186
|
-
function
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const preview = result.slice(0, 100).replace(/\n/g, " ");
|
|
198
|
-
console.log(` ${status} ${chalk2.gray(preview)}${result.length > 100 ? "..." : ""}`);
|
|
199
|
-
console.log();
|
|
200
|
-
}
|
|
201
|
-
var statusInterval = null;
|
|
202
|
-
var startTime = 0;
|
|
203
|
-
function showThinkingStatus(message) {
|
|
204
|
-
startTime = Date.now();
|
|
205
|
-
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
206
|
-
let frameIndex = 0;
|
|
207
|
-
statusInterval = setInterval(() => {
|
|
208
|
-
const elapsed = Math.floor((Date.now() - startTime) / 1e3);
|
|
209
|
-
const frame = frames[frameIndex % frames.length];
|
|
210
|
-
frameIndex++;
|
|
211
|
-
readline.clearLine(process.stdout, 0);
|
|
212
|
-
readline.cursorTo(process.stdout, 0);
|
|
213
|
-
process.stdout.write(
|
|
214
|
-
chalk2.cyan(frame) + " " + chalk2.bold(message) + chalk2.gray(` (esc to cancel, ${elapsed}s)`)
|
|
215
|
-
);
|
|
216
|
-
}, 80);
|
|
217
|
-
}
|
|
218
|
-
function hideThinkingStatus() {
|
|
219
|
-
if (statusInterval) {
|
|
220
|
-
clearInterval(statusInterval);
|
|
221
|
-
statusInterval = null;
|
|
222
|
-
readline.clearLine(process.stdout, 0);
|
|
223
|
-
readline.cursorTo(process.stdout, 0);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
function printStatusBar(cwd, model, contextPercent) {
|
|
227
|
-
const width = process.stdout.columns || 80;
|
|
228
|
-
const cwdPart = chalk2.cyan(shortenPath(cwd));
|
|
229
|
-
const modelPart = chalk2.magenta(`${model} (${contextPercent}% context left)`);
|
|
230
|
-
const padding = width - stripAnsi(cwdPart).length - stripAnsi(modelPart).length - 4;
|
|
231
|
-
const spacer = " ".repeat(Math.max(padding, 2));
|
|
232
|
-
console.log();
|
|
233
|
-
console.log(chalk2.gray("\u2500".repeat(width)));
|
|
234
|
-
console.log(`${cwdPart}${spacer}${modelPart}`);
|
|
235
|
-
}
|
|
236
|
-
function shortenPath(p) {
|
|
237
|
-
const home = process.env.HOME || "";
|
|
238
|
-
if (p.startsWith(home)) {
|
|
239
|
-
return "~" + p.slice(home.length);
|
|
832
|
+
function needsApproval(toolName, mode) {
|
|
833
|
+
const category = categorize(toolName);
|
|
834
|
+
switch (mode) {
|
|
835
|
+
case "full-auto":
|
|
836
|
+
return false;
|
|
837
|
+
case "auto-edit":
|
|
838
|
+
return category === "shell";
|
|
839
|
+
case "suggest":
|
|
840
|
+
return category === "file_write" || category === "shell";
|
|
841
|
+
default:
|
|
842
|
+
return true;
|
|
240
843
|
}
|
|
241
|
-
return p;
|
|
242
|
-
}
|
|
243
|
-
function stripAnsi(str) {
|
|
244
|
-
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
245
|
-
}
|
|
246
|
-
function printAssistantPrefix() {
|
|
247
|
-
process.stdout.write(chalk2.magenta("\u2726 "));
|
|
248
844
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
845
|
+
|
|
846
|
+
// src/utils/conversation.ts
|
|
847
|
+
async function processConversation(opts) {
|
|
848
|
+
const { messages, systemMessage, model, approvalMode, onEvent, requestApproval, isActive } = opts;
|
|
849
|
+
while (isActive()) {
|
|
850
|
+
let fullContent = "";
|
|
851
|
+
let toolCalls;
|
|
852
|
+
onEvent({ type: "thinking" });
|
|
853
|
+
try {
|
|
854
|
+
await streamChat(messages, systemMessage, model, {
|
|
855
|
+
onContent: (content) => {
|
|
856
|
+
if (!isActive()) return;
|
|
857
|
+
fullContent += content;
|
|
858
|
+
onEvent({ type: "content", text: content });
|
|
859
|
+
},
|
|
860
|
+
onToolCall: () => {
|
|
861
|
+
},
|
|
862
|
+
onDone: (_content, tcs) => {
|
|
863
|
+
toolCalls = tcs;
|
|
864
|
+
},
|
|
865
|
+
onError: (error) => {
|
|
866
|
+
onEvent({ type: "error", message: error.message });
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
} catch (error) {
|
|
870
|
+
onEvent({ type: "error", message: error.message });
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
if (!isActive()) break;
|
|
874
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
875
|
+
messages.push({
|
|
876
|
+
role: "assistant",
|
|
877
|
+
content: fullContent,
|
|
878
|
+
tool_calls: toolCalls
|
|
879
|
+
});
|
|
880
|
+
for (const tc of toolCalls) {
|
|
881
|
+
if (!isActive()) break;
|
|
882
|
+
const args = JSON.parse(tc.function.arguments);
|
|
883
|
+
const execution = {
|
|
884
|
+
id: tc.id,
|
|
885
|
+
tool: tc.function.name,
|
|
886
|
+
args
|
|
887
|
+
};
|
|
888
|
+
onEvent({ type: "tool_start", execution });
|
|
889
|
+
if (needsApproval(tc.function.name, approvalMode)) {
|
|
890
|
+
const approved = await requestApproval(execution);
|
|
891
|
+
if (!approved) {
|
|
892
|
+
execution.approved = false;
|
|
893
|
+
execution.success = false;
|
|
894
|
+
execution.result = "User declined this action.";
|
|
895
|
+
messages.push({
|
|
896
|
+
role: "tool",
|
|
897
|
+
tool_call_id: tc.id,
|
|
898
|
+
content: "User declined this action."
|
|
899
|
+
});
|
|
900
|
+
onEvent({ type: "tool_done", execution });
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
execution.approved = true;
|
|
904
|
+
} else {
|
|
905
|
+
execution.approved = true;
|
|
906
|
+
}
|
|
907
|
+
const result = await executeTool(tc.function.name, args);
|
|
908
|
+
execution.result = result.result;
|
|
909
|
+
execution.success = result.success;
|
|
910
|
+
messages.push({
|
|
911
|
+
role: "tool",
|
|
912
|
+
tool_call_id: tc.id,
|
|
913
|
+
content: result.result
|
|
914
|
+
});
|
|
915
|
+
onEvent({ type: "tool_done", execution });
|
|
916
|
+
}
|
|
917
|
+
continue;
|
|
918
|
+
} else {
|
|
919
|
+
if (fullContent) {
|
|
920
|
+
messages.push({ role: "assistant", content: fullContent });
|
|
921
|
+
}
|
|
922
|
+
onEvent({ type: "done", content: fullContent });
|
|
923
|
+
break;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
257
926
|
}
|
|
258
927
|
|
|
259
928
|
// src/utils/context.ts
|
|
260
|
-
import * as
|
|
261
|
-
import * as
|
|
929
|
+
import * as fs3 from "fs/promises";
|
|
930
|
+
import * as path3 from "path";
|
|
262
931
|
import { exec as exec2 } from "child_process";
|
|
263
932
|
import { promisify as promisify2 } from "util";
|
|
264
933
|
var execAsync2 = promisify2(exec2);
|
|
265
|
-
function buildSystemMessage(model, codebaseContext) {
|
|
934
|
+
function buildSystemMessage(model, codebaseContext, projectInstructions) {
|
|
266
935
|
let systemMessage = `You are Codea, an expert AI coding assistant created by Alia. You help developers write, debug, refactor, and understand code directly in their terminal.
|
|
267
936
|
|
|
268
937
|
## Core Principles
|
|
@@ -276,22 +945,30 @@ You have powerful tools to interact with the user's workspace:
|
|
|
276
945
|
|
|
277
946
|
- **read_file**: Read file contents. Use to understand existing code before making changes.
|
|
278
947
|
- **write_file**: Create new files or completely rewrite existing ones.
|
|
279
|
-
- **edit_file**: Make precise, targeted changes
|
|
948
|
+
- **edit_file**: Make precise, targeted changes using exact text match and replace.
|
|
949
|
+
- **apply_patch**: Apply unified diff patches to files. Preferred for multi-line or multi-file changes. Supports fuzzy line matching.
|
|
280
950
|
- **list_files**: Explore directory structure. Use to understand project layout.
|
|
281
|
-
- **search_files**: Find text/patterns across the codebase
|
|
951
|
+
- **search_files**: Find text/patterns across the codebase with context lines. Uses ripgrep when available.
|
|
282
952
|
- **run_command**: Execute shell commands (build, test, git, npm, etc.)
|
|
283
953
|
|
|
284
954
|
## Best Practices
|
|
285
955
|
1. **Read before writing**: Always read relevant files before modifying them.
|
|
286
956
|
2. **Minimal changes**: Make the smallest change necessary to accomplish the task.
|
|
287
957
|
3. **Preserve style**: Match existing formatting, naming conventions, and patterns.
|
|
288
|
-
4. **
|
|
958
|
+
4. **Prefer apply_patch**: For multi-line edits, use apply_patch with unified diff format.
|
|
959
|
+
5. **Explain when helpful**: For complex changes, briefly explain the approach.
|
|
289
960
|
|
|
290
961
|
## Response Style
|
|
291
962
|
- Use markdown for formatting code blocks, lists, and emphasis.
|
|
292
963
|
- For code explanations, be thorough but focused.
|
|
293
964
|
- For code changes, be precise and action-oriented.
|
|
294
965
|
- If unsure about requirements, ask clarifying questions.`;
|
|
966
|
+
if (projectInstructions) {
|
|
967
|
+
systemMessage += `
|
|
968
|
+
|
|
969
|
+
## Project Instructions (from CODEA.md)
|
|
970
|
+
${projectInstructions}`;
|
|
971
|
+
}
|
|
295
972
|
if (codebaseContext) {
|
|
296
973
|
systemMessage += `
|
|
297
974
|
|
|
@@ -300,12 +977,57 @@ ${codebaseContext}`;
|
|
|
300
977
|
}
|
|
301
978
|
return systemMessage;
|
|
302
979
|
}
|
|
980
|
+
async function loadProjectInstructions() {
|
|
981
|
+
const parts = [];
|
|
982
|
+
const home = process.env.HOME || "";
|
|
983
|
+
if (home) {
|
|
984
|
+
const globalPath = path3.join(home, ".codea", "CODEA.md");
|
|
985
|
+
try {
|
|
986
|
+
const content = await fs3.readFile(globalPath, "utf-8");
|
|
987
|
+
if (content.trim()) {
|
|
988
|
+
parts.push(`# Global Instructions (~/.codea/CODEA.md)
|
|
989
|
+
${content.trim()}`);
|
|
990
|
+
}
|
|
991
|
+
} catch {
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
let gitRoot = "";
|
|
995
|
+
try {
|
|
996
|
+
const { stdout } = await execAsync2("git rev-parse --show-toplevel", { cwd: process.cwd() });
|
|
997
|
+
gitRoot = stdout.trim();
|
|
998
|
+
} catch {
|
|
999
|
+
}
|
|
1000
|
+
if (gitRoot) {
|
|
1001
|
+
const projectPath = path3.join(gitRoot, "CODEA.md");
|
|
1002
|
+
try {
|
|
1003
|
+
const content = await fs3.readFile(projectPath, "utf-8");
|
|
1004
|
+
if (content.trim()) {
|
|
1005
|
+
parts.push(`# Project Instructions (CODEA.md)
|
|
1006
|
+
${content.trim()}`);
|
|
1007
|
+
}
|
|
1008
|
+
} catch {
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
const cwd = process.cwd();
|
|
1012
|
+
if (cwd !== gitRoot) {
|
|
1013
|
+
const dirPath = path3.join(cwd, "CODEA.md");
|
|
1014
|
+
try {
|
|
1015
|
+
const content = await fs3.readFile(dirPath, "utf-8");
|
|
1016
|
+
if (content.trim()) {
|
|
1017
|
+
parts.push(`# Directory Instructions (./CODEA.md)
|
|
1018
|
+
${content.trim()}`);
|
|
1019
|
+
}
|
|
1020
|
+
} catch {
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return parts.join("\n\n---\n\n");
|
|
1024
|
+
}
|
|
303
1025
|
async function getCodebaseContext() {
|
|
304
1026
|
const cwd = process.cwd();
|
|
305
1027
|
const contextParts = [];
|
|
306
1028
|
try {
|
|
307
|
-
const pkgPath =
|
|
308
|
-
const pkgContent = await
|
|
1029
|
+
const pkgPath = path3.join(cwd, "package.json");
|
|
1030
|
+
const pkgContent = await fs3.readFile(pkgPath, "utf-8");
|
|
309
1031
|
const pkg = JSON.parse(pkgContent);
|
|
310
1032
|
contextParts.push(`Project: ${pkg.name || "Unknown"} (${pkg.description || "No description"})`);
|
|
311
1033
|
if (pkg.dependencies) {
|
|
@@ -342,16 +1064,16 @@ async function getRelevantFiles(dir, maxFiles = 20) {
|
|
|
342
1064
|
async function walk(currentDir, depth = 0) {
|
|
343
1065
|
if (depth > 3 || relevantFiles.length >= maxFiles) return;
|
|
344
1066
|
try {
|
|
345
|
-
const entries = await
|
|
1067
|
+
const entries = await fs3.readdir(currentDir, { withFileTypes: true });
|
|
346
1068
|
for (const entry of entries) {
|
|
347
1069
|
if (relevantFiles.length >= maxFiles) break;
|
|
348
1070
|
if (ignoreDirs.includes(entry.name)) continue;
|
|
349
|
-
const fullPath =
|
|
350
|
-
const relativePath =
|
|
1071
|
+
const fullPath = path3.join(currentDir, entry.name);
|
|
1072
|
+
const relativePath = path3.relative(dir, fullPath);
|
|
351
1073
|
if (entry.isDirectory()) {
|
|
352
1074
|
await walk(fullPath, depth + 1);
|
|
353
1075
|
} else {
|
|
354
|
-
const ext =
|
|
1076
|
+
const ext = path3.extname(entry.name);
|
|
355
1077
|
if (relevantExtensions.includes(ext) || entry.name === "README.md" || entry.name === "package.json") {
|
|
356
1078
|
relevantFiles.push(relativePath);
|
|
357
1079
|
}
|
|
@@ -364,326 +1086,369 @@ async function getRelevantFiles(dir, maxFiles = 20) {
|
|
|
364
1086
|
return relevantFiles;
|
|
365
1087
|
}
|
|
366
1088
|
|
|
367
|
-
// src/
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
let contextUsed = 0;
|
|
373
|
-
const maxContext = 128e3;
|
|
374
|
-
printTips();
|
|
375
|
-
let codebaseContext = "";
|
|
376
|
-
if (options.context !== false) {
|
|
377
|
-
printInfo("Analyzing codebase...");
|
|
378
|
-
codebaseContext = await getCodebaseContext();
|
|
379
|
-
if (codebaseContext) {
|
|
380
|
-
printInfo(`Loaded context from ${codebaseContext.split("\n").length} files`);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
const rl = readline2.createInterface({
|
|
384
|
-
input: process.stdin,
|
|
385
|
-
output: process.stdout,
|
|
386
|
-
terminal: true
|
|
387
|
-
});
|
|
388
|
-
rl.on("SIGINT", () => {
|
|
389
|
-
if (isProcessing) {
|
|
390
|
-
isProcessing = false;
|
|
391
|
-
hideThinkingStatus();
|
|
392
|
-
console.log(chalk3.yellow("\nCancelled."));
|
|
393
|
-
printPrompt();
|
|
394
|
-
} else {
|
|
395
|
-
console.log(chalk3.gray("\nGoodbye!"));
|
|
396
|
-
process.exit(0);
|
|
397
|
-
}
|
|
398
|
-
});
|
|
399
|
-
const askQuestion = () => {
|
|
400
|
-
printPrompt();
|
|
401
|
-
rl.question("", async (input) => {
|
|
402
|
-
const trimmed = input.trim();
|
|
403
|
-
if (!trimmed) {
|
|
404
|
-
askQuestion();
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
if (trimmed.startsWith("/")) {
|
|
408
|
-
await handleSlashCommand(trimmed, messages, session, options);
|
|
409
|
-
askQuestion();
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
messages.push({ role: "user", content: trimmed });
|
|
413
|
-
isProcessing = true;
|
|
414
|
-
const systemMessage = buildSystemMessage(options.model, codebaseContext);
|
|
415
|
-
await processConversation(messages, systemMessage, options.model, () => isProcessing);
|
|
416
|
-
isProcessing = false;
|
|
417
|
-
session.messages = messages.map((m) => ({ role: m.role, content: m.content }));
|
|
418
|
-
session.title = messages[0]?.content.slice(0, 50) || "New conversation";
|
|
419
|
-
session.updatedAt = Date.now();
|
|
420
|
-
saveSession(session);
|
|
421
|
-
contextUsed = Math.min(95, Math.floor(messages.reduce((acc, m) => acc + m.content.length, 0) / maxContext * 100));
|
|
422
|
-
printStatusBar(process.cwd(), getModelDisplayName(options.model), 100 - contextUsed);
|
|
423
|
-
askQuestion();
|
|
424
|
-
});
|
|
425
|
-
};
|
|
426
|
-
askQuestion();
|
|
1089
|
+
// src/app.tsx
|
|
1090
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1091
|
+
var msgCounter = 0;
|
|
1092
|
+
function nextId() {
|
|
1093
|
+
return `msg-${++msgCounter}`;
|
|
427
1094
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
1095
|
+
function App({ options }) {
|
|
1096
|
+
const { exit } = useApp();
|
|
1097
|
+
const [displayMessages, setDisplayMessages] = useState3([]);
|
|
1098
|
+
const [isProcessing, setIsProcessing] = useState3(false);
|
|
1099
|
+
const [thinkingLabel, setThinkingLabel] = useState3("Thinking");
|
|
1100
|
+
const [approvalMode, setApprovalMode] = useState3(options.approvalMode);
|
|
1101
|
+
const [contextPercent, setContextPercent] = useState3(100);
|
|
1102
|
+
const [pendingApproval, setPendingApproval] = useState3(null);
|
|
1103
|
+
const [ready, setReady] = useState3(false);
|
|
1104
|
+
const [codebaseContext, setCodebaseContext] = useState3("");
|
|
1105
|
+
const [instructions, setInstructions] = useState3("");
|
|
1106
|
+
const messagesRef = useRef([]);
|
|
1107
|
+
const sessionRef = useRef(createSession());
|
|
1108
|
+
const activeRef = useRef(true);
|
|
1109
|
+
const streamingIdRef = useRef(null);
|
|
1110
|
+
useEffect2(() => {
|
|
1111
|
+
let cancelled = false;
|
|
1112
|
+
(async () => {
|
|
1113
|
+
let ctx = "";
|
|
1114
|
+
let instr = "";
|
|
1115
|
+
if (options.context !== false) {
|
|
1116
|
+
ctx = await getCodebaseContext();
|
|
1117
|
+
if (ctx && !cancelled) {
|
|
1118
|
+
setDisplayMessages((prev) => [
|
|
1119
|
+
...prev,
|
|
1120
|
+
{ id: nextId(), type: "info", content: `Loaded context from ${ctx.split("\n").length} lines` }
|
|
1121
|
+
]);
|
|
452
1122
|
}
|
|
453
|
-
});
|
|
454
|
-
} catch (error) {
|
|
455
|
-
hideThinkingStatus();
|
|
456
|
-
printError(error.message);
|
|
457
|
-
break;
|
|
458
|
-
}
|
|
459
|
-
if (!isActive()) break;
|
|
460
|
-
if (toolCalls && toolCalls.length > 0) {
|
|
461
|
-
messages.push({
|
|
462
|
-
role: "assistant",
|
|
463
|
-
content: fullContent,
|
|
464
|
-
tool_calls: toolCalls
|
|
465
|
-
});
|
|
466
|
-
if (fullContent) {
|
|
467
|
-
console.log();
|
|
468
1123
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
printToolResult(result.success, result.result);
|
|
477
|
-
messages.push({
|
|
478
|
-
role: "tool",
|
|
479
|
-
tool_call_id: tc.id,
|
|
480
|
-
content: result.result
|
|
481
|
-
});
|
|
1124
|
+
instr = await loadProjectInstructions();
|
|
1125
|
+
if (instr && !cancelled) {
|
|
1126
|
+
const count = instr.split("\n---\n").length;
|
|
1127
|
+
setDisplayMessages((prev) => [
|
|
1128
|
+
...prev,
|
|
1129
|
+
{ id: nextId(), type: "info", content: `Loaded ${count} CODEA.md instruction file(s)` }
|
|
1130
|
+
]);
|
|
482
1131
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
console.log();
|
|
1132
|
+
if (!cancelled) {
|
|
1133
|
+
setCodebaseContext(ctx);
|
|
1134
|
+
setInstructions(instr);
|
|
1135
|
+
setReady(true);
|
|
488
1136
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
console.log(chalk3.cyan(" /exit") + chalk3.gray(" - Exit Codea"));
|
|
505
|
-
console.log();
|
|
506
|
-
break;
|
|
507
|
-
case "clear":
|
|
508
|
-
messages.length = 0;
|
|
509
|
-
console.log(chalk3.green("Conversation cleared."));
|
|
510
|
-
break;
|
|
511
|
-
case "model":
|
|
512
|
-
const modelArg = args[0];
|
|
513
|
-
if (modelArg) {
|
|
514
|
-
options.model = modelArg.startsWith("alia-") ? modelArg : `alia-v1-${modelArg}`;
|
|
515
|
-
console.log(chalk3.green(`Model switched to ${options.model}`));
|
|
1137
|
+
})();
|
|
1138
|
+
return () => {
|
|
1139
|
+
cancelled = true;
|
|
1140
|
+
};
|
|
1141
|
+
}, []);
|
|
1142
|
+
useInput2((_input, key) => {
|
|
1143
|
+
if (key.ctrl && (_input === "c" || _input === "C")) {
|
|
1144
|
+
if (isProcessing) {
|
|
1145
|
+
activeRef.current = false;
|
|
1146
|
+
setIsProcessing(false);
|
|
1147
|
+
setPendingApproval(null);
|
|
1148
|
+
setDisplayMessages((prev) => [
|
|
1149
|
+
...prev,
|
|
1150
|
+
{ id: nextId(), type: "info", content: "Cancelled." }
|
|
1151
|
+
]);
|
|
516
1152
|
} else {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
1153
|
+
exit();
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
const addMessage = useCallback((msg) => {
|
|
1158
|
+
setDisplayMessages((prev) => [...prev, msg]);
|
|
1159
|
+
}, []);
|
|
1160
|
+
const updateLastAssistant = useCallback((text) => {
|
|
1161
|
+
setDisplayMessages((prev) => {
|
|
1162
|
+
const last = prev[prev.length - 1];
|
|
1163
|
+
if (last && last.type === "assistant" && last.streaming) {
|
|
1164
|
+
return [...prev.slice(0, -1), { ...last, content: last.content + text }];
|
|
1165
|
+
}
|
|
1166
|
+
return prev;
|
|
1167
|
+
});
|
|
1168
|
+
}, []);
|
|
1169
|
+
const finalizeAssistant = useCallback(() => {
|
|
1170
|
+
setDisplayMessages((prev) => {
|
|
1171
|
+
const last = prev[prev.length - 1];
|
|
1172
|
+
if (last && last.type === "assistant" && last.streaming) {
|
|
1173
|
+
return [...prev.slice(0, -1), { ...last, streaming: false }];
|
|
1174
|
+
}
|
|
1175
|
+
return prev;
|
|
1176
|
+
});
|
|
1177
|
+
}, []);
|
|
1178
|
+
const handleSubmit = useCallback(async (input) => {
|
|
1179
|
+
if (input.startsWith("/")) {
|
|
1180
|
+
const [cmd, ...args] = input.slice(1).split(" ");
|
|
1181
|
+
switch (cmd.toLowerCase()) {
|
|
1182
|
+
case "help":
|
|
1183
|
+
addMessage({
|
|
1184
|
+
id: nextId(),
|
|
1185
|
+
type: "info",
|
|
1186
|
+
content: "Commands: /help, /clear, /mode <suggest|auto-edit|full-auto>, /model <name>, /exit"
|
|
1187
|
+
});
|
|
1188
|
+
return;
|
|
1189
|
+
case "clear":
|
|
1190
|
+
messagesRef.current = [];
|
|
1191
|
+
setDisplayMessages([]);
|
|
1192
|
+
setContextPercent(100);
|
|
1193
|
+
return;
|
|
1194
|
+
case "mode":
|
|
1195
|
+
if (args[0] && ["suggest", "auto-edit", "full-auto"].includes(args[0])) {
|
|
1196
|
+
setApprovalMode(args[0]);
|
|
1197
|
+
addMessage({ id: nextId(), type: "info", content: `Approval mode: ${args[0]}` });
|
|
526
1198
|
} else {
|
|
527
|
-
|
|
1199
|
+
addMessage({ id: nextId(), type: "info", content: `Current mode: ${approvalMode}. Options: suggest, auto-edit, full-auto` });
|
|
528
1200
|
}
|
|
529
|
-
|
|
530
|
-
|
|
1201
|
+
return;
|
|
1202
|
+
case "model":
|
|
1203
|
+
if (args[0]) {
|
|
1204
|
+
options.model = args[0].startsWith("alia-") ? args[0] : `alia-v1-${args[0]}`;
|
|
1205
|
+
addMessage({ id: nextId(), type: "info", content: `Model: ${options.model}` });
|
|
1206
|
+
} else {
|
|
1207
|
+
addMessage({ id: nextId(), type: "info", content: `Current model: ${options.model}` });
|
|
1208
|
+
}
|
|
1209
|
+
return;
|
|
1210
|
+
case "exit":
|
|
1211
|
+
case "quit":
|
|
1212
|
+
exit();
|
|
1213
|
+
return;
|
|
1214
|
+
default:
|
|
1215
|
+
addMessage({ id: nextId(), type: "info", content: `Unknown command: /${cmd}` });
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
addMessage({ id: nextId(), type: "user", content: input });
|
|
1220
|
+
messagesRef.current.push({ role: "user", content: input });
|
|
1221
|
+
setIsProcessing(true);
|
|
1222
|
+
activeRef.current = true;
|
|
1223
|
+
streamingIdRef.current = null;
|
|
1224
|
+
const systemMessage = buildSystemMessage(options.model, codebaseContext, instructions);
|
|
1225
|
+
await processConversation({
|
|
1226
|
+
messages: messagesRef.current,
|
|
1227
|
+
systemMessage,
|
|
1228
|
+
model: options.model,
|
|
1229
|
+
approvalMode,
|
|
1230
|
+
isActive: () => activeRef.current,
|
|
1231
|
+
requestApproval: (execution) => {
|
|
1232
|
+
return new Promise((resolve3) => {
|
|
1233
|
+
setPendingApproval({ execution, resolve: resolve3 });
|
|
1234
|
+
});
|
|
1235
|
+
},
|
|
1236
|
+
onEvent: (event) => {
|
|
1237
|
+
switch (event.type) {
|
|
1238
|
+
case "thinking":
|
|
1239
|
+
setThinkingLabel("Thinking");
|
|
1240
|
+
streamingIdRef.current = nextId();
|
|
1241
|
+
setDisplayMessages((prev) => [
|
|
1242
|
+
...prev,
|
|
1243
|
+
{ id: streamingIdRef.current, type: "assistant", content: "", streaming: true }
|
|
1244
|
+
]);
|
|
1245
|
+
break;
|
|
1246
|
+
case "content":
|
|
1247
|
+
updateLastAssistant(event.text);
|
|
1248
|
+
break;
|
|
1249
|
+
case "tool_start":
|
|
1250
|
+
finalizeAssistant();
|
|
1251
|
+
setThinkingLabel(`Running ${event.execution.tool}`);
|
|
1252
|
+
addMessage({
|
|
1253
|
+
id: nextId(),
|
|
1254
|
+
type: "tool",
|
|
1255
|
+
content: "",
|
|
1256
|
+
toolExecution: { ...event.execution }
|
|
1257
|
+
});
|
|
1258
|
+
break;
|
|
1259
|
+
case "tool_done":
|
|
1260
|
+
setDisplayMessages((prev) => {
|
|
1261
|
+
const idx = prev.findLastIndex(
|
|
1262
|
+
(m) => m.type === "tool" && m.toolExecution?.id === event.execution.id
|
|
1263
|
+
);
|
|
1264
|
+
if (idx >= 0) {
|
|
1265
|
+
const updated = [...prev];
|
|
1266
|
+
updated[idx] = {
|
|
1267
|
+
...updated[idx],
|
|
1268
|
+
toolExecution: { ...event.execution }
|
|
1269
|
+
};
|
|
1270
|
+
return updated;
|
|
1271
|
+
}
|
|
1272
|
+
return prev;
|
|
1273
|
+
});
|
|
1274
|
+
break;
|
|
1275
|
+
case "done":
|
|
1276
|
+
finalizeAssistant();
|
|
1277
|
+
break;
|
|
1278
|
+
case "error":
|
|
1279
|
+
finalizeAssistant();
|
|
1280
|
+
addMessage({ id: nextId(), type: "info", content: `Error: ${event.message}` });
|
|
1281
|
+
break;
|
|
531
1282
|
}
|
|
532
1283
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
1284
|
+
});
|
|
1285
|
+
setIsProcessing(false);
|
|
1286
|
+
setPendingApproval(null);
|
|
1287
|
+
const session = sessionRef.current;
|
|
1288
|
+
session.messages = messagesRef.current.map((m) => ({ role: m.role, content: m.content }));
|
|
1289
|
+
session.title = messagesRef.current[0]?.content.slice(0, 50) || "New conversation";
|
|
1290
|
+
session.updatedAt = Date.now();
|
|
1291
|
+
saveSession(session);
|
|
1292
|
+
const totalChars = messagesRef.current.reduce((acc, m) => acc + m.content.length, 0);
|
|
1293
|
+
const maxContext = 128e3;
|
|
1294
|
+
setContextPercent(Math.max(5, 100 - Math.floor(totalChars / maxContext * 100)));
|
|
1295
|
+
}, [approvalMode, codebaseContext, instructions, options]);
|
|
1296
|
+
const handleApprovalResolve = useCallback((approved) => {
|
|
1297
|
+
if (pendingApproval) {
|
|
1298
|
+
pendingApproval.resolve(approved);
|
|
1299
|
+
setPendingApproval(null);
|
|
1300
|
+
}
|
|
1301
|
+
}, [pendingApproval]);
|
|
1302
|
+
const modelDisplay = options.model.replace("alia-v1-", "");
|
|
1303
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1304
|
+
/* @__PURE__ */ jsx7(
|
|
1305
|
+
Header,
|
|
1306
|
+
{
|
|
1307
|
+
cwd: process.cwd(),
|
|
1308
|
+
model: modelDisplay,
|
|
1309
|
+
approvalMode,
|
|
1310
|
+
contextPercent
|
|
1311
|
+
}
|
|
1312
|
+
),
|
|
1313
|
+
/* @__PURE__ */ jsx7(MessageList, { messages: displayMessages }),
|
|
1314
|
+
pendingApproval ? /* @__PURE__ */ jsx7(
|
|
1315
|
+
ApprovalPrompt,
|
|
1316
|
+
{
|
|
1317
|
+
execution: pendingApproval.execution,
|
|
1318
|
+
onResolve: handleApprovalResolve
|
|
1319
|
+
}
|
|
1320
|
+
) : /* @__PURE__ */ jsx7(
|
|
1321
|
+
InputBar,
|
|
1322
|
+
{
|
|
1323
|
+
onSubmit: handleSubmit,
|
|
1324
|
+
isProcessing,
|
|
1325
|
+
thinkingLabel
|
|
1326
|
+
}
|
|
1327
|
+
)
|
|
1328
|
+
] });
|
|
569
1329
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
1330
|
+
|
|
1331
|
+
// src/commands/repl.ts
|
|
1332
|
+
async function startRepl(options) {
|
|
1333
|
+
const appOptions = {
|
|
1334
|
+
model: options.model,
|
|
1335
|
+
approvalMode: options.approvalMode || "suggest",
|
|
1336
|
+
context: options.context
|
|
575
1337
|
};
|
|
576
|
-
|
|
1338
|
+
const { waitUntilExit } = render(React4.createElement(App, { options: appOptions }));
|
|
1339
|
+
await waitUntilExit();
|
|
577
1340
|
}
|
|
578
1341
|
|
|
579
1342
|
// src/commands/run.ts
|
|
580
|
-
import
|
|
1343
|
+
import chalk2 from "chalk";
|
|
1344
|
+
import * as readline from "readline";
|
|
581
1345
|
async function runPrompt(prompt, options) {
|
|
582
1346
|
const messages = [];
|
|
1347
|
+
const toolResults = [];
|
|
583
1348
|
let codebaseContext = "";
|
|
584
1349
|
if (options.context !== false) {
|
|
585
1350
|
codebaseContext = await getCodebaseContext();
|
|
586
1351
|
}
|
|
1352
|
+
const instructions = await loadProjectInstructions();
|
|
587
1353
|
messages.push({ role: "user", content: prompt });
|
|
588
|
-
const systemMessage = buildSystemMessage(options.model, codebaseContext);
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
});
|
|
628
|
-
if (fullContent) console.log();
|
|
629
|
-
for (const tc of toolCalls) {
|
|
630
|
-
const args = JSON.parse(tc.function.arguments);
|
|
631
|
-
const isDestructive = ["write_file", "edit_file", "run_command"].includes(tc.function.name);
|
|
632
|
-
if (isDestructive && !autoApprove) {
|
|
633
|
-
console.log();
|
|
634
|
-
console.log(chalk4.yellow("\u26A0 ") + chalk4.bold("Approval required:"));
|
|
635
|
-
console.log(formatToolCall(tc.function.name, args));
|
|
636
|
-
console.log();
|
|
637
|
-
const approved = await askApproval();
|
|
638
|
-
if (!approved) {
|
|
639
|
-
messages.push({
|
|
640
|
-
role: "tool",
|
|
641
|
-
tool_call_id: tc.id,
|
|
642
|
-
content: "User declined this action."
|
|
1354
|
+
const systemMessage = buildSystemMessage(options.model, codebaseContext, instructions);
|
|
1355
|
+
const approvalMode = options.yes ? "full-auto" : options.approvalMode || "suggest";
|
|
1356
|
+
let fullResponse = "";
|
|
1357
|
+
await processConversation({
|
|
1358
|
+
messages,
|
|
1359
|
+
systemMessage,
|
|
1360
|
+
model: options.model,
|
|
1361
|
+
approvalMode,
|
|
1362
|
+
isActive: () => true,
|
|
1363
|
+
requestApproval: async (execution) => {
|
|
1364
|
+
if (options.quiet || options.json) return false;
|
|
1365
|
+
return askApproval(execution);
|
|
1366
|
+
},
|
|
1367
|
+
onEvent: (event) => {
|
|
1368
|
+
switch (event.type) {
|
|
1369
|
+
case "thinking":
|
|
1370
|
+
if (!options.quiet && !options.json) {
|
|
1371
|
+
process.stdout.write(chalk2.magenta("\u2726 "));
|
|
1372
|
+
}
|
|
1373
|
+
break;
|
|
1374
|
+
case "content":
|
|
1375
|
+
fullResponse += event.text;
|
|
1376
|
+
if (!options.quiet && !options.json) {
|
|
1377
|
+
process.stdout.write(event.text);
|
|
1378
|
+
}
|
|
1379
|
+
break;
|
|
1380
|
+
case "tool_start":
|
|
1381
|
+
if (!options.quiet && !options.json) {
|
|
1382
|
+
console.log();
|
|
1383
|
+
console.log(chalk2.cyan(" \u2192 ") + chalk2.bold(event.execution.tool) + " " + chalk2.gray(formatArgs3(event.execution)));
|
|
1384
|
+
}
|
|
1385
|
+
break;
|
|
1386
|
+
case "tool_done":
|
|
1387
|
+
if (event.execution.result !== void 0) {
|
|
1388
|
+
toolResults.push({
|
|
1389
|
+
tool: event.execution.tool,
|
|
1390
|
+
args: event.execution.args,
|
|
1391
|
+
result: event.execution.result,
|
|
1392
|
+
success: event.execution.success ?? false
|
|
643
1393
|
});
|
|
644
|
-
continue;
|
|
645
1394
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
console.log();
|
|
1395
|
+
if (!options.quiet && !options.json) {
|
|
1396
|
+
const icon = event.execution.success ? chalk2.green(" \u2713") : chalk2.red(" \u2717");
|
|
1397
|
+
const preview = (event.execution.result || "").slice(0, 100).replace(/\n/g, " ");
|
|
1398
|
+
console.log(`${icon} ${chalk2.gray(preview)}`);
|
|
1399
|
+
}
|
|
1400
|
+
break;
|
|
1401
|
+
case "done":
|
|
1402
|
+
if (!options.quiet && !options.json) {
|
|
1403
|
+
console.log();
|
|
1404
|
+
}
|
|
1405
|
+
break;
|
|
1406
|
+
case "error":
|
|
1407
|
+
if (!options.json) {
|
|
1408
|
+
console.error(chalk2.red("Error: ") + event.message);
|
|
1409
|
+
}
|
|
1410
|
+
break;
|
|
663
1411
|
}
|
|
664
|
-
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
if (options.json) {
|
|
1415
|
+
const output = {
|
|
1416
|
+
model: options.model,
|
|
1417
|
+
prompt,
|
|
1418
|
+
response: fullResponse,
|
|
1419
|
+
tool_calls: toolResults
|
|
1420
|
+
};
|
|
1421
|
+
console.log(JSON.stringify(output, null, 2));
|
|
1422
|
+
} else if (options.quiet) {
|
|
1423
|
+
if (fullResponse) {
|
|
1424
|
+
console.log(fullResponse);
|
|
665
1425
|
}
|
|
666
1426
|
}
|
|
667
1427
|
}
|
|
668
|
-
async function askApproval() {
|
|
669
|
-
const
|
|
670
|
-
const rl = readline5.createInterface({
|
|
1428
|
+
async function askApproval(execution) {
|
|
1429
|
+
const rl = readline.createInterface({
|
|
671
1430
|
input: process.stdin,
|
|
672
1431
|
output: process.stdout
|
|
673
1432
|
});
|
|
674
|
-
|
|
675
|
-
|
|
1433
|
+
const desc = formatArgs3(execution);
|
|
1434
|
+
console.log();
|
|
1435
|
+
console.log(chalk2.yellow("\u26A0 ") + chalk2.bold(execution.tool) + " " + desc);
|
|
1436
|
+
return new Promise((resolve3) => {
|
|
1437
|
+
rl.question(chalk2.cyan(" Allow? [y/N] "), (answer) => {
|
|
676
1438
|
rl.close();
|
|
677
|
-
|
|
1439
|
+
resolve3(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
678
1440
|
});
|
|
679
1441
|
});
|
|
680
1442
|
}
|
|
681
|
-
function
|
|
682
|
-
|
|
1443
|
+
function formatArgs3(execution) {
|
|
1444
|
+
const { tool, args } = execution;
|
|
1445
|
+
switch (tool) {
|
|
683
1446
|
case "read_file":
|
|
684
1447
|
case "write_file":
|
|
685
1448
|
case "edit_file":
|
|
686
1449
|
return args.path || "";
|
|
1450
|
+
case "apply_patch":
|
|
1451
|
+
return "applying patch...";
|
|
687
1452
|
case "list_files":
|
|
688
1453
|
return args.path || ".";
|
|
689
1454
|
case "search_files":
|
|
@@ -691,16 +1456,25 @@ function formatToolArgs2(name, args) {
|
|
|
691
1456
|
case "run_command":
|
|
692
1457
|
return args.command || "";
|
|
693
1458
|
default:
|
|
694
|
-
return JSON.stringify(args).slice(0,
|
|
1459
|
+
return JSON.stringify(args).slice(0, 60);
|
|
695
1460
|
}
|
|
696
1461
|
}
|
|
697
1462
|
|
|
698
1463
|
// src/commands/auth.ts
|
|
699
|
-
import * as
|
|
1464
|
+
import * as readline2 from "readline";
|
|
700
1465
|
import * as crypto from "crypto";
|
|
701
1466
|
import * as http from "http";
|
|
702
1467
|
import { exec as exec3 } from "child_process";
|
|
703
|
-
import
|
|
1468
|
+
import chalk3 from "chalk";
|
|
1469
|
+
function printSuccess(message) {
|
|
1470
|
+
console.log(chalk3.green("\u2713 ") + message);
|
|
1471
|
+
}
|
|
1472
|
+
function printError(message) {
|
|
1473
|
+
console.log(chalk3.red("\u2717 Error: ") + message);
|
|
1474
|
+
}
|
|
1475
|
+
function printInfo(message) {
|
|
1476
|
+
console.log(chalk3.blue("\u2139 ") + message);
|
|
1477
|
+
}
|
|
704
1478
|
function openBrowser(url) {
|
|
705
1479
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? 'start ""' : "xdg-open";
|
|
706
1480
|
exec3(`${cmd} "${url}"`);
|
|
@@ -708,7 +1482,7 @@ function openBrowser(url) {
|
|
|
708
1482
|
async function loginWithBrowser() {
|
|
709
1483
|
const codeVerifier = crypto.randomBytes(32).toString("base64url");
|
|
710
1484
|
const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
|
|
711
|
-
return new Promise((
|
|
1485
|
+
return new Promise((resolve3) => {
|
|
712
1486
|
const server = http.createServer(async (req, res) => {
|
|
713
1487
|
const url = new URL(req.url, `http://localhost`);
|
|
714
1488
|
if (url.pathname !== "/callback") {
|
|
@@ -724,7 +1498,7 @@ async function loginWithBrowser() {
|
|
|
724
1498
|
'<html><body style="font-family:system-ui;text-align:center;padding:60px"><h2>Authorization cancelled</h2><p>You can close this window.</p></body></html>'
|
|
725
1499
|
);
|
|
726
1500
|
server.close();
|
|
727
|
-
|
|
1501
|
+
resolve3(false);
|
|
728
1502
|
return;
|
|
729
1503
|
}
|
|
730
1504
|
try {
|
|
@@ -749,7 +1523,7 @@ async function loginWithBrowser() {
|
|
|
749
1523
|
console.log();
|
|
750
1524
|
printSuccess("Logged in successfully!");
|
|
751
1525
|
server.close();
|
|
752
|
-
|
|
1526
|
+
resolve3(true);
|
|
753
1527
|
} else {
|
|
754
1528
|
throw new Error("No token received");
|
|
755
1529
|
}
|
|
@@ -760,7 +1534,7 @@ async function loginWithBrowser() {
|
|
|
760
1534
|
);
|
|
761
1535
|
printError("Failed to exchange authorization code.");
|
|
762
1536
|
server.close();
|
|
763
|
-
|
|
1537
|
+
resolve3(false);
|
|
764
1538
|
}
|
|
765
1539
|
});
|
|
766
1540
|
server.listen(0, () => {
|
|
@@ -770,29 +1544,29 @@ async function loginWithBrowser() {
|
|
|
770
1544
|
printInfo("Opening browser for authorization...");
|
|
771
1545
|
openBrowser(authorizeUrl);
|
|
772
1546
|
console.log(
|
|
773
|
-
|
|
1547
|
+
chalk3.gray("\nIf the browser doesn't open, visit:\n") + chalk3.cyan(authorizeUrl) + "\n"
|
|
774
1548
|
);
|
|
775
|
-
console.log(
|
|
1549
|
+
console.log(chalk3.gray("Waiting for authorization..."));
|
|
776
1550
|
});
|
|
777
1551
|
setTimeout(() => {
|
|
778
1552
|
server.close();
|
|
779
1553
|
printError("Authorization timed out.");
|
|
780
|
-
|
|
1554
|
+
resolve3(false);
|
|
781
1555
|
}, 5 * 60 * 1e3);
|
|
782
1556
|
});
|
|
783
1557
|
}
|
|
784
1558
|
async function loginWithApiKey() {
|
|
785
|
-
const rl =
|
|
1559
|
+
const rl = readline2.createInterface({
|
|
786
1560
|
input: process.stdin,
|
|
787
1561
|
output: process.stdout
|
|
788
1562
|
});
|
|
789
|
-
return new Promise((
|
|
790
|
-
rl.question(
|
|
1563
|
+
return new Promise((resolve3) => {
|
|
1564
|
+
rl.question(chalk3.cyan("API Key: "), async (apiKey) => {
|
|
791
1565
|
rl.close();
|
|
792
1566
|
const trimmedKey = apiKey.trim();
|
|
793
1567
|
if (!trimmedKey) {
|
|
794
1568
|
printError("No API key provided.");
|
|
795
|
-
|
|
1569
|
+
resolve3(false);
|
|
796
1570
|
return;
|
|
797
1571
|
}
|
|
798
1572
|
printInfo("Validating API key...");
|
|
@@ -807,63 +1581,63 @@ async function loginWithApiKey() {
|
|
|
807
1581
|
console.log();
|
|
808
1582
|
printSuccess("Logged in successfully!");
|
|
809
1583
|
if (data.name) {
|
|
810
|
-
console.log(
|
|
1584
|
+
console.log(chalk3.gray(`Welcome, ${data.name}!`));
|
|
811
1585
|
}
|
|
812
|
-
|
|
1586
|
+
resolve3(true);
|
|
813
1587
|
} else {
|
|
814
1588
|
printError("Invalid API key. Please check and try again.");
|
|
815
|
-
|
|
1589
|
+
resolve3(false);
|
|
816
1590
|
}
|
|
817
1591
|
} catch (error) {
|
|
818
1592
|
printError(`Could not validate API key: ${error.message}`);
|
|
819
|
-
|
|
1593
|
+
resolve3(false);
|
|
820
1594
|
}
|
|
821
1595
|
});
|
|
822
1596
|
});
|
|
823
1597
|
}
|
|
824
1598
|
async function login() {
|
|
825
1599
|
console.log();
|
|
826
|
-
console.log(
|
|
1600
|
+
console.log(chalk3.bold("Codea CLI Login"));
|
|
827
1601
|
console.log();
|
|
828
1602
|
const success = await loginWithBrowser();
|
|
829
1603
|
if (success) return true;
|
|
830
1604
|
console.log();
|
|
831
1605
|
console.log(
|
|
832
|
-
|
|
1606
|
+
chalk3.gray("Alternatively, paste your API key from: ") + chalk3.cyan("https://alia.onl/settings/api")
|
|
833
1607
|
);
|
|
834
1608
|
console.log();
|
|
835
1609
|
return loginWithApiKey();
|
|
836
1610
|
}
|
|
837
1611
|
|
|
838
1612
|
// src/commands/sessions.ts
|
|
839
|
-
import
|
|
840
|
-
import * as
|
|
1613
|
+
import chalk4 from "chalk";
|
|
1614
|
+
import * as readline3 from "readline";
|
|
841
1615
|
async function listSessions() {
|
|
842
1616
|
const sessions = getSessions();
|
|
843
1617
|
if (sessions.length === 0) {
|
|
844
|
-
|
|
845
|
-
console.log(
|
|
1618
|
+
console.log(chalk4.blue("\u2139 ") + "No saved sessions found.");
|
|
1619
|
+
console.log(chalk4.gray("Start a new session with: ") + chalk4.cyan("codea"));
|
|
846
1620
|
return;
|
|
847
1621
|
}
|
|
848
1622
|
console.log();
|
|
849
|
-
console.log(
|
|
850
|
-
console.log(
|
|
1623
|
+
console.log(chalk4.bold("Recent Sessions"));
|
|
1624
|
+
console.log(chalk4.gray("\u2500".repeat(60)));
|
|
851
1625
|
sessions.slice(0, 10).forEach((session, index) => {
|
|
852
1626
|
const date = new Date(session.updatedAt).toLocaleDateString();
|
|
853
1627
|
const time = new Date(session.updatedAt).toLocaleTimeString();
|
|
854
1628
|
const messageCount = session.messages?.length || 0;
|
|
855
1629
|
const title = session.title.slice(0, 40) + (session.title.length > 40 ? "..." : "");
|
|
856
1630
|
console.log(
|
|
857
|
-
|
|
1631
|
+
chalk4.cyan(`${index + 1}.`) + " " + chalk4.white(title) + " " + chalk4.gray(`(${messageCount} msgs, ${date} ${time})`)
|
|
858
1632
|
);
|
|
859
1633
|
});
|
|
860
1634
|
console.log();
|
|
861
|
-
console.log(
|
|
1635
|
+
console.log(chalk4.gray("Resume a session with: ") + chalk4.cyan("codea resume <number>"));
|
|
862
1636
|
}
|
|
863
1637
|
async function resumeSession(sessionId) {
|
|
864
1638
|
const sessions = getSessions();
|
|
865
1639
|
if (sessions.length === 0) {
|
|
866
|
-
|
|
1640
|
+
console.log(chalk4.blue("\u2139 ") + "No saved sessions found.");
|
|
867
1641
|
return;
|
|
868
1642
|
}
|
|
869
1643
|
let selectedSession;
|
|
@@ -875,34 +1649,34 @@ async function resumeSession(sessionId) {
|
|
|
875
1649
|
selectedSession = getSession(sessionId);
|
|
876
1650
|
}
|
|
877
1651
|
if (!selectedSession) {
|
|
878
|
-
|
|
1652
|
+
console.log(chalk4.red("\u2717 Error: ") + `Session not found: ${sessionId}`);
|
|
879
1653
|
return;
|
|
880
1654
|
}
|
|
881
1655
|
} else {
|
|
882
1656
|
console.log();
|
|
883
|
-
console.log(
|
|
1657
|
+
console.log(chalk4.bold("Select a session to resume:"));
|
|
884
1658
|
console.log();
|
|
885
1659
|
sessions.slice(0, 10).forEach((session, index) => {
|
|
886
1660
|
const date = new Date(session.updatedAt).toLocaleDateString();
|
|
887
1661
|
const title = session.title.slice(0, 50) + (session.title.length > 50 ? "..." : "");
|
|
888
|
-
console.log(
|
|
1662
|
+
console.log(chalk4.cyan(` ${index + 1}.`) + " " + title + " " + chalk4.gray(`(${date})`));
|
|
889
1663
|
});
|
|
890
1664
|
console.log();
|
|
891
|
-
const rl =
|
|
1665
|
+
const rl = readline3.createInterface({
|
|
892
1666
|
input: process.stdin,
|
|
893
1667
|
output: process.stdout
|
|
894
1668
|
});
|
|
895
|
-
return new Promise((
|
|
896
|
-
rl.question(
|
|
1669
|
+
return new Promise((resolve3) => {
|
|
1670
|
+
rl.question(chalk4.cyan("Enter number: "), (answer) => {
|
|
897
1671
|
rl.close();
|
|
898
1672
|
const index = parseInt(answer) - 1;
|
|
899
1673
|
if (isNaN(index) || index < 0 || index >= sessions.length) {
|
|
900
|
-
|
|
901
|
-
|
|
1674
|
+
console.log(chalk4.red("\u2717 Error: ") + "Invalid selection.");
|
|
1675
|
+
resolve3();
|
|
902
1676
|
return;
|
|
903
1677
|
}
|
|
904
1678
|
selectedSession = sessions[index];
|
|
905
|
-
startRestoredSession(selectedSession).then(
|
|
1679
|
+
startRestoredSession(selectedSession).then(resolve3);
|
|
906
1680
|
});
|
|
907
1681
|
});
|
|
908
1682
|
}
|
|
@@ -911,41 +1685,41 @@ async function resumeSession(sessionId) {
|
|
|
911
1685
|
}
|
|
912
1686
|
}
|
|
913
1687
|
async function startRestoredSession(session) {
|
|
914
|
-
|
|
1688
|
+
console.log(chalk4.blue("\u2139 ") + `Resuming: ${session.title}`);
|
|
915
1689
|
console.log();
|
|
916
1690
|
for (const msg of session.messages || []) {
|
|
917
1691
|
if (msg.role === "user") {
|
|
918
|
-
console.log(
|
|
1692
|
+
console.log(chalk4.cyan("\u276F ") + msg.content);
|
|
919
1693
|
} else if (msg.role === "assistant") {
|
|
920
|
-
console.log(
|
|
1694
|
+
console.log(chalk4.magenta("\u2726 ") + msg.content.slice(0, 200) + (msg.content.length > 200 ? "..." : ""));
|
|
921
1695
|
}
|
|
922
1696
|
console.log();
|
|
923
1697
|
}
|
|
924
|
-
console.log(
|
|
925
|
-
console.log(
|
|
1698
|
+
console.log(chalk4.gray("\u2500".repeat(60)));
|
|
1699
|
+
console.log(chalk4.gray("Session restored. Continue the conversation below."));
|
|
926
1700
|
console.log();
|
|
927
1701
|
const model = config.get("defaultModel") || "alia-v1-codea";
|
|
928
1702
|
await startRepl({ model, context: true });
|
|
929
1703
|
}
|
|
930
1704
|
|
|
931
1705
|
// src/index.ts
|
|
932
|
-
import
|
|
933
|
-
var VERSION = "
|
|
1706
|
+
import chalk5 from "chalk";
|
|
1707
|
+
var VERSION = "2.0.0";
|
|
934
1708
|
var program = new Command();
|
|
935
1709
|
var banner = `
|
|
936
|
-
${
|
|
937
|
-
${
|
|
938
|
-
${
|
|
939
|
-
${
|
|
940
|
-
${
|
|
941
|
-
${
|
|
1710
|
+
${chalk5.cyan(" ____ _ ")}
|
|
1711
|
+
${chalk5.cyan(" / ___|___ __| | ___ __ _ ")}
|
|
1712
|
+
${chalk5.cyan(" | | / _ \\ / _` |/ _ \\/ _` |")}
|
|
1713
|
+
${chalk5.cyan(" | |__| (_) | (_| | __/ (_| |")}
|
|
1714
|
+
${chalk5.cyan(" \\____\\___/ \\__,_|\\___|\\__,_|")}
|
|
1715
|
+
${chalk5.gray(" AI Coding Assistant by Alia")}
|
|
942
1716
|
`;
|
|
943
1717
|
program.name("codea").description("Codea CLI - AI coding assistant for your terminal").version(VERSION).hook("preAction", async () => {
|
|
944
1718
|
const command = program.args[0];
|
|
945
1719
|
if (command === "login" || command === "help") return;
|
|
946
1720
|
if (!config.get("apiKey")) {
|
|
947
1721
|
console.log(banner);
|
|
948
|
-
console.log(
|
|
1722
|
+
console.log(chalk5.yellow("No API key found. Let's get you logged in.\n"));
|
|
949
1723
|
const success = await login();
|
|
950
1724
|
if (!success) {
|
|
951
1725
|
process.exit(1);
|
|
@@ -953,13 +1727,15 @@ program.name("codea").description("Codea CLI - AI coding assistant for your term
|
|
|
953
1727
|
console.log();
|
|
954
1728
|
}
|
|
955
1729
|
});
|
|
956
|
-
program.command("chat", { isDefault: true }).description("Start an interactive chat session").option("-m, --model <model>", "Model to use (codea, codea-pro, codea-thinking)", "alia-v1-codea").option("--no-context", "Disable automatic codebase context").action(async (options) => {
|
|
957
|
-
console.log(banner);
|
|
1730
|
+
program.command("chat", { isDefault: true }).description("Start an interactive chat session").option("-m, --model <model>", "Model to use (codea, codea-pro, codea-thinking)", "alia-v1-codea").option("-a, --approval-mode <mode>", "Approval mode: suggest, auto-edit, full-auto", "suggest").option("--no-context", "Disable automatic codebase context").option("--no-instructions", "Disable CODEA.md project instructions").action(async (options) => {
|
|
958
1731
|
await startRepl(options);
|
|
959
1732
|
});
|
|
960
|
-
program.command("run <prompt>").alias("r").description("Run a single prompt and exit").option("-m, --model <model>", "Model to use", "alia-v1-codea").option("-y, --yes", "Auto-approve all
|
|
1733
|
+
program.command("run <prompt>").alias("r").description("Run a single prompt and exit").option("-m, --model <model>", "Model to use", "alia-v1-codea").option("-y, --yes", "Auto-approve all actions (full-auto mode)").option("-a, --approval-mode <mode>", "Approval mode: suggest, auto-edit, full-auto", "suggest").option("-q, --quiet", "Suppress UI, output only response text").option("--json", "Output structured JSON").option("--no-context", "Disable automatic codebase context").action(async (prompt, options) => {
|
|
961
1734
|
await runPrompt(prompt, options);
|
|
962
1735
|
});
|
|
1736
|
+
program.command("exec <prompt>").alias("x").description("Execute a prompt in full-auto mode with JSON output").option("-m, --model <model>", "Model to use", "alia-v1-codea").option("--no-context", "Disable automatic codebase context").action(async (prompt, options) => {
|
|
1737
|
+
await runPrompt(prompt, { ...options, yes: true, quiet: false, json: true });
|
|
1738
|
+
});
|
|
963
1739
|
program.command("login").description("Configure your Alia API key").action(async () => {
|
|
964
1740
|
await login();
|
|
965
1741
|
});
|