omnexx 0.1.0 → 0.6.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/chat-server-NB3GPOMD.js +205 -0
- package/dist/chat-server-NB3GPOMD.js.map +1 -0
- package/dist/chat-settings-D3WKVSFU.js +110 -0
- package/dist/chat-settings-D3WKVSFU.js.map +1 -0
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-55VJCO7L.js +718 -0
- package/dist/chunk-55VJCO7L.js.map +1 -0
- package/dist/chunk-5T4EFIY2.js +278 -0
- package/dist/chunk-5T4EFIY2.js.map +1 -0
- package/dist/chunk-7PC4SSYS.js +78 -0
- package/dist/chunk-7PC4SSYS.js.map +1 -0
- package/dist/chunk-7PNGHZYH.js +87 -0
- package/dist/chunk-7PNGHZYH.js.map +1 -0
- package/dist/chunk-AOIUX7TJ.js +182 -0
- package/dist/chunk-AOIUX7TJ.js.map +1 -0
- package/dist/chunk-CVDLLOMJ.js +98 -0
- package/dist/chunk-CVDLLOMJ.js.map +1 -0
- package/dist/chunk-M64SDRL3.js +1584 -0
- package/dist/chunk-M64SDRL3.js.map +1 -0
- package/dist/chunk-N2WJ2YTX.js +8470 -0
- package/dist/chunk-N2WJ2YTX.js.map +1 -0
- package/dist/chunk-PY47EEHG.js +5539 -0
- package/dist/chunk-PY47EEHG.js.map +1 -0
- package/dist/chunk-S3TUDM5B.js +1560 -0
- package/dist/chunk-S3TUDM5B.js.map +1 -0
- package/dist/chunk-UZ6WXVKI.js +146 -0
- package/dist/chunk-UZ6WXVKI.js.map +1 -0
- package/dist/cli.js +2311 -6619
- package/dist/cli.js.map +1 -1
- package/dist/model-picker-7NLP3QO6.js +20 -0
- package/dist/model-picker-7NLP3QO6.js.map +1 -0
- package/dist/ollama-V6LN6HAR.js +67 -0
- package/dist/ollama-V6LN6HAR.js.map +1 -0
- package/dist/print-OUSVBDAA.js +56 -0
- package/dist/print-OUSVBDAA.js.map +1 -0
- package/dist/server-HSTNODMT.js +295 -0
- package/dist/server-HSTNODMT.js.map +1 -0
- package/dist/session-YXEM22IA.js +26 -0
- package/dist/session-YXEM22IA.js.map +1 -0
- package/dist/start-ZJQLZBS6.js +709 -0
- package/dist/start-ZJQLZBS6.js.map +1 -0
- package/package.json +18 -2
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,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
println
|
|
4
|
+
} from "./chunk-7PC4SSYS.js";
|
|
5
|
+
import {
|
|
6
|
+
describeError
|
|
7
|
+
} from "./chunk-7PNGHZYH.js";
|
|
8
|
+
import "./chunk-2H7UOFLK.js";
|
|
9
|
+
|
|
10
|
+
// src/cli/chat-server.ts
|
|
11
|
+
import { createInterface } from "readline";
|
|
12
|
+
var MODES = /* @__PURE__ */ new Set(["chat", "plan", "run"]);
|
|
13
|
+
var EMIT_EVERY_MS = 40;
|
|
14
|
+
function parseCommand(line) {
|
|
15
|
+
let data;
|
|
16
|
+
try {
|
|
17
|
+
data = JSON.parse(line);
|
|
18
|
+
} catch {
|
|
19
|
+
throw new Error("expected one JSON object per line");
|
|
20
|
+
}
|
|
21
|
+
if (typeof data !== "object" || data === null || typeof data.type !== "string")
|
|
22
|
+
throw new Error('expected an object with a "type"');
|
|
23
|
+
return data;
|
|
24
|
+
}
|
|
25
|
+
function text(c, key) {
|
|
26
|
+
const v = c[key];
|
|
27
|
+
if (typeof v !== "string" || !v.trim()) throw new Error(`"${c.type}" needs a "${key}" string`);
|
|
28
|
+
return v;
|
|
29
|
+
}
|
|
30
|
+
async function serveChat(io, runCli, opts) {
|
|
31
|
+
const { Session } = await import("./session-YXEM22IA.js");
|
|
32
|
+
const session = new Session(io, runCli);
|
|
33
|
+
const write = (msg) => {
|
|
34
|
+
println(io.stdout, JSON.stringify(msg));
|
|
35
|
+
};
|
|
36
|
+
const fail = (err) => {
|
|
37
|
+
write({ type: "error", message: describeError(err) });
|
|
38
|
+
};
|
|
39
|
+
let sentUpTo = -1;
|
|
40
|
+
let clearedAt = -1;
|
|
41
|
+
let timer;
|
|
42
|
+
const emit = () => {
|
|
43
|
+
timer = void 0;
|
|
44
|
+
const fresh = session.entries.filter((e) => e.id > sentUpTo);
|
|
45
|
+
sentUpTo = session.entries.at(-1)?.id ?? sentUpTo;
|
|
46
|
+
const t = session.chatTelemetry;
|
|
47
|
+
const state = {
|
|
48
|
+
type: "state",
|
|
49
|
+
entries: fresh,
|
|
50
|
+
live: session.live,
|
|
51
|
+
todos: session.todos,
|
|
52
|
+
pending: session.pending?.question ?? null,
|
|
53
|
+
mode: session.mode,
|
|
54
|
+
busy: session.busy !== void 0 || session.chatBusy,
|
|
55
|
+
paused: session.chatPaused,
|
|
56
|
+
autoApprove: session.autoApprove,
|
|
57
|
+
model: session.chatModelName ?? null,
|
|
58
|
+
context: session.context ?? null,
|
|
59
|
+
usage: t ? {
|
|
60
|
+
startedAt: t.startedAt ?? null,
|
|
61
|
+
tokens: t.tokens,
|
|
62
|
+
usd: t.usd,
|
|
63
|
+
turns: t.turns,
|
|
64
|
+
toolCalls: t.toolCalls,
|
|
65
|
+
toolErrors: t.toolErrors,
|
|
66
|
+
compactions: t.compactions
|
|
67
|
+
} : null,
|
|
68
|
+
run: session.runId ? { id: session.runId, alive: session.runAlive, ...session.info } : null
|
|
69
|
+
};
|
|
70
|
+
write(state);
|
|
71
|
+
};
|
|
72
|
+
const schedule = () => {
|
|
73
|
+
timer ??= setTimeout(emit, EMIT_EVERY_MS);
|
|
74
|
+
};
|
|
75
|
+
session.onChange(schedule);
|
|
76
|
+
const sendModels = async () => {
|
|
77
|
+
const { loadModelChoices } = await import("./model-picker-7NLP3QO6.js");
|
|
78
|
+
const { items, unreachable } = await loadModelChoices(io);
|
|
79
|
+
write({
|
|
80
|
+
type: "models",
|
|
81
|
+
items: items.filter((i) => i.kind === "model"),
|
|
82
|
+
unreachable,
|
|
83
|
+
current: session.chatModelName ?? null
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
const sendSettings = async () => {
|
|
87
|
+
const { providerSettings } = await import("./chat-settings-D3WKVSFU.js");
|
|
88
|
+
write({ type: "settings", ...await providerSettings(io) });
|
|
89
|
+
};
|
|
90
|
+
const providersChanged = async (message) => {
|
|
91
|
+
session.push("system", message);
|
|
92
|
+
await sendSettings();
|
|
93
|
+
await sendModels();
|
|
94
|
+
};
|
|
95
|
+
const run = (p) => {
|
|
96
|
+
p.catch(fail).finally(schedule);
|
|
97
|
+
};
|
|
98
|
+
const handle = (c) => {
|
|
99
|
+
switch (c.type) {
|
|
100
|
+
case "send":
|
|
101
|
+
run(session.submit(text(c, "text")));
|
|
102
|
+
return;
|
|
103
|
+
case "answer":
|
|
104
|
+
if (!session.pending) throw new Error("nothing is waiting for an answer");
|
|
105
|
+
run(session.submit(c.yes === true ? "y" : "n"));
|
|
106
|
+
return;
|
|
107
|
+
case "stop":
|
|
108
|
+
case "pause":
|
|
109
|
+
case "resume":
|
|
110
|
+
case "undo":
|
|
111
|
+
case "compact":
|
|
112
|
+
run(session.command(c.type));
|
|
113
|
+
return;
|
|
114
|
+
case "clear":
|
|
115
|
+
run(
|
|
116
|
+
session.command("clear").then(() => {
|
|
117
|
+
clearedAt = session.entries.at(-1)?.id ?? clearedAt;
|
|
118
|
+
sentUpTo = clearedAt;
|
|
119
|
+
write({ type: "ready", version: opts.version });
|
|
120
|
+
emit();
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
return;
|
|
124
|
+
case "mode": {
|
|
125
|
+
const mode = c.mode;
|
|
126
|
+
if (!MODES.has(mode)) throw new Error("mode is one of chat, plan, run");
|
|
127
|
+
if (session.mode !== mode) session.setMode(mode);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
case "yolo":
|
|
131
|
+
if (c.on === true !== session.autoApprove) run(session.submit("/yolo"));
|
|
132
|
+
return;
|
|
133
|
+
case "model":
|
|
134
|
+
run(session.switchModel(text(c, "ref")));
|
|
135
|
+
return;
|
|
136
|
+
case "models":
|
|
137
|
+
run(sendModels());
|
|
138
|
+
return;
|
|
139
|
+
case "settings":
|
|
140
|
+
run(sendSettings());
|
|
141
|
+
return;
|
|
142
|
+
case "addProvider": {
|
|
143
|
+
const str = (k) => {
|
|
144
|
+
const v = c[k];
|
|
145
|
+
return typeof v === "string" ? v : void 0;
|
|
146
|
+
};
|
|
147
|
+
run(
|
|
148
|
+
import("./chat-settings-D3WKVSFU.js").then(async ({ addProvider }) => {
|
|
149
|
+
await providersChanged(
|
|
150
|
+
await addProvider(io, {
|
|
151
|
+
provider: str("provider"),
|
|
152
|
+
key: str("key"),
|
|
153
|
+
url: str("url")
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
case "removeProvider":
|
|
161
|
+
run(
|
|
162
|
+
import("./chat-settings-D3WKVSFU.js").then(async ({ removeProvider }) => {
|
|
163
|
+
await providersChanged(await removeProvider(io, text(c, "name")));
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
return;
|
|
167
|
+
case "connect": {
|
|
168
|
+
const name = typeof c.name === "string" && c.name.trim() ? ` --name ${c.name.trim()}` : "";
|
|
169
|
+
run(session.submit(`/model ${text(c, "value")}${name}`));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
case "sync":
|
|
173
|
+
sentUpTo = clearedAt;
|
|
174
|
+
write({ type: "ready", version: opts.version });
|
|
175
|
+
emit();
|
|
176
|
+
return;
|
|
177
|
+
default:
|
|
178
|
+
throw new Error(`unknown command "${c.type}"`);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
await session.loadCommands();
|
|
182
|
+
await session.continueChat().catch(fail);
|
|
183
|
+
await session.greet(false).catch(fail);
|
|
184
|
+
write({ type: "ready", version: opts.version });
|
|
185
|
+
emit();
|
|
186
|
+
const lines = createInterface({ input: io.stdin, crlfDelay: Infinity });
|
|
187
|
+
for await (const line of lines) {
|
|
188
|
+
if (!line.trim()) continue;
|
|
189
|
+
try {
|
|
190
|
+
handle(parseCommand(line));
|
|
191
|
+
} catch (err) {
|
|
192
|
+
fail(err);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (session.chatBusy) await session.command("stop");
|
|
196
|
+
if (timer) {
|
|
197
|
+
clearTimeout(timer);
|
|
198
|
+
emit();
|
|
199
|
+
}
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
export {
|
|
203
|
+
serveChat
|
|
204
|
+
};
|
|
205
|
+
//# sourceMappingURL=chat-server-NB3GPOMD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/chat-server.ts"],"sourcesContent":["import { createInterface } from 'node:readline';\nimport { describeError } from '../errors.js';\nimport type { Telemetry } from '../telemetry/aggregate.js';\nimport type { TodoItem } from '../tools/todo.js';\nimport type { Entry, Mode, RunCli } from '../tui/session.js';\nimport { println, type CliIO } from './io.js';\n\n/**\n * `omnexx chat-server`: the interactive session behind a JSON-lines protocol on stdin/stdout, for\n * editor panels (the VS Code chat). It drives the same Session as the terminal UI, so slash\n * commands, approvals, model switching and plan mode behave the same way.\n *\n * In (one JSON object per line): send {text} · answer {yes} · stop · pause · resume · mode {mode} ·\n * yolo {on} ·\n * model {ref} · models · connect {value, name?} · undo · clear · compact · sync (resend all) ·\n * settings · addProvider {provider?, key?, url?} · removeProvider {name}.\n * Out: ready {version} · state (below; `entries` holds only lines not sent before) · models\n * {items, unreachable, current} · settings {catalog, connected} · error {message}.\n */\n\nexport interface ChatUsage {\n /** When the chat's first event happened (epoch ms), for an uptime clock. */\n startedAt: number | null;\n tokens: Telemetry['tokens'];\n usd: number;\n turns: number;\n toolCalls: number;\n toolErrors: number;\n compactions: number;\n}\n\nexport interface ChatState {\n type: 'state';\n entries: Entry[];\n /** The reply as it streams in; empty once it lands in `entries`. */\n live: string;\n todos: TodoItem[];\n /** A question waiting for allow / deny, or null. */\n pending: string | null;\n mode: Mode;\n busy: boolean;\n paused: boolean;\n autoApprove: boolean;\n model: string | null;\n context: { used: number; limit: number } | null;\n usage: ChatUsage | null;\n run: { id: string; alive: boolean; phase?: string; done?: number; tasks?: number } | null;\n}\n\nconst MODES = new Set<Mode>(['chat', 'plan', 'run']);\n/** Coalesce bursts of changes (a streaming reply fires one per token) into one state line. */\nconst EMIT_EVERY_MS = 40;\n\ntype Command = { type: string } & Record<string, unknown>;\n\nfunction parseCommand(line: string): Command {\n let data: unknown;\n try {\n data = JSON.parse(line);\n } catch {\n throw new Error('expected one JSON object per line');\n }\n if (typeof data !== 'object' || data === null || typeof (data as Command).type !== 'string')\n throw new Error('expected an object with a \"type\"');\n return data as Command;\n}\n\nfunction text(c: Command, key: string): string {\n const v = c[key];\n if (typeof v !== 'string' || !v.trim()) throw new Error(`\"${c.type}\" needs a \"${key}\" string`);\n return v;\n}\n\nexport async function serveChat(\n io: CliIO,\n runCli: RunCli,\n opts: { version: string },\n): Promise<number> {\n const { Session } = await import('../tui/session.js');\n const session = new Session(io, runCli);\n const write = (msg: object): void => {\n println(io.stdout, JSON.stringify(msg));\n };\n const fail = (err: unknown): void => {\n write({ type: 'error', message: describeError(err) });\n };\n\n let sentUpTo = -1;\n /** The last entry before the latest new chat; a reloaded panel starts after it. */\n let clearedAt = -1;\n let timer: NodeJS.Timeout | undefined;\n const emit = (): void => {\n timer = undefined;\n const fresh = session.entries.filter((e) => e.id > sentUpTo);\n sentUpTo = session.entries.at(-1)?.id ?? sentUpTo;\n const t = session.chatTelemetry;\n const state: ChatState = {\n type: 'state',\n entries: fresh,\n live: session.live,\n todos: session.todos,\n pending: session.pending?.question ?? null,\n mode: session.mode,\n busy: session.busy !== undefined || session.chatBusy,\n paused: session.chatPaused,\n autoApprove: session.autoApprove,\n model: session.chatModelName ?? null,\n context: session.context ?? null,\n usage: t\n ? {\n startedAt: t.startedAt ?? null,\n tokens: t.tokens,\n usd: t.usd,\n turns: t.turns,\n toolCalls: t.toolCalls,\n toolErrors: t.toolErrors,\n compactions: t.compactions,\n }\n : null,\n run: session.runId ? { id: session.runId, alive: session.runAlive, ...session.info } : null,\n };\n write(state);\n };\n const schedule = (): void => {\n timer ??= setTimeout(emit, EMIT_EVERY_MS);\n };\n session.onChange(schedule);\n\n const sendModels = async (): Promise<void> => {\n const { loadModelChoices } = await import('../tui/model-picker.js');\n const { items, unreachable } = await loadModelChoices(io);\n write({\n type: 'models',\n items: items.filter((i) => i.kind === 'model'),\n unreachable,\n current: session.chatModelName ?? null,\n });\n };\n const sendSettings = async (): Promise<void> => {\n const { providerSettings } = await import('./chat-settings.js');\n write({ type: 'settings', ...(await providerSettings(io)) });\n };\n /** After a provider change: say what happened, then refresh the settings page and models. */\n const providersChanged = async (message: string): Promise<void> => {\n session.push('system', message);\n await sendSettings();\n await sendModels();\n };\n\n const run = (p: Promise<unknown>): void => {\n p.catch(fail).finally(schedule);\n };\n\n const handle = (c: Command): void => {\n switch (c.type) {\n case 'send':\n run(session.submit(text(c, 'text')));\n return;\n case 'answer':\n if (!session.pending) throw new Error('nothing is waiting for an answer');\n run(session.submit(c.yes === true ? 'y' : 'n'));\n return;\n case 'stop':\n case 'pause':\n case 'resume':\n case 'undo':\n case 'compact':\n run(session.command(c.type));\n return;\n case 'clear':\n run(\n session.command('clear').then(() => {\n // A new chat starts the panel over: nothing from before the clear is sent again.\n clearedAt = session.entries.at(-1)?.id ?? clearedAt;\n sentUpTo = clearedAt;\n write({ type: 'ready', version: opts.version });\n emit();\n }),\n );\n return;\n case 'mode': {\n const mode = c.mode as Mode;\n if (!MODES.has(mode)) throw new Error('mode is one of chat, plan, run');\n if (session.mode !== mode) session.setMode(mode);\n return;\n }\n case 'yolo':\n // /yolo toggles; only send it when the state differs from what was asked.\n if ((c.on === true) !== session.autoApprove) run(session.submit('/yolo'));\n return;\n case 'model':\n run(session.switchModel(text(c, 'ref')));\n return;\n case 'models':\n run(sendModels());\n return;\n case 'settings':\n run(sendSettings());\n return;\n case 'addProvider': {\n const str = (k: string): string | undefined => {\n const v = c[k];\n return typeof v === 'string' ? v : undefined;\n };\n run(\n import('./chat-settings.js').then(async ({ addProvider }) => {\n await providersChanged(\n await addProvider(io, {\n provider: str('provider'),\n key: str('key'),\n url: str('url'),\n }),\n );\n }),\n );\n return;\n }\n case 'removeProvider':\n run(\n import('./chat-settings.js').then(async ({ removeProvider }) => {\n await providersChanged(await removeProvider(io, text(c, 'name')));\n }),\n );\n return;\n case 'connect': {\n const name = typeof c.name === 'string' && c.name.trim() ? ` --name ${c.name.trim()}` : '';\n run(session.submit(`/model ${text(c, 'value')}${name}`));\n return;\n }\n case 'sync':\n // A reloaded panel: start it over with the conversation since the last new chat.\n sentUpTo = clearedAt;\n write({ type: 'ready', version: opts.version });\n emit();\n return;\n default:\n throw new Error(`unknown command \"${c.type}\"`);\n }\n };\n\n await session.loadCommands();\n await session.continueChat().catch(fail);\n await session.greet(false).catch(fail);\n write({ type: 'ready', version: opts.version });\n emit();\n\n const lines = createInterface({ input: io.stdin, crlfDelay: Infinity });\n for await (const line of lines) {\n if (!line.trim()) continue;\n try {\n handle(parseCommand(line));\n } catch (err) {\n fail(err);\n }\n }\n // The editor closed the pipe: end any turn in progress, then flush the last state.\n if (session.chatBusy) await session.command('stop');\n if (timer) {\n clearTimeout(timer);\n emit();\n }\n return 0;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
connect,
|
|
4
|
+
describeConnect
|
|
5
|
+
} from "./chunk-AOIUX7TJ.js";
|
|
6
|
+
import {
|
|
7
|
+
PROVIDER_TEMPLATES,
|
|
8
|
+
removeEndpointBlock
|
|
9
|
+
} from "./chunk-5T4EFIY2.js";
|
|
10
|
+
import "./chunk-7PC4SSYS.js";
|
|
11
|
+
import {
|
|
12
|
+
clearKey,
|
|
13
|
+
findProviderKey,
|
|
14
|
+
loadConfig,
|
|
15
|
+
maskKey,
|
|
16
|
+
resolvePaths,
|
|
17
|
+
userConfigFile
|
|
18
|
+
} from "./chunk-S3TUDM5B.js";
|
|
19
|
+
import {
|
|
20
|
+
UsageError
|
|
21
|
+
} from "./chunk-7PNGHZYH.js";
|
|
22
|
+
import "./chunk-2H7UOFLK.js";
|
|
23
|
+
|
|
24
|
+
// src/cli/chat-settings.ts
|
|
25
|
+
var ANTHROPIC = {
|
|
26
|
+
name: "anthropic",
|
|
27
|
+
label: "Anthropic (Claude)",
|
|
28
|
+
baseUrl: "https://api.anthropic.com",
|
|
29
|
+
local: false,
|
|
30
|
+
signup: "https://console.anthropic.com/settings/keys",
|
|
31
|
+
keyPrefix: "sk-ant-"
|
|
32
|
+
};
|
|
33
|
+
var NAME = /^[a-z][a-z0-9_-]{0,39}$/;
|
|
34
|
+
function providerCatalog() {
|
|
35
|
+
const listed = Object.entries(PROVIDER_TEMPLATES).filter(([name]) => name !== "custom").map(([name, t]) => ({
|
|
36
|
+
name,
|
|
37
|
+
label: t.label ?? name,
|
|
38
|
+
baseUrl: t.baseUrl,
|
|
39
|
+
local: t.local === true,
|
|
40
|
+
...t.signup ? { signup: t.signup } : {},
|
|
41
|
+
...t.keyPrefix ? { keyPrefix: t.keyPrefix } : {}
|
|
42
|
+
}));
|
|
43
|
+
return [ANTHROPIC, ...listed];
|
|
44
|
+
}
|
|
45
|
+
async function providerSettings(io) {
|
|
46
|
+
const paths = resolvePaths(io.env);
|
|
47
|
+
const { config } = await loadConfig({ cwd: io.cwd, env: io.env });
|
|
48
|
+
const catalog = providerCatalog();
|
|
49
|
+
const labelOf = (name) => catalog.find((c) => c.name === name)?.label ?? name;
|
|
50
|
+
const connected = [];
|
|
51
|
+
const anthropic = await findProviderKey(paths, io.env, "anthropic");
|
|
52
|
+
if (anthropic)
|
|
53
|
+
connected.push({
|
|
54
|
+
name: "anthropic",
|
|
55
|
+
label: ANTHROPIC.label,
|
|
56
|
+
baseUrl: ANTHROPIC.baseUrl,
|
|
57
|
+
local: false,
|
|
58
|
+
free: false,
|
|
59
|
+
key: maskKey(anthropic.key),
|
|
60
|
+
keySource: anthropic.source
|
|
61
|
+
});
|
|
62
|
+
for (const [name, ep] of Object.entries(config.providers.endpoints)) {
|
|
63
|
+
const found = await findProviderKey(paths, io.env, name, ep.api_key_env);
|
|
64
|
+
connected.push({
|
|
65
|
+
name,
|
|
66
|
+
label: labelOf(name),
|
|
67
|
+
baseUrl: ep.base_url,
|
|
68
|
+
local: PROVIDER_TEMPLATES[name]?.local === true || /\/\/(localhost|127\.0\.0\.1)[:/]/.test(ep.base_url),
|
|
69
|
+
free: ep.free,
|
|
70
|
+
key: found ? maskKey(found.key) : null,
|
|
71
|
+
keySource: found?.source ?? null
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return { catalog, connected };
|
|
75
|
+
}
|
|
76
|
+
async function addProvider(io, a) {
|
|
77
|
+
const url = a.url?.trim();
|
|
78
|
+
const provider = a.provider?.trim().toLowerCase();
|
|
79
|
+
const key = a.key?.trim() ? a.key.trim() : void 0;
|
|
80
|
+
if (url && !/^https?:\/\/\S+$/i.test(url))
|
|
81
|
+
throw new UsageError("the URL must start with http:// or https://");
|
|
82
|
+
if (provider && !NAME.test(provider))
|
|
83
|
+
throw new UsageError(
|
|
84
|
+
`"${provider}" is not a usable name`,
|
|
85
|
+
"lowercase letters, digits, _ and -"
|
|
86
|
+
);
|
|
87
|
+
if (!url && !provider && !key) throw new UsageError("pick a provider or enter a URL");
|
|
88
|
+
const target = url ?? provider ?? key ?? "";
|
|
89
|
+
const r = await connect(io, target, url ? key : target === key ? void 0 : key, {
|
|
90
|
+
...url && provider ? { name: provider } : {}
|
|
91
|
+
});
|
|
92
|
+
return describeConnect(r);
|
|
93
|
+
}
|
|
94
|
+
async function removeProvider(io, name) {
|
|
95
|
+
const lower = name.trim().toLowerCase();
|
|
96
|
+
if (!NAME.test(lower)) throw new UsageError(`"${name}" is not a provider name`);
|
|
97
|
+
const paths = resolvePaths(io.env);
|
|
98
|
+
const removedBlock = lower === "anthropic" ? false : await removeEndpointBlock(userConfigFile(paths), lower);
|
|
99
|
+
const removedKey = await clearKey(paths, lower);
|
|
100
|
+
if (!removedBlock && !removedKey)
|
|
101
|
+
return `${lower} had nothing to remove (a key from an environment variable stays set)`;
|
|
102
|
+
return `removed ${lower}${removedKey ? " and its stored key" : ""}`;
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
addProvider,
|
|
106
|
+
providerCatalog,
|
|
107
|
+
providerSettings,
|
|
108
|
+
removeProvider
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=chat-settings-D3WKVSFU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/chat-settings.ts"],"sourcesContent":["import { clearKey, findProviderKey, maskKey } from '../auth/keys.js';\nimport { loadConfig } from '../config/load.js';\nimport { resolvePaths, userConfigFile } from '../core/paths.js';\nimport { UsageError } from '../errors.js';\nimport { PROVIDER_TEMPLATES, removeEndpointBlock } from './commands/providers.js';\nimport { connect, describeConnect } from './connect.js';\nimport type { CliIO } from './io.js';\n\n/**\n * Provider settings for editor panels (`omnexx chat-server`): what can be added, what is set up,\n * and adding or removing one. Keys go to the 0600 credentials file and are only ever shown masked.\n */\n\nexport interface CatalogProvider {\n name: string;\n label: string;\n baseUrl: string;\n /** A local server the person runs themselves: no key. */\n local: boolean;\n /** Where to get a key. */\n signup?: string;\n /** How its keys start, for a placeholder. */\n keyPrefix?: string;\n}\n\nexport interface ConnectedProvider {\n name: string;\n label: string;\n baseUrl: string;\n local: boolean;\n free: boolean;\n /** The key, masked (`sk-…wxyz`), or null when none is set. */\n key: string | null;\n /** Where the key comes from: an environment variable or the credentials file. */\n keySource: string | null;\n}\n\nexport interface ProviderSettings {\n catalog: CatalogProvider[];\n connected: ConnectedProvider[];\n}\n\nconst ANTHROPIC: CatalogProvider = {\n name: 'anthropic',\n label: 'Anthropic (Claude)',\n baseUrl: 'https://api.anthropic.com',\n local: false,\n signup: 'https://console.anthropic.com/settings/keys',\n keyPrefix: 'sk-ant-',\n};\n\nconst NAME = /^[a-z][a-z0-9_-]{0,39}$/;\n\nexport function providerCatalog(): CatalogProvider[] {\n const listed = Object.entries(PROVIDER_TEMPLATES)\n .filter(([name]) => name !== 'custom')\n .map(([name, t]) => ({\n name,\n label: t.label ?? name,\n baseUrl: t.baseUrl,\n local: t.local === true,\n ...(t.signup ? { signup: t.signup } : {}),\n ...(t.keyPrefix ? { keyPrefix: t.keyPrefix } : {}),\n }));\n return [ANTHROPIC, ...listed];\n}\n\nexport async function providerSettings(io: CliIO): Promise<ProviderSettings> {\n const paths = resolvePaths(io.env);\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n const catalog = providerCatalog();\n const labelOf = (name: string): string => catalog.find((c) => c.name === name)?.label ?? name;\n const connected: ConnectedProvider[] = [];\n const anthropic = await findProviderKey(paths, io.env, 'anthropic');\n if (anthropic)\n connected.push({\n name: 'anthropic',\n label: ANTHROPIC.label,\n baseUrl: ANTHROPIC.baseUrl,\n local: false,\n free: false,\n key: maskKey(anthropic.key),\n keySource: anthropic.source,\n });\n for (const [name, ep] of Object.entries(config.providers.endpoints)) {\n const found = await findProviderKey(paths, io.env, name, ep.api_key_env);\n connected.push({\n name,\n label: labelOf(name),\n baseUrl: ep.base_url,\n local:\n PROVIDER_TEMPLATES[name]?.local === true ||\n /\\/\\/(localhost|127\\.0\\.0\\.1)[:/]/.test(ep.base_url),\n free: ep.free,\n key: found ? maskKey(found.key) : null,\n keySource: found?.source ?? null,\n });\n }\n return { catalog, connected };\n}\n\nexport interface AddProvider {\n /** A catalog name, or a new name for a custom URL. */\n provider?: string | undefined;\n key?: string | undefined;\n /** For a custom or local OpenAI-compatible server. */\n url?: string | undefined;\n}\n\n/** Connect a provider from the settings form; returns what to tell the person. */\nexport async function addProvider(io: CliIO, a: AddProvider): Promise<string> {\n const url = a.url?.trim();\n const provider = a.provider?.trim().toLowerCase();\n const key = a.key?.trim() ? a.key.trim() : undefined;\n if (url && !/^https?:\\/\\/\\S+$/i.test(url))\n throw new UsageError('the URL must start with http:// or https://');\n if (provider && !NAME.test(provider))\n throw new UsageError(\n `\"${provider}\" is not a usable name`,\n 'lowercase letters, digits, _ and -',\n );\n if (!url && !provider && !key) throw new UsageError('pick a provider or enter a URL');\n // A catalog provider is connected by name; a URL gets the name it was given, if any.\n const target = url ?? provider ?? key ?? '';\n const r = await connect(io, target, url ? key : target === key ? undefined : key, {\n ...(url && provider ? { name: provider } : {}),\n });\n return describeConnect(r);\n}\n\n/** Remove a provider: its endpoint block and its stored key. Environment variables stay. */\nexport async function removeProvider(io: CliIO, name: string): Promise<string> {\n const lower = name.trim().toLowerCase();\n if (!NAME.test(lower)) throw new UsageError(`\"${name}\" is not a provider name`);\n const paths = resolvePaths(io.env);\n const removedBlock =\n lower === 'anthropic' ? false : await removeEndpointBlock(userConfigFile(paths), lower);\n const removedKey = await clearKey(paths, lower);\n if (!removedBlock && !removedKey)\n return `${lower} had nothing to remove (a key from an environment variable stays set)`;\n return `removed ${lower}${removedKey ? ' and its stored key' : ''}`;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -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":[]}
|