omnirush 0.1.1 → 0.3.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.
@@ -1,12 +1,24 @@
1
- // Downloads the platform bun runtime (v1.4.2) on install so `omnirush`
2
- // works immediately on linux / macOS / Windows.
1
+ // Postinstall runtime fetcher:
2
+ // 1. bun (v1.4.2) — required; the agent core runs on it.
3
+ // 2. fd (v10.5.0) — optional; the agent's file search + autocomplete.
4
+ // 3. rg (v15.2.0) — optional; the agent's content search.
5
+ //
6
+ // Binaries land in .runtime/bin/ (per-platform release packages ship the
7
+ // same layout prebuilt). The launcher copies fd/rg into the agent's
8
+ // managed-bin dir on every run, so the agent never attempts its own
9
+ // GitHub download at startup (which fails on restricted networks and
10
+ // repeats on every launch). fd/rg failures are warnings: search degrades
11
+ // gracefully without them. A bun failure stays fatal.
3
12
  import { execFileSync } from "node:child_process";
4
13
  import fs from "node:fs";
5
14
  import https from "node:https";
6
15
  import path from "node:path";
7
16
  import { fileURLToPath } from "node:url";
8
17
 
9
- const VERSION = "bun-v1.4.2";
18
+ const BUN_VERSION = "bun-v1.4.2";
19
+ // fd release tags are v-prefixed; ripgrep tags are bare ("15.2.0").
20
+ const FD_VERSION = "v10.5.0";
21
+ const RG_VERSION = "15.2.0";
10
22
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
23
  const ROOT = path.resolve(__dirname, "..");
12
24
  const RUNTIME = path.join(ROOT, ".runtime", "bin");
@@ -18,6 +30,10 @@ function platform() {
18
30
  return `${plat}-${arch}`;
19
31
  }
20
32
 
33
+ function exe(name) {
34
+ return name + (process.platform === "win32" ? ".exe" : "");
35
+ }
36
+
21
37
  function get(url, redirects = 0) {
22
38
  if (redirects > 5) throw new Error("too many redirects");
23
39
  return new Promise((resolve, reject) => {
@@ -57,22 +73,98 @@ function extractZip(buf, dest) {
57
73
  fs.rmSync(tmp, { force: true });
58
74
  }
59
75
 
60
- async function main() {
61
- // Skip when bundled (GitHub release packages ship .runtime prebuilt).
62
- const existing = path.join(RUNTIME, process.platform === "win32" ? "bun.exe" : "bun");
76
+ function extractTarGz(buf, dest, stamp) {
77
+ const tmp = path.join(dest, stamp);
78
+ fs.writeFileSync(tmp, buf);
79
+ // tar handles .tar.gz on linux/macOS; Windows 10+ ships bsdtar in
80
+ // System32 (the agent core relies on the same thing).
81
+ try {
82
+ execFileSync("tar", ["xzf", tmp, "-C", dest]);
83
+ } catch {
84
+ execFileSync("python3", ["-m", "tarfile", "-e", tmp, "-C", dest]);
85
+ }
86
+ fs.rmSync(tmp, { force: true });
87
+ }
88
+
89
+ function findFile(rootDir, fileName) {
90
+ const stack = [rootDir];
91
+ while (stack.length > 0) {
92
+ const dir = stack.pop();
93
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
94
+ const full = path.join(dir, entry.name);
95
+ if (entry.isFile() && entry.name === fileName) return full;
96
+ if (entry.isDirectory()) stack.push(full);
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+
102
+ async function fetchBun(plat) {
103
+ const existing = path.join(RUNTIME, exe("bun"));
63
104
  if (fs.existsSync(existing)) return;
64
- const plat = platform();
65
- const zipUrl = `https://github.com/oven-sh/bun/releases/download/${VERSION}/bun-${plat}.zip`;
105
+ const zipUrl = `https://github.com/oven-sh/bun/releases/download/${BUN_VERSION}/bun-${plat}.zip`;
66
106
  process.stdout.write(`omnirush: downloading agent runtime (bun for ${plat})...\n`);
67
107
  const buf = await get(zipUrl);
68
108
  fs.mkdirSync(RUNTIME, { recursive: true });
69
109
  extractZip(buf, ROOT);
70
- const exe = process.platform === "win32" ? "bun.exe" : "bun";
71
- const src = path.join(ROOT, `bun-${plat}`, exe);
110
+ const src = path.join(ROOT, `bun-${plat}`, exe("bun"));
72
111
  fs.copyFileSync(src, existing);
73
112
  if (process.platform !== "win32") fs.chmodSync(existing, 0o755);
74
113
  fs.rmSync(path.join(ROOT, `bun-${plat}`), { recursive: true, force: true });
75
114
  console.log("omnirush: runtime ready");
76
115
  }
77
116
 
117
+ // fd/rg release archives both nest the binary under a versioned directory
118
+ // (fd-v10.5.0-x86_64-unknown-linux-musl/fd, ripgrep-15.2.0-.../rg).
119
+ async function fetchSearchTool(name, version, repo, archiveName, plat) {
120
+ const existing = path.join(RUNTIME, exe(name));
121
+ if (fs.existsSync(existing)) return;
122
+ const isZip = plat === "windows-x64";
123
+ const asset = isZip ? `${archiveName}.zip` : `${archiveName}.tar.gz`;
124
+ const url = `https://github.com/${repo}/releases/download/${version}/${asset}`;
125
+ process.stdout.write(`omnirush: downloading ${name} (${plat})...\n`);
126
+ const buf = await get(url);
127
+ fs.mkdirSync(RUNTIME, { recursive: true });
128
+ const extractDir = path.join(RUNTIME, `extract-${name}`);
129
+ fs.mkdirSync(extractDir, { recursive: true });
130
+ if (isZip) extractZip(buf, extractDir);
131
+ else extractTarGz(buf, extractDir, asset);
132
+ const found = findFile(extractDir, exe(name));
133
+ if (!found) throw new Error(`${exe(name)} not found in ${asset}`);
134
+ fs.copyFileSync(found, existing);
135
+ if (process.platform !== "win32") fs.chmodSync(existing, 0o755);
136
+ fs.rmSync(extractDir, { recursive: true, force: true });
137
+ }
138
+
139
+ function fdAsset(plat) {
140
+ const arch = process.arch === "arm64" ? "aarch64" : "x86_64";
141
+ if (plat === "windows-x64") return `fd-${FD_VERSION}-${arch}-pc-windows-msvc`;
142
+ if (plat === "darwin-x64" || plat === "darwin-aarch64") return `fd-${FD_VERSION}-${arch}-apple-darwin`;
143
+ return `fd-${FD_VERSION}-${arch}-unknown-linux-musl`;
144
+ }
145
+
146
+ function rgAsset(plat) {
147
+ const arch = process.arch === "arm64" ? "aarch64" : "x86_64";
148
+ if (plat === "windows-x64") return `ripgrep-${RG_VERSION}-${arch}-pc-windows-msvc`;
149
+ if (plat === "darwin-x64" || plat === "darwin-aarch64") return `ripgrep-${RG_VERSION}-${arch}-apple-darwin`;
150
+ return `ripgrep-${RG_VERSION}-${arch}-unknown-linux-musl`;
151
+ }
152
+
153
+ async function main() {
154
+ const plat = platform();
155
+ await fetchBun(plat);
156
+ for (const [name, version, repo, archive] of [
157
+ ["fd", FD_VERSION, "sharkdp/fd", fdAsset(plat)],
158
+ ["rg", RG_VERSION, "BurntSushi/ripgrep", rgAsset(plat)],
159
+ ]) {
160
+ try {
161
+ await fetchSearchTool(name, version, repo, archive, plat);
162
+ } catch (error) {
163
+ // Optional tools: warn and continue (the launcher only installs what
164
+ // exists; the agent degrades to PATH lookup / built-in search).
165
+ console.warn(`omnirush: ${name} download failed (search may be degraded):`, error.message);
166
+ }
167
+ }
168
+ }
169
+
78
170
  main().catch((e) => { console.error("omnirush postinstall:", e.message); process.exit(1); });
package/src/bin.js CHANGED
@@ -18,10 +18,12 @@
18
18
  // OMNIRUSH_GATEWAY_URL provider base URL override ({origin}/v1)
19
19
  // OMNIRUSH_DIR state dir override (default ~/.omnirush)
20
20
  // OMNIRUSH_MODEL model override for plain runs (default
21
- // gpt-6-astra; user --model still wins)
21
+ // gpt-6-astra; gpt-5.6-sol also available; user
22
+ // --model still wins)
22
23
  //
23
- // Agent runs default to `--provider omnirush --model gpt-6-astra` (SOTA
24
- // only — charter rule 1). The sota extension watches every response,
24
+ // Agent runs default to `--provider omnirush --model gpt-6-astra`; the
25
+ // catalog also carries gpt-5.6-sol (`--model gpt-5.6-sol` or
26
+ // OMNIRUSH_MODEL=gpt-5.6-sol). The sota extension watches every response,
25
27
  // refreshes the device token single-flight on 401 (retry once), and
26
28
  // warns on stderr if the gateway serves a different model than
27
29
  // requested; `--strict-sota` makes that violation fatal (exit 3).
@@ -42,6 +44,7 @@ import {
42
44
  withModelDefaults,
43
45
  resolveModelDefaults,
44
46
  } from "./lib.js";
47
+ import { installManagedTools } from "./tools.js";
45
48
  import {
46
49
  clearAuth,
47
50
  deviceAuthorize,
@@ -99,6 +102,11 @@ function ensureDirs() {
99
102
  fs.mkdirSync(OMNI_DIR, { recursive: true, mode: 0o700 });
100
103
  fs.mkdirSync(PI_AGENT_DIR, { recursive: true });
101
104
  fs.mkdirSync(PI_EXT_DIR, { recursive: true });
105
+ try {
106
+ installManagedTools({ agentDir: PI_AGENT_DIR, runtimeRoot: path.resolve(__dirname, "..", ".runtime") });
107
+ } catch {
108
+ /* best effort — pi falls back to PATH, then its own downloader */
109
+ }
102
110
  }
103
111
 
104
112
  // Merge our provider into the user's models.json without clobbering
@@ -353,10 +361,78 @@ async function cmdDoctor() {
353
361
  }
354
362
  }
355
363
 
364
+ // Update check: npm latest vs this install (cached, once/day).
365
+ try {
366
+ const own = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")).version;
367
+ const cacheFile = path.join(OMNI_DIR, "update-check.json");
368
+ let latest = null;
369
+ let cache = {};
370
+ try { cache = JSON.parse(fs.readFileSync(cacheFile, "utf8")); } catch {}
371
+ if (cache.latest && Date.now() - cache.checked_at < 24 * 60 * 60 * 1000) {
372
+ latest = cache.latest;
373
+ } else {
374
+ const r = await fetchJson("https://registry.npmjs.org/omnirush/latest", { timeout: 5000 });
375
+ if (r.status === 200) {
376
+ latest = JSON.parse(r.body).version;
377
+ fs.writeFileSync(cacheFile, JSON.stringify({ latest, checked_at: Date.now() }));
378
+ }
379
+ }
380
+ if (latest && latest !== own) {
381
+ const cmp = (a, b) => {
382
+ const pa = a.split(".").map(Number), pb = b.split(".").map(Number);
383
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
384
+ const d = (pa[i] || 0) - (pb[i] || 0);
385
+ if (d) return d;
386
+ }
387
+ return 0;
388
+ };
389
+ if (cmp(latest, own) > 0) {
390
+ ok("update", true, `update available: ${latest} (installed ${own}) — run omnirush update`);
391
+ } else {
392
+ ok("update", true, `installed ${own} is newer than published ${latest} (pre-release)`);
393
+ }
394
+ } else if (latest) {
395
+ ok("update", true, `up to date (${own})`);
396
+ }
397
+ } catch {
398
+ /* update check is advisory only */
399
+ }
400
+
356
401
  installModelsJson(gateway);
357
402
  installExtensions();
358
- ok("pi config", true, PI_MODELS_JSON);
403
+ try {
404
+ installManagedTools({ agentDir: PI_AGENT_DIR, runtimeRoot: path.resolve(__dirname, "..", ".runtime") });
405
+ } catch {
406
+ /* best effort */
407
+ }
408
+ ok("agent config", true, PI_MODELS_JSON);
359
409
  ok("extensions", true, PI_EXT_DIR);
410
+ // Search tools: bundled fd/rg (or system ones) keep the agent off its
411
+ // GitHub download path, which fails on restricted networks.
412
+ const toolsRoot = path.join(PI_AGENT_DIR, "bin");
413
+ for (const tool of ["fd", "rg"]) {
414
+ const exe = process.platform === "win32" ? ".exe" : "";
415
+ const managed = path.join(toolsRoot, tool + exe);
416
+ const hasManaged = fs.existsSync(managed);
417
+ let viaPath = false;
418
+ if (!hasManaged) {
419
+ for (const dir of (process.env.PATH || "").split(path.delimiter)) {
420
+ if (!dir) continue;
421
+ try {
422
+ fs.accessSync(path.join(dir, tool + exe), fs.constants.X_OK);
423
+ viaPath = true;
424
+ break;
425
+ } catch {
426
+ /* next */
427
+ }
428
+ }
429
+ }
430
+ ok(
431
+ `search tool ${tool}`,
432
+ hasManaged || viaPath,
433
+ hasManaged ? managed : viaPath ? "found on PATH" : "missing — find/autocomplete degraded",
434
+ );
435
+ }
360
436
  process.exit(healthy ? 0 : 1);
361
437
  }
362
438
 
@@ -385,9 +461,13 @@ function childEnv(auth) {
385
461
  OMNIRUSH_ORIGIN: origin,
386
462
  OMNIRUSH_GATEWAY_URL: gateway,
387
463
  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.
464
+ // The agent resolves its config dir from <APP>_CODING_AGENT_DIR. The
465
+ // branding patch renames the app to omnirush, so set the omnirush
466
+ // spelling; the pi spelling stays for unpatched cores.
467
+ OMNIRUSH_CODING_AGENT_DIR: PI_AGENT_DIR,
390
468
  PI_CODING_AGENT_DIR: PI_AGENT_DIR,
469
+ // The agent core is a fork/rebrand: never phone home to pi.dev.
470
+ PI_TELEMETRY: "0",
391
471
  };
392
472
  }
393
473
 
@@ -433,7 +513,7 @@ if (cmd === "login") await cmdLogin(argv.slice(1));
433
513
  else if (cmd === "logout") await cmdLogout();
434
514
  else if (cmd === "whoami") await cmdWhoami();
435
515
  else if (cmd === "doctor") await cmdDoctor();
436
- else if (cmd === "--version" || cmd === "-v") console.log("omnirush 0.2.0 (pi core)");
516
+ else if (cmd === "--version" || cmd === "-v") console.log("omnirush 0.3.0");
437
517
  else if (cmd === "collect") await cmdCollect(argv.slice(1));
438
518
  else if (cmd === undefined || cmd === "run") await cmdRun();
439
519
  else {
package/src/lib.js CHANGED
@@ -67,7 +67,8 @@ const VALUE_FLAGS = new Set([
67
67
 
68
68
  /**
69
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").
70
+ * default model — any catalog id below, optionally with a thinking level
71
+ * like "gpt-6-astra:high" or "gpt-5.6-sol:xhigh".
71
72
  */
72
73
  export function resolveModelDefaults(env = process.env) {
73
74
  return {
@@ -76,6 +77,35 @@ export function resolveModelDefaults(env = process.env) {
76
77
  };
77
78
  }
78
79
 
80
+ // The product's shipped catalog: gpt-6-astra (default) and gpt-5.6-sol.
81
+ // Live-verified 2026-09-22 against the production gateway: GET /v1/models
82
+ // lists both ids and POST /v1/responses with model "gpt-5.6-sol" is served
83
+ // as gpt-5.6-sol (x-omnirush-model response header + the model named in
84
+ // every SSE frame), so the client may offer the switch honestly.
85
+ export const MODELS = [
86
+ {
87
+ id: "gpt-6-astra",
88
+ name: "GPT-6 Astra",
89
+ reasoning: true,
90
+ input: ["text", "image"],
91
+ cost: { input: 10, output: 50, cacheRead: 2.5, cacheWrite: 12.5 },
92
+ contextWindow: 400000,
93
+ compat: { supportsMaxOutputTokens: false },
94
+ },
95
+ {
96
+ id: "gpt-5.6-sol",
97
+ name: "GPT-5.6 Sol",
98
+ reasoning: true,
99
+ input: ["text", "image"],
100
+ // Sol list pricing is not published; pi's cost display therefore shows
101
+ // zeros for it (documented default when cost is omitted).
102
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
103
+ // Same codex backend family as astra; same context window.
104
+ contextWindow: 400000,
105
+ compat: { supportsMaxOutputTokens: false },
106
+ },
107
+ ];
108
+
79
109
  /**
80
110
  * Prepend `--provider`/`--model` defaults for agent runs, without
81
111
  * duplicating flags the user already specified. Consumption rules mirror
@@ -145,12 +175,13 @@ export function withModelDefaults(args, defaults) {
145
175
 
146
176
  /**
147
177
  * 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.
178
+ * product's shipped model catalog (MODELS above). The gateway allow-lists
179
+ * the same ids server-side; the client SOTA guard stays on as verification
180
+ * (it compares the model named in the response frames against the one
181
+ * requested).
151
182
  *
152
183
  * Hard-won compat fact: the codex backend rejects
153
- * `max_output_tokens`, so the model sets
184
+ * `max_output_tokens`, so every model sets
154
185
  * `compat.supportsMaxOutputTokens: false` and ships no maxTokens — pi would
155
186
  * otherwise clamp a default and break every request.
156
187
  */
@@ -162,18 +193,22 @@ export function modelsConfig({ gatewayUrl } = {}) {
162
193
  baseUrl: gatewayUrl ?? DEFAULT_GATEWAY_URL,
163
194
  api: "openai-responses",
164
195
  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
- ],
196
+ models: MODELS.map((model) => ({ ...model })),
176
197
  },
177
198
  },
178
199
  };
179
200
  }
201
+
202
+ /**
203
+ * Pure mapping of the bundled search-tool binaries for the launcher: pi
204
+ * looks for managed `fd`/`rg` in <agentDir>/bin first and downloads from
205
+ * GitHub when absent. We ship both binaries and the launcher places them
206
+ * there, so the agent never attempts (or repeats) a download.
207
+ */
208
+ export function managedToolTargets({ platform = process.platform, runtimeRoot } = {}) {
209
+ const ext = platform === "win32" ? ".exe" : "";
210
+ return [
211
+ { tool: "fd", source: `${runtimeRoot}/bin/fd${ext}`, dest: `bin/fd${ext}` },
212
+ { tool: "rg", source: `${runtimeRoot}/bin/rg${ext}`, dest: `bin/rg${ext}` },
213
+ ];
214
+ }
package/src/tools.js ADDED
@@ -0,0 +1,40 @@
1
+ // Managed search-tool install (fd, rg) for the agent core.
2
+ //
3
+ // pi checks <agentDir>/bin for managed fd/rg first and, when absent,
4
+ // downloads them from GitHub at every agent startup — on restricted
5
+ // networks that download fails and the attempt repeats on every launch,
6
+ // breaking the find tool and file autocomplete. We bundle the binaries
7
+ // (.runtime/bin, filled by postinstall or the release package) and the
8
+ // launcher places them exactly where pi looks. Best effort: any failure
9
+ // leaves the agent to its own PATH lookup / downloader.
10
+
11
+ import fs from "node:fs";
12
+ import path from "node:path";
13
+
14
+ import { managedToolTargets } from "./lib.js";
15
+
16
+ export function installManagedTools({
17
+ agentDir,
18
+ runtimeRoot,
19
+ platform = process.platform,
20
+ fsImpl = fs,
21
+ } = {}) {
22
+ const installed = [];
23
+ if (!agentDir || !runtimeRoot) return installed;
24
+ for (const target of managedToolTargets({ platform, runtimeRoot })) {
25
+ const dest = path.join(agentDir, target.dest);
26
+ if (fsImpl.existsSync(dest)) continue;
27
+ if (!fsImpl.existsSync(target.source)) continue;
28
+ fsImpl.mkdirSync(path.dirname(dest), { recursive: true });
29
+ fsImpl.copyFileSync(target.source, dest);
30
+ if (platform !== "win32") {
31
+ try {
32
+ fsImpl.chmodSync(dest, 0o755);
33
+ } catch {
34
+ /* best effort */
35
+ }
36
+ }
37
+ installed.push(target.tool);
38
+ }
39
+ return installed;
40
+ }