coder-agent 2.1.0 → 2.2.1
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/agent.js +176 -23
- package/dist/index.js +48 -42
- package/dist/memory.js +2 -1
- package/package.json +2 -1
package/dist/agent.js
CHANGED
|
@@ -2,6 +2,136 @@ import chalk from "chalk";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { TOOL_DEFINITIONS, dispatchTool } from "./tools.js";
|
|
4
4
|
import { Memory } from "./memory.js";
|
|
5
|
+
// ─── Loading Spinner ──────────────────────────────────────────────────────────
|
|
6
|
+
let spinnerTimer = null;
|
|
7
|
+
let currentFrame = 0;
|
|
8
|
+
const frames = ['· ', '·· ', '···', ' ··', ' ·'];
|
|
9
|
+
const colorBlue = chalk.hex('#0a84ff');
|
|
10
|
+
function startSpinner(label) {
|
|
11
|
+
stopSpinner();
|
|
12
|
+
currentFrame = 0;
|
|
13
|
+
process.stdout.write(`\r${colorBlue(frames[currentFrame])} ${chalk.dim(label)}`);
|
|
14
|
+
spinnerTimer = setInterval(() => {
|
|
15
|
+
currentFrame = (currentFrame + 1) % frames.length;
|
|
16
|
+
process.stdout.write(`\r${colorBlue(frames[currentFrame])} ${chalk.dim(label)}`);
|
|
17
|
+
}, 120);
|
|
18
|
+
}
|
|
19
|
+
function updateSpinner(label) {
|
|
20
|
+
if (spinnerTimer) {
|
|
21
|
+
process.stdout.write(`\r\x1b[K${colorBlue(frames[currentFrame])} ${chalk.dim(label)}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function stopSpinner() {
|
|
25
|
+
if (spinnerTimer) {
|
|
26
|
+
clearInterval(spinnerTimer);
|
|
27
|
+
spinnerTimer = null;
|
|
28
|
+
process.stdout.write('\r\x1b[K');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// ─── Formatting Helpers ──────────────────────────────────────────────────────
|
|
32
|
+
function getToolBadge(name) {
|
|
33
|
+
const blue = chalk.hex('#0a84ff');
|
|
34
|
+
const amber = chalk.hex('#ff9f0a');
|
|
35
|
+
const green = chalk.hex('#30d158');
|
|
36
|
+
switch (name) {
|
|
37
|
+
case "read_file":
|
|
38
|
+
case "read_file_lines":
|
|
39
|
+
return blue("[read] ");
|
|
40
|
+
case "write_file":
|
|
41
|
+
case "patch_file":
|
|
42
|
+
return amber("[write] ");
|
|
43
|
+
case "run_shell":
|
|
44
|
+
return green("[run] ");
|
|
45
|
+
case "list_directory":
|
|
46
|
+
case "find_files":
|
|
47
|
+
case "search_grep":
|
|
48
|
+
case "web_search":
|
|
49
|
+
return blue("[search]");
|
|
50
|
+
default:
|
|
51
|
+
return blue("[tool] ");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function getToolTargetDescription(name, args) {
|
|
55
|
+
switch (name) {
|
|
56
|
+
case "read_file":
|
|
57
|
+
case "read_file_lines":
|
|
58
|
+
case "write_file":
|
|
59
|
+
case "patch_file":
|
|
60
|
+
return args.file_path || "unknown file";
|
|
61
|
+
case "run_shell":
|
|
62
|
+
return args.command || "unknown command";
|
|
63
|
+
case "list_directory":
|
|
64
|
+
return args.dir_path || ".";
|
|
65
|
+
case "find_files":
|
|
66
|
+
return `find: "${args.query}"`;
|
|
67
|
+
case "search_grep":
|
|
68
|
+
return `grep: "${args.query}"`;
|
|
69
|
+
case "web_search":
|
|
70
|
+
return `search: "${args.query}"`;
|
|
71
|
+
default:
|
|
72
|
+
return JSON.stringify(args);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function getToolDetailsDescription(name, args) {
|
|
76
|
+
switch (name) {
|
|
77
|
+
case "read_file":
|
|
78
|
+
return "read entire file";
|
|
79
|
+
case "read_file_lines":
|
|
80
|
+
return `lines ${args.start_line}–${args.end_line}`;
|
|
81
|
+
case "write_file":
|
|
82
|
+
return `write ${args.content?.length || 0} chars`;
|
|
83
|
+
case "patch_file":
|
|
84
|
+
return "apply selective patch";
|
|
85
|
+
case "run_shell":
|
|
86
|
+
return `cwd: ${args.cwd || "project root"}`;
|
|
87
|
+
case "list_directory":
|
|
88
|
+
return "list directory contents";
|
|
89
|
+
case "find_files":
|
|
90
|
+
return `search in ${args.dir_path || "."}`;
|
|
91
|
+
case "search_grep":
|
|
92
|
+
return args.is_regex ? "regex search" : "literal search";
|
|
93
|
+
case "web_search":
|
|
94
|
+
return "DuckDuckGo search";
|
|
95
|
+
default:
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function getToolSuccessSummary(name, args, result) {
|
|
100
|
+
switch (name) {
|
|
101
|
+
case "read_file":
|
|
102
|
+
return `read ${args.file_path} (${result.length} chars)`;
|
|
103
|
+
case "read_file_lines":
|
|
104
|
+
return `read lines ${args.start_line}–${args.end_line} in ${args.file_path}`;
|
|
105
|
+
case "write_file":
|
|
106
|
+
return `written changes to ${args.file_path}`;
|
|
107
|
+
case "patch_file":
|
|
108
|
+
return `patched file ${args.file_path}`;
|
|
109
|
+
case "run_shell":
|
|
110
|
+
return `executed command: ${args.command}`;
|
|
111
|
+
case "list_directory":
|
|
112
|
+
return `listed ${args.dir_path || "."}`;
|
|
113
|
+
case "find_files":
|
|
114
|
+
const fileCount = result.split("\n").length;
|
|
115
|
+
return `found ${fileCount} matching files`;
|
|
116
|
+
case "search_grep":
|
|
117
|
+
const occurrenceCount = result.split("\n").length;
|
|
118
|
+
return `found ${occurrenceCount} occurrences`;
|
|
119
|
+
case "web_search":
|
|
120
|
+
return `retrieved search results for "${args.query}"`;
|
|
121
|
+
default:
|
|
122
|
+
return "completed successfully";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function formatResponseText(text) {
|
|
126
|
+
const parts = text.split(/(\*\*[^*]+\*\*)/g);
|
|
127
|
+
return parts.map(part => {
|
|
128
|
+
if (part.startsWith('**') && part.endsWith('**')) {
|
|
129
|
+
return chalk.white.bold(part.slice(2, -2));
|
|
130
|
+
}
|
|
131
|
+
return chalk.white(part);
|
|
132
|
+
}).join('');
|
|
133
|
+
}
|
|
134
|
+
// ─── Extract Text Tool Calls ──────────────────────────────────────────────────
|
|
5
135
|
function extractTextToolCalls(content) {
|
|
6
136
|
const calls = [];
|
|
7
137
|
// Pattern 1: <function(name)> {args} </function>
|
|
@@ -60,6 +190,7 @@ function extractTextToolCalls(content) {
|
|
|
60
190
|
}
|
|
61
191
|
return calls;
|
|
62
192
|
}
|
|
193
|
+
// ─── Gemini API client ────────────────────────────────────────────────────────
|
|
63
194
|
async function callGeminiAPI(apiKey, params, maxRetries = 3, initialDelayMs = 1500) {
|
|
64
195
|
let attempts = 0;
|
|
65
196
|
while (attempts < maxRetries) {
|
|
@@ -86,7 +217,6 @@ async function callGeminiAPI(apiKey, params, maxRetries = 3, initialDelayMs = 15
|
|
|
86
217
|
const isRetryableError = status === 429 || status === 503 || (status >= 500 && status < 600) || !status;
|
|
87
218
|
if (isRetryableError && attempts < maxRetries) {
|
|
88
219
|
const delay = initialDelayMs * Math.pow(2, attempts - 1);
|
|
89
|
-
console.log(chalk.gray(` ⚠️ Gemini API busy (Status ${status || "network"}). Retrying in ${(delay / 1000).toFixed(1)}s (attempt ${attempts}/${maxRetries})...`));
|
|
90
220
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
91
221
|
continue;
|
|
92
222
|
}
|
|
@@ -94,6 +224,7 @@ async function callGeminiAPI(apiKey, params, maxRetries = 3, initialDelayMs = 15
|
|
|
94
224
|
}
|
|
95
225
|
}
|
|
96
226
|
}
|
|
227
|
+
// ─── Agent Class ─────────────────────────────────────────────────────────────
|
|
97
228
|
export class Agent {
|
|
98
229
|
apiKey;
|
|
99
230
|
memory;
|
|
@@ -121,10 +252,15 @@ export class Agent {
|
|
|
121
252
|
await this.memory.init(this.memoryScope, "coder");
|
|
122
253
|
this.memory.add({ role: "user", content: userMessage });
|
|
123
254
|
let iterations = 0;
|
|
124
|
-
const MAX_ITERATIONS = 12;
|
|
255
|
+
const MAX_ITERATIONS = 12;
|
|
125
256
|
const modifiedFiles = new Set();
|
|
257
|
+
let cleanContent = "";
|
|
258
|
+
// ── Phase 1: Input to Thinking ──────────────────────────────────────────
|
|
259
|
+
console.log(chalk.dim('\n' + '─'.repeat(48) + '\n'));
|
|
260
|
+
startSpinner("thinking...");
|
|
126
261
|
while (iterations < MAX_ITERATIONS) {
|
|
127
262
|
iterations++;
|
|
263
|
+
updateSpinner("thinking...");
|
|
128
264
|
const response = await callGeminiAPI(this.apiKey, {
|
|
129
265
|
model: this.model,
|
|
130
266
|
messages: this.memory.getAll(),
|
|
@@ -132,6 +268,7 @@ export class Agent {
|
|
|
132
268
|
tool_choice: "auto",
|
|
133
269
|
temperature: 0.2,
|
|
134
270
|
});
|
|
271
|
+
stopSpinner();
|
|
135
272
|
const choice = response.choices[0];
|
|
136
273
|
const msg = choice.message;
|
|
137
274
|
// Extract text-based tool calls if native tool_calls is empty
|
|
@@ -147,7 +284,7 @@ export class Agent {
|
|
|
147
284
|
}
|
|
148
285
|
}));
|
|
149
286
|
}
|
|
150
|
-
// Save assistant message to memory
|
|
287
|
+
// Save assistant message to memory
|
|
151
288
|
const assistantMsg = {
|
|
152
289
|
role: "assistant",
|
|
153
290
|
content: msg.content ?? "",
|
|
@@ -155,23 +292,17 @@ export class Agent {
|
|
|
155
292
|
};
|
|
156
293
|
this.memory.add(assistantMsg);
|
|
157
294
|
// Clean up text tool calls from the conversational output content
|
|
158
|
-
|
|
295
|
+
cleanContent = msg.content ?? "";
|
|
159
296
|
for (const call of extracted) {
|
|
160
297
|
cleanContent = cleanContent.replace(call.rawText, "");
|
|
161
298
|
}
|
|
162
299
|
cleanContent = cleanContent.trim();
|
|
163
300
|
// ── No tool calls → final answer ─────────────────────────────────────
|
|
164
301
|
if (toolCalls.length === 0) {
|
|
165
|
-
if (cleanContent) {
|
|
166
|
-
console.log("\n" + chalk.bold.white("coder-agent ❯ ") + cleanContent);
|
|
167
|
-
}
|
|
168
302
|
break;
|
|
169
303
|
}
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
console.log("\n" + chalk.bold.white("coder-agent ❯ ") + cleanContent);
|
|
173
|
-
}
|
|
174
|
-
// ── Execute tool calls ────────────────────────────────────────────────
|
|
304
|
+
// ── Phase 2: Tool Execution ───────────────────────────────────────────
|
|
305
|
+
const statusLines = [];
|
|
175
306
|
for (const toolCall of toolCalls) {
|
|
176
307
|
const name = toolCall.function.name;
|
|
177
308
|
let args = {};
|
|
@@ -179,7 +310,12 @@ export class Agent {
|
|
|
179
310
|
args = JSON.parse(toolCall.function.arguments);
|
|
180
311
|
}
|
|
181
312
|
catch { }
|
|
182
|
-
|
|
313
|
+
// Render tool execution card
|
|
314
|
+
const badge = getToolBadge(name);
|
|
315
|
+
const target = getToolTargetDescription(name, args);
|
|
316
|
+
const details = getToolDetailsDescription(name, args);
|
|
317
|
+
console.log(` ${badge} ${chalk.gray(target)}`);
|
|
318
|
+
console.log(chalk.dim(` ${details}`));
|
|
183
319
|
// Track created or modified files
|
|
184
320
|
if (name === "write_file" || name === "patch_file") {
|
|
185
321
|
if (args.file_path) {
|
|
@@ -187,10 +323,14 @@ export class Agent {
|
|
|
187
323
|
}
|
|
188
324
|
}
|
|
189
325
|
const result = await dispatchTool(name, args);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
326
|
+
// Generate outcome result status line
|
|
327
|
+
const isError = result.startsWith("ERROR:");
|
|
328
|
+
if (isError) {
|
|
329
|
+
statusLines.push(`${chalk.hex('#ff453a')('✕')} ${chalk.gray(result.slice(6).trim())}`);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
statusLines.push(`${chalk.hex('#30d158')('✓')} ${chalk.gray(getToolSuccessSummary(name, args, result))}`);
|
|
333
|
+
}
|
|
194
334
|
this.memory.add({
|
|
195
335
|
role: "tool",
|
|
196
336
|
content: result,
|
|
@@ -198,16 +338,29 @@ export class Agent {
|
|
|
198
338
|
name,
|
|
199
339
|
});
|
|
200
340
|
}
|
|
341
|
+
// Stream results status lines
|
|
342
|
+
console.log("");
|
|
343
|
+
for (const line of statusLines) {
|
|
344
|
+
console.log(line);
|
|
345
|
+
}
|
|
346
|
+
if (iterations < MAX_ITERATIONS) {
|
|
347
|
+
console.log(chalk.dim('\n' + '─'.repeat(48) + '\n'));
|
|
348
|
+
startSpinner("thinking...");
|
|
349
|
+
}
|
|
201
350
|
}
|
|
202
|
-
//
|
|
351
|
+
// Output modified files metadata line if applicable
|
|
203
352
|
if (modifiedFiles.size > 0) {
|
|
204
|
-
console.log(chalk.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
353
|
+
console.log(chalk.dim('─') + ' ' + chalk.dim(`${modifiedFiles.size} file(s) modified`));
|
|
354
|
+
}
|
|
355
|
+
// ── Phase 3: Final Agent Response ───────────────────────────────────────
|
|
356
|
+
console.log(chalk.dim('\n' + '─'.repeat(48) + '\n'));
|
|
357
|
+
if (cleanContent) {
|
|
358
|
+
console.log(formatResponseText(cleanContent));
|
|
359
|
+
console.log(""); // one trailing blank line
|
|
208
360
|
}
|
|
209
361
|
if (iterations >= MAX_ITERATIONS) {
|
|
210
|
-
console.log(chalk.
|
|
362
|
+
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
363
|
+
console.log(chalk.dim(' Max tool iterations reached.'));
|
|
211
364
|
}
|
|
212
365
|
}
|
|
213
366
|
}
|
package/dist/index.js
CHANGED
|
@@ -9,30 +9,28 @@ const VALID_MODELS = [
|
|
|
9
9
|
"gemini-2.0-flash",
|
|
10
10
|
"gemini-2.0-pro-exp"
|
|
11
11
|
];
|
|
12
|
-
// ───
|
|
12
|
+
// ─── Startup Screen ──────────────────────────────────────────────────────────
|
|
13
13
|
function printBanner(modelName) {
|
|
14
|
+
console.log(chalk.white.bold('coder-agent') + chalk.dim(' 1.1.0'));
|
|
14
15
|
console.log("");
|
|
15
|
-
console.log(chalk.
|
|
16
|
-
console.log(chalk.gray(
|
|
17
|
-
console.log(chalk.gray(
|
|
18
|
-
console.log(chalk.gray(" ──────────────────────────────────────────────────────────"));
|
|
19
|
-
console.log(chalk.gray(" Tools: ") + chalk.white("read_file · write_file · patch_file · search_grep · run_shell · web_search"));
|
|
20
|
-
console.log(chalk.gray(" Commands: ") + chalk.white("/model · /clear · /status · /help · /exit"));
|
|
21
|
-
console.log(chalk.gray(" ──────────────────────────────────────────────────────────"));
|
|
16
|
+
console.log(chalk.dim('model ') + chalk.gray(modelName));
|
|
17
|
+
console.log(chalk.dim('context ') + chalk.gray('1m+ tokens'));
|
|
18
|
+
console.log(chalk.dim('tools ') + chalk.gray('read · write · run · search'));
|
|
22
19
|
console.log("");
|
|
20
|
+
console.log(chalk.dim('─'.repeat(48)));
|
|
23
21
|
}
|
|
24
22
|
function printHelp() {
|
|
25
|
-
console.log(chalk.bold
|
|
23
|
+
console.log(chalk.white.bold("\n Coder CLI v1.1\n"));
|
|
26
24
|
console.log(chalk.gray(" Usage:"));
|
|
27
25
|
console.log(chalk.white(" coder-agent — Start the interactive REPL"));
|
|
28
26
|
console.log(chalk.white(" coder-agent [options] — Run with config options"));
|
|
29
|
-
console.log(chalk.white(" coder-agent \"your query here\" — Run in single-shot mode
|
|
27
|
+
console.log(chalk.white(" coder-agent \"your query here\" — Run in single-shot mode"));
|
|
30
28
|
console.log("");
|
|
31
29
|
console.log(chalk.gray(" Options:"));
|
|
32
30
|
console.log(chalk.white(" -h, --help — Show this help screen"));
|
|
33
31
|
console.log(chalk.white(" -v, --version — Show version information"));
|
|
34
32
|
console.log(chalk.white(" --model <model_name> — Set default model globally"));
|
|
35
|
-
console.log(chalk.white(" --memory <scope> — Set memory scope: 'user', 'project', or 'local'
|
|
33
|
+
console.log(chalk.white(" --memory <scope> — Set memory scope: 'user', 'project', or 'local'"));
|
|
36
34
|
console.log(chalk.white(" --set-key <api_key> — Save your Gemini API Key globally"));
|
|
37
35
|
console.log(chalk.white(" --set-gemini-key <api_key> — Save your Gemini API Key globally (alias)"));
|
|
38
36
|
console.log("");
|
|
@@ -50,12 +48,13 @@ async function promptApiKey() {
|
|
|
50
48
|
output: process.stdout,
|
|
51
49
|
});
|
|
52
50
|
return new Promise((resolve) => {
|
|
53
|
-
console.log(chalk.
|
|
54
|
-
console.log(chalk.
|
|
51
|
+
console.log(chalk.dim("\n 🔑 Gemini API Key is required."));
|
|
52
|
+
console.log(chalk.dim(" Get a free key at https://aistudio.google.com"));
|
|
55
53
|
rl.question(chalk.gray("\n Enter Gemini API Key: "), async (key) => {
|
|
56
54
|
const trimmedKey = key.trim();
|
|
57
55
|
if (!trimmedKey) {
|
|
58
|
-
console.log(chalk.
|
|
56
|
+
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
57
|
+
console.log(chalk.dim(" API Key cannot be empty."));
|
|
59
58
|
rl.close();
|
|
60
59
|
process.exit(1);
|
|
61
60
|
}
|
|
@@ -63,10 +62,10 @@ async function promptApiKey() {
|
|
|
63
62
|
const save = answer.toLowerCase().startsWith("y");
|
|
64
63
|
if (save) {
|
|
65
64
|
await saveApiKey(trimmedKey);
|
|
66
|
-
console.log(chalk.
|
|
65
|
+
console.log(chalk.dim(` ✓ Key saved to ~/.coder-config.json`));
|
|
67
66
|
}
|
|
68
67
|
else {
|
|
69
|
-
console.log(chalk.
|
|
68
|
+
console.log(chalk.dim(` ⚠ Using key for this session only (not saved).`));
|
|
70
69
|
}
|
|
71
70
|
rl.close();
|
|
72
71
|
resolve(trimmedKey);
|
|
@@ -89,7 +88,8 @@ async function main() {
|
|
|
89
88
|
else if (args[i] === "--memory") {
|
|
90
89
|
const scope = args[i + 1];
|
|
91
90
|
if (scope !== "user" && scope !== "project" && scope !== "local") {
|
|
92
|
-
console.log(chalk.
|
|
91
|
+
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
92
|
+
console.log(chalk.dim(" Invalid memory scope. Choose 'user', 'project', or 'local'."));
|
|
93
93
|
process.exit(1);
|
|
94
94
|
}
|
|
95
95
|
tempMemoryScope = scope;
|
|
@@ -98,11 +98,12 @@ async function main() {
|
|
|
98
98
|
else if (args[i] === "--set-key" || args[i] === "--set-gemini-key") {
|
|
99
99
|
const key = args[i + 1];
|
|
100
100
|
if (!key) {
|
|
101
|
-
console.log(chalk.
|
|
101
|
+
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
102
|
+
console.log(chalk.dim(" Please specify an API key."));
|
|
102
103
|
process.exit(1);
|
|
103
104
|
}
|
|
104
105
|
await saveApiKey(key);
|
|
105
|
-
console.log(chalk.
|
|
106
|
+
console.log(chalk.dim("✓ Gemini API Key saved successfully to global config."));
|
|
106
107
|
process.exit(0);
|
|
107
108
|
}
|
|
108
109
|
else if (args[i] === "-h" || args[i] === "--help") {
|
|
@@ -127,14 +128,15 @@ async function main() {
|
|
|
127
128
|
defaultModel = storedModel;
|
|
128
129
|
}
|
|
129
130
|
if (tempModel && !VALID_MODELS.includes(tempModel)) {
|
|
130
|
-
console.log(chalk.
|
|
131
|
+
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
132
|
+
console.log(chalk.dim(` Invalid model: ${tempModel}. Choose one of: ${VALID_MODELS.join(", ")}`));
|
|
131
133
|
process.exit(1);
|
|
132
134
|
}
|
|
133
135
|
const modelToUse = tempModel || defaultModel;
|
|
134
136
|
// Save model if specified without query
|
|
135
137
|
if (tempModel && queryArgs.length === 0) {
|
|
136
138
|
await saveModel(tempModel);
|
|
137
|
-
console.log(chalk.
|
|
139
|
+
console.log(chalk.dim(`✓ Default model set to: ${tempModel}`));
|
|
138
140
|
process.exit(0);
|
|
139
141
|
}
|
|
140
142
|
// Bootstrap API Key if missing
|
|
@@ -145,20 +147,19 @@ async function main() {
|
|
|
145
147
|
// Single-Shot Mode
|
|
146
148
|
if (queryArgs.length > 0) {
|
|
147
149
|
const singleShotPrompt = queryArgs.join(" ").trim();
|
|
148
|
-
console.log(chalk.gray(`🚀 Running single-shot query using model '${modelToUse}'...`));
|
|
149
|
-
console.log(chalk.gray(`❯ Query: ${singleShotPrompt}`));
|
|
150
150
|
try {
|
|
151
151
|
await agent.chat(singleShotPrompt);
|
|
152
152
|
}
|
|
153
153
|
catch (err) {
|
|
154
|
+
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
154
155
|
if (err?.status === 401) {
|
|
155
|
-
console.log(chalk.
|
|
156
|
+
console.log(chalk.dim(" Invalid API key. Check your configuration."));
|
|
156
157
|
}
|
|
157
158
|
else if (err?.status === 429) {
|
|
158
|
-
console.log(chalk.
|
|
159
|
+
console.log(chalk.dim(" Rate limit exceeded on Gemini API."));
|
|
159
160
|
}
|
|
160
161
|
else {
|
|
161
|
-
console.log(chalk.
|
|
162
|
+
console.log(chalk.dim(` ${err.message}`));
|
|
162
163
|
}
|
|
163
164
|
process.exit(1);
|
|
164
165
|
}
|
|
@@ -172,7 +173,7 @@ async function main() {
|
|
|
172
173
|
terminal: true,
|
|
173
174
|
});
|
|
174
175
|
const prompt = () => {
|
|
175
|
-
rl.question(chalk.
|
|
176
|
+
rl.question(chalk.hex('#0a84ff')('›') + ' ', async (input) => {
|
|
176
177
|
const trimmed = input.trim();
|
|
177
178
|
if (!trimmed) {
|
|
178
179
|
prompt();
|
|
@@ -180,43 +181,47 @@ async function main() {
|
|
|
180
181
|
}
|
|
181
182
|
// Built-in slash commands
|
|
182
183
|
if (trimmed === "/exit" || trimmed === "/quit") {
|
|
183
|
-
console.log(chalk.gray("\nGoodbye."));
|
|
184
184
|
rl.close();
|
|
185
185
|
process.exit(0);
|
|
186
186
|
}
|
|
187
187
|
if (trimmed === "/clear") {
|
|
188
188
|
agent.clearMemory();
|
|
189
|
+
console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray('Memory cleared'));
|
|
189
190
|
prompt();
|
|
190
191
|
return;
|
|
191
192
|
}
|
|
192
193
|
if (trimmed === "/status") {
|
|
193
|
-
console.log(chalk.
|
|
194
|
-
console.log(chalk.gray(` Memory: `) + chalk.white(agent.memoryStatus()));
|
|
194
|
+
console.log(chalk.dim(`session · ${agent.memoryStatus()}`));
|
|
195
195
|
prompt();
|
|
196
196
|
return;
|
|
197
197
|
}
|
|
198
198
|
if (trimmed.startsWith("/model")) {
|
|
199
199
|
const parts = trimmed.split(/\s+/);
|
|
200
200
|
if (parts.length === 1) {
|
|
201
|
-
console.log(chalk.
|
|
202
|
-
console.log(chalk.gray(`
|
|
203
|
-
console.log(chalk.gray(`\n To switch model, run: /model <model-name>`));
|
|
201
|
+
console.log(chalk.dim(`model `) + chalk.gray(agent.getModel()));
|
|
202
|
+
console.log(chalk.gray(`options `) + chalk.gray(VALID_MODELS.join(" · ")));
|
|
204
203
|
}
|
|
205
204
|
else {
|
|
206
205
|
const newModel = parts[1];
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
if (VALID_MODELS.includes(newModel)) {
|
|
207
|
+
agent.setModel(newModel);
|
|
208
|
+
console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray(`Switched model to: ${newModel}`));
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
212
|
+
console.log(chalk.dim(` Model must be one of: ${VALID_MODELS.join(" · ")}`));
|
|
213
|
+
}
|
|
209
214
|
}
|
|
210
215
|
prompt();
|
|
211
216
|
return;
|
|
212
217
|
}
|
|
213
218
|
if (trimmed === "/help") {
|
|
214
|
-
console.log(chalk.bold
|
|
219
|
+
console.log(chalk.white.bold("\n Interactive Commands:"));
|
|
215
220
|
console.log(chalk.gray(` /model [name] — View active model or switch to [name]
|
|
216
221
|
/clear — Wipe conversation memory
|
|
217
222
|
/status — Show active model and memory usage
|
|
218
223
|
/exit — Exit Coder`));
|
|
219
|
-
console.log(chalk.bold
|
|
224
|
+
console.log(chalk.white.bold("\n API Keys & Configuration:"));
|
|
220
225
|
console.log(chalk.gray(` • Stored at: ~/.coder-config.json
|
|
221
226
|
• To change key: Exit and run 'coder-agent --set-key <key>'
|
|
222
227
|
• Env variable option: GEMINI_API_KEY`));
|
|
@@ -227,14 +232,15 @@ async function main() {
|
|
|
227
232
|
await agent.chat(trimmed);
|
|
228
233
|
}
|
|
229
234
|
catch (err) {
|
|
235
|
+
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
230
236
|
if (err?.status === 401) {
|
|
231
|
-
console.log(chalk.
|
|
237
|
+
console.log(chalk.dim(" Invalid API key. Check your configuration."));
|
|
232
238
|
}
|
|
233
239
|
else if (err?.status === 429) {
|
|
234
|
-
console.log(chalk.
|
|
240
|
+
console.log(chalk.dim(" Rate limit exceeded on Gemini API."));
|
|
235
241
|
}
|
|
236
242
|
else {
|
|
237
|
-
console.log(chalk.
|
|
243
|
+
console.log(chalk.dim(` ${err.message}`));
|
|
238
244
|
}
|
|
239
245
|
}
|
|
240
246
|
prompt();
|
|
@@ -242,12 +248,12 @@ async function main() {
|
|
|
242
248
|
};
|
|
243
249
|
// Handle Ctrl+C gracefully
|
|
244
250
|
rl.on("SIGINT", () => {
|
|
245
|
-
console.log(chalk.gray("\n\nGoodbye."));
|
|
246
251
|
process.exit(0);
|
|
247
252
|
});
|
|
248
253
|
prompt();
|
|
249
254
|
}
|
|
250
255
|
main().catch((err) => {
|
|
251
|
-
console.error(chalk.
|
|
256
|
+
console.error(chalk.hex('#ff453a')('✕ error'));
|
|
257
|
+
console.error(chalk.dim(` ${err.message}`));
|
|
252
258
|
process.exit(1);
|
|
253
259
|
});
|
package/dist/memory.js
CHANGED
|
@@ -6,7 +6,7 @@ const SYSTEM_PROMPT = `You are a powerful, intelligent CLI coding agent named Co
|
|
|
6
6
|
The tools available to you are provided automatically by the API schema. Do NOT describe the tools in your text or invent custom tags.
|
|
7
7
|
|
|
8
8
|
PRINCIPLES & SYSTEM PROTOCOLS FOR ERROR-FREE EXECUTION:
|
|
9
|
-
1. Ground Truth Workspace Context: Use the provided environment platform info, workspace file structure snapshot, and package configurations as your primary source of requirements. Do not
|
|
9
|
+
1. Ground Truth Workspace Context: Use the provided environment platform info, workspace file structure snapshot, and package configurations as your primary source of requirements. Note that the file structure snapshot only lists top-level files and folders, NOT the recursive contents of subdirectories. Do not assume a file in a subdirectory does not exist just because it is not in the top-level snapshot.
|
|
10
10
|
2. Read before Writing/Editing: Always look at the files you want to change first. Read the relevant lines (using read_file_lines or read_file) to understand import requirements, types, and architecture.
|
|
11
11
|
3. Precise Target Editing: Prefer patching files (using patch_file) over complete overwriting. Ensure targeted matches are unique and match exactly, leaving existing unrelated functions/comments intact.
|
|
12
12
|
4. Auto-Verification Loop: After any code or file edit, you MUST run the appropriate compiler, type-check, build script, or test tool (e.g. npm run build, npx tsc, pytest, cargo build, etc.) to verify your changes are syntactically and logically correct. If compilation fails, diagnose the error and patch it immediately.
|
|
@@ -20,6 +20,7 @@ Guidelines:
|
|
|
20
20
|
- When you encounter errors, diagnose and fix them autonomously.
|
|
21
21
|
- Prefer running commands to verify assumptions rather than guessing.
|
|
22
22
|
- Before answering questions or checking for errors in the codebase, always inspect the workspace (e.g., list directories, read files) to identify the files and languages present. Do not guess file names or run commands on files without checking if they exist first.
|
|
23
|
+
- Never assume a file mentioned in the user's query or diagnostic payload does not exist just because it is missing from the top-level file snapshot. Always attempt to read the file path directly (using read_file or read_file_lines), or search for it (using find_files) to verify its existence.
|
|
23
24
|
- Focus on the actual files and programming language of the codebase you are currently running in (the user's repository). Do not assume the project is in a different language. Identify the project type and use appropriate commands (e.g., check package.json/tsconfig.json and run 'npm run build' or 'npx tsc' for TypeScript, or use python commands only if the workspace is a Python project).
|
|
24
25
|
- NEVER create dummy or placeholder files (such as write_file of a hello-world 'test.py' or 'filename.py') to simulate compilation or execution. Work only with the actual code of the project.
|
|
25
26
|
- When asked to "check for syntax errors" or "compile", do NOT search the files for the literal string "syntax error". Instead, run the project's compiler, build script, type-checker, or linter (such as 'npm run build', 'npx tsc --noEmit', or standard language compilers) to find actual code syntax issues.
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coder-agent",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "CLI coding agent powered by Google Gemini",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
+
"coder-agent": "dist/index.js",
|
|
8
9
|
"coder": "dist/index.js",
|
|
9
10
|
"gemini-agent": "dist/index.js",
|
|
10
11
|
"groq-agent": "dist/index.js"
|