holycodex 0.2.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.
Files changed (93) hide show
  1. package/LICENSE.md +82 -0
  2. package/README.md +107 -0
  3. package/THIRD-PARTY-NOTICES.md +7 -0
  4. package/bin/holycodex.js +2 -0
  5. package/marketplace.json +12 -0
  6. package/package.json +59 -0
  7. package/plugin/.codex-plugin/plugin.json +29 -0
  8. package/plugin/.mcp.json +8 -0
  9. package/plugin/agents/explorer.toml +6 -0
  10. package/plugin/agents/librarian.toml +6 -0
  11. package/plugin/agents/worker.toml +6 -0
  12. package/plugin/hooks/hooks.json +93 -0
  13. package/plugin/runtime/bootstrap.js +23 -0
  14. package/plugin/runtime/cli.js +233 -0
  15. package/plugin/runtime/git-bash.js +359 -0
  16. package/plugin/runtime/lsp.js +3674 -0
  17. package/plugin/runtime/rules.js +146 -0
  18. package/plugin/runtime/src-tRIOClWZ.js +209 -0
  19. package/plugin/skills/ast-grep/LICENSE +21 -0
  20. package/plugin/skills/ast-grep/SKILL.md +20 -0
  21. package/plugin/skills/ast-grep/references/cli.md +3 -0
  22. package/plugin/skills/ast-grep/references/install.md +3 -0
  23. package/plugin/skills/ast-grep/references/patterns.md +3 -0
  24. package/plugin/skills/ast-grep/references/pitfalls.md +3 -0
  25. package/plugin/skills/ast-grep/references/recipes.md +9 -0
  26. package/plugin/skills/ast-grep/references/sgconfig.md +3 -0
  27. package/plugin/skills/ast-grep/references/yaml-rules.md +3 -0
  28. package/plugin/skills/caveman/LICENSE +21 -0
  29. package/plugin/skills/caveman/SKILL.md +84 -0
  30. package/plugin/skills/comment-checker/SKILL.md +16 -0
  31. package/plugin/skills/compress/SKILL.md +26 -0
  32. package/plugin/skills/debugging/SKILL.md +23 -0
  33. package/plugin/skills/debugging/references/runtimes/README.md +9 -0
  34. package/plugin/skills/debugging/references/tools/README.md +8 -0
  35. package/plugin/skills/define-goal/LICENSE.txt +201 -0
  36. package/plugin/skills/define-goal/SKILL.md +36 -0
  37. package/plugin/skills/frontend/ATTRIBUTION.md +218 -0
  38. package/plugin/skills/frontend/LICENSE-Apache-2.0.txt +201 -0
  39. package/plugin/skills/frontend/SKILL.md +42 -0
  40. package/plugin/skills/frontend/references/perfection/README.md +5 -0
  41. package/plugin/skills/frontend/references/perfection/react-perf-tooling.md +3 -0
  42. package/plugin/skills/frontend/references/ui-ux-db/README.md +5 -0
  43. package/plugin/skills/frontend/references/ui-ux-db/data/charts.csv +26 -0
  44. package/plugin/skills/frontend/references/ui-ux-db/data/colors.csv +162 -0
  45. package/plugin/skills/frontend/references/ui-ux-db/data/icons.csv +106 -0
  46. package/plugin/skills/frontend/references/ui-ux-db/data/landing.csv +35 -0
  47. package/plugin/skills/frontend/references/ui-ux-db/data/products.csv +162 -0
  48. package/plugin/skills/frontend/references/ui-ux-db/data/react-performance.csv +45 -0
  49. package/plugin/skills/frontend/references/ui-ux-db/data/stacks/astro.csv +54 -0
  50. package/plugin/skills/frontend/references/ui-ux-db/data/stacks/flutter.csv +53 -0
  51. package/plugin/skills/frontend/references/ui-ux-db/data/stacks/html-tailwind.csv +56 -0
  52. package/plugin/skills/frontend/references/ui-ux-db/data/stacks/jetpack-compose.csv +53 -0
  53. package/plugin/skills/frontend/references/ui-ux-db/data/stacks/nextjs.csv +53 -0
  54. package/plugin/skills/frontend/references/ui-ux-db/data/stacks/nuxt-ui.csv +51 -0
  55. package/plugin/skills/frontend/references/ui-ux-db/data/stacks/nuxtjs.csv +59 -0
  56. package/plugin/skills/frontend/references/ui-ux-db/data/stacks/react-native.csv +52 -0
  57. package/plugin/skills/frontend/references/ui-ux-db/data/stacks/react.csv +54 -0
  58. package/plugin/skills/frontend/references/ui-ux-db/data/stacks/shadcn.csv +61 -0
  59. package/plugin/skills/frontend/references/ui-ux-db/data/stacks/svelte.csv +54 -0
  60. package/plugin/skills/frontend/references/ui-ux-db/data/stacks/swiftui.csv +51 -0
  61. package/plugin/skills/frontend/references/ui-ux-db/data/stacks/vue.csv +50 -0
  62. package/plugin/skills/frontend/references/ui-ux-db/data/styles.csv +85 -0
  63. package/plugin/skills/frontend/references/ui-ux-db/data/typography.csv +74 -0
  64. package/plugin/skills/frontend/references/ui-ux-db/data/ui-reasoning.csv +162 -0
  65. package/plugin/skills/frontend/references/ui-ux-db/data/ux-guidelines.csv +100 -0
  66. package/plugin/skills/frontend/references/ui-ux-db/data/web-interface.csv +31 -0
  67. package/plugin/skills/frontend/references/ui-ux-db/scripts/core.py +262 -0
  68. package/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +1148 -0
  69. package/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +114 -0
  70. package/plugin/skills/handoff/SKILL.md +10 -0
  71. package/plugin/skills/lsp/SKILL.md +35 -0
  72. package/plugin/skills/lsp-setup/SKILL.md +18 -0
  73. package/plugin/skills/lsp-setup/scripts/detect-lsp.ts +233 -0
  74. package/plugin/skills/lsp-setup/scripts/lsp-server-table.ts +188 -0
  75. package/plugin/skills/lsp-setup/scripts/tsconfig.json +17 -0
  76. package/plugin/skills/lsp-setup/scripts/verify-lsp.ts +166 -0
  77. package/plugin/skills/plan/SKILL.md +14 -0
  78. package/plugin/skills/plan-review/SKILL.md +12 -0
  79. package/plugin/skills/programming/SKILL.md +35 -0
  80. package/plugin/skills/programming/references/code-smells.md +3 -0
  81. package/plugin/skills/programming/references/go/README.md +3 -0
  82. package/plugin/skills/programming/references/logging.md +3 -0
  83. package/plugin/skills/programming/references/python/README.md +3 -0
  84. package/plugin/skills/programming/references/rust/README.md +3 -0
  85. package/plugin/skills/programming/references/rust-ub/README.md +3 -0
  86. package/plugin/skills/programming/references/typescript/README.md +3 -0
  87. package/plugin/skills/refactor/SKILL.md +21 -0
  88. package/plugin/skills/remove-ai-slops/SKILL.md +20 -0
  89. package/plugin/skills/rules/SKILL.md +16 -0
  90. package/plugin/skills/security-research/SKILL.md +38 -0
  91. package/plugin/skills/tdd/SKILL.md +24 -0
  92. package/plugin/skills/tdd/mocking.md +60 -0
  93. package/plugin/skills/tdd/tests.md +77 -0
@@ -0,0 +1,233 @@
1
+ import { copyFile, cp, lstat, mkdir, readFile, readdir, readlink, rename, rm, stat, writeFile } from "node:fs/promises";
2
+ import { basename, dirname, join } from "node:path";
3
+ import process$1 from "node:process";
4
+ import { homedir, tmpdir } from "node:os";
5
+ import { fileURLToPath } from "node:url";
6
+ //#region src/config.ts
7
+ var START = "# >>> holycodex managed >>>";
8
+ var END = "# <<< holycodex managed <<<";
9
+ var OLD_NAMESPACES = [
10
+ "marketplaces.sisyphuslabs",
11
+ "plugins.\"omo@sisyphuslabs\"",
12
+ "marketplaces.lazycodex",
13
+ "plugins.\"omo@lazycodex\"",
14
+ "marketplaces.code-yeongyu-codex-plugins",
15
+ "plugins.\"omo@code-yeongyu-codex-plugins\"",
16
+ "agents.plan",
17
+ "agents.metis",
18
+ "agents.momus",
19
+ "agents.oracle",
20
+ "agents.sisyphus",
21
+ "agents.prometheus",
22
+ "agents.atlas",
23
+ "agents.hephaestus",
24
+ "hooks.state.\"omo@sisyphuslabs",
25
+ "hooks.state.\"omo@lazycodex",
26
+ "hooks.state.\"omo@code-yeongyu-codex-plugins"
27
+ ];
28
+ function removeManaged(input) {
29
+ const escapedStart = START.replaceAll(">", "\\>");
30
+ const escapedEnd = END.replaceAll("<", "\\<");
31
+ return input.replace(new RegExp(`${escapedStart}[\\s\\S]*?${escapedEnd}\\r?\\n?`, "g"), "").trim();
32
+ }
33
+ function removeLegacyOmo(input) {
34
+ return input.split(/(?=^\s*\[)/m).filter((section) => {
35
+ const header = /^\s*\[([^\]]+)]/.exec(section)?.[1];
36
+ if (header === void 0) return true;
37
+ if (OLD_NAMESPACES.some((name) => header === name || header.startsWith(`${name}.`) || name.includes("\"omo@") && header.startsWith(name))) return false;
38
+ return ![
39
+ "agents.explorer",
40
+ "agents.librarian",
41
+ "agents.worker"
42
+ ].some((name) => header === name || header.startsWith(`${name}.`)) || !/(?:sisyphuslabs|omo@|oh-my|code-yeongyu)/i.test(section);
43
+ }).join("").trimEnd();
44
+ }
45
+ function rewriteForbiddenReasoning(input) {
46
+ return input.split(/(?=^\s*\[)/m).map((section) => {
47
+ const model = /^\s*model\s*=\s*"([^"]+)"/im.exec(section)?.[1]?.toLowerCase();
48
+ if (model === void 0 || !/(?:sol|terra|luna)/.test(model)) return section;
49
+ const allowsHigh = model.includes("luna");
50
+ const rewritten = section.replace(/^(\s*model_reasoning_effort\s*=\s*)"([^"]+)"/gim, (_match, prefix, effort) => {
51
+ const normalized = effort.toLowerCase();
52
+ if (normalized === "low" || normalized === "medium" || allowsHigh && normalized === "high") return `${prefix}"${normalized}"`;
53
+ return `${prefix}"${normalized === "high" ? "medium" : "low"}"`;
54
+ });
55
+ return /^\s*model_reasoning_effort\s*=/im.test(rewritten) ? rewritten : rewritten.replace(/^(\s*model\s*=\s*"[^"]+")/im, "$1\nmodel_reasoning_effort = \"low\"");
56
+ }).join("");
57
+ }
58
+ function installConfig(input, autonomous) {
59
+ const base = rewriteForbiddenReasoning(removeLegacyOmo(removeManaged(input)));
60
+ const firstTable = base.search(/^\s*\[/m);
61
+ const rootSection = firstTable < 0 ? base : base.slice(0, firstTable);
62
+ const tables = firstTable < 0 ? "" : base.slice(firstTable);
63
+ const preservedRoot = rootSection.replace(/^\s*max_concurrent_threads_per_session\s*=.*\r?\n?/gm, "").replace(autonomous ? /^\s*(?:approval_policy|sandbox_mode)\s*=.*\r?\n?/gm : /$^/g, "").trimEnd();
64
+ const rootBlock = `${START}\n${/^\s*model\s*=/m.test(preservedRoot) ? "" : "model = \"gpt-5.6-sol\"\nmodel_reasoning_effort = \"low\"\n"}${autonomous ? "approval_policy = \"never\"\nsandbox_mode = \"danger-full-access\"\n" : ""}max_concurrent_threads_per_session = 2\n${END}`;
65
+ const agents = [
66
+ "explorer",
67
+ "librarian",
68
+ "worker"
69
+ ].filter((name) => !new RegExp(`^\\s*\\[agents\\.${name}]`, "m").test(base)).map((name) => `[agents.${name}]\nconfig_file = "holycodex/agents/${name}.toml"`).join("\n\n");
70
+ const pluginBlock = `${START}\n[marketplaces.holycodex]\nsource = "https://github.com/davidbasilefilho/holycodex.git"\n\n[plugins."holycodex@holycodex"]\nenabled = true${agents.length > 0 ? `\n\n${agents}` : ""}\n${END}`;
71
+ return `${rootBlock}${preservedRoot.length > 0 ? `\n${preservedRoot}` : ""}${tables.length > 0 ? `\n\n${tables}` : ""}\n\n${pluginBlock}\n`;
72
+ }
73
+ //#endregion
74
+ //#region src/files.ts
75
+ async function exists(path) {
76
+ try {
77
+ await stat(path);
78
+ return true;
79
+ } catch (error) {
80
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
81
+ throw error;
82
+ }
83
+ }
84
+ async function backup(path, root) {
85
+ if (!await exists(path)) return void 0;
86
+ const target = join(root, path.replace(/^([A-Za-z]:)?[\\/]+/, "").replaceAll(":", ""));
87
+ await mkdir(dirname(target), { recursive: true });
88
+ await copyBackup(path, target);
89
+ return target;
90
+ }
91
+ async function copyBackup(source, target) {
92
+ const metadata = await lstat(source);
93
+ if (metadata.isSymbolicLink()) {
94
+ await writeFile(`${target}.symlink`, await readlink(source), "utf8");
95
+ return;
96
+ }
97
+ if (!metadata.isDirectory()) {
98
+ await copyFile(source, target);
99
+ return;
100
+ }
101
+ await mkdir(target, { recursive: true });
102
+ for (const entry of await readdir(source)) await copyBackup(join(source, entry), join(target, entry));
103
+ }
104
+ async function atomicWrite(path, content) {
105
+ await mkdir(dirname(path), { recursive: true });
106
+ const temporary = `${path}.${process.pid}.tmp`;
107
+ await writeFile(temporary, content, "utf8");
108
+ await rename(temporary, path);
109
+ }
110
+ async function readText(path) {
111
+ return await exists(path) ? readFile(path, "utf8") : "";
112
+ }
113
+ //#endregion
114
+ //#region src/install.ts
115
+ var moduleDirectory = dirname(fileURLToPath(import.meta.url));
116
+ var packageRoot = join(moduleDirectory, basename(moduleDirectory) === "runtime" ? "../.." : "..");
117
+ function paths(home = process.env.CODEX_HOME ?? join(homedir(), ".codex")) {
118
+ const cacheRoot = join(home, "plugins", "cache", "holycodex", "holycodex");
119
+ return {
120
+ home,
121
+ config: join(home, "config.toml"),
122
+ cacheRoot,
123
+ cache: join(cacheRoot, "0.2.1"),
124
+ agents: join(home, "holycodex", "agents"),
125
+ legacy: [
126
+ join(home, "plugins", "cache", "sisyphuslabs", "omo"),
127
+ join(home, "plugins", "cache", "lazycodex", "omo"),
128
+ join(home, "plugins", "cache", "code-yeongyu-codex-plugins", "omo")
129
+ ]
130
+ };
131
+ }
132
+ function backupRoot() {
133
+ return join(tmpdir(), "holycodex-backups", (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-"));
134
+ }
135
+ async function install(options) {
136
+ const target = paths();
137
+ const root = backupRoot();
138
+ const backups = [
139
+ await backup(target.config, root),
140
+ await backup(target.cacheRoot, root),
141
+ await backup(target.agents, root),
142
+ ...await Promise.all(target.legacy.map((path) => backup(path, root)))
143
+ ].filter((path) => path !== void 0);
144
+ const config = installConfig(await readText(target.config), options.autonomous);
145
+ await atomicWrite(target.config, config);
146
+ await rm(target.cacheRoot, {
147
+ recursive: true,
148
+ force: true
149
+ });
150
+ await mkdir(dirname(target.cache), { recursive: true });
151
+ await cp(join(packageRoot, "plugin"), target.cache, { recursive: true });
152
+ await rm(target.agents, {
153
+ recursive: true,
154
+ force: true
155
+ });
156
+ await cp(join(packageRoot, "plugin", "agents"), target.agents, { recursive: true });
157
+ const removedLegacy = [];
158
+ for (const path of target.legacy) {
159
+ if (!await exists(path)) continue;
160
+ await rm(path, { recursive: true });
161
+ removedLegacy.push(path);
162
+ }
163
+ return {
164
+ action: "install",
165
+ changed: [
166
+ target.config,
167
+ target.cache,
168
+ target.agents,
169
+ ...removedLegacy
170
+ ],
171
+ backups
172
+ };
173
+ }
174
+ async function cleanup(_options) {
175
+ const target = paths();
176
+ const root = backupRoot();
177
+ const backups = [
178
+ await backup(target.config, root),
179
+ await backup(target.cacheRoot, root),
180
+ await backup(target.agents, root)
181
+ ].filter((path) => path !== void 0);
182
+ const changed = [];
183
+ if (await exists(target.config)) {
184
+ const current = await readText(target.config);
185
+ const unmanaged = removeManaged(current);
186
+ const cleaned = `${unmanaged}\n`;
187
+ if (unmanaged.length === 0 && current.includes("# >>> holycodex managed >>>")) {
188
+ await rm(target.config);
189
+ changed.push(target.config);
190
+ } else if (cleaned !== current) {
191
+ await atomicWrite(target.config, cleaned);
192
+ changed.push(target.config);
193
+ }
194
+ }
195
+ if (await exists(target.cacheRoot)) {
196
+ await rm(target.cacheRoot, { recursive: true });
197
+ changed.push(target.cacheRoot);
198
+ }
199
+ if (await exists(target.agents)) {
200
+ await rm(target.agents, { recursive: true });
201
+ changed.push(target.agents);
202
+ }
203
+ return {
204
+ action: "cleanup",
205
+ changed,
206
+ backups
207
+ };
208
+ }
209
+ //#endregion
210
+ //#region src/cli.ts
211
+ var VERSION = "0.2.1";
212
+ var HELP = `HolyCodex ${VERSION}\n\nUsage: holycodex <install|cleanup> [options]\n\nOptions:\n --help Show help\n --version Show version\n --no-tui Accepted; commands are noninteractive\n --codex-autonomous Set autonomous Codex permissions\n --json Print machine-readable result\n`;
213
+ async function main() {
214
+ const args = process$1.argv.slice(2);
215
+ if (args.includes("--help") || args.length === 0) {
216
+ process$1.stdout.write(HELP);
217
+ return;
218
+ }
219
+ if (args.includes("--version")) {
220
+ process$1.stdout.write(`${VERSION}\n`);
221
+ return;
222
+ }
223
+ const command = args.find((arg) => !arg.startsWith("--"));
224
+ const options = {
225
+ autonomous: args.includes("--codex-autonomous"),
226
+ json: args.includes("--json")
227
+ };
228
+ const result = command === "install" ? await install(options) : command === "cleanup" ? await cleanup(options) : void 0;
229
+ if (result === void 0) throw new Error(`Unknown command: ${command ?? ""}`);
230
+ process$1.stdout.write(options.json ? `${JSON.stringify(result)}\n` : `HolyCodex ${result.action} complete.\n`);
231
+ }
232
+ await main();
233
+ //#endregion
@@ -0,0 +1,359 @@
1
+ #!/usr/bin/env node
2
+ import { a as successResponse, n as errorResponse, o as isPlainRecord, r as jsonRpcId, t as runJsonRpcStdioServer } from "./src-tRIOClWZ.js";
3
+ import { join } from "node:path";
4
+ import { argv, stderr } from "node:process";
5
+ import { tmpdir } from "node:os";
6
+ import { execFileSync, spawn } from "node:child_process";
7
+ import { closeSync, existsSync, mkdtempSync, openSync, readFileSync, rmSync } from "node:fs";
8
+ //#region packages/git-bash-mcp/src/git-bash-resolver.ts
9
+ var GIT_BASH_ENV_KEY = "HOLYCODEX_GIT_BASH_PATH";
10
+ var PROGRAM_FILES = "C:\\Program Files\\Git\\bin\\bash.exe";
11
+ var PROGRAM_FILES_X86 = "C:\\Program Files (x86)\\Git\\bin\\bash.exe";
12
+ var INVALID_LAUNCHERS = ["\\windows\\system32\\", "\\microsoft\\windowsapps\\"];
13
+ function resolveGitBash(input) {
14
+ if (input.platform !== "win32") return {
15
+ found: true,
16
+ path: null,
17
+ source: "not-required",
18
+ checkedPaths: []
19
+ };
20
+ const checkedPaths = [];
21
+ const configured = input.env[GIT_BASH_ENV_KEY]?.trim();
22
+ if (configured) {
23
+ checkedPaths.push(configured);
24
+ return isBash(configured) && input.exists(configured) ? {
25
+ found: true,
26
+ path: configured,
27
+ source: "env",
28
+ checkedPaths
29
+ } : missing(checkedPaths);
30
+ }
31
+ for (const candidate of [{
32
+ path: PROGRAM_FILES,
33
+ source: "program-files"
34
+ }, {
35
+ path: PROGRAM_FILES_X86,
36
+ source: "program-files-x86"
37
+ }]) {
38
+ checkedPaths.push(candidate.path);
39
+ if (input.exists(candidate.path)) return {
40
+ found: true,
41
+ path: candidate.path,
42
+ source: candidate.source,
43
+ checkedPaths
44
+ };
45
+ }
46
+ for (const raw of input.where("bash")) {
47
+ const candidate = raw.trim();
48
+ if (!candidate) continue;
49
+ checkedPaths.push(candidate);
50
+ const normalized = candidate.replaceAll("/", "\\").toLowerCase();
51
+ if (INVALID_LAUNCHERS.some((part) => normalized.includes(part))) continue;
52
+ if (isBash(candidate) && input.exists(candidate)) return {
53
+ found: true,
54
+ path: candidate,
55
+ source: "path",
56
+ checkedPaths
57
+ };
58
+ }
59
+ return missing(checkedPaths);
60
+ }
61
+ function resolveGitBashForCurrentProcess(input = {}) {
62
+ return resolveGitBash({
63
+ platform: input.platform ?? process.platform,
64
+ env: input.env ?? process.env,
65
+ exists: existsSync,
66
+ where: (command) => {
67
+ try {
68
+ return execFileSync("where", [command], { encoding: "utf8" }).split(/\r?\n/).filter(Boolean);
69
+ } catch (error) {
70
+ if (error instanceof Error) return [];
71
+ throw error;
72
+ }
73
+ }
74
+ });
75
+ }
76
+ function isBash(path) {
77
+ return path.toLowerCase().endsWith("bash.exe");
78
+ }
79
+ function missing(checkedPaths) {
80
+ return {
81
+ found: false,
82
+ checkedPaths,
83
+ 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`
84
+ };
85
+ }
86
+ //#endregion
87
+ //#region packages/git-bash-mcp/src/runner.ts
88
+ async function runGitBashCommand(input) {
89
+ return await new Promise((resolve, reject) => {
90
+ const outputDirectory = mkdtempSync(join(tmpdir(), "holycodex-git-bash-run-"));
91
+ const stdoutPath = join(outputDirectory, "stdout");
92
+ const stderrPath = join(outputDirectory, "stderr");
93
+ const stdoutFd = openSync(stdoutPath, "w+");
94
+ const stderrFd = openSync(stderrPath, "w+");
95
+ let outputClosed = false;
96
+ function closeOutputFiles() {
97
+ if (outputClosed) return;
98
+ closeSync(stdoutFd);
99
+ closeSync(stderrFd);
100
+ outputClosed = true;
101
+ }
102
+ function readAndRemoveOutput() {
103
+ closeOutputFiles();
104
+ const stdout = readFileSync(stdoutPath, "utf8");
105
+ const stderr = readFileSync(stderrPath, "utf8");
106
+ rmSync(outputDirectory, {
107
+ recursive: true,
108
+ force: true
109
+ });
110
+ return {
111
+ stdout,
112
+ stderr
113
+ };
114
+ }
115
+ const child = spawn(input.bashPath, ["-lc", input.command], {
116
+ cwd: input.cwd,
117
+ env: input.env,
118
+ windowsHide: true,
119
+ stdio: [
120
+ "ignore",
121
+ stdoutFd,
122
+ stderrFd
123
+ ]
124
+ });
125
+ let timedOut = false;
126
+ const timeout = setTimeout(() => {
127
+ timedOut = true;
128
+ child.kill();
129
+ }, input.timeoutMs);
130
+ timeout.unref();
131
+ child.on("error", (error) => {
132
+ clearTimeout(timeout);
133
+ closeOutputFiles();
134
+ rmSync(outputDirectory, {
135
+ recursive: true,
136
+ force: true
137
+ });
138
+ reject(error);
139
+ });
140
+ child.on("close", (exitCode) => {
141
+ clearTimeout(timeout);
142
+ resolve({
143
+ exitCode,
144
+ ...readAndRemoveOutput(),
145
+ timedOut
146
+ });
147
+ });
148
+ });
149
+ }
150
+ //#endregion
151
+ //#region packages/git-bash-mcp/src/mcp.ts
152
+ var DEFAULT_TIMEOUT_MS = 12e4;
153
+ var MAX_TIMEOUT_MS = 30 * 6e4;
154
+ var EXEC_COMMAND_TIMEOUT_ENV_KEYS = [
155
+ "HOLYCODEX_GIT_BASH_TIMEOUT_MS",
156
+ "HOLYCODEX_EXEC_COMMAND_TIMEOUT_MS",
157
+ "CODEX_EXEC_COMMAND_TIMEOUT_MS",
158
+ "EXEC_COMMAND_TIMEOUT_MS"
159
+ ];
160
+ async function handleGitBashMcpRequest(input, options = {}) {
161
+ if (!isPlainRecord(input)) return errorResponse(null, -32600, "Invalid Request");
162
+ const id = jsonRpcId(input["id"]);
163
+ const method = typeof input["method"] === "string" ? input["method"] : null;
164
+ if (method === "initialize") return successResponse(id, {
165
+ capabilities: { tools: { listChanged: false } },
166
+ serverInfo: {
167
+ name: "git_bash",
168
+ version: "0.2.1"
169
+ },
170
+ protocolVersion: protocolVersionFromInput(input) ?? "2024-11-05"
171
+ });
172
+ if (method === "tools/list") return successResponse(id, { tools: toolsForOptions(options) });
173
+ if (method === "tools/call") {
174
+ const params = isPlainRecord(input["params"]) ? input["params"] : {};
175
+ return await callTool(id, typeof params["name"] === "string" ? params["name"] : "", isPlainRecord(params["arguments"]) ? params["arguments"] : {}, options);
176
+ }
177
+ if (method === "notifications/initialized") return void 0;
178
+ return errorResponse(id, -32601, "Method not found");
179
+ }
180
+ async function runMcpStdioServer(input, output, options = {}) {
181
+ if (!canRunGitBash(options)) return;
182
+ await runJsonRpcStdioServer({
183
+ input,
184
+ output,
185
+ handler: handleGitBashMcpRequest,
186
+ handlerOptions: options,
187
+ idleTimeoutMs: 0,
188
+ log: options.lifecycleLog,
189
+ parseErrorResponse: () => errorResponse(null, -32601, "Method not found")
190
+ });
191
+ }
192
+ async function callTool(id, name, args, options) {
193
+ if (name === "which_bash") return toolResponse(id, whichBashPayload(resolve$1(options)));
194
+ if (name === "diagnose") return toolResponse(id, diagnosePayload(resolve$1(options), platformFromOptions(options)));
195
+ if (name === "run") return await runToolResponse(id, args, options);
196
+ return toolResponse(id, `Unknown git_bash tool: ${name}`, true);
197
+ }
198
+ async function runToolResponse(id, args, options) {
199
+ if (platformFromOptions(options) !== "win32") return toolResponse(id, "git_bash run is only available on native Windows.", true);
200
+ const command = typeof args.command === "string" ? args.command.trim() : "";
201
+ if (command.length === 0) return toolResponse(id, "run.command must be a non-empty string.", true);
202
+ const cwd = parseWorkdir(args);
203
+ if (cwd === null) return toolResponse(id, "run.workdir must be a non-empty string when provided.", true);
204
+ const timeoutMs = parseTimeoutMs(args.timeout ?? args.timeout_ms, options);
205
+ if (timeoutMs === null) return toolResponse(id, `run.timeout must be an integer between 1 and ${MAX_TIMEOUT_MS}.`, true);
206
+ const resolution = resolve$1(options);
207
+ if (!resolution.found || resolution.path === null) return toolResponse(id, whichBashPayload(resolution), true);
208
+ try {
209
+ return toolResponse(id, runPayload(await (options.runGitBash ?? runGitBashCommand)({
210
+ bashPath: resolution.path,
211
+ command,
212
+ cwd,
213
+ timeoutMs,
214
+ env: options.env ?? process.env
215
+ })));
216
+ } catch (error) {
217
+ return toolResponse(id, error instanceof Error ? error.message : String(error), true);
218
+ }
219
+ }
220
+ function toolsForOptions(options) {
221
+ const sharedTools = [{
222
+ name: "which_bash",
223
+ description: "Use before Windows shell work when the Git Bash executable path must be confirmed.",
224
+ inputSchema: {
225
+ type: "object",
226
+ properties: {},
227
+ additionalProperties: false
228
+ }
229
+ }, {
230
+ name: "diagnose",
231
+ description: "Use when git_bash cannot run or Windows shell readiness needs diagnosis.",
232
+ inputSchema: {
233
+ type: "object",
234
+ properties: {},
235
+ additionalProperties: false
236
+ }
237
+ }];
238
+ if (!canRunGitBash(options)) return sharedTools;
239
+ return [{
240
+ name: "run",
241
+ description: "Use on native Windows for Bash commands, POSIX behavior, Git tooling, or Unix utilities; use exec_command only when git_bash is unavailable or the operation is not shell work.",
242
+ inputSchema: {
243
+ type: "object",
244
+ properties: {
245
+ command: {
246
+ type: "string",
247
+ description: "The command to execute."
248
+ },
249
+ timeout: {
250
+ type: "integer",
251
+ minimum: 1,
252
+ maximum: MAX_TIMEOUT_MS,
253
+ description: `Optional timeout in milliseconds. If omitted, uses the inherited exec_command timeout when configured; otherwise ${defaultTimeoutMs(options)}ms.`
254
+ },
255
+ workdir: {
256
+ type: "string",
257
+ description: "The working directory to run the command in. Defaults to the current directory. Use this instead of 'cd' commands."
258
+ },
259
+ description: {
260
+ type: "string",
261
+ description: "Clear, concise description of what this command does in 5-10 words."
262
+ }
263
+ },
264
+ required: ["command"],
265
+ additionalProperties: false
266
+ }
267
+ }, ...sharedTools];
268
+ }
269
+ function canRunGitBash(options) {
270
+ if (platformFromOptions(options) !== "win32") return false;
271
+ const resolution = resolve$1(options);
272
+ return resolution.found && resolution.path !== null;
273
+ }
274
+ function resolve$1(options) {
275
+ if (options.exists === void 0 && options.where === void 0) return resolveGitBashForCurrentProcess({
276
+ platform: options.platform,
277
+ env: options.env
278
+ });
279
+ return resolveGitBash({
280
+ platform: platformFromOptions(options),
281
+ env: options.env ?? process.env,
282
+ exists: options.exists ?? (() => false),
283
+ where: options.where ?? (() => [])
284
+ });
285
+ }
286
+ function platformFromOptions(options) {
287
+ return options.platform ?? process.platform;
288
+ }
289
+ function whichBashPayload(resolution) {
290
+ return JSON.stringify(resolution, null, 2);
291
+ }
292
+ function diagnosePayload(resolution, platform) {
293
+ const enabled = platform === "win32" && resolution.found && resolution.path !== null;
294
+ return JSON.stringify({
295
+ platform,
296
+ enabled,
297
+ status: platform === "win32" ? enabled ? "ready" : "missing-git-bash" : "disabled: git_bash command execution is only exposed on native Windows",
298
+ resolution
299
+ }, null, 2);
300
+ }
301
+ function runPayload(result) {
302
+ return JSON.stringify(result, null, 2);
303
+ }
304
+ function toolResponse(id, text, isError = false) {
305
+ return successResponse(id, {
306
+ content: [{
307
+ type: "text",
308
+ text
309
+ }],
310
+ isError
311
+ });
312
+ }
313
+ function parseWorkdir(args) {
314
+ const value = args.workdir ?? args.cwd;
315
+ if (value === void 0) return void 0;
316
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
317
+ }
318
+ function parseTimeoutMs(value, options) {
319
+ if (value === void 0) return defaultTimeoutMs(options);
320
+ return normalizeTimeoutMs(value);
321
+ }
322
+ function defaultTimeoutMs(options) {
323
+ const configured = normalizeTimeoutMs(options.defaultTimeoutMs);
324
+ if (configured !== null) return configured;
325
+ const env = options.env ?? process.env;
326
+ for (const key of EXEC_COMMAND_TIMEOUT_ENV_KEYS) {
327
+ const timeoutMs = normalizeTimeoutMs(env[key]);
328
+ if (timeoutMs !== null) return timeoutMs;
329
+ }
330
+ return DEFAULT_TIMEOUT_MS;
331
+ }
332
+ function normalizeTimeoutMs(value) {
333
+ const parsed = typeof value === "string" && value.trim().length > 0 ? Number(value) : value;
334
+ if (!Number.isInteger(parsed)) return null;
335
+ const timeoutMs = Number(parsed);
336
+ if (timeoutMs < 1 || timeoutMs > MAX_TIMEOUT_MS) return null;
337
+ return timeoutMs;
338
+ }
339
+ function protocolVersionFromInput(input) {
340
+ if (!isPlainRecord(input["params"])) return null;
341
+ const params = input["params"];
342
+ return typeof params["protocolVersion"] === "string" ? params["protocolVersion"] : null;
343
+ }
344
+ //#endregion
345
+ //#region packages/git-bash-mcp/src/cli.ts
346
+ async function main() {
347
+ const [command = "mcp"] = argv.slice(2);
348
+ if (command === "mcp") {
349
+ await runMcpStdioServer(process.stdin, process.stdout);
350
+ return;
351
+ }
352
+ stderr.write("Usage: holycodex-git-bash [mcp]\n");
353
+ process.exitCode = 2;
354
+ }
355
+ main().catch((error) => {
356
+ stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
357
+ process.exitCode = 1;
358
+ });
359
+ //#endregion