omnirush 0.1.1

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/src/bin.js ADDED
@@ -0,0 +1,456 @@
1
+ #!/usr/bin/env node
2
+ // omnirush — launcher for the Omnirush coding agent (pi core).
3
+ //
4
+ // The CLI is a thin client of the Omnirush manager (a single public
5
+ // HTTPS origin, real cert — no helper proxy, no custom CA):
6
+ //
7
+ // omnirush login device-flow sign-in (browser approval; tokens
8
+ // stored 0600 in ~/.omnirush/auth.json)
9
+ // omnirush logout forget the stored tokens
10
+ // omnirush whoami show the signed-in device identity
11
+ // omnirush doctor check auth (or pending login), manager, pi config
12
+ // omnirush collect one-shot workspace snapshot upload
13
+ // omnirush run the agent (default; forwards to pi)
14
+ //
15
+ // Environment:
16
+ // OMNIRUSH_ORIGIN manager origin override (default
17
+ // https://omnirush.ai/omnirush)
18
+ // OMNIRUSH_GATEWAY_URL provider base URL override ({origin}/v1)
19
+ // OMNIRUSH_DIR state dir override (default ~/.omnirush)
20
+ // OMNIRUSH_MODEL model override for plain runs (default
21
+ // gpt-6-astra; user --model still wins)
22
+ //
23
+ // Agent runs default to `--provider omnirush --model gpt-6-astra` (SOTA
24
+ // only — charter rule 1). The sota extension watches every response,
25
+ // refreshes the device token single-flight on 401 (retry once), and
26
+ // warns on stderr if the gateway serves a different model than
27
+ // requested; `--strict-sota` makes that violation fatal (exit 3).
28
+
29
+ import { spawn } from "node:child_process";
30
+ import fs from "node:fs";
31
+ import http from "node:http";
32
+ import https from "node:https";
33
+ import os from "node:os";
34
+ import path from "node:path";
35
+ import { createRequire } from "node:module";
36
+ import { fileURLToPath } from "node:url";
37
+
38
+ import {
39
+ DEFAULT_ORIGIN,
40
+ modelsConfig,
41
+ resolveGatewayUrl,
42
+ withModelDefaults,
43
+ resolveModelDefaults,
44
+ } from "./lib.js";
45
+ import {
46
+ clearAuth,
47
+ deviceAuthorize,
48
+ deviceMe,
49
+ loadAuth,
50
+ loadPendingFlow,
51
+ omniDir,
52
+ openBrowser,
53
+ pollForToken,
54
+ redactForDisplay,
55
+ refreshTokens,
56
+ saveAuth,
57
+ savePendingFlow,
58
+ clearPendingFlow,
59
+ } from "../assets/extensions/omnirush/auth.js";
60
+
61
+ const require = createRequire(import.meta.url);
62
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
63
+ const OMNI_DIR = omniDir();
64
+ // OMNIRUSH_PI_AGENT_DIR overrides where pi's config/extensions live
65
+ // (tests use it to keep the real ~/.pi untouched).
66
+ const PI_AGENT_DIR = process.env.OMNIRUSH_PI_AGENT_DIR
67
+ ? path.resolve(process.env.OMNIRUSH_PI_AGENT_DIR)
68
+ : path.join(os.homedir(), ".pi", "agent");
69
+ const PI_MODELS_JSON = path.join(PI_AGENT_DIR, "models.json");
70
+ const PI_EXT_DIR = path.join(PI_AGENT_DIR, "extensions", "omnirush");
71
+
72
+ function managerOrigin() {
73
+ return (
74
+ (process.env.OMNIRUSH_ORIGIN || "").trim().replace(/\/+$/, "") ||
75
+ DEFAULT_ORIGIN
76
+ );
77
+ }
78
+
79
+ function piEntry() {
80
+ // The pi package is Bun-first and its "exports" map blocks plain Node
81
+ // resolution, so locate it by path: walk up from our own package root
82
+ // to the nearest node_modules containing it.
83
+ const PKG = "@earendil-works/pi-coding-agent";
84
+ let dir = path.resolve(__dirname, "..");
85
+ for (let i = 0; i < 6; i++) {
86
+ const pkgPath = path.join(dir, "node_modules", PKG, "package.json");
87
+ if (fs.existsSync(pkgPath)) {
88
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
89
+ if (pkg.bin?.pi) return path.join(dir, "node_modules", PKG, pkg.bin.pi);
90
+ }
91
+ const parent = path.dirname(dir);
92
+ if (parent === dir) break;
93
+ dir = parent;
94
+ }
95
+ throw new Error("could not locate the pinned pi CLI (@earendil-works/pi-coding-agent)");
96
+ }
97
+
98
+ function ensureDirs() {
99
+ fs.mkdirSync(OMNI_DIR, { recursive: true, mode: 0o700 });
100
+ fs.mkdirSync(PI_AGENT_DIR, { recursive: true });
101
+ fs.mkdirSync(PI_EXT_DIR, { recursive: true });
102
+ }
103
+
104
+ // Merge our provider into the user's models.json without clobbering
105
+ // unrelated providers. Ours is authoritative: this file is how the
106
+ // product ships its provider. Other providers untouched.
107
+ function installModelsJson(gatewayUrl) {
108
+ let current = {};
109
+ try {
110
+ current = JSON.parse(fs.readFileSync(PI_MODELS_JSON, "utf8"));
111
+ } catch {
112
+ /* fresh install */
113
+ }
114
+ if (typeof current !== "object" || current === null) current = {};
115
+ current.providers = {
116
+ ...(current.providers || {}),
117
+ ...modelsConfig({ gatewayUrl }).providers,
118
+ };
119
+ fs.writeFileSync(PI_MODELS_JSON, JSON.stringify(current, null, 2) + "\n");
120
+ }
121
+
122
+ function installExtensions() {
123
+ const src = path.join(__dirname, "..", "assets", "extensions", "omnirush");
124
+ for (const f of fs.readdirSync(src)) {
125
+ // pi loads extensions/<dir>/index.ts only; that aggregator imports the
126
+ // sibling .ts modules and the shared auth.js — copy all loadable files.
127
+ if (!f.endsWith(".ts") && !f.endsWith(".js")) continue;
128
+ fs.copyFileSync(path.join(src, f), path.join(PI_EXT_DIR, f));
129
+ }
130
+ }
131
+
132
+ function agentGatewayUrl(auth) {
133
+ return resolveGatewayUrl(process.env, auth?.gatewayUrl);
134
+ }
135
+
136
+ function findBun() {
137
+ // Vendored per-platform bun first (bundled for linux-x64, darwin-arm64,
138
+ // darwin-x64, windows-x64), then PATH, then the standard single-user
139
+ // install location.
140
+ const runtimeRoot = path.resolve(__dirname, "..", ".runtime");
141
+ const plat = process.platform;
142
+ const arch = process.arch === "arm64" ? "aarch64" : "x64";
143
+ const candidates = [];
144
+ if (plat === "win32") {
145
+ candidates.push(
146
+ path.join(runtimeRoot, "bun-windows-x64", "bin", "bun.exe"),
147
+ );
148
+ for (const dir of (process.env.PATH || "").split(path.delimiter)) {
149
+ if (dir) candidates.push(path.join(dir, "bun.exe"));
150
+ }
151
+ } else {
152
+ if (plat === "linux" && arch === "x64") {
153
+ candidates.push(path.join(runtimeRoot, "bin", "bun"));
154
+ }
155
+ candidates.push(path.join(runtimeRoot, `bun-${plat}-${arch}`, "bin", "bun"));
156
+ for (const dir of (process.env.PATH || "").split(path.delimiter)) {
157
+ if (dir) candidates.push(path.join(dir, "bun"));
158
+ }
159
+ candidates.push(path.join(os.homedir(), ".bun", "bin", "bun"));
160
+ }
161
+ for (const c of candidates) {
162
+ try {
163
+ fs.accessSync(c, fs.constants.X_OK);
164
+ return c;
165
+ } catch {
166
+ /* next */
167
+ }
168
+ }
169
+ return null;
170
+ }
171
+
172
+ function runtimeFor(entry) {
173
+ // pi is Bun-first; its CLI imports node:module#enableCompileCache,
174
+ // which needs Node >= 22.8. Prefer bun when present, else enforce the
175
+ // Node floor with a clear message.
176
+ const bun = findBun();
177
+ if (bun) return { cmd: bun, args: [entry] };
178
+ const [major, minor] = process.versions.node.split(".").map(Number);
179
+ if (major > 22 || (major === 22 && minor >= 8)) {
180
+ return { cmd: process.execPath, args: [entry] };
181
+ }
182
+ console.error(
183
+ `omnirush: Node ${process.versions.node} is too old for the agent core ` +
184
+ "(need >= 22.8) and bun was not found. Install Node 22+ or bun.",
185
+ );
186
+ process.exit(1);
187
+ }
188
+
189
+ async function fetchJson(url, init = {}) {
190
+ const client = url.startsWith("http:") ? http : https;
191
+ return new Promise((resolve, reject) => {
192
+ const req = client.request(url, {
193
+ method: init.method || "GET",
194
+ headers: init.headers || {},
195
+ timeout: init.timeout ?? 10_000,
196
+ }, (res) => {
197
+ let body = "";
198
+ res.on("data", (c) => (body += c));
199
+ res.on("end", () => resolve({ status: res.statusCode, body }));
200
+ });
201
+ req.on("timeout", () => req.destroy(new Error("timeout")));
202
+ req.on("error", reject);
203
+ if (init.body) req.write(init.body);
204
+ req.end();
205
+ });
206
+ }
207
+
208
+ // --- auth commands ------------------------------------------------------
209
+
210
+ function deviceName() {
211
+ return (os.hostname() || "omnirush-cli").split(/[.\s]/)[0].slice(0, 64) || "omnirush-cli";
212
+ }
213
+
214
+ async function cmdLogin(argv) {
215
+ ensureDirs();
216
+ const origin = managerOrigin();
217
+ const noOpen = argv.includes("--no-open");
218
+ const authorize = await deviceAuthorize(origin, {
219
+ deviceName: deviceName(),
220
+ platform: process.platform,
221
+ fetchImpl: globalThis.fetch,
222
+ });
223
+
224
+ console.log("Omnirush device login");
225
+ console.log(` code: ${authorize.userCode}`);
226
+ console.log(` open: ${authorize.verificationUriComplete}`);
227
+ if (!noOpen && openBrowser(authorize.verificationUriComplete)) {
228
+ console.log(" (browser launched — approve there; if it did not open, use the URL above)");
229
+ } else {
230
+ console.log(" (open the URL above in a browser and approve)");
231
+ }
232
+
233
+ savePendingFlow(OMNI_DIR, {
234
+ device_code: authorize.deviceCode,
235
+ user_code: authorize.userCode,
236
+ verification_uri_complete: authorize.verificationUriComplete,
237
+ origin,
238
+ expires_at: authorize.expiresAt,
239
+ started_at: new Date().toISOString(),
240
+ });
241
+
242
+ let tokens;
243
+ try {
244
+ tokens = await pollForToken(origin, {
245
+ deviceCode: authorize.deviceCode,
246
+ intervalSec: authorize.intervalSec,
247
+ expiresAt: authorize.expiresAt,
248
+ fetchImpl: globalThis.fetch,
249
+ onWait: () => process.stdout.write("."),
250
+ });
251
+ } catch (error) {
252
+ clearPendingFlow(OMNI_DIR);
253
+ console.error(`\nomnirush login failed: ${error.message}`);
254
+ process.exit(1);
255
+ }
256
+ process.stdout.write("\n");
257
+ clearPendingFlow(OMNI_DIR);
258
+
259
+ saveAuth(OMNI_DIR, {
260
+ accessToken: tokens.accessToken,
261
+ refreshToken: tokens.refreshToken,
262
+ ...(tokens.gatewayUrl ? { gatewayUrl: tokens.gatewayUrl } : {}),
263
+ });
264
+ console.log(`Signed in. Tokens stored 0600 in ${path.join(OMNI_DIR, "auth.json")}`);
265
+
266
+ let me = null;
267
+ try {
268
+ me = await deviceMe(origin, { accessToken: tokens.accessToken, fetchImpl: globalThis.fetch });
269
+ } catch {
270
+ /* identity display is best effort */
271
+ }
272
+ if (me && typeof me === "object") {
273
+ console.log(`Identity: ${JSON.stringify(redactForDisplay(me))}`);
274
+ }
275
+ console.log("Run `omnirush` inside a project to start.");
276
+ }
277
+
278
+ async function cmdLogout() {
279
+ clearAuth(OMNI_DIR);
280
+ console.log(`Signed out. Credentials removed from ${OMNI_DIR}`);
281
+ }
282
+
283
+ async function fetchMeWithRefresh(auth) {
284
+ const origin = managerOrigin();
285
+ let me = await deviceMe(origin, { accessToken: auth.accessToken, fetchImpl: globalThis.fetch });
286
+ if (me !== null) return { me, auth };
287
+ // 401: single-flight refresh (rotate both), retry once.
288
+ const next = await refreshTokens(origin, { refreshToken: auth.refreshToken, fetchImpl: globalThis.fetch });
289
+ saveAuth(OMNI_DIR, next);
290
+ me = await deviceMe(origin, { accessToken: next.accessToken, fetchImpl: globalThis.fetch });
291
+ return { me, auth: next };
292
+ }
293
+
294
+ async function cmdWhoami() {
295
+ const auth = loadAuth(OMNI_DIR);
296
+ if (!auth) {
297
+ console.error("Omnirush: not signed in. Run `omnirush login` first.");
298
+ process.exit(1);
299
+ }
300
+ const { me } = await fetchMeWithRefresh(auth);
301
+ if (!me) {
302
+ console.error("Omnirush: credentials rejected — run `omnirush login` again.");
303
+ process.exit(1);
304
+ }
305
+ console.log(JSON.stringify(redactForDisplay(me), null, 2));
306
+ }
307
+
308
+ async function cmdDoctor() {
309
+ ensureDirs();
310
+ let healthy = true;
311
+ const ok = (label, good, detail) => {
312
+ if (!good) healthy = false;
313
+ console.log(`${good ? "ok " : "FAIL"} ${label}${detail ? " — " + detail : ""}`);
314
+ };
315
+
316
+ // Manager reachability: /v1/models must answer 401 (route live, auth enforced).
317
+ const origin = managerOrigin();
318
+ const gateway = agentGatewayUrl(loadAuth(OMNI_DIR));
319
+ try {
320
+ const res = await fetchJson(`${gateway}/models`, { timeout: 5000 });
321
+ ok("manager", res.status === 401 || res.status < 500, `${origin} -> HTTP ${res.status}`);
322
+ } catch (e) {
323
+ ok("manager", false, `${origin} unreachable (${e.message})`);
324
+ }
325
+
326
+ // Auth: signed in, or a login waiting for browser approval (ok-pending).
327
+ const auth = loadAuth(OMNI_DIR);
328
+ if (auth) {
329
+ try {
330
+ const { me } = await fetchMeWithRefresh(auth);
331
+ if (me) {
332
+ const label =
333
+ (me && typeof me === "object" && (me.device_name || me.email || me.user_id || me.id)) ||
334
+ "device recognized";
335
+ ok("auth", true, `signed in (${label})`);
336
+ } else {
337
+ ok("auth", false, "credentials rejected — run `omnirush login` again");
338
+ }
339
+ } catch (e) {
340
+ if (e?.name === "RefreshRejectedError") {
341
+ ok("auth", false, "session expired — run `omnirush login` again");
342
+ } else {
343
+ ok("auth", false, `cannot verify session (${e.message})`);
344
+ }
345
+ }
346
+ } else {
347
+ const pending = loadPendingFlow(OMNI_DIR);
348
+ if (pending && (!pending.expires_at || Date.now() < pending.expires_at)) {
349
+ ok("auth", true, `PENDING approval — open ${pending.verification_uri_complete}`);
350
+ console.log(" login completes automatically once you approve in the browser");
351
+ } else {
352
+ ok("auth", false, "not signed in — run `omnirush login`");
353
+ }
354
+ }
355
+
356
+ installModelsJson(gateway);
357
+ installExtensions();
358
+ ok("pi config", true, PI_MODELS_JSON);
359
+ ok("extensions", true, PI_EXT_DIR);
360
+ process.exit(healthy ? 0 : 1);
361
+ }
362
+
363
+ // --- agent run ----------------------------------------------------------
364
+
365
+ function requireAuth() {
366
+ const auth = loadAuth(OMNI_DIR);
367
+ if (auth) return auth;
368
+ const pending = loadPendingFlow(OMNI_DIR);
369
+ if (pending && (!pending.expires_at || Date.now() < pending.expires_at)) {
370
+ console.error("Omnirush: login pending — approve in the browser to finish:");
371
+ console.error(` ${pending.verification_uri_complete}`);
372
+ process.exit(1);
373
+ }
374
+ console.error("Omnirush: not signed in. Run `omnirush login` first.");
375
+ process.exit(1);
376
+ }
377
+
378
+ function childEnv(auth) {
379
+ const gateway = agentGatewayUrl(auth);
380
+ const origin = (process.env.OMNIRUSH_ORIGIN || "").trim().replace(/\/+$/, "") ||
381
+ gateway.replace(/\/+$/, "").replace(/\/v1$/, ""); // provider base -> manager origin
382
+ return {
383
+ OMNIRUSH_TOKEN: auth.accessToken,
384
+ OMNIRUSH_ACCESS_TOKEN: auth.accessToken,
385
+ OMNIRUSH_ORIGIN: origin,
386
+ OMNIRUSH_GATEWAY_URL: gateway,
387
+ OMNIRUSH_DIR: OMNI_DIR,
388
+ // pi resolves its config dir from this env var; keep the child pi on
389
+ // exactly the models.json/extensions bin.js just installed.
390
+ PI_CODING_AGENT_DIR: PI_AGENT_DIR,
391
+ };
392
+ }
393
+
394
+ async function cmdRun() {
395
+ ensureDirs();
396
+ const auth = requireAuth();
397
+ installModelsJson(agentGatewayUrl(auth));
398
+ installExtensions();
399
+ const entry = piEntry();
400
+ const { cmd, args } = runtimeFor(entry);
401
+ // Plain `omnirush` always runs the SOTA model (charter rule 1); user
402
+ // flags win when present.
403
+ const piArgs = withModelDefaults([], resolveModelDefaults(process.env));
404
+ const child = spawn(cmd, [...args, ...piArgs], {
405
+ stdio: "inherit",
406
+ env: { ...process.env, ...childEnv(auth) },
407
+ });
408
+ child.on("exit", (code, signal) => {
409
+ if (signal) process.kill(process.pid, signal);
410
+ else process.exit(code ?? 0);
411
+ });
412
+ }
413
+
414
+ async function cmdCollect(argv) {
415
+ ensureDirs();
416
+ const auth = requireAuth();
417
+ installExtensions();
418
+ const entry = path.join(__dirname, "..", "assets", "collect-once.ts");
419
+ const { cmd, args } = runtimeFor(entry);
420
+ const child = spawn(cmd, [...args, ...argv], {
421
+ stdio: "inherit",
422
+ env: { ...process.env, ...childEnv(auth) },
423
+ });
424
+ child.on("exit", (code, signal) => {
425
+ if (signal) process.kill(process.pid, signal);
426
+ else process.exit(code ?? 0);
427
+ });
428
+ }
429
+
430
+ const [, , ...argv] = process.argv;
431
+ const cmd = argv[0];
432
+ if (cmd === "login") await cmdLogin(argv.slice(1));
433
+ else if (cmd === "logout") await cmdLogout();
434
+ else if (cmd === "whoami") await cmdWhoami();
435
+ else if (cmd === "doctor") await cmdDoctor();
436
+ else if (cmd === "--version" || cmd === "-v") console.log("omnirush 0.2.0 (pi core)");
437
+ else if (cmd === "collect") await cmdCollect(argv.slice(1));
438
+ else if (cmd === undefined || cmd === "run") await cmdRun();
439
+ else {
440
+ // Forward everything (including argv[0] — e.g. `--help`, `-p prompt`,
441
+ // `--provider x`) to pi, defaulting the model the same way
442
+ // `omnirush run` does; user-specified --provider/--model are never
443
+ // duplicated and pi admin subcommands pass through untouched.
444
+ ensureDirs();
445
+ const auth = requireAuth();
446
+ installModelsJson(agentGatewayUrl(auth));
447
+ installExtensions();
448
+ const entry = piEntry();
449
+ const { cmd: bin, args } = runtimeFor(entry);
450
+ const piArgs = withModelDefaults(argv, resolveModelDefaults(process.env));
451
+ const child = spawn(bin, [...args, ...piArgs], {
452
+ stdio: "inherit",
453
+ env: { ...process.env, ...childEnv(auth) },
454
+ });
455
+ child.on("exit", (code) => process.exit(code ?? 0));
456
+ }
package/src/lib.js ADDED
@@ -0,0 +1,179 @@
1
+ // Omnirush CLI core logic — pure functions, no side effects, so node:test
2
+ // can cover them without spawning the agent.
3
+
4
+ export const DEFAULT_PROVIDER = "omnirush";
5
+ export const DEFAULT_MODEL = "gpt-6-astra";
6
+
7
+ // Manager origin the CLI talks to directly (public HTTPS, real cert —
8
+ // no helper proxy, no custom CA). The pi provider base URL is
9
+ // {origin}/v1; device-flow endpoints live directly under {origin}.
10
+ export const DEFAULT_ORIGIN = "https://omnirush.ai/omnirush";
11
+ export const DEFAULT_GATEWAY_URL = `${DEFAULT_ORIGIN}/v1`;
12
+
13
+ /**
14
+ * Gateway (provider base) URL resolution: OMNIRUSH_GATEWAY_URL wins,
15
+ * then OMNIRUSH_ORIGIN, then a gateway_url rotated in with the device
16
+ * tokens, then the shipped default.
17
+ */
18
+ export function resolveGatewayUrl(env = process.env, authGatewayUrl) {
19
+ const override = (env.OMNIRUSH_GATEWAY_URL || "").trim();
20
+ if (override) return override;
21
+ const origin = (env.OMNIRUSH_ORIGIN || "").trim().replace(/\/+$/, "");
22
+ if (origin) return `${origin}/v1`;
23
+ const rotated = (authGatewayUrl || "").trim();
24
+ if (rotated) return rotated.replace(/\/+$/, "").replace(/\/v1$/, "") + "/v1";
25
+ return DEFAULT_GATEWAY_URL;
26
+ }
27
+
28
+ // pi admin subcommands (dispatched on argv[0] inside pi). These reject our
29
+ // agent flags ("Unknown option --model for ..."), so forward them untouched.
30
+ const PI_SUBCOMMANDS = new Set([
31
+ "auth",
32
+ "install",
33
+ "remove",
34
+ "uninstall",
35
+ "update",
36
+ "list",
37
+ "config",
38
+ ]);
39
+
40
+ // pi flags that consume the next argv token as their value (mirror of
41
+ // parseArgs in pi's dist/cli/args.js, minus --provider/--model which are
42
+ // handled explicitly above the generic loop).
43
+ const VALUE_FLAGS = new Set([
44
+ "--mode",
45
+ "--api-key",
46
+ "--system-prompt",
47
+ "--append-system-prompt",
48
+ "--name",
49
+ "-n",
50
+ "--session",
51
+ "--session-id",
52
+ "--fork",
53
+ "--session-dir",
54
+ "--models",
55
+ "--tools",
56
+ "-t",
57
+ "--exclude-tools",
58
+ "-xt",
59
+ "--thinking",
60
+ "--export",
61
+ "--extension",
62
+ "-e",
63
+ "--skill",
64
+ "--prompt-template",
65
+ "--theme",
66
+ ]);
67
+
68
+ /**
69
+ * Model defaults for plain `omnirush` runs. OMNIRUSH_MODEL overrides the
70
+ * default model (e.g. to pin a thinking level like "gpt-6-astra:high").
71
+ */
72
+ export function resolveModelDefaults(env = process.env) {
73
+ return {
74
+ provider: DEFAULT_PROVIDER,
75
+ model: (env.OMNIRUSH_MODEL || "").trim() || DEFAULT_MODEL,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Prepend `--provider`/`--model` defaults for agent runs, without
81
+ * duplicating flags the user already specified. Consumption rules mirror
82
+ * pi's own parseArgs so a value token is never mistaken for a flag (e.g.
83
+ * `omnirush -p "how do I use --model flags"` must not suppress defaults).
84
+ *
85
+ * Admin subcommands (auth/install/...) and `--`-terminated positionals are
86
+ * respected: subcommands pass through untouched; scanning stops at `--`.
87
+ */
88
+ export function withModelDefaults(args, defaults) {
89
+ const provider = defaults?.provider ?? DEFAULT_PROVIDER;
90
+ const model = defaults?.model ?? DEFAULT_MODEL;
91
+ if (args.length > 0 && PI_SUBCOMMANDS.has(args[0])) {
92
+ return [...args];
93
+ }
94
+ let hasProvider = false;
95
+ let hasModel = false;
96
+ for (let i = 0; i < args.length; i++) {
97
+ const arg = args[i];
98
+ if (arg === "--") break;
99
+ if (arg === "--provider") {
100
+ // pi only applies --provider/--model when a value token follows; a
101
+ // bare flag falls through to pi's unknown-flag handling, so it does
102
+ // not count as user-specified here either.
103
+ if (i + 1 < args.length) {
104
+ hasProvider = true;
105
+ i++; // pi consumes the next token unconditionally
106
+ }
107
+ } else if (arg === "--model") {
108
+ if (i + 1 < args.length) {
109
+ hasModel = true;
110
+ i++;
111
+ }
112
+ } else if (arg.startsWith("--provider=")) {
113
+ hasProvider = true;
114
+ } else if (arg.startsWith("--model=")) {
115
+ hasModel = true;
116
+ } else if (arg === "--print" || arg === "-p") {
117
+ const next = args[i + 1];
118
+ if (
119
+ next !== undefined &&
120
+ !next.startsWith("@") &&
121
+ (!next.startsWith("-") || next.startsWith("---"))
122
+ ) {
123
+ i++;
124
+ }
125
+ } else if (arg === "--list-models") {
126
+ const next = args[i + 1];
127
+ if (next !== undefined && !next.startsWith("-") && !next.startsWith("@")) i++;
128
+ } else if (arg === "--use-theme" || arg === "--tui-mode") {
129
+ const next = args[i + 1];
130
+ if (next !== undefined && !next.startsWith("-")) i++;
131
+ } else if (VALUE_FLAGS.has(arg)) {
132
+ if (i + 1 < args.length) i++;
133
+ } else if (arg.startsWith("--")) {
134
+ // Unknown long flag: pi consumes the next token as its value when it
135
+ // is not another flag or an @file.
136
+ const next = args[i + 1];
137
+ if (next !== undefined && !next.startsWith("-") && !next.startsWith("@")) i++;
138
+ }
139
+ }
140
+ const out = [];
141
+ if (!hasProvider) out.push("--provider", provider);
142
+ if (!hasModel) out.push("--model", model);
143
+ return [...out, ...args];
144
+ }
145
+
146
+ /**
147
+ * The provider/models.json payload omnirush installs into pi. This is the
148
+ * product's shipped model catalog: SOTA only (charter rule 1). The served
149
+ * model is forced server-side by the gateway; the client SOTA guard stays
150
+ * on as verification.
151
+ *
152
+ * Hard-won compat fact: the codex backend rejects
153
+ * `max_output_tokens`, so the model sets
154
+ * `compat.supportsMaxOutputTokens: false` and ships no maxTokens — pi would
155
+ * otherwise clamp a default and break every request.
156
+ */
157
+ export function modelsConfig({ gatewayUrl } = {}) {
158
+ return {
159
+ providers: {
160
+ [DEFAULT_PROVIDER]: {
161
+ name: "Omnirush",
162
+ baseUrl: gatewayUrl ?? DEFAULT_GATEWAY_URL,
163
+ api: "openai-responses",
164
+ apiKey: "$OMNIRUSH_TOKEN",
165
+ models: [
166
+ {
167
+ id: DEFAULT_MODEL,
168
+ name: "GPT-6 Astra",
169
+ reasoning: true,
170
+ input: ["text", "image"],
171
+ cost: { input: 10, output: 50, cacheRead: 2.5, cacheWrite: 12.5 },
172
+ contextWindow: 400000,
173
+ compat: { supportsMaxOutputTokens: false },
174
+ },
175
+ ],
176
+ },
177
+ },
178
+ };
179
+ }