omegon 0.6.3 → 0.6.4

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.
Files changed (69) hide show
  1. package/README.md +12 -10
  2. package/bin/omegon.mjs +40 -0
  3. package/bin/pi.mjs +5 -26
  4. package/extensions/00-secrets/index.ts +146 -39
  5. package/extensions/01-auth/auth.ts +1 -1
  6. package/extensions/01-auth/index.ts +3 -3
  7. package/extensions/auto-compact.ts +1 -1
  8. package/extensions/bootstrap/deps.ts +42 -0
  9. package/extensions/bootstrap/index.ts +326 -110
  10. package/extensions/chronos/index.ts +1 -1
  11. package/extensions/cleave/dispatcher.ts +6 -6
  12. package/extensions/cleave/index.ts +6 -6
  13. package/extensions/cleave/planner.ts +1 -1
  14. package/extensions/cleave/worktree.ts +1 -1
  15. package/extensions/core-renderers.ts +24 -84
  16. package/extensions/dashboard/footer.ts +184 -40
  17. package/extensions/dashboard/git.ts +2 -2
  18. package/extensions/dashboard/index.ts +4 -4
  19. package/extensions/dashboard/overlay-data.ts +5 -5
  20. package/extensions/dashboard/overlay.ts +5 -5
  21. package/extensions/dashboard/render-utils.ts +1 -1
  22. package/extensions/dashboard/types.ts +15 -0
  23. package/extensions/defaults.ts +4 -12
  24. package/extensions/design-tree/dashboard-state.ts +6 -6
  25. package/extensions/design-tree/design-card.ts +3 -3
  26. package/extensions/design-tree/index.ts +64 -44
  27. package/extensions/design-tree/types.ts +4 -2
  28. package/extensions/distill.ts +1 -1
  29. package/extensions/effort/index.ts +137 -10
  30. package/extensions/lib/model-routing.ts +304 -32
  31. package/extensions/lib/operator-fallback.ts +1 -1
  32. package/extensions/lib/operator-profile.ts +1 -1
  33. package/extensions/lib/provider-env.ts +163 -0
  34. package/extensions/{sci-ui.ts → lib/sci-ui.ts} +119 -2
  35. package/extensions/{shared-state.ts → lib/shared-state.ts} +13 -9
  36. package/extensions/lib/slash-command-bridge.ts +1 -1
  37. package/extensions/{types.d.ts → lib/types.d.ts} +3 -3
  38. package/extensions/local-inference/index.ts +1 -1
  39. package/extensions/mcp-bridge/index.ts +1 -1
  40. package/extensions/model-budget.ts +10 -10
  41. package/extensions/offline-driver.ts +11 -4
  42. package/extensions/openspec/archive-gate.ts +1 -1
  43. package/extensions/openspec/branch-cleanup.ts +1 -1
  44. package/extensions/openspec/dashboard-state.ts +3 -3
  45. package/extensions/openspec/index.ts +5 -5
  46. package/extensions/project-memory/factstore.ts +5 -11
  47. package/extensions/project-memory/index.ts +48 -34
  48. package/extensions/project-memory/package.json +1 -1
  49. package/extensions/project-memory/sci-renderers.ts +1 -1
  50. package/extensions/render/index.ts +1 -1
  51. package/extensions/session-log.ts +1 -1
  52. package/extensions/spinner-verbs.ts +1 -1
  53. package/extensions/style.ts +1 -1
  54. package/extensions/terminal-title.ts +3 -3
  55. package/extensions/tool-profile/index.ts +1 -1
  56. package/extensions/vault/index.ts +1 -1
  57. package/extensions/version-check.ts +13 -9
  58. package/extensions/view/index.ts +4 -4
  59. package/extensions/web-search/index.ts +5 -2
  60. package/extensions/web-ui/index.ts +1 -1
  61. package/extensions/web-ui/state.ts +1 -1
  62. package/package.json +8 -7
  63. package/scripts/preinstall.sh +19 -3
  64. package/scripts/publish-pi-mono.sh +92 -0
  65. package/skills/pi-extensions/SKILL.md +2 -2
  66. package/skills/pi-tui/SKILL.md +17 -17
  67. package/skills/typescript/SKILL.md +1 -1
  68. package/themes/alpharius.json +7 -6
  69. /package/extensions/{debug.ts → lib/debug.ts} +0 -0
@@ -10,7 +10,7 @@
10
10
  * /bootstrap — Run interactive setup (install missing deps + profile)
11
11
  * /bootstrap status — Show dependency checklist without installing
12
12
  * /bootstrap install — Install all missing core + recommended deps
13
- * /update-pi — Update pi binary to latest @cwilson613/pi-coding-agent release
13
+ * /update-pi — Update pi binary to latest @styrene-lab/pi-coding-agent release
14
14
  * /update-pi --dry-run — Check for update without installing
15
15
  *
16
16
  * Guards:
@@ -20,10 +20,10 @@
20
20
  */
21
21
 
22
22
  import { spawn } from "node:child_process";
23
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
24
- import { join } from "node:path";
23
+ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
24
+ import { dirname, join } from "node:path";
25
25
  import { homedir, tmpdir } from "node:os";
26
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
26
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
27
27
  import { checkAllProviders, type AuthResult } from "../01-auth/auth.ts";
28
28
  import { loadPiConfig } from "../lib/model-preferences.ts";
29
29
  import {
@@ -33,7 +33,7 @@ import {
33
33
  type OperatorCapabilityProfile,
34
34
  type OperatorProfileCandidate,
35
35
  } from "../lib/operator-profile.ts";
36
- import { sharedState } from "../shared-state.ts";
36
+ import { sharedState } from "../lib/shared-state.ts";
37
37
  import { getDefaultPolicy, type ProviderRoutingPolicy } from "../lib/model-routing.ts";
38
38
  import { DEPS, checkAll, formatReport, bestInstallCmd, sortByRequires, type DepStatus, type DepTier } from "./deps.ts";
39
39
 
@@ -313,6 +313,9 @@ export default function (pi: ExtensionAPI) {
313
313
  if (!isFirstRun()) return;
314
314
  if (!ctx.hasUI) return;
315
315
 
316
+ // Signal other extensions to suppress redundant "no providers" warnings
317
+ sharedState.bootstrapPending = true;
318
+
316
319
  const statuses = checkAll();
317
320
  const missing = statuses.filter((s) => !s.available);
318
321
  const needsProfile = needsOperatorProfileSetup(getConfigRoot(ctx));
@@ -376,124 +379,316 @@ export default function (pi: ExtensionAPI) {
376
379
  },
377
380
  });
378
381
 
379
- // --- /update-pi: update the pi binary from the cwilson613 fork ---
380
- // Pulls the latest @cwilson613/pi-coding-agent from npm and relaunches.
381
- // Useful after a new patch is published without leaving pi.
382
- pi.registerCommand("update-pi", {
383
- description: "Update the pi binary to the latest @cwilson613/pi-coding-agent release",
382
+ // --- /update: unified update command ---
383
+ // Detects dev vs installed mode and runs the appropriate lifecycle:
384
+ // Dev mode (.git exists): pull submodule sync build dependency refresh → relink → verify → restart handoff
385
+ // Installed (no .git): npm install -g omegon@latest → verify → restart handoff
386
+ // Replaces the old split update mental model with a singular-package lifecycle.
387
+ pi.registerCommand("update", {
388
+ description: "Run the authoritative Omegon update lifecycle, then hand off to restart",
384
389
  handler: async (args, ctx) => {
385
390
  const dryRun = args.trim() === "--dry-run";
386
- const PKG = "@cwilson613/pi-coding-agent";
387
-
388
- // Resolve the npm registry latest
389
- ctx.ui.notify(`Checking latest version of ${PKG}…`, "info");
390
- let latestVersion: string;
391
- try {
392
- latestVersion = await new Promise<string>((resolve, reject) => {
393
- let out = "";
394
- const child = spawn("npm", ["view", PKG, "version", "--json"], {
395
- stdio: ["ignore", "pipe", "pipe"],
396
- });
397
- child.stdout.on("data", (d: Buffer) => { out += d.toString(); });
398
- child.on("close", (code: number) => {
399
- if (code !== 0) return reject(new Error("npm view failed"));
400
- resolve(JSON.parse(out.trim()));
401
- });
402
- });
403
- } catch {
404
- ctx.ui.notify(`Failed to query npm registry. Are you online?`, "warning");
405
- return;
406
- }
391
+ const omegonRoot = process.env.PI_CODING_AGENT_DIR ?? join(import.meta.dirname ?? ".", "..");
392
+ const isDevMode = existsSync(join(omegonRoot, ".git"));
407
393
 
408
- // Determine installed version
409
- let installedVersion = "unknown";
410
- try {
411
- installedVersion = await new Promise<string>((resolve) => {
412
- let out = "";
413
- const child = spawn("npm", ["list", "-g", PKG, "--json", "--depth=0"], {
414
- stdio: ["ignore", "pipe", "pipe"],
415
- });
416
- child.stdout.on("data", (d: Buffer) => { out += d.toString(); });
417
- child.on("close", () => {
418
- try {
419
- const data = JSON.parse(out);
420
- resolve(data.dependencies?.[PKG]?.version ?? "unknown");
421
- } catch {
422
- resolve("unknown");
423
- }
424
- });
425
- });
426
- } catch { /* ignore */ }
427
-
428
- if (installedVersion === latestVersion) {
429
- ctx.ui.notify(`Already on latest: ${PKG}@${latestVersion} ✅`, "info");
430
- return;
394
+ if (isDevMode) {
395
+ await updateDevMode(omegonRoot, dryRun, ctx);
396
+ } else {
397
+ await updateInstalledMode(dryRun, ctx);
431
398
  }
432
-
433
- ctx.ui.notify(
434
- `Update available: ${installedVersion} → ${latestVersion}\n` +
435
- (dryRun ? "(dry run — not installing)" : "Installing…"),
436
- "info"
437
- );
438
-
439
- if (dryRun) return;
440
-
441
- const confirmed = await ctx.ui.confirm(
442
- "Update pi binary?",
443
- `Install ${PKG}@${latestVersion} globally via npm?\n\nThis will replace the currently running binary. Restart pi after the update completes.`
444
- );
445
- if (!confirmed) {
446
- ctx.ui.notify("Update cancelled.", "info");
447
- return;
448
- }
449
-
450
- await new Promise<void>((resolve, reject) => {
451
- let stderr = "";
452
- const child = spawn("npm", ["install", "-g", `${PKG}@${latestVersion}`], {
453
- stdio: ["ignore", "pipe", "pipe"],
454
- });
455
- child.stderr.on("data", (d: Buffer) => { stderr += d.toString(); });
456
- child.on("close", (code: number) => {
457
- if (code !== 0) {
458
- ctx.ui.notify(`npm install failed:\n${stderr}`, "warning");
459
- reject(new Error("install failed"));
460
- } else {
461
- resolve();
462
- }
463
- });
464
- }).catch(() => { /* error already notified */ return; });
465
-
466
- ctx.ui.notify(
467
- `✅ Updated to ${PKG}@${latestVersion}.\n` +
468
- "Restart pi to use the new version (/exit, then pi).",
469
- "info"
470
- );
471
399
  },
472
400
  });
473
401
 
474
- // --- /refresh: clear jiti transpilation cache + reload ---
475
- // jiti's fs cache uses path-based hashing, so source changes aren't
476
- // detected on /reload. /refresh clears the cache first.
402
+ // --- /refresh: lightweight cache clear + reload only ---
477
403
  pi.registerCommand("refresh", {
478
- description: "Clear transpilation cache and reload extensions",
404
+ description: "Clear transpilation cache and reload extensions without package/runtime mutation",
479
405
  handler: async (_args, ctx) => {
480
- const jitiCacheDir = join(tmpdir(), "jiti");
481
- let cleared = 0;
482
- if (existsSync(jitiCacheDir)) {
483
- try {
484
- const files = readdirSync(jitiCacheDir);
485
- cleared = files.length;
486
- rmSync(jitiCacheDir, { recursive: true, force: true });
487
- } catch { /* best-effort */ }
488
- }
489
- ctx.ui.notify(cleared > 0
490
- ? `Cleared ${cleared} cached transpilations. Reloading…`
491
- : "No transpilation cache found. Reloading…", "info");
406
+ clearJitiCache(ctx);
492
407
  await ctx.reload();
493
408
  },
494
409
  });
495
410
  }
496
411
 
412
+ // ── /update helpers ──────────────────────────────────────────────────────
413
+
414
+ /** Run a command, collect stdout+stderr, resolve with exit code. */
415
+ function run(
416
+ cmd: string, args: string[], opts?: { cwd?: string },
417
+ ): Promise<{ code: number; stdout: string; stderr: string }> {
418
+ return new Promise((resolve) => {
419
+ let stdout = "", stderr = "";
420
+ const child = spawn(cmd, args, { cwd: opts?.cwd, stdio: ["ignore", "pipe", "pipe"] });
421
+ child.stdout.on("data", (d: Buffer) => { stdout += d.toString(); });
422
+ child.stderr.on("data", (d: Buffer) => { stderr += d.toString(); });
423
+ child.on("close", (code: number) => resolve({ code: code ?? 1, stdout, stderr }));
424
+ });
425
+ }
426
+
427
+ /** Clear jiti transpilation cache. Returns count of cleared entries. */
428
+ function clearJitiCache(_ctx?: unknown): number {
429
+ const jitiCacheDir = join(tmpdir(), "jiti");
430
+ let cleared = 0;
431
+ if (existsSync(jitiCacheDir)) {
432
+ try {
433
+ cleared = readdirSync(jitiCacheDir).length;
434
+ rmSync(jitiCacheDir, { recursive: true, force: true });
435
+ } catch { /* best-effort */ }
436
+ }
437
+ return cleared;
438
+ }
439
+
440
+ export interface PiResolutionInfo {
441
+ omegonRoot: string;
442
+ cli: string;
443
+ resolutionMode: "vendor" | "npm";
444
+ agentDir: string;
445
+ }
446
+
447
+ export interface OmegonBinaryVerification {
448
+ ok: boolean;
449
+ executableName: string;
450
+ executablePath: string;
451
+ realExecutablePath: string;
452
+ resolution?: PiResolutionInfo;
453
+ reason?: string;
454
+ }
455
+
456
+ export function normalizeExecutablePath(executablePath: string): string {
457
+ if (!executablePath) return "";
458
+ try {
459
+ return realpathSync(executablePath);
460
+ } catch {
461
+ return executablePath;
462
+ }
463
+ }
464
+
465
+ async function getActiveExecutablePath(executableName = "omegon"): Promise<string> {
466
+ const which = await run("which", [executableName]);
467
+ return which.code === 0 ? which.stdout.trim() : "";
468
+ }
469
+
470
+ export function validateOmegonBinaryVerification(
471
+ executableName: string,
472
+ executablePath: string,
473
+ realExecutablePath: string,
474
+ resolution: PiResolutionInfo,
475
+ ): OmegonBinaryVerification {
476
+ const binaryLooksOwnedByOmegon = /[\\/]omegon[\\/]/.test(realExecutablePath) || /[\\/]omegon[\\/]bin[\\/](?:omegon|pi)(?:\.mjs)?$/.test(realExecutablePath);
477
+ if (!/omegon(?:[\\/]|$)/.test(resolution.omegonRoot)) {
478
+ return { ok: false, executableName, executablePath, realExecutablePath, resolution, reason: `active ${executableName} resolved to non-Omegon root: ${resolution.omegonRoot}` };
479
+ }
480
+ if (!binaryLooksOwnedByOmegon) {
481
+ return { ok: false, executableName, executablePath, realExecutablePath, resolution, reason: `active ${executableName} symlink target does not appear to point at Omegon: ${realExecutablePath}` };
482
+ }
483
+ return { ok: true, executableName, executablePath, realExecutablePath, resolution };
484
+ }
485
+
486
+ async function inspectActiveOmegonBinary(): Promise<OmegonBinaryVerification> {
487
+ const executableName = "omegon";
488
+ const executablePath = await getActiveExecutablePath(executableName);
489
+ if (!executablePath) {
490
+ return { ok: false, executableName, executablePath: "", realExecutablePath: "", reason: "`omegon` command not found on PATH" };
491
+ }
492
+ const realExecutablePath = normalizeExecutablePath(executablePath);
493
+ const probe = await run(executablePath, ["--where"]);
494
+ if (probe.code !== 0) {
495
+ return { ok: false, executableName, executablePath, realExecutablePath, reason: "active omegon binary did not return Omegon resolution metadata" };
496
+ }
497
+ try {
498
+ const resolution = JSON.parse(probe.stdout.trim()) as PiResolutionInfo;
499
+ return validateOmegonBinaryVerification(executableName, executablePath, realExecutablePath, resolution);
500
+ } catch {
501
+ return { ok: false, executableName, executablePath, realExecutablePath, reason: "active omegon returned invalid verification metadata" };
502
+ }
503
+ }
504
+
505
+ function formatVerification(verification: OmegonBinaryVerification): string {
506
+ if (!verification.ok || !verification.resolution) {
507
+ return `✗ omegon target verification failed${verification.reason ? `: ${verification.reason}` : ""}`;
508
+ }
509
+ return [
510
+ `✓ active ${verification.executableName}: ${verification.executablePath}`,
511
+ `✓ binary target: ${verification.realExecutablePath}`,
512
+ `✓ runtime root: ${verification.resolution.omegonRoot}`,
513
+ `✓ core resolution: ${verification.resolution.resolutionMode} (${verification.resolution.cli})`,
514
+ ].join("\n");
515
+ }
516
+
517
+ /** Dev mode: git pull → submodule update → build → install deps → relink → verify → restart handoff. */
518
+ async function updateDevMode(
519
+ omegonRoot: string,
520
+ dryRun: boolean,
521
+ ctx: { ui: { notify: (message: string, type?: "error" | "warning" | "info") => void } },
522
+ ): Promise<void> {
523
+ const steps: string[] = [];
524
+
525
+ // ── Step 1: git pull omegon ──────────────────────────────────────
526
+ ctx.ui.notify("▸ Pulling omegon…", "info");
527
+ const pull = await run("git", ["pull", "--ff-only"], { cwd: omegonRoot });
528
+ if (pull.code !== 0) {
529
+ // Non-ff merge needed — not fatal, just skip
530
+ const msg = pull.stderr.includes("fatal")
531
+ ? `git pull failed: ${pull.stderr.trim().split("\n")[0]}`
532
+ : "git pull: non-fast-forward — skipping (merge manually if needed)";
533
+ steps.push(`⚠ ${msg}`);
534
+ } else {
535
+ const summary = pull.stdout.trim().split("\n").pop() ?? "";
536
+ const upToDate = pull.stdout.includes("Already up to date");
537
+ steps.push(upToDate ? "✓ omegon: already up to date" : `✓ omegon: ${summary}`);
538
+ }
539
+
540
+ // ── Step 2: update submodule (pi-mono fork) ──────────────────────
541
+ ctx.ui.notify("▸ Updating pi-mono submodule…", "info");
542
+ const sub = await run(
543
+ "git", ["submodule", "update", "--init", "--recursive"],
544
+ { cwd: omegonRoot },
545
+ );
546
+ if (sub.code !== 0) {
547
+ steps.push(`⚠ submodule update failed: ${sub.stderr.trim().split("\n")[0]}`);
548
+ } else {
549
+ steps.push("✓ pi-mono submodule synced");
550
+ }
551
+
552
+ // ── Step 3: build pi-mono ────────────────────────────────────────
553
+ if (dryRun) {
554
+ steps.push("· build: skipped (dry run)");
555
+ } else {
556
+ ctx.ui.notify("▸ Building pi-mono…", "info");
557
+ const piMonoRoot = join(omegonRoot, "vendor/pi-mono");
558
+ const build = await run("npm", ["run", "build"], { cwd: piMonoRoot });
559
+ if (build.code !== 0) {
560
+ const errLine = build.stderr.trim().split("\n").filter(l => !l.startsWith("npm warn")).pop() ?? "unknown error";
561
+ steps.push(`✗ build failed: ${errLine}`);
562
+ ctx.ui.notify(`Update incomplete:\n${steps.join("\n")}`, "warning");
563
+ return;
564
+ }
565
+ steps.push("✓ pi-mono built");
566
+ }
567
+
568
+ // ── Step 4: npm install (pick up any new deps) ───────────────────
569
+ if (dryRun) {
570
+ steps.push("· npm install: skipped (dry run)");
571
+ } else {
572
+ ctx.ui.notify("▸ Refreshing omegon dependencies…", "info");
573
+ const inst = await run("npm", ["install", "--install-links=false"], { cwd: omegonRoot });
574
+ if (inst.code !== 0) {
575
+ steps.push(`⚠ npm install had issues (non-fatal)`);
576
+ } else {
577
+ steps.push("✓ omegon dependencies refreshed");
578
+ }
579
+ }
580
+
581
+ // ── Step 5: relink omegon globally ───────────────────────────────
582
+ if (dryRun) {
583
+ steps.push("· npm link --force: skipped (dry run)");
584
+ } else {
585
+ ctx.ui.notify("▸ Relinking omegon globally…", "info");
586
+ const link = await run("npm", ["link", "--force"], { cwd: omegonRoot });
587
+ if (link.code !== 0) {
588
+ steps.push(`✗ npm link failed: ${(link.stderr.trim().split("\n").filter((l) => !l.startsWith("npm warn")).pop() ?? "unknown error")}`);
589
+ ctx.ui.notify(`Update incomplete:\n${steps.join("\n")}`, "warning");
590
+ return;
591
+ }
592
+ steps.push("✓ omegon relinked globally");
593
+ }
594
+
595
+ // ── Step 6: verify active binary target ──────────────────────────
596
+ if (dryRun) {
597
+ steps.push("· omegon target verification: skipped (dry run)");
598
+ ctx.ui.notify(`Dry run:\n${steps.join("\n")}`, "info");
599
+ return;
600
+ }
601
+ const verification = await inspectActiveOmegonBinary();
602
+ if (!verification.ok) {
603
+ steps.push(formatVerification(verification));
604
+ ctx.ui.notify(`Update incomplete:\n${steps.join("\n")}`, "warning");
605
+ return;
606
+ }
607
+ steps.push(formatVerification(verification));
608
+
609
+ // ── Step 7: clear cache + explicit restart handoff ───────────────
610
+ const cleared = clearJitiCache(ctx);
611
+ if (cleared > 0) steps.push(`✓ cleared ${cleared} cached transpilations`);
612
+ steps.push("✓ update complete — restart Omegon now (/exit, then `omegon`) to load the rebuilt runtime");
613
+ ctx.ui.notify(steps.join("\n"), "info");
614
+ }
615
+
616
+ /** Installed mode: npm install -g omegon@latest → verify → cache clear → restart handoff. */
617
+ async function updateInstalledMode(
618
+ dryRun: boolean,
619
+ ctx: {
620
+ ui: {
621
+ notify: (message: string, type?: "error" | "warning" | "info") => void;
622
+ confirm: (title: string, message: string) => Promise<boolean>;
623
+ };
624
+ },
625
+ ): Promise<void> {
626
+ const PKG = "omegon";
627
+
628
+ // Check latest version on npm
629
+ ctx.ui.notify(`Checking latest version of ${PKG}…`, "info");
630
+ const view = await run("npm", ["view", PKG, "version", "--json"]);
631
+ if (view.code !== 0) {
632
+ ctx.ui.notify("Failed to query npm registry. Are you online?", "warning");
633
+ return;
634
+ }
635
+ const latestVersion = JSON.parse(view.stdout.trim());
636
+
637
+ // Determine installed version
638
+ const list = await run("npm", ["list", "-g", PKG, "--json", "--depth=0"]);
639
+ let installedVersion = "unknown";
640
+ try {
641
+ const data = JSON.parse(list.stdout);
642
+ installedVersion = data.dependencies?.[PKG]?.version ?? "unknown";
643
+ } catch { /* ignore */ }
644
+
645
+ if (installedVersion === latestVersion) {
646
+ ctx.ui.notify(`Already on latest: ${PKG}@${latestVersion} ✅`, "info");
647
+ return;
648
+ }
649
+
650
+ ctx.ui.notify(
651
+ `Update available: ${installedVersion} → ${latestVersion}` +
652
+ (dryRun ? "\n(dry run — not installing)" : ""),
653
+ "info"
654
+ );
655
+ if (dryRun) return;
656
+
657
+ const confirmed = await ctx.ui.confirm(
658
+ "Update omegon?",
659
+ `Install ${PKG}@${latestVersion} globally via npm?\n\nThis will update Omegon, its bundled agent core, extensions, themes, and skills.\nRestart Omegon after the update completes.`,
660
+ );
661
+ if (!confirmed) {
662
+ ctx.ui.notify("Update cancelled.", "info");
663
+ return;
664
+ }
665
+
666
+ ctx.ui.notify("Installing…", "info");
667
+ const inst = await run("npm", ["install", "-g", `${PKG}@${latestVersion}`]);
668
+ if (inst.code !== 0) {
669
+ ctx.ui.notify(`npm install failed:\n${inst.stderr}`, "warning");
670
+ return;
671
+ }
672
+
673
+ const verification = await inspectActiveOmegonBinary();
674
+ if (!verification.ok) {
675
+ ctx.ui.notify(
676
+ `Updated to ${PKG}@${latestVersion}, but post-install verification failed.\n${formatVerification(verification)}\nResolve the Omegon binary target before restarting Omegon.`,
677
+ "warning",
678
+ );
679
+ return;
680
+ }
681
+
682
+ const cleared = clearJitiCache(ctx);
683
+ ctx.ui.notify(
684
+ `✅ Updated to ${PKG}@${latestVersion}.` +
685
+ `\n${formatVerification(verification)}` +
686
+ (cleared > 0 ? `\nCleared ${cleared} cached transpilations.` : "") +
687
+ "\nRestart Omegon to use the new version (/exit, then omegon).",
688
+ "info"
689
+ );
690
+ }
691
+
497
692
  async function interactiveSetup(pi: ExtensionAPI, ctx: CommandContext): Promise<void> {
498
693
  const statuses = checkAll();
499
694
  const missing = statuses.filter((s) => !s.available);
@@ -544,14 +739,35 @@ async function interactiveSetup(pi: ExtensionAPI, ctx: CommandContext): Promise<
544
739
  );
545
740
  }
546
741
 
742
+ // API key guidance — check if any cloud provider is configured
743
+ const providerReadiness = await checkAllProviders(pi);
744
+ const hasAnyCloudKey = providerReadiness.some(
745
+ (r: AuthResult) => r.status === "ok" && r.provider !== "local",
746
+ );
747
+ if (!hasAnyCloudKey) {
748
+ ctx.ui.notify(
749
+ "\n🔑 **No cloud API keys detected.**\n" +
750
+ "Omegon needs at least one provider key to function. The fastest options:\n" +
751
+ " • Anthropic: `/secrets configure ANTHROPIC_API_KEY` (get key at console.anthropic.com)\n" +
752
+ " • OpenAI: `/secrets configure OPENAI_API_KEY` (get key at platform.openai.com)\n" +
753
+ " • GitHub Copilot: `/login github` (requires Copilot subscription)\n",
754
+ "warning"
755
+ );
756
+ }
757
+
547
758
  await ensureOperatorProfile(pi, ctx);
548
759
 
549
760
  const recheck = checkAll();
550
761
  const stillMissing = recheck.filter((s) => !s.available && (s.dep.tier === "core" || s.dep.tier === "recommended"));
551
762
 
552
- if (stillMissing.length === 0) {
763
+ if (stillMissing.length === 0 && hasAnyCloudKey) {
553
764
  ctx.ui.notify("\n🎉 Setup complete! All core and recommended dependencies are available.");
554
765
  markDone();
766
+ } else if (stillMissing.length === 0) {
767
+ ctx.ui.notify(
768
+ "\n✅ Dependencies installed. Configure an API key (see above) to start using Omegon.",
769
+ );
770
+ markDone();
555
771
  } else {
556
772
  ctx.ui.notify(
557
773
  `\n⚠️ ${stillMissing.length} dep${stillMissing.length > 1 ? "s" : ""} still missing. `
@@ -13,7 +13,7 @@
13
13
  import { existsSync } from "node:fs";
14
14
  import { join } from "node:path";
15
15
  import { StringEnum } from "../lib/typebox-helpers";
16
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
16
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
17
17
  import { Type } from "@sinclair/typebox";
18
18
 
19
19
  const CHRONOS_SH = join(import.meta.dirname ?? __dirname, "chronos.sh");
@@ -19,13 +19,13 @@
19
19
  import { spawn } from "node:child_process";
20
20
  import { readFileSync } from "node:fs";
21
21
  import { join } from "node:path";
22
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
23
- import { DASHBOARD_UPDATE_EVENT, sharedState } from "../shared-state.ts";
22
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
23
+ import { DASHBOARD_UPDATE_EVENT, sharedState } from "../lib/shared-state.ts";
24
24
  import type { ChildState, CleaveState, ModelTier } from "./types.ts";
25
25
  import { computeDispatchWaves } from "./planner.ts";
26
26
  import { executeWithReview, type ReviewConfig, type ReviewExecutor, DEFAULT_REVIEW_CONFIG } from "./review.ts";
27
27
  import { saveState } from "./workspace.ts";
28
- import { resolveTier, getDefaultPolicy, type ProviderRoutingPolicy, type RegistryModel } from "../lib/model-routing.ts";
28
+ import { resolveTier, getDefaultPolicy, getViableModels, type ProviderRoutingPolicy, type RegistryModel } from "../lib/model-routing.ts";
29
29
 
30
30
  // ─── Large-run threshold ────────────────────────────────────────────────────
31
31
 
@@ -678,14 +678,14 @@ async function dispatchSingleChild(
678
678
  try {
679
679
  const registry = (pi as any).modelRegistry;
680
680
  if (registry != null) {
681
- registryModels = registry.getAll();
681
+ registryModels = getViableModels(registry);
682
682
  }
683
683
  // If modelRegistry is absent (e.g. test environment), registryModels stays []
684
684
  // and resolveTier will use policy-based fallbacks.
685
685
  } catch (err) {
686
- // getAll() threw — log and continue with empty registry so resolver can still
686
+ // getViableModels() threw — log and continue with empty registry so resolver can still
687
687
  // apply policy-based fallbacks rather than silently passing no --model flag.
688
- console.warn("[cleave] modelRegistry.getAll() threw:", err);
688
+ console.warn("[cleave] getViableModels() threw:", err);
689
689
  }
690
690
  const modelFlag = resolveModelIdForTier(effectiveTier, registryModels, activePolicy, localModel);
691
691
  child.backend = child.executeModel === "local" ? "local" : "cloud";
@@ -14,18 +14,18 @@
14
14
  * The Claude Code SDK calls are replaced with pi's extension API.
15
15
  */
16
16
 
17
- import type { ExtensionAPI, ExtensionCommandContext, AgentToolUpdateCallback } from "@cwilson613/pi-coding-agent";
18
- import { truncateTail, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "@cwilson613/pi-coding-agent";
17
+ import type { ExtensionAPI, ExtensionCommandContext, AgentToolUpdateCallback } from "@styrene-lab/pi-coding-agent";
18
+ import { truncateTail, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "@styrene-lab/pi-coding-agent";
19
19
 
20
- import { Text } from "@cwilson613/pi-tui";
20
+ import { Text } from "@styrene-lab/pi-tui";
21
21
  import { Type } from "@sinclair/typebox";
22
22
  import { spawn, execFile } from "node:child_process";
23
23
  import { promisify } from "node:util";
24
24
  import { createHash } from "node:crypto";
25
25
 
26
- import { sharedState, DASHBOARD_UPDATE_EVENT } from "../shared-state.ts";
27
- import { sciCall, sciOk, sciErr, sciExpanded } from "../sci-ui.ts";
28
- import { debug } from "../debug.ts";
26
+ import { sharedState, DASHBOARD_UPDATE_EVENT } from "../lib/shared-state.ts";
27
+ import { sciCall, sciOk, sciErr, sciExpanded } from "../lib/sci-ui.ts";
28
+ import { debug } from "../lib/debug.ts";
29
29
  import { emitOpenSpecState } from "../openspec/dashboard-state.ts";
30
30
  import { getSharedBridge, buildSlashCommandResult } from "../lib/slash-command-bridge.ts";
31
31
  import { buildAssessBridgeResult } from "./bridge.ts";
@@ -9,7 +9,7 @@
9
9
  * a JSON split strategy with 2-4 children.
10
10
  */
11
11
 
12
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
12
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
13
13
  import type { ChildPlan, SplitPlan } from "./types.ts";
14
14
 
15
15
  /**
@@ -9,7 +9,7 @@
9
9
  import { mkdirSync } from "node:fs";
10
10
  import { homedir } from "node:os";
11
11
  import { join } from "node:path";
12
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
12
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
13
13
 
14
14
  /** Base directory for cleave worktrees. */
15
15
  const WORKTREE_HOME = join(homedir(), ".pi", "cleave", "wt");