oh-my-opencode 4.5.12 → 4.6.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/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/index.js +1837 -450
- 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-mcp.d.ts +1 -0
- package/dist/cli/install-codex/codex-config-permissions.d.ts +1 -0
- package/dist/cli/install-codex/codex-config-reasoning.d.ts +1 -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-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/builtin-skills/skills/debugging.d.ts +2 -0
- package/dist/features/builtin-skills/skills/index.d.ts +1 -0
- package/dist/hooks/index.d.ts +0 -1
- package/dist/index.js +267 -114
- 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/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/package.json +22 -17
- package/packages/git-bash-mcp/dist/cli.js +367 -0
- 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/windows-git-bash.md +10 -0
- 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 +5 -4
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +22 -0
- package/packages/omo-codex/plugin/components/ultrawork/README.md +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/agents/codex-ultrawork-reviewer.toml +1 -0
- package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +8 -7
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +2 -1
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +31 -5
- 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 +27 -205
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +230 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +102 -5
- package/packages/omo-codex/plugin/hooks/hooks.json +24 -2
- 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/build-bundled-mcp-runtimes.mjs +16 -1
- package/packages/omo-codex/plugin/scripts/build-components.mjs +2 -1
- package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +87 -0
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +27 -2
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +20 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +27 -205
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +230 -0
- package/packages/omo-codex/plugin/test/aggregate.test.mjs +23 -8
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +56 -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/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 +66 -0
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +32 -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 +36 -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/mcp-runtime-cache.mjs +5 -1
- 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 +14 -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-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 +62 -0
- package/packages/omo-codex/scripts/install-config.test.mjs +206 -0
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +129 -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/review-work/SKILL.md +27 -2
- package/packages/shared-skills/skills/start-work/SKILL.md +20 -0
- package/dist/hooks/context-window-monitor.d.ts +0 -19
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { installMarketplaceLocally } from "./install-local.mjs";
|
|
8
|
+
|
|
9
|
+
const windowsGitBashPath = "C:\\Program Files\\Git\\bin\\bash.exe";
|
|
10
|
+
const lspCliPath = join(process.cwd(), "packages", "lsp-tools-mcp", "dist", "cli.js");
|
|
11
|
+
|
|
12
|
+
async function withBundledLspRuntimeForTest(run) {
|
|
13
|
+
try {
|
|
14
|
+
await stat(lspCliPath);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (!(error instanceof Error)) throw error;
|
|
17
|
+
await mkdir(join(process.cwd(), "packages", "lsp-tools-mcp", "dist"), { recursive: true });
|
|
18
|
+
await writeFile(lspCliPath, "#!/usr/bin/env node\n");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return run();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test("#given Windows without Git Bash and auto install skip env #when installing local marketplace #then rejects before marketplace or config mutation", async () => {
|
|
25
|
+
const repoRoot = await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-missing-repo-"));
|
|
26
|
+
const codexHome = await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-missing-home-"));
|
|
27
|
+
const commands = [];
|
|
28
|
+
|
|
29
|
+
await assert.rejects(
|
|
30
|
+
installMarketplaceLocally({
|
|
31
|
+
repoRoot,
|
|
32
|
+
codexHome,
|
|
33
|
+
platform: "win32",
|
|
34
|
+
env: { OMO_CODEX_SKIP_GIT_BASH_AUTO_INSTALL: "1" },
|
|
35
|
+
gitBashResolver: () => ({
|
|
36
|
+
found: false,
|
|
37
|
+
checkedPaths: [windowsGitBashPath],
|
|
38
|
+
installHint: [
|
|
39
|
+
"Git Bash is required.",
|
|
40
|
+
"winget install --id Git.Git -e --source winget",
|
|
41
|
+
"OMO_CODEX_GIT_BASH_PATH=C:\\path\\to\\bash.exe",
|
|
42
|
+
"rerun `npx lazycodex-ai install`",
|
|
43
|
+
].join("\n"),
|
|
44
|
+
}),
|
|
45
|
+
runCommand: async (command, args, options) => {
|
|
46
|
+
commands.push([command, ...args, options.cwd].join(" "));
|
|
47
|
+
},
|
|
48
|
+
log: () => {},
|
|
49
|
+
}),
|
|
50
|
+
/winget install --id Git\.Git -e --source winget/,
|
|
51
|
+
);
|
|
52
|
+
assert.deepEqual(commands, []);
|
|
53
|
+
await assert.rejects(stat(join(codexHome, "config.toml")), /ENOENT/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("#given Windows without Git Bash #when winget succeeds and resolver recovers #then install continues", async () => {
|
|
57
|
+
const runCalls = [];
|
|
58
|
+
const resolutions = [
|
|
59
|
+
{ found: false, checkedPaths: [windowsGitBashPath], installHint: "install hint before winget" },
|
|
60
|
+
{ found: true, path: windowsGitBashPath, source: "program-files" },
|
|
61
|
+
];
|
|
62
|
+
let resolveCallCount = 0;
|
|
63
|
+
|
|
64
|
+
const result = await withBundledLspRuntimeForTest(async () => installMarketplaceLocally({
|
|
65
|
+
repoRoot: process.cwd(),
|
|
66
|
+
codexHome: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-auto-home-")),
|
|
67
|
+
binDir: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-auto-bin-")),
|
|
68
|
+
platform: "win32",
|
|
69
|
+
gitBashResolver: () => resolutions[resolveCallCount++] ?? resolutions[resolutions.length - 1],
|
|
70
|
+
runCommand: async (command, args, options) => {
|
|
71
|
+
runCalls.push([command, ...args, options.cwd].join(" "));
|
|
72
|
+
},
|
|
73
|
+
log: () => {},
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
assert.equal(resolveCallCount, 2);
|
|
77
|
+
assert.match(runCalls.join("\n"), /^winget install --id Git\.Git -e --source winget /m);
|
|
78
|
+
assert.equal(result.gitBashPath, windowsGitBashPath);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("#given non-Windows install #when running installer #then winget is never called", async () => {
|
|
82
|
+
const runCalls = [];
|
|
83
|
+
const result = await withBundledLspRuntimeForTest(async () => installMarketplaceLocally({
|
|
84
|
+
repoRoot: process.cwd(),
|
|
85
|
+
codexHome: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-no-winget-home-")),
|
|
86
|
+
binDir: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-no-winget-bin-")),
|
|
87
|
+
platform: "linux",
|
|
88
|
+
runCommand: async (command, args, options) => {
|
|
89
|
+
runCalls.push([command, ...args, options.cwd].join(" "));
|
|
90
|
+
},
|
|
91
|
+
log: () => {},
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
assert.equal(result.gitBashPath, null);
|
|
95
|
+
assert.equal(runCalls.some((command) => command.startsWith("winget ")), false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("#given Windows env override resolves Git Bash #when installing local marketplace #then install continues", async () => {
|
|
99
|
+
const result = await withBundledLspRuntimeForTest(async () => installMarketplaceLocally({
|
|
100
|
+
repoRoot: process.cwd(),
|
|
101
|
+
codexHome: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-home-")),
|
|
102
|
+
binDir: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-bin-")),
|
|
103
|
+
platform: "win32",
|
|
104
|
+
gitBashResolver: () => ({ found: true, path: windowsGitBashPath, source: "env" }),
|
|
105
|
+
runCommand: async () => {},
|
|
106
|
+
log: () => {},
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
assert.equal(result.gitBashPath, windowsGitBashPath);
|
|
110
|
+
assert.equal(result.installed.length, 1);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("#given Windows env override in installer options #when no custom resolver is provided #then default resolver uses it", async () => {
|
|
114
|
+
const codexHome = await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-env-home-"));
|
|
115
|
+
const gitBashPath = join(await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-env-")), "bash.exe");
|
|
116
|
+
await writeFile(gitBashPath, "");
|
|
117
|
+
|
|
118
|
+
const result = await withBundledLspRuntimeForTest(async () => installMarketplaceLocally({
|
|
119
|
+
repoRoot: process.cwd(),
|
|
120
|
+
codexHome,
|
|
121
|
+
binDir: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-env-bin-")),
|
|
122
|
+
platform: "win32",
|
|
123
|
+
env: { OMO_CODEX_GIT_BASH_PATH: gitBashPath },
|
|
124
|
+
runCommand: async () => {},
|
|
125
|
+
log: () => {},
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
assert.equal(result.gitBashPath, gitBashPath);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("#given non-Windows local install #when resolver would fail #then installer keeps existing behavior", async () => {
|
|
132
|
+
const codexHome = await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-linux-home-"));
|
|
133
|
+
const result = await withBundledLspRuntimeForTest(async () => installMarketplaceLocally({
|
|
134
|
+
repoRoot: process.cwd(),
|
|
135
|
+
codexHome,
|
|
136
|
+
binDir: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-linux-bin-")),
|
|
137
|
+
platform: "linux",
|
|
138
|
+
gitBashResolver: () => ({ found: false, checkedPaths: [windowsGitBashPath], installHint: "should not be used" }),
|
|
139
|
+
runCommand: async () => {},
|
|
140
|
+
log: () => {},
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
assert.equal(result.gitBashPath, null);
|
|
144
|
+
assert.match(await readFile(join(codexHome, "config.toml"), "utf8"), /\[marketplaces\.sisyphuslabs\]/);
|
|
145
|
+
});
|
|
@@ -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 === true,
|
|
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
|
+
});
|