oh-my-opencode 4.9.2 → 4.10.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.
Files changed (211) hide show
  1. package/.agents/skills/opencode-qa/scripts/lib/common.sh +39 -1
  2. package/.agents/skills/tech-debt-audit/SKILL.md +277 -0
  3. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/execution-plan.md +1 -1
  4. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/execution-plan.md +1 -1
  5. package/bin/platform.js +5 -0
  6. package/bin/platform.test.ts +56 -0
  7. package/dist/agents/atlas/agent.d.ts +4 -3
  8. package/dist/agents/gpt-apply-patch-guard.d.ts +2 -2
  9. package/dist/agents/hephaestus/agent.d.ts +5 -0
  10. package/dist/agents/hephaestus/index.d.ts +1 -1
  11. package/dist/agents/metis.d.ts +1 -0
  12. package/dist/agents/prometheus/system-prompt.d.ts +1 -1
  13. package/dist/agents/sisyphus/kimi-k2-7.d.ts +17 -0
  14. package/dist/agents/sisyphus-junior/agent.d.ts +1 -1
  15. package/dist/agents/sisyphus-junior/kimi-k2-7.d.ts +11 -0
  16. package/dist/agents/types.d.ts +2 -2
  17. package/dist/cli/doctor/checks/codex-components.d.ts +13 -0
  18. package/dist/cli/doctor/checks/tui-plugin-config.d.ts +1 -0
  19. package/dist/cli/doctor/constants.d.ts +1 -1
  20. package/dist/cli/index.js +929 -291
  21. package/dist/cli/install-codex/codex-cleanup.d.ts +4 -0
  22. package/dist/cli/install-codex/install-codex-test-fixtures.d.ts +34 -0
  23. package/dist/cli/install-codex/link-cached-plugin-agents.d.ts +4 -0
  24. package/dist/cli/model-fallback.d.ts +1 -0
  25. package/dist/cli/provider-availability.d.ts +2 -0
  26. package/dist/cli-node/index.js +929 -291
  27. package/dist/config/schema/agent-overrides.d.ts +80 -16
  28. package/dist/config/schema/experimental.d.ts +0 -1
  29. package/dist/config/schema/hooks.d.ts +0 -1
  30. package/dist/config/schema/internal/permission.d.ts +5 -1
  31. package/dist/config/schema/oh-my-opencode-config.d.ts +75 -16
  32. package/dist/create-hooks.d.ts +0 -1
  33. package/dist/features/background-agent/index.d.ts +1 -1
  34. package/dist/features/background-agent/manager.d.ts +6 -0
  35. package/dist/features/background-agent/types.d.ts +2 -0
  36. package/dist/features/claude-code-plugin-loader/types.d.ts +3 -0
  37. package/dist/features/claude-code-session-state/state.d.ts +1 -0
  38. package/dist/features/skill-mcp-manager/manager.d.ts +11 -7
  39. package/dist/features/team-mode/team-mailbox/pending-delivery-recovery.d.ts +31 -0
  40. package/dist/features/team-mode/team-runtime/delete-team.d.ts +2 -1
  41. package/dist/features/team-mode/tools/lifecycle-inline-spec.d.ts +2 -2
  42. package/dist/features/tmux-subagent/stale-tmux-resource-sweeper.d.ts +12 -0
  43. package/dist/features/tool-metadata-store/store.d.ts +5 -0
  44. package/dist/hooks/anthropic-context-window-limit-recovery/storage/constants.d.ts +3 -0
  45. package/dist/hooks/{session-recovery → anthropic-context-window-limit-recovery}/storage/messages-reader.d.ts +1 -1
  46. package/dist/hooks/{session-recovery → anthropic-context-window-limit-recovery}/storage/part-content.d.ts +1 -1
  47. package/dist/hooks/{session-recovery → anthropic-context-window-limit-recovery}/storage/parts-reader.d.ts +1 -1
  48. package/dist/hooks/{session-recovery → anthropic-context-window-limit-recovery/storage}/types.d.ts +0 -13
  49. package/dist/hooks/auto-update-checker/checker/bundled-version.d.ts +1 -0
  50. package/dist/hooks/auto-update-checker/checker.d.ts +1 -0
  51. package/dist/hooks/auto-update-checker/constants.d.ts +3 -3
  52. package/dist/hooks/auto-update-checker/hook.d.ts +2 -1
  53. package/dist/hooks/claude-code-hooks/types.d.ts +4 -0
  54. package/dist/hooks/index.d.ts +0 -1
  55. package/dist/hooks/team-session-events/team-idle-wake-hint.d.ts +5 -0
  56. package/dist/index.js +2991 -2367
  57. package/dist/oh-my-opencode.schema.json +120 -18
  58. package/dist/plugin/build-team-idle-wake-hint-client.d.ts +2 -0
  59. package/dist/plugin/event-session-lifecycle.d.ts +0 -3
  60. package/dist/plugin/hooks/create-continuation-hooks.d.ts +0 -6
  61. package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
  62. package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
  63. package/dist/shared/command-executor/execute-hook-command.d.ts +7 -0
  64. package/dist/shared/plugin-identity.d.ts +2 -2
  65. package/dist/shared/tmux/tmux-utils/server-health.d.ts +2 -1
  66. package/dist/shared/tmux/tmux-utils/stale-attach-pane-sweep.d.ts +16 -0
  67. package/dist/shared/tmux/tmux-utils.d.ts +1 -0
  68. package/dist/tools/background-task/clients.d.ts +2 -0
  69. package/dist/tools/background-task/full-session-format.d.ts +1 -0
  70. package/dist/tools/background-task/types.d.ts +1 -0
  71. package/dist/tools/delegate-task/sync-prompt-sender.d.ts +1 -1
  72. package/dist/tools/delegate-task/sync-session-lifecycle.d.ts +2 -1
  73. package/dist/tools/look-at/look-at-input-preparer.d.ts +6 -2
  74. package/dist/tools/look-at/look-at-prompt.d.ts +2 -1
  75. package/dist/tools/look-at/look-at-session-runner.d.ts +3 -4
  76. package/dist/tools/look-at/types.d.ts +2 -0
  77. package/dist/tools/session-manager/types.d.ts +1 -0
  78. package/dist/tools/skill-mcp/types.d.ts +1 -0
  79. package/package.json +14 -13
  80. package/packages/ast-grep-mcp/dist/cli.js +50 -17
  81. package/packages/lsp-daemon/dist/cli.js +8 -5
  82. package/packages/lsp-daemon/dist/index.js +8 -5
  83. package/packages/lsp-tools-mcp/dist/lsp/connection.js +1 -1
  84. package/packages/lsp-tools-mcp/dist/lsp/server-definitions.js +2 -2
  85. package/packages/lsp-tools-mcp/dist/lsp/transport.d.ts +10 -1
  86. package/packages/lsp-tools-mcp/dist/lsp/transport.js +6 -3
  87. package/packages/omo-codex/lazycodex-repository/.github/workflows/pr-source-guidance.yml +11 -12
  88. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  89. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +2583 -0
  90. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +17 -0
  91. package/packages/omo-codex/plugin/components/bootstrap/manifests/ast-grep.json +22 -0
  92. package/packages/omo-codex/plugin/components/bootstrap/manifests/node.json +10 -0
  93. package/packages/omo-codex/plugin/components/bootstrap/package.json +20 -0
  94. package/packages/omo-codex/plugin/components/bootstrap/scripts/bootstrap.ps1 +310 -0
  95. package/packages/omo-codex/plugin/components/bootstrap/scripts/build.mjs +35 -0
  96. package/packages/omo-codex/plugin/components/bootstrap/scripts/generate-manifests.mjs +115 -0
  97. package/packages/omo-codex/plugin/components/bootstrap/src/cli.ts +153 -0
  98. package/packages/omo-codex/plugin/components/bootstrap/src/download.ts +212 -0
  99. package/packages/omo-codex/plugin/components/bootstrap/src/environment.ts +286 -0
  100. package/packages/omo-codex/plugin/components/bootstrap/src/hook.ts +108 -0
  101. package/packages/omo-codex/plugin/components/bootstrap/src/provision.ts +243 -0
  102. package/packages/omo-codex/plugin/components/bootstrap/src/setup.ts +294 -0
  103. package/packages/omo-codex/plugin/components/bootstrap/src/worker.ts +279 -0
  104. package/packages/omo-codex/plugin/components/bootstrap/test/download.test.ts +295 -0
  105. package/packages/omo-codex/plugin/components/bootstrap/test/environment.test.ts +375 -0
  106. package/packages/omo-codex/plugin/components/bootstrap/test/provision.test.ts +464 -0
  107. package/packages/omo-codex/plugin/components/bootstrap/tsconfig.json +25 -0
  108. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  109. package/packages/omo-codex/plugin/components/comment-checker/package.json +4 -4
  110. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  111. package/packages/omo-codex/plugin/components/git-bash/package.json +2 -2
  112. package/packages/omo-codex/plugin/components/lsp/dist/codex-hook-cli.js +6 -10
  113. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  114. package/packages/omo-codex/plugin/components/lsp/package.json +4 -4
  115. package/packages/omo-codex/plugin/components/lsp/scripts/build-lsp-tools.test.mjs +8 -3
  116. package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +5 -8
  117. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-cli.test.ts +24 -1
  118. package/packages/omo-codex/plugin/components/rules/bundled-rules/windows-git-bash.md +3 -1
  119. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  120. package/packages/omo-codex/plugin/components/rules/package.json +4 -4
  121. package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +35 -1
  122. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  123. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +4 -4
  124. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  125. package/packages/omo-codex/plugin/components/telemetry/package.json +4 -4
  126. package/packages/omo-codex/plugin/components/ultrawork/biome.json +1 -1
  127. package/packages/omo-codex/plugin/components/ultrawork/directive.md +155 -99
  128. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  129. package/packages/omo-codex/plugin/components/ultrawork/package.json +4 -4
  130. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +19 -51
  131. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +46 -51
  132. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +19 -0
  133. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +0 -1
  134. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +9 -1
  135. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +1 -0
  136. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +18 -0
  137. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.js +1 -3
  138. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +2 -2
  139. package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -4
  140. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +6 -2
  141. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +19 -0
  142. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts +1 -1
  143. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-commands.test.ts +6 -0
  144. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-complete-goals.test.ts +26 -1
  145. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-json-errors.test.ts +89 -0
  146. package/packages/omo-codex/plugin/hooks/hooks.json +27 -16
  147. package/packages/omo-codex/plugin/package-lock.json +193 -193
  148. package/packages/omo-codex/plugin/package.json +1 -1
  149. package/packages/omo-codex/plugin/scripts/auto-update-state.d.mts +20 -0
  150. package/packages/omo-codex/plugin/scripts/auto-update.mjs +28 -8
  151. package/packages/omo-codex/plugin/scripts/build-components.mjs +36 -5
  152. package/packages/omo-codex/plugin/scripts/install-flow.mjs +43 -0
  153. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +79 -28
  154. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +2 -2
  155. package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +7 -6
  156. package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
  157. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +19 -51
  158. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +46 -51
  159. package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +1 -0
  160. package/packages/omo-codex/plugin/test/auto-update.test.mjs +145 -0
  161. package/packages/omo-codex/plugin/test/bootstrap-binlinks.test.mjs +250 -0
  162. package/packages/omo-codex/plugin/test/bootstrap-hooks.test.mjs +166 -0
  163. package/packages/omo-codex/plugin/test/bootstrap-orchestration.test.mjs +371 -0
  164. package/packages/omo-codex/plugin/test/bootstrap-ps-guard.test.mjs +134 -0
  165. package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +249 -0
  166. package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +10 -1
  167. package/packages/omo-codex/plugin/test/ulw-plan-skill.test.mjs +46 -0
  168. package/packages/omo-codex/scripts/atomic-write.test.mjs +82 -0
  169. package/packages/omo-codex/scripts/install/agents.d.mts +18 -0
  170. package/packages/omo-codex/scripts/install/agents.mjs +78 -5
  171. package/packages/omo-codex/scripts/install/atomic-write.mjs +59 -0
  172. package/packages/omo-codex/scripts/install/bin-dir.d.mts +7 -0
  173. package/packages/omo-codex/scripts/install/bin-links.d.mts +18 -0
  174. package/packages/omo-codex/scripts/install/config.d.mts +35 -0
  175. package/packages/omo-codex/scripts/install/config.mjs +13 -3
  176. package/packages/omo-codex/scripts/install/git-bash-mcp-env.d.mts +5 -0
  177. package/packages/omo-codex/scripts/install/git-bash.d.mts +23 -0
  178. package/packages/omo-codex/scripts/install/hook-trust.d.mts +10 -0
  179. package/packages/omo-codex/scripts/install-agent-links.test.mjs +41 -0
  180. package/packages/omo-codex/scripts/install-local.mjs +3 -2
  181. package/packages/shared-skills/skills/lcx-contribute-bug-fix/SKILL.md +79 -28
  182. package/packages/shared-skills/skills/lcx-contribute-bug-fix/agents/openai.yaml +2 -2
  183. package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +7 -6
  184. package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +1 -1
  185. package/dist/hooks/session-recovery/constants.d.ts +0 -4
  186. package/dist/hooks/session-recovery/detect-error-type.d.ts +0 -4
  187. package/dist/hooks/session-recovery/error-recovery.d.ts +0 -4
  188. package/dist/hooks/session-recovery/hook-types.d.ts +0 -22
  189. package/dist/hooks/session-recovery/hook.d.ts +0 -4
  190. package/dist/hooks/session-recovery/index.d.ts +0 -5
  191. package/dist/hooks/session-recovery/interrupted-idle-message-fetch-timeout.d.ts +0 -7
  192. package/dist/hooks/session-recovery/interrupted-tool-results.d.ts +0 -3
  193. package/dist/hooks/session-recovery/message-state.d.ts +0 -4
  194. package/dist/hooks/session-recovery/recover-thinking-block-order.d.ts +0 -5
  195. package/dist/hooks/session-recovery/recover-thinking-disabled-violation.d.ts +0 -5
  196. package/dist/hooks/session-recovery/recover-tool-result-missing.d.ts +0 -10
  197. package/dist/hooks/session-recovery/recover-unavailable-tool.d.ts +0 -5
  198. package/dist/hooks/session-recovery/resume.d.ts +0 -7
  199. package/dist/hooks/session-recovery/storage/latest-assistant-message.d.ts +0 -5
  200. package/dist/hooks/session-recovery/storage/orphan-thinking-search.d.ts +0 -2
  201. package/dist/hooks/session-recovery/storage/thinking-block-search.d.ts +0 -2
  202. package/dist/hooks/session-recovery/storage/thinking-prepend.d.ts +0 -33
  203. package/dist/hooks/session-recovery/storage/thinking-strip.d.ts +0 -11
  204. package/dist/hooks/session-recovery/storage.d.ts +0 -20
  205. package/dist/plugin/event-session-recovery.d.ts +0 -9
  206. package/dist/plugin/user-abort-interrupted-recovery-guard.d.ts +0 -6
  207. /package/dist/hooks/{session-recovery → anthropic-context-window-limit-recovery}/storage/empty-messages.d.ts +0 -0
  208. /package/dist/hooks/{session-recovery → anthropic-context-window-limit-recovery}/storage/empty-text.d.ts +0 -0
  209. /package/dist/hooks/{session-recovery → anthropic-context-window-limit-recovery}/storage/message-dir.d.ts +0 -0
  210. /package/dist/hooks/{session-recovery → anthropic-context-window-limit-recovery}/storage/part-id.d.ts +0 -0
  211. /package/dist/hooks/{session-recovery → anthropic-context-window-limit-recovery}/storage/text-part-injector.d.ts +0 -0
@@ -0,0 +1,2583 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { realpathSync } from "node:fs";
5
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
6
+
7
+ // src/download.ts
8
+ import { createHash, randomUUID } from "node:crypto";
9
+ import { createWriteStream } from "node:fs";
10
+ import { mkdir, readFile, rename, rm } from "node:fs/promises";
11
+ import { basename, dirname, join } from "node:path";
12
+ import { Readable } from "node:stream";
13
+ import { pipeline } from "node:stream/promises";
14
+ import { fileURLToPath } from "node:url";
15
+ var DownloadError = class extends Error {
16
+ code;
17
+ constructor(code, message) {
18
+ super(message);
19
+ this.name = "DownloadError";
20
+ this.code = code;
21
+ }
22
+ };
23
+ var ChecksumMismatchError = class extends DownloadError {
24
+ expectedSha256;
25
+ actualSha256;
26
+ constructor(options) {
27
+ super(
28
+ "checksum-mismatch",
29
+ `Checksum mismatch for ${options.url}: expected sha256 ${options.expectedSha256} but downloaded sha256 ${options.actualSha256}; deleted the partial download.`
30
+ );
31
+ this.name = "ChecksumMismatchError";
32
+ this.expectedSha256 = options.expectedSha256;
33
+ this.actualSha256 = options.actualSha256;
34
+ }
35
+ };
36
+ var UnsupportedPlatformError = class extends DownloadError {
37
+ manifestName;
38
+ platformKey;
39
+ constructor(options) {
40
+ super(
41
+ "unsupported-platform",
42
+ `Manifest "${options.manifestName}" has no asset for unsupported platform "${options.platformKey}" (available: ${options.availablePlatforms.join(", ")}).`
43
+ );
44
+ this.name = "UnsupportedPlatformError";
45
+ this.manifestName = options.manifestName;
46
+ this.platformKey = options.platformKey;
47
+ }
48
+ };
49
+ var PROXY_ENV_KEYS = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"];
50
+ function proxyLimitationNote(env) {
51
+ const configuredKey = PROXY_ENV_KEYS.find((key) => (env[key] ?? "").trim().length > 0);
52
+ if (configuredKey === void 0) return "";
53
+ return ` Note: ${configuredKey} is set, but the bootstrap downloader does not tunnel through HTTP(S) proxies in v1; the download was attempted directly.`;
54
+ }
55
+ function describeFailure(error) {
56
+ return error instanceof Error ? error.message : String(error);
57
+ }
58
+ async function writeBodyToFile(body, tempPath) {
59
+ const hash = createHash("sha256");
60
+ if (body === null) {
61
+ await pipeline(Readable.from([]), createWriteStream(tempPath));
62
+ return hash.digest("hex");
63
+ }
64
+ await pipeline(
65
+ Readable.fromWeb(body),
66
+ async function* hashChunks(source) {
67
+ for await (const chunk of source) {
68
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
69
+ hash.update(buffer);
70
+ yield buffer;
71
+ }
72
+ },
73
+ createWriteStream(tempPath)
74
+ );
75
+ return hash.digest("hex");
76
+ }
77
+ async function downloadChecksummedAsset(options) {
78
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
79
+ const env = options.env ?? process.env;
80
+ const expectedSha256 = options.sha256.toLowerCase();
81
+ await mkdir(dirname(options.destination), { recursive: true });
82
+ const tempPath = `${options.destination}.${randomUUID().slice(0, 8)}.partial`;
83
+ let response;
84
+ try {
85
+ response = await fetchImpl(options.url);
86
+ } catch (error) {
87
+ throw new DownloadError(
88
+ "download-failed",
89
+ `Download failed for ${options.url}: ${describeFailure(error)}.${proxyLimitationNote(env)}`
90
+ );
91
+ }
92
+ if (!response.ok) {
93
+ throw new DownloadError(
94
+ "download-failed",
95
+ `Download failed for ${options.url}: HTTP ${response.status}.${proxyLimitationNote(env)}`
96
+ );
97
+ }
98
+ let actualSha256;
99
+ try {
100
+ actualSha256 = await writeBodyToFile(response.body, tempPath);
101
+ } catch (error) {
102
+ await rm(tempPath, { force: true });
103
+ throw new DownloadError(
104
+ "download-failed",
105
+ `Download failed for ${options.url} while writing the response body: ${describeFailure(error)}.${proxyLimitationNote(env)}`
106
+ );
107
+ }
108
+ if (actualSha256 !== expectedSha256) {
109
+ await rm(tempPath, { force: true });
110
+ throw new ChecksumMismatchError({ actualSha256, expectedSha256, url: options.url });
111
+ }
112
+ await rename(tempPath, options.destination);
113
+ return options.destination;
114
+ }
115
+ function resolveDefaultManifestsDir() {
116
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "manifests");
117
+ }
118
+ function isRecord(value) {
119
+ return typeof value === "object" && value !== null && !Array.isArray(value);
120
+ }
121
+ function parseManifestAsset(value, manifestName, platformKey) {
122
+ if (!isRecord(value) || typeof value["url"] !== "string" || typeof value["sha256"] !== "string") {
123
+ throw new Error(`Manifest "${manifestName}" platform "${platformKey}" must pin both url and sha256 strings.`);
124
+ }
125
+ return { sha256: value["sha256"], url: value["url"] };
126
+ }
127
+ function parseAssetManifest(raw, manifestName) {
128
+ const data = JSON.parse(raw);
129
+ if (!isRecord(data) || typeof data["name"] !== "string" || typeof data["version"] !== "string" || !isRecord(data["platforms"])) {
130
+ throw new Error(`Manifest "${manifestName}" must declare name, version, and a platforms object.`);
131
+ }
132
+ const platforms = {};
133
+ for (const [platformKey, asset] of Object.entries(data["platforms"])) {
134
+ platforms[platformKey] = parseManifestAsset(asset, manifestName, platformKey);
135
+ }
136
+ return { name: data["name"], platforms, version: data["version"] };
137
+ }
138
+ async function loadAssetManifest(manifestName, manifestsDir) {
139
+ const directory = manifestsDir ?? resolveDefaultManifestsDir();
140
+ const raw = await readFile(join(directory, `${manifestName}.json`), "utf8");
141
+ return parseAssetManifest(raw, manifestName);
142
+ }
143
+ async function downloadFromManifest(options) {
144
+ const manifest = await loadAssetManifest(options.manifestName, options.manifestsDir);
145
+ const asset = manifest.platforms[options.platformKey];
146
+ if (asset === void 0) {
147
+ throw new UnsupportedPlatformError({
148
+ availablePlatforms: Object.keys(manifest.platforms),
149
+ manifestName: options.manifestName,
150
+ platformKey: options.platformKey
151
+ });
152
+ }
153
+ const destination = join(options.destinationDir, basename(new URL(asset.url).pathname));
154
+ return downloadChecksummedAsset({
155
+ destination,
156
+ sha256: asset.sha256,
157
+ url: asset.url,
158
+ ...options.fetchImpl === void 0 ? {} : { fetchImpl: options.fetchImpl },
159
+ ...options.env === void 0 ? {} : { env: options.env }
160
+ });
161
+ }
162
+
163
+ // src/hook.ts
164
+ import { spawn } from "node:child_process";
165
+ import { stat as stat5 } from "node:fs/promises";
166
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
167
+
168
+ // ../../scripts/auto-update-state.mjs
169
+ import { appendFile, mkdir as mkdir2, open, readFile as readFile2, rm as rm2, stat, writeFile } from "node:fs/promises";
170
+ import { homedir } from "node:os";
171
+ import { dirname as dirname2, join as join2 } from "node:path";
172
+ var DEFAULT_LOCK_STALE_MS = 10 * 60 * 1e3;
173
+ function resolveStatePath(env) {
174
+ if (env.LAZYCODEX_AUTO_UPDATE_STATE_PATH?.trim()) return env.LAZYCODEX_AUTO_UPDATE_STATE_PATH;
175
+ const dataRoot = env.PLUGIN_DATA?.trim() || join2(homedir(), ".local", "share", "lazycodex");
176
+ return join2(dataRoot, "auto-update.json");
177
+ }
178
+ function resolveLockPath(env, statePath) {
179
+ if (env.LAZYCODEX_AUTO_UPDATE_LOCK_PATH?.trim()) return env.LAZYCODEX_AUTO_UPDATE_LOCK_PATH;
180
+ return `${statePath}.lock`;
181
+ }
182
+ async function acquireLock(lockPath, now, staleMs = DEFAULT_LOCK_STALE_MS) {
183
+ await mkdir2(dirname2(lockPath), { recursive: true });
184
+ try {
185
+ const handle = await open(lockPath, "wx");
186
+ await handle.writeFile(`${now}
187
+ `);
188
+ await handle.close();
189
+ return {
190
+ release: () => rm2(lockPath, { force: true })
191
+ };
192
+ } catch (error) {
193
+ if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
194
+ if (!await removeStaleLock(lockPath, now, staleMs)) return null;
195
+ return acquireLock(lockPath, now, 0);
196
+ }
197
+ }
198
+ async function readState(statePath) {
199
+ try {
200
+ const raw = await readFile2(statePath, "utf8");
201
+ const parsed = JSON.parse(raw);
202
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
203
+ } catch (error) {
204
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
205
+ return {};
206
+ }
207
+ }
208
+ async function writeState(statePath, state) {
209
+ await mkdir2(dirname2(statePath), { recursive: true });
210
+ await writeFile(statePath, `${JSON.stringify(state, null, 2)}
211
+ `);
212
+ }
213
+ async function removeStaleLock(lockPath, now, staleMs) {
214
+ if (staleMs <= 0) return false;
215
+ try {
216
+ const lockStat = await stat(lockPath);
217
+ if (now - lockStat.mtimeMs < staleMs) return false;
218
+ await rm2(lockPath, { force: true });
219
+ return true;
220
+ } catch (error) {
221
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return true;
222
+ throw error;
223
+ }
224
+ }
225
+
226
+ // src/environment.ts
227
+ import { stat as stat2 } from "node:fs/promises";
228
+ import { readFile as readFile3 } from "node:fs/promises";
229
+ import { homedir as homedir2 } from "node:os";
230
+ import { dirname as dirname3, join as join3, resolve } from "node:path";
231
+ var INSTALL_SNAPSHOT_FILENAME = "lazycodex-install.json";
232
+ var DEFAULT_MARKETPLACE_NAME = "sisyphuslabs";
233
+ var MAX_CODEX_HOME_WALK_UP_LEVELS = 6;
234
+ async function detectInstallFlowDetailed(options) {
235
+ const marketplaceName = options.marketplaceName ?? DEFAULT_MARKETPLACE_NAME;
236
+ const snapshotPresent = await isFile(join3(options.pluginRoot, INSTALL_SNAPSHOT_FILENAME));
237
+ const snapshotSignal = snapshotPresent ? "npx-local" : "marketplace";
238
+ const snapshotReason = snapshotPresent ? `${INSTALL_SNAPSHOT_FILENAME} present at plugin root (written only by the npx installer)` : `${INSTALL_SNAPSHOT_FILENAME} absent from plugin root`;
239
+ const scan = options.configToml === void 0 ? { kind: "absent" } : scanMarketplaceSource(options.configToml, marketplaceName);
240
+ if (scan.kind === "absent") {
241
+ return {
242
+ configSignal: void 0,
243
+ configSource: void 0,
244
+ flow: snapshotSignal,
245
+ reason: `${snapshotReason}; no [marketplaces.${marketplaceName}] source to cross-check`,
246
+ snapshotPresent
247
+ };
248
+ }
249
+ if (scan.kind === "unparsable") {
250
+ return {
251
+ configSignal: "unparsable",
252
+ configSource: void 0,
253
+ flow: "unknown",
254
+ reason: `${snapshotReason}; [marketplaces.${marketplaceName}] source value is unparsable`,
255
+ snapshotPresent
256
+ };
257
+ }
258
+ const configSignal = classifyMarketplaceSource(scan.source);
259
+ if (configSignal === "unparsable") {
260
+ return {
261
+ configSignal,
262
+ configSource: scan.source,
263
+ flow: "unknown",
264
+ reason: `${snapshotReason}; marketplace source ${JSON.stringify(scan.source)} is neither a local absolute path nor a git URL`,
265
+ snapshotPresent
266
+ };
267
+ }
268
+ if (configSignal !== snapshotSignal) {
269
+ return {
270
+ configSignal,
271
+ configSource: scan.source,
272
+ flow: "unknown",
273
+ reason: `${snapshotReason}, but marketplace source ${JSON.stringify(scan.source)} indicates ${configSignal}; signals disagree`,
274
+ snapshotPresent
275
+ };
276
+ }
277
+ return {
278
+ configSignal,
279
+ configSource: scan.source,
280
+ flow: snapshotSignal,
281
+ reason: `${snapshotReason}; marketplace source ${JSON.stringify(scan.source)} agrees`,
282
+ snapshotPresent
283
+ };
284
+ }
285
+ async function detectInstallFlow(options) {
286
+ return (await detectInstallFlowDetailed(options)).flow;
287
+ }
288
+ async function detectInstallFlowFromEnvironment(options) {
289
+ const home = await resolveCodexHome({ env: options.env, pluginRoot: options.pluginRoot });
290
+ const configToml = await readOptionalFile(join3(home.path, "config.toml"));
291
+ return detectInstallFlowDetailed({
292
+ pluginRoot: options.pluginRoot,
293
+ ...configToml === void 0 ? {} : { configToml },
294
+ ...options.marketplaceName === void 0 ? {} : { marketplaceName: options.marketplaceName }
295
+ });
296
+ }
297
+ async function detectInstallFlowForTest(pluginRoot) {
298
+ const home = await resolveCodexHome({ env: {}, pluginRoot });
299
+ const configToml = home.source === "walk-up" ? await readOptionalFile(join3(home.path, "config.toml")) : void 0;
300
+ return detectInstallFlow({ pluginRoot, ...configToml === void 0 ? {} : { configToml } });
301
+ }
302
+ async function resolveCodexHome(options) {
303
+ const envHome = options.env["CODEX_HOME"]?.trim();
304
+ if (envHome !== void 0 && envHome.length > 0) {
305
+ return { path: resolve(envHome), source: "env" };
306
+ }
307
+ if (options.pluginRoot !== void 0) {
308
+ let current = resolve(options.pluginRoot);
309
+ for (let level = 0; level < MAX_CODEX_HOME_WALK_UP_LEVELS; level += 1) {
310
+ const parent = dirname3(current);
311
+ if (parent === current) break;
312
+ current = parent;
313
+ if (await isFile(join3(current, "config.toml"))) {
314
+ return { path: current, source: "walk-up" };
315
+ }
316
+ }
317
+ }
318
+ return { path: join3(homedir2(), ".codex"), source: "default" };
319
+ }
320
+ function resolveBootstrapStatePath(pluginData) {
321
+ return join3(pluginData, "bootstrap", "state.json");
322
+ }
323
+ function resolveBootstrapLockPath(pluginData) {
324
+ return `${resolveBootstrapStatePath(pluginData)}.lock`;
325
+ }
326
+ async function bootstrapLocks(options) {
327
+ const now = options.now ?? Date.now();
328
+ const staleMs = options.staleMs ?? DEFAULT_LOCK_STALE_MS;
329
+ const statePath = resolveBootstrapStatePath(options.pluginData);
330
+ const bootstrapLockPath = resolveBootstrapLockPath(options.pluginData);
331
+ const autoUpdateLockPath = resolveLockPath(options.env, resolveStatePath(options.env));
332
+ const bootstrapLock = await acquireLock(bootstrapLockPath, now, staleMs);
333
+ if (bootstrapLock === null) return null;
334
+ if (autoUpdateLockPath === bootstrapLockPath) {
335
+ return { autoUpdateLockPath, bootstrapLockPath, release: () => bootstrapLock.release(), statePath };
336
+ }
337
+ const autoUpdateLock = await acquireLock(autoUpdateLockPath, now, staleMs);
338
+ if (autoUpdateLock === null) {
339
+ await bootstrapLock.release();
340
+ return null;
341
+ }
342
+ return {
343
+ autoUpdateLockPath,
344
+ bootstrapLockPath,
345
+ release: async () => {
346
+ await autoUpdateLock.release();
347
+ await bootstrapLock.release();
348
+ },
349
+ statePath
350
+ };
351
+ }
352
+ function scanMarketplaceSource(configToml, marketplaceName) {
353
+ const expectedHeaders = /* @__PURE__ */ new Set([`marketplaces.${marketplaceName}`, `marketplaces.${JSON.stringify(marketplaceName)}`]);
354
+ let inMarketplaceSection = false;
355
+ for (const line of configToml.split("\n")) {
356
+ const header = parseTomlHeader(line);
357
+ if (header !== null) {
358
+ inMarketplaceSection = expectedHeaders.has(header);
359
+ continue;
360
+ }
361
+ if (!inMarketplaceSection) continue;
362
+ const valueText = parseSourceAssignment(line);
363
+ if (valueText === null) continue;
364
+ const source = parseTomlStringValue(valueText);
365
+ return source === void 0 ? { kind: "unparsable" } : { kind: "source", source };
366
+ }
367
+ return { kind: "absent" };
368
+ }
369
+ function parseTomlHeader(line) {
370
+ const trimmed = line.trim();
371
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return null;
372
+ if (trimmed.startsWith("[[")) return null;
373
+ return trimmed.slice(1, -1).trim();
374
+ }
375
+ function parseSourceAssignment(line) {
376
+ const match = /^\s*source\s*=\s*(.+)$/.exec(line);
377
+ return match === null ? null : match[1] ?? null;
378
+ }
379
+ function parseTomlStringValue(valueText) {
380
+ const trimmed = valueText.trim();
381
+ if (trimmed.startsWith('"')) return parseLeadingJsonString(trimmed);
382
+ if (trimmed.startsWith("'")) {
383
+ const closingIndex = trimmed.indexOf("'", 1);
384
+ return closingIndex === -1 ? void 0 : trimmed.slice(1, closingIndex);
385
+ }
386
+ return void 0;
387
+ }
388
+ function parseLeadingJsonString(value) {
389
+ let escaped = false;
390
+ for (let index = 1; index < value.length; index += 1) {
391
+ if (escaped) {
392
+ escaped = false;
393
+ continue;
394
+ }
395
+ const char = value[index];
396
+ if (char === "\\") {
397
+ escaped = true;
398
+ continue;
399
+ }
400
+ if (char === '"') {
401
+ try {
402
+ const parsed = JSON.parse(value.slice(0, index + 1));
403
+ return typeof parsed === "string" ? parsed : void 0;
404
+ } catch {
405
+ return void 0;
406
+ }
407
+ }
408
+ }
409
+ return void 0;
410
+ }
411
+ function classifyMarketplaceSource(source) {
412
+ const trimmed = source.trim();
413
+ if (trimmed.length === 0) return "unparsable";
414
+ if (/^(https?|ssh|git):\/\//i.test(trimmed) || trimmed.startsWith("git@")) return "marketplace";
415
+ if (trimmed.startsWith("/") || trimmed.startsWith("~") || trimmed.startsWith("\\\\") || /^[A-Za-z]:[\\/]/.test(trimmed)) {
416
+ return "npx-local";
417
+ }
418
+ if (trimmed.toLowerCase().endsWith(".git")) return "marketplace";
419
+ return "unparsable";
420
+ }
421
+ async function isFile(path) {
422
+ try {
423
+ return (await stat2(path)).isFile();
424
+ } catch {
425
+ return false;
426
+ }
427
+ }
428
+ async function readOptionalFile(path) {
429
+ try {
430
+ return await readFile3(path, "utf8");
431
+ } catch {
432
+ return void 0;
433
+ }
434
+ }
435
+
436
+ // src/worker.ts
437
+ import { appendFile as appendFile2, mkdir as mkdir8, readFile as readFile12 } from "node:fs/promises";
438
+ import { homedir as homedir4 } from "node:os";
439
+ import { dirname as dirname8, join as join15, resolve as resolve5 } from "node:path";
440
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
441
+
442
+ // src/provision.ts
443
+ import { execFile } from "node:child_process";
444
+ import { randomUUID as randomUUID2 } from "node:crypto";
445
+ import { chmod, mkdir as mkdir3, readFile as readFile4, rename as rename2, rm as rm3, writeFile as writeFile2 } from "node:fs/promises";
446
+ import { basename as basename2, dirname as dirname5, join as join5 } from "node:path";
447
+ import { promisify } from "node:util";
448
+ import { inflateRawSync } from "node:zlib";
449
+
450
+ // ../../../../ast-grep-mcp/src/sg-cli-path.ts
451
+ import { createRequire } from "node:module";
452
+ import { homedir as defaultHomedir } from "node:os";
453
+ import { dirname as dirname4, join as join4 } from "node:path";
454
+ import { existsSync, statSync } from "node:fs";
455
+ var SG_PATH_ENV_KEY = "OMO_AST_GREP_SG_PATH";
456
+ var WINDOWS_EXECUTABLE_EXTENSIONS = [".exe", ".cmd", ".bat"];
457
+ function isValidBinary(filePath) {
458
+ try {
459
+ const stats = statSync(filePath);
460
+ if (!stats.isFile()) {
461
+ return false;
462
+ }
463
+ const size = stats.size;
464
+ const lowerPath = filePath.toLowerCase();
465
+ if (lowerPath.endsWith(".cmd") || lowerPath.endsWith(".bat")) {
466
+ return size > 0;
467
+ }
468
+ return size > 1e4;
469
+ } catch {
470
+ return false;
471
+ }
472
+ }
473
+ function executableCandidates(filePath, platform = process.platform) {
474
+ if (platform !== "win32") return [filePath];
475
+ const candidates = [filePath];
476
+ const lowerPath = filePath.toLowerCase();
477
+ if (WINDOWS_EXECUTABLE_EXTENSIONS.some((extension) => lowerPath.endsWith(extension))) {
478
+ return candidates;
479
+ }
480
+ for (const extension of WINDOWS_EXECUTABLE_EXTENSIONS) {
481
+ candidates.push(`${filePath}${extension}`);
482
+ }
483
+ return candidates;
484
+ }
485
+ function findValidExecutable(filePath, platform = process.platform) {
486
+ for (const candidate of executableCandidates(filePath, platform)) {
487
+ if (existsSync(candidate) && isValidBinary(candidate)) {
488
+ return candidate;
489
+ }
490
+ }
491
+ return null;
492
+ }
493
+ function getPlatformPackageName(platform, arch) {
494
+ const platformMap = {
495
+ "darwin-arm64": "@ast-grep/cli-darwin-arm64",
496
+ "darwin-x64": "@ast-grep/cli-darwin-x64",
497
+ "linux-arm64": "@ast-grep/cli-linux-arm64-gnu",
498
+ "linux-x64": "@ast-grep/cli-linux-x64-gnu",
499
+ "win32-x64": "@ast-grep/cli-win32-x64-msvc",
500
+ "win32-arm64": "@ast-grep/cli-win32-arm64-msvc",
501
+ "win32-ia32": "@ast-grep/cli-win32-ia32-msvc"
502
+ };
503
+ return platformMap[`${platform}-${arch}`] ?? null;
504
+ }
505
+ function isModuleResolutionFailure(error) {
506
+ return error instanceof Error && (error.message.includes("Cannot find module") || error.message.includes("Cannot find package"));
507
+ }
508
+ function defaultResolveModulePath(specifier) {
509
+ const require2 = createRequire(import.meta.url);
510
+ return require2.resolve(specifier);
511
+ }
512
+ function nonEmptyValue(value) {
513
+ if (value === void 0) return void 0;
514
+ const trimmed = value.trim();
515
+ return trimmed.length === 0 ? void 0 : trimmed;
516
+ }
517
+ function findEnvOverrideSgPath(env, platform) {
518
+ const overridePath = nonEmptyValue(env[SG_PATH_ENV_KEY]);
519
+ if (overridePath === void 0) return null;
520
+ return findValidExecutable(overridePath, platform);
521
+ }
522
+ function findRuntimeDirSgPath(env, platform, arch, homedir5) {
523
+ const codexHome = nonEmptyValue(env["CODEX_HOME"]) ?? join4(homedir5(), ".codex");
524
+ const binaryName = platform === "win32" ? "sg.exe" : "sg";
525
+ const runtimePath = join4(codexHome, "runtime", "ast-grep", `${platform}-${arch}`, binaryName);
526
+ return findValidExecutable(runtimePath, platform);
527
+ }
528
+ function findSgCliPathSync(options = {}) {
529
+ const env = options.env ?? process.env;
530
+ const platform = options.platform ?? process.platform;
531
+ const arch = options.arch ?? process.arch;
532
+ const homedir5 = options.homedir ?? defaultHomedir;
533
+ const resolveModulePath = options.resolveModulePath ?? defaultResolveModulePath;
534
+ const envOverridePath = findEnvOverrideSgPath(env, platform);
535
+ if (envOverridePath) {
536
+ return envOverridePath;
537
+ }
538
+ const runtimeDirPath = findRuntimeDirSgPath(env, platform, arch, homedir5);
539
+ if (runtimeDirPath) {
540
+ return runtimeDirPath;
541
+ }
542
+ const binaryName = "sg";
543
+ try {
544
+ const cliPackageJsonPath = resolveModulePath("@ast-grep/cli/package.json");
545
+ const cliDirectory = dirname4(cliPackageJsonPath);
546
+ const sgPath = join4(cliDirectory, binaryName);
547
+ const validSgPath = findValidExecutable(sgPath, platform);
548
+ if (validSgPath) {
549
+ return validSgPath;
550
+ }
551
+ } catch (error) {
552
+ if (!isModuleResolutionFailure(error)) {
553
+ throw error;
554
+ }
555
+ }
556
+ const platformPackage = getPlatformPackageName(platform, arch);
557
+ if (platformPackage) {
558
+ try {
559
+ const packageJsonPath = resolveModulePath(`${platformPackage}/package.json`);
560
+ const packageDirectory = dirname4(packageJsonPath);
561
+ const astGrepBinaryName = "ast-grep";
562
+ const binaryPath = join4(packageDirectory, astGrepBinaryName);
563
+ const validBinaryPath = findValidExecutable(binaryPath, platform);
564
+ if (validBinaryPath) {
565
+ return validBinaryPath;
566
+ }
567
+ } catch (error) {
568
+ if (!isModuleResolutionFailure(error)) {
569
+ throw error;
570
+ }
571
+ }
572
+ }
573
+ if (platform === "darwin") {
574
+ const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
575
+ for (const path of homebrewPaths) {
576
+ if (existsSync(path) && isValidBinary(path)) {
577
+ return path;
578
+ }
579
+ }
580
+ }
581
+ return null;
582
+ }
583
+
584
+ // src/provision.ts
585
+ var SG_PROVISION_COMPONENT = "ast_grep";
586
+ var SG_FORCE_PROVISION_ENV_KEY = "OMO_BOOTSTRAP_FORCE_PROVISION";
587
+ var SG_MANIFEST_NAME = "ast-grep";
588
+ function sgProvisionDestination(context, arch) {
589
+ const binaryName = context.platform === "win32" ? "sg.exe" : "sg";
590
+ return join5(context.codexHome, "runtime", "ast-grep", `${context.platform}-${arch}`, binaryName);
591
+ }
592
+ async function runSgProvision(context, seams = {}) {
593
+ const arch = seams.arch ?? process.arch;
594
+ const destination = sgProvisionDestination(context, arch);
595
+ if (context.env[SG_FORCE_PROVISION_ENV_KEY] !== "1") {
596
+ const preexisting = (seams.resolvePreexistingSg ?? defaultResolvePreexistingSg)({
597
+ arch,
598
+ codexHome: context.codexHome,
599
+ env: context.env,
600
+ platform: context.platform
601
+ });
602
+ if (preexisting !== null) {
603
+ await appendBootstrapLog(context.pluginData, context.now, "sg-provision", { sg: `preexisting:${preexisting}` });
604
+ return { degraded: [] };
605
+ }
606
+ }
607
+ const stagingDir = join5(dirname5(destination), `.staging-${randomUUID2().slice(0, 8)}`);
608
+ try {
609
+ const version = await provisionFromManifest(context, seams, { arch, destination, stagingDir });
610
+ await appendBootstrapLog(context.pluginData, context.now, "sg-provision", {
611
+ sg: `provisioned:${destination}`,
612
+ version
613
+ });
614
+ return { degraded: [] };
615
+ } catch (error) {
616
+ const reason = error instanceof Error ? error.message : String(error);
617
+ await appendBootstrapLog(context.pluginData, context.now, "sg-provision-failed", { reason });
618
+ return { degraded: [{ component: SG_PROVISION_COMPONENT, hint: BOOTSTRAP_DOCTOR_HINT, reason }] };
619
+ } finally {
620
+ await rm3(stagingDir, { force: true, recursive: true });
621
+ }
622
+ }
623
+ async function provisionFromManifest(context, seams, layout) {
624
+ const manifest = await loadAssetManifest(SG_MANIFEST_NAME, context.flags.manifestDir);
625
+ const platformKey = `${context.platform}-${layout.arch}`;
626
+ const asset = manifest.platforms[platformKey];
627
+ if (asset === void 0) {
628
+ throw new Error(
629
+ `ast-grep ${manifest.version} has no asset for unsupported platform "${platformKey}" (available: ${Object.keys(manifest.platforms).join(", ")}).`
630
+ );
631
+ }
632
+ await mkdir3(layout.stagingDir, { recursive: true });
633
+ const archivePath = await downloadChecksummedAsset({
634
+ destination: join5(layout.stagingDir, basename2(new URL(asset.url).pathname)),
635
+ env: context.env,
636
+ sha256: asset.sha256,
637
+ url: asset.url,
638
+ ...seams.fetchImpl === void 0 ? {} : { fetchImpl: seams.fetchImpl }
639
+ });
640
+ const binaryBytes = extractStandaloneSgBinary(await readFile4(archivePath), context.platform);
641
+ const stagedBinary = join5(layout.stagingDir, basename2(layout.destination));
642
+ await writeFile2(stagedBinary, binaryBytes);
643
+ await chmod(stagedBinary, 493);
644
+ await rename2(stagedBinary, layout.destination);
645
+ await verifyProvisionedVersion(layout.destination, manifest.version, seams);
646
+ return manifest.version;
647
+ }
648
+ async function verifyProvisionedVersion(destination, pinnedVersion, seams) {
649
+ let reported;
650
+ try {
651
+ reported = (await (seams.runVersionProbe ?? defaultVersionProbe)(destination)).trim();
652
+ } catch (error) {
653
+ await rm3(destination, { force: true });
654
+ throw new Error(
655
+ `provisioned sg at ${destination} failed its --version probe: ${error instanceof Error ? error.message : String(error)}`
656
+ );
657
+ }
658
+ if (!reported.includes(pinnedVersion)) {
659
+ await rm3(destination, { force: true });
660
+ throw new Error(
661
+ `provisioned sg at ${destination} reported "${reported}" but the manifest pins version ${pinnedVersion}; removed the binary.`
662
+ );
663
+ }
664
+ }
665
+ function defaultResolvePreexistingSg(options) {
666
+ return findSgCliPathSync({
667
+ arch: options.arch,
668
+ env: { ...options.env, CODEX_HOME: options.codexHome },
669
+ platform: options.platform
670
+ });
671
+ }
672
+ var execFileAsync = promisify(execFile);
673
+ async function defaultVersionProbe(binaryPath) {
674
+ const { stdout } = await execFileAsync(binaryPath, ["--version"]);
675
+ return String(stdout);
676
+ }
677
+ function extractStandaloneSgBinary(zip, platform) {
678
+ const suffix = platform === "win32" ? ".exe" : "";
679
+ const entries = listZipEntries(zip);
680
+ const preferredNames = [`ast-grep${suffix}`, `sg${suffix}`];
681
+ for (const preferred of preferredNames) {
682
+ const entry = entries.find((candidate) => zipEntryBaseName(candidate.name) === preferred);
683
+ if (entry !== void 0) return readZipEntryBytes(zip, entry);
684
+ }
685
+ throw new Error(
686
+ `ast-grep release zip has no ${preferredNames.join(" or ")} entry (found: ${entries.map((entry) => entry.name).join(", ")}).`
687
+ );
688
+ }
689
+ function zipEntryBaseName(entryName) {
690
+ const segments = entryName.split("/");
691
+ return segments[segments.length - 1] ?? entryName;
692
+ }
693
+ var EOCD_SIGNATURE = 101010256;
694
+ var CENTRAL_SIGNATURE = 33639248;
695
+ var LOCAL_SIGNATURE = 67324752;
696
+ var ZIP64_SENTINEL = 4294967295;
697
+ function listZipEntries(zip) {
698
+ const eocdOffset = findEndOfCentralDirectory(zip);
699
+ const entryCount = zip.readUInt16LE(eocdOffset + 10);
700
+ let cursor = zip.readUInt32LE(eocdOffset + 16);
701
+ const entries = [];
702
+ for (let index = 0; index < entryCount; index += 1) {
703
+ if (cursor + 46 > zip.length || zip.readUInt32LE(cursor) !== CENTRAL_SIGNATURE) {
704
+ throw new Error("zip central directory is corrupt (bad entry signature)");
705
+ }
706
+ const nameLength = zip.readUInt16LE(cursor + 28);
707
+ const extraLength = zip.readUInt16LE(cursor + 30);
708
+ const commentLength = zip.readUInt16LE(cursor + 32);
709
+ entries.push({
710
+ compressedSize: zip.readUInt32LE(cursor + 20),
711
+ localHeaderOffset: zip.readUInt32LE(cursor + 42),
712
+ method: zip.readUInt16LE(cursor + 10),
713
+ name: zip.subarray(cursor + 46, cursor + 46 + nameLength).toString("utf8"),
714
+ uncompressedSize: zip.readUInt32LE(cursor + 24)
715
+ });
716
+ cursor += 46 + nameLength + extraLength + commentLength;
717
+ }
718
+ return entries;
719
+ }
720
+ function findEndOfCentralDirectory(zip) {
721
+ const lowestOffset = Math.max(0, zip.length - 22 - 65535);
722
+ for (let offset = zip.length - 22; offset >= lowestOffset; offset -= 1) {
723
+ if (zip.readUInt32LE(offset) === EOCD_SIGNATURE) return offset;
724
+ }
725
+ throw new Error("downloaded asset is not a zip archive (end-of-central-directory record missing)");
726
+ }
727
+ function readZipEntryBytes(zip, entry) {
728
+ if (entry.compressedSize === ZIP64_SENTINEL || entry.uncompressedSize === ZIP64_SENTINEL || entry.localHeaderOffset === ZIP64_SENTINEL) {
729
+ throw new Error(`zip entry ${entry.name} uses unsupported zip64 extensions`);
730
+ }
731
+ if (zip.readUInt32LE(entry.localHeaderOffset) !== LOCAL_SIGNATURE) {
732
+ throw new Error(`zip entry ${entry.name} has a corrupt local header`);
733
+ }
734
+ const nameLength = zip.readUInt16LE(entry.localHeaderOffset + 26);
735
+ const extraLength = zip.readUInt16LE(entry.localHeaderOffset + 28);
736
+ const dataStart = entry.localHeaderOffset + 30 + nameLength + extraLength;
737
+ const raw = zip.subarray(dataStart, dataStart + entry.compressedSize);
738
+ const bytes = decompressZipEntry(raw, entry);
739
+ if (bytes.length !== entry.uncompressedSize) {
740
+ throw new Error(
741
+ `zip entry ${entry.name} inflated to ${bytes.length} bytes but the archive declares ${entry.uncompressedSize}`
742
+ );
743
+ }
744
+ return bytes;
745
+ }
746
+ function decompressZipEntry(raw, entry) {
747
+ if (entry.method === 0) return Buffer.from(raw);
748
+ if (entry.method === 8) return inflateRawSync(raw);
749
+ throw new Error(`zip entry ${entry.name} uses unsupported compression method ${entry.method}`);
750
+ }
751
+
752
+ // src/setup.ts
753
+ import { execFile as execFile2 } from "node:child_process";
754
+ import { copyFile as copyFile2, mkdir as mkdir7, readdir as readdir3, rm as rm7, stat as stat4 } from "node:fs/promises";
755
+ import { join as join14 } from "node:path";
756
+ import { promisify as promisify2 } from "node:util";
757
+
758
+ // ../../../scripts/install/agents.mjs
759
+ import { basename as basename3, join as join6 } from "node:path";
760
+ import { copyFile, lstat, mkdir as mkdir4, readFile as readFile5, readdir, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
761
+
762
+ // ../../../scripts/install/utils.mjs
763
+ import { constants as fsConstants } from "node:fs";
764
+ import { access } from "node:fs/promises";
765
+ async function exists(path) {
766
+ try {
767
+ await access(path, fsConstants.F_OK);
768
+ return true;
769
+ } catch {
770
+ return false;
771
+ }
772
+ }
773
+ function isRecord2(value) {
774
+ return typeof value === "object" && value !== null && !Array.isArray(value);
775
+ }
776
+
777
+ // ../../../scripts/install/agents.mjs
778
+ var MANIFEST_FILE = ".installed-agents.json";
779
+ async function capturePreservedAgentReasoning({ codexHome }) {
780
+ const agentsDir = join6(codexHome, "agents");
781
+ if (!await exists(agentsDir)) return /* @__PURE__ */ new Map();
782
+ const preserved = /* @__PURE__ */ new Map();
783
+ const agentEntries = await readdir(agentsDir, { withFileTypes: true });
784
+ for (const entry of agentEntries) {
785
+ if (!entry.name.endsWith(".toml")) continue;
786
+ const content = await readTextIfExists(join6(agentsDir, entry.name));
787
+ if (content === null) continue;
788
+ const effort = extractReasoningEffort(content);
789
+ if (effort !== null) preserved.set(agentNameFromToml(entry.name), effort);
790
+ }
791
+ return preserved;
792
+ }
793
+ async function capturePreservedAgentServiceTier({ codexHome }) {
794
+ const agentsDir = join6(codexHome, "agents");
795
+ if (!await exists(agentsDir)) return /* @__PURE__ */ new Map();
796
+ const preserved = /* @__PURE__ */ new Map();
797
+ const agentEntries = await readdir(agentsDir, { withFileTypes: true });
798
+ for (const entry of agentEntries) {
799
+ if (!entry.name.endsWith(".toml")) continue;
800
+ const content = await readTextIfExists(join6(agentsDir, entry.name));
801
+ if (content === null) continue;
802
+ preserved.set(agentNameFromToml(entry.name), extractServiceTier(content));
803
+ }
804
+ return preserved;
805
+ }
806
+ async function linkCachedPluginAgents({ codexHome, pluginRoot, preservedReasoning = /* @__PURE__ */ new Map(), preservedServiceTier = /* @__PURE__ */ new Map() }) {
807
+ const bundledAgents = await discoverBundledAgents(pluginRoot);
808
+ if (bundledAgents.length === 0) {
809
+ await writeManifest(pluginRoot, []);
810
+ return [];
811
+ }
812
+ const agentsDir = join6(codexHome, "agents");
813
+ await mkdir4(agentsDir, { recursive: true });
814
+ const linked = [];
815
+ for (const agentPath of bundledAgents) {
816
+ const agentFileName = basename3(agentPath);
817
+ const agentName = agentNameFromToml(agentFileName);
818
+ const linkPath = join6(agentsDir, agentFileName);
819
+ await replaceWithCopy(linkPath, agentPath);
820
+ await restorePreservedReasoning({ linkPath, target: agentPath, value: preservedReasoning.get(agentName) });
821
+ await restorePreservedServiceTier({
822
+ linkPath,
823
+ preserved: preservedServiceTier.has(agentName),
824
+ value: preservedServiceTier.get(agentName) ?? null
825
+ });
826
+ linked.push({ name: agentFileName, path: linkPath, target: agentPath });
827
+ }
828
+ await writeManifest(pluginRoot, linked.map((entry) => entry.path));
829
+ return linked;
830
+ }
831
+ async function restorePreservedServiceTier({ linkPath, preserved, value }) {
832
+ if (!preserved) return;
833
+ const content = await readFile5(linkPath, "utf8");
834
+ if (extractServiceTier(content) === value) return;
835
+ const replacement = replaceServiceTier(content, value);
836
+ if (!replacement.replaced) return;
837
+ await writeFile3(linkPath, replacement.content);
838
+ }
839
+ async function discoverBundledAgents(pluginRoot) {
840
+ const componentsRoot = join6(pluginRoot, "components");
841
+ if (!await exists(componentsRoot)) return [];
842
+ const componentEntries = await readdir(componentsRoot, { withFileTypes: true });
843
+ const agents = [];
844
+ for (const entry of componentEntries) {
845
+ if (!entry.isDirectory()) continue;
846
+ const agentsRoot = join6(componentsRoot, entry.name, "agents");
847
+ if (!await exists(agentsRoot)) continue;
848
+ const agentEntries = await readdir(agentsRoot, { withFileTypes: true });
849
+ for (const file of agentEntries) {
850
+ if (!file.isFile() || !file.name.endsWith(".toml")) continue;
851
+ agents.push(join6(agentsRoot, file.name));
852
+ }
853
+ }
854
+ agents.sort();
855
+ return agents;
856
+ }
857
+ async function replaceWithCopy(linkPath, target) {
858
+ await prepareReplacement(linkPath);
859
+ await copyFile(target, linkPath);
860
+ }
861
+ async function prepareReplacement(linkPath) {
862
+ if (!await lstatExists(linkPath)) return;
863
+ const entryStat = await lstat(linkPath);
864
+ if (entryStat.isDirectory() && !entryStat.isSymbolicLink()) {
865
+ throw new Error(`${linkPath} already exists and is a directory; refusing to replace`);
866
+ }
867
+ await rm4(linkPath, { force: true });
868
+ }
869
+ async function writeManifest(pluginRoot, agentPaths) {
870
+ const manifestPath = join6(pluginRoot, MANIFEST_FILE);
871
+ const payload = { agents: [...agentPaths].sort() };
872
+ await writeFile3(manifestPath, `${JSON.stringify(payload, null, " ")}
873
+ `);
874
+ }
875
+ async function restorePreservedReasoning({ linkPath, target, value }) {
876
+ if (value === void 0) return;
877
+ const content = await readFile5(target, "utf8");
878
+ if (extractReasoningEffort(content) === value) return;
879
+ const replacement = replaceReasoningEffort(content, value);
880
+ if (!replacement.replaced) return;
881
+ if (await lstatExists(linkPath)) {
882
+ await rm4(linkPath, { force: true });
883
+ }
884
+ await writeFile3(linkPath, replacement.content);
885
+ }
886
+ async function readTextIfExists(path) {
887
+ try {
888
+ return await readFile5(path, "utf8");
889
+ } catch (error) {
890
+ if (nodeErrorCode(error) === "ENOENT") return null;
891
+ throw error;
892
+ }
893
+ }
894
+ function extractReasoningEffort(content) {
895
+ return extractTopLevelStringSetting(content, "model_reasoning_effort");
896
+ }
897
+ function extractServiceTier(content) {
898
+ return extractTopLevelStringSetting(content, "service_tier");
899
+ }
900
+ function extractTopLevelStringSetting(content, key) {
901
+ for (const line of content.split(/\n/)) {
902
+ if (isSectionHeader(line)) return null;
903
+ const rawValue = topLevelStringSettingRawValue(line, key);
904
+ if (rawValue === void 0) continue;
905
+ return JSON.parse(rawValue);
906
+ }
907
+ return null;
908
+ }
909
+ function replaceReasoningEffort(content, value) {
910
+ return replaceTopLevelStringSetting(content, "model_reasoning_effort", value, { insertIfMissing: false });
911
+ }
912
+ function replaceServiceTier(content, value) {
913
+ return replaceTopLevelStringSetting(content, "service_tier", value, { insertIfMissing: true });
914
+ }
915
+ function replaceTopLevelStringSetting(content, key, value, options) {
916
+ let replaced = false;
917
+ const lines = content.split(/\n/);
918
+ for (let index = 0; index < lines.length; index += 1) {
919
+ const line = lines[index];
920
+ if (isSectionHeader(line)) break;
921
+ if (topLevelStringSettingRawValue(line, key) === void 0) continue;
922
+ if (value === null) {
923
+ lines.splice(index, 1);
924
+ replaced = true;
925
+ break;
926
+ }
927
+ lines[index] = line.replace(/=\s*"(?:[^"\\]|\\.)*"/, `= ${JSON.stringify(value)}`);
928
+ replaced = true;
929
+ break;
930
+ }
931
+ if (!replaced && value !== null && options.insertIfMissing) {
932
+ lines.splice(topLevelInsertionIndex(lines), 0, `${key} = ${JSON.stringify(value)}`);
933
+ replaced = true;
934
+ }
935
+ return { content: lines.join("\n"), replaced };
936
+ }
937
+ function topLevelStringSettingRawValue(line, key) {
938
+ const match = line.match(/^\s*([A-Za-z0-9_]+)\s*=\s*("(?:[^"\\]|\\.)*")/);
939
+ if (match === null) return void 0;
940
+ const settingKey = match[1];
941
+ const rawValue = match[2];
942
+ if (settingKey !== key || rawValue === void 0) return void 0;
943
+ return rawValue;
944
+ }
945
+ function topLevelInsertionIndex(lines) {
946
+ const sectionIndex = lines.findIndex((line) => isSectionHeader(line));
947
+ const topLevelEnd = sectionIndex === -1 ? lines.length : sectionIndex;
948
+ let insertionIndex = topLevelEnd;
949
+ while (insertionIndex > 0 && lines[insertionIndex - 1] === "") {
950
+ insertionIndex -= 1;
951
+ }
952
+ return insertionIndex;
953
+ }
954
+ function isSectionHeader(line) {
955
+ const trimmed = line.trim();
956
+ return trimmed.startsWith("[") && trimmed.endsWith("]");
957
+ }
958
+ function agentNameFromToml(fileName) {
959
+ return fileName.endsWith(".toml") ? fileName.slice(0, -".toml".length) : fileName;
960
+ }
961
+ async function lstatExists(path) {
962
+ try {
963
+ await lstat(path);
964
+ return true;
965
+ } catch (error) {
966
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
967
+ throw error;
968
+ }
969
+ }
970
+ function nodeErrorCode(error) {
971
+ if (!(error instanceof Error) || !("code" in error)) return null;
972
+ return typeof error.code === "string" ? error.code : null;
973
+ }
974
+
975
+ // ../../../scripts/install/bin-dir.mjs
976
+ import { homedir as homedir3 } from "node:os";
977
+ import { join as join7, resolve as resolve2 } from "node:path";
978
+ function resolveCodexInstallerBinDir(options = {}) {
979
+ const homeDir = resolve2(options.homeDir ?? homedir3());
980
+ const env = options.env ?? process.env;
981
+ const explicitBinDir = nonEmptyEnvValue(env, "CODEX_LOCAL_BIN_DIR");
982
+ if (explicitBinDir !== void 0) return explicitBinDir;
983
+ const codexHome = resolve2(options.codexHome ?? nonEmptyEnvValue(env, "CODEX_HOME") ?? join7(homeDir, ".codex"));
984
+ const defaultCodexHome = resolve2(join7(homeDir, ".codex"));
985
+ return codexHome === defaultCodexHome ? join7(homeDir, ".local", "bin") : join7(codexHome, "bin");
986
+ }
987
+ function nonEmptyEnvValue(env, key) {
988
+ const value = env[key];
989
+ if (typeof value !== "string") return void 0;
990
+ const trimmed = value.trim();
991
+ return trimmed.length === 0 ? void 0 : trimmed;
992
+ }
993
+
994
+ // ../../../scripts/install/bin-links.mjs
995
+ import { chmod as chmod2, lstat as lstat3, mkdir as mkdir5, readFile as readFile7, readdir as readdir2, readlink as readlink2, rm as rm6, stat as stat3, symlink, writeFile as writeFile4 } from "node:fs/promises";
996
+ import { basename as basename4, join as join9, relative, resolve as resolve3 } from "node:path";
997
+
998
+ // ../../../scripts/install/command-shim.mjs
999
+ var COMMAND_SHIM_MARKER = ":: generated by oh-my-openagent Codex installer";
1000
+
1001
+ // ../../../scripts/install/legacy-bins.mjs
1002
+ import { lstat as lstat2, readFile as readFile6, readlink, rm as rm5 } from "node:fs/promises";
1003
+ import { join as join8 } from "node:path";
1004
+ var LEGACY_CODEX_COMPONENT_BINS = [
1005
+ { name: "omo", component: "ulw-loop" },
1006
+ { name: "codex-comment-checker", component: "comment-checker" },
1007
+ { name: "codex-lsp", component: "lsp" },
1008
+ { name: "codex-rules", component: "rules" },
1009
+ { name: "codex-start-work-continuation", component: "start-work-continuation" },
1010
+ { name: "codex-telemetry", component: "telemetry" },
1011
+ { name: "codex-ultrawork", component: "ultrawork" }
1012
+ ];
1013
+ async function removeLegacyCodexComponentBins(binDir, platform) {
1014
+ for (const entry of LEGACY_CODEX_COMPONENT_BINS) {
1015
+ const linkPath = join8(binDir, platform === "win32" ? `${entry.name}.cmd` : entry.name);
1016
+ await removeLegacyCodexComponentBin(linkPath, entry.component, platform);
1017
+ }
1018
+ }
1019
+ async function removeLegacyCodexComponentBin(linkPath, component, platform) {
1020
+ try {
1021
+ const stat6 = await lstat2(linkPath);
1022
+ if (platform !== "win32") {
1023
+ if (!stat6.isSymbolicLink()) return;
1024
+ const target = await readlink(linkPath);
1025
+ if (isManagedLegacyComponentTarget(target, component)) await rm5(linkPath, { force: true });
1026
+ return;
1027
+ }
1028
+ if (!stat6.isFile()) return;
1029
+ const content = await readFile6(linkPath, "utf8");
1030
+ if (content.includes(COMMAND_SHIM_MARKER)) await rm5(linkPath, { force: true });
1031
+ } catch (error) {
1032
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return;
1033
+ throw error;
1034
+ }
1035
+ }
1036
+ function isManagedLegacyComponentTarget(target, component) {
1037
+ const parts = target.split(/[\\/]+/);
1038
+ const suffixStart = parts.length - 4;
1039
+ const suffix = parts.slice(-4);
1040
+ return suffix[0] === "components" && suffix[1] === component && suffix[2] === "dist" && suffix[3] === "cli.js" && (hasPluginCachePrefix(parts, suffixStart) || hasOmoCodexPluginPrefix(parts, suffixStart));
1041
+ }
1042
+ function hasPluginCachePrefix(parts, endExclusive) {
1043
+ for (let index = 0; index < endExclusive - 1; index += 1) {
1044
+ if (parts[index] === "plugins" && parts[index + 1] === "cache") return true;
1045
+ }
1046
+ return false;
1047
+ }
1048
+ function hasOmoCodexPluginPrefix(parts, endExclusive) {
1049
+ for (let index = 0; index <= endExclusive - 3; index += 1) {
1050
+ if (parts[index] === "packages" && parts[index + 1] === "omo-codex" && parts[index + 2] === "plugin") return true;
1051
+ }
1052
+ return false;
1053
+ }
1054
+
1055
+ // ../../../scripts/install/bin-links.mjs
1056
+ var RESERVED_NESTED_BIN_NAMES = /* @__PURE__ */ new Set(["omo", "lazycodex", "lazycodex-ai", "oh-my-opencode", "oh-my-openagent"]);
1057
+ var RUNTIME_WRAPPER_MARKER = "OMO_GENERATED_RUNTIME_WRAPPER";
1058
+ async function linkCachedPluginBins({ binDir, pluginRoot, platform = process.platform }) {
1059
+ const binLinks = await discoverPackageBins(pluginRoot);
1060
+ await mkdir5(binDir, { recursive: true });
1061
+ await removeLegacyCodexComponentBins(binDir, platform);
1062
+ const linked = [];
1063
+ for (const link of binLinks) {
1064
+ const linkPath = await linkCachedPluginBin(binDir, link, platform);
1065
+ linked.push({ name: link.name, path: linkPath, target: link.target });
1066
+ }
1067
+ return linked;
1068
+ }
1069
+ async function linkRootRuntimeBin({ binDir, codexHome, repoRoot, platform = process.platform }) {
1070
+ const cliPath = join9(repoRoot, "dist", "cli", "index.js");
1071
+ if (!await isFile2(cliPath)) return null;
1072
+ const nodeCliPath = join9(repoRoot, "dist", "cli-node", "index.js");
1073
+ await mkdir5(binDir, { recursive: true });
1074
+ if (platform === "win32") {
1075
+ const linkPath2 = join9(binDir, "omo.cmd");
1076
+ await replaceRuntimeWrapper(linkPath2, windowsRuntimeWrapper(cliPath, codexHome, binDir, nodeCliPath));
1077
+ return { name: "omo", path: linkPath2, target: cliPath };
1078
+ }
1079
+ const linkPath = join9(binDir, "omo");
1080
+ await replaceRuntimeWrapper(linkPath, posixRuntimeWrapper(cliPath, codexHome, binDir, nodeCliPath));
1081
+ await chmod2(linkPath, 493);
1082
+ return { name: "omo", path: linkPath, target: cliPath };
1083
+ }
1084
+ async function linkCachedPluginBin(binDir, link, platform) {
1085
+ if (platform === "win32") {
1086
+ const linkPath2 = join9(binDir, `${link.name}.cmd`);
1087
+ await replaceCommandShim(linkPath2, link.target);
1088
+ return linkPath2;
1089
+ }
1090
+ const linkPath = join9(binDir, link.name);
1091
+ await replaceSymlink(linkPath, link.target);
1092
+ return linkPath;
1093
+ }
1094
+ async function isFile2(path) {
1095
+ try {
1096
+ return (await stat3(path)).isFile();
1097
+ } catch (error) {
1098
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
1099
+ throw error;
1100
+ }
1101
+ }
1102
+ async function discoverPackageBins(root) {
1103
+ const links = [];
1104
+ await collectPackageBins(root, root, links);
1105
+ return links;
1106
+ }
1107
+ async function collectPackageBins(directory, root, links) {
1108
+ const entries = await readdir2(directory, { withFileTypes: true });
1109
+ const packageJsonPath = join9(directory, "package.json");
1110
+ if (entries.some((entry) => entry.isFile() && entry.name === "package.json")) {
1111
+ await appendPackageBinLinks(packageJsonPath, directory, root, links);
1112
+ }
1113
+ for (const entry of entries) {
1114
+ if (!entry.isDirectory()) continue;
1115
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") continue;
1116
+ const childPath = join9(directory, entry.name);
1117
+ if (!isPathInside(childPath, root)) continue;
1118
+ await collectPackageBins(childPath, root, links);
1119
+ }
1120
+ }
1121
+ async function appendPackageBinLinks(packageJsonPath, packageRoot, root, links) {
1122
+ const packageJson = JSON.parse(await readFile7(packageJsonPath, "utf8"));
1123
+ if (!isRecord3(packageJson)) return;
1124
+ const bin = packageJson.bin;
1125
+ if (typeof bin === "string" && typeof packageJson.name === "string") {
1126
+ const name = basename4(packageJson.name);
1127
+ if (!isReservedNestedBinName(name, packageRoot, root)) {
1128
+ links.push(createPackageBinLink(name, bin, packageRoot));
1129
+ }
1130
+ return;
1131
+ }
1132
+ if (!isRecord3(bin)) return;
1133
+ for (const [name, target] of Object.entries(bin)) {
1134
+ if (typeof target !== "string") continue;
1135
+ if (isReservedNestedBinName(name, packageRoot, root)) continue;
1136
+ links.push(createPackageBinLink(name, target, packageRoot));
1137
+ }
1138
+ }
1139
+ function createPackageBinLink(name, target, packageRoot) {
1140
+ assertSafeBinName(name);
1141
+ if (target.includes("\0")) {
1142
+ throw new Error(`package bin target for ${name} contains a NUL byte`);
1143
+ }
1144
+ const resolvedTarget = resolve3(packageRoot, target);
1145
+ if (!isPathInside(resolvedTarget, packageRoot)) {
1146
+ throw new Error(`package bin target for ${name} escapes package root`);
1147
+ }
1148
+ return { name, target: resolvedTarget };
1149
+ }
1150
+ function assertSafeBinName(name) {
1151
+ if (name.length === 0 || name === "." || name === ".." || name.includes("\0") || name.includes("/") || name.includes("\\")) {
1152
+ throw new Error(`invalid package bin name: ${name}`);
1153
+ }
1154
+ }
1155
+ function isReservedNestedBinName(name, packageRoot, root) {
1156
+ return packageRoot !== root && RESERVED_NESTED_BIN_NAMES.has(name);
1157
+ }
1158
+ async function replaceSymlink(linkPath, targetPath) {
1159
+ if (await existingNonSymlink(linkPath)) {
1160
+ throw new Error(`${linkPath} already exists and is not a symlink`);
1161
+ }
1162
+ await rm6(linkPath, { force: true });
1163
+ await symlink(targetPath, linkPath);
1164
+ }
1165
+ async function replaceCommandShim(linkPath, targetPath) {
1166
+ if (await existingNonShim(linkPath)) {
1167
+ throw new Error(`${linkPath} already exists and is not a command shim`);
1168
+ }
1169
+ await writeFile4(linkPath, `@echo off\r
1170
+ ${COMMAND_SHIM_MARKER}\r
1171
+ node "${targetPath}" %*\r
1172
+ `);
1173
+ }
1174
+ async function replaceRuntimeWrapper(linkPath, content) {
1175
+ if (await existingNonRuntimeWrapper(linkPath)) {
1176
+ throw new Error(`${linkPath} already exists and is not a generated OMO runtime wrapper`);
1177
+ }
1178
+ await rm6(linkPath, { force: true });
1179
+ await writeFile4(linkPath, content);
1180
+ }
1181
+ async function existingNonRuntimeWrapper(path) {
1182
+ try {
1183
+ const stat6 = await lstat3(path);
1184
+ if (stat6.isSymbolicLink()) return false;
1185
+ if (!stat6.isFile()) return true;
1186
+ const content = await readFile7(path, "utf8");
1187
+ return !content.includes(RUNTIME_WRAPPER_MARKER);
1188
+ } catch (error) {
1189
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
1190
+ throw error;
1191
+ }
1192
+ }
1193
+ function posixRuntimeWrapper(cliPath, codexHome, binDir, nodeCliPath) {
1194
+ const ulwLoopBin = join9(binDir, "omo-ulw-loop");
1195
+ const nodeCli = escapePosixDoubleQuoted(nodeCliPath);
1196
+ return [
1197
+ "#!/bin/sh",
1198
+ `# ${RUNTIME_WRAPPER_MARKER}`,
1199
+ `export CODEX_HOME="\${CODEX_HOME:-${escapePosixDoubleQuoted(codexHome)}}"`,
1200
+ 'export OMO_SPARKSHELL_APP_SERVER_SOCKET="${OMO_SPARKSHELL_APP_SERVER_SOCKET:-$CODEX_HOME/app-server-control/app-server-control.sock}"',
1201
+ 'if [ "$1" = "ulw-loop" ] && [ -x "' + escapePosixDoubleQuoted(ulwLoopBin) + '" ]; then',
1202
+ " shift",
1203
+ ' exec "' + escapePosixDoubleQuoted(ulwLoopBin) + '" "$@"',
1204
+ "fi",
1205
+ `if [ "\${OMO_RUNTIME:-}" = "node" ] && [ -f "${nodeCli}" ]; then`,
1206
+ ` exec node "${nodeCli}" "$@"`,
1207
+ "fi",
1208
+ 'BUN_BINARY="${BUN_BINARY:-}"',
1209
+ 'if [ -z "$BUN_BINARY" ] && command -v bun >/dev/null 2>&1; then',
1210
+ " BUN_BINARY=bun",
1211
+ "fi",
1212
+ 'if [ -z "$BUN_BINARY" ]; then',
1213
+ ' for omo_bun_candidate in "$HOME/.bun/bin/bun" /opt/homebrew/bin/bun /usr/local/bin/bun; do',
1214
+ ' if [ -x "$omo_bun_candidate" ]; then',
1215
+ ' BUN_BINARY="$omo_bun_candidate"',
1216
+ " break",
1217
+ " fi",
1218
+ " done",
1219
+ "fi",
1220
+ 'if [ -z "$BUN_BINARY" ]; then',
1221
+ ` if [ -f "${nodeCli}" ] && command -v node >/dev/null 2>&1; then`,
1222
+ ` exec node "${nodeCli}" "$@"`,
1223
+ " fi",
1224
+ ` echo "omo: bun runtime not found (checked PATH, ~/.bun/bin, /opt/homebrew/bin, /usr/local/bin) and the node fallback CLI is missing at ${nodeCli}; install bun from https://bun.sh, or reinstall omo and force the fallback with OMO_RUNTIME=node" >&2`,
1225
+ " exit 127",
1226
+ "fi",
1227
+ `exec "$BUN_BINARY" "${escapePosixDoubleQuoted(cliPath)}" "$@"`,
1228
+ ""
1229
+ ].join("\n");
1230
+ }
1231
+ function windowsRuntimeWrapper(cliPath, codexHome, binDir, nodeCliPath) {
1232
+ const ulwLoopBin = join9(binDir, "omo-ulw-loop.cmd");
1233
+ return [
1234
+ "@echo off",
1235
+ `rem ${RUNTIME_WRAPPER_MARKER}`,
1236
+ `if not defined CODEX_HOME set "CODEX_HOME=${codexHome}"`,
1237
+ 'if not defined OMO_SPARKSHELL_APP_SERVER_SOCKET set "OMO_SPARKSHELL_APP_SERVER_SOCKET=%CODEX_HOME%\\app-server-control\\app-server-control.sock"',
1238
+ `if "%~1"=="ulw-loop" if exist "${ulwLoopBin}" (`,
1239
+ " shift /1",
1240
+ ` "${ulwLoopBin}" %*`,
1241
+ " exit /b %ERRORLEVEL%",
1242
+ ")",
1243
+ `if "%OMO_RUNTIME%"=="node" if exist "${nodeCliPath}" (`,
1244
+ ` node "${nodeCliPath}" %*`,
1245
+ " exit /b %ERRORLEVEL%",
1246
+ ")",
1247
+ 'if not defined BUN_BINARY where bun >nul 2>nul && set "BUN_BINARY=bun"',
1248
+ 'if not defined BUN_BINARY if exist "%USERPROFILE%\\.bun\\bin\\bun.exe" set "BUN_BINARY=%USERPROFILE%\\.bun\\bin\\bun.exe"',
1249
+ "if not defined BUN_BINARY (",
1250
+ ` if exist "${nodeCliPath}" (`,
1251
+ ` node "${nodeCliPath}" %*`,
1252
+ " exit /b %ERRORLEVEL%",
1253
+ " )",
1254
+ ` echo omo: bun runtime not found and the node fallback CLI is missing at ${nodeCliPath}; install bun from https://bun.sh or reinstall omo and force OMO_RUNTIME=node 1>&2`,
1255
+ " exit /b 127",
1256
+ ")",
1257
+ `"%BUN_BINARY%" "${cliPath}" %*`,
1258
+ ""
1259
+ ].join("\r\n");
1260
+ }
1261
+ function escapePosixDoubleQuoted(value) {
1262
+ return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("$", "\\$").replaceAll("`", "\\`");
1263
+ }
1264
+ async function existingNonShim(path) {
1265
+ try {
1266
+ const stat6 = await lstat3(path);
1267
+ if (!stat6.isFile()) return true;
1268
+ const content = await readFile7(path, "utf8");
1269
+ if (content.includes(COMMAND_SHIM_MARKER)) return false;
1270
+ throw new Error(`${path} already exists and is not a generated command shim`);
1271
+ } catch (error) {
1272
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
1273
+ throw error;
1274
+ }
1275
+ }
1276
+ async function existingNonSymlink(path) {
1277
+ try {
1278
+ const stat6 = await lstat3(path);
1279
+ if (!stat6.isSymbolicLink()) return true;
1280
+ await readlink2(path);
1281
+ return false;
1282
+ } catch (error) {
1283
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
1284
+ throw error;
1285
+ }
1286
+ }
1287
+ function isRecord3(value) {
1288
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1289
+ }
1290
+ function isPathInside(candidatePath, rootPath) {
1291
+ const pathFromRoot = relative(rootPath, candidatePath);
1292
+ return pathFromRoot === "" || !pathFromRoot.startsWith("..") && !pathFromRoot.startsWith(`..\\`) && !isDriveRelative(pathFromRoot);
1293
+ }
1294
+ function isDriveRelative(path) {
1295
+ return /^[a-zA-Z]:/.test(path);
1296
+ }
1297
+
1298
+ // ../../../scripts/install/config.mjs
1299
+ import { mkdir as mkdir6, readFile as readFile9 } from "node:fs/promises";
1300
+ import { dirname as dirname7 } from "node:path";
1301
+
1302
+ // ../../../scripts/install/atomic-write.mjs
1303
+ import { lstat as lstat4, readlink as readlink3, realpath, rename as rename3, unlink, writeFile as writeFile5 } from "node:fs/promises";
1304
+ import { basename as basename5, dirname as dirname6, isAbsolute, join as join10, resolve as resolve4 } from "node:path";
1305
+ var RENAME_RETRY_DELAYS_MS = [10, 25, 50];
1306
+ var RETRIABLE_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY"]);
1307
+ function isRetriableRenameError(error) {
1308
+ if (!(error instanceof Error)) return false;
1309
+ return RETRIABLE_RENAME_CODES.has(Reflect.get(error, "code"));
1310
+ }
1311
+ async function writeFileAtomic(targetPath, data) {
1312
+ const writeTarget = await resolveSymlinkTarget(targetPath);
1313
+ const temporaryPath = join10(
1314
+ dirname6(writeTarget),
1315
+ `.tmp-${basename5(writeTarget)}-${process.pid}-${Date.now()}`
1316
+ );
1317
+ await writeFile5(temporaryPath, data);
1318
+ try {
1319
+ await renameWithRetry(temporaryPath, writeTarget);
1320
+ } catch (renameError) {
1321
+ await unlink(temporaryPath).catch(() => {
1322
+ });
1323
+ throw renameError;
1324
+ }
1325
+ }
1326
+ async function resolveSymlinkTarget(targetPath) {
1327
+ let linkStats;
1328
+ try {
1329
+ linkStats = await lstat4(targetPath);
1330
+ } catch {
1331
+ return targetPath;
1332
+ }
1333
+ if (!linkStats.isSymbolicLink()) return targetPath;
1334
+ try {
1335
+ return await realpath(targetPath);
1336
+ } catch {
1337
+ const linkValue = await readlink3(targetPath);
1338
+ return isAbsolute(linkValue) ? linkValue : resolve4(dirname6(targetPath), linkValue);
1339
+ }
1340
+ }
1341
+ async function renameWithRetry(fromPath, toPath) {
1342
+ for (let attempt = 0; ; attempt += 1) {
1343
+ try {
1344
+ await rename3(fromPath, toPath);
1345
+ return;
1346
+ } catch (renameError) {
1347
+ if (!isRetriableRenameError(renameError) || attempt >= RENAME_RETRY_DELAYS_MS.length) {
1348
+ throw renameError;
1349
+ }
1350
+ await delay(RENAME_RETRY_DELAYS_MS[attempt]);
1351
+ }
1352
+ }
1353
+ }
1354
+ function delay(milliseconds) {
1355
+ return new Promise((resolve6) => setTimeout(resolve6, milliseconds));
1356
+ }
1357
+
1358
+ // ../../../scripts/install/toml-editor.mjs
1359
+ function findTomlSection(config, header) {
1360
+ const headerLine = `[${header}]`;
1361
+ const lines = config.match(/[^\n]*\n?|$/g) ?? [];
1362
+ let offset = 0;
1363
+ let start = -1;
1364
+ for (const line of lines) {
1365
+ if (line.length === 0) break;
1366
+ const trimmed = line.trim();
1367
+ if (start === -1) {
1368
+ if (trimmed === headerLine) start = offset;
1369
+ } else if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
1370
+ return { start, end: offset, text: config.slice(start, offset) };
1371
+ }
1372
+ offset += line.length;
1373
+ }
1374
+ if (start === -1) return null;
1375
+ return { start, end: config.length, text: config.slice(start) };
1376
+ }
1377
+ function replaceOrInsertSetting(config, section, key, value) {
1378
+ const linePattern = new RegExp(`^${escapeRegExp(key)}\\s*=.*$`, "m");
1379
+ const replacement = linePattern.test(section.text) ? section.text.replace(linePattern, `${key} = ${value}`) : insertSetting(section.text, key, value);
1380
+ return config.slice(0, section.start) + replacement + config.slice(section.end);
1381
+ }
1382
+ function removeSetting(config, section, key) {
1383
+ const linePattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=.*(?:\\n|$)`, "m");
1384
+ const replacement = section.text.replace(linePattern, "");
1385
+ return config.slice(0, section.start) + replacement + config.slice(section.end);
1386
+ }
1387
+ function replaceOrInsertRootSetting(config, key, value) {
1388
+ const sectionStart = findFirstTableStart(config);
1389
+ const root = config.slice(0, sectionStart);
1390
+ const suffix = config.slice(sectionStart);
1391
+ const linePattern = new RegExp(`^${escapeRegExp(key)}\\s*=.*$`, "m");
1392
+ const replacement = linePattern.test(root) ? root.replace(linePattern, `${key} = ${value}`) : `${root.trimEnd()}${root.trimEnd().length > 0 ? "\n" : ""}${key} = ${value}
1393
+ `;
1394
+ if (suffix.length === 0) return replacement;
1395
+ return `${replacement.trimEnd()}
1396
+
1397
+ ${suffix.trimStart()}`;
1398
+ }
1399
+ function appendBlock(config, block) {
1400
+ const prefix = config.trimEnd();
1401
+ return `${prefix}${prefix.length > 0 ? "\n\n" : ""}${block.trimEnd()}
1402
+ `;
1403
+ }
1404
+ function findFirstTableStart(config) {
1405
+ const match = config.match(/^[[].*$/m);
1406
+ return match?.index ?? config.length;
1407
+ }
1408
+ function insertSetting(sectionText, key, value) {
1409
+ const lines = sectionText.split("\n");
1410
+ lines.splice(1, 0, `${key} = ${value}`);
1411
+ return lines.join("\n");
1412
+ }
1413
+ function escapeRegExp(value) {
1414
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1415
+ }
1416
+
1417
+ // ../../../scripts/install/multi-agent-v2-config.mjs
1418
+ var CODEX_MULTI_AGENT_V2_HEADER = "features.multi_agent_v2";
1419
+ var CODEX_MULTI_AGENT_V2_MAX_CONCURRENT_THREADS_PER_SESSION = 1e4;
1420
+ function ensureCodexMultiAgentV2Config(config) {
1421
+ const normalizedConfig = removeLegacyAgentsMaxThreadsSetting(removeFeatureFlagSetting(config, "multi_agent_v2"));
1422
+ const section = findTomlSection(normalizedConfig, CODEX_MULTI_AGENT_V2_HEADER);
1423
+ const maxThreadsValue = CODEX_MULTI_AGENT_V2_MAX_CONCURRENT_THREADS_PER_SESSION.toString();
1424
+ if (!section) {
1425
+ return appendBlock(
1426
+ normalizedConfig,
1427
+ `[${CODEX_MULTI_AGENT_V2_HEADER}]
1428
+ max_concurrent_threads_per_session = ${maxThreadsValue}
1429
+ hide_spawn_agent_metadata = false
1430
+ `
1431
+ );
1432
+ }
1433
+ const withMaxThreads = replaceOrInsertSetting(
1434
+ normalizedConfig,
1435
+ section,
1436
+ "max_concurrent_threads_per_session",
1437
+ maxThreadsValue
1438
+ );
1439
+ const updatedSection = findTomlSection(withMaxThreads, CODEX_MULTI_AGENT_V2_HEADER);
1440
+ return replaceOrInsertSetting(withMaxThreads, updatedSection, "hide_spawn_agent_metadata", "false");
1441
+ }
1442
+ function removeFeatureFlagSetting(config, featureName) {
1443
+ const section = findTomlSection(config, "features");
1444
+ if (!section) return config;
1445
+ return removeSetting(config, section, featureName);
1446
+ }
1447
+ function removeLegacyAgentsMaxThreadsSetting(config) {
1448
+ const section = findTomlSection(config, "agents");
1449
+ if (!section) return config;
1450
+ return removeSetting(config, section, "max_threads");
1451
+ }
1452
+
1453
+ // ../../../scripts/install/model-catalog.mjs
1454
+ import { readFile as readFile8 } from "node:fs/promises";
1455
+ import { join as join11 } from "node:path";
1456
+ var FALLBACK_CODEX_MODEL_CATALOG = {
1457
+ current: {
1458
+ model: "gpt-5.5",
1459
+ modelContextWindow: 4e5,
1460
+ modelReasoningEffort: "high",
1461
+ planModeReasoningEffort: "xhigh"
1462
+ },
1463
+ managedProfiles: [
1464
+ {
1465
+ model: "gpt-5.5",
1466
+ modelContextWindow: 1e6,
1467
+ modelReasoningEffort: "high",
1468
+ planModeReasoningEffort: "xhigh"
1469
+ },
1470
+ { model: "gpt-5.5", modelContextWindow: 272e3 }
1471
+ ]
1472
+ };
1473
+ async function readCodexModelCatalog(codexPackageRoot) {
1474
+ try {
1475
+ const parsed = JSON.parse(await readFile8(join11(codexPackageRoot, "plugin", "model-catalog.json"), "utf8"));
1476
+ return parseCodexModelCatalog(parsed) ?? FALLBACK_CODEX_MODEL_CATALOG;
1477
+ } catch (error) {
1478
+ if (!(error instanceof Error)) throw error;
1479
+ return FALLBACK_CODEX_MODEL_CATALOG;
1480
+ }
1481
+ }
1482
+ function parseCodexModelCatalog(value) {
1483
+ if (!isRecord4(value) || !isRecord4(value.current) || !Array.isArray(value.managedProfiles)) return null;
1484
+ const { current } = value;
1485
+ if (typeof current.model !== "string" || typeof current.model_context_window !== "number" || typeof current.model_reasoning_effort !== "string" || typeof current.plan_mode_reasoning_effort !== "string") {
1486
+ return null;
1487
+ }
1488
+ const managedProfiles = [];
1489
+ for (const profile of value.managedProfiles) {
1490
+ if (!isRecord4(profile) || !isRecord4(profile.match)) return null;
1491
+ managedProfiles.push(parseProfileMatch(profile.match));
1492
+ }
1493
+ return {
1494
+ current: {
1495
+ model: current.model,
1496
+ modelContextWindow: current.model_context_window,
1497
+ modelReasoningEffort: current.model_reasoning_effort,
1498
+ planModeReasoningEffort: current.plan_mode_reasoning_effort
1499
+ },
1500
+ managedProfiles
1501
+ };
1502
+ }
1503
+ function parseProfileMatch(match) {
1504
+ const profile = {};
1505
+ if (typeof match.model === "string") profile.model = match.model;
1506
+ if (typeof match.model_context_window === "number") profile.modelContextWindow = match.model_context_window;
1507
+ if (typeof match.model_reasoning_effort === "string") profile.modelReasoningEffort = match.model_reasoning_effort;
1508
+ if (typeof match.plan_mode_reasoning_effort === "string") profile.planModeReasoningEffort = match.plan_mode_reasoning_effort;
1509
+ return profile;
1510
+ }
1511
+ function isRecord4(value) {
1512
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1513
+ }
1514
+
1515
+ // ../../../scripts/install/reasoning-config.mjs
1516
+ var MANAGED_KEYS = ["model", "model_context_window", "model_reasoning_effort", "plan_mode_reasoning_effort"];
1517
+ function ensureCodexReasoningConfig(config, catalog) {
1518
+ const current = readRootReasoningSettings(config);
1519
+ if (Object.keys(current).length > 0 && !matchesProfile(current, catalog.current) && !catalog.managedProfiles.some((profile) => matchesProfile(current, profile))) {
1520
+ return config;
1521
+ }
1522
+ let next = replaceOrInsertRootSetting(config, "model", JSON.stringify(catalog.current.model));
1523
+ next = replaceOrInsertRootSetting(next, "model_context_window", catalog.current.modelContextWindow.toString());
1524
+ next = replaceOrInsertRootSetting(
1525
+ next,
1526
+ "model_reasoning_effort",
1527
+ JSON.stringify(catalog.current.modelReasoningEffort)
1528
+ );
1529
+ next = replaceOrInsertRootSetting(next, "plan_mode_reasoning_effort", JSON.stringify(catalog.current.planModeReasoningEffort));
1530
+ return next;
1531
+ }
1532
+ function readRootReasoningSettings(config) {
1533
+ const settings = {};
1534
+ for (const line of config.split(/\n/)) {
1535
+ if (isSectionHeader2(line)) break;
1536
+ for (const key of MANAGED_KEYS) {
1537
+ if (!isRootSetting(line, key)) continue;
1538
+ const value = parseTomlScalar(line.slice(line.indexOf("=") + 1));
1539
+ if (key === "model" && typeof value === "string") settings.model = value;
1540
+ if (key === "model_context_window" && typeof value === "number") settings.modelContextWindow = value;
1541
+ if (key === "model_reasoning_effort" && typeof value === "string") settings.modelReasoningEffort = value;
1542
+ if (key === "plan_mode_reasoning_effort" && typeof value === "string") settings.planModeReasoningEffort = value;
1543
+ }
1544
+ }
1545
+ return settings;
1546
+ }
1547
+ function matchesProfile(current, profile) {
1548
+ for (const [key, value] of Object.entries(profile)) {
1549
+ if (current[key] !== value) return false;
1550
+ }
1551
+ return true;
1552
+ }
1553
+ function parseTomlScalar(value) {
1554
+ const trimmed = value.trim();
1555
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
1556
+ try {
1557
+ return JSON.parse(trimmed);
1558
+ } catch (error) {
1559
+ if (error instanceof SyntaxError) return void 0;
1560
+ throw error;
1561
+ }
1562
+ }
1563
+ const numeric = Number(trimmed);
1564
+ return Number.isFinite(numeric) ? numeric : void 0;
1565
+ }
1566
+ function isSectionHeader2(line) {
1567
+ const trimmed = line.trim();
1568
+ return trimmed.startsWith("[") && trimmed.endsWith("]");
1569
+ }
1570
+ function isRootSetting(line, key) {
1571
+ const trimmed = line.trimStart();
1572
+ if (trimmed.startsWith("#") || trimmed.startsWith("[")) return false;
1573
+ const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=/);
1574
+ return match?.[1] === key;
1575
+ }
1576
+
1577
+ // ../../../scripts/install/permissions.mjs
1578
+ var AUTONOMOUS_FEATURES = ["multi_agent", "child_agents_md", "unified_exec", "goals"];
1579
+ function ensureAutonomousPermissions(config) {
1580
+ let next = replaceOrInsertRootSetting(config, "approval_policy", JSON.stringify("never"));
1581
+ next = replaceOrInsertRootSetting(next, "sandbox_mode", JSON.stringify("danger-full-access"));
1582
+ next = replaceOrInsertRootSetting(next, "network_access", JSON.stringify("enabled"));
1583
+ for (const featureName of AUTONOMOUS_FEATURES) {
1584
+ next = ensureFeatureEnabled(next, featureName);
1585
+ }
1586
+ next = removeWindowsSandboxSetting(next);
1587
+ next = ensureNoticeEnabled(next, "hide_full_access_warning");
1588
+ return ensureNoticeEnabled(next, "hide_world_writable_warning");
1589
+ }
1590
+ function removeWindowsSandboxSetting(config) {
1591
+ const section = findTomlSection(config, "windows");
1592
+ if (!section) return config;
1593
+ return removeSetting(config, section, "sandbox");
1594
+ }
1595
+ function ensureNoticeEnabled(config, key) {
1596
+ const section = findTomlSection(config, "notice");
1597
+ if (!section) return appendNoticeBlock(config, key);
1598
+ return replaceOrInsertSetting(config, section, key, "true");
1599
+ }
1600
+ function ensureFeatureEnabled(config, key) {
1601
+ const section = findTomlSection(config, "features");
1602
+ if (!section) return appendBlock(config, `[features]
1603
+ ${key} = true
1604
+ `);
1605
+ return replaceOrInsertSetting(config, section, key, "true");
1606
+ }
1607
+ function appendNoticeBlock(config, key) {
1608
+ return appendBlock(config, `[notice]
1609
+ ${key} = true
1610
+ `);
1611
+ }
1612
+
1613
+ // ../../../scripts/install/config.mjs
1614
+ var LEGACY_CODEX_PLUGIN_MARKETPLACE = ["code", "yeongyu", "codex", "plugins"].join("-");
1615
+ var SISYPHUS_LEGACY_MARKETPLACES = ["lazycodex", LEGACY_CODEX_PLUGIN_MARKETPLACE];
1616
+ var MANAGED_CODEX_AGENT_NAMES = [
1617
+ "codex-ultrawork-reviewer",
1618
+ "explorer",
1619
+ "librarian",
1620
+ "metis",
1621
+ "momus",
1622
+ "plan"
1623
+ ];
1624
+ async function updateCodexConfig({
1625
+ configPath,
1626
+ repoRoot,
1627
+ marketplaceName,
1628
+ marketplaceSource = defaultMarketplaceSource(repoRoot),
1629
+ preserveMarketplaceSource = false,
1630
+ pluginNames,
1631
+ platform = process.platform,
1632
+ trustedHookStates = [],
1633
+ agentConfigs = [],
1634
+ autonomousPermissions = false,
1635
+ gitBashEnabled = false
1636
+ }) {
1637
+ await mkdir6(dirname7(configPath), { recursive: true });
1638
+ let config = "";
1639
+ if (await exists(configPath)) config = await readFile9(configPath, "utf8");
1640
+ for (const legacyMarketplaceName of legacyMarketplaceNames(marketplaceName)) {
1641
+ config = removeMarketplaceBlock(config, legacyMarketplaceName);
1642
+ config = removeStaleMarketplacePluginBlocks(config, legacyMarketplaceName, /* @__PURE__ */ new Set());
1643
+ config = removeStaleMarketplaceHookStateBlocks(config, legacyMarketplaceName, /* @__PURE__ */ new Set());
1644
+ }
1645
+ config = removeStaleMarketplacePluginBlocks(config, marketplaceName, new Set(pluginNames));
1646
+ config = removeStaleMarketplaceHookStateBlocks(config, marketplaceName, new Set(pluginNames));
1647
+ config = removeStaleManagedAgentBlocks(config, new Set(agentConfigs.map((agentConfig) => agentConfig.name)));
1648
+ config = ensureFeatureEnabled2(config, "plugins");
1649
+ config = ensureFeatureEnabled2(config, "plugin_hooks");
1650
+ config = ensureFeatureEnabled2(config, "multi_agent");
1651
+ config = ensureFeatureEnabled2(config, "child_agents_md");
1652
+ config = ensureCodexReasoningConfig(config, await readCodexModelCatalog(repoRoot));
1653
+ config = ensureCodexMultiAgentV2Config(config);
1654
+ if (autonomousPermissions === true) config = ensureAutonomousPermissions(config);
1655
+ if (preserveMarketplaceSource !== true) {
1656
+ config = ensureMarketplaceBlock(config, marketplaceName, marketplaceSource);
1657
+ }
1658
+ for (const pluginName of pluginNames) {
1659
+ config = ensurePluginEnabled(config, `${pluginName}@${marketplaceName}`);
1660
+ }
1661
+ config = ensureOmoBuiltinMcpPolicies(config, { marketplaceName, pluginNames, platform, gitBashEnabled });
1662
+ for (const state of trustedHookStates) {
1663
+ config = ensureHookTrusted(config, state.key, state.trustedHash);
1664
+ }
1665
+ for (const agentConfig of agentConfigs) {
1666
+ config = ensureAgentConfig(config, agentConfig);
1667
+ }
1668
+ await writeFileAtomic(configPath, config.trimEnd() + "\n");
1669
+ }
1670
+ function legacyMarketplaceNames(marketplaceName) {
1671
+ return marketplaceName === "sisyphuslabs" ? SISYPHUS_LEGACY_MARKETPLACES : [];
1672
+ }
1673
+ function removeMarketplaceBlock(config, marketplaceName) {
1674
+ return removeTomlSections(config, (header) => header === `marketplaces.${marketplaceName}`);
1675
+ }
1676
+ function defaultMarketplaceSource(repoRoot) {
1677
+ return {
1678
+ sourceType: "local",
1679
+ source: repoRoot
1680
+ };
1681
+ }
1682
+ function removeStaleMarketplacePluginBlocks(config, marketplaceName, keepPluginNames) {
1683
+ return removeTomlSections(config, (header) => {
1684
+ const pluginKey = parsePluginHeaderKey(header);
1685
+ if (pluginKey === null) return false;
1686
+ const suffix = `@${marketplaceName}`;
1687
+ if (!pluginKey.endsWith(suffix)) return false;
1688
+ return !keepPluginNames.has(pluginKey.slice(0, -suffix.length));
1689
+ });
1690
+ }
1691
+ function removeStaleMarketplaceHookStateBlocks(config, marketplaceName, keepPluginNames) {
1692
+ return removeTomlSections(config, (header) => {
1693
+ const prefix = "hooks.state.";
1694
+ if (!header.startsWith(prefix)) return false;
1695
+ const hookKey = parseJsonString(header.slice(prefix.length));
1696
+ if (hookKey === null) return false;
1697
+ const separator = hookKey.indexOf(":");
1698
+ if (separator === -1) return false;
1699
+ const pluginKey = hookKey.slice(0, separator);
1700
+ const suffix = `@${marketplaceName}`;
1701
+ if (!pluginKey.endsWith(suffix)) return false;
1702
+ return !keepPluginNames.has(pluginKey.slice(0, -suffix.length));
1703
+ });
1704
+ }
1705
+ function removeStaleManagedAgentBlocks(config, keepAgentNames) {
1706
+ const managedAgentNames = new Set(MANAGED_CODEX_AGENT_NAMES);
1707
+ return splitTomlSections(config).filter((section) => {
1708
+ if (section.header === null) return true;
1709
+ const agentName = parseAgentHeaderName(section.header);
1710
+ if (agentName === null || !managedAgentNames.has(agentName) || keepAgentNames.has(agentName)) return true;
1711
+ return !section.text.includes(`config_file = ${JSON.stringify(`./agents/${agentName}.toml`)}`);
1712
+ }).map((section) => section.text).join("").replace(/\n{3,}/g, "\n\n");
1713
+ }
1714
+ function ensureFeatureEnabled2(config, featureName) {
1715
+ const section = findTomlSection(config, "features");
1716
+ if (!section) return appendBlock(config, `[features]
1717
+ ${featureName} = true
1718
+ `);
1719
+ return replaceOrInsertSetting(config, section, featureName, "true");
1720
+ }
1721
+ function ensureMarketplaceBlock(config, marketplaceName, source) {
1722
+ const header = `marketplaces.${marketplaceName}`;
1723
+ const block = [
1724
+ `[${header}]`,
1725
+ `last_updated = "${(/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z")}"`,
1726
+ `source_type = ${JSON.stringify(source.sourceType)}`,
1727
+ `source = ${JSON.stringify(source.source)}`,
1728
+ source.ref === void 0 ? null : `ref = ${JSON.stringify(source.ref)}`,
1729
+ ""
1730
+ ].filter((line) => line !== null).join("\n");
1731
+ const section = findTomlSection(config, header);
1732
+ if (section) return config.slice(0, section.start) + block + config.slice(section.end);
1733
+ return appendBlock(config, block);
1734
+ }
1735
+ function ensurePluginEnabled(config, pluginKey) {
1736
+ const header = `plugins.${JSON.stringify(pluginKey)}`;
1737
+ const section = findTomlSection(config, header);
1738
+ if (!section) return appendBlock(config, `[${header}]
1739
+ enabled = true
1740
+ `);
1741
+ return replaceOrInsertSetting(config, section, "enabled", "true");
1742
+ }
1743
+ function ensurePluginMcpEnabled(config, pluginKey, serverName, enabled) {
1744
+ const header = `plugins.${JSON.stringify(pluginKey)}.mcp_servers.${serverName}`;
1745
+ const section = findTomlSection(config, header);
1746
+ const enabledValue = enabled ? "true" : "false";
1747
+ if (!section) return appendBlock(config, `[${header}]
1748
+ enabled = ${enabledValue}
1749
+ `);
1750
+ return replaceOrInsertSetting(config, section, "enabled", enabledValue);
1751
+ }
1752
+ function ensureOmoBuiltinMcpPolicies(config, { marketplaceName, pluginNames, platform, gitBashEnabled }) {
1753
+ if (marketplaceName !== "sisyphuslabs" || !pluginNames.includes("omo")) return config;
1754
+ let nextConfig = ensurePluginMcpEnabled(config, "omo@sisyphuslabs", "context7", true);
1755
+ nextConfig = ensurePluginMcpEnabled(nextConfig, "omo@sisyphuslabs", "git_bash", platform === "win32" && gitBashEnabled === true);
1756
+ return nextConfig;
1757
+ }
1758
+ function ensureHookTrusted(config, key, trustedHash) {
1759
+ const header = `hooks.state.${JSON.stringify(key)}`;
1760
+ const section = findTomlSection(config, header);
1761
+ if (!section) return appendBlock(config, `[${header}]
1762
+ trusted_hash = ${JSON.stringify(trustedHash)}
1763
+ `);
1764
+ return replaceOrInsertSetting(config, section, "trusted_hash", JSON.stringify(trustedHash));
1765
+ }
1766
+ function ensureAgentConfig(config, agentConfig) {
1767
+ const header = `agents.${tomlKeySegment(agentConfig.name)}`;
1768
+ const section = findTomlSection(config, header);
1769
+ const configFile = JSON.stringify(agentConfig.configFile);
1770
+ if (!section) return appendBlock(config, `[${header}]
1771
+ config_file = ${configFile}
1772
+ `);
1773
+ return replaceOrInsertSetting(config, section, "config_file", configFile);
1774
+ }
1775
+ function tomlKeySegment(value) {
1776
+ return /^[A-Za-z0-9_-]+$/.test(value) ? value : JSON.stringify(value);
1777
+ }
1778
+ function removeTomlSections(config, shouldRemove) {
1779
+ return splitTomlSections(config).filter((section) => section.header === null || !shouldRemove(section.header)).map((section) => section.text).join("").replace(/\n{3,}/g, "\n\n");
1780
+ }
1781
+ function splitTomlSections(config) {
1782
+ const lines = config.match(/[^\n]*\n?|$/g) ?? [];
1783
+ const sections = [];
1784
+ let current = { header: null, text: "" };
1785
+ for (const line of lines) {
1786
+ if (line.length === 0) break;
1787
+ const header = parseTomlHeader2(line);
1788
+ if (header !== null) {
1789
+ if (current.text.length > 0) sections.push(current);
1790
+ current = { header, text: line };
1791
+ } else {
1792
+ current.text += line;
1793
+ }
1794
+ }
1795
+ if (current.text.length > 0) sections.push(current);
1796
+ return sections;
1797
+ }
1798
+ function parseTomlHeader2(line) {
1799
+ const trimmed = line.trim();
1800
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return null;
1801
+ if (trimmed.startsWith("[[")) return null;
1802
+ return trimmed.slice(1, -1);
1803
+ }
1804
+ function parsePluginHeaderKey(header) {
1805
+ const prefix = "plugins.";
1806
+ if (!header.startsWith(prefix)) return null;
1807
+ return parseLeadingJsonString2(header.slice(prefix.length));
1808
+ }
1809
+ function parseAgentHeaderName(header) {
1810
+ const prefix = "agents.";
1811
+ if (!header.startsWith(prefix)) return null;
1812
+ const key = header.slice(prefix.length);
1813
+ return key.startsWith('"') ? parseLeadingJsonString2(key) : key;
1814
+ }
1815
+ function parseLeadingJsonString2(value) {
1816
+ if (!value.startsWith('"')) return parseJsonString(value);
1817
+ let escaped = false;
1818
+ for (let index = 1; index < value.length; index += 1) {
1819
+ const char = value[index];
1820
+ if (escaped) {
1821
+ escaped = false;
1822
+ continue;
1823
+ }
1824
+ if (char === "\\") {
1825
+ escaped = true;
1826
+ continue;
1827
+ }
1828
+ if (char === '"') return parseJsonString(value.slice(0, index + 1));
1829
+ }
1830
+ return null;
1831
+ }
1832
+ function parseJsonString(value) {
1833
+ try {
1834
+ const parsed = JSON.parse(value);
1835
+ return typeof parsed === "string" ? parsed : null;
1836
+ } catch (error) {
1837
+ if (error instanceof Error) return null;
1838
+ return null;
1839
+ }
1840
+ }
1841
+
1842
+ // ../../../scripts/install/git-bash-mcp-env.mjs
1843
+ import { readFile as readFile10, writeFile as writeFile6 } from "node:fs/promises";
1844
+ import { join as join12 } from "node:path";
1845
+ var GIT_BASH_ENV_KEY = "OMO_CODEX_GIT_BASH_PATH";
1846
+ async function stampGitBashMcpEnv({ pluginRoot, env = process.env, platform = process.platform }) {
1847
+ if (platform !== "win32") return false;
1848
+ const override = typeof env[GIT_BASH_ENV_KEY] === "string" ? env[GIT_BASH_ENV_KEY].trim() : "";
1849
+ if (override === "") return false;
1850
+ const manifestPath = join12(pluginRoot, ".mcp.json");
1851
+ if (!await exists(manifestPath)) return false;
1852
+ const parsed = JSON.parse(await readFile10(manifestPath, "utf8"));
1853
+ if (!isRecord2(parsed) || !isRecord2(parsed.mcpServers) || !isRecord2(parsed.mcpServers.git_bash)) return false;
1854
+ const server = parsed.mcpServers.git_bash;
1855
+ const serverEnv = isRecord2(server.env) ? server.env : {};
1856
+ if (serverEnv[GIT_BASH_ENV_KEY] === override) return false;
1857
+ server.env = { ...serverEnv, [GIT_BASH_ENV_KEY]: override };
1858
+ await writeFile6(manifestPath, `${JSON.stringify(parsed, null, " ")}
1859
+ `);
1860
+ return true;
1861
+ }
1862
+
1863
+ // ../../../scripts/install/git-bash.mjs
1864
+ import { execFileSync } from "node:child_process";
1865
+ import { existsSync as existsSync2 } from "node:fs";
1866
+ var GIT_BASH_ENV_KEY2 = "OMO_CODEX_GIT_BASH_PATH";
1867
+ var SKIP_GIT_BASH_AUTO_INSTALL_ENV_KEY = "OMO_CODEX_SKIP_GIT_BASH_AUTO_INSTALL";
1868
+ var PROGRAM_FILES_GIT_BASH = "C:\\Program Files\\Git\\bin\\bash.exe";
1869
+ var PROGRAM_FILES_X86_GIT_BASH = "C:\\Program Files (x86)\\Git\\bin\\bash.exe";
1870
+ var WINGET_INSTALL_ARGS = ["install", "--id", "Git.Git", "-e", "--source", "winget"];
1871
+ function resolveGitBash({ platform, env, exists: exists2, where }) {
1872
+ if (platform !== "win32") return { found: true, path: null, source: "not-required", checkedPaths: [] };
1873
+ const checkedPaths = [];
1874
+ const envPath = nonEmptyEnvValue2(env, GIT_BASH_ENV_KEY2);
1875
+ if (envPath !== void 0) {
1876
+ checkedPaths.push(envPath);
1877
+ if (isBashExePath(envPath) && exists2(envPath)) return { found: true, path: envPath, source: "env", checkedPaths };
1878
+ return missingGitBash(checkedPaths);
1879
+ }
1880
+ for (const candidate of [
1881
+ { path: PROGRAM_FILES_GIT_BASH, source: "program-files" },
1882
+ { path: PROGRAM_FILES_X86_GIT_BASH, source: "program-files-x86" }
1883
+ ]) {
1884
+ checkedPaths.push(candidate.path);
1885
+ if (exists2(candidate.path)) return { found: true, path: candidate.path, source: candidate.source, checkedPaths };
1886
+ }
1887
+ for (const pathCandidate of where("bash")) {
1888
+ const candidate = pathCandidate.trim();
1889
+ if (candidate.length === 0) continue;
1890
+ checkedPaths.push(candidate);
1891
+ if (isKnownNonGitBashLauncher(candidate)) continue;
1892
+ if (isBashExePath(candidate) && exists2(candidate)) return { found: true, path: candidate, source: "path", checkedPaths };
1893
+ }
1894
+ return missingGitBash(checkedPaths);
1895
+ }
1896
+ function resolveGitBashForCurrentProcess(options = {}) {
1897
+ return resolveGitBash({
1898
+ platform: options.platform ?? process.platform,
1899
+ env: options.env ?? process.env,
1900
+ exists: existsSync2,
1901
+ where: whereCommand
1902
+ });
1903
+ }
1904
+ async function prepareGitBashForInstall(options) {
1905
+ const resolveGitBashWithDefaults = options.resolveGitBash ?? (() => resolveGitBashForCurrentProcess({ platform: options.platform, env: options.env }));
1906
+ const initialResolution = resolveGitBashWithDefaults();
1907
+ if (options.platform !== "win32" || initialResolution.found) return initialResolution;
1908
+ if (options.env[SKIP_GIT_BASH_AUTO_INSTALL_ENV_KEY] === "1") return initialResolution;
1909
+ try {
1910
+ await options.runCommand("winget", WINGET_INSTALL_ARGS, { cwd: options.cwd });
1911
+ } catch (error) {
1912
+ if (!(error instanceof Error)) throw error;
1913
+ return initialResolution;
1914
+ }
1915
+ return resolveGitBashWithDefaults();
1916
+ }
1917
+ function missingGitBash(checkedPaths) {
1918
+ return {
1919
+ found: false,
1920
+ checkedPaths,
1921
+ installHint: [
1922
+ "Git Bash is required for native Windows Codex profile installs.",
1923
+ "Install it with: winget install --id Git.Git -e --source winget",
1924
+ `For a custom install, set ${GIT_BASH_ENV_KEY2}=C:\\path\\to\\bash.exe`,
1925
+ "Then rerun `npx lazycodex-ai install`."
1926
+ ].join("\n")
1927
+ };
1928
+ }
1929
+ function nonEmptyEnvValue2(env, key) {
1930
+ const value = env[key];
1931
+ if (typeof value !== "string") return void 0;
1932
+ const trimmed = value.trim();
1933
+ return trimmed.length === 0 ? void 0 : trimmed;
1934
+ }
1935
+ function isBashExePath(path) {
1936
+ return path.toLowerCase().endsWith("bash.exe");
1937
+ }
1938
+ var NON_GIT_BASH_LAUNCHER_DIR_SEGMENTS = ["\\windows\\system32\\", "\\microsoft\\windowsapps\\"];
1939
+ function isKnownNonGitBashLauncher(path) {
1940
+ const normalized = path.replaceAll("/", "\\").toLowerCase();
1941
+ return NON_GIT_BASH_LAUNCHER_DIR_SEGMENTS.some((segment) => normalized.includes(segment));
1942
+ }
1943
+ function whereCommand(command) {
1944
+ try {
1945
+ return execFileSync("where", [command], { encoding: "utf8" }).split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
1946
+ } catch (error) {
1947
+ if (error instanceof Error) return [];
1948
+ throw error;
1949
+ }
1950
+ }
1951
+
1952
+ // ../../../scripts/install/hook-trust.mjs
1953
+ import { createHash as createHash2 } from "node:crypto";
1954
+ import { readFile as readFile11 } from "node:fs/promises";
1955
+ import { join as join13 } from "node:path";
1956
+ var EVENT_LABELS = /* @__PURE__ */ new Map([
1957
+ ["PreToolUse", "pre_tool_use"],
1958
+ ["PermissionRequest", "permission_request"],
1959
+ ["PostToolUse", "post_tool_use"],
1960
+ ["PreCompact", "pre_compact"],
1961
+ ["PostCompact", "post_compact"],
1962
+ ["SessionStart", "session_start"],
1963
+ ["UserPromptSubmit", "user_prompt_submit"],
1964
+ ["SubagentStart", "subagent_start"],
1965
+ ["SubagentStop", "subagent_stop"],
1966
+ ["Stop", "stop"]
1967
+ ]);
1968
+ async function trustedHookStatesForPlugin({ marketplaceName, pluginName, pluginRoot }) {
1969
+ const manifestPath = join13(pluginRoot, ".codex-plugin", "plugin.json");
1970
+ if (!await exists(manifestPath)) return [];
1971
+ const manifest = JSON.parse(await readFile11(manifestPath, "utf8"));
1972
+ if (!isRecord2(manifest) || typeof manifest.hooks !== "string") return [];
1973
+ const hooksPath = join13(pluginRoot, manifest.hooks);
1974
+ if (!await exists(hooksPath)) return [];
1975
+ const parsed = JSON.parse(await readFile11(hooksPath, "utf8"));
1976
+ if (!isRecord2(parsed) || !isRecord2(parsed.hooks)) return [];
1977
+ const keySource = `${pluginName}@${marketplaceName}:${stripDotSlash(manifest.hooks)}`;
1978
+ const states = [];
1979
+ for (const [eventName, groups] of Object.entries(parsed.hooks)) {
1980
+ if (!Array.isArray(groups)) continue;
1981
+ const eventLabel = EVENT_LABELS.get(eventName);
1982
+ if (eventLabel === void 0) continue;
1983
+ for (const [groupIndex, group] of groups.entries()) {
1984
+ if (!isRecord2(group) || !Array.isArray(group.hooks)) continue;
1985
+ for (const [handlerIndex, handler] of group.hooks.entries()) {
1986
+ if (!isRecord2(handler) || handler.type !== "command") continue;
1987
+ if (handler.async === true) continue;
1988
+ if (typeof handler.command !== "string" || handler.command.trim() === "") continue;
1989
+ const key = `${keySource}:${eventLabel}:${groupIndex}:${handlerIndex}`;
1990
+ states.push({
1991
+ key,
1992
+ trustedHash: commandHookHash(eventLabel, group.matcher, handler)
1993
+ });
1994
+ }
1995
+ }
1996
+ }
1997
+ return states;
1998
+ }
1999
+ function commandHookHash(eventName, matcher, handler) {
2000
+ const command = handler.command;
2001
+ const timeout = Math.max(Number(handler.timeout ?? 600), 1);
2002
+ const normalizedHandler = {
2003
+ type: "command",
2004
+ command,
2005
+ timeout,
2006
+ async: false
2007
+ };
2008
+ if (typeof handler.statusMessage === "string") normalizedHandler.statusMessage = handler.statusMessage;
2009
+ const identity = {
2010
+ event_name: eventName,
2011
+ hooks: [normalizedHandler]
2012
+ };
2013
+ if (typeof matcher === "string") identity.matcher = matcher;
2014
+ return `sha256:${createHash2("sha256").update(JSON.stringify(canonicalJson(identity))).digest("hex")}`;
2015
+ }
2016
+ function canonicalJson(value) {
2017
+ if (Array.isArray(value)) return value.map(canonicalJson);
2018
+ if (!isRecord2(value)) return value;
2019
+ const result = {};
2020
+ for (const key of Object.keys(value).sort()) {
2021
+ result[key] = canonicalJson(value[key]);
2022
+ }
2023
+ return result;
2024
+ }
2025
+ function stripDotSlash(value) {
2026
+ return value.startsWith("./") ? value.slice(2) : value;
2027
+ }
2028
+
2029
+ // src/setup.ts
2030
+ var SETUP_MARKETPLACE_NAME = "sisyphuslabs";
2031
+ var SETUP_PLUGIN_NAME = "omo";
2032
+ var GIT_BASH_INSTALL_HINT = "winget install --id Git.Git -e --source winget";
2033
+ async function runWorkerSetup(options) {
2034
+ const degraded = [];
2035
+ const gitBashEnabled = await resolveGitBashStep(options, degraded);
2036
+ const agents = await linkBundledAgentsStep(options);
2037
+ degraded.push(...agents.degraded);
2038
+ await updateConfigStep(options, { agentConfigs: agents.agentConfigs, gitBashEnabled }, degraded);
2039
+ await stampGitBashEnvStep(options, degraded);
2040
+ await linkComponentBinsStep(options, degraded);
2041
+ return { degraded };
2042
+ }
2043
+ async function resolveGitBashStep(options, degraded) {
2044
+ if (options.platform !== "win32") return false;
2045
+ try {
2046
+ const resolution = await prepareGitBashForInstall({
2047
+ cwd: options.pluginRoot,
2048
+ env: options.env,
2049
+ platform: options.platform,
2050
+ runCommand: options.runCommand ?? defaultRunCommand,
2051
+ ...options.resolveGitBash === void 0 ? {} : { resolveGitBash: options.resolveGitBash }
2052
+ });
2053
+ if (resolution.found) return true;
2054
+ degraded.push({
2055
+ component: "git-bash",
2056
+ hint: GIT_BASH_INSTALL_HINT,
2057
+ reason: "Git Bash was not found on this Windows machine; the omo git_bash MCP server stays disabled"
2058
+ });
2059
+ } catch (error) {
2060
+ degraded.push({
2061
+ component: "git-bash",
2062
+ hint: GIT_BASH_INSTALL_HINT,
2063
+ reason: `Git Bash preflight failed: ${errorMessage(error)}`
2064
+ });
2065
+ }
2066
+ return false;
2067
+ }
2068
+ async function linkBundledAgentsStep(options) {
2069
+ const agentsTarget = join14(options.codexHome, "agents");
2070
+ try {
2071
+ const stageRoot = join14(options.pluginData, "bootstrap", "agents-stage");
2072
+ await stageBundledAgents(options.pluginRoot, stageRoot);
2073
+ const preservedReasoning = await capturePreservedAgentReasoning({ codexHome: options.codexHome });
2074
+ const preservedServiceTier = await capturePreservedAgentServiceTier({ codexHome: options.codexHome });
2075
+ const linked = await linkCachedPluginAgents({
2076
+ codexHome: options.codexHome,
2077
+ pluginRoot: stageRoot,
2078
+ preservedReasoning,
2079
+ preservedServiceTier
2080
+ });
2081
+ const agentConfigs = linked.map((link) => ({ configFile: `./agents/${link.name}`, name: agentNameFromToml2(link.name) })).sort((left, right) => left.name.localeCompare(right.name));
2082
+ return { agentConfigs, degraded: [] };
2083
+ } catch (error) {
2084
+ return {
2085
+ agentConfigs: [],
2086
+ degraded: [
2087
+ {
2088
+ component: "agents",
2089
+ hint: BOOTSTRAP_DOCTOR_HINT,
2090
+ reason: `failed to link bundled agents into ${agentsTarget}: ${errorMessage(error)}`
2091
+ }
2092
+ ]
2093
+ };
2094
+ }
2095
+ }
2096
+ async function stageBundledAgents(pluginRoot, stageRoot) {
2097
+ await rm7(stageRoot, { force: true, recursive: true });
2098
+ await mkdir7(stageRoot, { recursive: true });
2099
+ const componentsRoot = join14(pluginRoot, "components");
2100
+ for (const componentName of await directoryNames(componentsRoot)) {
2101
+ const agentsDir = join14(componentsRoot, componentName, "agents");
2102
+ const agentFiles = (await fileNames(agentsDir)).filter((name) => name.endsWith(".toml"));
2103
+ if (agentFiles.length === 0) continue;
2104
+ const stagedAgentsDir = join14(stageRoot, "components", componentName, "agents");
2105
+ await mkdir7(stagedAgentsDir, { recursive: true });
2106
+ for (const agentFile of agentFiles) {
2107
+ await copyFile2(join14(agentsDir, agentFile), join14(stagedAgentsDir, agentFile));
2108
+ }
2109
+ }
2110
+ }
2111
+ async function updateConfigStep(options, inputs, degraded) {
2112
+ const configPath = join14(options.codexHome, "config.toml");
2113
+ try {
2114
+ await assertWritableConfigIfPresent(configPath);
2115
+ const trustedHookStates = await trustedHookStatesForPlugin({
2116
+ marketplaceName: SETUP_MARKETPLACE_NAME,
2117
+ pluginName: SETUP_PLUGIN_NAME,
2118
+ pluginRoot: options.pluginRoot
2119
+ });
2120
+ await updateCodexConfig({
2121
+ agentConfigs: inputs.agentConfigs,
2122
+ // Hard invariant: the bootstrap worker NEVER writes permission keys
2123
+ // (approval/sandbox/network policies stay installer-flag-only).
2124
+ autonomousPermissions: false,
2125
+ configPath,
2126
+ gitBashEnabled: inputs.gitBashEnabled,
2127
+ marketplaceName: SETUP_MARKETPLACE_NAME,
2128
+ platform: options.platform,
2129
+ pluginNames: [SETUP_PLUGIN_NAME],
2130
+ preserveMarketplaceSource: true,
2131
+ // The marketplace plugin tree has no <root>/plugin/model-catalog.json,
2132
+ // so updateCodexConfig falls back to the catalog bundled into this
2133
+ // dist; bootstrap-setup.test.mjs guards against drift between the two.
2134
+ repoRoot: options.pluginRoot,
2135
+ trustedHookStates
2136
+ });
2137
+ } catch (error) {
2138
+ degraded.push({
2139
+ component: "config",
2140
+ hint: BOOTSTRAP_DOCTOR_HINT,
2141
+ reason: `failed to update ${configPath}: ${errorMessage(error)}`
2142
+ });
2143
+ }
2144
+ }
2145
+ async function assertWritableConfigIfPresent(configPath) {
2146
+ try {
2147
+ if (((await stat4(configPath)).mode & 146) === 0) throw new Error(`${configPath} has no write permission bits set`);
2148
+ } catch (error) {
2149
+ if (errorCode(error) === "ENOENT") return;
2150
+ throw error;
2151
+ }
2152
+ }
2153
+ function errorCode(error) {
2154
+ return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
2155
+ }
2156
+ async function linkComponentBinsStep(options, degraded) {
2157
+ const binDir = resolveCodexInstallerBinDir({ codexHome: options.codexHome, env: options.env });
2158
+ try {
2159
+ await linkCachedPluginBins({ binDir, pluginRoot: options.pluginRoot, platform: options.platform });
2160
+ } catch (error) {
2161
+ degraded.push({
2162
+ component: "bin-links",
2163
+ hint: BOOTSTRAP_DOCTOR_HINT,
2164
+ reason: `failed to link component bins into ${binDir}: ${errorMessage(error)}`
2165
+ });
2166
+ }
2167
+ await linkRuntimeWrapperStep(options, binDir, degraded);
2168
+ }
2169
+ async function linkRuntimeWrapperStep(options, binDir, degraded) {
2170
+ const cliPath = join14(options.pluginRoot, "dist", "cli", "index.js");
2171
+ try {
2172
+ const linked = await linkRootRuntimeBin({
2173
+ binDir,
2174
+ codexHome: options.codexHome,
2175
+ platform: options.platform,
2176
+ repoRoot: options.pluginRoot
2177
+ });
2178
+ if (linked !== null) return;
2179
+ degraded.push({
2180
+ component: "omo-cli",
2181
+ hint: "use npx lazycodex-ai for the omo CLI",
2182
+ reason: "marketplace payload has no dist/cli"
2183
+ });
2184
+ await appendBootstrapLog(options.pluginData, options.now ?? Date.now(), "omo-cli-degraded", {
2185
+ warning: `Warning: skipped the omo runtime wrapper because ${cliPath} is missing; omo sparkshell/ulw-loop commands will be unavailable until a package shipping dist/cli is installed`
2186
+ });
2187
+ } catch (error) {
2188
+ degraded.push({
2189
+ component: "omo-cli",
2190
+ hint: BOOTSTRAP_DOCTOR_HINT,
2191
+ reason: `failed to link the omo runtime wrapper into ${binDir}: ${errorMessage(error)}`
2192
+ });
2193
+ }
2194
+ }
2195
+ async function stampGitBashEnvStep(options, degraded) {
2196
+ try {
2197
+ await stampGitBashMcpEnv({ env: options.env, platform: options.platform, pluginRoot: options.pluginRoot });
2198
+ } catch (error) {
2199
+ degraded.push({
2200
+ component: "git-bash-env",
2201
+ hint: BOOTSTRAP_DOCTOR_HINT,
2202
+ reason: `failed to stamp ${join14(options.pluginRoot, ".mcp.json")}: ${errorMessage(error)}`
2203
+ });
2204
+ }
2205
+ }
2206
+ var execFileAsync2 = promisify2(execFile2);
2207
+ async function defaultRunCommand(command, args, options) {
2208
+ return execFileAsync2(command, [...args], { cwd: options.cwd });
2209
+ }
2210
+ async function directoryNames(root) {
2211
+ return entryNames(root, (entry) => entry.isDirectory());
2212
+ }
2213
+ async function fileNames(root) {
2214
+ return entryNames(root, (entry) => entry.isFile());
2215
+ }
2216
+ async function entryNames(root, keep) {
2217
+ try {
2218
+ const entries = await readdir3(root, { withFileTypes: true });
2219
+ return entries.filter((entry) => keep(entry)).map((entry) => entry.name).sort();
2220
+ } catch (error) {
2221
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return [];
2222
+ throw error;
2223
+ }
2224
+ }
2225
+ function agentNameFromToml2(fileName) {
2226
+ return fileName.endsWith(".toml") ? fileName.slice(0, -".toml".length) : fileName;
2227
+ }
2228
+ function errorMessage(error) {
2229
+ return error instanceof Error ? error.message : String(error);
2230
+ }
2231
+
2232
+ // src/worker.ts
2233
+ var BOOTSTRAP_DOCTOR_HINT = "npx lazycodex-ai doctor";
2234
+ function parseWorkerFlags(argv) {
2235
+ let codexHome;
2236
+ let manifestDir;
2237
+ let once = false;
2238
+ let only;
2239
+ for (let index = 0; index < argv.length; index += 1) {
2240
+ const flag = argv[index];
2241
+ if (flag === "--once") {
2242
+ once = true;
2243
+ continue;
2244
+ }
2245
+ if (flag === "--codex-home") {
2246
+ codexHome = requireFlagValue(argv, index, flag);
2247
+ index += 1;
2248
+ continue;
2249
+ }
2250
+ if (flag === "--only") {
2251
+ only = requireFlagValue(argv, index, flag);
2252
+ index += 1;
2253
+ continue;
2254
+ }
2255
+ if (flag === "--manifest-dir") {
2256
+ manifestDir = requireFlagValue(argv, index, flag);
2257
+ index += 1;
2258
+ continue;
2259
+ }
2260
+ throw new Error(`unknown worker flag: ${flag}`);
2261
+ }
2262
+ return {
2263
+ once,
2264
+ ...codexHome === void 0 ? {} : { codexHome },
2265
+ ...manifestDir === void 0 ? {} : { manifestDir },
2266
+ ...only === void 0 ? {} : { only }
2267
+ };
2268
+ }
2269
+ function resolvePluginDataRoot(env) {
2270
+ const fromEnv = env["PLUGIN_DATA"]?.trim();
2271
+ if (fromEnv !== void 0 && fromEnv.length > 0) return fromEnv;
2272
+ return join15(homedir4(), ".local", "share", "lazycodex");
2273
+ }
2274
+ async function readPluginVersion(pluginRoot) {
2275
+ try {
2276
+ const parsed = JSON.parse(await readFile12(join15(pluginRoot, ".codex-plugin", "plugin.json"), "utf8"));
2277
+ if (typeof parsed !== "object" || parsed === null) return void 0;
2278
+ const version = parsed["version"];
2279
+ if (typeof version !== "string") return void 0;
2280
+ const trimmed = version.trim();
2281
+ return trimmed.length > 0 ? trimmed : void 0;
2282
+ } catch {
2283
+ return void 0;
2284
+ }
2285
+ }
2286
+ async function readBootstrapState(statePath) {
2287
+ return parseBootstrapState(await readState(statePath));
2288
+ }
2289
+ function parseBootstrapState(raw) {
2290
+ const completedForVersion = typeof raw["completedForVersion"] === "string" ? raw["completedForVersion"] : void 0;
2291
+ const lastAttemptAt = typeof raw["lastAttemptAt"] === "number" ? raw["lastAttemptAt"] : void 0;
2292
+ const lastStatus = raw["lastStatus"] === "success" || raw["lastStatus"] === "degraded" ? raw["lastStatus"] : void 0;
2293
+ const degraded = parseDegradedEntries(raw["degraded"]);
2294
+ return {
2295
+ ...completedForVersion === void 0 ? {} : { completedForVersion },
2296
+ ...lastAttemptAt === void 0 ? {} : { lastAttemptAt },
2297
+ ...lastStatus === void 0 ? {} : { lastStatus },
2298
+ ...degraded === void 0 ? {} : { degraded }
2299
+ };
2300
+ }
2301
+ function defaultWorkerSteps(seams = {}) {
2302
+ return [
2303
+ {
2304
+ name: "setup",
2305
+ run: (context) => runWorkerSetup(context)
2306
+ },
2307
+ {
2308
+ name: "sg",
2309
+ run: (context) => runSgProvision(context, seams.sg)
2310
+ }
2311
+ ];
2312
+ }
2313
+ async function runBootstrapWorker(options = {}) {
2314
+ const env = options.env ?? process.env;
2315
+ const now = options.now ?? Date.now();
2316
+ const platform = options.platform ?? process.platform;
2317
+ const flags = parseWorkerFlags(options.argv ?? []);
2318
+ const steps = options.steps ?? defaultWorkerSteps();
2319
+ const pluginRoot = resolvePluginRoot(env);
2320
+ const pluginData = resolvePluginDataRoot(env);
2321
+ const statePath = resolveBootstrapStatePath(pluginData);
2322
+ const lockEnv = { ...env, PLUGIN_DATA: pluginData };
2323
+ const locks = await bootstrapLocks({ env: lockEnv, now, pluginData });
2324
+ if (locks === null) return { ran: false, reason: "locked" };
2325
+ try {
2326
+ const pluginVersion = await readPluginVersion(pluginRoot);
2327
+ const marker = await readBootstrapState(statePath);
2328
+ if (!flags.once && pluginVersion !== void 0 && marker.completedForVersion === pluginVersion) {
2329
+ await appendBootstrapLog(pluginData, now, "worker-skipped", { reason: "already-completed", version: pluginVersion });
2330
+ return { ran: false, reason: "already-completed" };
2331
+ }
2332
+ const codexHome = flags.codexHome ?? (await resolveCodexHome({ env, pluginRoot })).path;
2333
+ const context = { codexHome, env, flags, now, platform, pluginData, pluginRoot, pluginVersion };
2334
+ await appendBootstrapLog(pluginData, now, "worker-started", { version: pluginVersion ?? "unknown" });
2335
+ const degraded = [];
2336
+ if (pluginVersion === void 0) {
2337
+ degraded.push({
2338
+ component: "bootstrap",
2339
+ hint: BOOTSTRAP_DOCTOR_HINT,
2340
+ reason: `plugin version unresolved from ${join15(pluginRoot, ".codex-plugin", "plugin.json")}`
2341
+ });
2342
+ }
2343
+ for (const step of steps) {
2344
+ if (flags.only !== void 0 && step.name !== flags.only) continue;
2345
+ degraded.push(...await runStep(step, context));
2346
+ }
2347
+ const status = degraded.length === 0 ? "success" : "degraded";
2348
+ const state = {
2349
+ ...pluginVersion === void 0 ? {} : { completedForVersion: pluginVersion },
2350
+ degraded,
2351
+ lastAttemptAt: now,
2352
+ lastStatus: status
2353
+ };
2354
+ await writeState(statePath, state);
2355
+ await appendBootstrapLog(pluginData, now, "worker-finished", { degradedCount: degraded.length, status });
2356
+ return { degraded, ran: true, statePath, status };
2357
+ } finally {
2358
+ await locks.release();
2359
+ }
2360
+ }
2361
+ async function runStep(step, context) {
2362
+ try {
2363
+ return (await step.run(context)).degraded;
2364
+ } catch (error) {
2365
+ return [
2366
+ {
2367
+ component: step.name,
2368
+ hint: BOOTSTRAP_DOCTOR_HINT,
2369
+ reason: error instanceof Error ? error.message : String(error)
2370
+ }
2371
+ ];
2372
+ }
2373
+ }
2374
+ function resolvePluginRoot(env) {
2375
+ const fromEnv = env["PLUGIN_ROOT"]?.trim();
2376
+ if (fromEnv !== void 0 && fromEnv.length > 0) return fromEnv;
2377
+ return resolve5(dirname8(fileURLToPath2(import.meta.url)), "..", "..", "..");
2378
+ }
2379
+ async function appendBootstrapLog(pluginData, now, event, details) {
2380
+ try {
2381
+ const logPath = join15(pluginData, "bootstrap", "bootstrap.log");
2382
+ await mkdir8(dirname8(logPath), { recursive: true });
2383
+ await appendFile2(logPath, `${JSON.stringify({ timestamp: new Date(now).toISOString(), event, ...details })}
2384
+ `);
2385
+ } catch {
2386
+ }
2387
+ }
2388
+ function parseDegradedEntries(raw) {
2389
+ if (!Array.isArray(raw)) return void 0;
2390
+ const entries = [];
2391
+ for (const candidate of raw) {
2392
+ if (typeof candidate !== "object" || candidate === null) continue;
2393
+ const record = candidate;
2394
+ if (typeof record["component"] !== "string" || typeof record["reason"] !== "string") continue;
2395
+ entries.push({
2396
+ component: record["component"],
2397
+ reason: record["reason"],
2398
+ ...typeof record["hint"] === "string" ? { hint: record["hint"] } : {}
2399
+ });
2400
+ }
2401
+ return entries;
2402
+ }
2403
+ function requireFlagValue(argv, index, flag) {
2404
+ const value = argv[index + 1];
2405
+ if (value === void 0 || value.startsWith("--")) {
2406
+ throw new Error(`${flag} requires a value`);
2407
+ }
2408
+ return value;
2409
+ }
2410
+
2411
+ // src/hook.ts
2412
+ var BOOTSTRAP_RESTART_NOTICE = "LazyCodex bootstrap running in background \u2014 restart the session when it completes";
2413
+ async function runSessionStartHook(options) {
2414
+ return (await executeSessionStartHook(options)).exitCode;
2415
+ }
2416
+ async function executeSessionStartHook(options) {
2417
+ if (options.stdin !== void 0) await drainStdin(options.stdin);
2418
+ const now = options.now ?? Date.now();
2419
+ const pluginRoot = options.env["PLUGIN_ROOT"]?.trim();
2420
+ const pluginData = options.env["PLUGIN_DATA"]?.trim();
2421
+ if (pluginRoot === void 0 || pluginRoot.length === 0 || pluginData === void 0 || pluginData.length === 0) {
2422
+ return { action: "skip-missing-env", exitCode: 0 };
2423
+ }
2424
+ const pluginVersion = await readPluginVersion(pluginRoot);
2425
+ if (pluginVersion === void 0) return { action: "skip-version-unresolved", exitCode: 0 };
2426
+ const state = await readBootstrapState(resolveBootstrapStatePath(pluginData));
2427
+ if (state.completedForVersion === pluginVersion) return { action: "skip-completed", exitCode: 0 };
2428
+ if (await isLockFresh(resolveBootstrapLockPath(pluginData), now)) return { action: "skip-locked", exitCode: 0 };
2429
+ const spawnWorker = options.spawnWorker ?? spawnDetachedWorker;
2430
+ spawnWorker({
2431
+ args: [options.workerCliPath ?? defaultWorkerCliPath(), "worker"],
2432
+ command: process.execPath,
2433
+ env: options.env
2434
+ });
2435
+ const writeNotice = options.writeNotice ?? ((line) => process.stdout.write(`${line}
2436
+ `));
2437
+ writeNotice(
2438
+ JSON.stringify({
2439
+ hookSpecificOutput: {
2440
+ hookEventName: "SessionStart",
2441
+ additionalContext: BOOTSTRAP_RESTART_NOTICE
2442
+ }
2443
+ })
2444
+ );
2445
+ return { action: "spawned", exitCode: 0 };
2446
+ }
2447
+ function spawnDetachedWorker(invocation) {
2448
+ const child = spawn(invocation.command, [...invocation.args], {
2449
+ detached: true,
2450
+ env: invocation.env,
2451
+ stdio: "ignore"
2452
+ });
2453
+ child.unref();
2454
+ }
2455
+ function defaultWorkerCliPath() {
2456
+ return fileURLToPath3(import.meta.url);
2457
+ }
2458
+ async function isLockFresh(lockPath, now) {
2459
+ try {
2460
+ const lockStat = await stat5(lockPath);
2461
+ return now - lockStat.mtimeMs < DEFAULT_LOCK_STALE_MS;
2462
+ } catch {
2463
+ return false;
2464
+ }
2465
+ }
2466
+ async function drainStdin(stdin) {
2467
+ if (stdin.isTTY === true) return;
2468
+ for await (const chunk of stdin) {
2469
+ void chunk;
2470
+ }
2471
+ }
2472
+
2473
+ // src/cli.ts
2474
+ var TOP_LEVEL_HELP = "Usage:\n omo-bootstrap hook session-start\n omo-bootstrap worker [--codex-home <dir>] [--once] [--only <step>] [--manifest-dir <dir>]\n omo-bootstrap download <manifest> <platform> <destination-dir>\n omo-bootstrap help | --help | -h\n";
2475
+ async function runDownloadCommand(args) {
2476
+ const [manifestName, platformKey, destinationDir] = args;
2477
+ if (manifestName === void 0 || platformKey === void 0 || destinationDir === void 0) {
2478
+ process.stderr.write(`[omo-bootstrap] download requires <manifest> <platform> <destination-dir>
2479
+ ${TOP_LEVEL_HELP}`);
2480
+ return 1;
2481
+ }
2482
+ try {
2483
+ const destination = await downloadFromManifest({ destinationDir, manifestName, platformKey });
2484
+ process.stdout.write(`OK:${destination}
2485
+ `);
2486
+ return 0;
2487
+ } catch (error) {
2488
+ process.stderr.write(`[omo-bootstrap] download failed: ${error instanceof Error ? error.message : String(error)}
2489
+ `);
2490
+ return 1;
2491
+ }
2492
+ }
2493
+ async function runWorkerCommand(args) {
2494
+ let result;
2495
+ try {
2496
+ result = await runBootstrapWorker({ argv: args, env: process.env });
2497
+ } catch (error) {
2498
+ const message = error instanceof Error ? error.message : String(error);
2499
+ if (/flag/.test(message)) {
2500
+ process.stderr.write(`[omo-bootstrap] ${message}
2501
+ ${TOP_LEVEL_HELP}`);
2502
+ return 1;
2503
+ }
2504
+ process.stderr.write(`[omo-bootstrap] worker error: ${message}
2505
+ `);
2506
+ return 0;
2507
+ }
2508
+ process.stdout.write(
2509
+ result.ran ? `[omo-bootstrap] worker finished: ${result.status}
2510
+ ` : `[omo-bootstrap] worker skipped: ${result.reason}
2511
+ `
2512
+ );
2513
+ return 0;
2514
+ }
2515
+ async function main() {
2516
+ const argv = process.argv.slice(2);
2517
+ const command = argv[0];
2518
+ if (command === void 0 || command === "help" || command === "--help" || command === "-h") {
2519
+ process.stdout.write(TOP_LEVEL_HELP);
2520
+ return 0;
2521
+ }
2522
+ if (command === "hook" && argv[1] === "session-start") {
2523
+ return runSessionStartHook({ env: process.env, stdin: process.stdin });
2524
+ }
2525
+ if (command === "worker") {
2526
+ return runWorkerCommand(argv.slice(1));
2527
+ }
2528
+ if (command === "download") {
2529
+ return runDownloadCommand(argv.slice(1));
2530
+ }
2531
+ process.stderr.write(`[omo-bootstrap] unknown command: ${argv.join(" ")}
2532
+ ${TOP_LEVEL_HELP}`);
2533
+ return 1;
2534
+ }
2535
+ function isProcessEntry() {
2536
+ const entry = process.argv[1];
2537
+ if (entry === void 0) return false;
2538
+ try {
2539
+ return realpathSync(entry) === realpathSync(fileURLToPath4(import.meta.url));
2540
+ } catch {
2541
+ return false;
2542
+ }
2543
+ }
2544
+ if (isProcessEntry()) {
2545
+ main().then((code) => {
2546
+ process.exit(code);
2547
+ }).catch((error) => {
2548
+ process.stderr.write(`[omo-bootstrap] ${error instanceof Error ? error.message : String(error)}
2549
+ `);
2550
+ process.exit(0);
2551
+ });
2552
+ }
2553
+ export {
2554
+ BOOTSTRAP_DOCTOR_HINT,
2555
+ BOOTSTRAP_RESTART_NOTICE,
2556
+ GIT_BASH_INSTALL_HINT,
2557
+ INSTALL_SNAPSHOT_FILENAME,
2558
+ SETUP_MARKETPLACE_NAME,
2559
+ SETUP_PLUGIN_NAME,
2560
+ SG_FORCE_PROVISION_ENV_KEY,
2561
+ SG_PROVISION_COMPONENT,
2562
+ appendBootstrapLog,
2563
+ bootstrapLocks,
2564
+ defaultWorkerSteps,
2565
+ detectInstallFlow,
2566
+ detectInstallFlowDetailed,
2567
+ detectInstallFlowForTest,
2568
+ detectInstallFlowFromEnvironment,
2569
+ executeSessionStartHook,
2570
+ parseBootstrapState,
2571
+ parseWorkerFlags,
2572
+ readBootstrapState,
2573
+ readPluginVersion,
2574
+ resolveBootstrapLockPath,
2575
+ resolveBootstrapStatePath,
2576
+ resolveCodexHome,
2577
+ resolvePluginDataRoot,
2578
+ runBootstrapWorker,
2579
+ runSessionStartHook,
2580
+ runSgProvision,
2581
+ runWorkerSetup,
2582
+ sgProvisionDestination
2583
+ };