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.
Files changed (44) hide show
  1. package/README.md +85 -22
  2. package/dist/chat-server-NB3GPOMD.js +205 -0
  3. package/dist/chat-server-NB3GPOMD.js.map +1 -0
  4. package/dist/chat-settings-D3WKVSFU.js +110 -0
  5. package/dist/chat-settings-D3WKVSFU.js.map +1 -0
  6. package/dist/chunk-2H7UOFLK.js +11 -0
  7. package/dist/chunk-2H7UOFLK.js.map +1 -0
  8. package/dist/chunk-55VJCO7L.js +718 -0
  9. package/dist/chunk-55VJCO7L.js.map +1 -0
  10. package/dist/chunk-5T4EFIY2.js +278 -0
  11. package/dist/chunk-5T4EFIY2.js.map +1 -0
  12. package/dist/chunk-7PC4SSYS.js +78 -0
  13. package/dist/chunk-7PC4SSYS.js.map +1 -0
  14. package/dist/chunk-7PNGHZYH.js +87 -0
  15. package/dist/chunk-7PNGHZYH.js.map +1 -0
  16. package/dist/chunk-AOIUX7TJ.js +182 -0
  17. package/dist/chunk-AOIUX7TJ.js.map +1 -0
  18. package/dist/chunk-CVDLLOMJ.js +98 -0
  19. package/dist/chunk-CVDLLOMJ.js.map +1 -0
  20. package/dist/chunk-M64SDRL3.js +1584 -0
  21. package/dist/chunk-M64SDRL3.js.map +1 -0
  22. package/dist/chunk-N2WJ2YTX.js +8470 -0
  23. package/dist/chunk-N2WJ2YTX.js.map +1 -0
  24. package/dist/chunk-PY47EEHG.js +5539 -0
  25. package/dist/chunk-PY47EEHG.js.map +1 -0
  26. package/dist/chunk-S3TUDM5B.js +1560 -0
  27. package/dist/chunk-S3TUDM5B.js.map +1 -0
  28. package/dist/chunk-UZ6WXVKI.js +146 -0
  29. package/dist/chunk-UZ6WXVKI.js.map +1 -0
  30. package/dist/cli.js +2311 -6619
  31. package/dist/cli.js.map +1 -1
  32. package/dist/model-picker-7NLP3QO6.js +20 -0
  33. package/dist/model-picker-7NLP3QO6.js.map +1 -0
  34. package/dist/ollama-V6LN6HAR.js +67 -0
  35. package/dist/ollama-V6LN6HAR.js.map +1 -0
  36. package/dist/print-OUSVBDAA.js +56 -0
  37. package/dist/print-OUSVBDAA.js.map +1 -0
  38. package/dist/server-HSTNODMT.js +295 -0
  39. package/dist/server-HSTNODMT.js.map +1 -0
  40. package/dist/session-YXEM22IA.js +26 -0
  41. package/dist/session-YXEM22IA.js.map +1 -0
  42. package/dist/start-ZJQLZBS6.js +709 -0
  43. package/dist/start-ZJQLZBS6.js.map +1 -0
  44. 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.** `0.1.0` is the first release: a personal tool, not a product. It uses your own Anthropic API key and spends your money. Verified against the real API: a one-task run (plan, fix, commit, report) cost $0.06; a single fix cycle cost $0.007 with a 90% cache-read share.
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
- ## Try it
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
- - Docker sandbox (`sandbox = "docker"` fails with "not implemented"). Host mode's limits are in [docs/safety.md](docs/safety.md).
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
- - OpenAI-compatible providers, `open_pr`, the benchmark harness, npm publishing.
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":[]}