omnirush 0.10.4 → 0.10.5

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.
@@ -20,7 +20,7 @@
20
20
  import { readSessionLedger, SESSION_LEDGER_FILE } from "./capture/workspace-sync";
21
21
  import { deviceMe, omniDir, resolveOrigin, sessionEndedMessage } from "./auth";
22
22
  import { sharedRefresher } from "./refresh";
23
- import { formatSyncLine, formatStatusLines } from "./status-lib";
23
+ import { formatStatusLines } from "./status-lib";
24
24
  import { formatModeStatus } from "./yolo-lib";
25
25
  import {
26
26
  formatDeviceLines,
@@ -129,7 +129,7 @@ export default function (pi: ExtensionAPI) {
129
129
  });
130
130
 
131
131
  pi.registerCommand("status", {
132
- description: "Show the Omnirush session status (identity, model, grant, session uploads)",
132
+ description: "Show the Omnirush session status (identity, model, grant)",
133
133
  handler: async (_args: string, ctx: any) => {
134
134
  const { me } = await fetchMe();
135
135
  const usage = gatewayUsage();
@@ -46,7 +46,7 @@
46
46
 
47
47
  import { createHash } from "node:crypto";
48
48
  import markSubagentSession from "./subagent-marker";
49
- import { createReadStream, readdirSync, readFileSync, rmSync } from "node:fs";
49
+ import { appendFileSync, createReadStream, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync } from "node:fs";
50
50
  import { readdir, stat } from "node:fs/promises";
51
51
  import os from "node:os";
52
52
  import path from "node:path";
@@ -483,10 +483,27 @@ export default function (pi: any) {
483
483
  const version = cliVersion();
484
484
  const engineVersion = (process.env.OMNIRUSH_ENGINE_VERSION || "").trim();
485
485
  const verbose = (process.env.OMNIRUSH_DEBUG || "").trim() === "1";
486
+ // Upload diagnostics never reach the terminal (they interleave with the
487
+ // TUI): warnings go to <state dir>/logs/session-upload.log (kept under
488
+ // ~1 MB), and OMNIRUSH_DEBUG=1 also prints everything to stderr.
489
+ const logFile = path.join(stateDir, "logs", "session-upload.log");
486
490
  const log = (level: "info" | "warn", message: string, attributes?: Record<string, unknown>) => {
487
491
  if (level !== "warn" && !verbose) return;
492
+ const detail = attributes && Object.keys(attributes).length > 0 ? ` ${JSON.stringify(attributes)}` : "";
493
+ const line = `${new Date().toISOString()} ${level} ${message.replace(/^OmniRush session upload: /, "")}${detail}\n`;
494
+ try {
495
+ mkdirSync(path.dirname(logFile), { recursive: true });
496
+ try {
497
+ if (statSync(logFile).size > 1_000_000) renameSync(logFile, `${logFile}.1`);
498
+ } catch {
499
+ /* no log yet */
500
+ }
501
+ appendFileSync(logFile, line);
502
+ } catch {
503
+ /* diagnostics only */
504
+ }
505
+ if (!verbose) return;
488
506
  try {
489
- const detail = attributes && Object.keys(attributes).length > 0 ? ` ${JSON.stringify(attributes)}` : "";
490
507
  process.stderr.write(`omnirush session upload: ${message}${detail}\n`);
491
508
  } catch {
492
509
  /* stderr may be closed late in shutdown */
@@ -80,7 +80,6 @@ export function formatStatusLines(input: StatusInput): string[] {
80
80
  lines.push("grant: no data yet (appears after the first request or via /usage)");
81
81
  }
82
82
 
83
- lines.push(formatSyncLine(input.sync));
84
83
  if (input.yolo) lines.push(input.yolo);
85
84
  lines.push(`manager: ${input.origin}`);
86
85
  lines.push(`version: ${input.version}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnirush",
3
- "version": "0.10.4",
3
+ "version": "0.10.5",
4
4
  "description": "Omnirush — free daily tokens for the most powerful coding model on earth.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -57,6 +57,29 @@ export const CORE_ENV_PATTERN = `/^PI_(${CORE_ONLY_ENV_NAMES.join("|")})$/`;
57
57
  // `from` must match the installed dist exactly; applied with replaceAll to
58
58
  // every dist/**/*.js. A `to` must never contain its own `from` (idempotency).
59
59
  export const REPLACEMENTS = [
60
+ // --- app name fallback: omnirush even when the core can't read piConfig ---
61
+ // (a package dir resolved elsewhere, e.g. a bun binary or a moved folder).
62
+ // This is the name in "To resume this session: <name> --session <id>".
63
+ {
64
+ label: "app name fallback (bundle)",
65
+ from: 'APP_NAME=piConfigName||"pi"',
66
+ to: 'APP_NAME=piConfigName||"omnirush"',
67
+ },
68
+ {
69
+ label: "app name fallback (dist)",
70
+ from: 'APP_NAME = piConfigName || "pi"',
71
+ to: 'APP_NAME = piConfigName || "omnirush"',
72
+ },
73
+ {
74
+ label: "app title fallback (bundle)",
75
+ from: 'APP_TITLE=piConfigName?APP_NAME:"\\u03C0"',
76
+ to: 'APP_TITLE=APP_NAME',
77
+ },
78
+ {
79
+ label: "app title fallback (dist)",
80
+ from: 'APP_TITLE = piConfigName ? APP_NAME : "\u03c0"',
81
+ to: 'APP_TITLE = APP_NAME',
82
+ },
60
83
  // --- project config folder: legacy <project>/.pi fallback -------------
61
84
  {
62
85
  label: "project config dir hook (bundle)",
@@ -450,6 +473,23 @@ export const REPLACEMENTS = [
450
473
  },
451
474
  ];
452
475
 
476
+ // Replace a file in one step: the launcher may brand on first run while
477
+ // another omnirush start is loading the same core files.
478
+ function writeAtomic(file, data) {
479
+ const tmp = `${file}.${process.pid}.branding`;
480
+ try {
481
+ fs.writeFileSync(tmp, data);
482
+ fs.renameSync(tmp, file);
483
+ } catch (error) {
484
+ try {
485
+ fs.rmSync(tmp, { force: true });
486
+ } catch {
487
+ /* nothing to clean */
488
+ }
489
+ throw error;
490
+ }
491
+ }
492
+
453
493
  function findPiRoot(startDir) {
454
494
  let dir = startDir;
455
495
  for (let i = 0; i < 8; i++) {
@@ -485,6 +525,15 @@ export function applyBrandingPatches(startDir, { version } = {}) {
485
525
  if (!piRoot) {
486
526
  return { piRoot: null, packageJsonPatched: false, applied: [], missing: [{ label: "core", reason: `${PKG} not installed` }] };
487
527
  }
528
+ return brandCoreDir(piRoot, { version });
529
+ }
530
+
531
+ /**
532
+ * Apply the rebrand to the core laid out at `piRoot` (its package.json,
533
+ * CHANGELOG.md and dist/): the installed copy, or the launcher's branded
534
+ * copy of a read-only install (src/bin.js).
535
+ */
536
+ export function brandCoreDir(piRoot, { version } = {}) {
488
537
 
489
538
  // 1. The supported rebrand hook (+ the product version: the banner and
490
539
  // --version of the core read their own package.json).
@@ -494,7 +543,7 @@ export function applyBrandingPatches(startDir, { version } = {}) {
494
543
  if (pkg.piConfig?.name !== BRAND || pkg.piConfig?.configDir !== BRAND_CONFIG_DIR || (version && pkg.version !== version)) {
495
544
  pkg.piConfig = { ...(pkg.piConfig || {}), name: BRAND, configDir: BRAND_CONFIG_DIR };
496
545
  if (version) pkg.version = version;
497
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
546
+ writeAtomic(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
498
547
  packageJsonPatched = true;
499
548
  }
500
549
 
@@ -510,7 +559,7 @@ export function applyBrandingPatches(startDir, { version } = {}) {
510
559
  /* absent */
511
560
  }
512
561
  if (current !== ours) {
513
- fs.writeFileSync(target, ours);
562
+ writeAtomic(target, ours);
514
563
  changelogReplaced = true;
515
564
  }
516
565
  } catch {
@@ -541,7 +590,7 @@ export function applyBrandingPatches(startDir, { version } = {}) {
541
590
  if (hits > 0) applied.push({ label: replacement.label, files: hits });
542
591
  else missing.push({ label: replacement.label, reason: "target string not found (already applied or core updated)" });
543
592
  }
544
- for (const file of dirty) fs.writeFileSync(file, contents.get(file));
593
+ for (const file of dirty) writeAtomic(file, contents.get(file));
545
594
  return { piRoot, packageJsonPatched, changelogReplaced, applied, missing };
546
595
  }
547
596
 
@@ -561,6 +610,8 @@ function main() {
561
610
  // <package>/engine -> the core (what process listings and the system
562
611
  // prompt's doc paths show; the launcher re-creates it when missing).
563
612
  ensureEngineLink(repoRoot, report.piRoot);
613
+ // The launcher's first-run check (src/bin.js ensureBranded) reads this.
614
+ if (version) fs.writeFileSync(path.join(report.piRoot, ".omnirush-branded"), version + "\n");
564
615
  const patched = report.packageJsonPatched ? "package.json + " : "";
565
616
  const notes = report.changelogReplaced ? " + release notes" : "";
566
617
  console.log(
package/src/bin.js CHANGED
@@ -57,6 +57,7 @@ import { installManagedTools } from "./tools.js";
57
57
  import { engineEnvAliases, ensureEngineLink, migrateAgentDir, projectConfigDirName, resolveAgentDir } from "./compat.js";
58
58
  import { customSessionDir, moveLegacySubagentSessions } from "./sessions.js";
59
59
  import { ensureRuntime, managedBunPath } from "../scripts/postinstall.js";
60
+ import { applyBrandingPatches, brandCoreDir } from "../scripts/brand-engine.js";
60
61
  import {
61
62
  clearAuth,
62
63
  deviceAuthorize,
@@ -150,8 +151,106 @@ function piPackage() {
150
151
  let ENGINE_LINK_DIR = null;
151
152
  let ENGINE_ENTRY = null;
152
153
 
154
+ // Written into the core's folder once it is branded for this omnirush
155
+ // version. Newer npm skips install scripts by default, so the postinstall
156
+ // branding may never have run: the launcher then brands on first run.
157
+ const BRAND_STAMP = ".omnirush-branded";
158
+
159
+ function brandedStamp(dir, version) {
160
+ try {
161
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8"));
162
+ if (pkg?.piConfig?.name === "omnirush" && fs.readFileSync(path.join(dir, BRAND_STAMP), "utf8").trim() === version) {
163
+ return { dir, pkg };
164
+ }
165
+ } catch {
166
+ /* not branded (yet) */
167
+ }
168
+ return null;
169
+ }
170
+
171
+ /**
172
+ * A core folder we cannot write (a root-owned global prefix installed
173
+ * without its scripts): run a branded copy of its runtime files instead,
174
+ * <state dir>/engine/<version> (package.json, release notes, README, the
175
+ * runtime bundle with its themes, assets and export template; docs,
176
+ * examples and node_modules are links to the installed ones). Made
177
+ * once per version, in one step (a temporary folder renamed into place).
178
+ */
179
+ function brandedCopy(found, version) {
180
+ const dir = path.join(OMNI_DIR, "engine", version);
181
+ const ready = brandedStamp(dir, version);
182
+ if (ready) return ready;
183
+ const tmp = `${dir}.${process.pid}.tmp`;
184
+ try {
185
+ fs.rmSync(tmp, { recursive: true, force: true });
186
+ fs.mkdirSync(tmp, { recursive: true });
187
+ for (const name of ["package.json", "CHANGELOG.md", "README.md"]) {
188
+ if (fs.existsSync(path.join(found.dir, name))) fs.writeFileSync(path.join(tmp, name), fs.readFileSync(path.join(found.dir, name)));
189
+ }
190
+ // What the runtime bundle reads (the same set the 1.0.0 build ships).
191
+ // Copied by hand: the source is read-only, and its modes must not come along.
192
+ const copyTree = (from, to) => {
193
+ fs.mkdirSync(to, { recursive: true });
194
+ for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
195
+ const src = path.join(from, entry.name);
196
+ if (entry.isDirectory()) copyTree(src, path.join(to, entry.name));
197
+ else if (entry.isFile() && !entry.name.endsWith(".map") && !entry.name.endsWith(".d.ts")) {
198
+ fs.writeFileSync(path.join(to, entry.name), fs.readFileSync(src));
199
+ }
200
+ }
201
+ };
202
+ for (const part of [["bundle"], ["modes", "interactive", "theme"], ["modes", "interactive", "assets"], ["core", "export-html"]]) {
203
+ const from = path.join(found.dir, "dist", ...part);
204
+ if (fs.existsSync(from)) copyTree(from, path.join(tmp, "dist", ...part));
205
+ }
206
+ for (const name of ["node_modules", "docs", "examples"]) {
207
+ const target = path.join(found.dir, name);
208
+ if (!fs.existsSync(target)) continue;
209
+ if (process.platform === "win32") fs.symlinkSync(target, path.join(tmp, name), "junction");
210
+ else fs.symlinkSync(target, path.join(tmp, name), "dir");
211
+ }
212
+ brandCoreDir(tmp, { version });
213
+ fs.writeFileSync(path.join(tmp, BRAND_STAMP), version + "\n");
214
+ fs.rmSync(dir, { recursive: true, force: true });
215
+ try {
216
+ fs.renameSync(tmp, dir);
217
+ } catch (error) {
218
+ // A concurrent start may have finished first.
219
+ if (!brandedStamp(dir, version)) throw error;
220
+ }
221
+ return brandedStamp(dir, version);
222
+ } catch (error) {
223
+ if (process.env.OMNIRUSH_DEBUG === "1") process.stderr.write(`omnirush: branded copy failed: ${error?.stack || error}\n`);
224
+ return null;
225
+ } finally {
226
+ try {
227
+ fs.rmSync(tmp, { recursive: true, force: true });
228
+ } catch {
229
+ /* best effort */
230
+ }
231
+ }
232
+ }
233
+
234
+ function ensureBranded(found) {
235
+ const version = ownVersion();
236
+ const done = brandedStamp(found.dir, version);
237
+ if (done) return done;
238
+ try {
239
+ applyBrandingPatches(path.resolve(__dirname, ".."), { version });
240
+ fs.writeFileSync(path.join(found.dir, BRAND_STAMP), version + "\n");
241
+ return { dir: found.dir, pkg: JSON.parse(fs.readFileSync(path.join(found.dir, "package.json"), "utf8")) };
242
+ } catch (error) {
243
+ // Read-only install: a branded copy in the state dir.
244
+ const copy = brandedCopy(found, version);
245
+ if (copy) return copy;
246
+ process.stderr.write(`omnirush: could not finish setup (${error?.code || error?.message || error}); reinstall with: npm install -g omnirush@latest\n`);
247
+ return found;
248
+ }
249
+ }
250
+
153
251
  function piEntry() {
154
- const found = piPackage();
252
+ let found = piPackage();
253
+ if (found?.pkg?.bin?.pi) found = ensureBranded(found);
155
254
  if (found?.pkg?.bin?.pi) {
156
255
  const dir = ensureEngineLink(path.resolve(__dirname, ".."), found.dir);
157
256
  ENGINE_LINK_DIR = dir === path.resolve(found.dir) ? null : dir;
@@ -755,7 +854,8 @@ async function cmdDoctor() {
755
854
  // branding and shows pi.dev "Update Available" banners (Foxtran's
756
855
  // 0.73.1 report). Advisory WARN with the fix; exit code unaffected.
757
856
  {
758
- const found = piPackage();
857
+ let found = piPackage();
858
+ if (found?.pkg?.bin?.pi) found = ensureBranded(found); // the copy that runs (a read-only install's branded copy)
759
859
  const coreVersion = String(found?.pkg?.version ?? "");
760
860
  const own = ownVersion();
761
861
  if (!coreVersion) {
@@ -1049,7 +1149,6 @@ Usage:
1049
1149
  omnirush logout forget the stored device tokens
1050
1150
  omnirush whoami show the signed-in identity
1051
1151
  omnirush doctor check auth, manager, runtime, agent config
1052
- omnirush upload [--dir PATH] upload a one-shot workspace snapshot to omnirush.ai
1053
1152
  omnirush update update omnirush (npm install -g omnirush@latest)
1054
1153
  omnirush --version show the omnirush version
1055
1154
 
@@ -1077,7 +1176,7 @@ Options:
1077
1176
  Session commands (inside the agent):
1078
1177
  /grant today's token grant and usage
1079
1178
  /usage detailed usage from the manager (counters + devices)
1080
- /status session status (identity, model, grant, session uploads)
1179
+ /status session status (identity, model, grant)
1081
1180
  /mcp MCP server state (/mcp reconnect [server])
1082
1181
  /subagents model and effort of the sub-agents (/subagents model <id|same>,
1083
1182
  /subagents effort <level|same>; saved for the next runs)
@@ -1204,6 +1303,15 @@ async function runCore(args) {
1204
1303
 
1205
1304
  const [, , ...argv] = process.argv;
1206
1305
  const cmd = argv[0];
1306
+ // First run after an install whose scripts were skipped: brand the core and
1307
+ // make the engine link now, whatever command runs (cheap once stamped).
1308
+ {
1309
+ const core = piPackage();
1310
+ if (core?.pkg?.bin?.pi) {
1311
+ const branded = ensureBranded(core);
1312
+ ensureEngineLink(path.resolve(__dirname, ".."), branded.dir);
1313
+ }
1314
+ }
1207
1315
  if (cmd === "-h" || cmd === "--help" || cmd === "help") printHelp();
1208
1316
  else if (cmd === "login") await cmdLogin(argv.slice(1));
1209
1317
  else if (cmd === "logout") await cmdLogout();