oh-my-opencode 4.5.12 → 4.7.0
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/.agents/skills/opencode-qa/SKILL.md +194 -0
- package/.agents/skills/opencode-qa/references/cli-commands.md +188 -0
- package/.agents/skills/opencode-qa/references/db-investigation.md +197 -0
- package/.agents/skills/opencode-qa/references/events-hooks.md +110 -0
- package/.agents/skills/opencode-qa/references/sdk.md +96 -0
- package/.agents/skills/opencode-qa/references/server-api.md +200 -0
- package/.agents/skills/opencode-qa/references/testing-harness.md +218 -0
- package/.agents/skills/opencode-qa/references/tui-tmux.md +52 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-id.sh +53 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-name.sh +57 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-text.sh +158 -0
- package/.agents/skills/opencode-qa/scripts/export-roundtrip.sh +57 -0
- package/.agents/skills/opencode-qa/scripts/lib/common.sh +216 -0
- package/.agents/skills/opencode-qa/scripts/server-smoke.sh +64 -0
- package/.agents/skills/opencode-qa/scripts/sse-hook-probe.sh +106 -0
- package/.agents/skills/opencode-qa/scripts/tui-smoke.sh +89 -0
- package/README.ja.md +13 -3
- package/README.ko.md +13 -3
- package/README.md +24 -14
- package/README.ru.md +13 -3
- package/README.zh-cn.md +13 -3
- package/bin/oh-my-opencode.js +4 -3
- package/bin/oh-my-opencode.test.ts +35 -7
- package/bin/platform.d.ts +1 -1
- package/bin/platform.js +4 -4
- package/bin/platform.test.ts +31 -9
- package/bin/version-mismatch.js +47 -0
- package/bin/version-mismatch.test.ts +120 -0
- package/dist/cli/cleanup-command.d.ts +4 -0
- package/dist/cli/cleanup.d.ts +11 -0
- package/dist/cli/cli-program.d.ts +2 -1
- package/dist/cli/codex-ulw-loop.d.ts +12 -0
- package/dist/cli/doctor/checks/tui-plugin-config.d.ts +2 -0
- package/dist/cli/index.js +2189 -529
- package/dist/cli/install-codex/codex-cache.d.ts +1 -0
- package/dist/cli/install-codex/codex-cleanup-config.d.ts +6 -0
- package/dist/cli/install-codex/codex-cleanup.d.ts +21 -0
- package/dist/cli/install-codex/codex-config-permissions.d.ts +1 -0
- package/dist/cli/install-codex/codex-config-reasoning.d.ts +2 -0
- package/dist/cli/install-codex/codex-config-toml.d.ts +2 -1
- package/dist/cli/install-codex/codex-installation-detection.d.ts +36 -0
- package/dist/cli/install-codex/codex-model-catalog.d.ts +13 -0
- package/dist/cli/install-codex/codex-package-layout.d.ts +1 -0
- package/dist/cli/install-codex/codex-project-local-cleanup-best-effort.d.ts +7 -0
- package/dist/cli/install-codex/codex-project-local-cleanup.d.ts +35 -0
- package/dist/cli/install-codex/git-bash.d.ts +35 -0
- package/dist/cli/install-codex/index.d.ts +4 -0
- package/dist/cli/install-codex/toml-section-editor.d.ts +2 -0
- package/dist/cli/install-codex/types.d.ts +20 -0
- package/dist/cli/run/event-state.d.ts +1 -0
- package/dist/cli/run/poll-for-completion.d.ts +1 -0
- package/dist/cli/run/prompt-start.d.ts +7 -0
- package/dist/cli/star-request.d.ts +9 -0
- package/dist/config/schema/hooks.d.ts +0 -1
- package/dist/create-hooks.d.ts +0 -1
- package/dist/features/background-agent/concurrency.d.ts +1 -0
- package/dist/features/background-agent/process-cleanup.d.ts +6 -0
- package/dist/features/builtin-skills/skills/debugging.d.ts +2 -0
- package/dist/features/builtin-skills/skills/index.d.ts +1 -0
- package/dist/features/claude-code-session-state/state.d.ts +1 -0
- package/dist/features/opencode-skill-loader/index.d.ts +1 -0
- package/dist/features/opencode-skill-loader/opencode-config-skills-reader.d.ts +5 -0
- package/dist/features/tmux-subagent/attachable-session-status.d.ts +1 -1
- package/dist/features/tmux-subagent/session-status-parser.d.ts +1 -0
- package/dist/hooks/comment-checker/cli.d.ts +1 -0
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/tasks-todowrite-disabler/constants.d.ts +1 -1
- package/dist/index.js +1077 -563
- package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
- package/dist/plugin/messages-transform.d.ts +8 -1
- package/dist/plugin/user-abort-interrupted-recovery-guard.d.ts +6 -0
- package/dist/shared/command-executor/execute-hook-command.d.ts +2 -0
- package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +14 -0
- package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +7 -0
- package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +1 -0
- package/dist/shared/prompt-async-gate/timing.d.ts +1 -0
- package/dist/shared/prompt-async-gate/types.d.ts +2 -0
- package/dist/shared/prompt-async-gate.d.ts +1 -1
- package/dist/tools/skill/description-formatter.d.ts +5 -1
- package/dist/tools/skill/types.d.ts +1 -0
- package/package.json +22 -18
- package/packages/ast-grep-mcp/dist/cli.js +53 -9
- package/packages/git-bash-mcp/dist/cli.js +367 -0
- package/packages/lsp-tools-mcp/dist/lsp/process.js +1 -1
- package/packages/omo-codex/plugin/.mcp.json +11 -0
- package/packages/omo-codex/plugin/components/comment-checker/README.md +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +29 -0
- package/packages/omo-codex/plugin/components/git-bash/package.json +23 -0
- package/packages/omo-codex/plugin/components/git-bash/src/cli.ts +33 -0
- package/packages/omo-codex/plugin/components/git-bash/src/codex-hook.ts +180 -0
- package/packages/omo-codex/plugin/components/git-bash/src/index.ts +10 -0
- package/packages/omo-codex/plugin/components/git-bash/test/codex-hook.test.ts +195 -0
- package/packages/omo-codex/plugin/components/git-bash/tsconfig.build.json +13 -0
- package/packages/omo-codex/plugin/components/git-bash/tsconfig.json +25 -0
- package/packages/omo-codex/plugin/components/lsp/README.md +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/cli.ts +5 -5
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +33 -0
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +19 -27
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-cli.test.ts +28 -0
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-errors.test.ts +55 -0
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +7 -5
- package/packages/omo-codex/plugin/components/rules/README.md +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus.md +6 -4
- package/packages/omo-codex/plugin/components/rules/bundled-rules/windows-git-bash.md +10 -0
- package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +0 -2
- package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +3 -1
- package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +97 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +6 -5
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +22 -0
- package/packages/omo-codex/plugin/components/ultrawork/CHANGELOG.md +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/README.md +3 -3
- package/packages/omo-codex/plugin/components/ultrawork/agents/codex-ultrawork-reviewer.toml +4 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +8 -7
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +9 -8
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +32 -6
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +27 -4
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +25 -0
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +28 -205
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +231 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +12 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint.test.ts +19 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +102 -5
- package/packages/omo-codex/plugin/hooks/hooks.json +35 -2
- package/packages/omo-codex/plugin/model-catalog.json +49 -0
- package/packages/omo-codex/plugin/package-lock.json +19 -0
- package/packages/omo-codex/plugin/package.json +3 -1
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +159 -0
- package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +16 -1
- package/packages/omo-codex/plugin/scripts/build-components.mjs +2 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +269 -0
- package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +89 -0
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +6 -6
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +127 -0
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +9 -0
- package/packages/omo-codex/plugin/skills/refactor/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +33 -8
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +25 -5
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +28 -205
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +231 -0
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +17 -17
- package/packages/omo-codex/plugin/test/aggregate.test.mjs +188 -20
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +129 -0
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +58 -11
- package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +34 -0
- package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +21 -0
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +146 -0
- package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +48 -0
- package/packages/omo-codex/plugin/test/subagent-guidance.test.mjs +76 -0
- package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +67 -0
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +54 -2
- package/packages/omo-codex/scripts/install/cache.mjs +5 -3
- package/packages/omo-codex/scripts/install/cli-args.mjs +112 -0
- package/packages/omo-codex/scripts/install/config.mjs +23 -1
- package/packages/omo-codex/scripts/install/delegated-command.mjs +25 -0
- package/packages/omo-codex/scripts/install/git-bash.mjs +99 -0
- package/packages/omo-codex/scripts/install/git-bash.test.mjs +174 -0
- package/packages/omo-codex/scripts/install/legacy-bins.mjs +1 -0
- package/packages/omo-codex/scripts/install/mcp-runtime-cache.mjs +5 -1
- package/packages/omo-codex/scripts/install/model-catalog.mjs +66 -0
- package/packages/omo-codex/scripts/install/multi-agent-v2-config.mjs +7 -1
- package/packages/omo-codex/scripts/install/permissions.d.mts +1 -0
- package/packages/omo-codex/scripts/install/permissions.mjs +26 -0
- package/packages/omo-codex/scripts/install/project-local-cleanup.mjs +229 -0
- package/packages/omo-codex/scripts/install/reasoning-config.mjs +72 -0
- package/packages/omo-codex/scripts/install/source-package-build.mjs +20 -0
- package/packages/omo-codex/scripts/install/toml-editor.mjs +19 -2
- package/packages/omo-codex/scripts/install-bin-links.test.mjs +23 -0
- package/packages/omo-codex/scripts/install-cli-args.test.mjs +146 -0
- package/packages/omo-codex/scripts/install-config-autonomous.test.mjs +48 -0
- package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +141 -0
- package/packages/omo-codex/scripts/install-config.test.mjs +205 -0
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +157 -0
- package/packages/omo-codex/scripts/install-local-git-bash-preflight.test.mjs +145 -0
- package/packages/omo-codex/scripts/install-local.mjs +91 -8
- package/packages/omo-codex/scripts/install-local.test.mjs +15 -0
- package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +60 -0
- package/packages/omo-codex/scripts/install-packaged-local.test.mjs +67 -0
- package/packages/omo-codex/scripts/install-project-local-cleanup.test.mjs +277 -0
- package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +127 -0
- package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +9 -0
- package/packages/shared-skills/skills/review-work/SKILL.md +33 -8
- package/packages/shared-skills/skills/start-work/SKILL.md +25 -5
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +11 -11
- package/postinstall.mjs +36 -3
- package/dist/hooks/context-window-monitor.d.ts +0 -19
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { argv, stderr } from "node:process";
|
|
5
|
+
|
|
6
|
+
// src/git-bash-resolver.ts
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
var GIT_BASH_ENV_KEY = "OMO_CODEX_GIT_BASH_PATH";
|
|
10
|
+
var PROGRAM_FILES_GIT_BASH = "C:\\Program Files\\Git\\bin\\bash.exe";
|
|
11
|
+
var PROGRAM_FILES_X86_GIT_BASH = "C:\\Program Files (x86)\\Git\\bin\\bash.exe";
|
|
12
|
+
function resolveGitBash(input) {
|
|
13
|
+
if (input.platform !== "win32")
|
|
14
|
+
return { found: true, path: null, source: "not-required" };
|
|
15
|
+
const checkedPaths = [];
|
|
16
|
+
const envPath = nonEmptyEnvValue(input.env, GIT_BASH_ENV_KEY);
|
|
17
|
+
if (envPath !== undefined) {
|
|
18
|
+
checkedPaths.push(envPath);
|
|
19
|
+
if (isBashExePath(envPath) && input.exists(envPath))
|
|
20
|
+
return { found: true, path: envPath, source: "env" };
|
|
21
|
+
return missingGitBash(checkedPaths);
|
|
22
|
+
}
|
|
23
|
+
for (const candidate of [
|
|
24
|
+
{ path: PROGRAM_FILES_GIT_BASH, source: "program-files" },
|
|
25
|
+
{ path: PROGRAM_FILES_X86_GIT_BASH, source: "program-files-x86" }
|
|
26
|
+
]) {
|
|
27
|
+
checkedPaths.push(candidate.path);
|
|
28
|
+
if (input.exists(candidate.path))
|
|
29
|
+
return { found: true, path: candidate.path, source: candidate.source };
|
|
30
|
+
}
|
|
31
|
+
for (const pathCandidate of input.where("bash")) {
|
|
32
|
+
const candidate = pathCandidate.trim();
|
|
33
|
+
if (candidate.length === 0)
|
|
34
|
+
continue;
|
|
35
|
+
checkedPaths.push(candidate);
|
|
36
|
+
if (isBashExePath(candidate) && input.exists(candidate))
|
|
37
|
+
return { found: true, path: candidate, source: "path" };
|
|
38
|
+
}
|
|
39
|
+
return missingGitBash(checkedPaths);
|
|
40
|
+
}
|
|
41
|
+
function resolveGitBashForCurrentProcess(input = {}) {
|
|
42
|
+
return resolveGitBash({
|
|
43
|
+
platform: input.platform ?? process.platform,
|
|
44
|
+
env: input.env ?? process.env,
|
|
45
|
+
exists: existsSync,
|
|
46
|
+
where: whereCommand
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function missingGitBash(checkedPaths) {
|
|
50
|
+
return {
|
|
51
|
+
found: false,
|
|
52
|
+
checkedPaths,
|
|
53
|
+
installHint: [
|
|
54
|
+
"Git Bash is required before the git_bash MCP can run commands on native Windows.",
|
|
55
|
+
"Install it with: winget install --id Git.Git -e --source winget",
|
|
56
|
+
`For a custom install, set ${GIT_BASH_ENV_KEY}=C:\\path\\to\\bash.exe`
|
|
57
|
+
].join(`
|
|
58
|
+
`)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function nonEmptyEnvValue(env, key) {
|
|
62
|
+
const value = env[key];
|
|
63
|
+
if (value === undefined)
|
|
64
|
+
return;
|
|
65
|
+
const trimmed = value.trim();
|
|
66
|
+
return trimmed.length === 0 ? undefined : trimmed;
|
|
67
|
+
}
|
|
68
|
+
function isBashExePath(path) {
|
|
69
|
+
return path.toLowerCase().endsWith("bash.exe");
|
|
70
|
+
}
|
|
71
|
+
function whereCommand(command) {
|
|
72
|
+
try {
|
|
73
|
+
return execFileSync("where", [command], { encoding: "utf8" }).split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error instanceof Error)
|
|
76
|
+
return [];
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/runner.ts
|
|
82
|
+
import { spawn } from "node:child_process";
|
|
83
|
+
async function runGitBashCommand(input) {
|
|
84
|
+
return await new Promise((resolve, reject) => {
|
|
85
|
+
const child = spawn(input.bashPath, ["-lc", input.command], {
|
|
86
|
+
cwd: input.cwd,
|
|
87
|
+
env: input.env,
|
|
88
|
+
windowsHide: true,
|
|
89
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
90
|
+
});
|
|
91
|
+
let stdout = "";
|
|
92
|
+
let stderr = "";
|
|
93
|
+
let timedOut = false;
|
|
94
|
+
const timeout = setTimeout(() => {
|
|
95
|
+
timedOut = true;
|
|
96
|
+
child.kill();
|
|
97
|
+
}, input.timeoutMs);
|
|
98
|
+
timeout.unref();
|
|
99
|
+
child.stdout.setEncoding("utf8");
|
|
100
|
+
child.stderr.setEncoding("utf8");
|
|
101
|
+
child.stdout.on("data", (chunk) => {
|
|
102
|
+
stdout += chunk;
|
|
103
|
+
});
|
|
104
|
+
child.stderr.on("data", (chunk) => {
|
|
105
|
+
stderr += chunk;
|
|
106
|
+
});
|
|
107
|
+
child.on("error", (error) => {
|
|
108
|
+
clearTimeout(timeout);
|
|
109
|
+
reject(error);
|
|
110
|
+
});
|
|
111
|
+
child.on("close", (exitCode) => {
|
|
112
|
+
clearTimeout(timeout);
|
|
113
|
+
resolve({ exitCode, stdout, stderr, timedOut });
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/mcp.ts
|
|
119
|
+
var DEFAULT_TIMEOUT_MS = 120000;
|
|
120
|
+
var MAX_TIMEOUT_MS = 30 * 60000;
|
|
121
|
+
var EXEC_COMMAND_TIMEOUT_ENV_KEYS = [
|
|
122
|
+
"OMO_CODEX_GIT_BASH_TIMEOUT_MS",
|
|
123
|
+
"OMO_CODEX_EXEC_COMMAND_TIMEOUT_MS",
|
|
124
|
+
"CODEX_EXEC_COMMAND_TIMEOUT_MS",
|
|
125
|
+
"EXEC_COMMAND_TIMEOUT_MS"
|
|
126
|
+
];
|
|
127
|
+
async function handleGitBashMcpRequest(input, options = {}) {
|
|
128
|
+
if (!isRecord(input))
|
|
129
|
+
return errorResponse(null, -32600, "Invalid Request");
|
|
130
|
+
const id = jsonRpcId(input.id);
|
|
131
|
+
const method = typeof input.method === "string" ? input.method : null;
|
|
132
|
+
if (method === "initialize") {
|
|
133
|
+
const protocolVersion = protocolVersionFromInput(input) ?? "2024-11-05";
|
|
134
|
+
return successResponse(id, {
|
|
135
|
+
capabilities: { tools: { listChanged: false } },
|
|
136
|
+
serverInfo: { name: "git_bash", version: "0.1.0" },
|
|
137
|
+
protocolVersion
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (method === "tools/list")
|
|
141
|
+
return successResponse(id, { tools: toolsForOptions(options) });
|
|
142
|
+
if (method === "tools/call") {
|
|
143
|
+
const params = isRecord(input.params) ? input.params : {};
|
|
144
|
+
const name = typeof params.name === "string" ? params.name : "";
|
|
145
|
+
const args = isRecord(params.arguments) ? params.arguments : {};
|
|
146
|
+
return await callTool(id, name, args, options);
|
|
147
|
+
}
|
|
148
|
+
if (method === "notifications/initialized")
|
|
149
|
+
return;
|
|
150
|
+
return errorResponse(id, -32601, "Method not found");
|
|
151
|
+
}
|
|
152
|
+
async function runMcpStdioServer(input, output, options = {}) {
|
|
153
|
+
if (!canRunGitBash(options))
|
|
154
|
+
return;
|
|
155
|
+
let buffer = "";
|
|
156
|
+
for await (const chunk of input) {
|
|
157
|
+
buffer += String(chunk);
|
|
158
|
+
while (true) {
|
|
159
|
+
const lineEnd = buffer.indexOf(`
|
|
160
|
+
`);
|
|
161
|
+
if (lineEnd === -1)
|
|
162
|
+
break;
|
|
163
|
+
const line = buffer.slice(0, lineEnd).trim();
|
|
164
|
+
buffer = buffer.slice(lineEnd + 1);
|
|
165
|
+
if (line.length === 0)
|
|
166
|
+
continue;
|
|
167
|
+
const response = await handleGitBashMcpRequest(parseJsonRpcLine(line), options);
|
|
168
|
+
if (response !== undefined)
|
|
169
|
+
output.write(`${JSON.stringify(response)}
|
|
170
|
+
`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function callTool(id, name, args, options) {
|
|
175
|
+
if (name === "which_bash")
|
|
176
|
+
return toolResponse(id, whichBashPayload(resolve(options)));
|
|
177
|
+
if (name === "diagnose")
|
|
178
|
+
return toolResponse(id, diagnosePayload(resolve(options), platformFromOptions(options)));
|
|
179
|
+
if (name === "run")
|
|
180
|
+
return await runToolResponse(id, args, options);
|
|
181
|
+
return toolResponse(id, `Unknown git_bash tool: ${name}`, true);
|
|
182
|
+
}
|
|
183
|
+
async function runToolResponse(id, args, options) {
|
|
184
|
+
const platform = platformFromOptions(options);
|
|
185
|
+
if (platform !== "win32")
|
|
186
|
+
return toolResponse(id, "git_bash run is only available on native Windows.", true);
|
|
187
|
+
const command = typeof args.command === "string" ? args.command.trim() : "";
|
|
188
|
+
if (command.length === 0)
|
|
189
|
+
return toolResponse(id, "run.command must be a non-empty string.", true);
|
|
190
|
+
const cwd = parseWorkdir(args);
|
|
191
|
+
if (cwd === null)
|
|
192
|
+
return toolResponse(id, "run.workdir must be a non-empty string when provided.", true);
|
|
193
|
+
const timeoutMs = parseTimeoutMs(args.timeout ?? args.timeout_ms, options);
|
|
194
|
+
if (timeoutMs === null)
|
|
195
|
+
return toolResponse(id, `run.timeout must be an integer between 1 and ${MAX_TIMEOUT_MS}.`, true);
|
|
196
|
+
const resolution = resolve(options);
|
|
197
|
+
if (!resolution.found || resolution.path === null)
|
|
198
|
+
return toolResponse(id, whichBashPayload(resolution), true);
|
|
199
|
+
try {
|
|
200
|
+
const run = options.runGitBash ?? runGitBashCommand;
|
|
201
|
+
const result = await run({ bashPath: resolution.path, command, cwd, timeoutMs, env: options.env ?? process.env });
|
|
202
|
+
return toolResponse(id, runPayload(result));
|
|
203
|
+
} catch (error) {
|
|
204
|
+
return toolResponse(id, error instanceof Error ? error.message : String(error), true);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function toolsForOptions(options) {
|
|
208
|
+
const sharedTools = [
|
|
209
|
+
{
|
|
210
|
+
name: "which_bash",
|
|
211
|
+
description: "Resolve the Git Bash bash.exe path used by the git_bash MCP.",
|
|
212
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: "diagnose",
|
|
216
|
+
description: "Report whether Git Bash command execution is available on this host.",
|
|
217
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
218
|
+
}
|
|
219
|
+
];
|
|
220
|
+
if (!canRunGitBash(options))
|
|
221
|
+
return sharedTools;
|
|
222
|
+
return [
|
|
223
|
+
{
|
|
224
|
+
name: "run",
|
|
225
|
+
description: "Run a shell command through Git Bash on native Windows. Prefer this git_bash run tool for bash/shell commands on Windows before built-in exec_command or Bash; use exec_command only when git_bash is unavailable or for non-shell operations.",
|
|
226
|
+
inputSchema: {
|
|
227
|
+
type: "object",
|
|
228
|
+
properties: {
|
|
229
|
+
command: { type: "string", description: "The command to execute." },
|
|
230
|
+
timeout: {
|
|
231
|
+
type: "integer",
|
|
232
|
+
minimum: 1,
|
|
233
|
+
maximum: MAX_TIMEOUT_MS,
|
|
234
|
+
description: `Optional timeout in milliseconds. If omitted, uses the inherited exec_command timeout when configured; otherwise ${defaultTimeoutMs(options)}ms.`
|
|
235
|
+
},
|
|
236
|
+
workdir: {
|
|
237
|
+
type: "string",
|
|
238
|
+
description: "The working directory to run the command in. Defaults to the current directory. Use this instead of 'cd' commands."
|
|
239
|
+
},
|
|
240
|
+
description: {
|
|
241
|
+
type: "string",
|
|
242
|
+
description: "Clear, concise description of what this command does in 5-10 words."
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
required: ["command"],
|
|
246
|
+
additionalProperties: false
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
...sharedTools
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
function canRunGitBash(options) {
|
|
253
|
+
if (platformFromOptions(options) !== "win32")
|
|
254
|
+
return false;
|
|
255
|
+
const resolution = resolve(options);
|
|
256
|
+
return resolution.found && resolution.path !== null;
|
|
257
|
+
}
|
|
258
|
+
function resolve(options) {
|
|
259
|
+
if (options.exists === undefined && options.where === undefined) {
|
|
260
|
+
return resolveGitBashForCurrentProcess({
|
|
261
|
+
platform: options.platform,
|
|
262
|
+
env: options.env
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return resolveGitBash({
|
|
266
|
+
platform: platformFromOptions(options),
|
|
267
|
+
env: options.env ?? process.env,
|
|
268
|
+
exists: options.exists ?? (() => false),
|
|
269
|
+
where: options.where ?? (() => [])
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function platformFromOptions(options) {
|
|
273
|
+
return options.platform ?? process.platform;
|
|
274
|
+
}
|
|
275
|
+
function whichBashPayload(resolution) {
|
|
276
|
+
return JSON.stringify(resolution, null, 2);
|
|
277
|
+
}
|
|
278
|
+
function diagnosePayload(resolution, platform) {
|
|
279
|
+
const enabled = platform === "win32" && resolution.found && resolution.path !== null;
|
|
280
|
+
const payload = {
|
|
281
|
+
platform,
|
|
282
|
+
enabled,
|
|
283
|
+
status: platform === "win32" ? enabled ? "ready" : "missing-git-bash" : "disabled: git_bash command execution is only exposed on native Windows",
|
|
284
|
+
resolution
|
|
285
|
+
};
|
|
286
|
+
return JSON.stringify(payload, null, 2);
|
|
287
|
+
}
|
|
288
|
+
function runPayload(result) {
|
|
289
|
+
return JSON.stringify(result, null, 2);
|
|
290
|
+
}
|
|
291
|
+
function toolResponse(id, text, isError = false) {
|
|
292
|
+
return successResponse(id, { content: [{ type: "text", text }], isError });
|
|
293
|
+
}
|
|
294
|
+
function successResponse(id, result) {
|
|
295
|
+
return { jsonrpc: "2.0", id, result };
|
|
296
|
+
}
|
|
297
|
+
function errorResponse(id, code, message, data) {
|
|
298
|
+
return { jsonrpc: "2.0", id, error: data === undefined ? { code, message } : { code, message, data } };
|
|
299
|
+
}
|
|
300
|
+
function parseWorkdir(args) {
|
|
301
|
+
const value = args.workdir ?? args.cwd;
|
|
302
|
+
if (value === undefined)
|
|
303
|
+
return;
|
|
304
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
305
|
+
}
|
|
306
|
+
function parseTimeoutMs(value, options) {
|
|
307
|
+
if (value === undefined)
|
|
308
|
+
return defaultTimeoutMs(options);
|
|
309
|
+
return normalizeTimeoutMs(value);
|
|
310
|
+
}
|
|
311
|
+
function defaultTimeoutMs(options) {
|
|
312
|
+
const configured = normalizeTimeoutMs(options.defaultTimeoutMs);
|
|
313
|
+
if (configured !== null)
|
|
314
|
+
return configured;
|
|
315
|
+
const env = options.env ?? process.env;
|
|
316
|
+
for (const key of EXEC_COMMAND_TIMEOUT_ENV_KEYS) {
|
|
317
|
+
const timeoutMs = normalizeTimeoutMs(env[key]);
|
|
318
|
+
if (timeoutMs !== null)
|
|
319
|
+
return timeoutMs;
|
|
320
|
+
}
|
|
321
|
+
return DEFAULT_TIMEOUT_MS;
|
|
322
|
+
}
|
|
323
|
+
function normalizeTimeoutMs(value) {
|
|
324
|
+
const parsed = typeof value === "string" && value.trim().length > 0 ? Number(value) : value;
|
|
325
|
+
if (!Number.isInteger(parsed))
|
|
326
|
+
return null;
|
|
327
|
+
const timeoutMs = Number(parsed);
|
|
328
|
+
if (timeoutMs < 1 || timeoutMs > MAX_TIMEOUT_MS)
|
|
329
|
+
return null;
|
|
330
|
+
return timeoutMs;
|
|
331
|
+
}
|
|
332
|
+
function protocolVersionFromInput(input) {
|
|
333
|
+
if (!isRecord(input.params))
|
|
334
|
+
return null;
|
|
335
|
+
return typeof input.params.protocolVersion === "string" ? input.params.protocolVersion : null;
|
|
336
|
+
}
|
|
337
|
+
function parseJsonRpcLine(line) {
|
|
338
|
+
try {
|
|
339
|
+
const parsed = JSON.parse(line);
|
|
340
|
+
return parsed;
|
|
341
|
+
} catch (error) {
|
|
342
|
+
return { jsonrpc: "2.0", id: null, method: null, parseError: error instanceof Error ? error.message : String(error) };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function jsonRpcId(value) {
|
|
346
|
+
return typeof value === "string" || typeof value === "number" || value === null ? value : null;
|
|
347
|
+
}
|
|
348
|
+
function isRecord(value) {
|
|
349
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// src/cli.ts
|
|
353
|
+
async function main() {
|
|
354
|
+
const [command = "mcp"] = argv.slice(2);
|
|
355
|
+
if (command === "mcp") {
|
|
356
|
+
await runMcpStdioServer(process.stdin, process.stdout);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
stderr.write(`Usage: omo-git-bash [mcp]
|
|
360
|
+
`);
|
|
361
|
+
process.exitCode = 2;
|
|
362
|
+
}
|
|
363
|
+
main().catch((error) => {
|
|
364
|
+
stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}
|
|
365
|
+
`);
|
|
366
|
+
process.exitCode = 1;
|
|
367
|
+
});
|
|
@@ -97,7 +97,7 @@ function getWindowsPathExtensions(env) {
|
|
|
97
97
|
.map((extension) => extension.trim())
|
|
98
98
|
.filter(Boolean)
|
|
99
99
|
.map((extension) => (extension.startsWith(".") ? extension : `.${extension}`));
|
|
100
|
-
return [...new Set([
|
|
100
|
+
return [...new Set([...extensions, ".exe", ".cmd", ".bat", ""])];
|
|
101
101
|
}
|
|
102
102
|
function resolveWindowsCommand(command, env) {
|
|
103
103
|
const hasPathSeparator = command.includes("/") || command.includes("\\");
|
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
"args": ["../../ast-grep-mcp/dist/cli.js", "mcp"],
|
|
6
6
|
"cwd": "."
|
|
7
7
|
},
|
|
8
|
+
"grep_app": {
|
|
9
|
+
"url": "https://mcp.grep.app"
|
|
10
|
+
},
|
|
11
|
+
"context7": {
|
|
12
|
+
"url": "https://mcp.context7.com/mcp"
|
|
13
|
+
},
|
|
14
|
+
"git_bash": {
|
|
15
|
+
"command": "node",
|
|
16
|
+
"args": ["../../git-bash-mcp/dist/cli.js", "mcp"],
|
|
17
|
+
"cwd": "."
|
|
18
|
+
},
|
|
8
19
|
"lsp": {
|
|
9
20
|
"command": "node",
|
|
10
21
|
"args": ["../../lsp-tools-mcp/dist/cli.js", "mcp"],
|
|
@@ -52,7 +52,7 @@ node dist/cli.js hook post-tool-use < test/fixtures/post-tool-use.json
|
|
|
52
52
|
## Local Codex Installation
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
-
|
|
55
|
+
npx lazycodex-ai install
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
The installer builds and copies the plugin into `~/.codex/plugins/cache/sisyphuslabs/omo/0.1.0`, registers the `sisyphuslabs` marketplace from the `lazycodex` Git repository, installs runtime dependencies there, and enables:
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "^Bash$",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use",
|
|
10
|
+
"timeout": 5,
|
|
11
|
+
"statusMessage": "LazyCodex(0.1.0): Recommending Git Bash Mcp"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"PostCompact": [
|
|
17
|
+
{
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-compact",
|
|
22
|
+
"timeout": 5,
|
|
23
|
+
"statusMessage": "LazyCodex(0.1.0): Resetting Git Bash Mcp Reminder"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sisyphuslabs/codex-git-bash-hook",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Codex hook component that reminds Windows sessions to prefer the OMO git_bash MCP.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": true,
|
|
7
|
+
"bin": {
|
|
8
|
+
"omo-git-bash-hook": "./dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": ["dist", "hooks"],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc -p tsconfig.build.json",
|
|
13
|
+
"test": "bun test test/*.test.ts",
|
|
14
|
+
"typecheck": "tsc --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^25.7.0",
|
|
18
|
+
"typescript": "^6.0.3"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runGitBashHookCli } from "./codex-hook.js";
|
|
3
|
+
|
|
4
|
+
const TOP_LEVEL_HELP =
|
|
5
|
+
"Usage:\n omo-git-bash-hook hook pre-tool-use\n omo-git-bash-hook hook post-compact\n omo-git-bash-hook help | --help | -h\n";
|
|
6
|
+
|
|
7
|
+
async function main(): Promise<number> {
|
|
8
|
+
const argv = process.argv.slice(2);
|
|
9
|
+
const command = argv[0];
|
|
10
|
+
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
11
|
+
process.stdout.write(TOP_LEVEL_HELP);
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
if (command === "hook" && argv[1] === "pre-tool-use") {
|
|
15
|
+
await runGitBashHookCli(process.stdin, process.stdout, "pre-tool-use");
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
if (command === "hook" && argv[1] === "post-compact") {
|
|
19
|
+
await runGitBashHookCli(process.stdin, process.stdout, "post-compact");
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
process.stderr.write(`[omo-git-bash-hook] unknown command: ${argv.join(" ")}\n${TOP_LEVEL_HELP}`);
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
main()
|
|
27
|
+
.then((code) => {
|
|
28
|
+
process.exit(code);
|
|
29
|
+
})
|
|
30
|
+
.catch((error: unknown) => {
|
|
31
|
+
process.stderr.write(`[omo-git-bash-hook] ${error instanceof Error ? error.message : String(error)}\n`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export interface PreToolUsePayload {
|
|
6
|
+
readonly cwd: string;
|
|
7
|
+
readonly hook_event_name: "PreToolUse";
|
|
8
|
+
readonly model: string;
|
|
9
|
+
readonly permission_mode: string;
|
|
10
|
+
readonly session_id: string;
|
|
11
|
+
readonly tool_input: unknown;
|
|
12
|
+
readonly tool_name: string;
|
|
13
|
+
readonly tool_use_id: string;
|
|
14
|
+
readonly transcript_path: string | null;
|
|
15
|
+
readonly turn_id: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface GitBashHookOptions {
|
|
19
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
20
|
+
readonly platform?: NodeJS.Platform | string;
|
|
21
|
+
readonly pluginDataRoot?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PostCompactPayload {
|
|
25
|
+
readonly hook_event_name: "PostCompact";
|
|
26
|
+
readonly session_id: string;
|
|
27
|
+
readonly transcript_path?: string | null;
|
|
28
|
+
readonly trigger?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface PreToolUseHookOutput {
|
|
32
|
+
readonly hookSpecificOutput: {
|
|
33
|
+
readonly hookEventName: "PreToolUse";
|
|
34
|
+
readonly additionalContext: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const BASH_TOOL_NAME = "Bash";
|
|
39
|
+
const REMINDER =
|
|
40
|
+
"On Windows, prefer the OMO git_bash MCP for shell commands before using built-in exec_command. Use exec_command only when git_bash is unavailable or for non-shell operations.";
|
|
41
|
+
|
|
42
|
+
export function parsePreToolUsePayload(raw: string): PreToolUsePayload | null {
|
|
43
|
+
if (raw.trim().length === 0) return null;
|
|
44
|
+
try {
|
|
45
|
+
const parsed: unknown = JSON.parse(raw);
|
|
46
|
+
return isPreToolUsePayload(parsed) ? parsed : null;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error instanceof SyntaxError) return null;
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function parsePostCompactPayload(raw: string): PostCompactPayload | null {
|
|
54
|
+
if (raw.trim().length === 0) return null;
|
|
55
|
+
try {
|
|
56
|
+
const parsed: unknown = JSON.parse(raw);
|
|
57
|
+
return isPostCompactPayload(parsed) ? parsed : null;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error instanceof SyntaxError) return null;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function applyGitBashPreToolUseReminder(payload: PreToolUsePayload, options: GitBashHookOptions = {}): string {
|
|
65
|
+
if (payload.hook_event_name !== "PreToolUse") return "";
|
|
66
|
+
if (payload.tool_name !== BASH_TOOL_NAME) return "";
|
|
67
|
+
if (!isWindowsHost(options)) return "";
|
|
68
|
+
|
|
69
|
+
const markerPath = reminderMarkerPath(payload.session_id, options.pluginDataRoot);
|
|
70
|
+
if (hasReminderMarker(markerPath)) return "";
|
|
71
|
+
mkdirSync(dirname(markerPath), { recursive: true });
|
|
72
|
+
writeFileSync(markerPath, `${new Date().toISOString()}\n`);
|
|
73
|
+
|
|
74
|
+
const output: PreToolUseHookOutput = {
|
|
75
|
+
hookSpecificOutput: {
|
|
76
|
+
hookEventName: "PreToolUse",
|
|
77
|
+
additionalContext: REMINDER,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
return `${JSON.stringify(output)}\n`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function applyGitBashPostCompactReset(payload: PostCompactPayload, options: GitBashHookOptions = {}): string {
|
|
84
|
+
if (payload.hook_event_name !== "PostCompact") return "";
|
|
85
|
+
rmSync(reminderMarkerPath(payload.session_id, options.pluginDataRoot), { force: true });
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function runGitBashHookCli(
|
|
90
|
+
stdin: NodeJS.ReadableStream,
|
|
91
|
+
stdout: NodeJS.WritableStream,
|
|
92
|
+
eventName: "pre-tool-use" | "post-compact" = "pre-tool-use",
|
|
93
|
+
options: GitBashHookOptions = {},
|
|
94
|
+
): Promise<void> {
|
|
95
|
+
try {
|
|
96
|
+
const raw = await readAll(stdin);
|
|
97
|
+
const output =
|
|
98
|
+
eventName === "post-compact" ? postCompactOutput(raw, options) : preToolUseOutput(raw, options);
|
|
99
|
+
if (output.length > 0) stdout.write(output);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error instanceof Error) return;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function preToolUseOutput(raw: string, options: GitBashHookOptions): string {
|
|
107
|
+
const payload = parsePreToolUsePayload(raw);
|
|
108
|
+
if (payload === null) return "";
|
|
109
|
+
return applyGitBashPreToolUseReminder(payload, options);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function postCompactOutput(raw: string, options: GitBashHookOptions): string {
|
|
113
|
+
const payload = parsePostCompactPayload(raw);
|
|
114
|
+
if (payload === null) return "";
|
|
115
|
+
return applyGitBashPostCompactReset(payload, options);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isWindowsHost(options: GitBashHookOptions): boolean {
|
|
119
|
+
const platform = options.platform ?? process.platform;
|
|
120
|
+
if (platform === "win32") return true;
|
|
121
|
+
const env = options.env ?? process.env;
|
|
122
|
+
return env["OS"] === "Windows_NT" || env["ComSpec"] !== undefined || env["SystemRoot"] !== undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function hasReminderMarker(path: string): boolean {
|
|
126
|
+
return existsSync(path);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function reminderMarkerPath(sessionId: string, pluginDataRoot?: string): string {
|
|
130
|
+
const root = pluginDataRoot ?? process.env["PLUGIN_DATA"] ?? join(homedir(), ".codex", "omo-git-bash");
|
|
131
|
+
return join(root, "git-bash-reminder", `${safePathSegment(sessionId)}.seen`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function safePathSegment(value: string): string {
|
|
135
|
+
return value.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isPreToolUsePayload(value: unknown): value is PreToolUsePayload {
|
|
139
|
+
if (!isRecord(value)) return false;
|
|
140
|
+
return (
|
|
141
|
+
value["hook_event_name"] === "PreToolUse" &&
|
|
142
|
+
typeof value["cwd"] === "string" &&
|
|
143
|
+
typeof value["model"] === "string" &&
|
|
144
|
+
typeof value["permission_mode"] === "string" &&
|
|
145
|
+
typeof value["session_id"] === "string" &&
|
|
146
|
+
typeof value["tool_name"] === "string" &&
|
|
147
|
+
typeof value["tool_use_id"] === "string" &&
|
|
148
|
+
(value["transcript_path"] === null || typeof value["transcript_path"] === "string") &&
|
|
149
|
+
typeof value["turn_id"] === "string" &&
|
|
150
|
+
Object.hasOwn(value, "tool_input")
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function isPostCompactPayload(value: unknown): value is PostCompactPayload {
|
|
155
|
+
if (!isRecord(value)) return false;
|
|
156
|
+
return (
|
|
157
|
+
value["hook_event_name"] === "PostCompact" &&
|
|
158
|
+
typeof value["session_id"] === "string" &&
|
|
159
|
+
(value["transcript_path"] === undefined ||
|
|
160
|
+
value["transcript_path"] === null ||
|
|
161
|
+
typeof value["transcript_path"] === "string") &&
|
|
162
|
+
(value["trigger"] === undefined || typeof value["trigger"] === "string")
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
167
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function readAll(stdin: NodeJS.ReadableStream): Promise<string> {
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
let data = "";
|
|
173
|
+
stdin.setEncoding("utf8");
|
|
174
|
+
stdin.on("data", (chunk: unknown) => {
|
|
175
|
+
data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
|
|
176
|
+
});
|
|
177
|
+
stdin.once("error", reject);
|
|
178
|
+
stdin.once("end", () => resolve(data));
|
|
179
|
+
});
|
|
180
|
+
}
|