@tryglen/cli 0.4.1 → 0.5.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.
- package/dist/_utils/codex-app-server.d.ts +11 -0
- package/dist/_utils/codex-app-server.js +90 -0
- package/dist/_utils/codex-app-server.js.map +1 -0
- package/dist/_utils/codex-hooks-merge.d.ts +20 -0
- package/dist/_utils/codex-hooks-merge.js +63 -0
- package/dist/_utils/codex-hooks-merge.js.map +1 -0
- package/dist/_utils/codex-hooks-setup.d.ts +8 -0
- package/dist/_utils/codex-hooks-setup.js +152 -0
- package/dist/_utils/codex-hooks-setup.js.map +1 -0
- package/dist/_utils/codex-paths.d.ts +4 -0
- package/dist/_utils/codex-paths.js +10 -0
- package/dist/_utils/codex-paths.js.map +1 -0
- package/dist/_utils/codex-repair-trace.d.ts +11 -0
- package/dist/_utils/codex-repair-trace.js +42 -0
- package/dist/_utils/codex-repair-trace.js.map +1 -0
- package/dist/_utils/codex-trust-toml.d.ts +7 -0
- package/dist/_utils/codex-trust-toml.js +42 -0
- package/dist/_utils/codex-trust-toml.js.map +1 -0
- package/dist/_utils/ensure-global-cli.d.ts +28 -0
- package/dist/_utils/ensure-global-cli.js +83 -0
- package/dist/_utils/ensure-global-cli.js.map +1 -0
- package/dist/bin.js +28 -2
- package/dist/bin.js.map +1 -1
- package/dist/commands/doctor.d.ts +4 -3
- package/dist/commands/doctor.js +47 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/ingest.js +24 -4
- package/dist/commands/ingest.js.map +1 -1
- package/dist/commands/install.d.ts +1 -1
- package/dist/commands/install.js +122 -25
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/session-start.d.ts +1 -1
- package/dist/commands/session-start.js +21 -4
- package/dist/commands/session-start.js.map +1 -1
- package/dist/commands/uninstall.d.ts +11 -0
- package/dist/commands/uninstall.js +41 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Result } from "./result.js";
|
|
2
|
+
type CodexHookEntry = {
|
|
3
|
+
readonly key: string;
|
|
4
|
+
readonly command: string;
|
|
5
|
+
readonly source: string;
|
|
6
|
+
readonly currentHash: string;
|
|
7
|
+
readonly trustStatus: string;
|
|
8
|
+
};
|
|
9
|
+
declare const listCodexHooks: (cwd: string) => Promise<Result<CodexHookEntry[], string>>;
|
|
10
|
+
export { listCodexHooks };
|
|
11
|
+
export type { CodexHookEntry };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { err, ok } from "./result.js";
|
|
3
|
+
import { cliVersion } from "./version.js";
|
|
4
|
+
const TIMEOUT_MS = 15_000;
|
|
5
|
+
const extractEntries = (msg) => {
|
|
6
|
+
const result = msg.result;
|
|
7
|
+
if (!result || !Array.isArray(result.data))
|
|
8
|
+
return null;
|
|
9
|
+
const entries = [];
|
|
10
|
+
for (const item of result.data) {
|
|
11
|
+
if (!Array.isArray(item.hooks))
|
|
12
|
+
continue;
|
|
13
|
+
for (const h of item.hooks) {
|
|
14
|
+
if (typeof h.key === "string" &&
|
|
15
|
+
typeof h.command === "string" &&
|
|
16
|
+
typeof h.source === "string" &&
|
|
17
|
+
typeof h.currentHash === "string" &&
|
|
18
|
+
typeof h.trustStatus === "string") {
|
|
19
|
+
entries.push({
|
|
20
|
+
key: h.key,
|
|
21
|
+
command: h.command,
|
|
22
|
+
source: h.source,
|
|
23
|
+
currentHash: h.currentHash,
|
|
24
|
+
trustStatus: h.trustStatus,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return entries;
|
|
30
|
+
};
|
|
31
|
+
const rpc = (id, method, params) => `${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`;
|
|
32
|
+
const listCodexHooks = (cwd) => new Promise((resolve) => {
|
|
33
|
+
const child = spawn("codex", ["app-server"], {
|
|
34
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
35
|
+
});
|
|
36
|
+
const finish = (r) => {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
child.kill();
|
|
39
|
+
resolve(r);
|
|
40
|
+
};
|
|
41
|
+
const timer = setTimeout(() => finish(err("codex app-server timed out")), TIMEOUT_MS);
|
|
42
|
+
child.on("error", () => finish(err("could not spawn codex app-server")));
|
|
43
|
+
child.on("close", () => finish(err("codex app-server exited before answering")));
|
|
44
|
+
const state = { buf: "" };
|
|
45
|
+
child.stdout.on("data", (chunk) => {
|
|
46
|
+
state.buf += chunk.toString();
|
|
47
|
+
const lines = state.buf.split("\n");
|
|
48
|
+
state.buf = lines.pop() ?? "";
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
if (line.trim() === "")
|
|
51
|
+
continue;
|
|
52
|
+
const parsed = (() => {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(line);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
})();
|
|
60
|
+
if (!parsed || typeof parsed !== "object")
|
|
61
|
+
continue;
|
|
62
|
+
const id = parsed.id;
|
|
63
|
+
const rpcError = parsed.error;
|
|
64
|
+
if ((id === 1 || id === 2) && rpcError) {
|
|
65
|
+
const msg = typeof rpcError.message === "string"
|
|
66
|
+
? rpcError.message
|
|
67
|
+
: "unknown error";
|
|
68
|
+
finish(err(`codex app-server error: ${msg}`));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (id === 1) {
|
|
72
|
+
child.stdin.write(rpc(2, "hooks/list", { cwds: [cwd] }));
|
|
73
|
+
}
|
|
74
|
+
if (id === 2) {
|
|
75
|
+
const entries = extractEntries(parsed);
|
|
76
|
+
finish(entries ? ok(entries) : err("unexpected hooks/list response shape"));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
// Defer the handshake one microtask so an immediate spawn failure
|
|
81
|
+
// (ENOENT — codex not installed) wins the race and resolves err before
|
|
82
|
+
// we start talking to a process that never existed.
|
|
83
|
+
queueMicrotask(() => {
|
|
84
|
+
child.stdin.write(rpc(1, "initialize", {
|
|
85
|
+
clientInfo: { name: "glen", title: "glen CLI", version: cliVersion },
|
|
86
|
+
}));
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
export { listCodexHooks };
|
|
90
|
+
//# sourceMappingURL=codex-app-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-app-server.js","sourceRoot":"","sources":["../../src/_utils/codex-app-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
declare const GLEN_CODEX_HOOK_ENTRIES: {
|
|
2
|
+
readonly SessionStart: "glen session-start --agent codex";
|
|
3
|
+
readonly UserPromptSubmit: "glen ingest --agent codex";
|
|
4
|
+
};
|
|
5
|
+
declare const GLEN_OWNER_PREFIX = "glen ";
|
|
6
|
+
type HookHandler = {
|
|
7
|
+
type: string;
|
|
8
|
+
command: string;
|
|
9
|
+
} & Record<string, unknown>;
|
|
10
|
+
type MatcherGroup = {
|
|
11
|
+
hooks: HookHandler[];
|
|
12
|
+
} & Record<string, unknown>;
|
|
13
|
+
type HooksFile = {
|
|
14
|
+
hooks: Record<string, MatcherGroup[]>;
|
|
15
|
+
};
|
|
16
|
+
declare const normalizeHooksFile: (parsed: unknown) => HooksFile | null;
|
|
17
|
+
declare const stripGlenHooks: (file: HooksFile) => HooksFile;
|
|
18
|
+
declare const withGlenHooks: (file: HooksFile) => HooksFile;
|
|
19
|
+
export { GLEN_CODEX_HOOK_ENTRIES, GLEN_OWNER_PREFIX, normalizeHooksFile, stripGlenHooks, withGlenHooks, };
|
|
20
|
+
export type { HooksFile };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// The canonical glen hook lines for Codex. FROZEN: must stay identical to
|
|
2
|
+
// packages/codex-plugin/hooks/hooks.json (enforced by ingest.contract.test.ts).
|
|
3
|
+
const GLEN_CODEX_HOOK_ENTRIES = {
|
|
4
|
+
SessionStart: "glen session-start --agent codex",
|
|
5
|
+
UserPromptSubmit: "glen ingest --agent codex",
|
|
6
|
+
};
|
|
7
|
+
// Owner marker: any handler whose command starts with "glen " is ours.
|
|
8
|
+
// Our commands are bare global-CLI invocations (no absolute paths), so this
|
|
9
|
+
// prefix is stable across machines and versions.
|
|
10
|
+
const GLEN_OWNER_PREFIX = "glen ";
|
|
11
|
+
const isGlenHandler = (h) => typeof h.command === "string" &&
|
|
12
|
+
h.command.trimStart().startsWith(GLEN_OWNER_PREFIX);
|
|
13
|
+
const isMatcherGroup = (g) => typeof g === "object" &&
|
|
14
|
+
g !== null &&
|
|
15
|
+
Array.isArray(g.hooks);
|
|
16
|
+
// Accepts Codex's wrapped form {"hooks": {...}} and the legacy root-level
|
|
17
|
+
// form (events at the JSON root — older supermemory versions wrote this).
|
|
18
|
+
// Returns null for anything that doesn't look like a hooks file (caller
|
|
19
|
+
// backs the file up and starts fresh).
|
|
20
|
+
const normalizeHooksFile = (parsed) => {
|
|
21
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const obj = parsed;
|
|
25
|
+
const root = typeof obj.hooks === "object" &&
|
|
26
|
+
obj.hooks !== null &&
|
|
27
|
+
!Array.isArray(obj.hooks)
|
|
28
|
+
? obj.hooks
|
|
29
|
+
: obj;
|
|
30
|
+
const events = {};
|
|
31
|
+
for (const [event, value] of Object.entries(root)) {
|
|
32
|
+
if (!Array.isArray(value) || !value.every(isMatcherGroup))
|
|
33
|
+
return null;
|
|
34
|
+
events[event] = value;
|
|
35
|
+
}
|
|
36
|
+
return { hooks: events };
|
|
37
|
+
};
|
|
38
|
+
const stripGlenHooks = (file) => {
|
|
39
|
+
const events = {};
|
|
40
|
+
for (const [event, groups] of Object.entries(file.hooks)) {
|
|
41
|
+
const kept = groups
|
|
42
|
+
.map((g) => ({ ...g, hooks: g.hooks.filter((h) => !isGlenHandler(h)) }))
|
|
43
|
+
.filter((g) => g.hooks.length > 0);
|
|
44
|
+
if (kept.length > 0)
|
|
45
|
+
events[event] = kept;
|
|
46
|
+
}
|
|
47
|
+
return { hooks: events };
|
|
48
|
+
};
|
|
49
|
+
// Idempotent strip-and-replace: remove any glen-owned entries, then append
|
|
50
|
+
// the canonical ones. Other tools' groups are preserved verbatim.
|
|
51
|
+
const withGlenHooks = (file) => {
|
|
52
|
+
const base = stripGlenHooks(file);
|
|
53
|
+
const events = { ...base.hooks };
|
|
54
|
+
for (const [event, command] of Object.entries(GLEN_CODEX_HOOK_ENTRIES)) {
|
|
55
|
+
events[event] = [
|
|
56
|
+
...(events[event] ?? []),
|
|
57
|
+
{ hooks: [{ type: "command", command }] },
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
return { hooks: events };
|
|
61
|
+
};
|
|
62
|
+
export { GLEN_CODEX_HOOK_ENTRIES, GLEN_OWNER_PREFIX, normalizeHooksFile, stripGlenHooks, withGlenHooks, };
|
|
63
|
+
//# sourceMappingURL=codex-hooks-merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-hooks-merge.js","sourceRoot":"","sources":["../../src/_utils/codex-hooks-merge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Result } from "./result.js";
|
|
2
|
+
declare const isCodexHooksInstalled: () => Promise<boolean>;
|
|
3
|
+
declare const installCodexHooks: () => Promise<Result<{
|
|
4
|
+
trusted: boolean;
|
|
5
|
+
backedUp: boolean;
|
|
6
|
+
}, string>>;
|
|
7
|
+
declare const uninstallCodexHooks: () => Promise<Result<void, string>>;
|
|
8
|
+
export { installCodexHooks, isCodexHooksInstalled, uninstallCodexHooks };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { listCodexHooks } from "./codex-app-server.js";
|
|
4
|
+
import { GLEN_OWNER_PREFIX, normalizeHooksFile, stripGlenHooks, withGlenHooks, } from "./codex-hooks-merge.js";
|
|
5
|
+
import { codexConfigTomlPath, codexHooksJsonPath } from "./codex-paths.js";
|
|
6
|
+
import { removeTrustBlocks, upsertTrustBlocks } from "./codex-trust-toml.js";
|
|
7
|
+
import { err, fromPromise, ok } from "./result.js";
|
|
8
|
+
import { statePath } from "./state.js";
|
|
9
|
+
// Marker recording that THIS machine opted into glen's codex hooks (gates
|
|
10
|
+
// doctor --auto self-repair so Claude-only users never get codex config
|
|
11
|
+
// written). Lives next to state.json so GLEN_STATE_PATH overrides cover it.
|
|
12
|
+
const markerPath = () => join(dirname(statePath()), "codex-hooks-installed.json");
|
|
13
|
+
const parseJson = (raw) => {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(raw);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
// Reads the marker, tolerating the legacy `{installedAt}` payload (no
|
|
22
|
+
// trustedKeys) — null means "not installed on this machine".
|
|
23
|
+
const readMarker = async () => {
|
|
24
|
+
const raw = await fromPromise(readFile(markerPath(), "utf-8"));
|
|
25
|
+
if (!raw.ok)
|
|
26
|
+
return null;
|
|
27
|
+
const parsed = parseJson(raw.value);
|
|
28
|
+
const trustedKeys = Array.isArray(parsed?.trustedKeys)
|
|
29
|
+
? parsed.trustedKeys.filter((k) => typeof k === "string")
|
|
30
|
+
: [];
|
|
31
|
+
return { trustedKeys };
|
|
32
|
+
};
|
|
33
|
+
const isCodexHooksInstalled = async () => (await readMarker()) !== null;
|
|
34
|
+
const atomicWrite = async (filePath, content) => {
|
|
35
|
+
const mk = await fromPromise(mkdir(dirname(filePath), { recursive: true }));
|
|
36
|
+
if (!mk.ok)
|
|
37
|
+
return mk;
|
|
38
|
+
// pid suffix: interactive install and the detached doctor repair may race.
|
|
39
|
+
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
40
|
+
const w = await fromPromise(writeFile(tmp, content));
|
|
41
|
+
if (!w.ok)
|
|
42
|
+
return w;
|
|
43
|
+
return fromPromise(rename(tmp, filePath));
|
|
44
|
+
};
|
|
45
|
+
// Reads + normalizes the existing hooks.json. `absent` distinguishes a
|
|
46
|
+
// missing file from an empty one. Unparseable → backed up during install
|
|
47
|
+
// (never clobber another tool's data silently) and treated as empty;
|
|
48
|
+
// uninstall passes backupOnUnparseable=false and skips the rewrite instead.
|
|
49
|
+
const readExistingHooks = async (backupOnUnparseable) => {
|
|
50
|
+
const hooksPath = codexHooksJsonPath();
|
|
51
|
+
const raw = await fromPromise(readFile(hooksPath, "utf-8"));
|
|
52
|
+
if (!raw.ok)
|
|
53
|
+
return ok({ file: { hooks: {} }, backedUp: false, absent: true });
|
|
54
|
+
const normalized = normalizeHooksFile(parseJson(raw.value));
|
|
55
|
+
if (normalized)
|
|
56
|
+
return ok({ file: normalized, backedUp: false, absent: false });
|
|
57
|
+
if (!backupOnUnparseable)
|
|
58
|
+
return ok({ file: { hooks: {} }, backedUp: true, absent: false });
|
|
59
|
+
const bak = `${hooksPath}.bak.${Date.now()}`;
|
|
60
|
+
const b = await fromPromise(writeFile(bak, raw.value));
|
|
61
|
+
if (!b.ok)
|
|
62
|
+
return err(`could not back up unparseable ${hooksPath}`);
|
|
63
|
+
return ok({ file: { hooks: {} }, backedUp: true, absent: false });
|
|
64
|
+
};
|
|
65
|
+
const glenEntriesFrom = (entries) => entries
|
|
66
|
+
.filter((e) => e.source === "user" &&
|
|
67
|
+
e.command.trimStart().startsWith(GLEN_OWNER_PREFIX))
|
|
68
|
+
.map((e) => ({ key: e.key, hash: e.currentHash }));
|
|
69
|
+
// Write trust blocks for glen's user-layer hooks. Returns the keys written
|
|
70
|
+
// (empty array = trust step failed; install still counts, manual fallback).
|
|
71
|
+
const trustGlenHooks = async () => {
|
|
72
|
+
const listed = await listCodexHooks(process.cwd());
|
|
73
|
+
if (!listed.ok)
|
|
74
|
+
return [];
|
|
75
|
+
const entries = glenEntriesFrom(listed.value);
|
|
76
|
+
if (entries.length === 0)
|
|
77
|
+
return [];
|
|
78
|
+
const tomlPath = codexConfigTomlPath();
|
|
79
|
+
const existing = await fromPromise(readFile(tomlPath, "utf-8"));
|
|
80
|
+
const updated = upsertTrustBlocks(existing.ok ? existing.value : "", entries);
|
|
81
|
+
const w = await atomicWrite(tomlPath, updated);
|
|
82
|
+
return w.ok ? entries.map((e) => e.key) : [];
|
|
83
|
+
};
|
|
84
|
+
const installCodexHooks = async () => {
|
|
85
|
+
const existing = await readExistingHooks(true);
|
|
86
|
+
if (!existing.ok)
|
|
87
|
+
return existing;
|
|
88
|
+
const merged = withGlenHooks(existing.value.file);
|
|
89
|
+
const w = await atomicWrite(codexHooksJsonPath(), `${JSON.stringify(merged, null, 2)}\n`);
|
|
90
|
+
if (!w.ok)
|
|
91
|
+
return err(`could not write ${codexHooksJsonPath()}`);
|
|
92
|
+
const trustedKeys = await trustGlenHooks();
|
|
93
|
+
// The marker gates doctor self-heal AND records the trusted keys for
|
|
94
|
+
// uninstall — a silent failure here would strand the trust blocks.
|
|
95
|
+
const m = await atomicWrite(markerPath(), `${JSON.stringify({ installedAt: new Date().toISOString(), trustedKeys }, null, 2)}\n`);
|
|
96
|
+
if (!m.ok)
|
|
97
|
+
return err(`could not write install marker ${markerPath()}`);
|
|
98
|
+
return ok({
|
|
99
|
+
trusted: trustedKeys.length > 0,
|
|
100
|
+
backedUp: existing.value.backedUp,
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
// Removes glen's trust blocks from config.toml (no-op when the file is
|
|
104
|
+
// unreadable or there is nothing to remove).
|
|
105
|
+
const removeGlenTrustBlocks = async (keys) => {
|
|
106
|
+
if (keys.length === 0)
|
|
107
|
+
return ok(undefined);
|
|
108
|
+
const tomlPath = codexConfigTomlPath();
|
|
109
|
+
const raw = await fromPromise(readFile(tomlPath, "utf-8"));
|
|
110
|
+
if (!raw.ok)
|
|
111
|
+
return ok(undefined);
|
|
112
|
+
const updated = removeTrustBlocks(raw.value, keys);
|
|
113
|
+
const w = await atomicWrite(tomlPath, updated);
|
|
114
|
+
return w.ok ? ok(undefined) : err(`could not write ${tomlPath}`);
|
|
115
|
+
};
|
|
116
|
+
const uninstallCodexHooks = async () => {
|
|
117
|
+
const marker = await readMarker();
|
|
118
|
+
const existing = await readExistingHooks(false);
|
|
119
|
+
if (!existing.ok)
|
|
120
|
+
return existing;
|
|
121
|
+
// Clean machine (no hooks.json): never touch ~/.codex or spawn the
|
|
122
|
+
// app-server — only remove marker-recorded trust blocks if any.
|
|
123
|
+
if (existing.value.absent) {
|
|
124
|
+
const t = await removeGlenTrustBlocks(marker?.trustedKeys ?? []);
|
|
125
|
+
if (!t.ok)
|
|
126
|
+
return t;
|
|
127
|
+
await fromPromise(unlink(markerPath()));
|
|
128
|
+
return ok(undefined);
|
|
129
|
+
}
|
|
130
|
+
// Query keys BEFORE stripping — after the strip the entries are gone.
|
|
131
|
+
// Union with the marker's keys so cleanup works without codex on PATH.
|
|
132
|
+
const listed = await listCodexHooks(process.cwd());
|
|
133
|
+
const liveKeys = listed.ok
|
|
134
|
+
? glenEntriesFrom(listed.value).map((e) => e.key)
|
|
135
|
+
: [];
|
|
136
|
+
const keys = [...new Set([...(marker?.trustedKeys ?? []), ...liveKeys])];
|
|
137
|
+
// Unparseable hooks.json (backedUp under backupOnUnparseable=false means
|
|
138
|
+
// "skip") → leave it alone; still clean up trust blocks + marker.
|
|
139
|
+
if (!existing.value.backedUp) {
|
|
140
|
+
const stripped = stripGlenHooks(existing.value.file);
|
|
141
|
+
const w = await atomicWrite(codexHooksJsonPath(), `${JSON.stringify(stripped, null, 2)}\n`);
|
|
142
|
+
if (!w.ok)
|
|
143
|
+
return err(`could not write ${codexHooksJsonPath()}`);
|
|
144
|
+
}
|
|
145
|
+
const t = await removeGlenTrustBlocks(keys);
|
|
146
|
+
if (!t.ok)
|
|
147
|
+
return t;
|
|
148
|
+
await fromPromise(unlink(markerPath()));
|
|
149
|
+
return ok(undefined);
|
|
150
|
+
};
|
|
151
|
+
export { installCodexHooks, isCodexHooksInstalled, uninstallCodexHooks };
|
|
152
|
+
//# sourceMappingURL=codex-hooks-setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-hooks-setup.js","sourceRoot":"","sources":["../../src/_utils/codex-hooks-setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
// CODEX_HOME is Codex's own override for its config dir — honoring it means
|
|
4
|
+
// tests AND the CI smoke sandbox the same way Codex does. An empty string is
|
|
5
|
+
// treated as unset (|| not ??) so it can't yield relative paths.
|
|
6
|
+
const codexHome = () => process.env.CODEX_HOME || join(homedir(), ".codex");
|
|
7
|
+
const codexHooksJsonPath = () => join(codexHome(), "hooks.json");
|
|
8
|
+
const codexConfigTomlPath = () => join(codexHome(), "config.toml");
|
|
9
|
+
export { codexHome, codexHooksJsonPath, codexConfigTomlPath };
|
|
10
|
+
//# sourceMappingURL=codex-paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-paths.js","sourceRoot":"","sources":["../../src/_utils/codex-paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,4EAA4E;AAC5E,6EAA6E;AAC7E,iEAAiE;AACjE,MAAM,SAAS,GAAG,GAAW,EAAE,CAC7B,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AAEtD,MAAM,kBAAkB,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,YAAY,CAAC,CAAC;AAEzE,MAAM,mBAAmB,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,aAAa,CAAC,CAAC;AAE3E,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type RepairTrace = {
|
|
2
|
+
readonly at: string;
|
|
3
|
+
readonly ok: boolean;
|
|
4
|
+
readonly error?: string;
|
|
5
|
+
};
|
|
6
|
+
declare const writeRepairTrace: (outcome: {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
}) => Promise<void>;
|
|
10
|
+
declare const readRepairTrace: () => Promise<RepairTrace | null>;
|
|
11
|
+
export { readRepairTrace, writeRepairTrace };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Best-effort trace of the last detached codex-hooks repair, surfaced by
|
|
2
|
+
// `glen doctor` (the repair child runs with stdio ignored — this file is
|
|
3
|
+
// its only observable output). Lives next to state.json.
|
|
4
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fromPromise } from "./result.js";
|
|
7
|
+
import { statePath } from "./state.js";
|
|
8
|
+
const tracePath = () => join(dirname(statePath()), "codex-repair.json");
|
|
9
|
+
const writeRepairTrace = async (outcome) => {
|
|
10
|
+
const trace = {
|
|
11
|
+
at: new Date().toISOString(),
|
|
12
|
+
ok: outcome.ok,
|
|
13
|
+
...(outcome.error ? { error: outcome.error } : {}),
|
|
14
|
+
};
|
|
15
|
+
const mk = await fromPromise(mkdir(dirname(tracePath()), { recursive: true }));
|
|
16
|
+
if (!mk.ok)
|
|
17
|
+
return;
|
|
18
|
+
await fromPromise(writeFile(tracePath(), `${JSON.stringify(trace, null, 2)}\n`));
|
|
19
|
+
};
|
|
20
|
+
const readRepairTrace = async () => {
|
|
21
|
+
const raw = await fromPromise(readFile(tracePath(), "utf-8"));
|
|
22
|
+
if (!raw.ok)
|
|
23
|
+
return null;
|
|
24
|
+
const parsed = (() => {
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(raw.value);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
})();
|
|
32
|
+
const t = parsed;
|
|
33
|
+
if (!t || typeof t.at !== "string" || typeof t.ok !== "boolean")
|
|
34
|
+
return null;
|
|
35
|
+
return {
|
|
36
|
+
at: t.at,
|
|
37
|
+
ok: t.ok,
|
|
38
|
+
...(typeof t.error === "string" ? { error: t.error } : {}),
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export { readRepairTrace, writeRepairTrace };
|
|
42
|
+
//# sourceMappingURL=codex-repair-trace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-repair-trace.js","sourceRoot":"","sources":["../../src/_utils/codex-repair-trace.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,yEAAyE;AACzE,yDAAyD;AACzD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAQvC,MAAM,SAAS,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,EAAE,mBAAmB,CAAC,CAAC;AAEhF,MAAM,gBAAgB,GAAG,KAAK,EAAE,OAG/B,EAAiB,EAAE;IAClB,MAAM,KAAK,GAAgB;QACzB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC5B,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACnD,CAAC;IACF,MAAM,EAAE,GAAG,MAAM,WAAW,CAC1B,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CACjD,CAAC;IACF,IAAI,CAAC,EAAE,CAAC,EAAE;QAAE,OAAO;IACnB,MAAM,WAAW,CACf,SAAS,CAAC,SAAS,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAC9D,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,KAAK,IAAiC,EAAE;IAC9D,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;IAC9D,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,MAAM,GAAG,CAAC,GAAY,EAAE;QAC5B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAY,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IACL,MAAM,CAAC,GAAG,MAAwC,CAAC;IACnD,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC7E,OAAO;QACL,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,GAAG,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3D,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type TrustEntry = {
|
|
2
|
+
readonly key: string;
|
|
3
|
+
readonly hash: string;
|
|
4
|
+
};
|
|
5
|
+
declare const removeTrustBlocks: (toml: string, keys: readonly string[]) => string;
|
|
6
|
+
declare const upsertTrustBlocks: (toml: string, entries: readonly TrustEntry[]) => string;
|
|
7
|
+
export { removeTrustBlocks, upsertTrustBlocks };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Surgical, line-based editing of Codex's user config.toml [hooks.state]
|
|
2
|
+
// trust blocks. We deliberately do NOT parse/re-stringify the whole file —
|
|
3
|
+
// that would destroy the user's comments and formatting. A block is the
|
|
4
|
+
// exact header line `[hooks.state."<key>"]` plus every line until the next
|
|
5
|
+
// `[` table header. Keys come verbatim from `codex app-server` hooks/list.
|
|
6
|
+
const escapeKey = (key) => key.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
7
|
+
const headerFor = (key) => `[hooks.state."${escapeKey(key)}"]`;
|
|
8
|
+
// Matches [hooks.state."<key>"] with optional whitespace, single- or
|
|
9
|
+
// double-quoted key, and an optional trailing comment. Codex itself writes
|
|
10
|
+
// the canonical tight form; the tolerance is cheap insurance against
|
|
11
|
+
// alternate-but-valid spellings.
|
|
12
|
+
const HEADER_RE = /^\s*\[\s*hooks\.state\s*\.\s*(?:"((?:[^"\\]|\\.)*)"|'([^']*)')\s*\]\s*(?:#.*)?$/;
|
|
13
|
+
const unescapeKey = (raw) => raw.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
14
|
+
const headerKeyOf = (line) => {
|
|
15
|
+
const m = HEADER_RE.exec(line);
|
|
16
|
+
if (!m)
|
|
17
|
+
return null;
|
|
18
|
+
return m[1] !== undefined ? unescapeKey(m[1]) : (m[2] ?? null);
|
|
19
|
+
};
|
|
20
|
+
const isTableHeader = (line) => line.trimStart().startsWith("[");
|
|
21
|
+
const removeTrustBlocks = (toml, keys) => {
|
|
22
|
+
const keySet = new Set(keys);
|
|
23
|
+
const result = toml.split("\n").reduce((acc, line) => {
|
|
24
|
+
const headerKey = headerKeyOf(line);
|
|
25
|
+
const startsBlock = headerKey !== null && keySet.has(headerKey);
|
|
26
|
+
const skipping = startsBlock || (acc.skipping && !isTableHeader(line));
|
|
27
|
+
if (!skipping)
|
|
28
|
+
acc.out.push(line);
|
|
29
|
+
return { out: acc.out, skipping };
|
|
30
|
+
}, { out: [], skipping: false });
|
|
31
|
+
return result.out.join("\n");
|
|
32
|
+
};
|
|
33
|
+
const upsertTrustBlocks = (toml, entries) => {
|
|
34
|
+
const cleaned = removeTrustBlocks(toml, entries.map((e) => e.key));
|
|
35
|
+
const blocks = entries
|
|
36
|
+
.map((e) => `${headerFor(e.key)}\ntrusted_hash = "${e.hash}"\n`)
|
|
37
|
+
.join("\n");
|
|
38
|
+
const base = cleaned.trimEnd();
|
|
39
|
+
return base.length === 0 ? blocks : `${base}\n\n${blocks}`;
|
|
40
|
+
};
|
|
41
|
+
export { removeTrustBlocks, upsertTrustBlocks };
|
|
42
|
+
//# sourceMappingURL=codex-trust-toml.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-trust-toml.js","sourceRoot":"","sources":["../../src/_utils/codex-trust-toml.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,2EAA2E;AAC3E,wEAAwE;AACxE,2EAA2E;AAC3E,2EAA2E;AAI3E,MAAM,SAAS,GAAG,CAAC,GAAW,EAAU,EAAE,CACxC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAElD,MAAM,SAAS,GAAG,CAAC,GAAW,EAAU,EAAE,CAAC,iBAAiB,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;AAE/E,qEAAqE;AACrE,2EAA2E;AAC3E,qEAAqE;AACrE,iCAAiC;AACjC,MAAM,SAAS,GACb,iFAAiF,CAAC;AAEpF,MAAM,WAAW,GAAG,CAAC,GAAW,EAAU,EAAE,CAC1C,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAElD,MAAM,WAAW,GAAG,CAAC,IAAY,EAAiB,EAAE;IAClD,MAAM,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;AACjE,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,IAAY,EAAW,EAAE,CAC9C,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AAEnC,MAAM,iBAAiB,GAAG,CAAC,IAAY,EAAE,IAAuB,EAAU,EAAE;IAC1E,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CACpC,CAAC,GAAyC,EAAE,IAAI,EAAE,EAAE;QAClD,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,WAAW,GAAG,SAAS,KAAK,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,WAAW,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,QAAQ;YAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC;IACpC,CAAC,EACD,EAAE,GAAG,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAC7B,CAAC;IACF,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CACxB,IAAY,EACZ,OAA8B,EACtB,EAAE;IACV,MAAM,OAAO,GAAG,iBAAiB,CAC/B,IAAI,EACJ,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAC1B,CAAC;IACF,MAAM,MAAM,GAAG,OAAO;SACnB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,IAAI,KAAK,CAAC;SAC/D,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAC/B,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,MAAM,EAAE,CAAC;AAC7D,CAAC,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type Outcome = {
|
|
2
|
+
readonly status: "present";
|
|
3
|
+
} | {
|
|
4
|
+
readonly status: "installed";
|
|
5
|
+
} | {
|
|
6
|
+
readonly status: "updated";
|
|
7
|
+
} | {
|
|
8
|
+
readonly status: "no-npm";
|
|
9
|
+
} | {
|
|
10
|
+
readonly status: "path-missing";
|
|
11
|
+
readonly binDir: string | null;
|
|
12
|
+
} | {
|
|
13
|
+
readonly status: "failed";
|
|
14
|
+
readonly permission: boolean;
|
|
15
|
+
readonly hadExisting: boolean;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Make sure a current `glen` binary is reachable on PATH, installing or
|
|
19
|
+
* updating it globally via npm when it isn't. `npx @tryglen/cli install`
|
|
20
|
+
* runs this CLI from npx's cache without ever linking a global binary —
|
|
21
|
+
* without this step the plugins' hooks would have no `glen` to call and
|
|
22
|
+
* silently do nothing.
|
|
23
|
+
*
|
|
24
|
+
* `onInstallStart(hadExisting)` fires just before the (slow) npm install so
|
|
25
|
+
* the caller can print a progress line first.
|
|
26
|
+
*/
|
|
27
|
+
declare const ensureGlobalCli: (onInstallStart?: (hadExisting: boolean) => void) => Outcome;
|
|
28
|
+
export { ensureGlobalCli };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { newer } from "./update.js";
|
|
4
|
+
import { cliVersion } from "./version.js";
|
|
5
|
+
import { isInstalled } from "./which.js";
|
|
6
|
+
/** Version of the `glen` already on PATH, or null when absent/unreadable. */
|
|
7
|
+
const installedVersion = () => {
|
|
8
|
+
try {
|
|
9
|
+
const out = execFileSync("glen", ["--version"], {
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
timeout: 10_000,
|
|
12
|
+
});
|
|
13
|
+
return /(\d+\.\d+\.\d+)/.exec(out)?.[1] ?? null;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/** npm's global bin dir (`<prefix>/bin`) for PATH guidance, null when unknown. */
|
|
20
|
+
const npmGlobalBinDir = () => {
|
|
21
|
+
try {
|
|
22
|
+
const prefix = execFileSync("npm", ["prefix", "-g"], {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
timeout: 10_000,
|
|
25
|
+
}).trim();
|
|
26
|
+
return prefix ? join(prefix, "bin") : null;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const installGlobally = () => {
|
|
33
|
+
try {
|
|
34
|
+
execFileSync("npm", ["install", "-g", "@tryglen/cli@latest"], {
|
|
35
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
36
|
+
encoding: "utf8",
|
|
37
|
+
timeout: 300_000,
|
|
38
|
+
});
|
|
39
|
+
return { ok: true, permission: false };
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const stderr = error && typeof error === "object" && "stderr" in error
|
|
43
|
+
? String(error.stderr ?? "")
|
|
44
|
+
: "";
|
|
45
|
+
return { ok: false, permission: /EACCES|permission denied/i.test(stderr) };
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Make sure a current `glen` binary is reachable on PATH, installing or
|
|
50
|
+
* updating it globally via npm when it isn't. `npx @tryglen/cli install`
|
|
51
|
+
* runs this CLI from npx's cache without ever linking a global binary —
|
|
52
|
+
* without this step the plugins' hooks would have no `glen` to call and
|
|
53
|
+
* silently do nothing.
|
|
54
|
+
*
|
|
55
|
+
* `onInstallStart(hadExisting)` fires just before the (slow) npm install so
|
|
56
|
+
* the caller can print a progress line first.
|
|
57
|
+
*/
|
|
58
|
+
const ensureGlobalCli = (onInstallStart) => {
|
|
59
|
+
const hadExisting = isInstalled("glen");
|
|
60
|
+
if (hadExisting) {
|
|
61
|
+
const version = installedVersion();
|
|
62
|
+
// Up to date (or ahead, e.g. a dev build) — nothing to do. A stale or
|
|
63
|
+
// unreadable version falls through to a reinstall.
|
|
64
|
+
if (version && !newer(cliVersion, version))
|
|
65
|
+
return { status: "present" };
|
|
66
|
+
}
|
|
67
|
+
if (!isInstalled("npm"))
|
|
68
|
+
return { status: "no-npm" };
|
|
69
|
+
onInstallStart?.(hadExisting);
|
|
70
|
+
const result = installGlobally();
|
|
71
|
+
if (!result.ok) {
|
|
72
|
+
return { status: "failed", permission: result.permission, hadExisting };
|
|
73
|
+
}
|
|
74
|
+
if (hadExisting)
|
|
75
|
+
return { status: "updated" };
|
|
76
|
+
if (isInstalled("glen"))
|
|
77
|
+
return { status: "installed" };
|
|
78
|
+
// npm succeeded but `glen` still doesn't resolve: the global bin dir is
|
|
79
|
+
// not on PATH (common with version managers / fresh Homebrew node).
|
|
80
|
+
return { status: "path-missing", binDir: npmGlobalBinDir() };
|
|
81
|
+
};
|
|
82
|
+
export { ensureGlobalCli };
|
|
83
|
+
//# sourceMappingURL=ensure-global-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ensure-global-cli.js","sourceRoot":"","sources":["../../src/_utils/ensure-global-cli.ts"],"names":[],"mappings":"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"}
|