coder-agent 2.9.12 โ†’ 2.9.14

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/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # ๐Ÿค– Coder
2
2
 
3
- A powerful, zero-dependency, cross-platform CLI coding agent powered by **Google Gemini** (gemini-2.5-flash / gemini-2.5-pro) with native tool execution and a clean, minimal Apple-inspired design.
3
+ A powerful, zero-dependency, cross-platform CLI coding agent powered by **Google Gemini, Groq, Mistral, OpenRouter, NVIDIA NIM, and GitHub Models** with native tool execution and a clean, minimal Apple-inspired design.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **Apple-Inspired Design**: A clean, high-contrast, minimalist terminal UI styled in elegant monochrome greys and whites.
8
+ - **Multi-Provider AI Engine**: Query models from Gemini API, Groq, Mistral, OpenRouter, NVIDIA NIM, or GitHub Models.
9
+ - **Auto-Rotation & Fallbacks**: Automatically rotates to backup models within a provider family if a server error or rate-limit is hit.
10
+ - **Smart Rate-Limit Throttling**: Implements smart background cooldown timers between requests to prevent hitting provider Requests Per Minute (RPM) limits without disrupting the user experience.
8
11
  - **Global CLI Execution**: Install once and run anywhere via `coder-agent` (or the `coder` / `gemini-agent` / `groq-agent` compatibility aliases).
9
12
  - **System Automation Tools**:
10
13
  - `read_file` / `write_file` / `patch_file` โ€” Segmented reading and patch-based editing.
@@ -36,23 +39,30 @@ npx coder-agent
36
39
 
37
40
  ## Setup & Configuration
38
41
 
39
- The agent requires a **Gemini API Key**.
42
+ The agent requires at least one API Key to function. By default, it uses the **Gemini API Key**.
40
43
 
41
44
  ### 1. Interactive Bootstrapping
42
- Simply run `coder-agent`. If no key is configured, the CLI will interactively prompt you for the key and offer to save it globally.
45
+ Simply run `coder-agent`. If no key is configured, the CLI will interactively prompt you for a Gemini key and offer to save it globally.
43
46
 
44
- ### 2. Command Line Flags
45
- You can save your key globally at any time using flags:
47
+ ### 2. Environment Variables
48
+ You can export provider API keys as environment variables:
46
49
 
47
50
  ```bash
48
- coder-agent --set-key YOUR_GEMINI_API_KEY
51
+ export GEMINI_API_KEY=AIzaSy...
52
+ export GROQ_API_KEY=gsk_...
53
+ export MISTRAL_API_KEY=...
54
+ export OPENROUTER_API_KEY=sk-or-...
55
+ export NVIDIA_API_KEY=nvapi-...
56
+ export GITHUB_TOKEN=github_pat_...
49
57
  ```
50
58
 
51
- ### 3. Environment Variables
52
- Alternatively, you can export it as an environment variable:
59
+ ### 3. REPL Commands (Config File)
60
+ Inside the interactive REPL session, use the `/key` command to save API keys globally:
53
61
 
54
62
  ```bash
55
- export GEMINI_API_KEY=AIzaSy...
63
+ /key # View active API key status for all providers
64
+ /key <api_key> # Save your Gemini API Key globally
65
+ /key <provider> <api_key> # Save key for a specific provider (e.g. /key groq gsk_...)
56
66
  ```
57
67
 
58
68
  ---
@@ -74,7 +84,7 @@ coder-agent "write a binary search script in Python"
74
84
  ### CLI Command Options
75
85
  - `-h, --help` โ€” Show the help screen.
76
86
  - `-v, --version` โ€” Show version information.
77
- - `--model <name>` โ€” Set the default Gemini model globally.
87
+ - `--model <name>` โ€” Set the default model globally.
78
88
  - `--set-key <key>` โ€” Persist your Gemini API Key globally.
79
89
  - `--memory <scope>` โ€” Define memory scope (`project`, `user`, or `local`).
80
90
 
@@ -86,7 +96,8 @@ Type these commands directly inside the interactive session:
86
96
 
87
97
  | Command | Description |
88
98
  |---------|-------------|
89
- | `/model [name]` | View active model or switch active model dynamically (e.g. `/model gemini-2.5-pro`) |
99
+ | `/model [name]` | View active model or switch active model dynamically (e.g. `/model groq/llama-3.3-70b-specdec`) |
100
+ | `/key [provider] [key]` | View stored API keys or save keys globally |
90
101
  | `/clear` | Wipe conversation memory |
91
102
  | `/status` | Show active model and memory usage details |
92
103
  | `/help` | Show command summary |
@@ -95,10 +106,27 @@ Type these commands directly inside the interactive session:
95
106
  ---
96
107
 
97
108
  ## Popular Models Supported
98
- - `gemini-2.5-flash` (Default, highly capable, extremely fast, massive 1M+ token context)
99
- - `gemini-2.5-pro` (Reasoning model, superb for complex coding tasks)
100
- - `gemini-2.0-flash` (Ultra-fast, lightweight)
101
- - `gemini-2.0-pro-exp` (Experimental reasoning model)
109
+
110
+ To use a model from a different provider, prefix the model name with `provider/`:
111
+
112
+ * **Google Gemini (Default)**:
113
+ * `gemini-2.5-flash` โ€” Default, highly capable & fast (1M+ context).
114
+ * `gemini-2.5-pro` โ€” High reasoning model, superb for complex coding.
115
+ * `gemini-3.5-flash` โ€” Newest generation, ultra-fast.
116
+ * **Groq (`groq/`)**:
117
+ * `groq/llama-3.3-70b-specdec` โ€” Blazing fast reasoning.
118
+ * `groq/gemma-2-9b-it` โ€” Lightweight, fast open-weights.
119
+ * **Mistral (`mistral/`)**:
120
+ * `mistral/codestral` โ€” SOTA open coding model.
121
+ * **OpenRouter (`openrouter/`)**:
122
+ * `openrouter/qwen/qwen-2.5-coder-32b-instruct:free` โ€” Popular free-tier coding model.
123
+ * `openrouter/deepseek/deepseek-r1:free` โ€” Powerful reasoning/thinking model.
124
+ * **NVIDIA NIM (`nvidia/`)**:
125
+ * `nvidia/qwen-2.5-coder-32b-instruct` โ€” NVIDIA-optimized coding model.
126
+ * `nvidia/deepseek-r1` โ€” High-speed reasoning model.
127
+ * **GitHub Models (`github/`)**:
128
+ * `github/claude-3.5-sonnet` โ€” Claude 3.5 Sonnet (requires GitHub token).
129
+ * `github/gpt-4o` โ€” OpenAI GPT-4o.
102
130
 
103
131
  ---
104
132
 
package/dist/agent.js CHANGED
@@ -3,6 +3,7 @@ import * as path from "path";
3
3
  import * as fs from "fs/promises";
4
4
  import { TOOL_DEFINITIONS, dispatchTool } from "./tools.js";
5
5
  import { Memory, getAgentMemoryEntrypoint } from "./memory.js";
6
+ import { getProviderApiKey } from "./config.js";
6
7
  // โ”€โ”€โ”€ Loading Spinner โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
7
8
  let spinnerTimer = null;
8
9
  let currentFrame = 0;
@@ -405,21 +406,110 @@ function extractTextToolCalls(content) {
405
406
  return calls;
406
407
  }
407
408
  // โ”€โ”€โ”€ Gemini API client with Auto-Rotation Fallback โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
408
- async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initialDelayMs = 1500, signal, silent = false) {
409
- const rotationList = [
410
- "gemini-2.0-flash",
411
- "gemini-2.0-pro-exp",
409
+ const lastRequestTimestamps = {};
410
+ function getProviderAndModel(modelName) {
411
+ const lowercase = modelName.toLowerCase();
412
+ if (lowercase.startsWith("groq/")) {
413
+ return { provider: "groq", model: modelName.slice(5) };
414
+ }
415
+ if (lowercase.startsWith("mistral/")) {
416
+ return { provider: "mistral", model: modelName.slice(8) };
417
+ }
418
+ if (lowercase.startsWith("openrouter/")) {
419
+ return { provider: "openrouter", model: modelName.slice(11) };
420
+ }
421
+ if (lowercase.startsWith("nvidia/")) {
422
+ return { provider: "nvidia", model: modelName.slice(7) };
423
+ }
424
+ if (lowercase.startsWith("github/")) {
425
+ return { provider: "github", model: modelName.slice(7) };
426
+ }
427
+ return { provider: "gemini", model: modelName };
428
+ }
429
+ function getProviderEndpoint(provider) {
430
+ switch (provider) {
431
+ case "gemini":
432
+ return "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions";
433
+ case "groq":
434
+ return "https://api.groq.com/openai/v1/chat/completions";
435
+ case "mistral":
436
+ return "https://api.mistral.ai/v1/chat/completions";
437
+ case "openrouter":
438
+ return "https://openrouter.ai/api/v1/chat/completions";
439
+ case "nvidia":
440
+ return "https://integrate.api.nvidia.com/v1/chat/completions";
441
+ case "github":
442
+ return "https://models.inference.ai.azure.com/chat/completions";
443
+ }
444
+ return "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions";
445
+ }
446
+ function getRequiredDelayMs(provider, model) {
447
+ const lowercaseModel = model.toLowerCase();
448
+ switch (provider) {
449
+ case "gemini":
450
+ if (lowercaseModel.includes("pro")) {
451
+ return 30000; // 30s for Pro (2 RPM)
452
+ }
453
+ return 4000; // 4s for Flash (15 RPM)
454
+ case "groq":
455
+ return 2000; // 2s (30 RPM)
456
+ case "mistral":
457
+ return 1500; // 1.5s (1 request/second + buffer)
458
+ case "openrouter":
459
+ return 5000; // 5s for free/shared queue
460
+ case "nvidia":
461
+ return 2000; // 2s (40 RPM)
462
+ case "github":
463
+ if (lowercaseModel.includes("sonnet") || lowercaseModel.includes("gpt-4o")) {
464
+ return 30000; // 30s for flagship (50 RPD)
465
+ }
466
+ return 6000; // 6s for open-weights (150 RPD)
467
+ }
468
+ return 2000; // default fallback
469
+ }
470
+ const DEFAULT_ROTATION_MAP = {
471
+ gemini: [
412
472
  "gemini-2.5-flash",
413
473
  "gemini-2.5-pro",
414
474
  "gemini-3.5-flash",
415
475
  "gemini-3.1-flash-lite",
416
476
  "gemma-4-31b-it",
417
- "gemma-4-26b-a4b-it"
418
- ];
477
+ "gemma-4-26b-a4b-it",
478
+ "gemini-2.0-flash",
479
+ "gemini-2.0-pro-exp"
480
+ ],
481
+ groq: [
482
+ "groq/llama-3.3-70b-specdec",
483
+ "groq/gemma-2-9b-it"
484
+ ],
485
+ mistral: [
486
+ "mistral/codestral",
487
+ "mistral/mistral-large-latest"
488
+ ],
489
+ openrouter: [
490
+ "openrouter/qwen/qwen-2.5-coder-32b-instruct:free",
491
+ "openrouter/deepseek/deepseek-r1:free",
492
+ "openrouter/meta-llama/llama-3.3-70b-instruct:free"
493
+ ],
494
+ nvidia: [
495
+ "nvidia/qwen-2.5-coder-32b-instruct",
496
+ "nvidia/deepseek-r1",
497
+ "nvidia/meta/llama-3.3-70b-instruct"
498
+ ],
499
+ github: [
500
+ "github/claude-3.5-sonnet",
501
+ "github/gpt-4o",
502
+ "github/meta-llama-3.3-70b-instruct"
503
+ ]
504
+ };
505
+ async function callAPIWithRotationAndThrottling(defaultApiKey, params, maxRetries = 3, initialDelayMs = 1500, signal, silent = false) {
506
+ const { provider: initialProvider } = getProviderAndModel(params.model);
507
+ let rotationList = DEFAULT_ROTATION_MAP[initialProvider] || DEFAULT_ROTATION_MAP.gemini;
419
508
  let currentModel = params.model;
420
509
  let modelIndex = rotationList.indexOf(currentModel);
421
510
  if (modelIndex === -1) {
422
- modelIndex = 0; // Default if not in list
511
+ rotationList = [currentModel, ...rotationList];
512
+ modelIndex = 0;
423
513
  }
424
514
  let attempts = 0;
425
515
  while (attempts < maxRetries) {
@@ -428,19 +518,53 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
428
518
  abortErr.name = "AbortError";
429
519
  throw abortErr;
430
520
  }
521
+ const { provider, model: actualModelName } = getProviderAndModel(currentModel);
522
+ const providerKey = await getProviderApiKey(provider) || defaultApiKey;
523
+ if (!providerKey) {
524
+ throw new Error(`API key for provider '${provider}' is missing. Please set it using: /key ${provider} <api_key>`);
525
+ }
526
+ // Apply rate-limiting throttle cooldown
527
+ const requiredDelay = getRequiredDelayMs(provider, actualModelName);
528
+ const lastRequestTime = lastRequestTimestamps[provider] || 0;
529
+ const elapsed = Date.now() - lastRequestTime;
530
+ if (elapsed < requiredDelay) {
531
+ const waitMs = requiredDelay - elapsed;
532
+ if (!silent) {
533
+ updateSpinner("thinking...");
534
+ }
535
+ await new Promise((resolve, reject) => {
536
+ const timer = setTimeout(resolve, waitMs);
537
+ if (signal) {
538
+ const onAbort = () => {
539
+ clearTimeout(timer);
540
+ const abortErr = new Error("The user aborted a request.");
541
+ abortErr.name = "AbortError";
542
+ reject(abortErr);
543
+ };
544
+ signal.addEventListener("abort", onAbort);
545
+ }
546
+ });
547
+ }
548
+ lastRequestTimestamps[provider] = Date.now();
431
549
  try {
432
- const res = await fetch("https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", {
550
+ const endpoint = getProviderEndpoint(provider);
551
+ const headers = {
552
+ "Content-Type": "application/json",
553
+ "Authorization": `Bearer ${providerKey}`
554
+ };
555
+ if (provider === "openrouter") {
556
+ headers["HTTP-Referer"] = "https://github.com/google/antigravity";
557
+ headers["X-Title"] = "Coder Agent";
558
+ }
559
+ const res = await fetch(endpoint, {
433
560
  method: "POST",
434
- headers: {
435
- "Content-Type": "application/json",
436
- "Authorization": `Bearer ${apiKey}`
437
- },
438
- body: JSON.stringify({ ...params, model: currentModel }),
561
+ headers,
562
+ body: JSON.stringify({ ...params, model: actualModelName }),
439
563
  signal
440
564
  });
441
565
  if (!res.ok) {
442
566
  const errText = await res.text();
443
- const err = new Error(`Gemini API error (${res.status}): ${errText}`);
567
+ const err = new Error(`${provider.toUpperCase()} API error (${res.status}): ${errText}`);
444
568
  err.status = res.status;
445
569
  throw err;
446
570
  }
@@ -609,7 +733,6 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
609
733
  const isRetryableError = status === 429 || status === 503 || (status >= 500 && status < 600) || !status;
610
734
  const isModelError = status === 404 || status === 400;
611
735
  if (isRetryableError || isModelError) {
612
- // Rotate model immediately if rate limit or model error occurred
613
736
  if (modelIndex + 1 < rotationList.length) {
614
737
  modelIndex++;
615
738
  const nextModel = rotationList[modelIndex];
@@ -843,7 +966,7 @@ export class Agent {
843
966
  }
844
967
  iterations++;
845
968
  updateSpinner("thinking...");
846
- const responseObj = await callGeminiAPIWithRotation(this.apiKey, {
969
+ const responseObj = await callAPIWithRotationAndThrottling(this.apiKey, {
847
970
  model: this.model,
848
971
  messages: this.memory.getAll(),
849
972
  tools: TOOL_DEFINITIONS,
@@ -1062,7 +1185,7 @@ Instructions:
1062
1185
  3. Integrate these new learnings and file catalog updates into the existing memory structure. Keep existing useful learnings/preferences, but clean up duplicates or obsolete info.
1063
1186
  4. Keep the content concise, clean, and formatted in Markdown.
1064
1187
  5. If there are no new learnings, setup details, file updates, or instructions in the conversation, output EXACTLY the existing memory content. Do not add conversational text, just output the updated/existing markdown content.`;
1065
- const responseObj = await callGeminiAPIWithRotation(this.apiKey, {
1188
+ const responseObj = await callAPIWithRotationAndThrottling(this.apiKey, {
1066
1189
  model: this.model,
1067
1190
  messages: [{ role: "user", content: prompt }],
1068
1191
  temperature: 0.1,
package/dist/config.js CHANGED
@@ -53,15 +53,57 @@ async function writeConfig(config) {
53
53
  console.error(`\n โš ๏ธ Failed to save config to ${CONFIG_FILE}: ${err.message}`);
54
54
  }
55
55
  }
56
- export async function getStoredApiKey() {
56
+ export async function getStoredApiKey(provider) {
57
57
  const config = await readConfig();
58
- return config.geminiApiKey;
58
+ const p = provider?.toLowerCase();
59
+ if (!p || p === "gemini")
60
+ return config.geminiApiKey;
61
+ if (p === "groq")
62
+ return config.groqApiKey;
63
+ if (p === "mistral")
64
+ return config.mistralApiKey;
65
+ if (p === "openrouter")
66
+ return config.openrouterApiKey;
67
+ if (p === "nvidia")
68
+ return config.nvidiaApiKey;
69
+ if (p === "github")
70
+ return config.githubApiKey;
71
+ return undefined;
59
72
  }
60
- export async function saveApiKey(apiKey) {
73
+ export async function saveApiKey(apiKey, provider) {
61
74
  const config = await readConfig();
62
- config.geminiApiKey = apiKey.trim();
75
+ const trimmed = apiKey.trim();
76
+ const p = provider?.toLowerCase();
77
+ if (!p || p === "gemini")
78
+ config.geminiApiKey = trimmed;
79
+ else if (p === "groq")
80
+ config.groqApiKey = trimmed;
81
+ else if (p === "mistral")
82
+ config.mistralApiKey = trimmed;
83
+ else if (p === "openrouter")
84
+ config.openrouterApiKey = trimmed;
85
+ else if (p === "nvidia")
86
+ config.nvidiaApiKey = trimmed;
87
+ else if (p === "github")
88
+ config.githubApiKey = trimmed;
63
89
  await writeConfig(config);
64
90
  }
91
+ export async function getProviderApiKey(provider) {
92
+ const p = provider.toLowerCase();
93
+ if (p === "gemini")
94
+ return process.env.GEMINI_API_KEY || await getStoredApiKey("gemini");
95
+ if (p === "groq")
96
+ return process.env.GROQ_API_KEY || await getStoredApiKey("groq");
97
+ if (p === "mistral")
98
+ return process.env.MISTRAL_API_KEY || await getStoredApiKey("mistral");
99
+ if (p === "openrouter")
100
+ return process.env.OPENROUTER_API_KEY || await getStoredApiKey("openrouter");
101
+ if (p === "nvidia")
102
+ return process.env.NVIDIA_API_KEY || await getStoredApiKey("nvidia");
103
+ if (p === "github")
104
+ return process.env.GITHUB_API_KEY || process.env.GITHUB_TOKEN || await getStoredApiKey("github");
105
+ return undefined;
106
+ }
65
107
  export async function getStoredModel() {
66
108
  const config = await readConfig();
67
109
  return config.defaultModel;
@@ -72,13 +114,10 @@ export async function saveModel(model) {
72
114
  await writeConfig(config);
73
115
  }
74
116
  export async function getStoredGeminiApiKey() {
75
- const config = await readConfig();
76
- return config.geminiApiKey;
117
+ return getStoredApiKey("gemini");
77
118
  }
78
119
  export async function saveGeminiApiKey(geminiApiKey) {
79
- const config = await readConfig();
80
- config.geminiApiKey = geminiApiKey.trim();
81
- await writeConfig(config);
120
+ await saveApiKey(geminiApiKey, "gemini");
82
121
  }
83
122
  export async function getLastUsedInfo() {
84
123
  const config = await readConfig();
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import * as readline from "readline";
4
4
  import chalk from "chalk";
5
5
  import figlet from "figlet";
6
6
  import { Agent } from "./agent.js";
7
- import { getStoredApiKey, saveApiKey, getStoredModel, saveModel, getLastUsedInfo, saveLastUsedInfo } from "./config.js";
7
+ import { getStoredApiKey, saveApiKey, getStoredModel, saveModel, getLastUsedInfo, saveLastUsedInfo, getProviderApiKey } from "./config.js";
8
8
  const require = createRequire(import.meta.url);
9
9
  const packageJson = require("../package.json");
10
10
  const CURRENT_VERSION = packageJson.version;
@@ -18,6 +18,12 @@ const VALID_MODELS = [
18
18
  "gemma-4-31b-it",
19
19
  "gemma-4-26b-a4b-it"
20
20
  ];
21
+ function isValidModel(modelName) {
22
+ if (VALID_MODELS.includes(modelName))
23
+ return true;
24
+ const prefixes = ["groq/", "mistral/", "openrouter/", "nvidia/", "github/"];
25
+ return prefixes.some(prefix => modelName.toLowerCase().startsWith(prefix));
26
+ }
21
27
  function printBanner(modelName) {
22
28
  console.clear();
23
29
  console.log('');
@@ -83,6 +89,14 @@ function printHelp() {
83
89
  console.log(chalk.white(" gemini-2.0-flash โ€” (Deprecated) Ultra-fast, lightweight"));
84
90
  console.log(chalk.white(" gemini-2.0-pro-exp โ€” (Deprecated) Experimental reasoning"));
85
91
  console.log("");
92
+ console.log(chalk.gray(" Popular Multi-Provider Models (prefixed with provider/):"));
93
+ console.log(chalk.white(" groq/llama-3.3-70b-specdec โ€” Groq Llama 3.3 70B (extremely fast)"));
94
+ console.log(chalk.white(" mistral/codestral โ€” Mistral Codestral (highly rated coding model)"));
95
+ console.log(chalk.white(" openrouter/qwen/qwen-2.5-coder-32b-instruct:free โ€” OpenRouter Qwen 2.5 Coder Free"));
96
+ console.log(chalk.white(" openrouter/deepseek/deepseek-r1:free โ€” OpenRouter DeepSeek R1 Free"));
97
+ console.log(chalk.white(" nvidia/qwen-2.5-coder-32b-instruct โ€” NVIDIA NIM Qwen 2.5 Coder 32B"));
98
+ console.log(chalk.white(" github/claude-3.5-sonnet โ€” GitHub Claude 3.5 Sonnet (requires GitHub token)"));
99
+ console.log("");
86
100
  }
87
101
  // โ”€โ”€โ”€ API Key Bootstrap โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
88
102
  async function promptApiKey() {
@@ -171,12 +185,12 @@ async function main() {
171
185
  }
172
186
  const storedModel = await getStoredModel();
173
187
  let defaultModel = "gemini-2.5-flash";
174
- if (storedModel && VALID_MODELS.includes(storedModel)) {
188
+ if (storedModel && isValidModel(storedModel)) {
175
189
  defaultModel = storedModel;
176
190
  }
177
- if (tempModel && !VALID_MODELS.includes(tempModel)) {
191
+ if (tempModel && !isValidModel(tempModel)) {
178
192
  console.log(chalk.hex('#ff453a')('โœ• error'));
179
- console.log(chalk.dim(` Invalid model: ${tempModel}. Choose one of: ${VALID_MODELS.join(", ")}`));
193
+ console.log(chalk.dim(` Invalid model: ${tempModel}. Choose one of: ${VALID_MODELS.join(", ")} or prefix with groq/, mistral/, openrouter/, nvidia/, github/`));
180
194
  process.exit(1);
181
195
  }
182
196
  const modelToUse = tempModel || defaultModel;
@@ -322,7 +336,7 @@ async function main() {
322
336
  console.log();
323
337
  console.log(chalk.dim(" Available Commands:"));
324
338
  console.log(` ${chalk.hex('#0a84ff')('/model')} ${chalk.gray('[name]')} ${chalk.dim('โ€” View active model or switch to [name]')}`);
325
- console.log(` ${chalk.hex('#0a84ff')('/key')} ${chalk.gray('[api_key]')} ${chalk.dim('โ€” Set/save your Gemini API Key globally')}`);
339
+ console.log(` ${chalk.hex('#0a84ff')('/key')} ${chalk.gray('[provider] [api_key]')} ${chalk.dim('โ€” Set/save API keys globally')}`);
326
340
  console.log(` ${chalk.hex('#0a84ff')('/clear')} ${chalk.dim('โ€” Wipe conversation memory')}`);
327
341
  console.log(` ${chalk.hex('#0a84ff')('/status')} ${chalk.dim('โ€” Show active model and memory usage')}`);
328
342
  console.log(` ${chalk.hex('#0a84ff')('/help')} ${chalk.dim('โ€” Show help screen')}`);
@@ -378,17 +392,17 @@ async function main() {
378
392
  const parts = trimmed.split(/\s+/);
379
393
  if (parts.length === 1) {
380
394
  console.log(chalk.dim(`model `) + chalk.gray(agent.getModel()));
381
- console.log(chalk.gray(`options `) + chalk.gray(VALID_MODELS.join(" ยท ")));
395
+ console.log(chalk.gray(`options `) + chalk.gray(VALID_MODELS.join(" ยท ") + " ยท groq/... ยท mistral/... ยท openrouter/... ยท nvidia/... ยท github/..."));
382
396
  }
383
397
  else {
384
398
  const newModel = parts[1];
385
- if (VALID_MODELS.includes(newModel)) {
399
+ if (isValidModel(newModel)) {
386
400
  agent.setModel(newModel);
387
401
  console.log(chalk.hex('#30d158')('โœ“') + ' ' + chalk.gray(`Switched model to: ${newModel}`));
388
402
  }
389
403
  else {
390
404
  console.log(chalk.hex('#ff453a')('โœ• error'));
391
- console.log(chalk.dim(` Model must be one of: ${VALID_MODELS.join(" ยท ")}`));
405
+ console.log(chalk.dim(` Model must be one of: ${VALID_MODELS.join(" ยท ")} or prefixed with groq/, mistral/, openrouter/, nvidia/, github/`));
392
406
  }
393
407
  }
394
408
  if (!isRlClosed) {
@@ -403,26 +417,47 @@ async function main() {
403
417
  if (trimmed.startsWith("/key")) {
404
418
  const parts = trimmed.split(/\s+/);
405
419
  if (parts.length === 1) {
406
- const keyToCheck = agent.getApiKey();
407
- if (keyToCheck) {
408
- const masked = keyToCheck.slice(0, 7) + "..." + keyToCheck.slice(-4);
409
- console.log(chalk.dim(` Gemini API Key is set: `) + chalk.gray(masked));
410
- }
411
- else {
412
- console.log(chalk.hex('#ff453a')('โœ• error') + chalk.dim(' โ€” Gemini API Key is missing. Set it using: /key <your_api_key>'));
420
+ const providers = ["gemini", "groq", "mistral", "openrouter", "nvidia", "github"];
421
+ console.log(chalk.white.bold("\n Stored API Keys:"));
422
+ for (const p of providers) {
423
+ const keyToCheck = await getProviderApiKey(p);
424
+ if (keyToCheck) {
425
+ const masked = keyToCheck.slice(0, 7) + "..." + keyToCheck.slice(-4);
426
+ console.log(` ${chalk.dim(p.toUpperCase().padEnd(10))} : ${chalk.gray(masked)}`);
427
+ }
428
+ else {
429
+ console.log(` ${chalk.dim(p.toUpperCase().padEnd(10))} : ${chalk.hex('#ff453a')('missing')}`);
430
+ }
413
431
  }
432
+ console.log(chalk.dim("\n To set a key, use: /key <provider> <api_key> (e.g. /key groq gsk_...)"));
433
+ console.log(chalk.dim(" Or set it in environment (e.g. GEMINI_API_KEY, GROQ_API_KEY, etc.)\n"));
414
434
  }
415
- else {
435
+ else if (parts.length === 2) {
416
436
  const newKey = parts[1].trim();
417
437
  if (!newKey) {
418
438
  console.log(chalk.hex('#ff453a')('โœ• error') + chalk.dim(' โ€” API Key cannot be empty.'));
419
439
  }
420
440
  else {
421
- await saveApiKey(newKey);
441
+ await saveApiKey(newKey, "gemini");
422
442
  agent.setApiKey(newKey);
423
443
  console.log(chalk.hex('#30d158')('โœ“') + ' ' + chalk.gray('Gemini API Key saved and set successfully.'));
424
444
  }
425
445
  }
446
+ else {
447
+ const provider = parts[1].toLowerCase();
448
+ const newKey = parts[2].trim();
449
+ const validProviders = ["gemini", "groq", "mistral", "openrouter", "nvidia", "github"];
450
+ if (!validProviders.includes(provider)) {
451
+ console.log(chalk.hex('#ff453a')('โœ• error') + chalk.dim(` โ€” Invalid provider. Choose one of: ${validProviders.join(", ")}`));
452
+ }
453
+ else {
454
+ await saveApiKey(newKey, provider);
455
+ if (provider === "gemini") {
456
+ agent.setApiKey(newKey);
457
+ }
458
+ console.log(chalk.hex('#30d158')('โœ“') + ' ' + chalk.gray(`${provider.toUpperCase()} API Key saved and set successfully.`));
459
+ }
460
+ }
426
461
  if (!isRlClosed) {
427
462
  try {
428
463
  rl.resume();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-agent",
3
- "version": "2.9.12",
3
+ "version": "2.9.14",
4
4
  "description": "CLI coding agent powered by Google Gemini",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",