holycodex 0.5.2 → 0.6.1-dev.29547206710.2
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/README.md +6 -89
- package/THIRD-PARTY-NOTICES.md +6 -2
- package/bin/holycodex.js +1 -1
- package/dist/cli.js +834 -0
- package/package.json +15 -27
- package/marketplace.json +0 -12
- package/plugin/.codex-plugin/plugin.json +0 -23
- package/plugin/.mcp.json +0 -7
- package/plugin/agents/explorer.toml +0 -6
- package/plugin/agents/librarian.toml +0 -6
- package/plugin/agents/worker.toml +0 -6
- package/plugin/hooks/hooks.json +0 -62
- package/plugin/runtime/bootstrap.js +0 -31
- package/plugin/runtime/cli.js +0 -235
- package/plugin/runtime/core-instructions.js +0 -4
- package/plugin/runtime/git-bash.js +0 -355
- package/plugin/runtime/lsp.js +0 -3665
- package/plugin/runtime/mcp-stdio-core.js +0 -209
- package/plugin/runtime/rules.js +0 -171
- package/plugin/skills/ast-grep/LICENSE +0 -21
- package/plugin/skills/ast-grep/SKILL.md +0 -20
- package/plugin/skills/ast-grep/references/cli.md +0 -3
- package/plugin/skills/ast-grep/references/install.md +0 -3
- package/plugin/skills/ast-grep/references/patterns.md +0 -3
- package/plugin/skills/ast-grep/references/pitfalls.md +0 -3
- package/plugin/skills/ast-grep/references/recipes.md +0 -9
- package/plugin/skills/ast-grep/references/sgconfig.md +0 -3
- package/plugin/skills/ast-grep/references/yaml-rules.md +0 -3
- package/plugin/skills/caveman/LICENSE +0 -21
- package/plugin/skills/caveman/SKILL.md +0 -31
- package/plugin/skills/compress/SKILL.md +0 -18
- package/plugin/skills/debugging/SKILL.md +0 -22
- package/plugin/skills/debugging/references/runtimes/README.md +0 -9
- package/plugin/skills/debugging/references/tools/README.md +0 -8
- package/plugin/skills/define-goal/LICENSE.txt +0 -201
- package/plugin/skills/define-goal/SKILL.md +0 -37
- package/plugin/skills/frontend/ATTRIBUTION.md +0 -218
- package/plugin/skills/frontend/LICENSE-Apache-2.0.txt +0 -201
- package/plugin/skills/frontend/SKILL.md +0 -50
- package/plugin/skills/frontend/references/perfection/README.md +0 -5
- package/plugin/skills/frontend/references/perfection/react-perf-tooling.md +0 -3
- package/plugin/skills/frontend/references/ui-ux-db/README.md +0 -5
- package/plugin/skills/frontend/references/ui-ux-db/data/charts.csv +0 -26
- package/plugin/skills/frontend/references/ui-ux-db/data/colors.csv +0 -162
- package/plugin/skills/frontend/references/ui-ux-db/data/icons.csv +0 -106
- package/plugin/skills/frontend/references/ui-ux-db/data/landing.csv +0 -35
- package/plugin/skills/frontend/references/ui-ux-db/data/products.csv +0 -162
- package/plugin/skills/frontend/references/ui-ux-db/data/react-performance.csv +0 -45
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/astro.csv +0 -54
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/flutter.csv +0 -53
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/html-tailwind.csv +0 -56
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/jetpack-compose.csv +0 -53
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/nextjs.csv +0 -53
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/nuxt-ui.csv +0 -51
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/nuxtjs.csv +0 -59
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/react-native.csv +0 -52
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/react.csv +0 -54
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/shadcn.csv +0 -61
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/svelte.csv +0 -54
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/swiftui.csv +0 -51
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/vue.csv +0 -50
- package/plugin/skills/frontend/references/ui-ux-db/data/styles.csv +0 -85
- package/plugin/skills/frontend/references/ui-ux-db/data/typography.csv +0 -74
- package/plugin/skills/frontend/references/ui-ux-db/data/ui-reasoning.csv +0 -162
- package/plugin/skills/frontend/references/ui-ux-db/data/ux-guidelines.csv +0 -100
- package/plugin/skills/frontend/references/ui-ux-db/data/web-interface.csv +0 -31
- package/plugin/skills/frontend/references/ui-ux-db/scripts/core.py +0 -262
- package/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +0 -1148
- package/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +0 -114
- package/plugin/skills/handoff/SKILL.md +0 -10
- package/plugin/skills/lsp/SKILL.md +0 -35
- package/plugin/skills/lsp-setup/SKILL.md +0 -18
- package/plugin/skills/lsp-setup/scripts/detect-lsp.ts +0 -233
- package/plugin/skills/lsp-setup/scripts/lsp-server-table.ts +0 -188
- package/plugin/skills/lsp-setup/scripts/tsconfig.json +0 -17
- package/plugin/skills/lsp-setup/scripts/verify-lsp.ts +0 -166
- package/plugin/skills/plan/SKILL.md +0 -24
- package/plugin/skills/plan-review/SKILL.md +0 -22
- package/plugin/skills/programming/SKILL.md +0 -33
- package/plugin/skills/programming/references/code-smells.md +0 -3
- package/plugin/skills/programming/references/go/README.md +0 -3
- package/plugin/skills/programming/references/logging.md +0 -3
- package/plugin/skills/programming/references/python/README.md +0 -3
- package/plugin/skills/programming/references/rust/README.md +0 -3
- package/plugin/skills/programming/references/rust-ub/README.md +0 -3
- package/plugin/skills/programming/references/typescript/README.md +0 -3
- package/plugin/skills/refactor/SKILL.md +0 -21
- package/plugin/skills/remove-ai-slops/SKILL.md +0 -20
- package/plugin/skills/rules/SKILL.md +0 -16
- package/plugin/skills/security-research/SKILL.md +0 -38
package/dist/cli.js
ADDED
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
import process$1 from "node:process";
|
|
2
|
+
import { access, copyFile, cp, lstat, mkdir, readFile, readdir, readlink, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir, tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { pluginRoot } from "@holycodex/plugin";
|
|
8
|
+
import { Buffer } from "node:buffer";
|
|
9
|
+
//#region packages/cli/src/catalog.ts
|
|
10
|
+
var VERSION = "0.6.1-dev.29547206710.2";
|
|
11
|
+
var SKILLS = [
|
|
12
|
+
"ast-grep",
|
|
13
|
+
"caveman",
|
|
14
|
+
"compress",
|
|
15
|
+
"debugging",
|
|
16
|
+
"define-goal",
|
|
17
|
+
"frontend",
|
|
18
|
+
"handoff",
|
|
19
|
+
"lsp",
|
|
20
|
+
"lsp-setup",
|
|
21
|
+
"plan",
|
|
22
|
+
"plan-review",
|
|
23
|
+
"programming",
|
|
24
|
+
"refactor",
|
|
25
|
+
"remove-slop",
|
|
26
|
+
"rules",
|
|
27
|
+
"security-research"
|
|
28
|
+
];
|
|
29
|
+
var AGENTS = [
|
|
30
|
+
"explorer",
|
|
31
|
+
"librarian",
|
|
32
|
+
"worker"
|
|
33
|
+
];
|
|
34
|
+
var ROOT_MODEL = {
|
|
35
|
+
model: "gpt-5.6-sol",
|
|
36
|
+
reasoningEffort: "medium"
|
|
37
|
+
};
|
|
38
|
+
var AGENT_MODELS = {
|
|
39
|
+
explorer: {
|
|
40
|
+
model: "gpt-5.6-luna",
|
|
41
|
+
reasoningEffort: "low"
|
|
42
|
+
},
|
|
43
|
+
librarian: {
|
|
44
|
+
model: "gpt-5.6-luna",
|
|
45
|
+
reasoningEffort: "low"
|
|
46
|
+
},
|
|
47
|
+
worker: {
|
|
48
|
+
model: "gpt-5.6-terra",
|
|
49
|
+
reasoningEffort: "high"
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var MANAGED_AGENT_MODEL_HISTORY = {
|
|
53
|
+
explorer: [{
|
|
54
|
+
model: "gpt-5.6-luna",
|
|
55
|
+
reasoningEffort: "low"
|
|
56
|
+
}],
|
|
57
|
+
librarian: [{
|
|
58
|
+
model: "gpt-5.6-luna",
|
|
59
|
+
reasoningEffort: "low"
|
|
60
|
+
}],
|
|
61
|
+
worker: [{
|
|
62
|
+
model: "gpt-5.6-luna",
|
|
63
|
+
reasoningEffort: "medium"
|
|
64
|
+
}, {
|
|
65
|
+
model: "gpt-5.6-terra",
|
|
66
|
+
reasoningEffort: "high"
|
|
67
|
+
}]
|
|
68
|
+
};
|
|
69
|
+
var GENERATED_RUNTIMES = [
|
|
70
|
+
"bootstrap.js",
|
|
71
|
+
"core-instructions.js",
|
|
72
|
+
"git-bash.js",
|
|
73
|
+
"git-bash-resolver.js",
|
|
74
|
+
"LICENSE-LSP-MIT.txt",
|
|
75
|
+
"lsp.js",
|
|
76
|
+
"mcp-stdio-core.js",
|
|
77
|
+
"rules.js"
|
|
78
|
+
];
|
|
79
|
+
var WINDOWS_SHELL_POLICY = "On native Windows, before the first shell action, inspect callable and deferred tools until `mcp__git_bash__run` is resolved. Use it for every shell command, including Git, Bash, POSIX, package, build, test, and script commands. If unavailable, stop and report the blocker. Never fall back to PowerShell or cmd.";
|
|
80
|
+
function effectiveMcpServers(platform) {
|
|
81
|
+
return {
|
|
82
|
+
...platform === "win32" ? { git_bash: {
|
|
83
|
+
command: "node",
|
|
84
|
+
args: ["runtime/git-bash.js", "mcp"],
|
|
85
|
+
cwd: ".",
|
|
86
|
+
enabled_tools: ["run"]
|
|
87
|
+
} } : {},
|
|
88
|
+
lsp: {
|
|
89
|
+
command: "node",
|
|
90
|
+
args: ["runtime/lsp.js", "mcp"],
|
|
91
|
+
cwd: "."
|
|
92
|
+
},
|
|
93
|
+
context7: {
|
|
94
|
+
command: "bunx",
|
|
95
|
+
args: ["@upstash/context7-mcp"]
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function requiredPackageRuntimes(platform) {
|
|
100
|
+
return platform === "win32" ? GENERATED_RUNTIMES : GENERATED_RUNTIMES.filter((file) => file !== "git-bash.js" && file !== "git-bash-resolver.js");
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region packages/git-bash-mcp/src/git-bash-resolver.ts
|
|
104
|
+
var GIT_BASH_ENV_KEY = "HOLYCODEX_GIT_BASH_PATH";
|
|
105
|
+
var PROGRAM_FILES = "C:\\Program Files\\Git\\bin\\bash.exe";
|
|
106
|
+
var PROGRAM_FILES_X86 = "C:\\Program Files (x86)\\Git\\bin\\bash.exe";
|
|
107
|
+
var INVALID_LAUNCHERS = ["\\windows\\system32\\", "\\microsoft\\windowsapps\\"];
|
|
108
|
+
function resolveGitBash(input) {
|
|
109
|
+
if (input.platform !== "win32") return {
|
|
110
|
+
found: true,
|
|
111
|
+
path: null,
|
|
112
|
+
source: "not-required",
|
|
113
|
+
checkedPaths: []
|
|
114
|
+
};
|
|
115
|
+
const checkedPaths = [];
|
|
116
|
+
const configured = input.env[GIT_BASH_ENV_KEY]?.trim();
|
|
117
|
+
if (configured) {
|
|
118
|
+
checkedPaths.push(configured);
|
|
119
|
+
return isBash(configured) && input.exists(configured) ? {
|
|
120
|
+
found: true,
|
|
121
|
+
path: configured,
|
|
122
|
+
source: "env",
|
|
123
|
+
checkedPaths
|
|
124
|
+
} : missing(checkedPaths);
|
|
125
|
+
}
|
|
126
|
+
for (const candidate of [{
|
|
127
|
+
path: PROGRAM_FILES,
|
|
128
|
+
source: "program-files"
|
|
129
|
+
}, {
|
|
130
|
+
path: PROGRAM_FILES_X86,
|
|
131
|
+
source: "program-files-x86"
|
|
132
|
+
}]) {
|
|
133
|
+
checkedPaths.push(candidate.path);
|
|
134
|
+
if (input.exists(candidate.path)) return {
|
|
135
|
+
found: true,
|
|
136
|
+
path: candidate.path,
|
|
137
|
+
source: candidate.source,
|
|
138
|
+
checkedPaths
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
for (const raw of input.where("bash")) {
|
|
142
|
+
const candidate = raw.trim();
|
|
143
|
+
if (!candidate) continue;
|
|
144
|
+
checkedPaths.push(candidate);
|
|
145
|
+
const normalized = candidate.replaceAll("/", "\\").toLowerCase();
|
|
146
|
+
if (INVALID_LAUNCHERS.some((part) => normalized.includes(part))) continue;
|
|
147
|
+
if (isBash(candidate) && input.exists(candidate)) return {
|
|
148
|
+
found: true,
|
|
149
|
+
path: candidate,
|
|
150
|
+
source: "path",
|
|
151
|
+
checkedPaths
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return missing(checkedPaths);
|
|
155
|
+
}
|
|
156
|
+
function resolveGitBashForCurrentProcess(input = {}) {
|
|
157
|
+
return resolveGitBash({
|
|
158
|
+
platform: input.platform ?? process.platform,
|
|
159
|
+
env: input.env ?? process.env,
|
|
160
|
+
exists: existsSync,
|
|
161
|
+
where: (command) => {
|
|
162
|
+
try {
|
|
163
|
+
return execFileSync("where", [command], { encoding: "utf8" }).split(/\r?\n/).filter(Boolean);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error instanceof Error) return [];
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function isBash(path) {
|
|
172
|
+
return path.toLowerCase().endsWith("bash.exe");
|
|
173
|
+
}
|
|
174
|
+
function missing(checkedPaths) {
|
|
175
|
+
return {
|
|
176
|
+
found: false,
|
|
177
|
+
checkedPaths,
|
|
178
|
+
installHint: `Git Bash required. Install: winget install --id Git.Git -e --source winget\nCustom path: set ${GIT_BASH_ENV_KEY}=C:\\path\\to\\bash.exe`
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region packages/mcp-stdio-core/src/process.ts
|
|
183
|
+
var TRUNCATED_MARKER = "\n... diagnostic output truncated ...\n";
|
|
184
|
+
async function runManagedProcess(input) {
|
|
185
|
+
return await new Promise((resolve) => {
|
|
186
|
+
const child = spawn(input.command, [...input.args], {
|
|
187
|
+
...input.cwd === void 0 ? {} : { cwd: input.cwd },
|
|
188
|
+
...input.env === void 0 ? {} : { env: input.env },
|
|
189
|
+
stdio: [
|
|
190
|
+
"pipe",
|
|
191
|
+
"pipe",
|
|
192
|
+
"pipe"
|
|
193
|
+
],
|
|
194
|
+
windowsHide: true,
|
|
195
|
+
detached: input.platform !== "win32"
|
|
196
|
+
});
|
|
197
|
+
let stdout = {
|
|
198
|
+
head: "",
|
|
199
|
+
tail: "",
|
|
200
|
+
truncated: false
|
|
201
|
+
};
|
|
202
|
+
let stderr = {
|
|
203
|
+
head: "",
|
|
204
|
+
tail: "",
|
|
205
|
+
truncated: false
|
|
206
|
+
};
|
|
207
|
+
let timedOut = false;
|
|
208
|
+
let matched = false;
|
|
209
|
+
let settled = false;
|
|
210
|
+
let terminationFallback;
|
|
211
|
+
const finish = (exitCode, error) => {
|
|
212
|
+
if (settled) return;
|
|
213
|
+
settled = true;
|
|
214
|
+
clearTimeout(timeout);
|
|
215
|
+
if (terminationFallback !== void 0) clearTimeout(terminationFallback);
|
|
216
|
+
resolve({
|
|
217
|
+
exitCode,
|
|
218
|
+
stdout: outputText(stdout),
|
|
219
|
+
stderr: outputText(stderr),
|
|
220
|
+
timedOut,
|
|
221
|
+
matched,
|
|
222
|
+
outputTruncated: stdout.truncated || stderr.truncated,
|
|
223
|
+
...error === void 0 ? {} : { error }
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
const terminate = () => {
|
|
227
|
+
killProcessTree(child, input.platform);
|
|
228
|
+
terminationFallback ??= setTimeout(() => finish(child.exitCode), 2e3);
|
|
229
|
+
terminationFallback.unref();
|
|
230
|
+
};
|
|
231
|
+
const inspectMatch = () => {
|
|
232
|
+
if (matched || input.matchOutput === void 0) return;
|
|
233
|
+
if (input.matchOutput(`${outputText(stdout)}\n${outputText(stderr)}`)) {
|
|
234
|
+
matched = true;
|
|
235
|
+
terminate();
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
child.stdout.on("data", (chunk) => {
|
|
239
|
+
stdout = appendOutput(stdout, chunk.toString(), input.maxOutputChars);
|
|
240
|
+
inspectMatch();
|
|
241
|
+
});
|
|
242
|
+
child.stderr.on("data", (chunk) => {
|
|
243
|
+
stderr = appendOutput(stderr, chunk.toString(), input.maxOutputChars);
|
|
244
|
+
inspectMatch();
|
|
245
|
+
});
|
|
246
|
+
child.once("error", (error) => finish(null, error.message));
|
|
247
|
+
child.once("close", (code) => finish(code));
|
|
248
|
+
const timeout = setTimeout(() => {
|
|
249
|
+
timedOut = true;
|
|
250
|
+
terminate();
|
|
251
|
+
}, input.timeoutMs);
|
|
252
|
+
timeout.unref();
|
|
253
|
+
if (input.stdin === void 0) child.stdin.end();
|
|
254
|
+
else child.stdin.end(input.stdin);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function killProcessTree(child, platform, signal = "SIGTERM", runTaskkill = (command, args) => spawnSync(command, [...args], {
|
|
258
|
+
stdio: "ignore",
|
|
259
|
+
windowsHide: true
|
|
260
|
+
})) {
|
|
261
|
+
if (platform === "win32" && child.pid !== void 0) {
|
|
262
|
+
const result = runTaskkill("taskkill", [
|
|
263
|
+
"/pid",
|
|
264
|
+
String(child.pid),
|
|
265
|
+
"/f",
|
|
266
|
+
"/t"
|
|
267
|
+
]);
|
|
268
|
+
if (result.error === void 0 && result.status === 0) return;
|
|
269
|
+
}
|
|
270
|
+
if (platform !== "win32" && child.pid !== void 0) try {
|
|
271
|
+
process.kill(-child.pid, signal);
|
|
272
|
+
return;
|
|
273
|
+
} catch {}
|
|
274
|
+
try {
|
|
275
|
+
child.kill(signal);
|
|
276
|
+
} catch {}
|
|
277
|
+
}
|
|
278
|
+
function appendOutput(state, chunk, limit) {
|
|
279
|
+
const safeLimit = Math.max(1, limit);
|
|
280
|
+
const headLimit = Math.ceil(safeLimit / 2);
|
|
281
|
+
const tailLimit = Math.floor(safeLimit / 2);
|
|
282
|
+
if (!state.truncated && state.head.length + chunk.length <= safeLimit) return {
|
|
283
|
+
...state,
|
|
284
|
+
head: state.head + chunk
|
|
285
|
+
};
|
|
286
|
+
const combined = state.truncated ? chunk : state.head + chunk;
|
|
287
|
+
return {
|
|
288
|
+
head: state.truncated ? state.head : combined.slice(0, headLimit),
|
|
289
|
+
tail: `${state.tail}${combined.slice(state.truncated ? 0 : headLimit)}`.slice(-tailLimit),
|
|
290
|
+
truncated: true
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function outputText(state) {
|
|
294
|
+
return state.truncated ? `${state.head}${TRUNCATED_MARKER}${state.tail}` : state.head;
|
|
295
|
+
}
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region packages/cli/src/doctor.ts
|
|
298
|
+
async function runCommand(name, args, platform) {
|
|
299
|
+
const result = await runManagedProcess({
|
|
300
|
+
command: name,
|
|
301
|
+
args,
|
|
302
|
+
platform,
|
|
303
|
+
timeoutMs: 1e4,
|
|
304
|
+
maxOutputChars: 64 * 1024
|
|
305
|
+
});
|
|
306
|
+
return {
|
|
307
|
+
ok: result.exitCode === 0 && !result.timedOut && result.error === void 0,
|
|
308
|
+
output: `${result.stdout}\n${result.stderr}`.trim() || result.error || ""
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
async function startContext7(platform) {
|
|
312
|
+
const result = await runManagedProcess({
|
|
313
|
+
command: "bunx",
|
|
314
|
+
args: ["@upstash/context7-mcp"],
|
|
315
|
+
platform,
|
|
316
|
+
timeoutMs: 15e3,
|
|
317
|
+
maxOutputChars: 128 * 1024,
|
|
318
|
+
stdin: `${JSON.stringify({
|
|
319
|
+
jsonrpc: "2.0",
|
|
320
|
+
id: 1,
|
|
321
|
+
method: "initialize",
|
|
322
|
+
params: {
|
|
323
|
+
protocolVersion: "2025-03-26",
|
|
324
|
+
capabilities: {},
|
|
325
|
+
clientInfo: {
|
|
326
|
+
name: "holycodex-doctor",
|
|
327
|
+
version: VERSION
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
})}\n`,
|
|
331
|
+
matchOutput: (output) => output.includes("\"serverInfo\"") || output.includes("\"capabilities\"")
|
|
332
|
+
});
|
|
333
|
+
const diagnostic = `${result.stdout}\n${result.stderr}`.trim() || result.error || "";
|
|
334
|
+
return {
|
|
335
|
+
ok: result.matched,
|
|
336
|
+
packageFailure: /(?:404|failed to resolve|package.*not found|error: GET)/i.test(diagnostic),
|
|
337
|
+
detail: diagnostic
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
var defaultRuntime$1 = {
|
|
341
|
+
platform: process.platform,
|
|
342
|
+
command: (name, args) => runCommand(name, args, process.platform),
|
|
343
|
+
context7: () => startContext7(process.platform),
|
|
344
|
+
gitBash: resolveGitBashForCurrentProcess
|
|
345
|
+
};
|
|
346
|
+
function check(id, status, code, detail, fix) {
|
|
347
|
+
return {
|
|
348
|
+
id,
|
|
349
|
+
status,
|
|
350
|
+
code,
|
|
351
|
+
detail,
|
|
352
|
+
...fix === void 0 ? {} : { fix }
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
async function missingFiles(root, paths) {
|
|
356
|
+
const missing = [];
|
|
357
|
+
for (const path of paths) try {
|
|
358
|
+
await access(join(root, path));
|
|
359
|
+
} catch {
|
|
360
|
+
missing.push(path);
|
|
361
|
+
}
|
|
362
|
+
return missing;
|
|
363
|
+
}
|
|
364
|
+
function rootString(config, key) {
|
|
365
|
+
return new RegExp(`^\\s*${key}\\s*=\\s*"([^"]+)"`, "m").exec(config)?.[1];
|
|
366
|
+
}
|
|
367
|
+
function tableBoolean(config, table, key) {
|
|
368
|
+
const body = new RegExp(`^\\s*\\[${table.replaceAll(".", "\\.")}]\\s*$([\\s\\S]*?)(?=^\\s*\\[|(?![\\s\\S]))`, "m").exec(config)?.[1];
|
|
369
|
+
const value = body === void 0 ? void 0 : new RegExp(`^\\s*${key}\\s*=\\s*(true|false)`, "m").exec(body)?.[1];
|
|
370
|
+
return value === void 0 ? void 0 : value === "true";
|
|
371
|
+
}
|
|
372
|
+
function autonomy(config) {
|
|
373
|
+
const approval = rootString(config, "approval_policy");
|
|
374
|
+
const sandbox = rootString(config, "sandbox_mode");
|
|
375
|
+
const network = tableBoolean(config, "sandbox_workspace_write", "network_access");
|
|
376
|
+
if (approval === "on-request" && sandbox === "workspace-write" && network === true) return "safe-workspace";
|
|
377
|
+
if (approval === "never" && sandbox === "workspace-write" && network === true) return "autonomous-workspace";
|
|
378
|
+
if (approval === "never" && sandbox === "danger-full-access") return "dangerous";
|
|
379
|
+
return "unknown";
|
|
380
|
+
}
|
|
381
|
+
async function doctor(home = process.env.CODEX_HOME ?? join(homedir(), ".codex"), runtime = defaultRuntime$1) {
|
|
382
|
+
const checks = [];
|
|
383
|
+
const pluginRoot = join(home, "plugins", "cache", "holycodex", "holycodex", VERSION);
|
|
384
|
+
const configPath = join(home, "config.toml");
|
|
385
|
+
const missing = await missingFiles(pluginRoot, [
|
|
386
|
+
".codex-plugin/plugin.json",
|
|
387
|
+
".mcp.json",
|
|
388
|
+
"LICENSE-OH-MY-OPENCODE-SLIM-MIT.txt",
|
|
389
|
+
"hooks/hooks.json",
|
|
390
|
+
...requiredPackageRuntimes(runtime.platform).map((file) => `runtime/${file}`),
|
|
391
|
+
...AGENTS.map((name) => `agents/${name}.toml`),
|
|
392
|
+
...SKILLS.map((name) => `skills/${name}/SKILL.md`)
|
|
393
|
+
]);
|
|
394
|
+
checks.push(missing.length === 0 ? check("package", "ok", "package-ready", `Plugin ${VERSION}, generated runtime, hooks, three agents, and ${SKILLS.length} skills are present.`) : check("package", "error", "package-incomplete", `Missing ${missing.join(", ")}.`, "Reinstall HolyCodex."));
|
|
395
|
+
let mcp;
|
|
396
|
+
try {
|
|
397
|
+
mcp = JSON.parse(await readFile(join(pluginRoot, ".mcp.json"), "utf8"));
|
|
398
|
+
} catch (error) {
|
|
399
|
+
checks.push(check("mcp-config", "error", "malformed-mcp-config", error instanceof Error ? error.message : "Invalid MCP JSON.", "Reinstall HolyCodex."));
|
|
400
|
+
}
|
|
401
|
+
const servers = mcp?.mcpServers;
|
|
402
|
+
const requiredMcps = runtime.platform === "win32" ? ["git_bash", "lsp"] : ["lsp"];
|
|
403
|
+
for (const name of requiredMcps) checks.push(servers?.[name] === void 0 ? check(`mcp-${name}`, "error", "missing-required-mcp", `${name} is not configured.`, "Reinstall HolyCodex.") : check(`mcp-${name}`, "ok", "required-mcp-ready", `${name} is configured locally.`));
|
|
404
|
+
const gitBashConfig = servers?.git_bash;
|
|
405
|
+
if (runtime.platform === "win32" && gitBashConfig !== void 0) {
|
|
406
|
+
const expected = effectiveMcpServers("win32").git_bash;
|
|
407
|
+
checks.push(JSON.stringify(gitBashConfig) === JSON.stringify(expected) ? check("mcp-git_bash-config", "ok", "git-bash-mcp-config-ready", "Git Bash MCP exposes only run through the supported allowlist.") : check("mcp-git_bash-config", "error", "invalid-git-bash-mcp-config", "Git Bash MCP command or enabled_tools configuration is stale.", "Reinstall HolyCodex."));
|
|
408
|
+
} else if (runtime.platform !== "win32" && gitBashConfig !== void 0) checks.push(check("mcp-git_bash-config", "error", "unexpected-git-bash-mcp", "Git Bash MCP must not be installed on non-Windows platforms.", "Reinstall HolyCodex for this platform."));
|
|
409
|
+
const context7 = servers?.context7;
|
|
410
|
+
const obsoleteAuth = context7 !== void 0 && [
|
|
411
|
+
"headers",
|
|
412
|
+
"env",
|
|
413
|
+
"authorization",
|
|
414
|
+
"apiKey"
|
|
415
|
+
].some((key) => key in context7);
|
|
416
|
+
if (context7 === void 0) checks.push(check("context7-config", "error", "missing-context7", "Context7 is not configured.", "Reinstall HolyCodex."));
|
|
417
|
+
else if (typeof context7.url === "string") checks.push(check("context7-config", "error", "obsolete-context7-remote", "Context7 still uses a hosted URL.", "Reinstall to use local bunx Context7."));
|
|
418
|
+
else if (obsoleteAuth) checks.push(check("context7-config", "error", "obsolete-context7-auth", "Context7 contains obsolete authentication settings.", "Remove auth settings and reinstall."));
|
|
419
|
+
else if (context7.command !== "bunx" || JSON.stringify(context7.args) !== JSON.stringify(["@upstash/context7-mcp"])) checks.push(check("context7-config", "error", "wrong-context7-package", "Expected bunx @upstash/context7-mcp.", "Repair .mcp.json or reinstall."));
|
|
420
|
+
else checks.push(check("context7-config", "ok", "local-context7-config", "Local no-auth Context7 is configured."));
|
|
421
|
+
const bun = await runtime.command("bun", ["--version"]);
|
|
422
|
+
const bunx = await runtime.command("bunx", ["--version"]);
|
|
423
|
+
checks.push(bun.ok ? check("bun", "ok", "bun-ready", `Bun ${bun.output || "available"}.`) : check("bun", "error", "missing-bun", "Bun is unavailable.", "Install or repair Bun."));
|
|
424
|
+
checks.push(bunx.ok ? check("bunx", "ok", "bunx-ready", `bunx ${bunx.output || "available"}.`) : check("bunx", "error", "missing-bunx", "bunx is unavailable.", "Repair the Bun installation."));
|
|
425
|
+
if (bun.ok && bunx.ok && checks.some((item) => item.code === "local-context7-config")) {
|
|
426
|
+
const started = await runtime.context7();
|
|
427
|
+
checks.push(started.ok ? check("context7-startup", "ok", "context7-healthy", "Context7 completed a bounded MCP handshake.") : started.packageFailure ? check("context7-startup", "error", "context7-package-resolution-failed", started.detail || "Context7 package resolution failed.", "Check network/package availability.") : check("context7-startup", "error", "context7-startup-failed", started.detail || "Context7 did not complete an MCP handshake within 15 seconds.", runtime.platform === "win32" ? "Run bunx @upstash/context7-mcp in Git Bash." : "Run bunx @upstash/context7-mcp in the native shell."));
|
|
428
|
+
}
|
|
429
|
+
if (runtime.platform === "win32") {
|
|
430
|
+
const gitBash = runtime.gitBash();
|
|
431
|
+
checks.push(gitBash.found ? check("git-bash", "ok", "git-bash-ready", `Git Bash: ${gitBash.path ?? "configured"}.`) : check("git-bash", "error", "missing-git-bash", "Git Bash is required on Windows but unavailable.", gitBash.installHint));
|
|
432
|
+
} else checks.push(check("git-bash", "ok", "git-bash-not-applicable", "Git Bash is not applicable on this platform."));
|
|
433
|
+
let config = "";
|
|
434
|
+
try {
|
|
435
|
+
config = await readFile(configPath, "utf8");
|
|
436
|
+
} catch {
|
|
437
|
+
checks.push(check("codex-config", "error", "missing-codex-config", `Missing ${configPath}.`, "Run holycodex install."));
|
|
438
|
+
}
|
|
439
|
+
const mode = autonomy(config);
|
|
440
|
+
checks.push(mode === "unknown" ? check("autonomy", "error", "invalid-autonomy-config", "Approval, sandbox, and network settings do not match a supported mode.", "Rerun install with the intended autonomy flag.") : mode === "dangerous" ? check("autonomy", "warning", "dangerous-autonomy", "Explicit dangerous autonomy is active; workspace containment is removed.") : check("autonomy", "ok", `${mode}-ready`, mode === "safe-workspace" ? "Safe workspace autonomy is active." : "Approval-free workspace autonomy is active."));
|
|
441
|
+
checks.push(tableBoolean(config, "features", "default_mode_request_user_input") === true ? check("user-input", "ok", "user-input-ready", "default_mode_request_user_input is enabled.") : check("user-input", "error", "user-input-disabled", "default_mode_request_user_input is not enabled.", "Rerun holycodex install."));
|
|
442
|
+
checks.push(/^\s*status_line\s*=\s*\[[\s\S]*?context-remaining[\s\S]*?]/m.test(config) ? check("context-visibility", "warning", "context-visible-support-unverified", "status_line includes context-remaining. Current official Codex config documents this item, but publishes no minimum compatible Codex version.") : check("context-visibility", "error", "context-hidden", "status_line does not include context-remaining.", "Rerun holycodex install."));
|
|
443
|
+
const codex = await runtime.command("codex", ["--version"]);
|
|
444
|
+
checks.push(codex.ok ? check("codex", "ok", "codex-version", codex.output || "Codex is available.") : check("codex", "warning", "codex-version-unavailable", "Codex version could not be read; status-line compatibility cannot be independently confirmed."));
|
|
445
|
+
const agentModelFailures = [];
|
|
446
|
+
for (const agent of AGENTS) try {
|
|
447
|
+
const text = await readFile(join(pluginRoot, "agents", `${agent}.toml`), "utf8");
|
|
448
|
+
const expected = AGENT_MODELS[agent];
|
|
449
|
+
if (!text.includes(`model = "${expected.model}"`) || !text.includes(`model_reasoning_effort = "${expected.reasoningEffort}"`)) agentModelFailures.push(agent);
|
|
450
|
+
} catch {
|
|
451
|
+
agentModelFailures.push(agent);
|
|
452
|
+
}
|
|
453
|
+
checks.push(agentModelFailures.length === 0 ? check("agent-models", "ok", "agent-models-ready", "Explorer and librarian use Luna low; worker uses Terra high.") : check("agent-models", "error", "agent-models-stale", `Agent model configuration is stale for ${agentModelFailures.join(", ")}.`, "Reinstall HolyCodex."));
|
|
454
|
+
return {
|
|
455
|
+
healthy: !checks.some((item) => item.status === "error"),
|
|
456
|
+
autonomy: mode,
|
|
457
|
+
checks
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
//#endregion
|
|
461
|
+
//#region packages/cli/src/config.ts
|
|
462
|
+
var START = "# >>> holycodex managed >>>";
|
|
463
|
+
var END = "# <<< holycodex managed <<<";
|
|
464
|
+
var ORIGINAL_ROOT = "# holycodex original root: ";
|
|
465
|
+
var ORIGINAL_TABLE_KEY = "# holycodex original table key: ";
|
|
466
|
+
var OLD_NAMESPACES = [
|
|
467
|
+
"marketplaces.sisyphuslabs",
|
|
468
|
+
"plugins.\"omo@sisyphuslabs\"",
|
|
469
|
+
"marketplaces.lazycodex",
|
|
470
|
+
"plugins.\"omo@lazycodex\"",
|
|
471
|
+
"marketplaces.code-yeongyu-codex-plugins",
|
|
472
|
+
"plugins.\"omo@code-yeongyu-codex-plugins\"",
|
|
473
|
+
"agents.plan",
|
|
474
|
+
"agents.metis",
|
|
475
|
+
"agents.momus",
|
|
476
|
+
"agents.oracle",
|
|
477
|
+
"agents.sisyphus",
|
|
478
|
+
"agents.prometheus",
|
|
479
|
+
"agents.atlas",
|
|
480
|
+
"agents.hephaestus",
|
|
481
|
+
"hooks.state.\"omo@sisyphuslabs",
|
|
482
|
+
"hooks.state.\"omo@lazycodex",
|
|
483
|
+
"hooks.state.\"omo@code-yeongyu-codex-plugins"
|
|
484
|
+
];
|
|
485
|
+
function removeManaged(input) {
|
|
486
|
+
const escapedStart = START.replaceAll(">", "\\>");
|
|
487
|
+
const escapedEnd = END.replaceAll("<", "\\<");
|
|
488
|
+
return input.replace(new RegExp(`${escapedStart}([\\s\\S]*?)${escapedEnd}(?:\\r?\\n){0,2}`, "g"), (_match, body) => {
|
|
489
|
+
const encoded = body.match(/^# holycodex original root: ([A-Za-z0-9+/=]+)$/m)?.[1];
|
|
490
|
+
if (encoded !== void 0) return `${Buffer.from(encoded, "base64").toString("utf8")}\n`;
|
|
491
|
+
const tableKey = body.match(/^# holycodex original table key: ([A-Za-z0-9+/=]+)$/m)?.[1];
|
|
492
|
+
return tableKey === void 0 ? "" : `${Buffer.from(tableKey, "base64").toString("utf8")}\n`;
|
|
493
|
+
}).trim();
|
|
494
|
+
}
|
|
495
|
+
function removeLegacyOmo(input) {
|
|
496
|
+
return input.split(/(?=^\s*\[)/m).filter((section) => {
|
|
497
|
+
const header = /^\s*\[([^\]]+)]/.exec(section)?.[1];
|
|
498
|
+
if (header === void 0) return true;
|
|
499
|
+
if (OLD_NAMESPACES.some((name) => header === name || header.startsWith(`${name}.`) || name.includes("\"omo@") && header.startsWith(name))) return false;
|
|
500
|
+
return ![
|
|
501
|
+
"agents.explorer",
|
|
502
|
+
"agents.librarian",
|
|
503
|
+
"agents.worker"
|
|
504
|
+
].some((name) => header === name || header.startsWith(`${name}.`)) || !/(?:sisyphuslabs|omo@|oh-my|code-yeongyu)/i.test(section);
|
|
505
|
+
}).join("").trimEnd();
|
|
506
|
+
}
|
|
507
|
+
function injectTableKey(input, table, key, value) {
|
|
508
|
+
const match = new RegExp(`^\\s*\\[${table.replaceAll(".", "\\.")}]\\s*$`, "m").exec(input);
|
|
509
|
+
const tail = match === null ? "" : input.slice(match.index + match[0].length);
|
|
510
|
+
const tableEnd = nextTableBoundary(tail);
|
|
511
|
+
const tableBody = tableEnd < 0 ? tail : tail.slice(0, tableEnd);
|
|
512
|
+
const originalKey = new RegExp(`^[ \\t]*${key}[ \\t]*=.*$`, "m").exec(tableBody)?.[0];
|
|
513
|
+
const managed = `${START}\n${originalKey === void 0 ? "" : `${ORIGINAL_TABLE_KEY}${Buffer.from(originalKey).toString("base64")}\n`}${key} = ${value}\n${END}`;
|
|
514
|
+
if (match === null) return `${input.trimEnd()}\n\n${START}\n[${table}]\n${key} = ${value}\n${END}`.trim();
|
|
515
|
+
const bodyStart = match.index + match[0].length;
|
|
516
|
+
const next = nextTableBoundary(input.slice(bodyStart));
|
|
517
|
+
const bodyEnd = next < 0 ? input.length : bodyStart + next;
|
|
518
|
+
const cleanedBody = input.slice(bodyStart, bodyEnd).replace(new RegExp(`^\\s*${key}\\s*=.*\\r?\\n?`, "gm"), "").trim();
|
|
519
|
+
const suffix = input.slice(bodyEnd).trimStart();
|
|
520
|
+
return `${input.slice(0, bodyStart)}\n${cleanedBody ? `${cleanedBody}\n` : ""}${managed}${suffix ? `\n${suffix}` : ""}`.trim();
|
|
521
|
+
}
|
|
522
|
+
function nextTableBoundary(input) {
|
|
523
|
+
const header = /^\s*\[/m.exec(input)?.index ?? -1;
|
|
524
|
+
const managedHeader = /^# >>> holycodex managed >>>\r?\n\s*\[/m.exec(input)?.index ?? -1;
|
|
525
|
+
if (header < 0) return managedHeader;
|
|
526
|
+
if (managedHeader < 0) return header;
|
|
527
|
+
return Math.min(header, managedHeader);
|
|
528
|
+
}
|
|
529
|
+
function rootValue(input, key) {
|
|
530
|
+
if (key === "status_line") return /^\s*status_line\s*=\s*\[[\s\S]*?^\s*]\s*(?:#.*)?$/m.exec(input)?.[0] ?? /^\s*status_line\s*=.*$/m.exec(input)?.[0];
|
|
531
|
+
return new RegExp(`^\\s*${key}\\s*=.*$`, "m").exec(input)?.[0];
|
|
532
|
+
}
|
|
533
|
+
function removeRootValue(input, value) {
|
|
534
|
+
return value === void 0 ? input : input.replace(value, "");
|
|
535
|
+
}
|
|
536
|
+
function mergedStatusLine(original) {
|
|
537
|
+
if (original === void 0) return "[\"model-with-reasoning\", \"context-remaining\", \"current-dir\"]";
|
|
538
|
+
const items = [...original.slice(original.indexOf("=") + 1).matchAll(/"((?:\\.|[^"\\])*)"/g)].map((match) => JSON.parse(`"${match[1]}"`));
|
|
539
|
+
if (!items.includes("context-remaining")) items.push("context-remaining");
|
|
540
|
+
return `[${items.map((item) => JSON.stringify(item)).join(", ")}]`;
|
|
541
|
+
}
|
|
542
|
+
function installConfig(input, mode, _platform) {
|
|
543
|
+
const base = removeLegacyOmo(removeManaged(input));
|
|
544
|
+
const firstTable = base.search(/^\s*\[/m);
|
|
545
|
+
const root = firstTable < 0 ? base : base.slice(0, firstTable);
|
|
546
|
+
const tables = firstTable < 0 ? "" : base.slice(firstTable);
|
|
547
|
+
const controlled = [
|
|
548
|
+
"approval_policy",
|
|
549
|
+
"sandbox_mode",
|
|
550
|
+
"max_concurrent_threads_per_session",
|
|
551
|
+
"status_line"
|
|
552
|
+
].map((key) => rootValue(root, key));
|
|
553
|
+
const originalRoot = root.trim();
|
|
554
|
+
const preservedRoot = controlled.reduce(removeRootValue, root).trim();
|
|
555
|
+
const hasModel = /^\s*model\s*=/m.test(preservedRoot);
|
|
556
|
+
const hasEffort = /^\s*model_reasoning_effort\s*=/m.test(preservedRoot);
|
|
557
|
+
const model = hasModel ? "" : `model = "${ROOT_MODEL.model}"\n`;
|
|
558
|
+
const effort = hasEffort ? "" : `model_reasoning_effort = "${ROOT_MODEL.reasoningEffort}"\n`;
|
|
559
|
+
const approval = mode === "default" ? "on-request" : "never";
|
|
560
|
+
const sandbox = mode === "dangerous" ? "danger-full-access" : "workspace-write";
|
|
561
|
+
let configured = `${`${START}\n${originalRoot ? `${ORIGINAL_ROOT}${Buffer.from(originalRoot).toString("base64")}\n` : ""}${model}${effort}${preservedRoot ? `${preservedRoot}\n` : ""}approval_policy = "${approval}"\nsandbox_mode = "${sandbox}"\nstatus_line = ${mergedStatusLine(controlled[3])}\n${END}`}${tables ? `\n\n${tables}` : ""}`;
|
|
562
|
+
configured = injectTableKey(configured, "features", "default_mode_request_user_input", "true");
|
|
563
|
+
configured = injectTableKey(configured, "features", "multi_agent", "true");
|
|
564
|
+
configured = injectTableKey(configured, "agents", "max_threads", "2");
|
|
565
|
+
configured = injectTableKey(configured, "agents", "max_depth", "1");
|
|
566
|
+
if (mode !== "dangerous") configured = injectTableKey(configured, "sandbox_workspace_write", "network_access", "true");
|
|
567
|
+
for (const agent of AGENTS) configured = injectTableKey(configured, `agents.${agent}`, "config_file", `"holycodex/agents/${agent}.toml"`);
|
|
568
|
+
const plugin = `${START}\n[marketplaces.holycodex]\nsource = "https://github.com/davidbasilefilho/holycodex.git"\n\n[plugins."holycodex@holycodex"]\nenabled = true\n${END}`;
|
|
569
|
+
return `${configured.trim()}\n\n${plugin}\n`;
|
|
570
|
+
}
|
|
571
|
+
//#endregion
|
|
572
|
+
//#region packages/cli/src/files.ts
|
|
573
|
+
async function exists(path) {
|
|
574
|
+
try {
|
|
575
|
+
await stat(path);
|
|
576
|
+
return true;
|
|
577
|
+
} catch (error) {
|
|
578
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
|
|
579
|
+
throw error;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
async function backup(path, root) {
|
|
583
|
+
if (!await exists(path)) return void 0;
|
|
584
|
+
const target = join(root, path.replace(/^([A-Za-z]:)?[\\/]+/, "").replaceAll(":", ""));
|
|
585
|
+
await mkdir(dirname(target), { recursive: true });
|
|
586
|
+
await copyBackup(path, target);
|
|
587
|
+
return target;
|
|
588
|
+
}
|
|
589
|
+
async function copyBackup(source, target) {
|
|
590
|
+
const metadata = await lstat(source);
|
|
591
|
+
if (metadata.isSymbolicLink()) {
|
|
592
|
+
await writeFile(`${target}.symlink`, await readlink(source), "utf8");
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (!metadata.isDirectory()) {
|
|
596
|
+
await copyFile(source, target);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
await mkdir(target, { recursive: true });
|
|
600
|
+
for (const entry of await readdir(source)) await copyBackup(join(source, entry), join(target, entry));
|
|
601
|
+
}
|
|
602
|
+
async function atomicWrite(path, content) {
|
|
603
|
+
await mkdir(dirname(path), { recursive: true });
|
|
604
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
605
|
+
await writeFile(temporary, content, "utf8");
|
|
606
|
+
await rename(temporary, path);
|
|
607
|
+
}
|
|
608
|
+
async function readText(path) {
|
|
609
|
+
return await exists(path) ? readFile(path, "utf8") : "";
|
|
610
|
+
}
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region packages/cli/src/install.ts
|
|
613
|
+
var defaultRuntime = {
|
|
614
|
+
platform: process.platform,
|
|
615
|
+
gitBash: resolveGitBashForCurrentProcess
|
|
616
|
+
};
|
|
617
|
+
function paths(home = process.env.CODEX_HOME ?? join(homedir(), ".codex")) {
|
|
618
|
+
const marketplaceCache = join(home, "plugins", "cache", "holycodex");
|
|
619
|
+
const cacheRoot = join(marketplaceCache, "holycodex");
|
|
620
|
+
return {
|
|
621
|
+
home,
|
|
622
|
+
config: join(home, "config.toml"),
|
|
623
|
+
marketplaceCache,
|
|
624
|
+
cacheRoot,
|
|
625
|
+
cache: join(cacheRoot, VERSION),
|
|
626
|
+
agents: join(home, "holycodex", "agents"),
|
|
627
|
+
legacy: [
|
|
628
|
+
join(home, "plugins", "cache", "sisyphuslabs", "omo"),
|
|
629
|
+
join(home, "plugins", "cache", "lazycodex", "omo"),
|
|
630
|
+
join(home, "plugins", "cache", "code-yeongyu-codex-plugins", "omo")
|
|
631
|
+
]
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
function backupRoot() {
|
|
635
|
+
return join(tmpdir(), "holycodex-backups", (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-"));
|
|
636
|
+
}
|
|
637
|
+
function assertGitBashReady(platform, resolution) {
|
|
638
|
+
if (platform !== "win32") return;
|
|
639
|
+
if (!resolution.found) throw new Error(resolution.installHint);
|
|
640
|
+
}
|
|
641
|
+
async function install(options, runtime = defaultRuntime) {
|
|
642
|
+
assertGitBashReady(runtime.platform, runtime.gitBash());
|
|
643
|
+
const target = paths();
|
|
644
|
+
const root = backupRoot();
|
|
645
|
+
const backups = [
|
|
646
|
+
await backup(target.config, root),
|
|
647
|
+
await backup(target.marketplaceCache, root),
|
|
648
|
+
await backup(target.agents, root),
|
|
649
|
+
...await Promise.all(target.legacy.map((path) => backup(path, root)))
|
|
650
|
+
].filter((path) => path !== void 0);
|
|
651
|
+
const config = installConfig(await readText(target.config), options.autonomy, runtime.platform);
|
|
652
|
+
await atomicWrite(target.config, config);
|
|
653
|
+
await rm(target.marketplaceCache, {
|
|
654
|
+
recursive: true,
|
|
655
|
+
force: true
|
|
656
|
+
});
|
|
657
|
+
await mkdir(dirname(target.cache), { recursive: true });
|
|
658
|
+
await cp(pluginRoot, target.cache, { recursive: true });
|
|
659
|
+
await writePlatformPlugin(target.cache, runtime.platform);
|
|
660
|
+
const existingAgentPreferences = await readAgentPreferences(target.agents);
|
|
661
|
+
await rm(target.agents, {
|
|
662
|
+
recursive: true,
|
|
663
|
+
force: true
|
|
664
|
+
});
|
|
665
|
+
await cp(join(pluginRoot, "agents"), target.agents, { recursive: true });
|
|
666
|
+
await writePlatformAgents(target.agents, runtime.platform);
|
|
667
|
+
await preserveAgentPreferences(target.agents, existingAgentPreferences);
|
|
668
|
+
const removedLegacy = [];
|
|
669
|
+
for (const path of target.legacy) {
|
|
670
|
+
if (!await exists(path)) continue;
|
|
671
|
+
await rm(path, { recursive: true });
|
|
672
|
+
removedLegacy.push(path);
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
action: "install",
|
|
676
|
+
changed: [
|
|
677
|
+
target.config,
|
|
678
|
+
target.cache,
|
|
679
|
+
target.agents,
|
|
680
|
+
...removedLegacy
|
|
681
|
+
],
|
|
682
|
+
backups
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
async function readAgentPreferences(root) {
|
|
686
|
+
const preferences = {};
|
|
687
|
+
await Promise.all(AGENTS.map(async (agent) => {
|
|
688
|
+
const source = await readText(join(root, `${agent}.toml`));
|
|
689
|
+
const model = tomlString(source, "model");
|
|
690
|
+
const reasoningEffort = tomlString(source, "model_reasoning_effort");
|
|
691
|
+
if (model === void 0 && reasoningEffort === void 0) return;
|
|
692
|
+
if (!MANAGED_AGENT_MODEL_HISTORY[agent].some((item) => item.model === model && item.reasoningEffort === reasoningEffort)) preferences[agent] = {
|
|
693
|
+
...model === void 0 ? {} : { model },
|
|
694
|
+
...reasoningEffort === void 0 ? {} : { reasoningEffort }
|
|
695
|
+
};
|
|
696
|
+
}));
|
|
697
|
+
return preferences;
|
|
698
|
+
}
|
|
699
|
+
async function preserveAgentPreferences(root, preferences) {
|
|
700
|
+
await Promise.all(AGENTS.map(async (agent) => {
|
|
701
|
+
const preference = preferences[agent];
|
|
702
|
+
if (preference === void 0) return;
|
|
703
|
+
const path = join(root, `${agent}.toml`);
|
|
704
|
+
let source = await readText(path);
|
|
705
|
+
if (preference.model !== void 0) source = replaceTomlString(source, "model", preference.model);
|
|
706
|
+
if (preference.reasoningEffort !== void 0) source = replaceTomlString(source, "model_reasoning_effort", preference.reasoningEffort);
|
|
707
|
+
await atomicWrite(path, source);
|
|
708
|
+
}));
|
|
709
|
+
}
|
|
710
|
+
function tomlString(input, key) {
|
|
711
|
+
return new RegExp(`^${key}\\s*=\\s*"([^"]+)"`, "m").exec(input)?.[1];
|
|
712
|
+
}
|
|
713
|
+
function replaceTomlString(input, key, value) {
|
|
714
|
+
return input.replace(new RegExp(`^${key}\\s*=.*$`, "m"), `${key} = ${JSON.stringify(value)}`);
|
|
715
|
+
}
|
|
716
|
+
async function writePlatformPlugin(root, platform) {
|
|
717
|
+
await atomicWrite(join(root, ".mcp.json"), `${JSON.stringify({ mcpServers: effectiveMcpServers(platform) }, null, 2)}\n`);
|
|
718
|
+
await writePlatformAgents(join(root, "agents"), platform);
|
|
719
|
+
}
|
|
720
|
+
async function writePlatformAgents(root, platform) {
|
|
721
|
+
if (platform === "win32") return;
|
|
722
|
+
await Promise.all(AGENTS.map(async (agent) => {
|
|
723
|
+
const path = join(root, `${agent}.toml`);
|
|
724
|
+
await atomicWrite(path, (await readText(path)).replace(`${WINDOWS_SHELL_POLICY}\n\n`, ""));
|
|
725
|
+
}));
|
|
726
|
+
}
|
|
727
|
+
async function cleanup(_options) {
|
|
728
|
+
const target = paths();
|
|
729
|
+
const root = backupRoot();
|
|
730
|
+
const backups = [
|
|
731
|
+
await backup(target.config, root),
|
|
732
|
+
await backup(target.marketplaceCache, root),
|
|
733
|
+
await backup(target.agents, root)
|
|
734
|
+
].filter((path) => path !== void 0);
|
|
735
|
+
const changed = [];
|
|
736
|
+
if (await exists(target.config)) {
|
|
737
|
+
const current = await readText(target.config);
|
|
738
|
+
const unmanaged = removeManaged(current);
|
|
739
|
+
const cleaned = `${unmanaged}\n`;
|
|
740
|
+
if (unmanaged.length === 0 && current.includes("# >>> holycodex managed >>>")) {
|
|
741
|
+
await rm(target.config);
|
|
742
|
+
changed.push(target.config);
|
|
743
|
+
} else if (cleaned !== current) {
|
|
744
|
+
await atomicWrite(target.config, cleaned);
|
|
745
|
+
changed.push(target.config);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (await exists(target.marketplaceCache)) {
|
|
749
|
+
await rm(target.marketplaceCache, { recursive: true });
|
|
750
|
+
changed.push(target.marketplaceCache);
|
|
751
|
+
}
|
|
752
|
+
if (await exists(target.agents)) {
|
|
753
|
+
await rm(target.agents, { recursive: true });
|
|
754
|
+
changed.push(target.agents);
|
|
755
|
+
}
|
|
756
|
+
return {
|
|
757
|
+
action: "cleanup",
|
|
758
|
+
changed,
|
|
759
|
+
backups
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
//#endregion
|
|
763
|
+
//#region packages/cli/src/presentation.ts
|
|
764
|
+
var RESET = "\x1B[0m";
|
|
765
|
+
var BOLD = "\x1B[1m";
|
|
766
|
+
var CYAN = "\x1B[36m";
|
|
767
|
+
var GREEN = "\x1B[32m";
|
|
768
|
+
var YELLOW = "\x1B[33m";
|
|
769
|
+
var RED = "\x1B[31m";
|
|
770
|
+
var DIM = "\x1B[2m";
|
|
771
|
+
function paint(enabled, code, text) {
|
|
772
|
+
return enabled ? `${code}${text}${RESET}` : text;
|
|
773
|
+
}
|
|
774
|
+
function supportsColor(isTTY, noColor) {
|
|
775
|
+
return isTTY === true && noColor === void 0;
|
|
776
|
+
}
|
|
777
|
+
function renderHelp(version, color) {
|
|
778
|
+
const title = paint(color, `${BOLD}${CYAN}`, `HOLYCODEX ${version}`);
|
|
779
|
+
const section = (text) => paint(color, BOLD, text);
|
|
780
|
+
const muted = (text) => paint(color, DIM, text);
|
|
781
|
+
return `${title}\n${muted("Lean Codex toolkit installer and doctor")}\n\n${section("USAGE")}\n holycodex <command> [options]\n\n${section("COMMANDS")}\n install Install or update HolyCodex\n cleanup Remove HolyCodex-owned state\n doctor Diagnose installation and runtime\n\n${section("OPTIONS")}\n --help Show help\n --version Show version\n --no-tui Accepted; commands remain noninteractive\n --codex-autonomous Never ask; keep workspace sandbox\n --no-codex-autonomous Safe interactive defaults\n --dangerous-codex-autonomous Never ask; disable filesystem sandbox\n --json Print machine-readable output\n`;
|
|
782
|
+
}
|
|
783
|
+
function renderDoctor(result, color) {
|
|
784
|
+
return `${result.healthy ? paint(color, GREEN, "✓ HolyCodex doctor: healthy") : paint(color, RED, "✗ HolyCodex doctor: issues found")}\n${result.checks.map((item) => {
|
|
785
|
+
const label = item.status.toUpperCase().padEnd(7);
|
|
786
|
+
const line = ` ${paint(color, item.status === "ok" ? GREEN : item.status === "warning" ? YELLOW : RED, label)} ${item.id} ${item.detail}`;
|
|
787
|
+
return item.fix === void 0 ? line : `${line}\n ${paint(color, DIM, `Fix: ${item.fix}`)}`;
|
|
788
|
+
}).join("\n")}\n`;
|
|
789
|
+
}
|
|
790
|
+
function renderRunResult(result, color) {
|
|
791
|
+
return `${paint(color, GREEN, `✓ HolyCodex ${result.action} complete`)}\n Changed: ${result.changed.length}\n Backups: ${result.backups.length}\n`;
|
|
792
|
+
}
|
|
793
|
+
function renderNotice(kind, message, color) {
|
|
794
|
+
return `${paint(color, kind === "warning" ? RED : YELLOW, `! ${kind === "warning" ? "WARNING" : "NOTICE"}`)} ${message}\n`;
|
|
795
|
+
}
|
|
796
|
+
//#endregion
|
|
797
|
+
//#region packages/cli/src/cli.ts
|
|
798
|
+
async function main() {
|
|
799
|
+
const args = process$1.argv.slice(2);
|
|
800
|
+
const stdoutColor = supportsColor(process$1.stdout.isTTY, process$1.env.NO_COLOR);
|
|
801
|
+
const stderrColor = supportsColor(process$1.stderr.isTTY, process$1.env.NO_COLOR);
|
|
802
|
+
if (args.includes("--help") || args.length === 0) {
|
|
803
|
+
process$1.stdout.write(renderHelp(VERSION, stdoutColor));
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (args.includes("--version")) {
|
|
807
|
+
process$1.stdout.write(`${VERSION}\n`);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const command = args.find((arg) => !arg.startsWith("--"));
|
|
811
|
+
const autonomyFlags = args.filter((arg) => [
|
|
812
|
+
"--codex-autonomous",
|
|
813
|
+
"--no-codex-autonomous",
|
|
814
|
+
"--dangerous-codex-autonomous"
|
|
815
|
+
].includes(arg));
|
|
816
|
+
if (autonomyFlags.length > 1) throw new Error(`Conflicting autonomy flags: ${autonomyFlags.join(", ")}`);
|
|
817
|
+
const options = {
|
|
818
|
+
autonomy: args.includes("--dangerous-codex-autonomous") ? "dangerous" : args.includes("--codex-autonomous") ? "autonomous" : "default",
|
|
819
|
+
json: args.includes("--json")
|
|
820
|
+
};
|
|
821
|
+
if (command === "doctor") {
|
|
822
|
+
const result = await doctor();
|
|
823
|
+
process$1.stdout.write(options.json ? `${JSON.stringify(result)}\n` : renderDoctor(result, stdoutColor));
|
|
824
|
+
if (!result.healthy) process$1.exitCode = 1;
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
if (options.autonomy === "dangerous") process$1.stderr.write(renderNotice("warning", "Dangerous autonomy disables approvals and filesystem sandboxing.", stderrColor));
|
|
828
|
+
else if (options.autonomy === "autonomous") process$1.stderr.write(renderNotice("notice", "--codex-autonomous is now workspace-contained. Use --dangerous-codex-autonomous only for unrestricted behavior.", stderrColor));
|
|
829
|
+
const result = command === "install" ? await install(options) : command === "cleanup" ? await cleanup(options) : void 0;
|
|
830
|
+
if (result === void 0) throw new Error(`Unknown command: ${command ?? ""}`);
|
|
831
|
+
process$1.stdout.write(options.json ? `${JSON.stringify(result)}\n` : renderRunResult(result, stdoutColor));
|
|
832
|
+
}
|
|
833
|
+
await main();
|
|
834
|
+
//#endregion
|