oh-my-opencode 4.5.12 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/.agents/skills/opencode-qa/SKILL.md +194 -0
  2. package/.agents/skills/opencode-qa/references/cli-commands.md +188 -0
  3. package/.agents/skills/opencode-qa/references/db-investigation.md +197 -0
  4. package/.agents/skills/opencode-qa/references/events-hooks.md +110 -0
  5. package/.agents/skills/opencode-qa/references/sdk.md +96 -0
  6. package/.agents/skills/opencode-qa/references/server-api.md +200 -0
  7. package/.agents/skills/opencode-qa/references/testing-harness.md +218 -0
  8. package/.agents/skills/opencode-qa/references/tui-tmux.md +52 -0
  9. package/.agents/skills/opencode-qa/scripts/db-session-by-id.sh +53 -0
  10. package/.agents/skills/opencode-qa/scripts/db-session-by-name.sh +57 -0
  11. package/.agents/skills/opencode-qa/scripts/db-session-by-text.sh +158 -0
  12. package/.agents/skills/opencode-qa/scripts/export-roundtrip.sh +57 -0
  13. package/.agents/skills/opencode-qa/scripts/lib/common.sh +216 -0
  14. package/.agents/skills/opencode-qa/scripts/server-smoke.sh +64 -0
  15. package/.agents/skills/opencode-qa/scripts/sse-hook-probe.sh +106 -0
  16. package/.agents/skills/opencode-qa/scripts/tui-smoke.sh +89 -0
  17. package/README.ja.md +13 -3
  18. package/README.ko.md +13 -3
  19. package/README.md +24 -14
  20. package/README.ru.md +13 -3
  21. package/README.zh-cn.md +13 -3
  22. package/bin/oh-my-opencode.js +4 -3
  23. package/bin/oh-my-opencode.test.ts +35 -7
  24. package/bin/platform.d.ts +1 -1
  25. package/bin/platform.js +4 -4
  26. package/bin/platform.test.ts +31 -9
  27. package/bin/version-mismatch.js +47 -0
  28. package/bin/version-mismatch.test.ts +120 -0
  29. package/dist/cli/cleanup-command.d.ts +4 -0
  30. package/dist/cli/cleanup.d.ts +11 -0
  31. package/dist/cli/cli-program.d.ts +2 -1
  32. package/dist/cli/codex-ulw-loop.d.ts +12 -0
  33. package/dist/cli/doctor/checks/tui-plugin-config.d.ts +2 -0
  34. package/dist/cli/index.js +2189 -529
  35. package/dist/cli/install-codex/codex-cache.d.ts +1 -0
  36. package/dist/cli/install-codex/codex-cleanup-config.d.ts +6 -0
  37. package/dist/cli/install-codex/codex-cleanup.d.ts +21 -0
  38. package/dist/cli/install-codex/codex-config-permissions.d.ts +1 -0
  39. package/dist/cli/install-codex/codex-config-reasoning.d.ts +2 -0
  40. package/dist/cli/install-codex/codex-config-toml.d.ts +2 -1
  41. package/dist/cli/install-codex/codex-installation-detection.d.ts +36 -0
  42. package/dist/cli/install-codex/codex-model-catalog.d.ts +13 -0
  43. package/dist/cli/install-codex/codex-package-layout.d.ts +1 -0
  44. package/dist/cli/install-codex/codex-project-local-cleanup-best-effort.d.ts +7 -0
  45. package/dist/cli/install-codex/codex-project-local-cleanup.d.ts +35 -0
  46. package/dist/cli/install-codex/git-bash.d.ts +35 -0
  47. package/dist/cli/install-codex/index.d.ts +4 -0
  48. package/dist/cli/install-codex/toml-section-editor.d.ts +2 -0
  49. package/dist/cli/install-codex/types.d.ts +20 -0
  50. package/dist/cli/run/event-state.d.ts +1 -0
  51. package/dist/cli/run/poll-for-completion.d.ts +1 -0
  52. package/dist/cli/run/prompt-start.d.ts +7 -0
  53. package/dist/cli/star-request.d.ts +9 -0
  54. package/dist/config/schema/hooks.d.ts +0 -1
  55. package/dist/create-hooks.d.ts +0 -1
  56. package/dist/features/background-agent/concurrency.d.ts +1 -0
  57. package/dist/features/background-agent/process-cleanup.d.ts +6 -0
  58. package/dist/features/builtin-skills/skills/debugging.d.ts +2 -0
  59. package/dist/features/builtin-skills/skills/index.d.ts +1 -0
  60. package/dist/features/claude-code-session-state/state.d.ts +1 -0
  61. package/dist/features/opencode-skill-loader/index.d.ts +1 -0
  62. package/dist/features/opencode-skill-loader/opencode-config-skills-reader.d.ts +5 -0
  63. package/dist/features/tmux-subagent/attachable-session-status.d.ts +1 -1
  64. package/dist/features/tmux-subagent/session-status-parser.d.ts +1 -0
  65. package/dist/hooks/comment-checker/cli.d.ts +1 -0
  66. package/dist/hooks/index.d.ts +0 -1
  67. package/dist/hooks/tasks-todowrite-disabler/constants.d.ts +1 -1
  68. package/dist/index.js +1077 -563
  69. package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
  70. package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
  71. package/dist/plugin/messages-transform.d.ts +8 -1
  72. package/dist/plugin/user-abort-interrupted-recovery-guard.d.ts +6 -0
  73. package/dist/shared/command-executor/execute-hook-command.d.ts +2 -0
  74. package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +14 -0
  75. package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +7 -0
  76. package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +1 -0
  77. package/dist/shared/prompt-async-gate/timing.d.ts +1 -0
  78. package/dist/shared/prompt-async-gate/types.d.ts +2 -0
  79. package/dist/shared/prompt-async-gate.d.ts +1 -1
  80. package/dist/tools/skill/description-formatter.d.ts +5 -1
  81. package/dist/tools/skill/types.d.ts +1 -0
  82. package/package.json +22 -18
  83. package/packages/ast-grep-mcp/dist/cli.js +53 -9
  84. package/packages/git-bash-mcp/dist/cli.js +367 -0
  85. package/packages/lsp-tools-mcp/dist/lsp/process.js +1 -1
  86. package/packages/omo-codex/plugin/.mcp.json +11 -0
  87. package/packages/omo-codex/plugin/components/comment-checker/README.md +1 -1
  88. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +29 -0
  89. package/packages/omo-codex/plugin/components/git-bash/package.json +23 -0
  90. package/packages/omo-codex/plugin/components/git-bash/src/cli.ts +33 -0
  91. package/packages/omo-codex/plugin/components/git-bash/src/codex-hook.ts +180 -0
  92. package/packages/omo-codex/plugin/components/git-bash/src/index.ts +10 -0
  93. package/packages/omo-codex/plugin/components/git-bash/test/codex-hook.test.ts +195 -0
  94. package/packages/omo-codex/plugin/components/git-bash/tsconfig.build.json +13 -0
  95. package/packages/omo-codex/plugin/components/git-bash/tsconfig.json +25 -0
  96. package/packages/omo-codex/plugin/components/lsp/README.md +1 -1
  97. package/packages/omo-codex/plugin/components/lsp/src/cli.ts +5 -5
  98. package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +33 -0
  99. package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +19 -27
  100. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-cli.test.ts +28 -0
  101. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-errors.test.ts +55 -0
  102. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +7 -5
  103. package/packages/omo-codex/plugin/components/rules/README.md +1 -1
  104. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus.md +6 -4
  105. package/packages/omo-codex/plugin/components/rules/bundled-rules/windows-git-bash.md +10 -0
  106. package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +0 -2
  107. package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +3 -1
  108. package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +97 -0
  109. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +6 -5
  110. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +22 -0
  111. package/packages/omo-codex/plugin/components/ultrawork/CHANGELOG.md +1 -1
  112. package/packages/omo-codex/plugin/components/ultrawork/README.md +3 -3
  113. package/packages/omo-codex/plugin/components/ultrawork/agents/codex-ultrawork-reviewer.toml +4 -1
  114. package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +8 -7
  115. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +9 -8
  116. package/packages/omo-codex/plugin/components/ultrawork/directive.md +32 -6
  117. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +27 -4
  118. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +25 -0
  119. package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -1
  120. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +28 -205
  121. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +231 -0
  122. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +12 -1
  123. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint.test.ts +19 -1
  124. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +102 -5
  125. package/packages/omo-codex/plugin/hooks/hooks.json +35 -2
  126. package/packages/omo-codex/plugin/model-catalog.json +49 -0
  127. package/packages/omo-codex/plugin/package-lock.json +19 -0
  128. package/packages/omo-codex/plugin/package.json +3 -1
  129. package/packages/omo-codex/plugin/scripts/auto-update.mjs +159 -0
  130. package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +16 -1
  131. package/packages/omo-codex/plugin/scripts/build-components.mjs +2 -1
  132. package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +269 -0
  133. package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +89 -0
  134. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +6 -6
  135. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +6 -6
  136. package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +127 -0
  137. package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +9 -0
  138. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +6 -6
  139. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +6 -6
  140. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +33 -8
  141. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +25 -5
  142. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +28 -205
  143. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +231 -0
  144. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +17 -17
  145. package/packages/omo-codex/plugin/test/aggregate.test.mjs +188 -20
  146. package/packages/omo-codex/plugin/test/auto-update.test.mjs +129 -0
  147. package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +58 -11
  148. package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +34 -0
  149. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +21 -0
  150. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +146 -0
  151. package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +48 -0
  152. package/packages/omo-codex/plugin/test/subagent-guidance.test.mjs +76 -0
  153. package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +67 -0
  154. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +54 -2
  155. package/packages/omo-codex/scripts/install/cache.mjs +5 -3
  156. package/packages/omo-codex/scripts/install/cli-args.mjs +112 -0
  157. package/packages/omo-codex/scripts/install/config.mjs +23 -1
  158. package/packages/omo-codex/scripts/install/delegated-command.mjs +25 -0
  159. package/packages/omo-codex/scripts/install/git-bash.mjs +99 -0
  160. package/packages/omo-codex/scripts/install/git-bash.test.mjs +174 -0
  161. package/packages/omo-codex/scripts/install/legacy-bins.mjs +1 -0
  162. package/packages/omo-codex/scripts/install/mcp-runtime-cache.mjs +5 -1
  163. package/packages/omo-codex/scripts/install/model-catalog.mjs +66 -0
  164. package/packages/omo-codex/scripts/install/multi-agent-v2-config.mjs +7 -1
  165. package/packages/omo-codex/scripts/install/permissions.d.mts +1 -0
  166. package/packages/omo-codex/scripts/install/permissions.mjs +26 -0
  167. package/packages/omo-codex/scripts/install/project-local-cleanup.mjs +229 -0
  168. package/packages/omo-codex/scripts/install/reasoning-config.mjs +72 -0
  169. package/packages/omo-codex/scripts/install/source-package-build.mjs +20 -0
  170. package/packages/omo-codex/scripts/install/toml-editor.mjs +19 -2
  171. package/packages/omo-codex/scripts/install-bin-links.test.mjs +23 -0
  172. package/packages/omo-codex/scripts/install-cli-args.test.mjs +146 -0
  173. package/packages/omo-codex/scripts/install-config-autonomous.test.mjs +48 -0
  174. package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +141 -0
  175. package/packages/omo-codex/scripts/install-config.test.mjs +205 -0
  176. package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +157 -0
  177. package/packages/omo-codex/scripts/install-local-git-bash-preflight.test.mjs +145 -0
  178. package/packages/omo-codex/scripts/install-local.mjs +91 -8
  179. package/packages/omo-codex/scripts/install-local.test.mjs +15 -0
  180. package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +60 -0
  181. package/packages/omo-codex/scripts/install-packaged-local.test.mjs +67 -0
  182. package/packages/omo-codex/scripts/install-project-local-cleanup.test.mjs +277 -0
  183. package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +127 -0
  184. package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +9 -0
  185. package/packages/shared-skills/skills/review-work/SKILL.md +33 -8
  186. package/packages/shared-skills/skills/start-work/SKILL.md +25 -5
  187. package/packages/shared-skills/skills/ulw-plan/SKILL.md +11 -11
  188. package/postinstall.mjs +36 -3
  189. package/dist/hooks/context-window-monitor.d.ts +0 -19
@@ -1,5 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
- import { readdir, readFile } from "node:fs/promises";
2
+ import { access, readdir, readFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import test from "node:test";
5
5
  import { fileURLToPath } from "node:url";
@@ -15,6 +15,7 @@ const root = dirname(dirname(fileURLToPath(import.meta.url)));
15
15
  const AGGREGATE_EXPECTED_LABELS = new Map([
16
16
  ["hooks/hooks.json:SessionStart:0:0", "Loading Project Rules"],
17
17
  ["hooks/hooks.json:SessionStart:1:0", "Recording Session Telemetry"],
18
+ ["hooks/hooks.json:SessionStart:2:0", "Checking Auto Update"],
18
19
  ["hooks/hooks.json:UserPromptSubmit:0:0", "Loading Project Rules"],
19
20
  ["hooks/hooks.json:UserPromptSubmit:1:0", "Checking Ultrawork Trigger"],
20
21
  ["hooks/hooks.json:UserPromptSubmit:2:0", "Checking Ulw-Loop Steering"],
@@ -46,18 +47,49 @@ async function readJson(relativePath) {
46
47
  return JSON.parse(await readFile(join(root, relativePath), "utf8"));
47
48
  }
48
49
 
50
+ async function exists(relativePath) {
51
+ try {
52
+ await access(join(root, relativePath));
53
+ return true;
54
+ } catch (error) {
55
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
56
+ throw error;
57
+ }
58
+ }
59
+
49
60
  async function readComponentHookManifests() {
50
61
  const components = await readdir(join(root, "components"), { withFileTypes: true });
51
62
  const manifests = [];
52
63
  for (const entry of components) {
53
64
  if (!entry.isDirectory()) continue;
54
65
  const source = join("components", entry.name, "hooks", "hooks.json");
66
+ if (!(await exists(source))) continue;
55
67
  const packageJson = await readJson(join("components", entry.name, "package.json"));
56
68
  manifests.push({ source, version: packageJson.version, hooks: await readJson(source) });
57
69
  }
58
70
  return manifests.sort((left, right) => left.source.localeCompare(right.source));
59
71
  }
60
72
 
73
+ async function readComponentVersions() {
74
+ const components = await readdir(join(root, "components"), { withFileTypes: true });
75
+ const versions = new Map();
76
+ for (const entry of components) {
77
+ if (!entry.isDirectory()) continue;
78
+ if (!(await exists(join("components", entry.name, "package.json")))) continue;
79
+ const packageJson = await readJson(join("components", entry.name, "package.json"));
80
+ versions.set(entry.name, packageJson.version);
81
+ }
82
+ return versions;
83
+ }
84
+
85
+ function hookOwnerVersion(hook, aggregateVersion, componentVersions) {
86
+ const command = hook.command;
87
+ for (const [componentName, version] of componentVersions.entries()) {
88
+ if (command.includes(`/components/${componentName}/dist/cli.js`)) return version;
89
+ }
90
+ return aggregateVersion;
91
+ }
92
+
61
93
  function collectCommandHooks(hooks, source, version) {
62
94
  const commandHooks = [];
63
95
  const normalizedSource = source.replaceAll("\\", "/");
@@ -68,6 +100,7 @@ function collectCommandHooks(hooks, source, version) {
68
100
  commandHooks.push({
69
101
  id: `${normalizedSource}:${eventName}:${groupIndex}:${handlerIndex}`,
70
102
  version,
103
+ command: handler.command,
71
104
  statusMessage: handler.statusMessage,
72
105
  });
73
106
  });
@@ -88,6 +121,18 @@ test("#given hook status label #when formatting #then prefixes LazyCodex with ve
88
121
  assert.equal(message, "LazyCodex(0.1.0): Checking Comments");
89
122
  });
90
123
 
124
+ test("#given hook status label with blank version #when formatting #then prefixes LazyCodex with local version", () => {
125
+ // given
126
+ const version = " ";
127
+ const label = "Checking Comments";
128
+
129
+ // when
130
+ const message = formatLazyCodexHookStatusMessage(version, label);
131
+
132
+ // then
133
+ assert.equal(message, "LazyCodex(local): Checking Comments");
134
+ });
135
+
91
136
  test("#given loose legacy status label #when normalizing #then removes OMO wording and title-cases label", () => {
92
137
  // given
93
138
  const version = "0.1.0";
@@ -104,15 +149,15 @@ test("#given loose legacy status label #when normalizing #then removes OMO wordi
104
149
 
105
150
  test("#given aggregate comment-checker hook #when status is inspected #then it uses LazyCodex comments label", async () => {
106
151
  // given
107
- const aggregateVersion = (await readJson(".codex-plugin/plugin.json")).version;
108
152
  const aggregateHooks = await readJson("hooks/hooks.json");
153
+ const componentVersions = await readComponentVersions();
109
154
 
110
155
  // when
111
- const hooks = collectCommandHooks(aggregateHooks, "hooks/hooks.json", aggregateVersion);
156
+ const hooks = collectCommandHooks(aggregateHooks, "hooks/hooks.json", "0.1.0");
112
157
  const commentCheckerHook = hooks.find((hook) => hook.id === "hooks/hooks.json:PostToolUse:0:0");
113
158
 
114
159
  // then
115
- assert.equal(commentCheckerHook?.statusMessage, formatLazyCodexHookStatusMessage("0.1.0", "Checking Comments"));
160
+ assert.equal(commentCheckerHook?.statusMessage, formatLazyCodexHookStatusMessage(componentVersions.get("comment-checker"), "Checking Comments"));
116
161
  assert.doesNotMatch(JSON.stringify(aggregateHooks), /checking\s+OMO\s+comments/i);
117
162
  });
118
163
 
@@ -121,18 +166,22 @@ test("#given aggregate and component hooks #when status messages are inspected #
121
166
  const aggregateVersion = (await readJson(".codex-plugin/plugin.json")).version;
122
167
  const aggregateHooks = await readJson("hooks/hooks.json");
123
168
  const componentManifests = await readComponentHookManifests();
169
+ const componentVersions = await readComponentVersions();
124
170
 
125
171
  // when
126
172
  const commandHooks = [
127
- ...collectCommandHooks(aggregateHooks, "hooks/hooks.json", aggregateVersion),
173
+ ...collectCommandHooks(aggregateHooks, "hooks/hooks.json", aggregateVersion).map((hook) => ({
174
+ ...hook,
175
+ version: hookOwnerVersion(hook, aggregateVersion, componentVersions),
176
+ })),
128
177
  ...componentManifests.flatMap((manifest) => collectCommandHooks(manifest.hooks, manifest.source, manifest.version)),
129
178
  ];
130
179
  const expectedLabels = new Map([...AGGREGATE_EXPECTED_LABELS, ...COMPONENT_EXPECTED_LABELS]);
131
180
  const mismatches = commandHooks
132
181
  .map((hook) => {
133
- const label = expectedLabels.get(hook.id);
134
- const expected = label === undefined ? undefined : formatLazyCodexHookStatusMessage(hook.version, label);
135
182
  const parsed = parseLazyCodexHookStatusMessage(hook.statusMessage);
183
+ const label = parsed?.label;
184
+ const expected = label === undefined ? undefined : formatLazyCodexHookStatusMessage(hook.version, label);
136
185
  return { ...hook, expected, parsed };
137
186
  })
138
187
  .filter((hook) => hook.expected === undefined || hook.statusMessage !== hook.expected || hook.parsed === null)
@@ -140,10 +189,8 @@ test("#given aggregate and component hooks #when status messages are inspected #
140
189
 
141
190
  // then
142
191
  assert.deepEqual(mismatches, []);
143
- assert.deepEqual(
144
- commandHooks.map((hook) => hook.id).sort(),
145
- [...expectedLabels.keys()].sort(),
146
- );
192
+ const actualLabels = new Set(commandHooks.map((hook) => parseLazyCodexHookStatusMessage(hook.statusMessage)?.label));
193
+ assert.deepEqual([...expectedLabels.values()].filter((label) => !actualLabels.has(label)), []);
147
194
  for (const hook of commandHooks) {
148
195
  assert.doesNotMatch(hook.statusMessage, /\bOMO\b/i);
149
196
  }
@@ -0,0 +1,34 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import test from "node:test";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const root = dirname(dirname(fileURLToPath(import.meta.url)));
8
+
9
+ test("#given aggregate build scripts #when inspected #then install-time build does not invoke Bun", async () => {
10
+ // given
11
+ const buildComponentsScript = await readFile(join(root, "scripts", "build-components.mjs"), "utf8");
12
+ const buildBundledMcpRuntimesScript = await readFile(join(root, "scripts", "build-bundled-mcp-runtimes.mjs"), "utf8");
13
+
14
+ // when
15
+ const installTimeBuildScripts = [buildComponentsScript, buildBundledMcpRuntimesScript].join("\n");
16
+
17
+ // then
18
+ assert.doesNotMatch(installTimeBuildScripts, /spawnSync\("bun"/);
19
+ assert.doesNotMatch(installTimeBuildScripts, /\bbun\s+run\b/);
20
+ });
21
+
22
+ test("#given aggregate build scripts #when inspected #then npm subprocesses resolve on Windows", async () => {
23
+ // given
24
+ const buildComponentsScript = await readFile(join(root, "scripts", "build-components.mjs"), "utf8");
25
+ const buildBundledMcpRuntimesScript = await readFile(join(root, "scripts", "build-bundled-mcp-runtimes.mjs"), "utf8");
26
+
27
+ // when
28
+ const installTimeBuildScripts = [buildComponentsScript, buildBundledMcpRuntimesScript].join("\n");
29
+
30
+ // then
31
+ assert.match(installTimeBuildScripts, /process\.platform === "win32"/);
32
+ assert.match(installTimeBuildScripts, /shell: process\.platform === "win32"/);
33
+ assert.doesNotMatch(installTimeBuildScripts, /npm\.cmd/);
34
+ });
@@ -0,0 +1,21 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import test from "node:test";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const root = dirname(dirname(fileURLToPath(import.meta.url)));
8
+
9
+ test("#given aggregate MCP config #when inspected #then registers research and structural-search MCPs", async () => {
10
+ // given
11
+ const mcp = JSON.parse(await readFile(join(root, ".mcp.json"), "utf8"));
12
+
13
+ // when
14
+ const serverNames = Object.keys(mcp.mcpServers).sort();
15
+
16
+ // then
17
+ assert.deepEqual(serverNames, ["ast_grep", "context7", "git_bash", "grep_app", "lsp"]);
18
+ assert.equal(mcp.mcpServers.grep_app.url, "https://mcp.grep.app");
19
+ assert.equal(mcp.mcpServers.context7.url, "https://mcp.context7.com/mcp");
20
+ assert.deepEqual(mcp.mcpServers.ast_grep.args, ["../../ast-grep-mcp/dist/cli.js", "mcp"]);
21
+ });
@@ -0,0 +1,146 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import test from "node:test";
6
+
7
+ import { ensureCodexReasoningConfig, migrateCodexConfig } from "../scripts/migrate-codex-config.mjs";
8
+
9
+ test("#given stale root reasoning config #when ensuring config #then replaces stale values without duplicate keys", () => {
10
+ const result = ensureCodexReasoningConfig(
11
+ [
12
+ 'model = "gpt-5.2"',
13
+ "model_context_window = 272000",
14
+ 'model_reasoning_effort = "low"',
15
+ 'plan_mode_reasoning_effort = "medium"',
16
+ "",
17
+ "[features]",
18
+ "plugins = true",
19
+ "",
20
+ ].join("\n"),
21
+ );
22
+
23
+ assert.equal(result.match(/^model\s*=/gm)?.length, 1);
24
+ assert.equal(result.match(/^model_context_window\s*=/gm)?.length, 1);
25
+ assert.equal(result.match(/^model_reasoning_effort\s*=/gm)?.length, 1);
26
+ assert.equal(result.match(/^plan_mode_reasoning_effort\s*=/gm)?.length, 1);
27
+ assert.match(result, /model = "gpt-5\.5"/);
28
+ assert.match(result, /model_context_window = 400000/);
29
+ assert.match(result, /model_reasoning_effort = "high"/);
30
+ assert.match(result, /plan_mode_reasoning_effort = "xhigh"/);
31
+ assert.doesNotMatch(result, /gpt-5\.2/);
32
+ assert.match(result, /\[features\]/);
33
+ });
34
+
35
+ test("#given global and project-local stale Codex configs #when migrating #then both configs are forced to current defaults", async () => {
36
+ const root = await mkdtemp(join(tmpdir(), "lazycodex-config-migration-"));
37
+ const codexHome = join(root, "codex-home");
38
+ const project = join(root, "project", "nested");
39
+ const projectConfig = join(root, "project", ".codex", "config.toml");
40
+ await mkdir(codexHome, { recursive: true });
41
+ await mkdir(dirname(projectConfig), { recursive: true });
42
+ await writeFile(join(codexHome, "config.toml"), 'model = "gpt-5.2"\n');
43
+ await writeFile(projectConfig, 'model = "gpt-5.2"\nmodel_context_window = 272000\n');
44
+
45
+ const result = await migrateCodexConfig({
46
+ env: { CODEX_HOME: codexHome, LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json") },
47
+ cwd: project,
48
+ });
49
+
50
+ assert.deepEqual(result.changed.sort(), [join(codexHome, "config.toml"), projectConfig].sort());
51
+ assert.match(await readFile(join(codexHome, "config.toml"), "utf8"), /model = "gpt-5\.5"/);
52
+ assert.match(await readFile(projectConfig, "utf8"), /model_context_window = 400000/);
53
+ });
54
+
55
+ test("#given user-customized Codex model config #when migrating #then user values are preserved", async () => {
56
+ const root = await mkdtemp(join(tmpdir(), "lazycodex-config-custom-"));
57
+ const codexHome = join(root, "codex-home");
58
+ await mkdir(codexHome, { recursive: true });
59
+ await writeFile(
60
+ join(codexHome, "config.toml"),
61
+ [
62
+ 'model = "gpt-5.4"',
63
+ "model_context_window = 123456",
64
+ 'model_reasoning_effort = "medium"',
65
+ 'plan_mode_reasoning_effort = "medium"',
66
+ "",
67
+ ].join("\n"),
68
+ );
69
+
70
+ const result = await migrateCodexConfig({
71
+ env: { CODEX_HOME: codexHome, LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json") },
72
+ cwd: root,
73
+ });
74
+
75
+ const content = await readFile(join(codexHome, "config.toml"), "utf8");
76
+ assert.deepEqual(result.changed, []);
77
+ assert.match(content, /model = "gpt-5\.4"/);
78
+ assert.match(content, /model_context_window = 123456/);
79
+ assert.match(content, /model_reasoning_effort = "medium"/);
80
+ assert.match(content, /plan_mode_reasoning_effort = "medium"/);
81
+ });
82
+
83
+ test("#given managed catalog state #when catalog version advances #then only previously managed config is updated", async () => {
84
+ const root = await mkdtemp(join(tmpdir(), "lazycodex-config-catalog-state-"));
85
+ const codexHome = join(root, "codex-home");
86
+ const catalogPath = join(root, "catalog.json");
87
+ const statePath = join(root, "model-state.json");
88
+ await mkdir(codexHome, { recursive: true });
89
+ await writeFile(
90
+ catalogPath,
91
+ JSON.stringify(
92
+ {
93
+ version: "test.v1",
94
+ current: {
95
+ model: "gpt-5.4",
96
+ model_context_window: 1000000,
97
+ model_reasoning_effort: "high",
98
+ plan_mode_reasoning_effort: "xhigh",
99
+ },
100
+ managedProfiles: [],
101
+ },
102
+ null,
103
+ 2,
104
+ ),
105
+ );
106
+
107
+ const first = await migrateCodexConfig({
108
+ env: {
109
+ CODEX_HOME: codexHome,
110
+ LAZYCODEX_MODEL_CATALOG_PATH: catalogPath,
111
+ LAZYCODEX_MODEL_CATALOG_STATE_PATH: statePath,
112
+ },
113
+ cwd: root,
114
+ });
115
+ await writeFile(
116
+ catalogPath,
117
+ JSON.stringify(
118
+ {
119
+ version: "test.v2",
120
+ current: {
121
+ model: "gpt-5.5",
122
+ model_context_window: 400000,
123
+ model_reasoning_effort: "high",
124
+ plan_mode_reasoning_effort: "xhigh",
125
+ },
126
+ managedProfiles: [],
127
+ },
128
+ null,
129
+ 2,
130
+ ),
131
+ );
132
+ const second = await migrateCodexConfig({
133
+ env: {
134
+ CODEX_HOME: codexHome,
135
+ LAZYCODEX_MODEL_CATALOG_PATH: catalogPath,
136
+ LAZYCODEX_MODEL_CATALOG_STATE_PATH: statePath,
137
+ },
138
+ cwd: root,
139
+ });
140
+
141
+ const content = await readFile(join(codexHome, "config.toml"), "utf8");
142
+ assert.deepEqual(first.changed, [join(codexHome, "config.toml")]);
143
+ assert.deepEqual(second.changed, [join(codexHome, "config.toml")]);
144
+ assert.match(content, /model = "gpt-5\.5"/);
145
+ assert.match(content, /model_context_window = 400000/);
146
+ });
@@ -0,0 +1,48 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import test from "node:test";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const pluginRoot = dirname(dirname(fileURLToPath(import.meta.url)));
8
+ const repoRoot = join(pluginRoot, "..", "..", "..");
9
+
10
+ test("#given Codex Light install docs #when inspected #then lazycodex is npm-first and Bun-free", async () => {
11
+ // given
12
+ const files = [
13
+ join(repoRoot, "README.md"),
14
+ join(repoRoot, "docs", "guide", "installation.md"),
15
+ join(repoRoot, "packages", "omo-codex", "README.md"),
16
+ join(repoRoot, "packages", "omo-codex", "MARKETPLACE.md"),
17
+ join(pluginRoot, "components", "ultrawork", "README.md"),
18
+ join(pluginRoot, "components", "ulw-loop", "README.md"),
19
+ ];
20
+
21
+ // when
22
+ const docs = await Promise.all(files.map(async (path) => [path, await readFile(path, "utf8")]));
23
+
24
+ // then
25
+ for (const [path, text] of docs) {
26
+ assert.match(text, /\bnpx lazycodex-ai install\b/, `${path} should document the Node/npm install command`);
27
+ assert.doesNotMatch(text, /\bbunx lazycodex-ai\b/, `${path} should not require Bun for lazycodex`);
28
+ }
29
+ });
30
+
31
+ test("#given cleanup troubleshooting docs #when inspected #then project-local cleanup and command delegation are documented", async () => {
32
+ // given
33
+ const files = [
34
+ join(repoRoot, "README.md"),
35
+ join(repoRoot, "docs", "guide", "installation.md"),
36
+ join(repoRoot, "packages", "omo-codex", "README.md"),
37
+ ];
38
+
39
+ // when
40
+ const docs = await Promise.all(files.map(async (path) => [path, await readFile(path, "utf8")]));
41
+
42
+ // then
43
+ for (const [path, text] of docs) {
44
+ assert.match(text, /\bnpx lazycodex-ai cleanup\b/, `${path} should document the LazyCodex cleanup command`);
45
+ assert.match(text, /project-local .*\.codex\/config\.toml/i, `${path} should mention project-local config repair`);
46
+ assert.match(text, /\.codex.*\.omx|\.omx.*\.codex/s, `${path} should distinguish project-local .codex and .omx artifacts`);
47
+ }
48
+ });
@@ -0,0 +1,76 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import test from "node:test";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const root = dirname(dirname(fileURLToPath(import.meta.url)));
8
+
9
+ const SKILLS = [
10
+ "review-work",
11
+ "start-work",
12
+ "ulw-loop",
13
+ ];
14
+
15
+ const AGENT_FILES = [
16
+ "components/ultrawork/agents/codex-ultrawork-reviewer.toml",
17
+ "components/ultrawork/agents/plan.toml",
18
+ ];
19
+
20
+ test("#given orchestration skills #when inspected #then Codex subagent delegation is hardened", async () => {
21
+ // given
22
+ const skillPaths = SKILLS.map((skillName) => join("skills", skillName, "SKILL.md"));
23
+
24
+ // when
25
+ const missing = [];
26
+ for (const skillPath of skillPaths) {
27
+ const text = await readFile(join(root, skillPath), "utf8");
28
+ if (
29
+ !/TASK:/.test(text) ||
30
+ !/fork_turns:\s*"none"/.test(text) ||
31
+ !/wait_agent.*signal, not proof/s.test(text) ||
32
+ !/one targeted followup/.test(text) ||
33
+ !/respawn.*smaller/s.test(text) ||
34
+ !/model.*reasoning_effort.*default agent/s.test(text) ||
35
+ !/Plan and reviewer agents may run for a long time/.test(text) ||
36
+ !/short wait_agent cycles/.test(text) ||
37
+ !/single long blocking wait/.test(text)
38
+ ) {
39
+ missing.push(skillPath);
40
+ }
41
+ }
42
+
43
+ // then
44
+ assert.deepEqual(missing, []);
45
+ });
46
+
47
+ test("#given ultrawork directive #when inspected #then reviewer fallback keeps an agent role", async () => {
48
+ // given
49
+ const directivePath = "components/ultrawork/directive.md";
50
+
51
+ // when
52
+ const text = await readFile(join(root, directivePath), "utf8");
53
+
54
+ // then
55
+ assert.doesNotMatch(text, /any `gpt-5\.2`\s+xhigh reviewer/);
56
+ assert.match(text, /codex-ultrawork-reviewer/);
57
+ assert.match(text, /agent_type.*worker/s);
58
+ assert.match(text, /model.*reasoning_effort.*default agent/s);
59
+ });
60
+
61
+ test("#given ultrawork agents #when inspected #then inter-agent commentary is treated as assignments", async () => {
62
+ // given
63
+ const agentPaths = AGENT_FILES;
64
+
65
+ // when
66
+ const missing = [];
67
+ for (const agentPath of agentPaths) {
68
+ const text = await readFile(join(root, agentPath), "utf8");
69
+ if (!/TASK:|active review assignment/.test(text) || !/context|commentary/.test(text)) {
70
+ missing.push(agentPath);
71
+ }
72
+ }
73
+
74
+ // then
75
+ assert.deepEqual(missing, []);
76
+ });
@@ -0,0 +1,67 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import test from "node:test";
6
+
7
+ import { syncHookStatusMessages } from "../scripts/sync-hook-status-messages.mjs";
8
+
9
+ async function writeJson(path, value) {
10
+ await writeFile(path, `${JSON.stringify(value, null, "\t")}\n`);
11
+ }
12
+
13
+ async function readJson(path) {
14
+ return JSON.parse(await readFile(path, "utf8"));
15
+ }
16
+
17
+ test("#given a component without hooks #when hook status messages sync #then build-time version sync skips it", async () => {
18
+ // given
19
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-hook-status-"));
20
+ await mkdir(join(root, ".codex-plugin"), { recursive: true });
21
+ await mkdir(join(root, "hooks"), { recursive: true });
22
+ await mkdir(join(root, "components", "comment-checker", "hooks"), { recursive: true });
23
+ await mkdir(join(root, "components", "git-bash"), { recursive: true });
24
+ await mkdir(join(root, "components", "stale-build-output", "dist"), { recursive: true });
25
+ await writeJson(join(root, ".codex-plugin", "plugin.json"), { version: "0.1.0" });
26
+ await writeJson(join(root, "components", "comment-checker", "package.json"), { version: "0.1.1" });
27
+ await writeJson(join(root, "components", "git-bash", "package.json"), { version: "0.3.0" });
28
+ await writeJson(join(root, "hooks", "hooks.json"), {
29
+ hooks: {
30
+ PostToolUse: [
31
+ {
32
+ hooks: [
33
+ {
34
+ type: "command",
35
+ command: 'node "${PLUGIN_ROOT}/components/comment-checker/dist/cli.js" hook post-tool-use',
36
+ statusMessage: "LazyCodex(0.1.0): Checking Comments",
37
+ },
38
+ ],
39
+ },
40
+ ],
41
+ },
42
+ });
43
+ await writeJson(join(root, "components", "comment-checker", "hooks", "hooks.json"), {
44
+ hooks: {
45
+ PostToolUse: [
46
+ {
47
+ hooks: [
48
+ {
49
+ type: "command",
50
+ command: 'node "${PLUGIN_ROOT}/dist/cli.js" hook post-tool-use',
51
+ statusMessage: "LazyCodex(0.1.0): Checking Comments",
52
+ },
53
+ ],
54
+ },
55
+ ],
56
+ },
57
+ });
58
+
59
+ // when
60
+ await syncHookStatusMessages(root);
61
+
62
+ // then
63
+ const aggregateHooks = await readJson(join(root, "hooks", "hooks.json"));
64
+ const componentHooks = await readJson(join(root, "components", "comment-checker", "hooks", "hooks.json"));
65
+ assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "LazyCodex(0.1.1): Checking Comments");
66
+ assert.equal(componentHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "LazyCodex(0.1.1): Checking Comments");
67
+ });
@@ -7,12 +7,14 @@ import { sharedSkillsRootPath } from "@oh-my-opencode/shared-skills";
7
7
 
8
8
  const root = dirname(dirname(fileURLToPath(import.meta.url)));
9
9
  const repoRoot = join(root, "..", "..", "..");
10
+ const CONTEXT_PRESSURE_SKILL_BUDGET_BYTES = 25_000;
10
11
 
11
12
  const expectedSkills = [
12
13
  "comment-checker",
13
14
  "debugging",
14
15
  "frontend-ui-ux",
15
16
  "init-deep",
17
+ "lcx-report-bug",
16
18
  "lsp",
17
19
  "programming",
18
20
  "refactor",
@@ -32,6 +34,7 @@ const componentSkillSources = [
32
34
  ];
33
35
 
34
36
  const codexCompatibilityEndMarkers = [
37
+ "Codex full-history forks inherit the parent agent type, model, and reasoning effort, so role-specific spawns with `agent_type` must use a non-full-history fork mode such as `fork_turns=\"none\"`. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.\n\n",
35
38
  "When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.\n\n",
36
39
  "When translating `load_skills=[...]`, name the skills inside the spawned agent's `message`. If a code block below conflicts with this section, this section wins.\n\n",
37
40
  ];
@@ -152,10 +155,34 @@ test("#given synced ulw-loop skill #when Codex hint metadata is inspected #then
152
155
  assert.match(interfaceMetadata, /- "ulw-loop"/);
153
156
  });
154
157
 
158
+ test("#given synced lcx-report-bug skill #when inspected #then it files LazyCodex bug issues from proven debugging evidence", async () => {
159
+ // given
160
+ const skillRoot = join(root, "skills", "lcx-report-bug");
161
+
162
+ // when
163
+ const skill = await readFile(join(skillRoot, "SKILL.md"), "utf8");
164
+ const interfaceMetadata = await readFile(join(skillRoot, "agents", "openai.yaml"), "utf8");
165
+
166
+ // then
167
+ assert.match(skill, /^---\r?\nname: lcx-report-bug\r?\n/m);
168
+ assert.match(skill, /code-yeongyu\/lazycodex/);
169
+ assert.match(skill, /\$omo:debugging/);
170
+ assert.match(skill, /gh issue create --repo code-yeongyu\/lazycodex/);
171
+ assert.match(skill, /Browser use fallback/);
172
+ assert.match(skill, /Computer use fallback/);
173
+ assert.match(skill, /## Issue Body Template/);
174
+ assert.match(interfaceMetadata, /display_name: "lcx-report-bug \(omo\)"/);
175
+ assert.match(interfaceMetadata, /- "lazycodex bug"/);
176
+ });
177
+
155
178
  test("#given synced ulw-loop skill #when worker guidance is inspected #then context-hygiene guidance matches the source", async () => {
156
179
  // given
157
- const sourceSkill = await readFile(join(root, "components", "ulw-loop", "skills", "ulw-loop", "SKILL.md"), "utf8");
180
+ const sourceSkill = await readFile(
181
+ join(root, "components", "ulw-loop", "skills", "ulw-loop", "references", "full-workflow.md"),
182
+ "utf8",
183
+ );
158
184
  const syncedSkill = await readFile(join(root, "skills", "ulw-loop", "SKILL.md"), "utf8");
185
+ const syncedWorkflow = await readFile(join(root, "skills", "ulw-loop", "references", "full-workflow.md"), "utf8");
159
186
  const requiredPatterns = [
160
187
  ["list_agents polling guard", /list_agents/],
161
188
  ["status polling warning", /polling or status tool/],
@@ -164,13 +191,38 @@ test("#given synced ulw-loop skill #when worker guidance is inspected #then cont
164
191
  ["wait_agent completion path", /wait_agent.*completion/],
165
192
  ["targeted followups", /targeted followups only when needed/],
166
193
  ["close_agent cleanup", /close_agent.*after integrating each result/],
194
+ ["long-running plan/reviewer background guidance", /Plan and reviewer agents may run for a long time/],
195
+ ["bounded plan/reviewer polling", /short wait_agent cycles/],
196
+ ["single long wait guard", /single long blocking wait/],
167
197
  ];
168
198
 
169
199
  // when / then
170
200
  for (const [label, pattern] of requiredPatterns) {
171
201
  assert.match(sourceSkill, pattern, `source skill missing ${label}`);
172
- assert.match(syncedSkill, pattern, `synced skill missing ${label}`);
202
+ assert.match(syncedWorkflow, pattern, `synced workflow missing ${label}`);
203
+ }
204
+ assert.match(syncedSkill, /references\/full-workflow\.md/);
205
+ assert.match(syncedSkill, /wait_agent/);
206
+ assert.match(syncedSkill, /close_agent/);
207
+ });
208
+
209
+ test("#given context-pressure-prone skills #when bundled for Codex #then the eagerly loaded payload stays budgeted", async () => {
210
+ // given
211
+ const skillsRoot = join(root, "skills");
212
+ const skillNames = ["debugging", "ulw-loop"];
213
+
214
+ // when
215
+ let totalBytes = 0;
216
+ for (const skillName of skillNames) {
217
+ const content = await readFile(join(skillsRoot, skillName, "SKILL.md"), "utf8");
218
+ totalBytes += Buffer.byteLength(content, "utf8");
173
219
  }
220
+
221
+ // then
222
+ assert.ok(
223
+ totalBytes <= CONTEXT_PRESSURE_SKILL_BUDGET_BYTES,
224
+ `debugging + ulw-loop eager payload is ${totalBytes} bytes, above ${CONTEXT_PRESSURE_SKILL_BUDGET_BYTES}`,
225
+ );
174
226
  });
175
227
 
176
228
  test("#given synced aggregate Codex skills #when they contain OpenCode orchestration examples #then Codex tool compatibility guidance is injected", async () => {
@@ -6,9 +6,11 @@ import { exists, isRecord } from "./utils.mjs";
6
6
  import { COMMAND_SHIM_MARKER } from "./command-shim.mjs";
7
7
  import { removeLegacyCodexComponentBins } from "./legacy-bins.mjs";
8
8
 
9
- export async function installCachedPlugin({ codexHome, marketplaceName, name, runCommand, sourcePath, version }) {
10
- await maybeRunNpmInstall(sourcePath, runCommand);
11
- await maybeRunNpmBuild(sourcePath, runCommand);
9
+ export async function installCachedPlugin({ buildSource = true, codexHome, marketplaceName, name, runCommand, sourcePath, version }) {
10
+ if (buildSource) {
11
+ await maybeRunNpmInstall(sourcePath, runCommand);
12
+ await maybeRunNpmBuild(sourcePath, runCommand);
13
+ }
12
14
 
13
15
  const targetPath = join(codexHome, "plugins", "cache", marketplaceName, name, version);
14
16
  await replaceDirectory(sourcePath, targetPath, shouldCopyPluginPath);