omegon 0.6.11 → 0.6.13

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/bin/omegon.mjs CHANGED
@@ -52,10 +52,14 @@ function injectBundledResourceArgs(argv) {
52
52
  }
53
53
  };
54
54
 
55
+ // Omegon is the sole authority for bundled resources.
56
+ // Suppress pi's auto-discovery of skills, prompts, and themes (which scans
57
+ // ~/.pi/agent/*, installed packages, and project .pi/ dirs) so only our
58
+ // manifest-declared resources load. The --no-* flags disable discovery
59
+ // but still allow CLI-injected paths (our --extension manifest).
60
+ // Extensions are NOT suppressed — project-local .pi/extensions/ should still work.
61
+ injected.push("--no-skills", "--no-prompt-templates", "--no-themes");
55
62
  pushPair("--extension", omegonRoot);
56
- pushPair("--skill", join(omegonRoot, "skills"));
57
- pushPair("--prompt-template", join(omegonRoot, "prompts"));
58
- pushPair("--theme", join(omegonRoot, "themes"));
59
63
  return injected;
60
64
  }
61
65
 
@@ -46,46 +46,9 @@ function hasCmd(cmd: string): boolean {
46
46
  }
47
47
  }
48
48
 
49
- /**
50
- * Detect immutable/atomic Linux distros (Bazzite, Silverblue, Kinoite, etc.)
51
- * where dnf/apt are unavailable or aliased to guides. These distros typically
52
- * use Homebrew (Linuxbrew) or Flatpak for user-space packages.
53
- */
54
- function isImmutableLinux(): boolean {
55
- if (process.platform !== "linux") return false;
56
- try {
57
- const osRelease = execSync("cat /etc/os-release 2>/dev/null", { encoding: "utf-8" });
58
- // Bazzite, Silverblue, Kinoite, Aurora, Bluefin — all Fedora Atomic variants
59
- return /VARIANT_ID=.*(silverblue|kinoite|bazzite|aurora|bluefin|atomic)/i.test(osRelease)
60
- || /ostree/i.test(osRelease);
61
- } catch {
62
- return false;
63
- }
64
- }
65
-
66
- /** Cached immutable Linux detection */
67
- const _isImmutable = isImmutableLinux();
68
-
69
49
  /** Get the best install command for the current platform */
70
50
  export function bestInstallCmd(dep: Dep): string | undefined {
71
51
  const plat = process.platform === "darwin" ? "darwin" : "linux";
72
-
73
- // On immutable Linux (Bazzite, Silverblue, etc.), dnf/apt are unavailable
74
- // or aliased to documentation guides. Prefer brew commands.
75
- // On regular Linux, prefer non-brew (apt/dnf) unless brew is the only option.
76
- const hasBrew = hasCmd("brew");
77
- if (plat === "linux" && (_isImmutable || !hasBrew)) {
78
- // Immutable: must use brew (skip apt/dnf). Regular without brew: skip brew commands.
79
- const candidates = dep.install.filter((o) => o.platform === plat || o.platform === "any");
80
- if (_isImmutable && hasBrew) {
81
- const brewCmd = candidates.find((o) => o.cmd.startsWith("brew "));
82
- if (brewCmd) return brewCmd.cmd;
83
- } else if (!_isImmutable) {
84
- const nonBrew = candidates.find((o) => !o.cmd.startsWith("brew "));
85
- if (nonBrew) return nonBrew.cmd;
86
- }
87
- }
88
-
89
52
  return (
90
53
  dep.install.find((o) => o.platform === plat)?.cmd ??
91
54
  dep.install.find((o) => o.platform === "any")?.cmd ??
@@ -108,6 +71,18 @@ export function installHints(dep: Dep): string[] {
108
71
  */
109
72
  export const DEPS: Dep[] = [
110
73
  // --- Core: most users want these ---
74
+ {
75
+ id: "nix",
76
+ name: "Nix",
77
+ purpose: "Universal package manager — installs all other dependencies on any OS",
78
+ usedBy: ["bootstrap"],
79
+ tier: "core",
80
+ check: () => hasCmd("nix"),
81
+ install: [
82
+ { platform: "any", cmd: "curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install" },
83
+ ],
84
+ url: "https://zero-to-nix.com",
85
+ },
111
86
  {
112
87
  id: "ollama",
113
88
  name: "Ollama",
@@ -115,9 +90,9 @@ export const DEPS: Dep[] = [
115
90
  usedBy: ["local-inference", "project-memory", "cleave", "offline-driver"],
116
91
  tier: "core",
117
92
  check: () => hasCmd("ollama"),
93
+ requires: ["nix"],
118
94
  install: [
119
- { platform: "darwin", cmd: "brew install ollama" },
120
- { platform: "linux", cmd: "curl -fsSL https://ollama.com/install.sh | sh" },
95
+ { platform: "any", cmd: "nix profile install nixpkgs#ollama" },
121
96
  ],
122
97
  url: "https://ollama.com",
123
98
  },
@@ -128,9 +103,9 @@ export const DEPS: Dep[] = [
128
103
  usedBy: ["render", "view"],
129
104
  tier: "core",
130
105
  check: () => hasCmd("d2"),
106
+ requires: ["nix"],
131
107
  install: [
132
- { platform: "darwin", cmd: "brew install d2" },
133
- { platform: "linux", cmd: "curl -fsSL https://d2lang.com/install.sh | sh" },
108
+ { platform: "any", cmd: "nix profile install nixpkgs#d2" },
134
109
  ],
135
110
  url: "https://d2lang.com",
136
111
  },
@@ -143,10 +118,9 @@ export const DEPS: Dep[] = [
143
118
  usedBy: ["01-auth"],
144
119
  tier: "recommended",
145
120
  check: () => hasCmd("gh"),
121
+ requires: ["nix"],
146
122
  install: [
147
- { platform: "darwin", cmd: "brew install gh" },
148
- { platform: "linux", cmd: "brew install gh" },
149
- { platform: "linux", cmd: "sudo apt install gh || sudo dnf install gh" },
123
+ { platform: "any", cmd: "nix profile install nixpkgs#gh" },
150
124
  ],
151
125
  url: "https://cli.github.com",
152
126
  },
@@ -157,9 +131,9 @@ export const DEPS: Dep[] = [
157
131
  usedBy: ["view"],
158
132
  tier: "recommended",
159
133
  check: () => hasCmd("pandoc"),
134
+ requires: ["nix"],
160
135
  install: [
161
- { platform: "darwin", cmd: "brew install pandoc" },
162
- { platform: "linux", cmd: "sudo apt install pandoc || sudo dnf install pandoc" },
136
+ { platform: "any", cmd: "nix profile install nixpkgs#pandoc" },
163
137
  ],
164
138
  url: "https://pandoc.org",
165
139
  },
@@ -171,9 +145,7 @@ export const DEPS: Dep[] = [
171
145
  tier: "recommended",
172
146
  check: () => hasCmd("cargo"),
173
147
  install: [
174
- // -s -- -y passes -y to rustup-init, suppressing the interactive
175
- // "1) Proceed / 2) Customise / 3) Cancel" prompt that otherwise hangs.
176
- { platform: "any", cmd: "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y" },
148
+ { platform: "any", cmd: "nix profile install nixpkgs#rustup && rustup default stable" },
177
149
  ],
178
150
  url: "https://rustup.rs",
179
151
  },
@@ -199,10 +171,9 @@ export const DEPS: Dep[] = [
199
171
  usedBy: ["view"],
200
172
  tier: "optional",
201
173
  check: () => hasCmd("rsvg-convert"),
174
+ requires: ["nix"],
202
175
  install: [
203
- { platform: "darwin", cmd: "brew install librsvg" },
204
- { platform: "linux", cmd: "brew install librsvg" },
205
- { platform: "linux", cmd: "sudo apt install librsvg2-bin" },
176
+ { platform: "any", cmd: "nix profile install nixpkgs#librsvg" },
206
177
  ],
207
178
  },
208
179
  {
@@ -212,10 +183,9 @@ export const DEPS: Dep[] = [
212
183
  usedBy: ["view"],
213
184
  tier: "optional",
214
185
  check: () => hasCmd("pdftoppm"),
186
+ requires: ["nix"],
215
187
  install: [
216
- { platform: "darwin", cmd: "brew install poppler" },
217
- { platform: "linux", cmd: "brew install poppler" },
218
- { platform: "linux", cmd: "sudo apt install poppler-utils" },
188
+ { platform: "any", cmd: "nix profile install nixpkgs#poppler_utils" },
219
189
  ],
220
190
  },
221
191
  {
@@ -225,9 +195,9 @@ export const DEPS: Dep[] = [
225
195
  usedBy: ["render"],
226
196
  tier: "optional",
227
197
  check: () => hasCmd("uv"),
198
+ requires: ["nix"],
228
199
  install: [
229
- { platform: "darwin", cmd: "brew install uv" },
230
- { platform: "any", cmd: "curl -LsSf https://astral.sh/uv/install.sh | sh" },
200
+ { platform: "any", cmd: "nix profile install nixpkgs#uv" },
231
201
  ],
232
202
  url: "https://docs.astral.sh/uv/",
233
203
  },
@@ -238,10 +208,9 @@ export const DEPS: Dep[] = [
238
208
  usedBy: ["01-auth"],
239
209
  tier: "optional",
240
210
  check: () => hasCmd("aws"),
211
+ requires: ["nix"],
241
212
  install: [
242
- { platform: "darwin", cmd: "brew install awscli" },
243
- { platform: "linux", cmd: "brew install awscli" },
244
- { platform: "linux", cmd: "sudo apt install awscli" },
213
+ { platform: "any", cmd: "nix profile install nixpkgs#awscli2" },
245
214
  ],
246
215
  },
247
216
  {
@@ -251,10 +220,9 @@ export const DEPS: Dep[] = [
251
220
  usedBy: ["01-auth"],
252
221
  tier: "optional",
253
222
  check: () => hasCmd("kubectl"),
223
+ requires: ["nix"],
254
224
  install: [
255
- { platform: "darwin", cmd: "brew install kubectl" },
256
- { platform: "linux", cmd: "brew install kubectl" },
257
- { platform: "linux", cmd: "sudo apt install kubectl" },
225
+ { platform: "any", cmd: "nix profile install nixpkgs#kubectl" },
258
226
  ],
259
227
  },
260
228
  ];
@@ -269,13 +237,40 @@ export function checkAll(): DepStatus[] {
269
237
  }));
270
238
  }
271
239
 
240
+ /**
241
+ * Detect whether the terminal supports Unicode emoji rendering.
242
+ *
243
+ * Returns true for modern terminals (Windows Terminal, VS Code, xterm-256color,
244
+ * iTerm2, etc.) and false for legacy consoles (Windows conhost.exe) where emoji
245
+ * render as blank boxes. Errs on the side of ASCII when uncertain.
246
+ */
247
+ function supportsEmoji(): boolean {
248
+ // Windows Terminal sets WT_SESSION; conhost.exe does not
249
+ if (process.env["WT_SESSION"]) return true;
250
+ // VS Code integrated terminal
251
+ if (process.env["TERM_PROGRAM"] === "vscode") return true;
252
+ // iTerm2, Hyper, and other macOS/Linux terminals advertising 256-color
253
+ if (process.env["TERM_PROGRAM"] === "iTerm.app") return true;
254
+ // xterm-256color and similar modern TERM values
255
+ const term = process.env["TERM"] ?? "";
256
+ if (term.includes("256color") || term === "xterm-kitty") return true;
257
+ // COLORTERM=truecolor or 24bit signals a modern terminal
258
+ const colorterm = process.env["COLORTERM"] ?? "";
259
+ if (colorterm === "truecolor" || colorterm === "24bit") return true;
260
+ // CI environments typically render emoji correctly
261
+ if (process.env["CI"]) return true;
262
+ // Non-Windows: default to emoji; on Windows without the above signals, use ASCII
263
+ return process.platform !== "win32";
264
+ }
265
+
272
266
  /** Format a single dep status as a line, with install hint if missing */
273
267
  function formatStatus(s: DepStatus): string {
274
- const icon = s.available ? "✅" : "❌";
268
+ const emoji = supportsEmoji();
269
+ const icon = s.available ? (emoji ? "✅" : "[ok]") : (emoji ? "❌" : "[x]");
275
270
  let line = `${icon} ${s.dep.name} — ${s.dep.purpose}`;
276
271
  if (!s.available) {
277
272
  const cmd = bestInstallCmd(s.dep);
278
- if (cmd) line += `\n → \`${cmd}\``;
273
+ if (cmd) line += `\n ${emoji ? "" : "->"} \`${cmd}\``;
279
274
  }
280
275
  return line;
281
276
  }
@@ -303,10 +298,11 @@ export function formatReport(statuses: DepStatus[]): string {
303
298
  }
304
299
 
305
300
  const missing = statuses.filter((s) => !s.available);
301
+ const emoji = supportsEmoji();
306
302
  if (missing.length === 0) {
307
- lines.push("🎉 All dependencies are available!");
303
+ lines.push(emoji ? "🎉 All dependencies are available!" : "[ok] All dependencies are available!");
308
304
  } else {
309
- lines.push(`**${missing.length} missing** — run \`/bootstrap\` to install interactively.`);
305
+ lines.push(`${emoji ? "⚠️ " : "[!] "}**${missing.length} missing** — run \`/bootstrap\` to install interactively.`);
310
306
  }
311
307
 
312
308
  return lines.join("\n");
@@ -25,6 +25,7 @@ import { dirname, join } from "node:path";
25
25
  import { fileURLToPath } from "node:url";
26
26
  import { homedir, tmpdir } from "node:os";
27
27
  import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
28
+ import { resolveOmegonSubprocess } from "../lib/omegon-subprocess.ts";
28
29
  import { checkAllProviders, type AuthResult } from "../01-auth/auth.ts";
29
30
  import { loadPiConfig } from "../lib/model-preferences.ts";
30
31
  import {
@@ -362,11 +363,15 @@ export default function (pi: ExtensionAPI) {
362
363
 
363
364
  if (sub === "status") {
364
365
  const statuses = checkAll();
365
- cmdCtx.say(formatReport(statuses));
366
366
  const profile = loadOperatorProfile(getConfigRoot(cmdCtx));
367
- cmdCtx.say(profile
367
+ const profileLine = profile
368
368
  ? `\nOperator capability profile: ${profile.setupComplete ? "configured" : "defaulted"}`
369
- : "\nOperator capability profile: not configured");
369
+ : "\nOperator capability profile: not configured";
370
+ // Merge into a single say() call — the pi TUI showStatus() deduplication
371
+ // pattern replaces the previous notification when two consecutive say()
372
+ // calls are made synchronously, so splitting these would silently discard
373
+ // the dependency report.
374
+ cmdCtx.say(formatReport(statuses) + profileLine);
370
375
  return;
371
376
  }
372
377
 
@@ -409,10 +414,48 @@ export default function (pi: ExtensionAPI) {
409
414
  await ctx.reload();
410
415
  },
411
416
  });
417
+
418
+ // --- /restart: full process restart ---
419
+ pi.registerCommand("restart", {
420
+ description: "Restart Omegon (clears cache, spawns fresh process)",
421
+ handler: async (_args, ctx) => {
422
+ clearJitiCache(ctx);
423
+ ctx.ui.notify("Restarting Omegon…", "info");
424
+ await new Promise((r) => setTimeout(r, 500));
425
+ restartOmegon();
426
+ },
427
+ });
412
428
  }
413
429
 
414
430
  // ── /update helpers ──────────────────────────────────────────────────────
415
431
 
432
+ /**
433
+ * Replace the current Omegon process with a fresh instance.
434
+ *
435
+ * Spawns a new detached Omegon process with inherited stdio, then exits
436
+ * the current process. The user sees the terminal briefly reset and the
437
+ * new session starts automatically — no manual re-launch needed.
438
+ */
439
+ function restartOmegon(): never {
440
+ const { command, argvPrefix } = resolveOmegonSubprocess();
441
+ // Pass through any user-facing args from the original invocation
442
+ // (skip argv[0]=node, argv[1]=omegon.mjs which argvPrefix covers)
443
+ const userArgs = process.argv.slice(2).filter(a =>
444
+ // Strip injected resource flags — the new process injects its own
445
+ !a.startsWith("--extensions-dir=") &&
446
+ !a.startsWith("--themes-dir=") &&
447
+ !a.startsWith("--skills-dir=") &&
448
+ !a.startsWith("--prompts-dir=")
449
+ );
450
+ const child = spawn(command, [...argvPrefix, ...userArgs], {
451
+ stdio: "inherit",
452
+ detached: true,
453
+ env: process.env,
454
+ });
455
+ child.unref();
456
+ process.exit(0);
457
+ }
458
+
416
459
  /** Run a command, collect stdout+stderr, resolve with exit code. */
417
460
  function run(
418
461
  cmd: string, args: string[], opts?: { cwd?: string },
@@ -609,11 +652,15 @@ async function updateDevMode(
609
652
  }
610
653
  steps.push(formatVerification(verification));
611
654
 
612
- // ── Step 7: clear cache + explicit restart handoff ───────────────
655
+ // ── Step 7: clear cache + restart ────────────────────────────────
613
656
  const cleared = clearJitiCache(ctx);
614
657
  if (cleared > 0) steps.push(`✓ cleared ${cleared} cached transpilations`);
615
- steps.push("✓ update complete — restart Omegon now (/exit, then `omegon`) to load the rebuilt runtime");
658
+ steps.push("✓ update complete — restarting Omegon");
616
659
  ctx.ui.notify(steps.join("\n"), "info");
660
+
661
+ // Brief pause so the user sees the summary before the terminal resets
662
+ await new Promise((r) => setTimeout(r, 1500));
663
+ restartOmegon();
617
664
  }
618
665
 
619
666
  /** Installed mode: npm install -g omegon@latest → verify → cache clear → restart handoff. */
@@ -687,9 +734,12 @@ async function updateInstalledMode(
687
734
  `✅ Updated to ${PKG}@${latestVersion}.` +
688
735
  `\n${formatVerification(verification)}` +
689
736
  (cleared > 0 ? `\nCleared ${cleared} cached transpilations.` : "") +
690
- "\nRestart Omegon to use the new version (/exit, then omegon).",
737
+ "\nRestarting Omegon",
691
738
  "info"
692
739
  );
740
+
741
+ await new Promise((r) => setTimeout(r, 1500));
742
+ restartOmegon();
693
743
  }
694
744
 
695
745
  async function interactiveSetup(pi: ExtensionAPI, ctx: CommandContext): Promise<void> {
@@ -973,6 +1023,21 @@ function patchPathForCargo(): void {
973
1023
  }
974
1024
  }
975
1025
 
1026
+ /** After Determinate Nix install, add nix to PATH so subsequent installs work. */
1027
+ function patchPathForNix(): void {
1028
+ const nixPaths = [
1029
+ "/nix/var/nix/profiles/default/bin",
1030
+ join(homedir(), ".nix-profile", "bin"),
1031
+ ];
1032
+ const current = process.env.PATH ?? "";
1033
+ const parts = current.split(":");
1034
+ for (const nixBin of nixPaths) {
1035
+ if (existsSync(nixBin) && !parts.includes(nixBin)) {
1036
+ process.env.PATH = `${nixBin}:${process.env.PATH}`;
1037
+ }
1038
+ }
1039
+ }
1040
+
976
1041
  async function installDeps(ctx: CommandContext, deps: DepStatus[]): Promise<void> {
977
1042
  // Sort so prerequisites come first (e.g., cargo before mdserve)
978
1043
  const sorted = sortByRequires(deps);
@@ -1008,8 +1073,11 @@ async function installDeps(ctx: CommandContext, deps: DepStatus[]): Promise<void
1008
1073
  (line) => ctx.ui.notify(line),
1009
1074
  );
1010
1075
 
1011
- // Rustup installs to ~/.cargo/bin patch PATH immediately so the rest
1012
- // of the install sequence (e.g. mdserve) can find cargo.
1076
+ // Patch PATH immediately after installing bootstrapping deps so the rest
1077
+ // of the install sequence can find them without a new shell.
1078
+ if (dep.id === "nix" && exitCode === 0) {
1079
+ patchPathForNix();
1080
+ }
1013
1081
  if (dep.id === "cargo" && exitCode === 0) {
1014
1082
  patchPathForCargo();
1015
1083
  }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * clipboard-diag — Diagnostic command for clipboard image paste.
3
+ *
4
+ * Registers /cliptest to diagnose why Ctrl+V image paste may fail.
5
+ */
6
+ import type { ExtensionAPI } from "../../vendor/pi-mono/packages/coding-agent/src/core/extensions/types.js";
7
+
8
+ export default function clipboardDiag(pi: ExtensionAPI) {
9
+ pi.registerCommand("cliptest", {
10
+ description: "Test clipboard image access (diagnostic)",
11
+ async handler() {
12
+ const lines: string[] = ["**Clipboard Image Diagnostic**", ""];
13
+
14
+ // 1. Check native module
15
+ let clipModule: string | null = null;
16
+ let clipboard: { hasImage: () => boolean; getImageBinary: () => Promise<unknown> } | null = null;
17
+ const candidates = ["@cwilson613/clipboard", "@mariozechner/clipboard"];
18
+ for (const name of candidates) {
19
+ try {
20
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
21
+ const mod = require(name);
22
+ clipboard = mod;
23
+ clipModule = name;
24
+ break;
25
+ } catch {
26
+ // next
27
+ }
28
+ }
29
+
30
+ if (!clipboard) {
31
+ lines.push("❌ No clipboard native module found");
32
+ lines.push(` Tried: ${candidates.join(", ")}`);
33
+ } else {
34
+ lines.push(`✓ Module: ${clipModule}`);
35
+
36
+ // 2. Check hasImage
37
+ try {
38
+ const has = clipboard.hasImage();
39
+ lines.push(`${has ? "✓" : "❌"} hasImage(): ${has}`);
40
+
41
+ // 3. Try reading
42
+ if (has) {
43
+ try {
44
+ const data = await clipboard.getImageBinary();
45
+ const len = Array.isArray(data) ? data.length : (data as Uint8Array)?.length ?? 0;
46
+ lines.push(`${len > 0 ? "✓" : "❌"} getImageBinary(): ${len} bytes`);
47
+ } catch (e) {
48
+ lines.push(`❌ getImageBinary() threw: ${e instanceof Error ? e.message : String(e)}`);
49
+ }
50
+ }
51
+ } catch (e) {
52
+ lines.push(`❌ hasImage() threw: ${e instanceof Error ? e.message : String(e)}`);
53
+ }
54
+ }
55
+
56
+ // 4. Platform info
57
+ lines.push("");
58
+ lines.push(`Platform: ${process.platform}, TERM: ${process.env.TERM ?? "unset"}`);
59
+ lines.push(`DISPLAY: ${process.env.DISPLAY ?? "unset"}, WAYLAND: ${process.env.WAYLAND_DISPLAY ?? "unset"}`);
60
+
61
+ pi.sendMessage({ customType: "view", content: lines.join("\n"), display: true });
62
+ },
63
+ });
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omegon",
3
- "version": "0.6.11",
3
+ "version": "0.6.13",
4
4
  "description": "Omegon — an opinionated distribution of pi (by Mario Zechner) with extensions for lifecycle management, memory, orchestration, and visualization",
5
5
  "bin": {
6
6
  "omegon": "bin/omegon.mjs",
@@ -73,13 +73,17 @@
73
73
  "./extensions/tool-profile",
74
74
  "./extensions/vault",
75
75
  "./extensions/version-check.ts",
76
- "./extensions/web-ui"
76
+ "./extensions/web-ui",
77
+ "./extensions/clipboard-diag/index.ts"
77
78
  ],
78
79
  "skills": [
79
80
  "./skills"
80
81
  ],
81
82
  "prompts": [
82
83
  "./prompts"
84
+ ],
85
+ "themes": [
86
+ "./themes"
83
87
  ]
84
88
  },
85
89
  "dependencies": {