omegon 0.6.23 → 0.6.25
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.
|
@@ -62,7 +62,8 @@ export function diagnoseError(stderr: string): { status: AuthStatus; reason: str
|
|
|
62
62
|
// Not logged in — check before invalid to avoid "not authenticated" matching "invalid"
|
|
63
63
|
if (lower.includes("not logged") || lower.includes("no token") || lower.includes("not authenticated")
|
|
64
64
|
|| lower.includes("login required") || lower.includes("no credentials")
|
|
65
|
-
|| lower.includes("no valid credentials")
|
|
65
|
+
|| lower.includes("no valid credentials")
|
|
66
|
+
|| lower.includes("missing client token")) {
|
|
66
67
|
return { status: "none", reason: "Not authenticated" };
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -75,7 +76,7 @@ export function diagnoseError(stderr: string): { status: AuthStatus; reason: str
|
|
|
75
76
|
|
|
76
77
|
// Forbidden (authenticated but insufficient permissions)
|
|
77
78
|
if (/\b403\b/.test(lower) || lower.includes("insufficient scope")
|
|
78
|
-
|| lower.includes("access denied")) {
|
|
79
|
+
|| lower.includes("access denied") || lower.includes("permission denied")) {
|
|
79
80
|
return { status: "invalid", reason: `Authenticated but forbidden: ${extractErrorLine(stderr)}` };
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -211,6 +211,20 @@ export const DEPS: Dep[] = [
|
|
|
211
211
|
},
|
|
212
212
|
|
|
213
213
|
// --- Recommended: common workflows ---
|
|
214
|
+
{
|
|
215
|
+
id: "vault",
|
|
216
|
+
name: "Vault CLI",
|
|
217
|
+
purpose: "HashiCorp Vault authentication status checking and secret management",
|
|
218
|
+
usedBy: ["01-auth"],
|
|
219
|
+
tier: "recommended",
|
|
220
|
+
check: () => hasCmd("vault"),
|
|
221
|
+
install: [
|
|
222
|
+
{ platform: "darwin", cmd: "brew install hashicorp/tap/vault" },
|
|
223
|
+
{ platform: "linux", cmd: "brew install hashicorp/tap/vault" },
|
|
224
|
+
{ platform: "linux", cmd: "wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --yes --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg && echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main\" | sudo tee /etc/apt/sources.list.d/hashicorp.list && sudo apt update && sudo apt install -y vault" },
|
|
225
|
+
],
|
|
226
|
+
url: "https://developer.hashicorp.com/vault/install",
|
|
227
|
+
},
|
|
214
228
|
{
|
|
215
229
|
id: "gh",
|
|
216
230
|
name: "GitHub CLI",
|
|
@@ -333,20 +347,6 @@ export const DEPS: Dep[] = [
|
|
|
333
347
|
{ platform: "any", cmd: "brew install kubectl" },
|
|
334
348
|
],
|
|
335
349
|
},
|
|
336
|
-
{
|
|
337
|
-
id: "vault",
|
|
338
|
-
name: "Vault CLI",
|
|
339
|
-
purpose: "HashiCorp Vault authentication status checking and secret management",
|
|
340
|
-
usedBy: ["01-auth"],
|
|
341
|
-
tier: "optional",
|
|
342
|
-
check: () => hasCmd("vault"),
|
|
343
|
-
requires: ["nix"],
|
|
344
|
-
install: [
|
|
345
|
-
{ platform: "any", cmd: "nix profile install nixpkgs#vault" },
|
|
346
|
-
{ platform: "any", cmd: "brew install hashicorp/tap/vault" },
|
|
347
|
-
],
|
|
348
|
-
url: "https://developer.hashicorp.com/vault/install",
|
|
349
|
-
},
|
|
350
350
|
];
|
|
351
351
|
|
|
352
352
|
export type DepStatus = { dep: Dep; available: boolean };
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* - Never auto-installs anything — always asks or requires explicit command
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { spawn } from "node:child_process";
|
|
22
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
23
23
|
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
24
24
|
import { dirname, join } from "node:path";
|
|
25
25
|
import { fileURLToPath } from "node:url";
|
|
@@ -455,18 +455,45 @@ function restartOmegon(): never {
|
|
|
455
455
|
|
|
456
456
|
const parts = [command, ...argvPrefix, ...userArgs].map(shellEscape);
|
|
457
457
|
const script = join(tmpdir(), `omegon-restart-${process.pid}.sh`);
|
|
458
|
+
const oldPid = process.pid;
|
|
458
459
|
writeFileSync(script, [
|
|
459
460
|
"#!/bin/sh",
|
|
460
|
-
//
|
|
461
|
-
"
|
|
462
|
-
|
|
461
|
+
// Trap signals so Ctrl+C works; ignore HUP so parent death doesn't kill us
|
|
462
|
+
"trap 'stty sane 2>/dev/null; exit 130' INT TERM",
|
|
463
|
+
"trap '' HUP",
|
|
464
|
+
// Wait for the old process to fully die (poll with timeout)
|
|
465
|
+
"_w=0",
|
|
466
|
+
`while kill -0 ${oldPid} 2>/dev/null; do`,
|
|
467
|
+
" sleep 0.1",
|
|
468
|
+
" _w=$((_w + 1))",
|
|
469
|
+
// Bail after ~5 seconds — proceed anyway
|
|
470
|
+
' [ "$_w" -ge 50 ] && break',
|
|
471
|
+
"done",
|
|
472
|
+
// Extra grace period for fd/terminal release
|
|
473
|
+
"sleep 0.2",
|
|
474
|
+
// Reset terminal to sane cooked state (stty sane is sufficient;
|
|
475
|
+
// avoid printf '\\033c' which nukes scrollback history)
|
|
463
476
|
"stty sane 2>/dev/null",
|
|
464
|
-
|
|
477
|
+
// Clean up this script
|
|
478
|
+
`rm -f "${script}"`,
|
|
465
479
|
// Replace this shell with new omegon
|
|
466
480
|
`exec ${parts.join(" ")}`,
|
|
467
481
|
].join("\n") + "\n", { mode: 0o755 });
|
|
468
482
|
|
|
469
|
-
//
|
|
483
|
+
// Reset terminal to cooked mode BEFORE exiting so the restart script
|
|
484
|
+
// (and the user) aren't stuck with raw-mode terminal if something goes wrong.
|
|
485
|
+
try {
|
|
486
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
487
|
+
process.stdin.setRawMode(false);
|
|
488
|
+
}
|
|
489
|
+
// Also reset via stty — timeout guards against blocking on contested stdin
|
|
490
|
+
spawnSync("stty", ["sane"], { stdio: "inherit", timeout: 2000 });
|
|
491
|
+
} catch { /* best-effort */ }
|
|
492
|
+
|
|
493
|
+
// Spawn restart script detached (survives parent exit) but with SIGHUP
|
|
494
|
+
// ignored in the script so process-group teardown doesn't kill it.
|
|
495
|
+
// detached: true puts it in its own process group; the script's signal
|
|
496
|
+
// traps ensure Ctrl+C still works once the new omegon exec's.
|
|
470
497
|
const child = spawn("sh", [script], {
|
|
471
498
|
stdio: "inherit",
|
|
472
499
|
detached: true,
|
|
@@ -774,7 +801,10 @@ async function interactiveSetup(pi: ExtensionAPI, ctx: CommandContext): Promise<
|
|
|
774
801
|
const statuses = checkAll();
|
|
775
802
|
const missing = statuses.filter((s) => !s.available);
|
|
776
803
|
|
|
777
|
-
|
|
804
|
+
// Emit the dep report as a permanent warning-level message so it is never
|
|
805
|
+
// replaced by subsequent showStatus() calls (showWarning adds nodes to
|
|
806
|
+
// chatContainer without updating lastStatusText, breaking the deduplication chain).
|
|
807
|
+
ctx.ui.notify(formatReport(statuses), "warning");
|
|
778
808
|
|
|
779
809
|
if (missing.length === 0 && !needsOperatorProfileSetup(getConfigRoot(ctx))) {
|
|
780
810
|
markDone();
|
|
@@ -782,7 +812,7 @@ async function interactiveSetup(pi: ExtensionAPI, ctx: CommandContext): Promise<
|
|
|
782
812
|
}
|
|
783
813
|
|
|
784
814
|
if (!ctx.hasUI || !ctx.ui) {
|
|
785
|
-
ctx.ui.notify("\nRun individual install commands above, or use `/bootstrap install` to install all core + recommended deps.");
|
|
815
|
+
ctx.ui.notify("\nRun individual install commands above, or use `/bootstrap install` to install all core + recommended deps.", "warning");
|
|
786
816
|
await ensureOperatorProfile(pi, ctx);
|
|
787
817
|
return;
|
|
788
818
|
}
|
|
@@ -798,6 +828,7 @@ async function interactiveSetup(pi: ExtensionAPI, ctx: CommandContext): Promise<
|
|
|
798
828
|
`${coreMissing.length} missing: ${names}`,
|
|
799
829
|
);
|
|
800
830
|
if (proceed) {
|
|
831
|
+
ctx.ui.notify(`Installing ${coreMissing.length} core dep${coreMissing.length > 1 ? "s" : ""}… (this may take a while)`, "info");
|
|
801
832
|
await installDeps(ctx, coreMissing);
|
|
802
833
|
}
|
|
803
834
|
}
|
|
@@ -809,14 +840,19 @@ async function interactiveSetup(pi: ExtensionAPI, ctx: CommandContext): Promise<
|
|
|
809
840
|
`${recMissing.length} missing: ${names}`,
|
|
810
841
|
);
|
|
811
842
|
if (proceed) {
|
|
843
|
+
ctx.ui.notify(`Installing ${recMissing.length} recommended dep${recMissing.length > 1 ? "s" : ""}… (this may take a while)`, "info");
|
|
812
844
|
await installDeps(ctx, recMissing);
|
|
813
845
|
}
|
|
814
846
|
}
|
|
815
847
|
|
|
848
|
+
// Collect remaining summary lines and emit as a single notify at the end
|
|
849
|
+
// to avoid each line replacing the previous via showStatus() deduplication.
|
|
850
|
+
const summary: string[] = [];
|
|
851
|
+
|
|
816
852
|
if (optMissing.length > 0) {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
+ "
|
|
853
|
+
summary.push(
|
|
854
|
+
`${optMissing.length} optional dep${optMissing.length > 1 ? "s" : ""} not installed: ${optMissing.map((s) => s.dep.name).join(", ")}.`
|
|
855
|
+
+ "\nInstall individually when needed — see `/bootstrap status` for commands.",
|
|
820
856
|
);
|
|
821
857
|
}
|
|
822
858
|
|
|
@@ -826,13 +862,12 @@ async function interactiveSetup(pi: ExtensionAPI, ctx: CommandContext): Promise<
|
|
|
826
862
|
(r: AuthResult) => r.status === "ok" && r.provider !== "local",
|
|
827
863
|
);
|
|
828
864
|
if (!hasAnyCloudKey) {
|
|
829
|
-
|
|
830
|
-
"
|
|
865
|
+
summary.push(
|
|
866
|
+
"🔑 **No cloud API keys detected.**\n" +
|
|
831
867
|
"Omegon needs at least one provider key to function. The fastest options:\n" +
|
|
832
868
|
" • Anthropic: `/secrets configure ANTHROPIC_API_KEY` (get key at console.anthropic.com)\n" +
|
|
833
869
|
" • OpenAI: `/secrets configure OPENAI_API_KEY` (get key at platform.openai.com)\n" +
|
|
834
|
-
" • GitHub Copilot: `/login github` (requires Copilot subscription)
|
|
835
|
-
"warning"
|
|
870
|
+
" • GitHub Copilot: `/login github` (requires Copilot subscription)",
|
|
836
871
|
);
|
|
837
872
|
}
|
|
838
873
|
|
|
@@ -842,19 +877,21 @@ async function interactiveSetup(pi: ExtensionAPI, ctx: CommandContext): Promise<
|
|
|
842
877
|
const stillMissing = recheck.filter((s) => !s.available && (s.dep.tier === "core" || s.dep.tier === "recommended"));
|
|
843
878
|
|
|
844
879
|
if (stillMissing.length === 0 && hasAnyCloudKey) {
|
|
845
|
-
|
|
880
|
+
summary.push("🎉 Setup complete! All core and recommended dependencies are available.");
|
|
846
881
|
markDone();
|
|
847
882
|
} else if (stillMissing.length === 0) {
|
|
848
|
-
|
|
849
|
-
"\n✅ Dependencies installed. Configure an API key (see above) to start using Omegon.",
|
|
850
|
-
);
|
|
883
|
+
summary.push("✅ Dependencies installed. Configure an API key (see above) to start using Omegon.");
|
|
851
884
|
markDone();
|
|
852
885
|
} else {
|
|
853
|
-
|
|
854
|
-
|
|
886
|
+
summary.push(
|
|
887
|
+
`⚠️ ${stillMissing.length} dep${stillMissing.length > 1 ? "s" : ""} still missing. `
|
|
855
888
|
+ "Run `/bootstrap` again after installing manually.",
|
|
856
889
|
);
|
|
857
890
|
}
|
|
891
|
+
|
|
892
|
+
if (summary.length > 0) {
|
|
893
|
+
ctx.ui.notify(summary.join("\n\n"), "info");
|
|
894
|
+
}
|
|
858
895
|
}
|
|
859
896
|
|
|
860
897
|
async function installMissing(ctx: CommandContext, tiers: DepTier[]): Promise<void> {
|
|
@@ -951,9 +988,6 @@ function isSignificantLine(raw: string): boolean {
|
|
|
951
988
|
* piped so output is captured and forwarded through pi's notification
|
|
952
989
|
* system rather than fighting with the TUI renderer.
|
|
953
990
|
*
|
|
954
|
-
* A heartbeat tick fires every `heartbeatMs` so the operator knows the
|
|
955
|
-
* process is still alive during long compilations (e.g. cargo build).
|
|
956
|
-
*
|
|
957
991
|
* The install commands come exclusively from the static `deps.ts`
|
|
958
992
|
* registry and are never influenced by operator input.
|
|
959
993
|
*
|
|
@@ -963,7 +997,6 @@ export function runAsync(
|
|
|
963
997
|
cmd: string,
|
|
964
998
|
onLine: (line: string) => void,
|
|
965
999
|
timeoutMs: number = 600_000,
|
|
966
|
-
heartbeatMs: number = 15_000,
|
|
967
1000
|
): Promise<number> {
|
|
968
1001
|
return new Promise((resolve) => {
|
|
969
1002
|
const env = {
|
|
@@ -987,23 +1020,15 @@ export function runAsync(
|
|
|
987
1020
|
|
|
988
1021
|
let settled = false;
|
|
989
1022
|
let sigkillTimer: ReturnType<typeof setTimeout> | undefined;
|
|
990
|
-
let elapsedSec = 0;
|
|
991
1023
|
|
|
992
1024
|
const settle = (code: number) => {
|
|
993
1025
|
if (settled) return;
|
|
994
1026
|
settled = true;
|
|
995
1027
|
clearTimeout(timer);
|
|
996
|
-
clearInterval(heartbeat);
|
|
997
1028
|
clearTimeout(sigkillTimer);
|
|
998
1029
|
resolve(code);
|
|
999
1030
|
};
|
|
1000
1031
|
|
|
1001
|
-
// Heartbeat — fires every heartbeatMs while the process is running.
|
|
1002
|
-
const heartbeat = setInterval(() => {
|
|
1003
|
-
elapsedSec += heartbeatMs / 1000;
|
|
1004
|
-
onLine(` ⏳ still running… (${elapsedSec}s)`);
|
|
1005
|
-
}, heartbeatMs);
|
|
1006
|
-
|
|
1007
1032
|
// Forward captured lines from both streams.
|
|
1008
1033
|
const attachStream = (stream: NodeJS.ReadableStream | null) => {
|
|
1009
1034
|
if (!stream) return;
|
|
@@ -1084,6 +1109,15 @@ async function installDeps(ctx: CommandContext, deps: DepStatus[]): Promise<void
|
|
|
1084
1109
|
}
|
|
1085
1110
|
}
|
|
1086
1111
|
|
|
1112
|
+
// Stream install output: consecutive notify("info") calls update the
|
|
1113
|
+
// same text node in place via showStatus() deduplication. A final
|
|
1114
|
+
// notify("warning") pins the completed output permanently.
|
|
1115
|
+
let output = `${step} 📦 Installing ${dep.name}…`;
|
|
1116
|
+
const stream = (line: string) => {
|
|
1117
|
+
output += `\n${line}`;
|
|
1118
|
+
ctx.ui.notify(output, "info");
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1087
1121
|
// Check prerequisites — re-verify availability live (not from stale array)
|
|
1088
1122
|
if (dep.requires?.length) {
|
|
1089
1123
|
const unmet = dep.requires.filter((reqId) => {
|
|
@@ -1091,29 +1125,20 @@ async function installDeps(ctx: CommandContext, deps: DepStatus[]): Promise<void
|
|
|
1091
1125
|
return reqDep ? !reqDep.check() : false;
|
|
1092
1126
|
});
|
|
1093
1127
|
if (unmet.length > 0) {
|
|
1094
|
-
ctx.ui.notify(
|
|
1128
|
+
ctx.ui.notify(`${step} ⚠️ Skipping ${dep.name} — requires ${unmet.join(", ")} (not yet available)`, "info");
|
|
1095
1129
|
continue;
|
|
1096
1130
|
}
|
|
1097
1131
|
}
|
|
1098
1132
|
|
|
1099
1133
|
const cmd = bestInstallCmd(dep);
|
|
1100
1134
|
if (!cmd) {
|
|
1101
|
-
ctx.ui.notify(
|
|
1135
|
+
ctx.ui.notify(`${step} ⚠️ No install command available for ${dep.name} on this platform`, "info");
|
|
1102
1136
|
continue;
|
|
1103
1137
|
}
|
|
1104
1138
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
// so we can dump a readable block on failure.
|
|
1109
|
-
const outputLines: string[] = [];
|
|
1110
|
-
const exitCode = await runAsync(
|
|
1111
|
-
cmd,
|
|
1112
|
-
(line) => {
|
|
1113
|
-
outputLines.push(line);
|
|
1114
|
-
ctx.ui.notify(line);
|
|
1115
|
-
},
|
|
1116
|
-
);
|
|
1139
|
+
stream(` → \`${cmd}\``);
|
|
1140
|
+
|
|
1141
|
+
const exitCode = await runAsync(cmd, stream);
|
|
1117
1142
|
|
|
1118
1143
|
// Patch PATH immediately after installing bootstrapping deps so the rest
|
|
1119
1144
|
// of the install sequence can find them without a new shell.
|
|
@@ -1125,25 +1150,20 @@ async function installDeps(ctx: CommandContext, deps: DepStatus[]): Promise<void
|
|
|
1125
1150
|
}
|
|
1126
1151
|
|
|
1127
1152
|
if (exitCode === 0 && dep.check()) {
|
|
1128
|
-
|
|
1153
|
+
output += `\n${step} ✅ ${dep.name} installed successfully`;
|
|
1129
1154
|
} else if (exitCode === 124) {
|
|
1130
|
-
|
|
1155
|
+
output += `\n${step} ❌ ${dep.name} install timed out (10 min limit)`;
|
|
1156
|
+
} else if (exitCode === 0) {
|
|
1157
|
+
output += `\n${step} ⚠️ Command succeeded but ${dep.name} not found on PATH — you may need to open a new shell.`;
|
|
1131
1158
|
} else {
|
|
1132
|
-
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
: `Failed to install ${dep.name} (exit ${exitCode})`;
|
|
1137
|
-
const block = [
|
|
1138
|
-
`${step} ❌ ${status}`,
|
|
1139
|
-
"",
|
|
1140
|
-
"Output (last 20 lines):",
|
|
1141
|
-
"```",
|
|
1142
|
-
tail,
|
|
1143
|
-
"```",
|
|
1144
|
-
...(dep.url ? [`Manual install: ${dep.url}`] : []),
|
|
1145
|
-
].join("\n");
|
|
1146
|
-
ctx.ui.notify(block);
|
|
1159
|
+
output += `\n${step} ❌ Failed to install ${dep.name} (exit ${exitCode})`;
|
|
1160
|
+
const hints = dep.install.filter((o) => o.cmd !== cmd);
|
|
1161
|
+
if (hints.length > 0) output += `\n Alternative: \`${hints[0]!.cmd}\``;
|
|
1162
|
+
if (dep.url) output += `\n Manual install: ${dep.url}`;
|
|
1147
1163
|
}
|
|
1164
|
+
|
|
1165
|
+
// Pin the completed output permanently as warning so subsequent
|
|
1166
|
+
// showStatus() calls cannot overwrite it.
|
|
1167
|
+
ctx.ui.notify(output, "warning");
|
|
1148
1168
|
}
|
|
1149
1169
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omegon",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.25",
|
|
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",
|