omnexx 0.1.0 → 0.5.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/README.md +85 -22
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-CLAS22GZ.js +344 -0
- package/dist/chunk-CLAS22GZ.js.map +1 -0
- package/dist/chunk-CVDLLOMJ.js +98 -0
- package/dist/chunk-CVDLLOMJ.js.map +1 -0
- package/dist/chunk-IJPR52MO.js +180 -0
- package/dist/chunk-IJPR52MO.js.map +1 -0
- package/dist/chunk-OXDXHUTX.js +5605 -0
- package/dist/chunk-OXDXHUTX.js.map +1 -0
- package/dist/chunk-TZ3KO3PA.js +8360 -0
- package/dist/chunk-TZ3KO3PA.js.map +1 -0
- package/dist/chunk-Y74QSMFT.js +1637 -0
- package/dist/chunk-Y74QSMFT.js.map +1 -0
- package/dist/chunk-YMIRAOIB.js +710 -0
- package/dist/chunk-YMIRAOIB.js.map +1 -0
- package/dist/cli.js +2302 -6619
- package/dist/cli.js.map +1 -1
- package/dist/ollama-VFXDRFQ4.js +65 -0
- package/dist/ollama-VFXDRFQ4.js.map +1 -0
- package/dist/print-KUDY6NOG.js +55 -0
- package/dist/print-KUDY6NOG.js.map +1 -0
- package/dist/server-HSTNODMT.js +295 -0
- package/dist/server-HSTNODMT.js.map +1 -0
- package/dist/start-Y5B2FUMR.js +2342 -0
- package/dist/start-Y5B2FUMR.js.map +1 -0
- package/package.json +17 -1
package/README.md
CHANGED
|
@@ -4,7 +4,53 @@
|
|
|
4
4
|
|
|
5
5
|
Omnexx is a personal coding agent harness: one agent works on one huge task in one repo for about 24 hours with no one steering it. Your tests, typecheck and lint decide what's kept; every kept step is a small git commit on its own branch, and you get a report in the morning.
|
|
6
6
|
|
|
7
|
-
> **Alpha.**
|
|
7
|
+
> **Alpha.** A personal tool, not a product yet. It spends your own API credits.
|
|
8
|
+
|
|
9
|
+
## Quickstart
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g omnexx
|
|
13
|
+
mkdir monkey-site && cd monkey-site # or cd into any repo with tests
|
|
14
|
+
omnexx # paste an API key, then type a goal
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
That's it: you're chatting with an agent that works in your checkout and shows every step as it
|
|
18
|
+
goes. Type while it works to steer it; Esc stops it. For work that takes hours or days, `/run
|
|
19
|
+
<goal>` starts a long unattended run on its own branch: it plans, checks every step against your
|
|
20
|
+
tests, typecheck and lint, and keeps going. Close the terminal any time; `omnexx` re-attaches.
|
|
21
|
+
|
|
22
|
+
### Why omnexx over Claude Code, Codex or OpenCode?
|
|
23
|
+
|
|
24
|
+
| They | omnexx |
|
|
25
|
+
| ---------------------------- | ---------------------------------------------------------------- |
|
|
26
|
+
| you drive, turn by turn | one prompt, then it drives for hours or days |
|
|
27
|
+
| stops when the context fills | fresh context every cycle, so it never runs out |
|
|
28
|
+
| "done" = the model says so | done = your tests, types, lint and a real browser check all pass |
|
|
29
|
+
| stops at the literal ask | infers the intended build, ships it, then hardens and polishes |
|
|
30
|
+
| one model per session | a pool of models, each used until its quota runs out |
|
|
31
|
+
| "done" when the model stops | an independent review per change and an audit before finishing |
|
|
32
|
+
|
|
33
|
+
### Providers in one step
|
|
34
|
+
|
|
35
|
+
At the `omnexx` prompt:
|
|
36
|
+
|
|
37
|
+
- **Paste an API key**, on its own or in a sentence ("here's my mistral key …"). 25 hosted
|
|
38
|
+
providers are known by name; the key is saved to a 0600 file, never shown and never sent to a
|
|
39
|
+
model. `omnexx providers list` shows them all.
|
|
40
|
+
- **`/connect env`**: use the keys already in your environment (only the variable name is saved).
|
|
41
|
+
- **`/models`**: tick the models to use and how: top-first, random, or smart (Nimble picks).
|
|
42
|
+
Each runs until its quota is out, then the next takes over.
|
|
43
|
+
- **`/connect ollama`** (or `lmstudio`, `vllm`, `litellm`): a local model, no key.
|
|
44
|
+
- **`/connect https://host/v1 KEY`**: any OpenAI-compatible endpoint.
|
|
45
|
+
- **`/chat`**: talk to a connected model directly and ask it to set up more ("add my groq key").
|
|
46
|
+
Pasted keys only ever reach it as placeholders.
|
|
47
|
+
|
|
48
|
+
### Websites get checked in a real browser
|
|
49
|
+
|
|
50
|
+
Give a check `kind = "browser"` and omnexx starts your app on a free `$PORT`, opens it with
|
|
51
|
+
[agent-browser](https://github.com/vercel-labs/agent-browser) or playwright-core, and rejects any
|
|
52
|
+
change that leaves the page blank or throws a JavaScript error. New projects get this check
|
|
53
|
+
automatically once they have an `npm start` script.
|
|
8
54
|
|
|
9
55
|
## What it does
|
|
10
56
|
|
|
@@ -13,15 +59,28 @@ Omnexx is the loop around the model:
|
|
|
13
59
|
- **Fresh context every cycle.** Each cycle starts a new conversation built only from compact files on disk: the goal, a compact view of the plan, the last few progress entries, a capped lessons file and a codebase map. No transcript carries over.
|
|
14
60
|
- **Hierarchical, rolling-wave plan.** The planner writes milestones, then expands only the next one or two into small tasks. Nodes are never deleted, only parked with a reason.
|
|
15
61
|
- **Your commands are the judge.** Gates (tests, typecheck, lint, your own commands) run against a recorded baseline with a ratchet: no new failures, and the known-failure count can't go up. Anti-cheat rejects deleted tests, new `.skip`/`.only`/`@ts-ignore`/`eslint-disable`, snapshot rewrites and edits to protected files. A failed cycle is rolled back to the last green commit.
|
|
16
|
-
- **Stuck detection.** Repeated rejections, repeated failure signatures and A→B→A oscillation move a task up a strategy ladder (retry with evidence, then park). When nothing runnable is left, the run stops as `needs-human`.
|
|
17
|
-
- **Budgets.** USD, hours and cycle caps for the run, turn and token caps per cycle, and a pre-flight check before every model call so spend never overshoots by more than one turn. In the last 8% of the budget no new task starts.
|
|
62
|
+
- **Stuck detection.** Repeated rejections, repeated failure signatures and A→B→A oscillation move a task up a strategy ladder (retry with evidence, then the strong model, then a planner split into smaller tasks, then park). When nothing runnable is left, the run stops as `needs-human`.
|
|
63
|
+
- **Budgets.** USD, hours and cycle caps for the run, a rolling daily cap that pauses and resumes, turn and token caps per cycle, and a pre-flight check before every model call so spend never overshoots by more than one turn. In the last 8% of the budget no new task starts.
|
|
18
64
|
- **Crash-safe.** Atomic state, a phase machine, a lock with boot-id staleness, `--detach`, and a systemd user unit or launchd agent that resumes runs after a reboot.
|
|
19
65
|
- **Checkpoints and a morning-after report.** Each finished milestone is tagged; `omnexx report` writes `REPORT.md` with the outcome, the plan tree, what changed, test deltas, where it struggled, spend, decisions it needs from you, and how to merge.
|
|
66
|
+
- **Optional docker sandbox.** `sandbox = "docker"` runs the agent's commands and your gates in a locked-down container with only the worktree mounted.
|
|
67
|
+
- **Any provider, with failover.** Anthropic, Gemini, the OpenAI Responses API and any OpenAI-compatible endpoint. Each role can list a chain of models; a failing or capped provider hands the call to the next. Per-provider spend caps.
|
|
68
|
+
- **Helpers and memory.** The agent can hand read-only questions to helper agents that run in parallel with their own context, and `recall` searches the whole run's history, not just the last few cycles.
|
|
20
69
|
- **Your checkout is never touched.** Work happens in a git worktree on `omnexx/<runId>`; nothing is pushed unless you opt in.
|
|
21
70
|
|
|
22
71
|
Optional: an advisory **fast judge** (Nimble on Ollama, usually on your Mac over Tailscale) that suggests the next move and flags drift. It's off by default and can never override the gates. See [docs/judge.md](docs/judge.md).
|
|
23
72
|
|
|
24
|
-
##
|
|
73
|
+
## Free and local with Ollama
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
ollama pull qwen2.5-coder # or any model you like
|
|
77
|
+
npx omnexx --ollama # finds Ollama on localhost:11434, picks a coding model, opens the chat
|
|
78
|
+
npx omnexx --ollama llama3.3 # or name the model
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
No key and no config: with no provider set up, omnexx also connects a running Ollama by itself.
|
|
82
|
+
|
|
83
|
+
## From the plain terminal
|
|
25
84
|
|
|
26
85
|
```bash
|
|
27
86
|
npm i -g omnexx
|
|
@@ -32,6 +91,8 @@ omnexx init # detects package manager and gates, asks before w
|
|
|
32
91
|
export ANTHROPIC_API_KEY=... # or: omnexx auth set anthropic
|
|
33
92
|
omnexx run --plan-only "Port src/legacy to strict TypeScript"
|
|
34
93
|
omnexx run --detach --budget 5 --hours 2 "Port src/legacy to strict TypeScript"
|
|
94
|
+
omnexx run --detach --for 8h "improve this repo" # autonomous: keeps going until the time is up
|
|
95
|
+
omnexx connect notion # connect apps: Gmail, Slack, Linear, GitHub… (docs/connectors.md)
|
|
35
96
|
omnexx status && omnexx logs -f
|
|
36
97
|
omnexx report # in the morning
|
|
37
98
|
```
|
|
@@ -40,19 +101,19 @@ Requires Node 22+, git and (recommended) ripgrep.
|
|
|
40
101
|
|
|
41
102
|
## Commands
|
|
42
103
|
|
|
43
|
-
| Command |
|
|
44
|
-
| ----------------------------------------------------------------------- |
|
|
45
|
-
| `omnexx init [--yes]` | Detect gates, write `omnexx.toml`
|
|
46
|
-
| `omnexx run "<goal>"` | Plan, then cycle. `--goal-file`, `--detach`, `--budget`, `--hours`, `--gate`, `--model-worker`, `--push branch`, `--from`, `--plan-only`, `--i-know-there-are-no-checks` |
|
|
47
|
-
| `omnexx status [runId] [--json]` | Phase, task, progress, spend, heartbeat
|
|
48
|
-
| `omnexx logs [runId] [-f] [--events\|--progress\|--cmd <id>]` | Human view of the event log
|
|
49
|
-
| `omnexx plan [runId] [--edit]` | Show the plan, or edit `goal.md` (picked up next cycle)
|
|
50
|
-
| `omnexx pause\|resume\|stop [runId] [--now]` | Control a run; `resume` also restarts a crashed supervisor
|
|
51
|
-
| `omnexx resume --all` | What the service runs at boot
|
|
52
|
-
| `omnexx runs` · `diff [--since <milestone>]` · `checkpoints` · `report` | Inspect results
|
|
53
|
-
| `omnexx service install\|uninstall\|status [--dry-run]` | systemd user unit (Linux) or launchd agent (macOS)
|
|
54
|
-
| `omnexx doctor [--offline] [--json]` | Environment checks; never prints your key
|
|
55
|
-
| `omnexx auth set\|clear anthropic` | Store the key in a 0600 file
|
|
104
|
+
| Command | |
|
|
105
|
+
| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
106
|
+
| `omnexx init [--yes]` | Detect gates, write `omnexx.toml` |
|
|
107
|
+
| `omnexx run "<goal>"` | Plan, then cycle. `--goal-file`, `--detach`, `--budget`, `--hours`, `--for` (autonomous), `--gate`, `--model-worker`, `--push branch`, `--from`, `--plan-only`, `--i-know-there-are-no-checks` |
|
|
108
|
+
| `omnexx status [runId] [--json]` | Phase, task, progress, spend, heartbeat |
|
|
109
|
+
| `omnexx logs [runId] [-f] [--events\|--progress\|--cmd <id>]` | Human view of the event log |
|
|
110
|
+
| `omnexx plan [runId] [--edit]` | Show the plan, or edit `goal.md` (picked up next cycle) |
|
|
111
|
+
| `omnexx pause\|resume\|stop [runId] [--now]` | Control a run; `resume` also restarts a crashed supervisor |
|
|
112
|
+
| `omnexx resume --all` | What the service runs at boot |
|
|
113
|
+
| `omnexx runs` · `diff [--since <milestone>]` · `checkpoints` · `report` | Inspect results |
|
|
114
|
+
| `omnexx service install\|uninstall\|status [--dry-run]` | systemd user unit (Linux) or launchd agent (macOS) |
|
|
115
|
+
| `omnexx doctor [--offline] [--json]` | Environment checks; never prints your key |
|
|
116
|
+
| `omnexx auth set\|clear anthropic` | Store the key in a 0600 file |
|
|
56
117
|
|
|
57
118
|
Exit codes: `0` finished, `2` needs a human, `3` budget stop, `4` stopped by you, `1` error.
|
|
58
119
|
|
|
@@ -60,15 +121,17 @@ Exit codes: `0` finished, `2` needs a human, `3` budget stop, `4` stopped by you
|
|
|
60
121
|
|
|
61
122
|
Planned for M3 and later, and not in this build:
|
|
62
123
|
|
|
63
|
-
-
|
|
64
|
-
- Model routing (planner/worker/cheap per turn), in-cycle compaction and tool-result clearing, flaky-test handling, the rolling daily spend cap (`max_usd_per_day` is accepted but not enforced), in-cycle stuck signals.
|
|
65
|
-
- Ladder rungs 2–4 (escalate model, re-plan a task, different approach). The judge's `split_task` and `switch_to_strong_model` options are logged but can't be acted on yet.
|
|
124
|
+
- Ladder rung 4 (a planner-proposed different approach).
|
|
66
125
|
- Real worker adapters (Aider, OpenCode, Cline, Pi, Hermes, OpenHands, Claude Code). The interface, lifecycle and safety checks exist and are tested with a fake worker; enabling a worker fails with "adapter not available until M3". See [docs/workers.md](docs/workers.md).
|
|
67
|
-
-
|
|
126
|
+
- The benchmark harness. Token-saving claims for helpers and memory are not measured yet.
|
|
127
|
+
- Nothing has run longer than about 20 minutes in testing; set `--budget` and `--hours` for long runs.
|
|
128
|
+
|
|
129
|
+
Review costs: `[review]` runs on your worker models unless you set `review_models`; with a paid
|
|
130
|
+
worker each reviewed change is one extra call. See [docs/config.md](docs/config.md).
|
|
68
131
|
|
|
69
132
|
## Docs
|
|
70
133
|
|
|
71
|
-
[Architecture](docs/architecture.md) · [Config](docs/config.md) · [Safety](docs/safety.md) · [Judge](docs/judge.md) · [Workers](docs/workers.md) · [VPS quickstart](docs/deploy-vps.md) · [Decisions](docs/DECISIONS.md) · [Plan](docs/PLAN.md)
|
|
134
|
+
[Architecture](docs/architecture.md) · [Config](docs/config.md) · [Safety](docs/safety.md) · [Judge](docs/judge.md) · [Workers](docs/workers.md) · [VPS quickstart](docs/deploy-vps.md) · [Decisions](docs/DECISIONS.md) · [Plan](docs/PLAN.md) · [Comparison](docs/comparison.md)
|
|
72
135
|
|
|
73
136
|
## License
|
|
74
137
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
__export
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=chunk-2H7UOFLK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
EXIT,
|
|
4
|
+
UsageError,
|
|
5
|
+
findProviderKey,
|
|
6
|
+
loadConfig,
|
|
7
|
+
makeEndpointProvider,
|
|
8
|
+
parseDuration,
|
|
9
|
+
resolvePaths,
|
|
10
|
+
userConfigFile
|
|
11
|
+
} from "./chunk-Y74QSMFT.js";
|
|
12
|
+
|
|
13
|
+
// src/cli/io.ts
|
|
14
|
+
import { createInterface } from "readline";
|
|
15
|
+
function processIO() {
|
|
16
|
+
return {
|
|
17
|
+
stdout: process.stdout,
|
|
18
|
+
stderr: process.stderr,
|
|
19
|
+
stdin: process.stdin,
|
|
20
|
+
env: process.env,
|
|
21
|
+
cwd: process.cwd(),
|
|
22
|
+
isTTY: process.stdin.isTTY && process.stdout.isTTY
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function println(out, line = "") {
|
|
26
|
+
out.write(`${line}
|
|
27
|
+
`);
|
|
28
|
+
}
|
|
29
|
+
function readLine(io, prompt) {
|
|
30
|
+
io.stdout.write(prompt);
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
const rl = createInterface({ input: io.stdin, terminal: false });
|
|
33
|
+
let answered = false;
|
|
34
|
+
rl.once("line", (line) => {
|
|
35
|
+
answered = true;
|
|
36
|
+
rl.close();
|
|
37
|
+
resolve(line);
|
|
38
|
+
});
|
|
39
|
+
rl.once("close", () => {
|
|
40
|
+
if (!answered) resolve(void 0);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async function confirm(io, question) {
|
|
45
|
+
const answer = await readLine(io, `${question} [y/N] `);
|
|
46
|
+
return /^\s*y(es)?\s*$/i.test(answer ?? "");
|
|
47
|
+
}
|
|
48
|
+
async function readSecret(io, prompt) {
|
|
49
|
+
const stdin = io.stdin;
|
|
50
|
+
if (!io.isTTY || typeof stdin.setRawMode !== "function") return readLine(io, prompt);
|
|
51
|
+
io.stdout.write(prompt);
|
|
52
|
+
stdin.setRawMode(true);
|
|
53
|
+
stdin.resume();
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
let value = "";
|
|
56
|
+
const onData = (chunk) => {
|
|
57
|
+
for (const ch of chunk.toString("utf8")) {
|
|
58
|
+
if (ch === "\r" || ch === "\n" || ch === "") {
|
|
59
|
+
finish(value);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (ch === "") {
|
|
63
|
+
finish(void 0);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (ch === "\x7F") value = value.slice(0, -1);
|
|
67
|
+
else value += ch;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const finish = (v) => {
|
|
71
|
+
stdin.off("data", onData);
|
|
72
|
+
stdin.setRawMode(false);
|
|
73
|
+
stdin.pause();
|
|
74
|
+
io.stdout.write("\n");
|
|
75
|
+
resolve(v);
|
|
76
|
+
};
|
|
77
|
+
stdin.on("data", onData);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/cli/commands/providers.ts
|
|
82
|
+
import { appendFile, mkdir, readFile } from "fs/promises";
|
|
83
|
+
import { dirname } from "path";
|
|
84
|
+
import { stringify as stringifyToml } from "smol-toml";
|
|
85
|
+
var HOSTED = [
|
|
86
|
+
"openai | OpenAI | https://api.openai.com/v1 | OPENAI_API_KEY | sk- | https://platform.openai.com/api-keys",
|
|
87
|
+
"gemini | Google Gemini | https://generativelanguage.googleapis.com | GEMINI_API_KEY | AIza | https://aistudio.google.com/apikey",
|
|
88
|
+
"xai | xAI (Grok) | https://api.x.ai/v1 | XAI_API_KEY | xai- | https://console.x.ai",
|
|
89
|
+
"mistral | Mistral | https://api.mistral.ai/v1 | MISTRAL_API_KEY | - | https://console.mistral.ai/api-keys",
|
|
90
|
+
"deepseek | DeepSeek | https://api.deepseek.com/v1 | DEEPSEEK_API_KEY | - | https://platform.deepseek.com/api_keys",
|
|
91
|
+
"groq | Groq | https://api.groq.com/openai/v1 | GROQ_API_KEY | gsk_ | https://console.groq.com/keys",
|
|
92
|
+
"cerebras | Cerebras | https://api.cerebras.ai/v1 | CEREBRAS_API_KEY | csk- | https://cloud.cerebras.ai",
|
|
93
|
+
"openrouter | OpenRouter (hundreds of models) | https://openrouter.ai/api/v1 | OPENROUTER_API_KEY | sk-or- | https://openrouter.ai/keys",
|
|
94
|
+
"together | Together AI | https://api.together.xyz/v1 | TOGETHER_API_KEY | - | https://api.together.ai/settings/api-keys",
|
|
95
|
+
"fireworks | Fireworks | https://api.fireworks.ai/inference/v1 | FIREWORKS_API_KEY | fw_ | https://fireworks.ai/account/api-keys",
|
|
96
|
+
"nvidia | NVIDIA NIM | https://integrate.api.nvidia.com/v1 | NVIDIA_API_KEY | nvapi- | https://build.nvidia.com",
|
|
97
|
+
"moonshot | Moonshot (Kimi) | https://api.moonshot.ai/v1 | MOONSHOT_API_KEY | - | https://platform.moonshot.ai/console/api-keys",
|
|
98
|
+
"zai | Z.ai (GLM) | https://api.z.ai/api/paas/v4 | ZAI_API_KEY | - | https://z.ai/manage-apikey/apikey-list",
|
|
99
|
+
"dashscope | Alibaba Cloud (Qwen) | https://dashscope-intl.aliyuncs.com/compatible-mode/v1 | DASHSCOPE_API_KEY | - | https://modelstudio.console.alibabacloud.com",
|
|
100
|
+
"siliconflow | SiliconFlow | https://api.siliconflow.com/v1 | SILICONFLOW_API_KEY | - | https://cloud.siliconflow.com/account/ak",
|
|
101
|
+
"deepinfra | DeepInfra | https://api.deepinfra.com/v1/openai | DEEPINFRA_API_KEY | - | https://deepinfra.com/dash/api_keys",
|
|
102
|
+
"huggingface | Hugging Face Inference | https://router.huggingface.co/v1 | HF_TOKEN | hf_ | https://huggingface.co/settings/tokens",
|
|
103
|
+
"perplexity | Perplexity | https://api.perplexity.ai | PERPLEXITY_API_KEY | pplx- | https://www.perplexity.ai/settings/api",
|
|
104
|
+
"cohere | Cohere | https://api.cohere.ai/compatibility/v1 | COHERE_API_KEY | - | https://dashboard.cohere.com/api-keys",
|
|
105
|
+
"vercel | Vercel AI Gateway | https://ai-gateway.vercel.sh/v1 | AI_GATEWAY_API_KEY | - | https://vercel.com/ai-gateway",
|
|
106
|
+
"github | GitHub Models | https://models.github.ai/inference | GITHUB_TOKEN | - | https://github.com/settings/tokens",
|
|
107
|
+
"novita | Novita AI | https://api.novita.ai/v3/openai | NOVITA_API_KEY | - | https://novita.ai/settings/key-management",
|
|
108
|
+
"hyperbolic | Hyperbolic | https://api.hyperbolic.xyz/v1 | HYPERBOLIC_API_KEY | - | https://app.hyperbolic.xyz/settings",
|
|
109
|
+
"sambanova | SambaNova | https://api.sambanova.ai/v1 | SAMBANOVA_API_KEY | - | https://cloud.sambanova.ai/apis"
|
|
110
|
+
].map((line) => line.split(" | ").map((f) => f === "-" ? void 0 : f)).map(([name = "", label = "", baseUrl = "", keyEnv, keyPrefix, signup]) => [
|
|
111
|
+
name,
|
|
112
|
+
label,
|
|
113
|
+
baseUrl,
|
|
114
|
+
keyEnv,
|
|
115
|
+
keyPrefix,
|
|
116
|
+
signup
|
|
117
|
+
]);
|
|
118
|
+
var LOCAL = [
|
|
119
|
+
["ollama", "Ollama", "http://localhost:11434/v1"],
|
|
120
|
+
["lmstudio", "LM Studio", "http://localhost:1234/v1"],
|
|
121
|
+
["vllm", "vLLM", "http://localhost:8000/v1"],
|
|
122
|
+
["llamacpp", "llama.cpp server", "http://localhost:8080/v1"],
|
|
123
|
+
["litellm", "LiteLLM proxy", "http://localhost:4000/v1"],
|
|
124
|
+
["jan", "Jan", "http://localhost:1337/v1"]
|
|
125
|
+
];
|
|
126
|
+
var PROVIDER_TEMPLATES = Object.fromEntries([
|
|
127
|
+
...HOSTED.map(([name, label, baseUrl, keyEnv, keyPrefix, signup]) => [
|
|
128
|
+
name,
|
|
129
|
+
{
|
|
130
|
+
label,
|
|
131
|
+
baseUrl,
|
|
132
|
+
keyEnv,
|
|
133
|
+
free: false,
|
|
134
|
+
kind: name === "gemini" ? "gemini" : "openai",
|
|
135
|
+
...keyPrefix ? { keyPrefix } : {},
|
|
136
|
+
...signup ? { signup } : {}
|
|
137
|
+
}
|
|
138
|
+
]),
|
|
139
|
+
...LOCAL.map(([name, label, baseUrl]) => [
|
|
140
|
+
name,
|
|
141
|
+
{ label, baseUrl, keyEnv: void 0, free: true, kind: "openai", local: true }
|
|
142
|
+
]),
|
|
143
|
+
[
|
|
144
|
+
"custom",
|
|
145
|
+
{ baseUrl: "http://localhost:8000/v1", keyEnv: void 0, free: false, kind: "openai" }
|
|
146
|
+
]
|
|
147
|
+
]);
|
|
148
|
+
function templateFor(name) {
|
|
149
|
+
return PROVIDER_TEMPLATES[name.toLowerCase()];
|
|
150
|
+
}
|
|
151
|
+
async function appendEndpointBlock(file, name, t) {
|
|
152
|
+
const block = stringifyToml({
|
|
153
|
+
providers: {
|
|
154
|
+
endpoints: {
|
|
155
|
+
[name]: {
|
|
156
|
+
kind: t.kind,
|
|
157
|
+
base_url: t.baseUrl,
|
|
158
|
+
...t.keyEnv ? { api_key_env: t.keyEnv } : {},
|
|
159
|
+
...t.free ? { free: true } : {}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
await mkdir(dirname(file), { recursive: true });
|
|
165
|
+
await appendFile(file, `
|
|
166
|
+
${block}`);
|
|
167
|
+
}
|
|
168
|
+
async function removeEndpointBlock(file, name) {
|
|
169
|
+
let text;
|
|
170
|
+
try {
|
|
171
|
+
text = await readFile(file, "utf8");
|
|
172
|
+
} catch (err) {
|
|
173
|
+
if (err.code === "ENOENT") return false;
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
const lines = text.split("\n");
|
|
177
|
+
const start = lines.findIndex((l) => l.trim() === `[providers.endpoints.${name}]`);
|
|
178
|
+
if (start === -1) return false;
|
|
179
|
+
let end = lines.length;
|
|
180
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
181
|
+
if (/^\s*\[.+\]\s*$/.test(lines[i] ?? "")) {
|
|
182
|
+
end = i;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
await mkdir(dirname(file), { recursive: true });
|
|
187
|
+
const { writeFile } = await import("fs/promises");
|
|
188
|
+
await writeFile(file, lines.slice(0, start).concat(lines.slice(end)).join("\n"));
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
async function testEndpoint(io, name, baseUrl, apiKey, kind) {
|
|
192
|
+
const { config } = await loadConfig({ cwd: io.cwd, env: io.env });
|
|
193
|
+
const ep = config.providers.endpoints[name];
|
|
194
|
+
const timeoutMs = ep ? parseDuration(ep.request_timeout) : 1e4;
|
|
195
|
+
const opts = { name, baseUrl, apiKey, timeoutMs, ...io.fetch ? { fetch: io.fetch } : {} };
|
|
196
|
+
const provider = makeEndpointProvider(kind, opts);
|
|
197
|
+
try {
|
|
198
|
+
const res = await provider.complete({
|
|
199
|
+
model: "test",
|
|
200
|
+
system: [{ text: "Reply with the single word: ok" }],
|
|
201
|
+
tools: [],
|
|
202
|
+
messages: [{ role: "user", content: [{ type: "text", text: "ok" }] }],
|
|
203
|
+
maxTokens: 1,
|
|
204
|
+
messageBreakpoints: []
|
|
205
|
+
});
|
|
206
|
+
const text = res.content.filter((b) => b.type === "text").map((b) => b.text).join("").slice(0, 80);
|
|
207
|
+
return { ok: true, detail: text ? `answered (${text})` : "answered" };
|
|
208
|
+
} catch (err) {
|
|
209
|
+
return { ok: false, detail: err instanceof Error ? err.message : String(err) };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async function providersAdd(io, name, opts) {
|
|
213
|
+
const lower = name.toLowerCase();
|
|
214
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(lower))
|
|
215
|
+
throw new UsageError(`bad provider name "${name}"`, "use lowercase letters, digits, _ or -");
|
|
216
|
+
const { config } = await loadConfig({ cwd: io.cwd, env: io.env });
|
|
217
|
+
if (config.providers.endpoints[lower])
|
|
218
|
+
throw new UsageError(
|
|
219
|
+
`provider "${lower}" already exists`,
|
|
220
|
+
`use \`omnexx providers remove ${lower}\` first`
|
|
221
|
+
);
|
|
222
|
+
const template = templateFor(lower);
|
|
223
|
+
let baseUrl = opts.baseUrl;
|
|
224
|
+
let keyEnv = opts.keyEnv;
|
|
225
|
+
let free = opts.free;
|
|
226
|
+
const kind = opts.kind ?? template?.kind ?? "openai";
|
|
227
|
+
if (baseUrl === void 0 || keyEnv === void 0) {
|
|
228
|
+
if (io.isTTY && process.env.VITEST === void 0) {
|
|
229
|
+
println(io.stdout, `Adding provider "${lower}"${template ? ` (template: ${lower})` : ""}.`);
|
|
230
|
+
const askBase = await readLine(
|
|
231
|
+
io,
|
|
232
|
+
`Base URL [${template?.baseUrl ?? "http://localhost:8000/v1"}]: `
|
|
233
|
+
);
|
|
234
|
+
baseUrl = baseUrl ?? askBase?.trim() ?? template?.baseUrl ?? "http://localhost:8000/v1";
|
|
235
|
+
const askKey = await readLine(io, `Key env var [${template?.keyEnv ?? "none"}]: `);
|
|
236
|
+
const trimmedKey = (keyEnv ?? askKey?.trim() ?? template?.keyEnv ?? "").trim();
|
|
237
|
+
keyEnv = trimmedKey ? trimmedKey : void 0;
|
|
238
|
+
if (free === void 0) {
|
|
239
|
+
const askFree = await readLine(io, "Free / no key needed? [y/N]: ");
|
|
240
|
+
const saidYes = /^\s*y(es)?\s*$/i.test(askFree ?? "");
|
|
241
|
+
const templateFree = keyEnv === void 0 && template?.free === true;
|
|
242
|
+
free = saidYes || templateFree;
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
baseUrl ??= template?.baseUrl;
|
|
246
|
+
keyEnv ??= template?.keyEnv;
|
|
247
|
+
free ??= template?.free ?? false;
|
|
248
|
+
if (!baseUrl)
|
|
249
|
+
throw new UsageError("missing --base-url", "pass --base-url or use a known template name");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
free = free ?? false;
|
|
253
|
+
const file = userConfigFile(resolvePaths(io.env));
|
|
254
|
+
await appendEndpointBlock(file, lower, { baseUrl, keyEnv, free, kind });
|
|
255
|
+
println(io.stdout, `Added [providers.endpoints.${lower}] to ${file}.`);
|
|
256
|
+
if (!opts.skipTest) {
|
|
257
|
+
const paths = resolvePaths(io.env);
|
|
258
|
+
const key = await findProviderKey(paths, io.env, lower, keyEnv);
|
|
259
|
+
const t = await testEndpoint(io, lower, baseUrl, key?.key, kind);
|
|
260
|
+
println(io.stdout, t.ok ? `Test call ok: ${t.detail}` : `Test call failed: ${t.detail}`);
|
|
261
|
+
if (!t.ok) return EXIT.error;
|
|
262
|
+
}
|
|
263
|
+
return EXIT.ok;
|
|
264
|
+
}
|
|
265
|
+
async function providersList(io) {
|
|
266
|
+
const { config } = await loadConfig({ cwd: io.cwd, env: io.env });
|
|
267
|
+
println(io.stdout, "anthropic (built-in)");
|
|
268
|
+
for (const [name, ep] of Object.entries(config.providers.endpoints)) {
|
|
269
|
+
println(io.stdout, `${name} ${ep.base_url}${ep.free ? " (free)" : ""}`);
|
|
270
|
+
}
|
|
271
|
+
return EXIT.ok;
|
|
272
|
+
}
|
|
273
|
+
async function providersRemove(io, name) {
|
|
274
|
+
const lower = name.toLowerCase();
|
|
275
|
+
if (lower === "anthropic") throw new UsageError("cannot remove anthropic", "it is built in");
|
|
276
|
+
const file = userConfigFile(resolvePaths(io.env));
|
|
277
|
+
const removed = await removeEndpointBlock(file, lower);
|
|
278
|
+
println(
|
|
279
|
+
io.stdout,
|
|
280
|
+
removed ? `Removed [providers.endpoints.${lower}] from ${file}.` : `No [providers.endpoints.${lower}] in ${file}.`
|
|
281
|
+
);
|
|
282
|
+
return EXIT.ok;
|
|
283
|
+
}
|
|
284
|
+
async function providersTest(io, name) {
|
|
285
|
+
const lower = name.toLowerCase();
|
|
286
|
+
if (lower === "anthropic")
|
|
287
|
+
throw new UsageError("anthropic has no test yet", "run `omnexx doctor` to check the key");
|
|
288
|
+
const { config } = await loadConfig({ cwd: io.cwd, env: io.env });
|
|
289
|
+
const ep = config.providers.endpoints[lower];
|
|
290
|
+
if (!ep)
|
|
291
|
+
throw new UsageError(
|
|
292
|
+
`unknown provider "${lower}"`,
|
|
293
|
+
"`omnexx providers list` shows configured providers"
|
|
294
|
+
);
|
|
295
|
+
const key = await findProviderKey(resolvePaths(io.env), io.env, lower, ep.api_key_env);
|
|
296
|
+
const t = await testEndpoint(
|
|
297
|
+
io,
|
|
298
|
+
lower,
|
|
299
|
+
ep.base_url,
|
|
300
|
+
key?.key,
|
|
301
|
+
ep.kind ?? "openai"
|
|
302
|
+
);
|
|
303
|
+
println(io.stdout, t.ok ? `ok: ${t.detail}` : `failed: ${t.detail}`);
|
|
304
|
+
return t.ok ? EXIT.ok : EXIT.error;
|
|
305
|
+
}
|
|
306
|
+
var register = (program, io, setExit) => {
|
|
307
|
+
const p = program.command("providers").description("add, list, remove and test model providers");
|
|
308
|
+
p.command("add [name]").description("add a provider (interactive, or flags)").option("--base-url <url>", "endpoint base URL").option("--key-env <var>", "env var holding the key").option("--free", "no key needed (local models)").option("--kind <kind>", "openai|responses").option("--no-test", "skip the 1-token test call").action(
|
|
309
|
+
async (name, opts) => {
|
|
310
|
+
const n = name ?? "custom";
|
|
311
|
+
const kind = opts.kind === "responses" ? "responses" : "openai";
|
|
312
|
+
setExit(
|
|
313
|
+
await providersAdd(io, n, {
|
|
314
|
+
baseUrl: opts.baseUrl,
|
|
315
|
+
keyEnv: opts.keyEnv,
|
|
316
|
+
free: opts.free,
|
|
317
|
+
kind,
|
|
318
|
+
skipTest: opts.test === false
|
|
319
|
+
})
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
);
|
|
323
|
+
p.command("list").description("list configured providers").action(async () => {
|
|
324
|
+
setExit(await providersList(io));
|
|
325
|
+
});
|
|
326
|
+
p.command("remove <name>").description("remove a provider from user config").action(async (name) => {
|
|
327
|
+
setExit(await providersRemove(io, name));
|
|
328
|
+
});
|
|
329
|
+
p.command("test <name>").description("run a 1-token test call").action(async (name) => {
|
|
330
|
+
setExit(await providersTest(io, name));
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
export {
|
|
335
|
+
processIO,
|
|
336
|
+
println,
|
|
337
|
+
readLine,
|
|
338
|
+
confirm,
|
|
339
|
+
readSecret,
|
|
340
|
+
PROVIDER_TEMPLATES,
|
|
341
|
+
appendEndpointBlock,
|
|
342
|
+
register
|
|
343
|
+
};
|
|
344
|
+
//# sourceMappingURL=chunk-CLAS22GZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/io.ts","../src/cli/commands/providers.ts"],"sourcesContent":["import { createInterface } from 'node:readline';\nimport type { Readable, Writable } from 'node:stream';\nimport type { Clock } from '../core/clock.js';\nimport type { RunHooks } from '../core/run.js';\nimport type { SuperviseOptions } from '../core/supervisor.js';\nimport type { Provider } from '../providers/types.js';\n\n/** Everything a command touches in the outside world, injectable so tests drive it directly. */\nexport interface CliIO {\n stdout: Writable;\n stderr: Writable;\n stdin: Readable;\n env: NodeJS.ProcessEnv;\n cwd: string;\n isTTY: boolean;\n /** Builds the model provider from the API key. Tests inject a scripted provider here. */\n makeProvider?: (apiKey: string) => Provider;\n clock?: Clock;\n fetch?: typeof fetch;\n /** Opens a sign-in page; the default browser when unset (tests substitute a fake). */\n openUrl?: (url: string) => void;\n /** argv prefix used to start supervisors (`node dist/cli.js`); tests substitute a scripted entry. */\n entry?: string[];\n /** Phase hooks for supervisors started by this CLI (chaos tests kill at a phase). */\n hooks?: RunHooks;\n supervise?: SuperviseOptions;\n}\n\nexport function processIO(): CliIO {\n return {\n stdout: process.stdout,\n stderr: process.stderr,\n stdin: process.stdin,\n env: process.env,\n cwd: process.cwd(),\n isTTY: process.stdin.isTTY && process.stdout.isTTY,\n };\n}\n\nexport function println(out: Writable, line = ''): void {\n out.write(`${line}\\n`);\n}\n\n/** Read one line from stdin. Resolves `undefined` on EOF. */\nexport function readLine(io: CliIO, prompt: string): Promise<string | undefined> {\n io.stdout.write(prompt);\n return new Promise((resolve) => {\n const rl = createInterface({ input: io.stdin, terminal: false });\n let answered = false;\n rl.once('line', (line) => {\n answered = true;\n rl.close();\n resolve(line);\n });\n rl.once('close', () => {\n if (!answered) resolve(undefined);\n });\n });\n}\n\nexport async function confirm(io: CliIO, question: string): Promise<boolean> {\n const answer = await readLine(io, `${question} [y/N] `);\n return /^\\s*y(es)?\\s*$/i.test(answer ?? '');\n}\n\n/** Read a secret without echoing it when stdin is a TTY; plain line read when piped. */\nexport async function readSecret(io: CliIO, prompt: string): Promise<string | undefined> {\n const stdin = io.stdin as NodeJS.ReadStream;\n if (!io.isTTY || typeof stdin.setRawMode !== 'function') return readLine(io, prompt);\n io.stdout.write(prompt);\n stdin.setRawMode(true);\n stdin.resume();\n return new Promise((resolve) => {\n let value = '';\n const onData = (chunk: Buffer): void => {\n for (const ch of chunk.toString('utf8')) {\n if (ch === '\\r' || ch === '\\n' || ch === '\\u0004') {\n finish(value);\n return;\n }\n if (ch === '\\u0003') {\n finish(undefined);\n return;\n }\n if (ch === '\\u007f') value = value.slice(0, -1);\n else value += ch;\n }\n };\n const finish = (v: string | undefined): void => {\n stdin.off('data', onData);\n stdin.setRawMode(false);\n stdin.pause();\n io.stdout.write('\\n');\n resolve(v);\n };\n stdin.on('data', onData);\n });\n}\n","import { appendFile, mkdir, readFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport type { Command } from 'commander';\nimport { stringify as stringifyToml } from 'smol-toml';\nimport { findProviderKey } from '../../auth/keys.js';\nimport { loadConfig } from '../../config/load.js';\nimport { parseDuration } from '../../config/duration.js';\nimport { resolvePaths, userConfigFile } from '../../core/paths.js';\nimport { UsageError } from '../../errors.js';\nimport { makeEndpointProvider } from '../../providers/make.js';\nimport type { CommandRegistrar } from './extra/types.js';\nimport { println, readLine, type CliIO } from '../io.js';\nimport { EXIT } from '../exit-codes.js';\n\n/** Preset templates: suggestions only, written into user config on `add`. */\nexport interface ProviderTemplate {\n baseUrl: string;\n keyEnv: string | undefined;\n free: boolean;\n kind: 'openai' | 'responses' | 'gemini';\n /** Display name. */\n label?: string;\n /** How its API keys start, so a pasted key is recognised. */\n keyPrefix?: string;\n /** Where to get a key. */\n signup?: string;\n /** Local server: no key, started by the person. */\n local?: boolean;\n}\n\ntype Row = [\n name: string,\n label: string,\n baseUrl: string,\n keyEnv: string | undefined,\n keyPrefix: string | undefined,\n signup: string | undefined,\n];\n\n/**\n * Every provider omnexx knows by name (checked 2026-10). All speak the OpenAI Chat Completions\n * API except Gemini (native). Anthropic is built in and not listed here. Any other\n * OpenAI-compatible service works by URL: `/connect https://host/v1 KEY`.\n * One per line: name | label | base URL | key env var | key prefix | signup ('-' = none).\n */\nconst HOSTED: Row[] = [\n 'openai | OpenAI | https://api.openai.com/v1 | OPENAI_API_KEY | sk- | https://platform.openai.com/api-keys',\n 'gemini | Google Gemini | https://generativelanguage.googleapis.com | GEMINI_API_KEY | AIza | https://aistudio.google.com/apikey',\n 'xai | xAI (Grok) | https://api.x.ai/v1 | XAI_API_KEY | xai- | https://console.x.ai',\n 'mistral | Mistral | https://api.mistral.ai/v1 | MISTRAL_API_KEY | - | https://console.mistral.ai/api-keys',\n 'deepseek | DeepSeek | https://api.deepseek.com/v1 | DEEPSEEK_API_KEY | - | https://platform.deepseek.com/api_keys',\n 'groq | Groq | https://api.groq.com/openai/v1 | GROQ_API_KEY | gsk_ | https://console.groq.com/keys',\n 'cerebras | Cerebras | https://api.cerebras.ai/v1 | CEREBRAS_API_KEY | csk- | https://cloud.cerebras.ai',\n 'openrouter | OpenRouter (hundreds of models) | https://openrouter.ai/api/v1 | OPENROUTER_API_KEY | sk-or- | https://openrouter.ai/keys',\n 'together | Together AI | https://api.together.xyz/v1 | TOGETHER_API_KEY | - | https://api.together.ai/settings/api-keys',\n 'fireworks | Fireworks | https://api.fireworks.ai/inference/v1 | FIREWORKS_API_KEY | fw_ | https://fireworks.ai/account/api-keys',\n 'nvidia | NVIDIA NIM | https://integrate.api.nvidia.com/v1 | NVIDIA_API_KEY | nvapi- | https://build.nvidia.com',\n 'moonshot | Moonshot (Kimi) | https://api.moonshot.ai/v1 | MOONSHOT_API_KEY | - | https://platform.moonshot.ai/console/api-keys',\n 'zai | Z.ai (GLM) | https://api.z.ai/api/paas/v4 | ZAI_API_KEY | - | https://z.ai/manage-apikey/apikey-list',\n 'dashscope | Alibaba Cloud (Qwen) | https://dashscope-intl.aliyuncs.com/compatible-mode/v1 | DASHSCOPE_API_KEY | - | https://modelstudio.console.alibabacloud.com',\n 'siliconflow | SiliconFlow | https://api.siliconflow.com/v1 | SILICONFLOW_API_KEY | - | https://cloud.siliconflow.com/account/ak',\n 'deepinfra | DeepInfra | https://api.deepinfra.com/v1/openai | DEEPINFRA_API_KEY | - | https://deepinfra.com/dash/api_keys',\n 'huggingface | Hugging Face Inference | https://router.huggingface.co/v1 | HF_TOKEN | hf_ | https://huggingface.co/settings/tokens',\n 'perplexity | Perplexity | https://api.perplexity.ai | PERPLEXITY_API_KEY | pplx- | https://www.perplexity.ai/settings/api',\n 'cohere | Cohere | https://api.cohere.ai/compatibility/v1 | COHERE_API_KEY | - | https://dashboard.cohere.com/api-keys',\n 'vercel | Vercel AI Gateway | https://ai-gateway.vercel.sh/v1 | AI_GATEWAY_API_KEY | - | https://vercel.com/ai-gateway',\n 'github | GitHub Models | https://models.github.ai/inference | GITHUB_TOKEN | - | https://github.com/settings/tokens',\n 'novita | Novita AI | https://api.novita.ai/v3/openai | NOVITA_API_KEY | - | https://novita.ai/settings/key-management',\n 'hyperbolic | Hyperbolic | https://api.hyperbolic.xyz/v1 | HYPERBOLIC_API_KEY | - | https://app.hyperbolic.xyz/settings',\n 'sambanova | SambaNova | https://api.sambanova.ai/v1 | SAMBANOVA_API_KEY | - | https://cloud.sambanova.ai/apis',\n]\n .map((line) => line.split(' | ').map((f) => (f === '-' ? undefined : f)))\n .map(([name = '', label = '', baseUrl = '', keyEnv, keyPrefix, signup]) => [\n name,\n label,\n baseUrl,\n keyEnv,\n keyPrefix,\n signup,\n ]);\n\nconst LOCAL: [string, string, string][] = [\n ['ollama', 'Ollama', 'http://localhost:11434/v1'],\n ['lmstudio', 'LM Studio', 'http://localhost:1234/v1'],\n ['vllm', 'vLLM', 'http://localhost:8000/v1'],\n ['llamacpp', 'llama.cpp server', 'http://localhost:8080/v1'],\n ['litellm', 'LiteLLM proxy', 'http://localhost:4000/v1'],\n ['jan', 'Jan', 'http://localhost:1337/v1'],\n];\n\nexport const PROVIDER_TEMPLATES: Record<string, ProviderTemplate> = Object.fromEntries([\n ...HOSTED.map(([name, label, baseUrl, keyEnv, keyPrefix, signup]): [string, ProviderTemplate] => [\n name,\n {\n label,\n baseUrl,\n keyEnv,\n free: false,\n kind: name === 'gemini' ? 'gemini' : 'openai',\n ...(keyPrefix ? { keyPrefix } : {}),\n ...(signup ? { signup } : {}),\n },\n ]),\n ...LOCAL.map(([name, label, baseUrl]): [string, ProviderTemplate] => [\n name,\n { label, baseUrl, keyEnv: undefined, free: true, kind: 'openai', local: true },\n ]),\n [\n 'custom',\n { baseUrl: 'http://localhost:8000/v1', keyEnv: undefined, free: false, kind: 'openai' },\n ],\n]);\n\nexport function templateFor(name: string): ProviderTemplate | undefined {\n return PROVIDER_TEMPLATES[name.toLowerCase()];\n}\n\nexport async function appendEndpointBlock(\n file: string,\n name: string,\n t: ProviderTemplate,\n): Promise<void> {\n const block = stringifyToml({\n providers: {\n endpoints: {\n [name]: {\n kind: t.kind,\n base_url: t.baseUrl,\n ...(t.keyEnv ? { api_key_env: t.keyEnv } : {}),\n ...(t.free ? { free: true } : {}),\n },\n },\n },\n });\n await mkdir(dirname(file), { recursive: true });\n await appendFile(file, `\\n${block}`);\n}\n\nasync function removeEndpointBlock(file: string, name: string): Promise<boolean> {\n let text: string;\n try {\n text = await readFile(file, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return false;\n throw err;\n }\n const lines = text.split('\\n');\n const start = lines.findIndex((l) => l.trim() === `[providers.endpoints.${name}]`);\n if (start === -1) return false;\n let end = lines.length;\n for (let i = start + 1; i < lines.length; i++) {\n if (/^\\s*\\[.+\\]\\s*$/.test(lines[i] ?? '')) {\n end = i;\n break;\n }\n }\n await mkdir(dirname(file), { recursive: true });\n const { writeFile } = await import('node:fs/promises');\n await writeFile(file, lines.slice(0, start).concat(lines.slice(end)).join('\\n'));\n return true;\n}\n\nasync function testEndpoint(\n io: CliIO,\n name: string,\n baseUrl: string,\n apiKey: string | undefined,\n kind: 'openai' | 'responses' | 'gemini',\n): Promise<{ ok: boolean; detail: string }> {\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n const ep = config.providers.endpoints[name];\n const timeoutMs = ep ? parseDuration(ep.request_timeout) : 10_000;\n const opts = { name, baseUrl, apiKey, timeoutMs, ...(io.fetch ? { fetch: io.fetch } : {}) };\n const provider = makeEndpointProvider(kind, opts);\n try {\n // 1-token test call: tiny maxTokens, no tools.\n const res = await provider.complete({\n model: 'test',\n system: [{ text: 'Reply with the single word: ok' }],\n tools: [],\n messages: [{ role: 'user', content: [{ type: 'text', text: 'ok' }] }],\n maxTokens: 1,\n messageBreakpoints: [],\n });\n const text = res.content\n .filter((b) => b.type === 'text')\n .map((b) => (b as { text: string }).text)\n .join('')\n .slice(0, 80);\n return { ok: true, detail: text ? `answered (${text})` : 'answered' };\n } catch (err) {\n return { ok: false, detail: err instanceof Error ? err.message : String(err) };\n }\n}\n\nexport interface ProvidersAddOptions {\n baseUrl?: string | undefined;\n keyEnv?: string | undefined;\n free?: boolean | undefined;\n kind?: 'openai' | 'responses' | undefined;\n skipTest?: boolean | undefined;\n}\n\nexport async function providersAdd(\n io: CliIO,\n name: string,\n opts: ProvidersAddOptions,\n): Promise<number> {\n const lower = name.toLowerCase();\n if (!/^[a-z][a-z0-9_-]*$/.test(lower))\n throw new UsageError(`bad provider name \"${name}\"`, 'use lowercase letters, digits, _ or -');\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n if (config.providers.endpoints[lower])\n throw new UsageError(\n `provider \"${lower}\" already exists`,\n `use \\`omnexx providers remove ${lower}\\` first`,\n );\n const template = templateFor(lower);\n let baseUrl = opts.baseUrl;\n let keyEnv = opts.keyEnv;\n let free = opts.free;\n const kind = opts.kind ?? template?.kind ?? 'openai';\n if (baseUrl === undefined || keyEnv === undefined) {\n if (io.isTTY && process.env.VITEST === undefined) {\n // Interactive prompts (readline, no new deps).\n println(io.stdout, `Adding provider \"${lower}\"${template ? ` (template: ${lower})` : ''}.`);\n const askBase = await readLine(\n io,\n `Base URL [${template?.baseUrl ?? 'http://localhost:8000/v1'}]: `,\n );\n baseUrl = baseUrl ?? askBase?.trim() ?? template?.baseUrl ?? 'http://localhost:8000/v1';\n const askKey = await readLine(io, `Key env var [${template?.keyEnv ?? 'none'}]: `);\n const trimmedKey = (keyEnv ?? askKey?.trim() ?? template?.keyEnv ?? '').trim();\n keyEnv = trimmedKey ? trimmedKey : undefined;\n if (free === undefined) {\n const askFree = await readLine(io, 'Free / no key needed? [y/N]: ');\n const saidYes = /^\\s*y(es)?\\s*$/i.test(askFree ?? '');\n const templateFree = keyEnv === undefined && template?.free === true;\n free = saidYes || templateFree;\n }\n } else {\n baseUrl ??= template?.baseUrl;\n keyEnv ??= template?.keyEnv;\n free ??= template?.free ?? false;\n if (!baseUrl)\n throw new UsageError('missing --base-url', 'pass --base-url or use a known template name');\n }\n }\n free = free ?? false;\n const file = userConfigFile(resolvePaths(io.env));\n await appendEndpointBlock(file, lower, { baseUrl, keyEnv, free, kind });\n println(io.stdout, `Added [providers.endpoints.${lower}] to ${file}.`);\n if (!opts.skipTest) {\n const paths = resolvePaths(io.env);\n const key = await findProviderKey(paths, io.env, lower, keyEnv);\n const t = await testEndpoint(io, lower, baseUrl, key?.key, kind);\n println(io.stdout, t.ok ? `Test call ok: ${t.detail}` : `Test call failed: ${t.detail}`);\n if (!t.ok) return EXIT.error;\n }\n return EXIT.ok;\n}\n\nexport async function providersList(io: CliIO): Promise<number> {\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n println(io.stdout, 'anthropic (built-in)');\n for (const [name, ep] of Object.entries(config.providers.endpoints)) {\n println(io.stdout, `${name} ${ep.base_url}${ep.free ? ' (free)' : ''}`);\n }\n return EXIT.ok;\n}\n\nexport async function providersRemove(io: CliIO, name: string): Promise<number> {\n const lower = name.toLowerCase();\n if (lower === 'anthropic') throw new UsageError('cannot remove anthropic', 'it is built in');\n const file = userConfigFile(resolvePaths(io.env));\n const removed = await removeEndpointBlock(file, lower);\n println(\n io.stdout,\n removed\n ? `Removed [providers.endpoints.${lower}] from ${file}.`\n : `No [providers.endpoints.${lower}] in ${file}.`,\n );\n return EXIT.ok;\n}\n\nexport async function providersTest(io: CliIO, name: string): Promise<number> {\n const lower = name.toLowerCase();\n if (lower === 'anthropic')\n throw new UsageError('anthropic has no test yet', 'run `omnexx doctor` to check the key');\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n const ep = config.providers.endpoints[lower];\n if (!ep)\n throw new UsageError(\n `unknown provider \"${lower}\"`,\n '`omnexx providers list` shows configured providers',\n );\n const key = await findProviderKey(resolvePaths(io.env), io.env, lower, ep.api_key_env);\n const t = await testEndpoint(\n io,\n lower,\n ep.base_url,\n key?.key,\n (ep as { kind?: 'openai' | 'responses' }).kind ?? 'openai',\n );\n println(io.stdout, t.ok ? `ok: ${t.detail}` : `failed: ${t.detail}`);\n return t.ok ? EXIT.ok : EXIT.error;\n}\n\nexport const register: CommandRegistrar = (\n program: Command,\n io: CliIO,\n setExit: (c: number) => void,\n) => {\n const p = program.command('providers').description('add, list, remove and test model providers');\n p.command('add [name]')\n .description('add a provider (interactive, or flags)')\n .option('--base-url <url>', 'endpoint base URL')\n .option('--key-env <var>', 'env var holding the key')\n .option('--free', 'no key needed (local models)')\n .option('--kind <kind>', 'openai|responses')\n .option('--no-test', 'skip the 1-token test call')\n .action(\n async (\n name: string | undefined,\n opts: { baseUrl?: string; keyEnv?: string; free?: boolean; kind?: string; test?: boolean },\n ) => {\n const n = name ?? 'custom';\n const kind = opts.kind === 'responses' ? 'responses' : 'openai';\n setExit(\n await providersAdd(io, n, {\n baseUrl: opts.baseUrl,\n keyEnv: opts.keyEnv,\n free: opts.free,\n kind,\n skipTest: opts.test === false,\n }),\n );\n },\n );\n p.command('list')\n .description('list configured providers')\n .action(async () => {\n setExit(await providersList(io));\n });\n p.command('remove <name>')\n .description('remove a provider from user config')\n .action(async (name: string) => {\n setExit(await providersRemove(io, name));\n });\n p.command('test <name>')\n .description('run a 1-token test call')\n .action(async (name: string) => {\n setExit(await providersTest(io, name));\n });\n};\n"],"mappings":"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","names":[]}
|