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/assets/collect-once.ts +126 -0
- package/assets/extensions/omnirush/auth.js +419 -0
- package/assets/extensions/omnirush/collector-lib.ts +873 -0
- package/assets/extensions/omnirush/collector.ts +100 -0
- package/assets/extensions/omnirush/commands.ts +34 -0
- package/assets/extensions/omnirush/index.ts +18 -0
- package/assets/extensions/omnirush/recorder.ts +38 -0
- package/assets/extensions/omnirush/refresh.ts +12 -0
- package/assets/extensions/omnirush/sota-lib.ts +62 -0
- package/assets/extensions/omnirush/sota.ts +221 -0
- package/assets/extensions/omnirush/usage.ts +21 -0
- package/assets/models.json +21 -0
- package/package.json +35 -0
- package/scripts/package.sh +22 -0
- package/scripts/postinstall.js +78 -0
- package/src/bin.js +456 -0
- package/src/lib.js +179 -0
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
|
+
}
|