oh-my-opencode 4.5.12 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/opencode-qa/SKILL.md +194 -0
- package/.agents/skills/opencode-qa/references/cli-commands.md +188 -0
- package/.agents/skills/opencode-qa/references/db-investigation.md +197 -0
- package/.agents/skills/opencode-qa/references/events-hooks.md +110 -0
- package/.agents/skills/opencode-qa/references/sdk.md +96 -0
- package/.agents/skills/opencode-qa/references/server-api.md +200 -0
- package/.agents/skills/opencode-qa/references/testing-harness.md +218 -0
- package/.agents/skills/opencode-qa/references/tui-tmux.md +52 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-id.sh +53 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-name.sh +57 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-text.sh +158 -0
- package/.agents/skills/opencode-qa/scripts/export-roundtrip.sh +57 -0
- package/.agents/skills/opencode-qa/scripts/lib/common.sh +216 -0
- package/.agents/skills/opencode-qa/scripts/server-smoke.sh +64 -0
- package/.agents/skills/opencode-qa/scripts/sse-hook-probe.sh +106 -0
- package/.agents/skills/opencode-qa/scripts/tui-smoke.sh +89 -0
- package/README.ja.md +13 -3
- package/README.ko.md +13 -3
- package/README.md +24 -14
- package/README.ru.md +13 -3
- package/README.zh-cn.md +13 -3
- package/bin/oh-my-opencode.js +4 -3
- package/bin/oh-my-opencode.test.ts +35 -7
- package/bin/platform.d.ts +1 -1
- package/bin/platform.js +4 -4
- package/bin/platform.test.ts +31 -9
- package/bin/version-mismatch.js +47 -0
- package/bin/version-mismatch.test.ts +120 -0
- package/dist/cli/cleanup-command.d.ts +4 -0
- package/dist/cli/cleanup.d.ts +11 -0
- package/dist/cli/cli-program.d.ts +2 -1
- package/dist/cli/codex-ulw-loop.d.ts +12 -0
- package/dist/cli/doctor/checks/tui-plugin-config.d.ts +2 -0
- package/dist/cli/index.js +2189 -529
- package/dist/cli/install-codex/codex-cache.d.ts +1 -0
- package/dist/cli/install-codex/codex-cleanup-config.d.ts +6 -0
- package/dist/cli/install-codex/codex-cleanup.d.ts +21 -0
- package/dist/cli/install-codex/codex-config-permissions.d.ts +1 -0
- package/dist/cli/install-codex/codex-config-reasoning.d.ts +2 -0
- package/dist/cli/install-codex/codex-config-toml.d.ts +2 -1
- package/dist/cli/install-codex/codex-installation-detection.d.ts +36 -0
- package/dist/cli/install-codex/codex-model-catalog.d.ts +13 -0
- package/dist/cli/install-codex/codex-package-layout.d.ts +1 -0
- package/dist/cli/install-codex/codex-project-local-cleanup-best-effort.d.ts +7 -0
- package/dist/cli/install-codex/codex-project-local-cleanup.d.ts +35 -0
- package/dist/cli/install-codex/git-bash.d.ts +35 -0
- package/dist/cli/install-codex/index.d.ts +4 -0
- package/dist/cli/install-codex/toml-section-editor.d.ts +2 -0
- package/dist/cli/install-codex/types.d.ts +20 -0
- package/dist/cli/run/event-state.d.ts +1 -0
- package/dist/cli/run/poll-for-completion.d.ts +1 -0
- package/dist/cli/run/prompt-start.d.ts +7 -0
- package/dist/cli/star-request.d.ts +9 -0
- package/dist/config/schema/hooks.d.ts +0 -1
- package/dist/create-hooks.d.ts +0 -1
- package/dist/features/background-agent/concurrency.d.ts +1 -0
- package/dist/features/background-agent/process-cleanup.d.ts +6 -0
- package/dist/features/builtin-skills/skills/debugging.d.ts +2 -0
- package/dist/features/builtin-skills/skills/index.d.ts +1 -0
- package/dist/features/claude-code-session-state/state.d.ts +1 -0
- package/dist/features/opencode-skill-loader/index.d.ts +1 -0
- package/dist/features/opencode-skill-loader/opencode-config-skills-reader.d.ts +5 -0
- package/dist/features/tmux-subagent/attachable-session-status.d.ts +1 -1
- package/dist/features/tmux-subagent/session-status-parser.d.ts +1 -0
- package/dist/hooks/comment-checker/cli.d.ts +1 -0
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/tasks-todowrite-disabler/constants.d.ts +1 -1
- package/dist/index.js +1077 -563
- package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
- package/dist/plugin/messages-transform.d.ts +8 -1
- package/dist/plugin/user-abort-interrupted-recovery-guard.d.ts +6 -0
- package/dist/shared/command-executor/execute-hook-command.d.ts +2 -0
- package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +14 -0
- package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +7 -0
- package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +1 -0
- package/dist/shared/prompt-async-gate/timing.d.ts +1 -0
- package/dist/shared/prompt-async-gate/types.d.ts +2 -0
- package/dist/shared/prompt-async-gate.d.ts +1 -1
- package/dist/tools/skill/description-formatter.d.ts +5 -1
- package/dist/tools/skill/types.d.ts +1 -0
- package/package.json +22 -18
- package/packages/ast-grep-mcp/dist/cli.js +53 -9
- package/packages/git-bash-mcp/dist/cli.js +367 -0
- package/packages/lsp-tools-mcp/dist/lsp/process.js +1 -1
- package/packages/omo-codex/plugin/.mcp.json +11 -0
- package/packages/omo-codex/plugin/components/comment-checker/README.md +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +29 -0
- package/packages/omo-codex/plugin/components/git-bash/package.json +23 -0
- package/packages/omo-codex/plugin/components/git-bash/src/cli.ts +33 -0
- package/packages/omo-codex/plugin/components/git-bash/src/codex-hook.ts +180 -0
- package/packages/omo-codex/plugin/components/git-bash/src/index.ts +10 -0
- package/packages/omo-codex/plugin/components/git-bash/test/codex-hook.test.ts +195 -0
- package/packages/omo-codex/plugin/components/git-bash/tsconfig.build.json +13 -0
- package/packages/omo-codex/plugin/components/git-bash/tsconfig.json +25 -0
- package/packages/omo-codex/plugin/components/lsp/README.md +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/cli.ts +5 -5
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +33 -0
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +19 -27
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-cli.test.ts +28 -0
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-errors.test.ts +55 -0
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +7 -5
- package/packages/omo-codex/plugin/components/rules/README.md +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus.md +6 -4
- package/packages/omo-codex/plugin/components/rules/bundled-rules/windows-git-bash.md +10 -0
- package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +0 -2
- package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +3 -1
- package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +97 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +6 -5
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +22 -0
- package/packages/omo-codex/plugin/components/ultrawork/CHANGELOG.md +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/README.md +3 -3
- package/packages/omo-codex/plugin/components/ultrawork/agents/codex-ultrawork-reviewer.toml +4 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +8 -7
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +9 -8
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +32 -6
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +27 -4
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +25 -0
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +28 -205
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +231 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +12 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint.test.ts +19 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +102 -5
- package/packages/omo-codex/plugin/hooks/hooks.json +35 -2
- package/packages/omo-codex/plugin/model-catalog.json +49 -0
- package/packages/omo-codex/plugin/package-lock.json +19 -0
- package/packages/omo-codex/plugin/package.json +3 -1
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +159 -0
- package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +16 -1
- package/packages/omo-codex/plugin/scripts/build-components.mjs +2 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +269 -0
- package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +89 -0
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +6 -6
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +127 -0
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +9 -0
- package/packages/omo-codex/plugin/skills/refactor/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +33 -8
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +25 -5
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +28 -205
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +231 -0
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +17 -17
- package/packages/omo-codex/plugin/test/aggregate.test.mjs +188 -20
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +129 -0
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +58 -11
- package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +34 -0
- package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +21 -0
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +146 -0
- package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +48 -0
- package/packages/omo-codex/plugin/test/subagent-guidance.test.mjs +76 -0
- package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +67 -0
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +54 -2
- package/packages/omo-codex/scripts/install/cache.mjs +5 -3
- package/packages/omo-codex/scripts/install/cli-args.mjs +112 -0
- package/packages/omo-codex/scripts/install/config.mjs +23 -1
- package/packages/omo-codex/scripts/install/delegated-command.mjs +25 -0
- package/packages/omo-codex/scripts/install/git-bash.mjs +99 -0
- package/packages/omo-codex/scripts/install/git-bash.test.mjs +174 -0
- package/packages/omo-codex/scripts/install/legacy-bins.mjs +1 -0
- package/packages/omo-codex/scripts/install/mcp-runtime-cache.mjs +5 -1
- package/packages/omo-codex/scripts/install/model-catalog.mjs +66 -0
- package/packages/omo-codex/scripts/install/multi-agent-v2-config.mjs +7 -1
- package/packages/omo-codex/scripts/install/permissions.d.mts +1 -0
- package/packages/omo-codex/scripts/install/permissions.mjs +26 -0
- package/packages/omo-codex/scripts/install/project-local-cleanup.mjs +229 -0
- package/packages/omo-codex/scripts/install/reasoning-config.mjs +72 -0
- package/packages/omo-codex/scripts/install/source-package-build.mjs +20 -0
- package/packages/omo-codex/scripts/install/toml-editor.mjs +19 -2
- package/packages/omo-codex/scripts/install-bin-links.test.mjs +23 -0
- package/packages/omo-codex/scripts/install-cli-args.test.mjs +146 -0
- package/packages/omo-codex/scripts/install-config-autonomous.test.mjs +48 -0
- package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +141 -0
- package/packages/omo-codex/scripts/install-config.test.mjs +205 -0
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +157 -0
- package/packages/omo-codex/scripts/install-local-git-bash-preflight.test.mjs +145 -0
- package/packages/omo-codex/scripts/install-local.mjs +91 -8
- package/packages/omo-codex/scripts/install-local.test.mjs +15 -0
- package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +60 -0
- package/packages/omo-codex/scripts/install-packaged-local.test.mjs +67 -0
- package/packages/omo-codex/scripts/install-project-local-cleanup.test.mjs +277 -0
- package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +127 -0
- package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +9 -0
- package/packages/shared-skills/skills/review-work/SKILL.md +33 -8
- package/packages/shared-skills/skills/start-work/SKILL.md +25 -5
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +11 -11
- package/postinstall.mjs +36 -3
- package/dist/hooks/context-window-monitor.d.ts +0 -19
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
4
|
import { homedir } from "node:os";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
|
|
7
8
|
import {
|
|
@@ -12,6 +13,10 @@ import {
|
|
|
12
13
|
} from "./install/cache.mjs";
|
|
13
14
|
import { linkCachedPluginAgents } from "./install/agents.mjs";
|
|
14
15
|
import { updateCodexConfig } from "./install/config.mjs";
|
|
16
|
+
import {
|
|
17
|
+
emptyProjectLocalCodexCleanupResult,
|
|
18
|
+
repairNearestProjectLocalCodexArtifacts,
|
|
19
|
+
} from "./install/project-local-cleanup.mjs";
|
|
15
20
|
import { trustedHookStatesForPlugin } from "./install/hook-trust.mjs";
|
|
16
21
|
import { defaultRunCommand } from "./install/process.mjs";
|
|
17
22
|
import { writeInstalledMarketplaceSnapshot } from "./install/snapshot.mjs";
|
|
@@ -21,6 +26,10 @@ import {
|
|
|
21
26
|
resolvePluginSource,
|
|
22
27
|
validatePathSegment,
|
|
23
28
|
} from "./install/marketplace.mjs";
|
|
29
|
+
import { prepareGitBashForInstall, resolveGitBashForCurrentProcess } from "./install/git-bash.mjs";
|
|
30
|
+
import { formatLazyCodexInstallHelp, parseLazyCodexInstallCliArgs } from "./install/cli-args.mjs";
|
|
31
|
+
import { runDelegatedOmoCommand } from "./install/delegated-command.mjs";
|
|
32
|
+
import { shouldBuildSourcePackages } from "./install/source-package-build.mjs";
|
|
24
33
|
|
|
25
34
|
const LEGACY_CODEX_PLUGIN_MARKETPLACE = ["code", "yeongyu", "codex", "plugins"].join("-");
|
|
26
35
|
const SISYPHUS_LEGACY_CACHE_MARKETPLACES = ["lazycodex", LEGACY_CODEX_PLUGIN_MARKETPLACE];
|
|
@@ -41,10 +50,24 @@ export async function installMarketplaceLocally(options = {}) {
|
|
|
41
50
|
const env = options.env ?? process.env;
|
|
42
51
|
const homeDir = resolve(options.homeDir ?? homedir());
|
|
43
52
|
const codexHome = resolve(options.codexHome ?? nonEmptyEnvValue(env, "CODEX_HOME") ?? join(homeDir, ".codex"));
|
|
53
|
+
const projectDirectory = resolve(options.projectDirectory ?? nonEmptyEnvValue(env, "OMO_CODEX_PROJECT") ?? process.cwd());
|
|
44
54
|
const binDir = resolve(options.binDir ?? resolveCodexInstallerBinDir({ codexHome, env, homeDir }));
|
|
45
55
|
const platform = options.platform ?? process.platform;
|
|
46
56
|
const runCommand = options.runCommand ?? defaultRunCommand;
|
|
47
57
|
const log = options.log ?? console.log;
|
|
58
|
+
const buildSource = await shouldBuildSourcePackages(repoRoot);
|
|
59
|
+
const gitBashResolution = await prepareGitBashForInstall({
|
|
60
|
+
platform,
|
|
61
|
+
env,
|
|
62
|
+
cwd: repoRoot,
|
|
63
|
+
runCommand,
|
|
64
|
+
resolveGitBash: platform === "win32"
|
|
65
|
+
? (options.gitBashResolver ?? (() => resolveGitBashForCurrentProcess({ platform, env })))
|
|
66
|
+
: undefined,
|
|
67
|
+
});
|
|
68
|
+
if (!gitBashResolution.found) {
|
|
69
|
+
throw new Error(gitBashResolution.installHint);
|
|
70
|
+
}
|
|
48
71
|
const codexPackageRoot = join(repoRoot, "packages", "omo-codex");
|
|
49
72
|
const marketplace = await readMarketplace(repoRoot, {
|
|
50
73
|
marketplacePath: join(codexPackageRoot, "marketplace.json"),
|
|
@@ -66,6 +89,7 @@ export async function installMarketplaceLocally(options = {}) {
|
|
|
66
89
|
|
|
67
90
|
log(`Building ${entry.name}@${version}`);
|
|
68
91
|
const plugin = await installCachedPlugin({
|
|
92
|
+
buildSource,
|
|
69
93
|
codexHome,
|
|
70
94
|
marketplaceName: marketplace.name,
|
|
71
95
|
name: entry.name,
|
|
@@ -120,15 +144,38 @@ export async function installMarketplaceLocally(options = {}) {
|
|
|
120
144
|
marketplaceName: marketplace.name,
|
|
121
145
|
marketplaceSource: { sourceType: "local", source: marketplaceRoot },
|
|
122
146
|
pluginNames,
|
|
147
|
+
platform,
|
|
123
148
|
trustedHookStates,
|
|
124
149
|
agentConfigs: [...agentConfigs.values()].sort((left, right) => left.name.localeCompare(right.name)),
|
|
150
|
+
autonomousPermissions: options.autonomousPermissions !== false,
|
|
125
151
|
});
|
|
152
|
+
const projectCleanup = await repairProjectLocalCodexArtifactsBestEffort({ startDirectory: projectDirectory, codexHome, log });
|
|
153
|
+
for (const configCleanup of projectCleanup.configs) {
|
|
154
|
+
if (!configCleanup.changed) continue;
|
|
155
|
+
log(`Repaired project Codex config ${configCleanup.configPath} (backup: ${configCleanup.backupPath})`);
|
|
156
|
+
}
|
|
157
|
+
for (const artifact of projectCleanup.artifacts) {
|
|
158
|
+
log(`Found project-local legacy artifact ${artifact.path}; left in place`);
|
|
159
|
+
}
|
|
126
160
|
|
|
127
161
|
for (const plugin of installed) {
|
|
128
162
|
log(`Installed ${plugin.name}@${marketplace.name} -> ${plugin.path}`);
|
|
129
163
|
}
|
|
130
164
|
|
|
131
|
-
return { marketplaceName: marketplace.name, installed };
|
|
165
|
+
return { marketplaceName: marketplace.name, installed, gitBashPath: gitBashResolution.path, projectCleanup };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function repairProjectLocalCodexArtifactsBestEffort({ startDirectory, codexHome, log }) {
|
|
169
|
+
try {
|
|
170
|
+
return await repairNearestProjectLocalCodexArtifacts({ startDirectory, codexHome });
|
|
171
|
+
} catch (error) {
|
|
172
|
+
log(`Skipped project-local Codex cleanup: ${formatUnknownError(error)}`);
|
|
173
|
+
return emptyProjectLocalCodexCleanupResult();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function formatUnknownError(error) {
|
|
178
|
+
return error instanceof Error ? error.message : String(error);
|
|
132
179
|
}
|
|
133
180
|
|
|
134
181
|
function agentNameFromToml(fileName) {
|
|
@@ -170,21 +217,57 @@ function nonEmptyEnvValue(env, key) {
|
|
|
170
217
|
const value = env[key];
|
|
171
218
|
if (typeof value !== "string") return undefined;
|
|
172
219
|
const trimmed = value.trim();
|
|
173
|
-
return trimmed.length === 0 ? undefined :
|
|
220
|
+
return trimmed.length === 0 ? undefined : trimmed;
|
|
174
221
|
}
|
|
175
222
|
|
|
176
223
|
function legacyCacheMarketplaces(marketplaceName) {
|
|
177
224
|
return marketplaceName === "sisyphuslabs" ? SISYPHUS_LEGACY_CACHE_MARKETPLACES : [];
|
|
178
225
|
}
|
|
179
226
|
|
|
227
|
+
export function resolveDefaultRepoRoot() {
|
|
228
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
229
|
+
}
|
|
230
|
+
|
|
180
231
|
async function main() {
|
|
181
|
-
const
|
|
182
|
-
|
|
232
|
+
const parsed = parseLazyCodexInstallCliArgs(process.argv.slice(2));
|
|
233
|
+
if (parsed.kind === "help") {
|
|
234
|
+
console.log(formatLazyCodexInstallHelp());
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (parsed.kind === "version") {
|
|
238
|
+
const packageJson = JSON.parse(await readFile(join(resolveDefaultRepoRoot(), "package.json"), "utf8"));
|
|
239
|
+
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
240
|
+
console.log(`lazycodex-ai ${version}`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (parsed.kind === "command") {
|
|
244
|
+
await runDelegatedOmoCommand(parsed, { cwd: process.cwd(), log: console.log, runCommand: defaultRunCommand });
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const repoRoot = parsed.repoRoot ? resolve(parsed.repoRoot) : resolveDefaultRepoRoot();
|
|
249
|
+
const result = await installMarketplaceLocally({
|
|
250
|
+
repoRoot,
|
|
251
|
+
autonomousPermissions: parsed.autonomousPermissions,
|
|
252
|
+
});
|
|
183
253
|
console.log(`Installed ${result.installed.length} plugin(s) from ${result.marketplaceName}.`);
|
|
184
254
|
}
|
|
185
255
|
|
|
186
|
-
|
|
187
|
-
|
|
256
|
+
function resolveEntrypointPath(path) {
|
|
257
|
+
try {
|
|
258
|
+
return realpathSync(resolve(path));
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (error instanceof Error) return resolve(path);
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function isEntrypointInvocation(invokedPath) {
|
|
266
|
+
if (!invokedPath) return false;
|
|
267
|
+
return resolveEntrypointPath(invokedPath) === resolveEntrypointPath(fileURLToPath(import.meta.url));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (isEntrypointInvocation(process.argv[1] ?? "")) {
|
|
188
271
|
main().catch((error) => {
|
|
189
272
|
console.error(error instanceof Error ? error.message : error);
|
|
190
273
|
process.exitCode = 1;
|
|
@@ -67,6 +67,21 @@ test("#given explicit CODEX_LOCAL_BIN_DIR #when resolving local installer bin di
|
|
|
67
67
|
);
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
test("#given CODEX_LOCAL_BIN_DIR with surrounding whitespace #when resolving local installer bin dir #then trims the env value before use", () => {
|
|
71
|
+
const homeDir = join(tmpdir(), "omo-codex-home-trim");
|
|
72
|
+
const codexHome = join(tmpdir(), "omo-codex-install-trim");
|
|
73
|
+
const explicitBinDir = join(tmpdir(), "omo-codex-trim-bin");
|
|
74
|
+
|
|
75
|
+
assert.equal(
|
|
76
|
+
resolveCodexInstallerBinDir({
|
|
77
|
+
codexHome,
|
|
78
|
+
env: { CODEX_LOCAL_BIN_DIR: ` ${explicitBinDir} ` },
|
|
79
|
+
homeDir,
|
|
80
|
+
}),
|
|
81
|
+
explicitBinDir,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
70
85
|
test("#given omo plugin source #when inspecting identity #then uses sisyphuslabs omo metadata", async () => {
|
|
71
86
|
const pluginRoot = join(scriptDir, "..", "plugin");
|
|
72
87
|
|
|
@@ -12,6 +12,7 @@ test("#given external MCP package runtime #when installing cached plugin #then r
|
|
|
12
12
|
const codexHome = await makeTempDir();
|
|
13
13
|
const sourceRoot = join(repoRoot, "packages", "omo-codex", "plugin");
|
|
14
14
|
const astGrepPackageRoot = join(repoRoot, "packages", "ast-grep-mcp");
|
|
15
|
+
const gitBashPackageRoot = join(repoRoot, "packages", "git-bash-mcp");
|
|
15
16
|
const lspPackageRoot = join(repoRoot, "packages", "lsp-tools-mcp");
|
|
16
17
|
|
|
17
18
|
await writeJson(join(astGrepPackageRoot, "package.json"), {
|
|
@@ -26,6 +27,12 @@ test("#given external MCP package runtime #when installing cached plugin #then r
|
|
|
26
27
|
type: "module",
|
|
27
28
|
bin: { "omo-lsp": "./dist/cli.js" },
|
|
28
29
|
});
|
|
30
|
+
await writeJson(join(gitBashPackageRoot, "package.json"), {
|
|
31
|
+
name: "@example/git-bash-mcp",
|
|
32
|
+
version: "0.1.0",
|
|
33
|
+
type: "module",
|
|
34
|
+
bin: { "omo-git-bash": "./dist/cli.js" },
|
|
35
|
+
});
|
|
29
36
|
await writeJson(join(sourceRoot, "package.json"), {
|
|
30
37
|
name: "@example/omo",
|
|
31
38
|
version: "0.1.0",
|
|
@@ -37,6 +44,11 @@ test("#given external MCP package runtime #when installing cached plugin #then r
|
|
|
37
44
|
args: ["../../ast-grep-mcp/dist/cli.js", "mcp"],
|
|
38
45
|
cwd: ".",
|
|
39
46
|
},
|
|
47
|
+
git_bash: {
|
|
48
|
+
command: "node",
|
|
49
|
+
args: ["../../git-bash-mcp/dist/cli.js", "mcp"],
|
|
50
|
+
cwd: ".",
|
|
51
|
+
},
|
|
40
52
|
lsp: {
|
|
41
53
|
command: "node",
|
|
42
54
|
args: ["../../lsp-tools-mcp/dist/cli.js", "mcp"],
|
|
@@ -45,6 +57,7 @@ test("#given external MCP package runtime #when installing cached plugin #then r
|
|
|
45
57
|
},
|
|
46
58
|
});
|
|
47
59
|
await writeJson(join(astGrepPackageRoot, "dist", "cli.js"), { executable: true });
|
|
60
|
+
await writeJson(join(gitBashPackageRoot, "dist", "cli.js"), { executable: true });
|
|
48
61
|
await writeJson(join(lspPackageRoot, "dist", "cli.js"), { executable: true });
|
|
49
62
|
await writeJson(join(lspPackageRoot, "dist", "lsp", "manager.js"), { copied: true });
|
|
50
63
|
|
|
@@ -59,13 +72,17 @@ test("#given external MCP package runtime #when installing cached plugin #then r
|
|
|
59
72
|
|
|
60
73
|
const cachedMcp = JSON.parse(await readFile(join(result.path, ".mcp.json"), "utf8"));
|
|
61
74
|
const copiedAstGrepCli = join(result.path, "mcp", "ast_grep", "dist", "cli.js");
|
|
75
|
+
const copiedGitBashCli = join(result.path, "mcp", "git_bash", "dist", "cli.js");
|
|
62
76
|
const copiedCli = join(result.path, "mcp", "lsp", "dist", "cli.js");
|
|
63
77
|
|
|
64
78
|
assert.deepEqual(cachedMcp.mcpServers.ast_grep.args, [copiedAstGrepCli, "mcp"]);
|
|
79
|
+
assert.deepEqual(cachedMcp.mcpServers.git_bash.args, [copiedGitBashCli, "mcp"]);
|
|
65
80
|
assert.deepEqual(cachedMcp.mcpServers.lsp.args, [copiedCli, "mcp"]);
|
|
66
81
|
assert.equal(Object.hasOwn(cachedMcp.mcpServers.ast_grep, "cwd"), false);
|
|
82
|
+
assert.equal(Object.hasOwn(cachedMcp.mcpServers.git_bash, "cwd"), false);
|
|
67
83
|
assert.equal(Object.hasOwn(cachedMcp.mcpServers.lsp, "cwd"), false);
|
|
68
84
|
assert.equal((await stat(copiedAstGrepCli)).isFile(), true);
|
|
85
|
+
assert.equal((await stat(copiedGitBashCli)).isFile(), true);
|
|
69
86
|
assert.equal((await stat(copiedCli)).isFile(), true);
|
|
70
87
|
assert.equal((await stat(join(result.path, "mcp", "lsp", "dist", "lsp", "manager.js"))).isFile(), true);
|
|
71
88
|
});
|
|
@@ -171,3 +188,46 @@ test("#given structurally valid external MCP package without mcp suffix #when in
|
|
|
171
188
|
assert.deepEqual(cachedMcp.mcpServers.language_tools.args, [copiedCli, "mcp", join(sourceRoot, "..", "local-config.json")]);
|
|
172
189
|
assert.equal((await stat(copiedCli)).isFile(), true);
|
|
173
190
|
});
|
|
191
|
+
|
|
192
|
+
test("#given packaged external MCP runtime has only dist files #when installing cached plugin #then runtime is copied into the plugin cache", async () => {
|
|
193
|
+
// given
|
|
194
|
+
const repoRoot = await makeTempDir();
|
|
195
|
+
const codexHome = await makeTempDir();
|
|
196
|
+
const sourceRoot = join(repoRoot, "packages", "omo-codex", "plugin");
|
|
197
|
+
const lspPackageRoot = join(repoRoot, "packages", "lsp-tools-mcp");
|
|
198
|
+
|
|
199
|
+
await writeJson(join(sourceRoot, "package.json"), {
|
|
200
|
+
name: "@example/omo",
|
|
201
|
+
version: "0.1.0",
|
|
202
|
+
});
|
|
203
|
+
await writeJson(join(sourceRoot, ".mcp.json"), {
|
|
204
|
+
mcpServers: {
|
|
205
|
+
lsp: {
|
|
206
|
+
command: "node",
|
|
207
|
+
args: ["../../lsp-tools-mcp/dist/cli.js", "mcp"],
|
|
208
|
+
cwd: ".",
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
await writeJson(join(lspPackageRoot, "dist", "cli.js"), { executable: true });
|
|
213
|
+
await writeJson(join(lspPackageRoot, "dist", "lsp", "manager.js"), { copied: true });
|
|
214
|
+
|
|
215
|
+
// when
|
|
216
|
+
const result = await installCachedPlugin({
|
|
217
|
+
codexHome,
|
|
218
|
+
marketplaceName: "sisyphuslabs",
|
|
219
|
+
name: "omo",
|
|
220
|
+
runCommand: async () => {},
|
|
221
|
+
sourcePath: sourceRoot,
|
|
222
|
+
version: "0.1.0",
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// then
|
|
226
|
+
const cachedMcp = JSON.parse(await readFile(join(result.path, ".mcp.json"), "utf8"));
|
|
227
|
+
const copiedCli = join(result.path, "mcp", "lsp", "dist", "cli.js");
|
|
228
|
+
assert.deepEqual(cachedMcp.mcpServers.lsp.args, [copiedCli, "mcp"]);
|
|
229
|
+
assert.equal(Object.hasOwn(cachedMcp.mcpServers.lsp, "cwd"), false);
|
|
230
|
+
assert.equal((await stat(copiedCli)).isFile(), true);
|
|
231
|
+
assert.equal((await stat(join(result.path, "mcp", "lsp", "dist", "lsp", "manager.js"))).isFile(), true);
|
|
232
|
+
assert.notEqual(cachedMcp.mcpServers.lsp.args[0], join(lspPackageRoot, "dist", "cli.js"));
|
|
233
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
|
|
6
|
+
import { installMarketplaceLocally } from "./install-local.mjs";
|
|
7
|
+
import { makeTempDir, writeJson, writePluginAt } from "./install-test-fixtures.mjs";
|
|
8
|
+
|
|
9
|
+
test("#given packaged lazycodex adapter #when installing locally #then uses bundled artifacts without source builds", async () => {
|
|
10
|
+
// given
|
|
11
|
+
const repoRoot = await makeTempDir();
|
|
12
|
+
const codexHome = await makeTempDir();
|
|
13
|
+
const binDir = await makeTempDir();
|
|
14
|
+
const codexPackageRoot = join(repoRoot, "packages", "omo-codex");
|
|
15
|
+
const pluginRoot = join(codexPackageRoot, "plugin");
|
|
16
|
+
const lspRuntimeRoot = join(repoRoot, "packages", "lsp-tools-mcp");
|
|
17
|
+
|
|
18
|
+
await writeJson(join(repoRoot, "package.json"), {
|
|
19
|
+
name: "lazycodex-ai",
|
|
20
|
+
version: "0.1.2",
|
|
21
|
+
});
|
|
22
|
+
await writeJson(join(codexPackageRoot, "marketplace.json"), {
|
|
23
|
+
name: "sisyphuslabs",
|
|
24
|
+
plugins: [{ name: "omo", source: "./plugins/omo" }],
|
|
25
|
+
});
|
|
26
|
+
await writePluginAt(pluginRoot, "omo", "0.1.0");
|
|
27
|
+
await writeJson(join(pluginRoot, ".mcp.json"), {
|
|
28
|
+
mcpServers: {
|
|
29
|
+
lsp: {
|
|
30
|
+
command: "node",
|
|
31
|
+
args: ["../../lsp-tools-mcp/dist/cli.js", "mcp"],
|
|
32
|
+
cwd: ".",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
await mkdir(join(pluginRoot, "dist"), { recursive: true });
|
|
37
|
+
await writeFile(join(pluginRoot, "dist", "cli.js"), "#!/usr/bin/env node\nconsole.log('prebuilt')\n");
|
|
38
|
+
await writeJson(join(lspRuntimeRoot, "dist", "cli.js"), { executable: true });
|
|
39
|
+
|
|
40
|
+
const commands = [];
|
|
41
|
+
|
|
42
|
+
// when
|
|
43
|
+
const result = await installMarketplaceLocally({
|
|
44
|
+
repoRoot,
|
|
45
|
+
codexHome,
|
|
46
|
+
binDir,
|
|
47
|
+
platform: "linux",
|
|
48
|
+
runCommand: async (command, args, options) => {
|
|
49
|
+
commands.push([command, args.join(" "), options.cwd]);
|
|
50
|
+
},
|
|
51
|
+
log: () => {},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// then
|
|
55
|
+
const pluginCacheRoot = join(codexHome, "plugins", "cache", "sisyphuslabs", "omo", "0.1.0");
|
|
56
|
+
const cachedMcp = JSON.parse(await readFile(join(pluginCacheRoot, ".mcp.json"), "utf8"));
|
|
57
|
+
const cachedLspCli = join(pluginCacheRoot, "mcp", "lsp", "dist", "cli.js");
|
|
58
|
+
|
|
59
|
+
assert.deepEqual(result.installed.map((plugin) => `${plugin.name}@${plugin.version}`), ["omo@0.1.0"]);
|
|
60
|
+
assert.deepEqual(
|
|
61
|
+
commands.map(([command, args, cwd]) => [command, args, cwd]),
|
|
62
|
+
[["npm", "install --omit=dev", pluginCacheRoot]],
|
|
63
|
+
);
|
|
64
|
+
assert.deepEqual(cachedMcp.mcpServers.lsp.args, [cachedLspCli, "mcp"]);
|
|
65
|
+
assert.equal((await stat(cachedLspCli)).isFile(), true);
|
|
66
|
+
assert.notEqual(cachedMcp.mcpServers.lsp.args[0], join(lspRuntimeRoot, "dist", "cli.js"));
|
|
67
|
+
});
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { lstat, mkdir, readFile, symlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
|
|
6
|
+
import { installMarketplaceLocally } from "./install-local.mjs";
|
|
7
|
+
import { repairNearestProjectLocalCodexArtifacts } from "./install/project-local-cleanup.mjs";
|
|
8
|
+
import { makeTempDir, writeJson, writePluginAt } from "./install-test-fixtures.mjs";
|
|
9
|
+
|
|
10
|
+
test("#given stale project-local Codex config #when Node installer runs #then repairs the local conflict", async () => {
|
|
11
|
+
const repoRoot = await makeTempDir();
|
|
12
|
+
const codexHome = await makeTempDir();
|
|
13
|
+
const binDir = await makeTempDir();
|
|
14
|
+
const projectRoot = await makeTempDir();
|
|
15
|
+
const projectDirectory = join(projectRoot, "nested");
|
|
16
|
+
const codexPackageRoot = join(repoRoot, "packages", "omo-codex");
|
|
17
|
+
const pluginRoot = join(codexPackageRoot, "plugin");
|
|
18
|
+
const projectConfigPath = join(projectRoot, ".codex", "config.toml");
|
|
19
|
+
|
|
20
|
+
await mkdir(projectDirectory, { recursive: true });
|
|
21
|
+
await mkdir(join(projectRoot, ".git"), { recursive: true });
|
|
22
|
+
await mkdir(join(projectRoot, ".codex"), { recursive: true });
|
|
23
|
+
await writeJson(join(codexPackageRoot, "marketplace.json"), {
|
|
24
|
+
name: "debug-marketplace",
|
|
25
|
+
plugins: [{ name: "alpha", source: "./plugin" }],
|
|
26
|
+
});
|
|
27
|
+
await writePluginAt(pluginRoot, "alpha", "1.2.3");
|
|
28
|
+
await writeFile(
|
|
29
|
+
projectConfigPath,
|
|
30
|
+
[
|
|
31
|
+
"[features.multi_agent_v2]",
|
|
32
|
+
"enabled = true",
|
|
33
|
+
"",
|
|
34
|
+
"[agents]",
|
|
35
|
+
" max_threads = 10",
|
|
36
|
+
"max_depth = 4",
|
|
37
|
+
"",
|
|
38
|
+
].join("\n"),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const result = await installMarketplaceLocally({
|
|
42
|
+
repoRoot,
|
|
43
|
+
codexHome,
|
|
44
|
+
binDir,
|
|
45
|
+
projectDirectory,
|
|
46
|
+
platform: "linux",
|
|
47
|
+
runCommand: async (command, args, options) => {
|
|
48
|
+
if (command === "npm" && args.join(" ") === "run build") {
|
|
49
|
+
await mkdir(join(options.cwd, "dist"), { recursive: true });
|
|
50
|
+
await writeFile(join(options.cwd, "dist", "cli.js"), "#!/usr/bin/env node\n");
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
log: () => {},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
assert.equal(result.projectCleanup.configPath, projectConfigPath);
|
|
57
|
+
assert.equal(result.projectCleanup.changed, true);
|
|
58
|
+
assert.deepEqual(result.projectCleanup.removedKeys, ["max_threads"]);
|
|
59
|
+
assert.equal(result.projectCleanup.configs.length, 1);
|
|
60
|
+
assert.match(await readFile(result.projectCleanup.backupPath, "utf8"), /max_threads = 10/);
|
|
61
|
+
const content = await readFile(projectConfigPath, "utf8");
|
|
62
|
+
assert.doesNotMatch(content, /^\s*max_threads\s*=/m);
|
|
63
|
+
assert.match(content, /max_depth = 4/);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("#given root and nested project-local Codex configs #when script cleanup runs #then it repairs every loaded project layer", async () => {
|
|
67
|
+
const projectRoot = await makeTempDir();
|
|
68
|
+
const projectDirectory = join(projectRoot, "nested");
|
|
69
|
+
const rootConfigPath = join(projectRoot, ".codex", "config.toml");
|
|
70
|
+
const nestedConfigPath = join(projectDirectory, ".codex", "config.toml");
|
|
71
|
+
|
|
72
|
+
await mkdir(join(projectRoot, ".git"), { recursive: true });
|
|
73
|
+
await mkdir(join(projectRoot, ".codex"), { recursive: true });
|
|
74
|
+
await mkdir(join(projectDirectory, ".codex"), { recursive: true });
|
|
75
|
+
await mkdir(join(projectDirectory, ".omx"), { recursive: true });
|
|
76
|
+
await writeFile(join(projectDirectory, ".codex", "hooks.json"), "{}\n");
|
|
77
|
+
await writeFile(
|
|
78
|
+
rootConfigPath,
|
|
79
|
+
[
|
|
80
|
+
"[features.multi_agent_v2]",
|
|
81
|
+
"enabled = true",
|
|
82
|
+
"",
|
|
83
|
+
"[agents]",
|
|
84
|
+
"max_threads = 10",
|
|
85
|
+
"max_depth = 4",
|
|
86
|
+
"",
|
|
87
|
+
].join("\n"),
|
|
88
|
+
);
|
|
89
|
+
await writeFile(
|
|
90
|
+
nestedConfigPath,
|
|
91
|
+
[
|
|
92
|
+
"[features.multi_agent_v2]",
|
|
93
|
+
"enabled = true",
|
|
94
|
+
"",
|
|
95
|
+
"[agents]",
|
|
96
|
+
"job_max_runtime_seconds = 7200",
|
|
97
|
+
"",
|
|
98
|
+
].join("\n"),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const result = await repairNearestProjectLocalCodexArtifacts({
|
|
102
|
+
startDirectory: projectDirectory,
|
|
103
|
+
now: () => new Date("2026-06-01T01:02:03.004Z"),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
assert.equal(result.projectRoot, projectRoot);
|
|
107
|
+
assert.equal(result.changed, true);
|
|
108
|
+
assert.equal(result.configPath, rootConfigPath);
|
|
109
|
+
assert.deepEqual(
|
|
110
|
+
result.configs.map((config) => config.configPath),
|
|
111
|
+
[rootConfigPath, nestedConfigPath],
|
|
112
|
+
);
|
|
113
|
+
assert.deepEqual(
|
|
114
|
+
result.configs.map((config) => config.changed),
|
|
115
|
+
[true, false],
|
|
116
|
+
);
|
|
117
|
+
assert.equal(result.backupPath, `${rootConfigPath}.backup-2026-06-01T01-02-03-004Z`);
|
|
118
|
+
const rootContent = await readFile(rootConfigPath, "utf8");
|
|
119
|
+
const nestedContent = await readFile(nestedConfigPath, "utf8");
|
|
120
|
+
assert.doesNotMatch(rootContent, /^max_threads\s*=/m);
|
|
121
|
+
assert.match(rootContent, /max_depth = 4/);
|
|
122
|
+
assert.match(nestedContent, /job_max_runtime_seconds = 7200/);
|
|
123
|
+
assert.deepEqual(
|
|
124
|
+
result.artifacts.map((artifact) => artifact.path).sort(),
|
|
125
|
+
[join(projectDirectory, ".codex", "hooks.json"), join(projectDirectory, ".omx")],
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("#given no project-local config and CODEX_HOME in the parent chain #when script cleanup runs #then global Codex config is not treated as project state", async () => {
|
|
130
|
+
const homeRoot = await makeTempDir();
|
|
131
|
+
const codexHome = join(homeRoot, ".codex");
|
|
132
|
+
const projectDirectory = join(homeRoot, "workspace", "nested");
|
|
133
|
+
const globalConfigPath = join(codexHome, "config.toml");
|
|
134
|
+
|
|
135
|
+
await mkdir(projectDirectory, { recursive: true });
|
|
136
|
+
await mkdir(codexHome, { recursive: true });
|
|
137
|
+
await writeFile(
|
|
138
|
+
globalConfigPath,
|
|
139
|
+
[
|
|
140
|
+
"[features.multi_agent_v2]",
|
|
141
|
+
"enabled = true",
|
|
142
|
+
"",
|
|
143
|
+
"[agents]",
|
|
144
|
+
"max_threads = 10",
|
|
145
|
+
"max_depth = 4",
|
|
146
|
+
"",
|
|
147
|
+
].join("\n"),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const result = await repairNearestProjectLocalCodexArtifacts({ startDirectory: projectDirectory, codexHome });
|
|
151
|
+
|
|
152
|
+
assert.equal(result.configPath, null);
|
|
153
|
+
assert.equal(result.changed, false);
|
|
154
|
+
const content = await readFile(globalConfigPath, "utf8");
|
|
155
|
+
assert.match(content, /max_threads = 10/);
|
|
156
|
+
assert.match(content, /max_depth = 4/);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("#given project-local config is a symlink to CODEX_HOME #when script cleanup runs #then it skips the link without mutating the target", async () => {
|
|
160
|
+
if (process.platform === "win32") return;
|
|
161
|
+
|
|
162
|
+
const codexHome = await makeTempDir();
|
|
163
|
+
const projectRoot = await makeTempDir();
|
|
164
|
+
const globalConfigPath = join(codexHome, "config.toml");
|
|
165
|
+
const projectConfigPath = join(projectRoot, ".codex", "config.toml");
|
|
166
|
+
|
|
167
|
+
await mkdir(join(projectRoot, ".git"), { recursive: true });
|
|
168
|
+
await mkdir(join(projectRoot, ".codex"), { recursive: true });
|
|
169
|
+
await writeFile(
|
|
170
|
+
globalConfigPath,
|
|
171
|
+
[
|
|
172
|
+
"[features.multi_agent_v2]",
|
|
173
|
+
"enabled = true",
|
|
174
|
+
"",
|
|
175
|
+
"[agents]",
|
|
176
|
+
"max_threads = 10",
|
|
177
|
+
"max_depth = 4",
|
|
178
|
+
"",
|
|
179
|
+
].join("\n"),
|
|
180
|
+
);
|
|
181
|
+
await symlink(globalConfigPath, projectConfigPath);
|
|
182
|
+
|
|
183
|
+
const result = await repairNearestProjectLocalCodexArtifacts({
|
|
184
|
+
startDirectory: projectRoot,
|
|
185
|
+
codexHome,
|
|
186
|
+
now: () => new Date("2026-06-01T00:00:00Z"),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
assert.equal(result.configPath, null);
|
|
190
|
+
assert.equal(result.changed, false);
|
|
191
|
+
assert.equal(await pathExists(`${projectConfigPath}.backup-2026-06-01T00-00-00-000Z`), false);
|
|
192
|
+
const content = await readFile(globalConfigPath, "utf8");
|
|
193
|
+
assert.match(content, /max_threads = 10/);
|
|
194
|
+
assert.match(content, /max_depth = 4/);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("#given malformed project directory from the environment #when script cleanup runs #then it skips project-local cleanup without failing install", async () => {
|
|
198
|
+
const codexHome = await makeTempDir();
|
|
199
|
+
|
|
200
|
+
const result = await repairNearestProjectLocalCodexArtifacts({
|
|
201
|
+
startDirectory: `bad${"\0"}path`,
|
|
202
|
+
codexHome,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
assert.deepEqual(result, {
|
|
206
|
+
projectRoot: null,
|
|
207
|
+
configPath: null,
|
|
208
|
+
changed: false,
|
|
209
|
+
removedKeys: [],
|
|
210
|
+
configs: [],
|
|
211
|
+
artifacts: [],
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("#given absent project directory from the script surface #when script cleanup runs #then it skips project-local cleanup without throwing", async () => {
|
|
216
|
+
const codexHome = await makeTempDir();
|
|
217
|
+
|
|
218
|
+
const result = await repairNearestProjectLocalCodexArtifacts({ codexHome });
|
|
219
|
+
|
|
220
|
+
assert.deepEqual(result, {
|
|
221
|
+
projectRoot: null,
|
|
222
|
+
configPath: null,
|
|
223
|
+
changed: false,
|
|
224
|
+
removedKeys: [],
|
|
225
|
+
configs: [],
|
|
226
|
+
artifacts: [],
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("#given project cleanup hits a filesystem edge #when Node installer runs #then install succeeds and reports skipped cleanup", async () => {
|
|
231
|
+
const repoRoot = await makeTempDir();
|
|
232
|
+
const codexHome = await makeTempDir();
|
|
233
|
+
const binDir = await makeTempDir();
|
|
234
|
+
const projectRoot = await makeTempDir();
|
|
235
|
+
const projectDirectory = join(projectRoot, "not-a-directory");
|
|
236
|
+
const codexPackageRoot = join(repoRoot, "packages", "omo-codex");
|
|
237
|
+
const pluginRoot = join(codexPackageRoot, "plugin");
|
|
238
|
+
const logs = [];
|
|
239
|
+
|
|
240
|
+
await writeJson(join(codexPackageRoot, "marketplace.json"), {
|
|
241
|
+
name: "debug-marketplace",
|
|
242
|
+
plugins: [{ name: "alpha", source: "./plugin" }],
|
|
243
|
+
});
|
|
244
|
+
await writePluginAt(pluginRoot, "alpha", "1.2.3");
|
|
245
|
+
await writeFile(projectDirectory, "file, not directory\n");
|
|
246
|
+
|
|
247
|
+
const result = await installMarketplaceLocally({
|
|
248
|
+
repoRoot,
|
|
249
|
+
codexHome,
|
|
250
|
+
binDir,
|
|
251
|
+
projectDirectory,
|
|
252
|
+
platform: "linux",
|
|
253
|
+
runCommand: async (command, args, options) => {
|
|
254
|
+
if (command === "npm" && args.join(" ") === "run build") {
|
|
255
|
+
await mkdir(join(options.cwd, "dist"), { recursive: true });
|
|
256
|
+
await writeFile(join(options.cwd, "dist", "cli.js"), "#!/usr/bin/env node\n");
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
log: (message) => logs.push(message),
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
assert.equal(result.projectCleanup.projectRoot, null);
|
|
263
|
+
assert.equal(result.projectCleanup.changed, false);
|
|
264
|
+
assert.equal(logs.some((message) => message.includes("Skipped project-local Codex cleanup")), true);
|
|
265
|
+
assert.equal(logs.some((message) => message.includes("not a directory")), true);
|
|
266
|
+
assert.equal(await pathExists(join(codexHome, "config.toml")), true);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
async function pathExists(path) {
|
|
270
|
+
try {
|
|
271
|
+
await lstat(path);
|
|
272
|
+
return true;
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|