oh-my-codex 0.8.2 → 0.8.4
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/README.md +10 -2
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +17 -27
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-codex-version.test.d.ts +2 -0
- package/dist/cli/__tests__/setup-codex-version.test.d.ts.map +1 -0
- package/dist/cli/__tests__/setup-codex-version.test.js +99 -0
- package/dist/cli/__tests__/setup-codex-version.test.js.map +1 -0
- package/dist/cli/__tests__/setup-refresh.test.d.ts +2 -0
- package/dist/cli/__tests__/setup-refresh.test.d.ts.map +1 -0
- package/dist/cli/__tests__/setup-refresh.test.js +166 -0
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -0
- package/dist/cli/__tests__/setup-scope.test.js +7 -4
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/setup-skills-overwrite.test.js +5 -2
- package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +53 -0
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/setup.d.ts +2 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +335 -175
- package/dist/cli/setup.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +199 -128
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +55 -0
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/generator.d.ts +4 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +154 -111
- package/dist/config/generator.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +26 -4
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +76 -0
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/tmux-claude-workers-demo.test.js +4 -3
- package/dist/team/__tests__/tmux-claude-workers-demo.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +10 -3
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +92 -62
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/tmux-session.d.ts +4 -3
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +15 -9
- package/dist/team/tmux-session.js.map +1 -1
- package/package.json +1 -1
- package/dist/hooks/__tests__/emulator.test.d.ts +0 -2
- package/dist/hooks/__tests__/emulator.test.d.ts.map +0 -1
- package/dist/hooks/__tests__/emulator.test.js +0 -53
- package/dist/hooks/__tests__/emulator.test.js.map +0 -1
- package/dist/hooks/emulator.d.ts +0 -44
- package/dist/hooks/emulator.d.ts.map +0 -1
- package/dist/hooks/emulator.js +0 -105
- package/dist/hooks/emulator.js.map +0 -1
package/dist/cli/setup.js
CHANGED
|
@@ -2,55 +2,120 @@
|
|
|
2
2
|
* omx setup - Automated installation of oh-my-codex
|
|
3
3
|
* Installs skills, prompts, MCP servers config, and AGENTS.md
|
|
4
4
|
*/
|
|
5
|
-
import { mkdir, copyFile, readdir, readFile, writeFile, stat, rm } from
|
|
6
|
-
import { join, dirname } from
|
|
7
|
-
import { existsSync } from
|
|
8
|
-
import { spawnSync } from
|
|
9
|
-
import { createInterface } from
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
5
|
+
import { mkdir, copyFile, readdir, readFile, writeFile, stat, rm, } from "fs/promises";
|
|
6
|
+
import { join, dirname, relative } from "path";
|
|
7
|
+
import { existsSync } from "fs";
|
|
8
|
+
import { spawnSync } from "child_process";
|
|
9
|
+
import { createInterface } from "readline/promises";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { codexHome, codexConfigPath, codexPromptsDir, userSkillsDir, omxStateDir, omxPlansDir, omxLogsDir, omxAgentsConfigDir, } from "../utils/paths.js";
|
|
12
|
+
import { buildMergedConfig, getRootModelName } from "../config/generator.js";
|
|
13
|
+
import { generateAgentToml } from "../agents/native-config.js";
|
|
14
|
+
import { AGENT_DEFINITIONS } from "../agents/definitions.js";
|
|
15
|
+
import { getPackageRoot } from "../utils/package.js";
|
|
16
|
+
import { readSessionState, isSessionStale } from "../hooks/session.js";
|
|
17
|
+
import { getCatalogHeadlineCounts } from "./catalog-contract.js";
|
|
18
|
+
import { tryReadCatalogManifest } from "../catalog/reader.js";
|
|
17
19
|
/**
|
|
18
20
|
* Legacy scope values that may appear in persisted setup-scope.json files.
|
|
19
21
|
* Both 'project-local' (renamed) and old 'project' (minimal, removed) are
|
|
20
22
|
* migrated to the current 'project' scope on read.
|
|
21
23
|
*/
|
|
22
24
|
const LEGACY_SCOPE_MIGRATION = {
|
|
23
|
-
|
|
25
|
+
"project-local": "project",
|
|
24
26
|
};
|
|
25
|
-
export const SETUP_SCOPES = [
|
|
27
|
+
export const SETUP_SCOPES = ["user", "project"];
|
|
26
28
|
function applyScopePathRewritesToAgentsTemplate(content, scope) {
|
|
27
|
-
if (scope !==
|
|
29
|
+
if (scope !== "project")
|
|
28
30
|
return content;
|
|
29
31
|
return content
|
|
30
|
-
.replaceAll(
|
|
31
|
-
.replaceAll(
|
|
32
|
+
.replaceAll("~/.codex", "./.codex")
|
|
33
|
+
.replaceAll("~/.agents", "./.agents");
|
|
32
34
|
}
|
|
33
35
|
const REQUIRED_TEAM_CLI_API_MARKERS = [
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
"if (subcommand === 'api')",
|
|
37
|
+
"executeTeamApiOperation",
|
|
38
|
+
"TEAM_API_OPERATIONS",
|
|
37
39
|
];
|
|
38
|
-
const DEFAULT_SETUP_SCOPE =
|
|
40
|
+
const DEFAULT_SETUP_SCOPE = "user";
|
|
41
|
+
const LEGACY_SETUP_MODEL = "gpt-5.3-codex";
|
|
42
|
+
const DEFAULT_SETUP_MODEL = "gpt-5.4";
|
|
43
|
+
function createEmptyCategorySummary() {
|
|
44
|
+
return {
|
|
45
|
+
updated: 0,
|
|
46
|
+
unchanged: 0,
|
|
47
|
+
backedUp: 0,
|
|
48
|
+
skipped: 0,
|
|
49
|
+
removed: 0,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function createEmptyRunSummary() {
|
|
53
|
+
return {
|
|
54
|
+
prompts: createEmptyCategorySummary(),
|
|
55
|
+
skills: createEmptyCategorySummary(),
|
|
56
|
+
nativeAgents: createEmptyCategorySummary(),
|
|
57
|
+
agentsMd: createEmptyCategorySummary(),
|
|
58
|
+
config: createEmptyCategorySummary(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function getBackupContext(scope, projectRoot) {
|
|
62
|
+
const timestamp = new Date().toISOString().replace(/[:]/g, "-");
|
|
63
|
+
if (scope === "project") {
|
|
64
|
+
return {
|
|
65
|
+
backupRoot: join(projectRoot, ".omx", "backups", "setup", timestamp),
|
|
66
|
+
baseRoot: projectRoot,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
backupRoot: join(homedir(), ".omx", "backups", "setup", timestamp),
|
|
71
|
+
baseRoot: homedir(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function ensureBackup(destinationPath, contentChanged, backupContext, options) {
|
|
75
|
+
if (!contentChanged || !existsSync(destinationPath))
|
|
76
|
+
return false;
|
|
77
|
+
const relativePath = relative(backupContext.baseRoot, destinationPath);
|
|
78
|
+
const safeRelativePath = relativePath.startsWith("..") || relativePath === ""
|
|
79
|
+
? destinationPath.replace(/^[/]+/, "")
|
|
80
|
+
: relativePath;
|
|
81
|
+
const backupPath = join(backupContext.backupRoot, safeRelativePath);
|
|
82
|
+
if (!options.dryRun) {
|
|
83
|
+
await mkdir(dirname(backupPath), { recursive: true });
|
|
84
|
+
await copyFile(destinationPath, backupPath);
|
|
85
|
+
}
|
|
86
|
+
if (options.verbose) {
|
|
87
|
+
console.log(` backup ${destinationPath} -> ${backupPath}`);
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
async function filesDiffer(src, dst) {
|
|
92
|
+
if (!existsSync(dst))
|
|
93
|
+
return true;
|
|
94
|
+
const [srcContent, dstContent] = await Promise.all([
|
|
95
|
+
readFile(src, "utf-8"),
|
|
96
|
+
readFile(dst, "utf-8"),
|
|
97
|
+
]);
|
|
98
|
+
return srcContent !== dstContent;
|
|
99
|
+
}
|
|
100
|
+
function logCategorySummary(name, summary) {
|
|
101
|
+
console.log(` ${name}: updated=${summary.updated}, unchanged=${summary.unchanged}, ` +
|
|
102
|
+
`backed_up=${summary.backedUp}, skipped=${summary.skipped}, removed=${summary.removed}`);
|
|
103
|
+
}
|
|
39
104
|
function isSetupScope(value) {
|
|
40
105
|
return SETUP_SCOPES.includes(value);
|
|
41
106
|
}
|
|
42
107
|
function getScopeFilePath(projectRoot) {
|
|
43
|
-
return join(projectRoot,
|
|
108
|
+
return join(projectRoot, ".omx", "setup-scope.json");
|
|
44
109
|
}
|
|
45
110
|
export function resolveScopeDirectories(scope, projectRoot) {
|
|
46
|
-
if (scope ===
|
|
47
|
-
const codexHomeDir = join(projectRoot,
|
|
111
|
+
if (scope === "project") {
|
|
112
|
+
const codexHomeDir = join(projectRoot, ".codex");
|
|
48
113
|
return {
|
|
49
|
-
codexConfigFile: join(codexHomeDir,
|
|
114
|
+
codexConfigFile: join(codexHomeDir, "config.toml"),
|
|
50
115
|
codexHomeDir,
|
|
51
|
-
nativeAgentsDir: join(projectRoot,
|
|
52
|
-
promptsDir: join(codexHomeDir,
|
|
53
|
-
skillsDir: join(projectRoot,
|
|
116
|
+
nativeAgentsDir: join(projectRoot, ".omx", "agents"),
|
|
117
|
+
promptsDir: join(codexHomeDir, "prompts"),
|
|
118
|
+
skillsDir: join(projectRoot, ".agents", "skills"),
|
|
54
119
|
};
|
|
55
120
|
}
|
|
56
121
|
return {
|
|
@@ -66,9 +131,9 @@ async function readPersistedSetupScope(projectRoot) {
|
|
|
66
131
|
if (!existsSync(scopePath))
|
|
67
132
|
return undefined;
|
|
68
133
|
try {
|
|
69
|
-
const raw = await readFile(scopePath,
|
|
134
|
+
const raw = await readFile(scopePath, "utf-8");
|
|
70
135
|
const parsed = JSON.parse(raw);
|
|
71
|
-
if (parsed && typeof parsed.scope ===
|
|
136
|
+
if (parsed && typeof parsed.scope === "string") {
|
|
72
137
|
// Direct match to current scopes
|
|
73
138
|
if (isSetupScope(parsed.scope))
|
|
74
139
|
return parsed.scope;
|
|
@@ -95,19 +160,21 @@ async function promptForSetupScope(defaultScope) {
|
|
|
95
160
|
output: process.stdout,
|
|
96
161
|
});
|
|
97
162
|
try {
|
|
98
|
-
console.log(
|
|
163
|
+
console.log("Select setup scope:");
|
|
99
164
|
console.log(` 1) user (default) — installs to ~/.codex, ~/.agents`);
|
|
100
|
-
console.log(
|
|
101
|
-
const answer = (await rl.question(
|
|
102
|
-
|
|
103
|
-
|
|
165
|
+
console.log(" 2) project — installs to ./.codex, ./.agents (local to project)");
|
|
166
|
+
const answer = (await rl.question("Scope [1-2] (default: 1): "))
|
|
167
|
+
.trim()
|
|
168
|
+
.toLowerCase();
|
|
169
|
+
if (answer === "2" || answer === "project")
|
|
170
|
+
return "project";
|
|
104
171
|
return defaultScope;
|
|
105
172
|
}
|
|
106
173
|
finally {
|
|
107
174
|
rl.close();
|
|
108
175
|
}
|
|
109
176
|
}
|
|
110
|
-
async function
|
|
177
|
+
async function promptForModelUpgrade(currentModel, targetModel) {
|
|
111
178
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
112
179
|
return false;
|
|
113
180
|
}
|
|
@@ -116,10 +183,10 @@ async function promptForAgentsOverwrite() {
|
|
|
116
183
|
output: process.stdout,
|
|
117
184
|
});
|
|
118
185
|
try {
|
|
119
|
-
const answer = (await rl.question(
|
|
186
|
+
const answer = (await rl.question(`Detected model "${currentModel}". Update to "${targetModel}"? [Y/n]: `))
|
|
120
187
|
.trim()
|
|
121
188
|
.toLowerCase();
|
|
122
|
-
return answer ===
|
|
189
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
123
190
|
}
|
|
124
191
|
finally {
|
|
125
192
|
rl.close();
|
|
@@ -127,17 +194,17 @@ async function promptForAgentsOverwrite() {
|
|
|
127
194
|
}
|
|
128
195
|
async function resolveSetupScope(projectRoot, requestedScope) {
|
|
129
196
|
if (requestedScope) {
|
|
130
|
-
return { scope: requestedScope, source:
|
|
197
|
+
return { scope: requestedScope, source: "cli" };
|
|
131
198
|
}
|
|
132
199
|
const persisted = await readPersistedSetupScope(projectRoot);
|
|
133
200
|
if (persisted) {
|
|
134
|
-
return { scope: persisted, source:
|
|
201
|
+
return { scope: persisted, source: "persisted" };
|
|
135
202
|
}
|
|
136
203
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
137
204
|
const scope = await promptForSetupScope(DEFAULT_SETUP_SCOPE);
|
|
138
|
-
return { scope, source:
|
|
205
|
+
return { scope, source: "prompt" };
|
|
139
206
|
}
|
|
140
|
-
return { scope: DEFAULT_SETUP_SCOPE, source:
|
|
207
|
+
return { scope: DEFAULT_SETUP_SCOPE, source: "default" };
|
|
141
208
|
}
|
|
142
209
|
async function persistSetupScope(projectRoot, scope, options) {
|
|
143
210
|
const scopePath = getScopeFilePath(projectRoot);
|
|
@@ -148,22 +215,22 @@ async function persistSetupScope(projectRoot, scope, options) {
|
|
|
148
215
|
}
|
|
149
216
|
await mkdir(dirname(scopePath), { recursive: true });
|
|
150
217
|
const payload = { scope };
|
|
151
|
-
await writeFile(scopePath, JSON.stringify(payload, null, 2) +
|
|
218
|
+
await writeFile(scopePath, JSON.stringify(payload, null, 2) + "\n");
|
|
152
219
|
if (options.verbose)
|
|
153
220
|
console.log(` Wrote ${scopePath}`);
|
|
154
221
|
}
|
|
155
222
|
export async function setup(options = {}) {
|
|
156
|
-
const { force = false, dryRun = false, scope: requestedScope, verbose = false,
|
|
223
|
+
const { force = false, dryRun = false, scope: requestedScope, verbose = false, modelUpgradePrompt, } = options;
|
|
157
224
|
const pkgRoot = getPackageRoot();
|
|
158
225
|
const projectRoot = process.cwd();
|
|
159
226
|
const resolvedScope = await resolveSetupScope(projectRoot, requestedScope);
|
|
160
227
|
const scopeDirs = resolveScopeDirectories(resolvedScope.scope, projectRoot);
|
|
161
|
-
const scopeSourceMessage = resolvedScope.source ===
|
|
162
|
-
console.log(
|
|
163
|
-
console.log(
|
|
228
|
+
const scopeSourceMessage = resolvedScope.source === "persisted" ? " (from .omx/setup-scope.json)" : "";
|
|
229
|
+
console.log("oh-my-codex setup");
|
|
230
|
+
console.log("=================\n");
|
|
164
231
|
console.log(`Using setup scope: ${resolvedScope.scope}${scopeSourceMessage}\n`);
|
|
165
232
|
// Step 1: Ensure directories exist
|
|
166
|
-
console.log(
|
|
233
|
+
console.log("[1/8] Creating directories...");
|
|
167
234
|
const dirs = [
|
|
168
235
|
scopeDirs.codexHomeDir,
|
|
169
236
|
scopeDirs.promptsDir,
|
|
@@ -180,19 +247,25 @@ export async function setup(options = {}) {
|
|
|
180
247
|
if (verbose)
|
|
181
248
|
console.log(` mkdir ${dir}`);
|
|
182
249
|
}
|
|
183
|
-
await persistSetupScope(projectRoot, resolvedScope.scope, {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
250
|
+
await persistSetupScope(projectRoot, resolvedScope.scope, {
|
|
251
|
+
dryRun,
|
|
252
|
+
verbose,
|
|
253
|
+
});
|
|
254
|
+
console.log(" Done.\n");
|
|
187
255
|
const catalogCounts = getCatalogHeadlineCounts();
|
|
256
|
+
const summary = createEmptyRunSummary();
|
|
257
|
+
const backupContext = getBackupContext(resolvedScope.scope, projectRoot);
|
|
258
|
+
// Step 2: Install agent prompts
|
|
259
|
+
console.log("[2/8] Installing agent prompts...");
|
|
188
260
|
{
|
|
189
|
-
const promptsSrc = join(pkgRoot,
|
|
261
|
+
const promptsSrc = join(pkgRoot, "prompts");
|
|
190
262
|
const promptsDst = scopeDirs.promptsDir;
|
|
191
|
-
|
|
263
|
+
summary.prompts = await installDirectory(promptsSrc, promptsDst, ".md", backupContext, { force, dryRun, verbose }, "prompt");
|
|
192
264
|
const cleanedLegacyPromptShims = await cleanupLegacySkillPromptShims(promptsSrc, promptsDst, {
|
|
193
265
|
dryRun,
|
|
194
266
|
verbose,
|
|
195
267
|
});
|
|
268
|
+
summary.prompts.removed += cleanedLegacyPromptShims;
|
|
196
269
|
if (cleanedLegacyPromptShims > 0) {
|
|
197
270
|
if (dryRun) {
|
|
198
271
|
console.log(` Would remove ${cleanedLegacyPromptShims} legacy skill prompt shim file(s).`);
|
|
@@ -202,127 +275,133 @@ export async function setup(options = {}) {
|
|
|
202
275
|
}
|
|
203
276
|
}
|
|
204
277
|
if (catalogCounts) {
|
|
205
|
-
console.log(`
|
|
278
|
+
console.log(` Prompt refresh complete (catalog baseline: ${catalogCounts.prompts}).\n`);
|
|
206
279
|
}
|
|
207
280
|
else {
|
|
208
|
-
console.log(
|
|
281
|
+
console.log(" Prompt refresh complete.\n");
|
|
209
282
|
}
|
|
210
283
|
}
|
|
211
284
|
// Step 3: Install native agent configs
|
|
212
|
-
console.log(
|
|
285
|
+
console.log("[3/8] Installing native agent configs...");
|
|
213
286
|
{
|
|
214
|
-
|
|
215
|
-
force,
|
|
287
|
+
summary.nativeAgents = await refreshNativeAgentConfigs(pkgRoot, scopeDirs.nativeAgentsDir, backupContext, {
|
|
216
288
|
dryRun,
|
|
217
289
|
verbose,
|
|
218
|
-
agentsDir: scopeDirs.nativeAgentsDir,
|
|
219
290
|
});
|
|
220
|
-
console.log(`
|
|
291
|
+
console.log(` Native agent refresh complete (${scopeDirs.nativeAgentsDir}).\n`);
|
|
221
292
|
}
|
|
222
293
|
// Step 4: Install skills
|
|
223
|
-
console.log(
|
|
294
|
+
console.log("[4/8] Installing skills...");
|
|
224
295
|
{
|
|
225
|
-
const skillsSrc = join(pkgRoot,
|
|
296
|
+
const skillsSrc = join(pkgRoot, "skills");
|
|
226
297
|
const skillsDst = scopeDirs.skillsDir;
|
|
227
|
-
|
|
298
|
+
summary.skills = await installSkills(skillsSrc, skillsDst, backupContext, {
|
|
299
|
+
force,
|
|
300
|
+
dryRun,
|
|
301
|
+
verbose,
|
|
302
|
+
});
|
|
228
303
|
if (catalogCounts) {
|
|
229
|
-
console.log(`
|
|
304
|
+
console.log(` Skill refresh complete (catalog baseline: ${catalogCounts.skills}).\n`);
|
|
230
305
|
}
|
|
231
306
|
else {
|
|
232
|
-
console.log(
|
|
307
|
+
console.log(" Skill refresh complete.\n");
|
|
233
308
|
}
|
|
234
309
|
}
|
|
235
310
|
// Step 5: Update config.toml
|
|
236
|
-
console.log(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
verbose,
|
|
240
|
-
agentsConfigDir: scopeDirs.nativeAgentsDir,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
console.log(` Done (${scopeDirs.codexConfigFile}).\n`);
|
|
311
|
+
console.log("[5/8] Updating config.toml...");
|
|
312
|
+
await updateManagedConfig(scopeDirs.codexConfigFile, pkgRoot, scopeDirs.nativeAgentsDir, summary.config, backupContext, { dryRun, verbose, modelUpgradePrompt });
|
|
313
|
+
console.log(` Config refresh complete (${scopeDirs.codexConfigFile}).\n`);
|
|
244
314
|
// Step 5.5: Verify team CLI interop surface is available.
|
|
245
|
-
console.log(
|
|
315
|
+
console.log("[5.5/8] Verifying Team CLI API interop...");
|
|
246
316
|
const teamToolsCheck = await verifyTeamCliApiInterop(pkgRoot);
|
|
247
317
|
if (teamToolsCheck.ok) {
|
|
248
|
-
console.log(
|
|
318
|
+
console.log(" omx team api command detected (CLI-first interop ready)");
|
|
249
319
|
}
|
|
250
320
|
else {
|
|
251
321
|
console.log(` WARNING: ${teamToolsCheck.message}`);
|
|
252
|
-
console.log(
|
|
322
|
+
console.log(" Run `npm run build` and then re-run `omx setup`.");
|
|
253
323
|
}
|
|
254
324
|
console.log();
|
|
255
325
|
// Step 6: Generate AGENTS.md
|
|
256
|
-
console.log(
|
|
257
|
-
const agentsMdSrc = join(pkgRoot,
|
|
258
|
-
const agentsMdDst = join(projectRoot,
|
|
326
|
+
console.log("[6/8] Generating AGENTS.md...");
|
|
327
|
+
const agentsMdSrc = join(pkgRoot, "templates", "AGENTS.md");
|
|
328
|
+
const agentsMdDst = join(projectRoot, "AGENTS.md");
|
|
259
329
|
const agentsMdExists = existsSync(agentsMdDst);
|
|
260
330
|
// Guard: refuse to overwrite AGENTS.md during active session
|
|
261
331
|
const activeSession = await readSessionState(projectRoot);
|
|
262
332
|
const sessionIsActive = activeSession && !isSessionStale(activeSession);
|
|
263
333
|
if (existsSync(agentsMdSrc)) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (sessionIsActive && shouldOverwriteAgentsMd) {
|
|
271
|
-
console.log(' WARNING: Active omx session detected (pid ' + activeSession?.pid + ').');
|
|
272
|
-
console.log(' Skipping AGENTS.md overwrite to avoid corrupting runtime overlay.');
|
|
273
|
-
if (force) {
|
|
274
|
-
console.log(' Stop the active session first, then re-run setup --force.');
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
console.log(' Stop the active session first, then re-run setup and approve overwrite (or use --force).');
|
|
278
|
-
}
|
|
334
|
+
const content = await readFile(agentsMdSrc, "utf-8");
|
|
335
|
+
const rewritten = applyScopePathRewritesToAgentsTemplate(content, resolvedScope.scope);
|
|
336
|
+
let changed = true;
|
|
337
|
+
if (agentsMdExists) {
|
|
338
|
+
const existing = await readFile(agentsMdDst, "utf-8");
|
|
339
|
+
changed = existing !== rewritten;
|
|
279
340
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
console.log(
|
|
341
|
+
if (sessionIsActive && agentsMdExists && changed) {
|
|
342
|
+
summary.agentsMd.skipped += 1;
|
|
343
|
+
console.log(" WARNING: Active omx session detected (pid " +
|
|
344
|
+
activeSession?.pid +
|
|
345
|
+
").");
|
|
346
|
+
console.log(" Skipping AGENTS.md overwrite to avoid corrupting runtime overlay.");
|
|
347
|
+
console.log(" Stop the active session first, then re-run setup.");
|
|
287
348
|
}
|
|
288
349
|
else {
|
|
289
|
-
|
|
350
|
+
await syncManagedContent(rewritten, agentsMdDst, summary.agentsMd, backupContext, { dryRun, verbose }, "AGENTS.md");
|
|
351
|
+
if (summary.agentsMd.updated > 0) {
|
|
352
|
+
console.log(" Generated AGENTS.md in project root.");
|
|
353
|
+
}
|
|
354
|
+
else if (summary.agentsMd.unchanged > 0) {
|
|
355
|
+
console.log(" AGENTS.md already up to date.");
|
|
356
|
+
}
|
|
290
357
|
}
|
|
291
358
|
}
|
|
292
359
|
else {
|
|
293
|
-
|
|
360
|
+
summary.agentsMd.skipped += 1;
|
|
361
|
+
console.log(" AGENTS.md template not found, skipping.");
|
|
294
362
|
}
|
|
295
363
|
console.log();
|
|
296
364
|
// Step 7: Set up notify hook
|
|
297
|
-
console.log(
|
|
365
|
+
console.log("[7/8] Configuring notification hook...");
|
|
298
366
|
await setupNotifyHook(pkgRoot, { dryRun, verbose });
|
|
299
|
-
console.log(
|
|
367
|
+
console.log(" Done.\n");
|
|
300
368
|
// Step 8: Configure HUD
|
|
301
|
-
console.log(
|
|
302
|
-
const hudConfigPath = join(projectRoot,
|
|
369
|
+
console.log("[8/8] Configuring HUD...");
|
|
370
|
+
const hudConfigPath = join(projectRoot, ".omx", "hud-config.json");
|
|
303
371
|
if (force || !existsSync(hudConfigPath)) {
|
|
304
372
|
if (!dryRun) {
|
|
305
|
-
const defaultHudConfig = { preset:
|
|
373
|
+
const defaultHudConfig = { preset: "focused" };
|
|
306
374
|
await writeFile(hudConfigPath, JSON.stringify(defaultHudConfig, null, 2));
|
|
307
375
|
}
|
|
308
376
|
if (verbose)
|
|
309
|
-
console.log(
|
|
310
|
-
console.log(
|
|
377
|
+
console.log(" Wrote .omx/hud-config.json");
|
|
378
|
+
console.log(" HUD config created (preset: focused).");
|
|
311
379
|
}
|
|
312
380
|
else {
|
|
313
|
-
console.log(
|
|
381
|
+
console.log(" HUD config already exists (use --force to overwrite).");
|
|
314
382
|
}
|
|
315
|
-
console.log(
|
|
383
|
+
console.log(" StatusLine configured in config.toml via [tui] section.");
|
|
384
|
+
console.log();
|
|
385
|
+
console.log("Setup refresh summary:");
|
|
386
|
+
logCategorySummary("prompts", summary.prompts);
|
|
387
|
+
logCategorySummary("skills", summary.skills);
|
|
388
|
+
logCategorySummary("native_agents", summary.nativeAgents);
|
|
389
|
+
logCategorySummary("agents_md", summary.agentsMd);
|
|
390
|
+
logCategorySummary("config", summary.config);
|
|
316
391
|
console.log();
|
|
392
|
+
if (force) {
|
|
393
|
+
console.log("Force mode: enabled additional destructive maintenance (for example stale deprecated skill cleanup).");
|
|
394
|
+
console.log();
|
|
395
|
+
}
|
|
317
396
|
console.log('Setup complete! Run "omx doctor" to verify installation.');
|
|
318
|
-
console.log(
|
|
319
|
-
console.log(
|
|
320
|
-
console.log(
|
|
321
|
-
console.log(
|
|
322
|
-
console.log(
|
|
323
|
-
console.log(
|
|
397
|
+
console.log("\nNext steps:");
|
|
398
|
+
console.log(" 1. Start Codex CLI in your project directory");
|
|
399
|
+
console.log(" 2. Use /prompts:architect, /prompts:executor, /prompts:planner as slash commands");
|
|
400
|
+
console.log(" 3. Skills are available via /skills or implicit matching");
|
|
401
|
+
console.log(" 4. The AGENTS.md orchestration brain is loaded automatically");
|
|
402
|
+
console.log(" 5. Native agent roles registered in config.toml [agents.*]");
|
|
324
403
|
if (isGitHubCliConfigured()) {
|
|
325
|
-
console.log(
|
|
404
|
+
console.log("\nSupport the project: gh repo star Yeachan-Heo/oh-my-codex");
|
|
326
405
|
}
|
|
327
406
|
}
|
|
328
407
|
function isLegacySkillPromptShim(content) {
|
|
@@ -332,19 +411,18 @@ function isLegacySkillPromptShim(content) {
|
|
|
332
411
|
async function cleanupLegacySkillPromptShims(promptsSrcDir, promptsDstDir, options) {
|
|
333
412
|
if (!existsSync(promptsSrcDir) || !existsSync(promptsDstDir))
|
|
334
413
|
return 0;
|
|
335
|
-
const sourceFiles = new Set((await readdir(promptsSrcDir))
|
|
336
|
-
.filter(name => name.endsWith('.md')));
|
|
414
|
+
const sourceFiles = new Set((await readdir(promptsSrcDir)).filter((name) => name.endsWith(".md")));
|
|
337
415
|
const installedFiles = await readdir(promptsDstDir);
|
|
338
416
|
let removed = 0;
|
|
339
417
|
for (const file of installedFiles) {
|
|
340
|
-
if (!file.endsWith(
|
|
418
|
+
if (!file.endsWith(".md"))
|
|
341
419
|
continue;
|
|
342
420
|
if (sourceFiles.has(file))
|
|
343
421
|
continue;
|
|
344
422
|
const fullPath = join(promptsDstDir, file);
|
|
345
|
-
let content =
|
|
423
|
+
let content = "";
|
|
346
424
|
try {
|
|
347
|
-
content = await readFile(fullPath,
|
|
425
|
+
content = await readFile(fullPath, "utf-8");
|
|
348
426
|
}
|
|
349
427
|
catch {
|
|
350
428
|
continue;
|
|
@@ -361,14 +439,56 @@ async function cleanupLegacySkillPromptShims(promptsSrcDir, promptsDstDir, optio
|
|
|
361
439
|
return removed;
|
|
362
440
|
}
|
|
363
441
|
function isGitHubCliConfigured() {
|
|
364
|
-
const result = spawnSync(
|
|
442
|
+
const result = spawnSync("gh", ["auth", "status"], { stdio: "ignore" });
|
|
365
443
|
return result.status === 0;
|
|
366
444
|
}
|
|
367
|
-
async function
|
|
445
|
+
async function syncManagedFileFromDisk(srcPath, dstPath, summary, backupContext, options, verboseLabel) {
|
|
446
|
+
const destinationExists = existsSync(dstPath);
|
|
447
|
+
const changed = !destinationExists || (await filesDiffer(srcPath, dstPath));
|
|
448
|
+
if (!changed) {
|
|
449
|
+
summary.unchanged += 1;
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (await ensureBackup(dstPath, destinationExists, backupContext, options)) {
|
|
453
|
+
summary.backedUp += 1;
|
|
454
|
+
}
|
|
455
|
+
if (!options.dryRun) {
|
|
456
|
+
await mkdir(dirname(dstPath), { recursive: true });
|
|
457
|
+
await copyFile(srcPath, dstPath);
|
|
458
|
+
}
|
|
459
|
+
summary.updated += 1;
|
|
460
|
+
if (options.verbose) {
|
|
461
|
+
console.log(` ${options.dryRun ? "would update" : "updated"} ${verboseLabel}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
async function syncManagedContent(content, dstPath, summary, backupContext, options, verboseLabel) {
|
|
465
|
+
const destinationExists = existsSync(dstPath);
|
|
466
|
+
let changed = true;
|
|
467
|
+
if (destinationExists) {
|
|
468
|
+
const existing = await readFile(dstPath, "utf-8");
|
|
469
|
+
changed = existing !== content;
|
|
470
|
+
}
|
|
471
|
+
if (!changed) {
|
|
472
|
+
summary.unchanged += 1;
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (await ensureBackup(dstPath, destinationExists, backupContext, options)) {
|
|
476
|
+
summary.backedUp += 1;
|
|
477
|
+
}
|
|
478
|
+
if (!options.dryRun) {
|
|
479
|
+
await mkdir(dirname(dstPath), { recursive: true });
|
|
480
|
+
await writeFile(dstPath, content);
|
|
481
|
+
}
|
|
482
|
+
summary.updated += 1;
|
|
483
|
+
if (options.verbose) {
|
|
484
|
+
console.log(` ${options.dryRun ? "would update" : "updated"} ${verboseLabel}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
async function installDirectory(srcDir, dstDir, ext, backupContext, options, kindLabel) {
|
|
488
|
+
const summary = createEmptyCategorySummary();
|
|
368
489
|
if (!existsSync(srcDir))
|
|
369
|
-
return
|
|
490
|
+
return summary;
|
|
370
491
|
const files = await readdir(srcDir);
|
|
371
|
-
let count = 0;
|
|
372
492
|
for (const file of files) {
|
|
373
493
|
if (!file.endsWith(ext))
|
|
374
494
|
continue;
|
|
@@ -377,78 +497,67 @@ async function installDirectory(srcDir, dstDir, ext, options) {
|
|
|
377
497
|
const srcStat = await stat(src);
|
|
378
498
|
if (!srcStat.isFile())
|
|
379
499
|
continue;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
500
|
+
await syncManagedFileFromDisk(src, dst, summary, backupContext, options, `${kindLabel} ${file}`);
|
|
501
|
+
}
|
|
502
|
+
return summary;
|
|
503
|
+
}
|
|
504
|
+
async function refreshNativeAgentConfigs(pkgRoot, agentsDir, backupContext, options) {
|
|
505
|
+
const summary = createEmptyCategorySummary();
|
|
506
|
+
if (!options.dryRun) {
|
|
507
|
+
await mkdir(agentsDir, { recursive: true });
|
|
508
|
+
}
|
|
509
|
+
for (const [name, agent] of Object.entries(AGENT_DEFINITIONS)) {
|
|
510
|
+
const promptPath = join(pkgRoot, "prompts", `${name}.md`);
|
|
511
|
+
if (!existsSync(promptPath)) {
|
|
512
|
+
continue;
|
|
387
513
|
}
|
|
514
|
+
const promptContent = await readFile(promptPath, "utf-8");
|
|
515
|
+
const toml = generateAgentToml(agent, promptContent);
|
|
516
|
+
const dst = join(agentsDir, `${name}.toml`);
|
|
517
|
+
await syncManagedContent(toml, dst, summary, backupContext, options, `native agent ${name}.toml`);
|
|
388
518
|
}
|
|
389
|
-
return
|
|
519
|
+
return summary;
|
|
390
520
|
}
|
|
391
|
-
async function installSkills(srcDir, dstDir, options) {
|
|
521
|
+
async function installSkills(srcDir, dstDir, backupContext, options) {
|
|
522
|
+
const summary = createEmptyCategorySummary();
|
|
392
523
|
if (!existsSync(srcDir))
|
|
393
|
-
return
|
|
524
|
+
return summary;
|
|
394
525
|
const manifest = tryReadCatalogManifest();
|
|
395
526
|
const skillStatusByName = manifest
|
|
396
527
|
? new Map(manifest.skills.map((skill) => [skill.name, skill.status]))
|
|
397
528
|
: null;
|
|
398
|
-
const isInstallableStatus = (status) => status ===
|
|
529
|
+
const isInstallableStatus = (status) => status === "active" || status === "internal";
|
|
399
530
|
const entries = await readdir(srcDir, { withFileTypes: true });
|
|
400
531
|
const staleCandidateSkillNames = new Set(manifest?.skills.map((skill) => skill.name) ?? []);
|
|
401
|
-
let count = 0;
|
|
402
532
|
for (const entry of entries) {
|
|
403
533
|
if (!entry.isDirectory())
|
|
404
534
|
continue;
|
|
405
535
|
staleCandidateSkillNames.add(entry.name);
|
|
406
536
|
const status = skillStatusByName?.get(entry.name);
|
|
407
537
|
if (skillStatusByName && !isInstallableStatus(status)) {
|
|
538
|
+
summary.skipped += 1;
|
|
408
539
|
if (options.verbose) {
|
|
409
|
-
const label = status ??
|
|
540
|
+
const label = status ?? "unlisted";
|
|
410
541
|
console.log(` skipped ${entry.name}/ (status: ${label})`);
|
|
411
542
|
}
|
|
412
543
|
continue;
|
|
413
544
|
}
|
|
414
545
|
const skillSrc = join(srcDir, entry.name);
|
|
415
546
|
const skillDst = join(dstDir, entry.name);
|
|
416
|
-
const skillMd = join(skillSrc,
|
|
547
|
+
const skillMd = join(skillSrc, "SKILL.md");
|
|
417
548
|
if (!existsSync(skillMd))
|
|
418
549
|
continue;
|
|
419
|
-
let copied = 0;
|
|
420
|
-
let overwritten = 0;
|
|
421
|
-
let skipped = 0;
|
|
422
|
-
const skillFiles = await readdir(skillSrc);
|
|
423
550
|
if (!options.dryRun) {
|
|
424
551
|
await mkdir(skillDst, { recursive: true });
|
|
425
552
|
}
|
|
553
|
+
const skillFiles = await readdir(skillSrc);
|
|
426
554
|
for (const sf of skillFiles) {
|
|
427
555
|
const sfPath = join(skillSrc, sf);
|
|
428
556
|
const sfStat = await stat(sfPath);
|
|
429
557
|
if (!sfStat.isFile())
|
|
430
558
|
continue;
|
|
431
559
|
const dstPath = join(skillDst, sf);
|
|
432
|
-
|
|
433
|
-
if (dstExists && !options.force) {
|
|
434
|
-
skipped++;
|
|
435
|
-
continue;
|
|
436
|
-
}
|
|
437
|
-
if (!options.dryRun) {
|
|
438
|
-
await copyFile(sfPath, dstPath);
|
|
439
|
-
}
|
|
440
|
-
if (dstExists) {
|
|
441
|
-
overwritten++;
|
|
442
|
-
}
|
|
443
|
-
else {
|
|
444
|
-
copied++;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
if (copied + overwritten > 0) {
|
|
448
|
-
count++;
|
|
449
|
-
}
|
|
450
|
-
if (options.verbose) {
|
|
451
|
-
console.log(` ${entry.name}/ (copied: ${copied}, overwritten: ${overwritten}, skipped: ${skipped})`);
|
|
560
|
+
await syncManagedFileFromDisk(sfPath, dstPath, summary, backupContext, options, `skill ${entry.name}/${sf}`);
|
|
452
561
|
}
|
|
453
562
|
}
|
|
454
563
|
if (options.force && manifest && existsSync(dstDir)) {
|
|
@@ -462,20 +571,68 @@ async function installSkills(srcDir, dstDir, options) {
|
|
|
462
571
|
if (!options.dryRun) {
|
|
463
572
|
await rm(staleSkillDir, { recursive: true, force: true });
|
|
464
573
|
}
|
|
574
|
+
summary.removed += 1;
|
|
465
575
|
if (options.verbose) {
|
|
466
|
-
const prefix = options.dryRun
|
|
467
|
-
|
|
576
|
+
const prefix = options.dryRun
|
|
577
|
+
? "would remove stale skill"
|
|
578
|
+
: "removed stale skill";
|
|
579
|
+
const label = status ?? "unlisted";
|
|
468
580
|
console.log(` ${prefix} ${staleSkill}/ (status: ${label})`);
|
|
469
581
|
}
|
|
470
582
|
}
|
|
471
583
|
}
|
|
472
|
-
return
|
|
584
|
+
return summary;
|
|
585
|
+
}
|
|
586
|
+
async function updateManagedConfig(configPath, pkgRoot, agentsConfigDir, summary, backupContext, options) {
|
|
587
|
+
const existing = existsSync(configPath)
|
|
588
|
+
? await readFile(configPath, "utf-8")
|
|
589
|
+
: "";
|
|
590
|
+
const currentModel = getRootModelName(existing);
|
|
591
|
+
let modelOverride;
|
|
592
|
+
if (currentModel === LEGACY_SETUP_MODEL) {
|
|
593
|
+
const shouldPrompt = typeof options.modelUpgradePrompt === "function" ||
|
|
594
|
+
(process.stdin.isTTY && process.stdout.isTTY);
|
|
595
|
+
if (shouldPrompt) {
|
|
596
|
+
const shouldUpgrade = options.modelUpgradePrompt
|
|
597
|
+
? await options.modelUpgradePrompt(currentModel, DEFAULT_SETUP_MODEL)
|
|
598
|
+
: await promptForModelUpgrade(currentModel, DEFAULT_SETUP_MODEL);
|
|
599
|
+
if (shouldUpgrade) {
|
|
600
|
+
modelOverride = DEFAULT_SETUP_MODEL;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
const finalConfig = buildMergedConfig(existing, pkgRoot, {
|
|
605
|
+
agentsConfigDir,
|
|
606
|
+
modelOverride,
|
|
607
|
+
verbose: options.verbose,
|
|
608
|
+
});
|
|
609
|
+
const changed = existing !== finalConfig;
|
|
610
|
+
if (!changed) {
|
|
611
|
+
summary.unchanged += 1;
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (await ensureBackup(configPath, existsSync(configPath), backupContext, options)) {
|
|
615
|
+
summary.backedUp += 1;
|
|
616
|
+
}
|
|
617
|
+
if (!options.dryRun) {
|
|
618
|
+
await writeFile(configPath, finalConfig);
|
|
619
|
+
}
|
|
620
|
+
if (options.verbose &&
|
|
621
|
+
modelOverride &&
|
|
622
|
+
currentModel &&
|
|
623
|
+
currentModel !== modelOverride) {
|
|
624
|
+
console.log(` ${options.dryRun ? "would update" : "updated"} root model from ${currentModel} to ${modelOverride}`);
|
|
625
|
+
}
|
|
626
|
+
summary.updated += 1;
|
|
627
|
+
if (options.verbose) {
|
|
628
|
+
console.log(` ${options.dryRun ? "would update" : "updated"} config ${configPath}`);
|
|
629
|
+
}
|
|
473
630
|
}
|
|
474
631
|
async function setupNotifyHook(pkgRoot, options) {
|
|
475
|
-
const hookScript = join(pkgRoot,
|
|
632
|
+
const hookScript = join(pkgRoot, "scripts", "notify-hook.js");
|
|
476
633
|
if (!existsSync(hookScript)) {
|
|
477
634
|
if (options.verbose)
|
|
478
|
-
console.log(
|
|
635
|
+
console.log(" Notify hook script not found, skipping.");
|
|
479
636
|
return;
|
|
480
637
|
}
|
|
481
638
|
// The notify hook is configured in config.toml via mergeConfig
|
|
@@ -483,15 +640,18 @@ async function setupNotifyHook(pkgRoot, options) {
|
|
|
483
640
|
console.log(` Notify hook: ${hookScript}`);
|
|
484
641
|
}
|
|
485
642
|
async function verifyTeamCliApiInterop(pkgRoot) {
|
|
486
|
-
const teamCliPath = join(pkgRoot,
|
|
643
|
+
const teamCliPath = join(pkgRoot, "dist", "cli", "team.js");
|
|
487
644
|
if (!existsSync(teamCliPath)) {
|
|
488
645
|
return { ok: false, message: `missing ${teamCliPath}` };
|
|
489
646
|
}
|
|
490
647
|
try {
|
|
491
|
-
const content = await readFile(teamCliPath,
|
|
648
|
+
const content = await readFile(teamCliPath, "utf-8");
|
|
492
649
|
const missing = REQUIRED_TEAM_CLI_API_MARKERS.filter((marker) => !content.includes(marker));
|
|
493
650
|
if (missing.length > 0) {
|
|
494
|
-
return {
|
|
651
|
+
return {
|
|
652
|
+
ok: false,
|
|
653
|
+
message: `team CLI interop markers missing: ${missing.join(", ")}`,
|
|
654
|
+
};
|
|
495
655
|
}
|
|
496
656
|
return { ok: true };
|
|
497
657
|
}
|