omnirush 0.3.3 → 0.4.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.
@@ -151,6 +151,83 @@ export function formatGrantLines({
151
151
  return lines;
152
152
  }
153
153
 
154
+ /**
155
+ * Exact ints for /usage, rendered with both the compact human form and the
156
+ * full digits (the full number is what support asks for).
157
+ */
158
+ function tokenLine(label: string, value: unknown): string | null {
159
+ const n = intOr(value);
160
+ if (n === null) return null;
161
+ return `${label}: ${formatTokenCount(n)} (${n.toLocaleString("en-US")})`;
162
+ }
163
+
164
+ /**
165
+ * The detailed /usage block from a /device/me payload: grant date, token
166
+ * limit, used/reserved/remaining. Every field is optional server-side
167
+ * (early in a grant day only grant_date may be present), so lines appear
168
+ * only when the data exists.
169
+ */
170
+ export function formatUsageDetails({ me }: { me: any }): string[] {
171
+ const u = me?.usage ?? null;
172
+ if (!u || typeof u !== "object") return ["no usage reported by the manager yet"];
173
+ const lines: string[] = [];
174
+ if (u.grant_date) lines.push(`grant date: ${u.grant_date}`);
175
+ let counters = 0;
176
+ for (const [label, key] of [
177
+ ["token limit", "token_limit"],
178
+ ["used", "used_tokens"],
179
+ ["reserved", "reserved_tokens"],
180
+ ["remaining", "remaining_tokens"],
181
+ ] as const) {
182
+ const line = tokenLine(label, u[key]);
183
+ if (line) {
184
+ lines.push(line);
185
+ counters += 1;
186
+ }
187
+ }
188
+ if (counters === 0) {
189
+ lines.push("no token counters reported yet (they appear with the first settled request)");
190
+ }
191
+ return lines;
192
+ }
193
+
194
+ /**
195
+ * One-line /usage summary, e.g.
196
+ * "997.2M of 1.0B tokens remaining (used 2.8M) — 2026-09-23".
197
+ * Falls back to whatever subset is present; empty string with no data.
198
+ */
199
+ export function formatUsageSummary(me: any): string {
200
+ const u = me?.usage ?? null;
201
+ if (!u || typeof u !== "object") return "";
202
+ const limit = intOr(u.token_limit);
203
+ const used = intOr(u.used_tokens);
204
+ const reserved = intOr(u.reserved_tokens);
205
+ const remaining = intOr(u.remaining_tokens);
206
+ const parts: string[] = [];
207
+ if (remaining !== null && limit !== null) {
208
+ parts.push(`${formatTokenCount(remaining)} of ${formatTokenCount(limit)} tokens remaining`);
209
+ } else if (used !== null) {
210
+ parts.push(`${formatTokenCount(used)} tokens used`);
211
+ }
212
+ if (reserved !== null && reserved > 0) parts.push(`${formatTokenCount(reserved)} reserved`);
213
+ if (u.grant_date) parts.push(String(u.grant_date));
214
+ return parts.join(" — ");
215
+ }
216
+
217
+ /** Device list lines for /usage and /status (no credentials in me payloads). */
218
+ export function formatDeviceLines(me: any): string[] {
219
+ const devices = Array.isArray(me?.devices) ? me.devices : [];
220
+ if (devices.length === 0) return [];
221
+ const lines = devices.map((device: any, index: number) => {
222
+ const name = typeof device?.device_name === "string" ? device.device_name : "device";
223
+ const platform = typeof device?.platform === "string" ? ` (${device.platform})` : "";
224
+ const seen = typeof device?.last_seen_at === "string" ? ` last seen ${device.last_seen_at}` : "";
225
+ const revoked = device?.revoked_at ? " [revoked]" : "";
226
+ return ` ${index + 1}. ${name}${platform}${seen}${revoked}`;
227
+ });
228
+ return [`devices signed in: ${devices.length}`, ...lines];
229
+ }
230
+
154
231
  /** One-line footer summary for the TUI status area, or null with no data. */
155
232
  export function formatGrantStatusLine(
156
233
  usage: Record<string, string> | null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnirush",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "description": "Omnirush \u2014 free daily tokens for the most powerful coding model on earth.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -13,6 +13,7 @@
13
13
  "scripts": {
14
14
  "test": "node --test",
15
15
  "test:bun": "if [ -x .runtime/bin/bun ]; then .runtime/bin/bun test test/; else bun test test/; fi",
16
+ "build:mcp": "node scripts/build-mcp-bundle.js",
16
17
  "postinstall": "node scripts/patch-pi-branding.js && node scripts/postinstall.js"
17
18
  },
18
19
  "dependencies": {
@@ -32,5 +33,9 @@
32
33
  "assets",
33
34
  "scripts",
34
35
  "README.md"
35
- ]
36
+ ],
37
+ "devDependencies": {
38
+ "@modelcontextprotocol/sdk": "^1.30.0",
39
+ "esbuild": "^0.28.2"
40
+ }
36
41
  }
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ // Bundle the MCP client SDK (@modelcontextprotocol/sdk) into a single
3
+ // self-contained ESM file committed at
4
+ // assets/extensions/omnirush/mcp-client.bundle.js.
5
+ //
6
+ // Why a committed bundle: the omnirush extension directory is copied to
7
+ // ~/.pi/agent/extensions/omnirush/ and its files can only import
8
+ // siblings — the jiti loader that runs pi extensions has no npm
9
+ // resolution there, and the published npm package ships no node_modules.
10
+ // The bundle ships in the repo, so CI and `npm pack` never need the
11
+ // toolchain; regenerate only when bumping the SDK:
12
+ //
13
+ // npm run build:mcp
14
+ //
15
+ // The output is deterministic for a given SDK version (esbuild, fixed
16
+ // entry, no code splitting) so diffs stay reviewable.
17
+
18
+ import { build } from "esbuild";
19
+ import path from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+
22
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
23
+ const entry = path.join(root, "dev", "mcp-bundle-entry.js");
24
+ const outfile = path.join(root, "assets", "extensions", "omnirush", "mcp-client.bundle.js");
25
+
26
+ await build({
27
+ entryPoints: [entry],
28
+ outfile,
29
+ bundle: true,
30
+ platform: "node",
31
+ format: "esm",
32
+ target: "node18",
33
+ // Node builtins stay external (real `node:*` imports in the output);
34
+ // everything else — including cross-spawn — is inlined.
35
+ external: ["node:*"],
36
+ minify: false,
37
+ sourcemap: false,
38
+ legalComments: "none",
39
+ logLevel: "info",
40
+ banner: {
41
+ js: [
42
+ "// GENERATED FILE — do not edit by hand.",
43
+ "// Regenerate with: npm run build:mcp",
44
+ "// Bundled from @modelcontextprotocol/sdk (devDependency) via esbuild.",
45
+ ].join("\n"),
46
+ },
47
+ });
@@ -93,6 +93,33 @@ export const REPLACEMENTS = [
93
93
  from: "Update pi only (self works as alias to pi)",
94
94
  to: "Update the agent only (self works as an alias)",
95
95
  },
96
+ // Belt-and-braces against the pi.dev update banner (Foxtran's 0.73.1
97
+ // report: a stale core showed "Update Available — pi.dev/changelog"
98
+ // even though PI_OFFLINE should suppress the check). PI_OFFLINE=1 in
99
+ // the spawn env stops the check; these rewrites make the banner
100
+ // UNRENDERABLE even where PI_OFFLINE is absent (direct pi spawns,
101
+ // older cores): showNewVersionNotification returns before its body
102
+ // (the parameter is renamed so the replacement stays idempotent and
103
+ // never re-matches), and the pi.dev changelog URL is blanked. The
104
+ // parameter spelling differs between the standalone dist (readable)
105
+ // and the minified runtime bundle chunk, hence the two variants; a
106
+ // future core that renames the parameter just reports "missing"
107
+ // (fail-soft) and we would re-derive the target.
108
+ {
109
+ label: "TUI update banner (standalone dist)",
110
+ from: "showNewVersionNotification(release) {",
111
+ to: "showNewVersionNotification(_release) { return;",
112
+ },
113
+ {
114
+ label: "TUI update banner (runtime bundle chunk)",
115
+ from: "showNewVersionNotification(release2){",
116
+ to: "showNewVersionNotification(_r){return;",
117
+ },
118
+ {
119
+ label: "pi.dev changelog URL",
120
+ from: '"https://pi.dev/changelog"',
121
+ to: '""',
122
+ },
96
123
  ];
97
124
 
98
125
  function findPiRoot(startDir) {
package/src/bin.js CHANGED
@@ -79,7 +79,7 @@ function managerOrigin() {
79
79
  );
80
80
  }
81
81
 
82
- function piEntry() {
82
+ function piPackage() {
83
83
  // The pi package is Bun-first and its "exports" map blocks plain Node
84
84
  // resolution, so locate it by path: walk up from our own package root
85
85
  // to the nearest node_modules containing it.
@@ -88,13 +88,22 @@ function piEntry() {
88
88
  for (let i = 0; i < 6; i++) {
89
89
  const pkgPath = path.join(dir, "node_modules", PKG, "package.json");
90
90
  if (fs.existsSync(pkgPath)) {
91
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
92
- if (pkg.bin?.pi) return path.join(dir, "node_modules", PKG, pkg.bin.pi);
91
+ try {
92
+ return { dir: path.dirname(pkgPath), pkg: JSON.parse(fs.readFileSync(pkgPath, "utf8")) };
93
+ } catch {
94
+ return null;
95
+ }
93
96
  }
94
97
  const parent = path.dirname(dir);
95
98
  if (parent === dir) break;
96
99
  dir = parent;
97
100
  }
101
+ return null;
102
+ }
103
+
104
+ function piEntry() {
105
+ const found = piPackage();
106
+ if (found?.pkg?.bin?.pi) return path.join(found.dir, found.pkg.bin.pi);
98
107
  throw new Error("could not locate the pinned pi CLI (@earendil-works/pi-coding-agent)");
99
108
  }
100
109
 
@@ -421,6 +430,24 @@ async function cmdDoctor() {
421
430
  }
422
431
  ok("agent config", true, PI_MODELS_JSON);
423
432
  ok("extensions", true, PI_EXT_DIR);
433
+ // Agent core version: a stale core predates the omnirush provider and
434
+ // branding and shows pi.dev "Update Available" banners (Foxtran's
435
+ // 0.73.1 report). Advisory WARN with the fix; exit code unaffected.
436
+ {
437
+ const found = piPackage();
438
+ const coreVersion = String(found?.pkg?.version ?? "");
439
+ const own = ownVersion();
440
+ if (!coreVersion) {
441
+ console.log(`WARN agent core — pinned agent package not found (broken install). Fix: omnirush update`);
442
+ } else if (coreVersion === own) {
443
+ ok("agent core", true, `v${coreVersion}`);
444
+ } else {
445
+ console.log(
446
+ `WARN agent core — installed core is v${coreVersion}, this omnirush expects v${own} ` +
447
+ "(stale install: pi.dev update banners, missing omnirush provider). Fix: omnirush update",
448
+ );
449
+ }
450
+ }
424
451
  // Search tools: bundled fd/rg (or system ones) keep the agent off its
425
452
  // GitHub download path, which fails on restricted networks.
426
453
  const toolsRoot = path.join(PI_AGENT_DIR, "bin");
@@ -465,6 +492,14 @@ function requireAuth() {
465
492
  process.exit(1);
466
493
  }
467
494
 
495
+ function ownVersion() {
496
+ try {
497
+ return JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")).version;
498
+ } catch {
499
+ return "unknown";
500
+ }
501
+ }
502
+
468
503
  function childEnv(auth) {
469
504
  const gateway = agentGatewayUrl(auth);
470
505
  const origin = (process.env.OMNIRUSH_ORIGIN || "").trim().replace(/\/+$/, "") ||
@@ -475,12 +510,19 @@ function childEnv(auth) {
475
510
  OMNIRUSH_ORIGIN: origin,
476
511
  OMNIRUSH_GATEWAY_URL: gateway,
477
512
  OMNIRUSH_DIR: OMNI_DIR,
513
+ // /status shows the CLI version; extensions have no package.json of
514
+ // their own in ~/.pi, so the launcher hands it over.
515
+ OMNIRUSH_VERSION: ownVersion(),
478
516
  // The agent resolves its config dir from <APP>_CODING_AGENT_DIR. The
479
517
  // branding patch renames the app to omnirush, so set the omnirush
480
518
  // spelling; the pi spelling stays for unpatched cores.
481
519
  OMNIRUSH_CODING_AGENT_DIR: PI_AGENT_DIR,
482
520
  PI_CODING_AGENT_DIR: PI_AGENT_DIR,
483
521
  // The agent core is a fork/rebrand: never phone home to pi.dev.
522
+ // PI_OFFLINE kills startup update checks (the "Update Available —
523
+ // pi.dev/changelog" banner), package updates, and install/update
524
+ // telemetry; PI_TELEMETRY stays for cores that predate PI_OFFLINE.
525
+ PI_OFFLINE: "1",
484
526
  PI_TELEMETRY: "0",
485
527
  };
486
528
  }
@@ -527,7 +569,7 @@ if (cmd === "login") await cmdLogin(argv.slice(1));
527
569
  else if (cmd === "logout") await cmdLogout();
528
570
  else if (cmd === "whoami") await cmdWhoami();
529
571
  else if (cmd === "doctor") await cmdDoctor();
530
- else if (cmd === "--version" || cmd === "-v") console.log("omnirush 0.3.3");
572
+ else if (cmd === "--version" || cmd === "-v") console.log(`omnirush ${ownVersion()}`);
531
573
  else if (cmd === "collect") await cmdCollect(argv.slice(1));
532
574
  else if (cmd === undefined || cmd === "run") await cmdRun();
533
575
  else {