coder-agent 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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; // prevent infinite tool loops
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 (with standard/parsed tool calls)
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
- let cleanContent = msg.content ?? "";
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
- // If the model returned conversational text along with tool calls, print it cleanly
171
- if (cleanContent) {
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
- console.log(chalk.gray(` ○ tool: ${name}`) + chalk.gray(` (${JSON.stringify(args).slice(0, 100)}...)`));
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
- const preview = result.length > 300 ? result.slice(0, 300) + "\n … (truncated)" : result;
191
- // Format preview text elegantly with indentation
192
- const indentedPreview = preview.split("\n").map(l => " " + l).join("\n");
193
- console.log(chalk.gray(indentedPreview));
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
- // Print a clean summary of modified files at the end of the conversation turn
351
+ // Output modified files metadata line if applicable
203
352
  if (modifiedFiles.size > 0) {
204
- console.log(chalk.gray("\n Modified files:"));
205
- for (const file of modifiedFiles) {
206
- console.log(chalk.white(` • ${file}`));
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.red(" ✗ Max tool iterations reached."));
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
- // ─── Banner ──────────────────────────────────────────────────────────────────
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.bold.white(" Coder"));
16
- console.log(chalk.gray(` Model: ${modelName}`));
17
- console.log(chalk.gray(" Status: Ready"));
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.white("\n Coder CLI v1.1\n"));
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 (exits after completion)"));
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' (default: 'project')"));
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.gray("\n 🔑 Gemini API Key is required."));
54
- console.log(chalk.gray(" Get a free key at https://aistudio.google.com"));
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.red(" ✗ API Key cannot be empty."));
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.gray(` ✓ Key saved to ~/.coder-config.json`));
65
+ console.log(chalk.dim(` ✓ Key saved to ~/.coder-config.json`));
67
66
  }
68
67
  else {
69
- console.log(chalk.gray(` ⚠ Using key for this session only (not saved).`));
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.red("✗ Invalid memory scope. Choose 'user', 'project', or 'local'."));
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.red("✗ Please specify an API key."));
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.gray("✓ Gemini API Key saved successfully to global config."));
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.red(`✗ Invalid model: ${tempModel}. Choose one of: ${VALID_MODELS.join(", ")}`));
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.gray(`✓ Default model set to: ${tempModel}`));
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.red("Invalid API key. Check your configuration."));
156
+ console.log(chalk.dim(" Invalid API key. Check your configuration."));
156
157
  }
157
158
  else if (err?.status === 429) {
158
- console.log(chalk.red("\n✗ Rate limit exceeded on Gemini API."));
159
+ console.log(chalk.dim(" Rate limit exceeded on Gemini API."));
159
160
  }
160
161
  else {
161
- console.log(chalk.red(`✗ Error: ${err.message}`));
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.gray("\ncoder-agent "), async (input) => {
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.gray(` Model: `) + chalk.white(agent.getModel()));
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.gray(`\n Active model: ${agent.getModel()}`));
202
- console.log(chalk.gray(` Available models:\n` + VALID_MODELS.map(m => ` • ${m}`).join("\n")));
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
- agent.setModel(newModel);
208
- console.log(chalk.gray(`\n✓ Switched active model to: ${newModel}`));
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.white("\n Interactive Commands:"));
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.white("\n API Keys & Configuration:"));
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.red("Invalid API key. Check your configuration."));
237
+ console.log(chalk.dim(" Invalid API key. Check your configuration."));
232
238
  }
233
239
  else if (err?.status === 429) {
234
- console.log(chalk.red("\n✗ Rate limit exceeded on Gemini API."));
240
+ console.log(chalk.dim(" Rate limit exceeded on Gemini API."));
235
241
  }
236
242
  else {
237
- console.log(chalk.red(`✗ Error: ${err.message}`));
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.red(`Fatal Error: ${err.message}`));
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 guess what files exist or how the project compiles.
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,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-agent",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "CLI coding agent powered by Google Gemini",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",