oh-my-opencode 4.5.12 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/opencode-qa/SKILL.md +194 -0
- package/.agents/skills/opencode-qa/references/cli-commands.md +188 -0
- package/.agents/skills/opencode-qa/references/db-investigation.md +197 -0
- package/.agents/skills/opencode-qa/references/events-hooks.md +110 -0
- package/.agents/skills/opencode-qa/references/sdk.md +96 -0
- package/.agents/skills/opencode-qa/references/server-api.md +200 -0
- package/.agents/skills/opencode-qa/references/testing-harness.md +218 -0
- package/.agents/skills/opencode-qa/references/tui-tmux.md +52 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-id.sh +53 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-name.sh +57 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-text.sh +158 -0
- package/.agents/skills/opencode-qa/scripts/export-roundtrip.sh +57 -0
- package/.agents/skills/opencode-qa/scripts/lib/common.sh +216 -0
- package/.agents/skills/opencode-qa/scripts/server-smoke.sh +64 -0
- package/.agents/skills/opencode-qa/scripts/sse-hook-probe.sh +106 -0
- package/.agents/skills/opencode-qa/scripts/tui-smoke.sh +89 -0
- package/README.ja.md +13 -3
- package/README.ko.md +13 -3
- package/README.md +24 -14
- package/README.ru.md +13 -3
- package/README.zh-cn.md +13 -3
- package/bin/oh-my-opencode.js +4 -3
- package/bin/oh-my-opencode.test.ts +35 -7
- package/bin/platform.d.ts +1 -1
- package/bin/platform.js +4 -4
- package/bin/platform.test.ts +31 -9
- package/bin/version-mismatch.js +47 -0
- package/bin/version-mismatch.test.ts +120 -0
- package/dist/cli/cleanup-command.d.ts +4 -0
- package/dist/cli/cleanup.d.ts +11 -0
- package/dist/cli/cli-program.d.ts +2 -1
- package/dist/cli/codex-ulw-loop.d.ts +12 -0
- package/dist/cli/doctor/checks/tui-plugin-config.d.ts +2 -0
- package/dist/cli/index.js +2189 -529
- package/dist/cli/install-codex/codex-cache.d.ts +1 -0
- package/dist/cli/install-codex/codex-cleanup-config.d.ts +6 -0
- package/dist/cli/install-codex/codex-cleanup.d.ts +21 -0
- package/dist/cli/install-codex/codex-config-permissions.d.ts +1 -0
- package/dist/cli/install-codex/codex-config-reasoning.d.ts +2 -0
- package/dist/cli/install-codex/codex-config-toml.d.ts +2 -1
- package/dist/cli/install-codex/codex-installation-detection.d.ts +36 -0
- package/dist/cli/install-codex/codex-model-catalog.d.ts +13 -0
- package/dist/cli/install-codex/codex-package-layout.d.ts +1 -0
- package/dist/cli/install-codex/codex-project-local-cleanup-best-effort.d.ts +7 -0
- package/dist/cli/install-codex/codex-project-local-cleanup.d.ts +35 -0
- package/dist/cli/install-codex/git-bash.d.ts +35 -0
- package/dist/cli/install-codex/index.d.ts +4 -0
- package/dist/cli/install-codex/toml-section-editor.d.ts +2 -0
- package/dist/cli/install-codex/types.d.ts +20 -0
- package/dist/cli/run/event-state.d.ts +1 -0
- package/dist/cli/run/poll-for-completion.d.ts +1 -0
- package/dist/cli/run/prompt-start.d.ts +7 -0
- package/dist/cli/star-request.d.ts +9 -0
- package/dist/config/schema/hooks.d.ts +0 -1
- package/dist/create-hooks.d.ts +0 -1
- package/dist/features/background-agent/concurrency.d.ts +1 -0
- package/dist/features/background-agent/process-cleanup.d.ts +6 -0
- package/dist/features/builtin-skills/skills/debugging.d.ts +2 -0
- package/dist/features/builtin-skills/skills/index.d.ts +1 -0
- package/dist/features/claude-code-session-state/state.d.ts +1 -0
- package/dist/features/opencode-skill-loader/index.d.ts +1 -0
- package/dist/features/opencode-skill-loader/opencode-config-skills-reader.d.ts +5 -0
- package/dist/features/tmux-subagent/attachable-session-status.d.ts +1 -1
- package/dist/features/tmux-subagent/session-status-parser.d.ts +1 -0
- package/dist/hooks/comment-checker/cli.d.ts +1 -0
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/tasks-todowrite-disabler/constants.d.ts +1 -1
- package/dist/index.js +1077 -563
- package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
- package/dist/plugin/messages-transform.d.ts +8 -1
- package/dist/plugin/user-abort-interrupted-recovery-guard.d.ts +6 -0
- package/dist/shared/command-executor/execute-hook-command.d.ts +2 -0
- package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +14 -0
- package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +7 -0
- package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +1 -0
- package/dist/shared/prompt-async-gate/timing.d.ts +1 -0
- package/dist/shared/prompt-async-gate/types.d.ts +2 -0
- package/dist/shared/prompt-async-gate.d.ts +1 -1
- package/dist/tools/skill/description-formatter.d.ts +5 -1
- package/dist/tools/skill/types.d.ts +1 -0
- package/package.json +22 -18
- package/packages/ast-grep-mcp/dist/cli.js +53 -9
- package/packages/git-bash-mcp/dist/cli.js +367 -0
- package/packages/lsp-tools-mcp/dist/lsp/process.js +1 -1
- package/packages/omo-codex/plugin/.mcp.json +11 -0
- package/packages/omo-codex/plugin/components/comment-checker/README.md +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +29 -0
- package/packages/omo-codex/plugin/components/git-bash/package.json +23 -0
- package/packages/omo-codex/plugin/components/git-bash/src/cli.ts +33 -0
- package/packages/omo-codex/plugin/components/git-bash/src/codex-hook.ts +180 -0
- package/packages/omo-codex/plugin/components/git-bash/src/index.ts +10 -0
- package/packages/omo-codex/plugin/components/git-bash/test/codex-hook.test.ts +195 -0
- package/packages/omo-codex/plugin/components/git-bash/tsconfig.build.json +13 -0
- package/packages/omo-codex/plugin/components/git-bash/tsconfig.json +25 -0
- package/packages/omo-codex/plugin/components/lsp/README.md +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/cli.ts +5 -5
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +33 -0
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +19 -27
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-cli.test.ts +28 -0
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-errors.test.ts +55 -0
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +7 -5
- package/packages/omo-codex/plugin/components/rules/README.md +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus.md +6 -4
- package/packages/omo-codex/plugin/components/rules/bundled-rules/windows-git-bash.md +10 -0
- package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +0 -2
- package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +3 -1
- package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +97 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +6 -5
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +22 -0
- package/packages/omo-codex/plugin/components/ultrawork/CHANGELOG.md +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/README.md +3 -3
- package/packages/omo-codex/plugin/components/ultrawork/agents/codex-ultrawork-reviewer.toml +4 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +8 -7
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +9 -8
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +32 -6
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +27 -4
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +25 -0
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +28 -205
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +231 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +12 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint.test.ts +19 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +102 -5
- package/packages/omo-codex/plugin/hooks/hooks.json +35 -2
- package/packages/omo-codex/plugin/model-catalog.json +49 -0
- package/packages/omo-codex/plugin/package-lock.json +19 -0
- package/packages/omo-codex/plugin/package.json +3 -1
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +159 -0
- package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +16 -1
- package/packages/omo-codex/plugin/scripts/build-components.mjs +2 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +269 -0
- package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +89 -0
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +6 -6
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +127 -0
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +9 -0
- package/packages/omo-codex/plugin/skills/refactor/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +33 -8
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +25 -5
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +28 -205
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +231 -0
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +17 -17
- package/packages/omo-codex/plugin/test/aggregate.test.mjs +188 -20
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +129 -0
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +58 -11
- package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +34 -0
- package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +21 -0
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +146 -0
- package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +48 -0
- package/packages/omo-codex/plugin/test/subagent-guidance.test.mjs +76 -0
- package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +67 -0
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +54 -2
- package/packages/omo-codex/scripts/install/cache.mjs +5 -3
- package/packages/omo-codex/scripts/install/cli-args.mjs +112 -0
- package/packages/omo-codex/scripts/install/config.mjs +23 -1
- package/packages/omo-codex/scripts/install/delegated-command.mjs +25 -0
- package/packages/omo-codex/scripts/install/git-bash.mjs +99 -0
- package/packages/omo-codex/scripts/install/git-bash.test.mjs +174 -0
- package/packages/omo-codex/scripts/install/legacy-bins.mjs +1 -0
- package/packages/omo-codex/scripts/install/mcp-runtime-cache.mjs +5 -1
- package/packages/omo-codex/scripts/install/model-catalog.mjs +66 -0
- package/packages/omo-codex/scripts/install/multi-agent-v2-config.mjs +7 -1
- package/packages/omo-codex/scripts/install/permissions.d.mts +1 -0
- package/packages/omo-codex/scripts/install/permissions.mjs +26 -0
- package/packages/omo-codex/scripts/install/project-local-cleanup.mjs +229 -0
- package/packages/omo-codex/scripts/install/reasoning-config.mjs +72 -0
- package/packages/omo-codex/scripts/install/source-package-build.mjs +20 -0
- package/packages/omo-codex/scripts/install/toml-editor.mjs +19 -2
- package/packages/omo-codex/scripts/install-bin-links.test.mjs +23 -0
- package/packages/omo-codex/scripts/install-cli-args.test.mjs +146 -0
- package/packages/omo-codex/scripts/install-config-autonomous.test.mjs +48 -0
- package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +141 -0
- package/packages/omo-codex/scripts/install-config.test.mjs +205 -0
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +157 -0
- package/packages/omo-codex/scripts/install-local-git-bash-preflight.test.mjs +145 -0
- package/packages/omo-codex/scripts/install-local.mjs +91 -8
- package/packages/omo-codex/scripts/install-local.test.mjs +15 -0
- package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +60 -0
- package/packages/omo-codex/scripts/install-packaged-local.test.mjs +67 -0
- package/packages/omo-codex/scripts/install-project-local-cleanup.test.mjs +277 -0
- package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +127 -0
- package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +9 -0
- package/packages/shared-skills/skills/review-work/SKILL.md +33 -8
- package/packages/shared-skills/skills/start-work/SKILL.md +25 -5
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +11 -11
- package/postinstall.mjs +36 -3
- package/dist/hooks/context-window-monitor.d.ts +0 -19
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { copyFile, lstat, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { escapeRegExp, findTomlSection, removeSetting } from "./toml-editor.mjs";
|
|
5
|
+
|
|
6
|
+
const LEGACY_AGENT_CONFLICT_KEYS = ["max_threads"];
|
|
7
|
+
const PROJECT_LOCAL_ARTIFACT_PATHS = [
|
|
8
|
+
".omx",
|
|
9
|
+
".codex/hooks.json",
|
|
10
|
+
".codex/agents",
|
|
11
|
+
".codex/prompts",
|
|
12
|
+
".codex/skills",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export async function repairNearestProjectLocalCodexArtifacts({ startDirectory, codexHome, now = () => new Date() }) {
|
|
16
|
+
const project = await findProjectLocalCodexConfigs(startDirectory, codexHome);
|
|
17
|
+
if (project === null) {
|
|
18
|
+
return emptyProjectLocalCodexCleanupResult();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const artifacts = await collectProjectLocalArtifacts(project.artifactRoots);
|
|
22
|
+
const configs = [];
|
|
23
|
+
for (const configPath of project.configPaths) {
|
|
24
|
+
const original = await readFile(configPath, "utf8");
|
|
25
|
+
const repair = repairProjectLocalCodexConfigText(original);
|
|
26
|
+
if (!repair.changed) {
|
|
27
|
+
configs.push({
|
|
28
|
+
projectRoot: project.projectRoot,
|
|
29
|
+
configPath,
|
|
30
|
+
changed: false,
|
|
31
|
+
removedKeys: repair.removedKeys,
|
|
32
|
+
});
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const backupPath = `${configPath}.backup-${formatBackupTimestamp(now())}`;
|
|
37
|
+
await copyFile(configPath, backupPath);
|
|
38
|
+
await writeFile(configPath, `${repair.config.trimEnd()}\n`);
|
|
39
|
+
configs.push({
|
|
40
|
+
projectRoot: project.projectRoot,
|
|
41
|
+
configPath,
|
|
42
|
+
changed: true,
|
|
43
|
+
removedKeys: repair.removedKeys,
|
|
44
|
+
backupPath,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const changedConfigs = configs.filter((config) => config.changed);
|
|
49
|
+
const nearestChangedConfig = lastValue(changedConfigs);
|
|
50
|
+
const nearestConfig = lastValue(configs);
|
|
51
|
+
return {
|
|
52
|
+
projectRoot: project.projectRoot,
|
|
53
|
+
configPath: nearestChangedConfig?.configPath ?? nearestConfig?.configPath ?? null,
|
|
54
|
+
changed: changedConfigs.length > 0,
|
|
55
|
+
removedKeys: uniqueRemovedKeys(changedConfigs),
|
|
56
|
+
backupPath: nearestChangedConfig?.backupPath,
|
|
57
|
+
configs,
|
|
58
|
+
artifacts,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function emptyProjectLocalCodexCleanupResult() {
|
|
63
|
+
return {
|
|
64
|
+
projectRoot: null,
|
|
65
|
+
configPath: null,
|
|
66
|
+
changed: false,
|
|
67
|
+
removedKeys: [],
|
|
68
|
+
configs: [],
|
|
69
|
+
artifacts: [],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function uniqueRemovedKeys(configs) {
|
|
74
|
+
const keys = [];
|
|
75
|
+
for (const config of configs) {
|
|
76
|
+
for (const key of config.removedKeys) {
|
|
77
|
+
if (!keys.includes(key)) keys.push(key);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return keys;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function lastValue(values) {
|
|
84
|
+
return values.length > 0 ? (values[values.length - 1] ?? null) : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function repairProjectLocalCodexConfigText(config) {
|
|
88
|
+
if (!isMultiAgentV2Enabled(config)) return { config, changed: false, removedKeys: [] };
|
|
89
|
+
|
|
90
|
+
let nextConfig = config;
|
|
91
|
+
const removedKeys = [];
|
|
92
|
+
for (const key of LEGACY_AGENT_CONFLICT_KEYS) {
|
|
93
|
+
const section = findTomlSection(nextConfig, "agents");
|
|
94
|
+
if (section === null || !hasSetting(section.text, key)) continue;
|
|
95
|
+
nextConfig = removeSetting(nextConfig, section, key);
|
|
96
|
+
removedKeys.push(key);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
config: nextConfig,
|
|
101
|
+
changed: removedKeys.length > 0,
|
|
102
|
+
removedKeys,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function findProjectLocalCodexConfigs(startDirectory, codexHome) {
|
|
107
|
+
if (typeof startDirectory !== "string" || startDirectory.includes("\0")) return null;
|
|
108
|
+
|
|
109
|
+
const startDirectoryStat = await maybeLstat(startDirectory);
|
|
110
|
+
if (startDirectoryStat !== null && !startDirectoryStat.isDirectory()) {
|
|
111
|
+
throw new ProjectLocalCleanupStartDirectoryError(startDirectory);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const codexHomeConfigPath = codexHome === undefined ? null : join(resolve(codexHome), "config.toml");
|
|
115
|
+
let current = resolve(startDirectory);
|
|
116
|
+
const configPathsFromCwd = [];
|
|
117
|
+
while (true) {
|
|
118
|
+
const configPath = join(current, ".codex", "config.toml");
|
|
119
|
+
if (await isRegularProjectLocalConfig(current, configPath)) {
|
|
120
|
+
if (codexHomeConfigPath === null || resolve(configPath) !== codexHomeConfigPath) {
|
|
121
|
+
configPathsFromCwd.push(configPath);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (await exists(join(current, ".git"))) {
|
|
126
|
+
return configPathsFromCwd.length === 0
|
|
127
|
+
? null
|
|
128
|
+
: {
|
|
129
|
+
projectRoot: current,
|
|
130
|
+
configPaths: [...configPathsFromCwd].reverse(),
|
|
131
|
+
artifactRoots: artifactRootsForConfigPaths(configPathsFromCwd),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const parent = dirname(current);
|
|
136
|
+
if (parent === current) {
|
|
137
|
+
const nearestConfigPath = configPathsFromCwd[0];
|
|
138
|
+
return nearestConfigPath === undefined
|
|
139
|
+
? null
|
|
140
|
+
: {
|
|
141
|
+
projectRoot: dirname(dirname(nearestConfigPath)),
|
|
142
|
+
configPaths: [nearestConfigPath],
|
|
143
|
+
artifactRoots: [dirname(dirname(nearestConfigPath))],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
current = parent;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function isRegularProjectLocalConfig(directory, configPath) {
|
|
151
|
+
const codexDirStat = await maybeLstat(join(directory, ".codex"));
|
|
152
|
+
if (codexDirStat === null || !codexDirStat.isDirectory() || codexDirStat.isSymbolicLink()) return false;
|
|
153
|
+
const configStat = await maybeLstat(configPath);
|
|
154
|
+
return configStat !== null && configStat.isFile() && !configStat.isSymbolicLink();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function artifactRootsForConfigPaths(configPaths) {
|
|
158
|
+
const roots = [];
|
|
159
|
+
for (const configPath of configPaths) {
|
|
160
|
+
const root = dirname(dirname(configPath));
|
|
161
|
+
if (!roots.includes(root)) roots.push(root);
|
|
162
|
+
}
|
|
163
|
+
return roots.reverse();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function collectProjectLocalArtifacts(projectRoots) {
|
|
167
|
+
const artifacts = [];
|
|
168
|
+
const seenPaths = new Set();
|
|
169
|
+
for (const projectRoot of projectRoots) {
|
|
170
|
+
for (const relativePath of PROJECT_LOCAL_ARTIFACT_PATHS) {
|
|
171
|
+
const artifactPath = join(projectRoot, relativePath);
|
|
172
|
+
if (seenPaths.has(artifactPath)) continue;
|
|
173
|
+
const entryStat = await maybeLstat(artifactPath);
|
|
174
|
+
if (entryStat === null) continue;
|
|
175
|
+
seenPaths.add(artifactPath);
|
|
176
|
+
artifacts.push({
|
|
177
|
+
relativePath,
|
|
178
|
+
path: artifactPath,
|
|
179
|
+
kind: entryStat.isDirectory() ? "directory" : entryStat.isFile() ? "file" : "other",
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return artifacts;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isMultiAgentV2Enabled(config) {
|
|
187
|
+
const featuresSection = findTomlSection(config, "features");
|
|
188
|
+
if (featuresSection !== null && settingIsBooleanTrue(featuresSection.text, "multi_agent_v2")) return true;
|
|
189
|
+
|
|
190
|
+
const multiAgentSection = findTomlSection(config, "features.multi_agent_v2");
|
|
191
|
+
return multiAgentSection !== null && settingIsBooleanTrue(multiAgentSection.text, "enabled");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function settingIsBooleanTrue(sectionText, key) {
|
|
195
|
+
return new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*true\\s*(?:#.*)?$`, "m").test(sectionText);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function hasSetting(sectionText, key) {
|
|
199
|
+
return new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`, "m").test(sectionText);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function formatBackupTimestamp(date) {
|
|
203
|
+
return date.toISOString().replace(/[:.]/g, "-");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function maybeLstat(path) {
|
|
207
|
+
try {
|
|
208
|
+
return await lstat(path);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (nodeErrorCode(error) === "ENOENT") return null;
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function exists(path) {
|
|
216
|
+
return (await maybeLstat(path)) !== null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function nodeErrorCode(error) {
|
|
220
|
+
if (!(error instanceof Error) || !("code" in error)) return null;
|
|
221
|
+
return typeof error.code === "string" ? error.code : null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
class ProjectLocalCleanupStartDirectoryError extends Error {
|
|
225
|
+
constructor(startDirectory) {
|
|
226
|
+
super(`Project-local Codex cleanup start path is not a directory: ${startDirectory}`);
|
|
227
|
+
this.name = "ProjectLocalCleanupStartDirectoryError";
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { replaceOrInsertRootSetting } from "./toml-editor.mjs";
|
|
2
|
+
|
|
3
|
+
const MANAGED_KEYS = ["model", "model_context_window", "model_reasoning_effort", "plan_mode_reasoning_effort"];
|
|
4
|
+
|
|
5
|
+
export function ensureCodexReasoningConfig(config, catalog) {
|
|
6
|
+
const current = readRootReasoningSettings(config);
|
|
7
|
+
if (
|
|
8
|
+
Object.keys(current).length > 0 &&
|
|
9
|
+
!matchesProfile(current, catalog.current) &&
|
|
10
|
+
!catalog.managedProfiles.some((profile) => matchesProfile(current, profile))
|
|
11
|
+
) {
|
|
12
|
+
return config;
|
|
13
|
+
}
|
|
14
|
+
let next = replaceOrInsertRootSetting(config, "model", JSON.stringify(catalog.current.model));
|
|
15
|
+
next = replaceOrInsertRootSetting(next, "model_context_window", catalog.current.modelContextWindow.toString());
|
|
16
|
+
next = replaceOrInsertRootSetting(
|
|
17
|
+
next,
|
|
18
|
+
"model_reasoning_effort",
|
|
19
|
+
JSON.stringify(catalog.current.modelReasoningEffort),
|
|
20
|
+
);
|
|
21
|
+
next = replaceOrInsertRootSetting(next, "plan_mode_reasoning_effort", JSON.stringify(catalog.current.planModeReasoningEffort));
|
|
22
|
+
return next;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readRootReasoningSettings(config) {
|
|
26
|
+
const settings = {};
|
|
27
|
+
for (const line of config.split(/\n/)) {
|
|
28
|
+
if (isSectionHeader(line)) break;
|
|
29
|
+
for (const key of MANAGED_KEYS) {
|
|
30
|
+
if (!isRootSetting(line, key)) continue;
|
|
31
|
+
const value = parseTomlScalar(line.slice(line.indexOf("=") + 1));
|
|
32
|
+
if (key === "model" && typeof value === "string") settings.model = value;
|
|
33
|
+
if (key === "model_context_window" && typeof value === "number") settings.modelContextWindow = value;
|
|
34
|
+
if (key === "model_reasoning_effort" && typeof value === "string") settings.modelReasoningEffort = value;
|
|
35
|
+
if (key === "plan_mode_reasoning_effort" && typeof value === "string") settings.planModeReasoningEffort = value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return settings;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function matchesProfile(current, profile) {
|
|
42
|
+
for (const [key, value] of Object.entries(profile)) {
|
|
43
|
+
if (current[key] !== value) return false;
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseTomlScalar(value) {
|
|
49
|
+
const trimmed = value.trim();
|
|
50
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(trimmed);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error instanceof SyntaxError) return undefined;
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const numeric = Number(trimmed);
|
|
59
|
+
return Number.isFinite(numeric) ? numeric : undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isSectionHeader(line) {
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
return trimmed.startsWith("[") && trimmed.endsWith("]");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isRootSetting(line, key) {
|
|
68
|
+
const trimmed = line.trimStart();
|
|
69
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("[")) return false;
|
|
70
|
+
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
71
|
+
return match?.[1] === key;
|
|
72
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const PACKAGED_INSTALLER_PACKAGE_NAMES = new Set([
|
|
6
|
+
"@code-yeongyu/lazycodex",
|
|
7
|
+
"@code-yeongyu/lazycodex-ai",
|
|
8
|
+
"lazycodex",
|
|
9
|
+
"lazycodex-ai",
|
|
10
|
+
"oh-my-opencode",
|
|
11
|
+
"oh-my-openagent",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export async function shouldBuildSourcePackages(repoRoot) {
|
|
15
|
+
if (existsSync(join(repoRoot, "src", "index.ts"))) return true;
|
|
16
|
+
const packageJsonPath = join(repoRoot, "package.json");
|
|
17
|
+
if (!existsSync(packageJsonPath)) return true;
|
|
18
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
19
|
+
return !PACKAGED_INSTALLER_PACKAGE_NAMES.has(packageJson?.name);
|
|
20
|
+
}
|
|
@@ -26,22 +26,39 @@ export function replaceOrInsertSetting(config, section, key, value) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function removeSetting(config, section, key) {
|
|
29
|
-
const linePattern = new RegExp(
|
|
29
|
+
const linePattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=.*(?:\\n|$)`, "m");
|
|
30
30
|
const replacement = section.text.replace(linePattern, "");
|
|
31
31
|
return config.slice(0, section.start) + replacement + config.slice(section.end);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export function replaceOrInsertRootSetting(config, key, value) {
|
|
35
|
+
const sectionStart = findFirstTableStart(config);
|
|
36
|
+
const root = config.slice(0, sectionStart);
|
|
37
|
+
const suffix = config.slice(sectionStart);
|
|
38
|
+
const linePattern = new RegExp(`^${escapeRegExp(key)}\\s*=.*$`, "m");
|
|
39
|
+
const replacement = linePattern.test(root)
|
|
40
|
+
? root.replace(linePattern, `${key} = ${value}`)
|
|
41
|
+
: `${root.trimEnd()}${root.trimEnd().length > 0 ? "\n" : ""}${key} = ${value}\n`;
|
|
42
|
+
if (suffix.length === 0) return replacement;
|
|
43
|
+
return `${replacement.trimEnd()}\n\n${suffix.trimStart()}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
export function appendBlock(config, block) {
|
|
35
47
|
const prefix = config.trimEnd();
|
|
36
48
|
return `${prefix}${prefix.length > 0 ? "\n\n" : ""}${block.trimEnd()}\n`;
|
|
37
49
|
}
|
|
38
50
|
|
|
51
|
+
function findFirstTableStart(config) {
|
|
52
|
+
const match = config.match(/^[[].*$/m);
|
|
53
|
+
return match?.index ?? config.length;
|
|
54
|
+
}
|
|
55
|
+
|
|
39
56
|
function insertSetting(sectionText, key, value) {
|
|
40
57
|
const lines = sectionText.split("\n");
|
|
41
58
|
lines.splice(1, 0, `${key} = ${value}`);
|
|
42
59
|
return lines.join("\n");
|
|
43
60
|
}
|
|
44
61
|
|
|
45
|
-
function escapeRegExp(value) {
|
|
62
|
+
export function escapeRegExp(value) {
|
|
46
63
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47
64
|
}
|
|
@@ -76,6 +76,29 @@ test("#given managed legacy Codex component symlink #when linking bins #then rem
|
|
|
76
76
|
assert.equal(await readlink(join(binDir, "omo-rules")), join(pluginRoot, "dist", "cli.js"));
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
+
test("#given managed legacy Codex LSP symlink #when linking bins #then removes stale lsp symlink", async () => {
|
|
80
|
+
const root = await makeTempDir();
|
|
81
|
+
const pluginRoot = join(root, "plugin");
|
|
82
|
+
const binDir = join(root, "bin");
|
|
83
|
+
const oldTarget = join(root, "codex-home", "plugins", "cache", "legacy-market", "omo", "0.0.1", "components", "lsp", "dist", "cli.js");
|
|
84
|
+
|
|
85
|
+
await mkdir(join(pluginRoot, "dist"), { recursive: true });
|
|
86
|
+
await mkdir(join(root, "codex-home", "plugins", "cache", "legacy-market", "omo", "0.0.1", "components", "lsp", "dist"), { recursive: true });
|
|
87
|
+
await mkdir(binDir, { recursive: true });
|
|
88
|
+
await writeJson(join(pluginRoot, "package.json"), {
|
|
89
|
+
name: "@example/omo",
|
|
90
|
+
bin: { omo: "./dist/cli.js" },
|
|
91
|
+
});
|
|
92
|
+
await writeFile(join(pluginRoot, "dist", "cli.js"), "#!/usr/bin/env node\n");
|
|
93
|
+
await writeFile(oldTarget, "#!/usr/bin/env node\n");
|
|
94
|
+
await symlink(oldTarget, join(binDir, "codex-lsp"));
|
|
95
|
+
|
|
96
|
+
await linkCachedPluginBins({ binDir, pluginRoot, platform: "linux" });
|
|
97
|
+
|
|
98
|
+
await assert.rejects(readlink(join(binDir, "codex-lsp")));
|
|
99
|
+
assert.equal(await readlink(join(binDir, "omo")), join(pluginRoot, "dist", "cli.js"));
|
|
100
|
+
});
|
|
101
|
+
|
|
79
102
|
test("#given user-owned legacy Codex symlink #when linking bins #then preserves the user symlink", async () => {
|
|
80
103
|
const root = await makeTempDir();
|
|
81
104
|
const pluginRoot = join(root, "plugin");
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { parseLazyCodexInstallCliArgs } from "./install/cli-args.mjs";
|
|
5
|
+
|
|
6
|
+
test("#given lazycodex install flags #when parsing Node installer argv #then keeps Codex autonomous intent", () => {
|
|
7
|
+
// given
|
|
8
|
+
const argv = ["install", "--no-tui", "--codex-autonomous", "--platform=codex"];
|
|
9
|
+
|
|
10
|
+
// when
|
|
11
|
+
const parsed = parseLazyCodexInstallCliArgs(argv);
|
|
12
|
+
|
|
13
|
+
// then
|
|
14
|
+
assert.deepEqual(parsed, {
|
|
15
|
+
kind: "install",
|
|
16
|
+
autonomousPermissions: true,
|
|
17
|
+
repoRoot: undefined,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("#given unsupported OpenCode platform override #when parsing Node installer argv #then rejects the Bun-backed path", () => {
|
|
22
|
+
// given
|
|
23
|
+
const argv = ["install", "--platform=both"];
|
|
24
|
+
|
|
25
|
+
// when
|
|
26
|
+
const parse = () => parseLazyCodexInstallCliArgs(argv);
|
|
27
|
+
|
|
28
|
+
// then
|
|
29
|
+
assert.throws(parse, /lazycodex-ai installs the Codex Light edition only/);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("#given missing platform value #when parsing Node installer argv #then rejects the incomplete option", () => {
|
|
33
|
+
// given
|
|
34
|
+
const argv = ["install", "--platform"];
|
|
35
|
+
|
|
36
|
+
// when
|
|
37
|
+
const parse = () => parseLazyCodexInstallCliArgs(argv);
|
|
38
|
+
|
|
39
|
+
// then
|
|
40
|
+
assert.throws(parse, /--platform requires a value/);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("#given repo root equals option #when parsing Node installer argv #then keeps the explicit path", () => {
|
|
44
|
+
// given
|
|
45
|
+
const argv = ["install", "--repo-root=/tmp/project"];
|
|
46
|
+
|
|
47
|
+
// when
|
|
48
|
+
const parsed = parseLazyCodexInstallCliArgs(argv);
|
|
49
|
+
|
|
50
|
+
// then
|
|
51
|
+
assert.deepEqual(parsed, {
|
|
52
|
+
kind: "install",
|
|
53
|
+
autonomousPermissions: undefined,
|
|
54
|
+
repoRoot: "/tmp/project",
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("#given unknown positional command #when parsing Node installer argv #then rejects instead of treating it as a repo root", () => {
|
|
59
|
+
// given
|
|
60
|
+
const argv = ["banana"];
|
|
61
|
+
|
|
62
|
+
// when
|
|
63
|
+
const parse = () => parseLazyCodexInstallCliArgs(argv);
|
|
64
|
+
|
|
65
|
+
// then
|
|
66
|
+
assert.throws(parse, /Unsupported lazycodex-ai command: banana/);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("#given install help flag #when parsing Node installer argv #then returns help", () => {
|
|
70
|
+
// given
|
|
71
|
+
const argv = ["install", "--help"];
|
|
72
|
+
|
|
73
|
+
// when
|
|
74
|
+
const parsed = parseLazyCodexInstallCliArgs(argv);
|
|
75
|
+
|
|
76
|
+
// then
|
|
77
|
+
assert.deepEqual(parsed, { kind: "help" });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("#given dry-run install with codex autonomy flags #when parsing Node installer argv #then keeps delegated install command and dry-run intent", () => {
|
|
81
|
+
// given
|
|
82
|
+
const argv = ["--dry-run", "install", "--no-tui", "--codex-autonomous"];
|
|
83
|
+
|
|
84
|
+
// when
|
|
85
|
+
const parsed = parseLazyCodexInstallCliArgs(argv);
|
|
86
|
+
|
|
87
|
+
// then
|
|
88
|
+
assert.deepEqual(parsed, {
|
|
89
|
+
kind: "command",
|
|
90
|
+
command: "install",
|
|
91
|
+
dryRun: true,
|
|
92
|
+
noTui: true,
|
|
93
|
+
skipAuth: false,
|
|
94
|
+
autonomousPermissions: true,
|
|
95
|
+
repoRoot: undefined,
|
|
96
|
+
args: [],
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("#given dry-run doctor command #when parsing Node installer argv #then returns delegated doctor command", () => {
|
|
101
|
+
// given
|
|
102
|
+
const argv = ["--dry-run", "doctor"];
|
|
103
|
+
|
|
104
|
+
// when
|
|
105
|
+
const parsed = parseLazyCodexInstallCliArgs(argv);
|
|
106
|
+
|
|
107
|
+
// then
|
|
108
|
+
assert.deepEqual(parsed, {
|
|
109
|
+
kind: "command",
|
|
110
|
+
command: "doctor",
|
|
111
|
+
dryRun: true,
|
|
112
|
+
args: [],
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("#given dry-run cleanup command #when parsing Node installer argv #then returns delegated codex cleanup command", () => {
|
|
117
|
+
// given
|
|
118
|
+
const argv = ["--dry-run", "cleanup", "--project", "/tmp/lazycodex-qa"];
|
|
119
|
+
|
|
120
|
+
// when
|
|
121
|
+
const parsed = parseLazyCodexInstallCliArgs(argv);
|
|
122
|
+
|
|
123
|
+
// then
|
|
124
|
+
assert.deepEqual(parsed, {
|
|
125
|
+
kind: "command",
|
|
126
|
+
command: "cleanup",
|
|
127
|
+
dryRun: true,
|
|
128
|
+
args: ["--project", "/tmp/lazycodex-qa"],
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("#given doctor flags #when parsing Node installer argv #then preserves pass-through arguments", () => {
|
|
133
|
+
// given
|
|
134
|
+
const argv = ["doctor", "--json"];
|
|
135
|
+
|
|
136
|
+
// when
|
|
137
|
+
const parsed = parseLazyCodexInstallCliArgs(argv);
|
|
138
|
+
|
|
139
|
+
// then
|
|
140
|
+
assert.deepEqual(parsed, {
|
|
141
|
+
kind: "command",
|
|
142
|
+
command: "doctor",
|
|
143
|
+
dryRun: false,
|
|
144
|
+
args: ["--json"],
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, readFile, 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 { updateCodexConfig } from "./install/config.mjs";
|
|
8
|
+
|
|
9
|
+
test("#given autonomous permissions requested #when script installer updates config #then enables full Codex autonomy", async () => {
|
|
10
|
+
// given
|
|
11
|
+
const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-autonomous-"));
|
|
12
|
+
const configPath = join(root, "config.toml");
|
|
13
|
+
await writeFile(
|
|
14
|
+
configPath,
|
|
15
|
+
[
|
|
16
|
+
'approval_policy = "on-request"',
|
|
17
|
+
'sandbox_mode = "workspace-write"',
|
|
18
|
+
'network_access = "disabled"',
|
|
19
|
+
"",
|
|
20
|
+
"[notice]",
|
|
21
|
+
"hide_full_access_warning = false",
|
|
22
|
+
"",
|
|
23
|
+
"[windows]",
|
|
24
|
+
'sandbox = "workspace-write"',
|
|
25
|
+
"",
|
|
26
|
+
].join("\n"),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// when
|
|
30
|
+
await updateCodexConfig({
|
|
31
|
+
configPath,
|
|
32
|
+
repoRoot: "/repo/packages/omo-codex",
|
|
33
|
+
marketplaceName: "debug",
|
|
34
|
+
marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
|
|
35
|
+
pluginNames: ["omo"],
|
|
36
|
+
autonomousPermissions: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// then
|
|
40
|
+
const config = await readFile(configPath, "utf8");
|
|
41
|
+
assert.match(config, /approval_policy = "never"/);
|
|
42
|
+
assert.match(config, /sandbox_mode = "danger-full-access"/);
|
|
43
|
+
assert.match(config, /network_access = "enabled"/);
|
|
44
|
+
assert.match(config, /\[notice\]/);
|
|
45
|
+
assert.match(config, /hide_full_access_warning = true/);
|
|
46
|
+
assert.match(config, /hide_world_writable_warning = true/);
|
|
47
|
+
assert.doesNotMatch(config, /sandbox = "workspace-write"/);
|
|
48
|
+
});
|