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 +43 -15
- package/dist/agent.js +140 -17
- package/dist/config.js +48 -9
- package/dist/index.js +52 -17
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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.
|
|
45
|
-
You can
|
|
47
|
+
### 2. Environment Variables
|
|
48
|
+
You can export provider API keys as environment variables:
|
|
46
49
|
|
|
47
50
|
```bash
|
|
48
|
-
|
|
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.
|
|
52
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
return config.geminiApiKey;
|
|
117
|
+
return getStoredApiKey("gemini");
|
|
77
118
|
}
|
|
78
119
|
export async function saveGeminiApiKey(geminiApiKey) {
|
|
79
|
-
|
|
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 &&
|
|
188
|
+
if (storedModel && isValidModel(storedModel)) {
|
|
175
189
|
defaultModel = storedModel;
|
|
176
190
|
}
|
|
177
|
-
if (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
|
|
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 (
|
|
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
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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();
|