@rely-ai/caliber 1.1.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.js +458 -286
  2. package/package.json +2 -1
package/dist/bin.js CHANGED
@@ -32,17 +32,17 @@ __export(constants_exports, {
32
32
  LEARNING_STATE_FILE: () => LEARNING_STATE_FILE,
33
33
  MANIFEST_FILE: () => MANIFEST_FILE
34
34
  });
35
- import path8 from "path";
35
+ import path9 from "path";
36
36
  import os2 from "os";
37
37
  var AUTH_DIR, CALIBER_DIR, MANIFEST_FILE, BACKUPS_DIR, LEARNING_DIR, LEARNING_SESSION_FILE, LEARNING_STATE_FILE, LEARNING_MAX_EVENTS;
38
38
  var init_constants = __esm({
39
39
  "src/constants.ts"() {
40
40
  "use strict";
41
- AUTH_DIR = path8.join(os2.homedir(), ".caliber");
41
+ AUTH_DIR = path9.join(os2.homedir(), ".caliber");
42
42
  CALIBER_DIR = ".caliber";
43
- MANIFEST_FILE = path8.join(CALIBER_DIR, "manifest.json");
44
- BACKUPS_DIR = path8.join(CALIBER_DIR, "backups");
45
- LEARNING_DIR = path8.join(CALIBER_DIR, "learning");
43
+ MANIFEST_FILE = path9.join(CALIBER_DIR, "manifest.json");
44
+ BACKUPS_DIR = path9.join(CALIBER_DIR, "backups");
45
+ LEARNING_DIR = path9.join(CALIBER_DIR, "learning");
46
46
  LEARNING_SESSION_FILE = "current-session.jsonl";
47
47
  LEARNING_STATE_FILE = "state.json";
48
48
  LEARNING_MAX_EVENTS = 500;
@@ -51,8 +51,8 @@ var init_constants = __esm({
51
51
 
52
52
  // src/cli.ts
53
53
  import { Command } from "commander";
54
- import fs25 from "fs";
55
- import path21 from "path";
54
+ import fs26 from "fs";
55
+ import path22 from "path";
56
56
  import { fileURLToPath } from "url";
57
57
 
58
58
  // src/commands/onboard.ts
@@ -60,7 +60,8 @@ import chalk5 from "chalk";
60
60
  import ora2 from "ora";
61
61
  import readline4 from "readline";
62
62
  import select2 from "@inquirer/select";
63
- import fs18 from "fs";
63
+ import checkbox from "@inquirer/checkbox";
64
+ import fs19 from "fs";
64
65
 
65
66
  // src/fingerprint/index.ts
66
67
  import fs6 from "fs";
@@ -145,6 +146,10 @@ function readExistingConfigs(dir) {
145
146
  if (fs2.existsSync(readmeMdPath)) {
146
147
  configs.readmeMd = fs2.readFileSync(readmeMdPath, "utf-8");
147
148
  }
149
+ const agentsMdPath = path2.join(dir, "AGENTS.md");
150
+ if (fs2.existsSync(agentsMdPath)) {
151
+ configs.agentsMd = fs2.readFileSync(agentsMdPath, "utf-8");
152
+ }
148
153
  const claudeMdPath = path2.join(dir, "CLAUDE.md");
149
154
  if (fs2.existsSync(claudeMdPath)) {
150
155
  configs.claudeMd = fs2.readFileSync(claudeMdPath, "utf-8");
@@ -1172,7 +1177,7 @@ async function llmJsonCall(options) {
1172
1177
  }
1173
1178
 
1174
1179
  // src/ai/prompts.ts
1175
- var GENERATION_SYSTEM_PROMPT = `You are an expert auditor for coding agent configurations (Claude Code and Cursor).
1180
+ var GENERATION_SYSTEM_PROMPT = `You are an expert auditor for coding agent configurations (Claude Code, Cursor, and Codex).
1176
1181
 
1177
1182
  Your job depends on context:
1178
1183
  - If no existing configs exist \u2192 generate an initial setup from scratch.
@@ -1180,7 +1185,9 @@ Your job depends on context:
1180
1185
 
1181
1186
  You understand these config files:
1182
1187
  - CLAUDE.md: Project context for Claude Code \u2014 build/test commands, architecture, conventions.
1188
+ - AGENTS.md: Primary instructions file for OpenAI Codex \u2014 same purpose as CLAUDE.md but for the Codex agent. Also serves as a cross-agent coordination file.
1183
1189
  - .claude/skills/{name}/SKILL.md: Skill files following the OpenSkills standard (agentskills.io). Each skill is a directory named after the skill, containing a SKILL.md with YAML frontmatter.
1190
+ - .agents/skills/{name}/SKILL.md: Same OpenSkills format for Codex skills (Codex scans .agents/skills/ for skills).
1184
1191
  - .cursorrules: Coding rules for Cursor (deprecated legacy format \u2014 do NOT generate this).
1185
1192
  - .cursor/rules/*.mdc: Modern Cursor rules with frontmatter (description, globs, alwaysApply).
1186
1193
  - .cursor/skills/{name}/SKILL.md: Same OpenSkills format as Claude skills.
@@ -1212,7 +1219,7 @@ Omit empty categories. Keep each reason punchy and specific. End with a blank li
1212
1219
 
1213
1220
  AgentSetup schema:
1214
1221
  {
1215
- "targetAgent": "claude" | "cursor" | "both",
1222
+ "targetAgent": ["claude", "cursor", "codex"] (array of selected agents),
1216
1223
  "fileDescriptions": {
1217
1224
  "<file-path>": "reason for this change (max 80 chars)"
1218
1225
  },
@@ -1223,6 +1230,10 @@ AgentSetup schema:
1223
1230
  "claudeMd": "string (markdown content for CLAUDE.md)",
1224
1231
  "skills": [{ "name": "string (kebab-case, matches directory name)", "description": "string (what this skill does and when to use it)", "content": "string (markdown body \u2014 NO frontmatter, it will be generated from name+description)" }]
1225
1232
  },
1233
+ "codex": {
1234
+ "agentsMd": "string (markdown content for AGENTS.md \u2014 the primary Codex instructions file, same quality/structure as CLAUDE.md)",
1235
+ "skills": [{ "name": "string (kebab-case, matches directory name)", "description": "string (what this skill does and when to use it)", "content": "string (markdown body \u2014 NO frontmatter, it will be generated from name+description)" }]
1236
+ },
1226
1237
  "cursor": {
1227
1238
  "skills": [{ "name": "string (kebab-case, matches directory name)", "description": "string (what this skill does and when to use it)", "content": "string (markdown body \u2014 NO frontmatter, it will be generated from name+description)" }],
1228
1239
  "rules": [{ "filename": "string.mdc", "content": "string (with frontmatter)" }]
@@ -1237,14 +1248,15 @@ All skills follow the OpenSkills standard (agentskills.io):
1237
1248
  - The "content" field is the markdown body only \u2014 do NOT include YAML frontmatter in the content, it will be generated from the name and description fields.
1238
1249
  - Keep skill content under 500 lines. Move detailed references to separate files if needed.
1239
1250
 
1240
- The "fileDescriptions" object MUST include a one-liner for every file that will be created or modified. Use actual file paths as keys (e.g. "CLAUDE.md", ".claude/skills/my-skill/SKILL.md", ".cursor/skills/my-skill/SKILL.md", ".cursor/rules/my-rule.mdc"). Each description should explain why the change is needed, be concise and lowercase.
1251
+ The "fileDescriptions" object MUST include a one-liner for every file that will be created or modified. Use actual file paths as keys (e.g. "CLAUDE.md", "AGENTS.md", ".claude/skills/my-skill/SKILL.md", ".agents/skills/my-skill/SKILL.md", ".cursor/skills/my-skill/SKILL.md", ".cursor/rules/my-rule.mdc"). Each description should explain why the change is needed, be concise and lowercase.
1241
1252
 
1242
1253
  The "deletions" array should list files that should be removed (e.g. duplicate skills, stale configs). Include a reason for each. Omit the array or leave empty if nothing should be deleted.
1243
1254
 
1244
1255
  SCORING CRITERIA \u2014 your output is scored deterministically. Optimize for 100/100:
1245
1256
 
1246
1257
  Existence (25 pts):
1247
- - CLAUDE.md exists (6 pts) \u2014 always generate
1258
+ - CLAUDE.md exists (6 pts) \u2014 always generate for claude/both targets
1259
+ - AGENTS.md exists (6 pts) \u2014 always generate for codex target (serves as primary instructions file)
1248
1260
  - Skills configured (8 pts) \u2014 generate exactly 3 focused skills for full points (6 base + 1 per extra, cap 2). Two skills = 7 pts, three = 8 pts.
1249
1261
  - MCP servers mentioned (3 pts) \u2014 reference detected MCP integrations
1250
1262
  - For "both" target: .cursorrules/.cursor/rules/ exist (3+3 pts), cross-platform parity (2 pts)
@@ -1278,12 +1290,12 @@ Bonus (5 pts):
1278
1290
  - Hooks configured (2 pts), AGENTS.md (1 pt), OpenSkills format (2 pts) \u2014 handled by caliber
1279
1291
 
1280
1292
  OUTPUT SIZE CONSTRAINTS \u2014 these are critical:
1281
- - CLAUDE.md: MUST be under 100 lines for maximum score. Aim for 70-90 lines. Be extremely concise \u2014 only commands, architecture overview, and key conventions. Use bullet points and tables, not prose.
1293
+ - CLAUDE.md / AGENTS.md: MUST be under 100 lines for maximum score. Aim for 70-90 lines. Be extremely concise \u2014 only commands, architecture overview, and key conventions. Use bullet points and tables, not prose.
1282
1294
  - Skills: generate exactly 3 skills per target platform. Only go above 3 for large multi-framework projects.
1283
1295
  - Each skill content: max 150 lines. Focus on patterns and examples, not exhaustive docs.
1284
1296
  - Cursor rules: max 5 .mdc files.
1285
1297
  - If the project is large, prioritize depth on the 3-4 most critical tools over breadth across everything.`;
1286
- var REFINE_SYSTEM_PROMPT = `You are an expert at modifying coding agent configurations (Claude Code and Cursor).
1298
+ var REFINE_SYSTEM_PROMPT = `You are an expert at modifying coding agent configurations (Claude Code, Cursor, and Codex).
1287
1299
 
1288
1300
  You will receive the current AgentSetup JSON and a user request describing what to change.
1289
1301
 
@@ -1291,7 +1303,7 @@ Apply the requested changes to the setup and return the complete updated AgentSe
1291
1303
 
1292
1304
  AgentSetup schema:
1293
1305
  {
1294
- "targetAgent": "claude" | "cursor" | "both",
1306
+ "targetAgent": ["claude", "cursor", "codex"] (array of selected agents),
1295
1307
  "fileDescriptions": {
1296
1308
  "<file-path>": "reason for this change (max 80 chars)"
1297
1309
  },
@@ -1302,6 +1314,10 @@ AgentSetup schema:
1302
1314
  "claudeMd": "string (markdown content for CLAUDE.md)",
1303
1315
  "skills": [{ "name": "string (kebab-case)", "description": "string", "content": "string (markdown body, no frontmatter)" }]
1304
1316
  },
1317
+ "codex": {
1318
+ "agentsMd": "string (markdown content for AGENTS.md)",
1319
+ "skills": [{ "name": "string (kebab-case)", "description": "string", "content": "string (markdown body, no frontmatter)" }]
1320
+ },
1305
1321
  "cursor": {
1306
1322
  "skills": [{ "name": "string (kebab-case)", "description": "string", "content": "string (markdown body, no frontmatter)" }],
1307
1323
  "rules": [{ "filename": "string.mdc", "content": "string (with frontmatter)" }]
@@ -1318,7 +1334,7 @@ var REFRESH_SYSTEM_PROMPT = `You are an expert at maintaining coding project doc
1318
1334
 
1319
1335
  You will receive:
1320
1336
  1. Git diffs showing what code changed
1321
- 2. Current contents of documentation files (CLAUDE.md, README.md, skills, cursor skills, cursor rules)
1337
+ 2. Current contents of documentation files (CLAUDE.md, AGENTS.md, README.md, skills, cursor skills, cursor rules)
1322
1338
  3. Project context (languages, frameworks)
1323
1339
 
1324
1340
  Rules:
@@ -1329,6 +1345,7 @@ Rules:
1329
1345
  - Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
1330
1346
  - If a doc doesn't need updating, return null for it
1331
1347
  - For CLAUDE.md: update commands, architecture notes, conventions, key files if the diffs affect them
1348
+ - For AGENTS.md: same as CLAUDE.md \u2014 this is the primary instructions file for Codex users
1332
1349
  - For README.md: update setup instructions, API docs, or feature descriptions if affected
1333
1350
  - For cursor skills: update skill content if the diffs affect their domains
1334
1351
 
@@ -1336,13 +1353,14 @@ Return a JSON object with this exact shape:
1336
1353
  {
1337
1354
  "updatedDocs": {
1338
1355
  "claudeMd": "<updated content or null>",
1356
+ "agentsMd": "<updated content or null>",
1339
1357
  "readmeMd": "<updated content or null>",
1340
1358
  "cursorRules": [{"filename": "name.mdc", "content": "..."}] or null,
1341
1359
  "cursorSkills": [{"slug": "string", "name": "string", "content": "..."}] or null,
1342
1360
  "claudeSkills": [{"filename": "name.md", "content": "..."}] or null
1343
1361
  },
1344
1362
  "changesSummary": "<1-2 sentence summary of what was updated and why>",
1345
- "docsUpdated": ["CLAUDE.md", "README.md"]
1363
+ "docsUpdated": ["CLAUDE.md", "AGENTS.md", "README.md"]
1346
1364
  }
1347
1365
 
1348
1366
  Respond with ONLY the JSON object, no markdown fences or extra text.`;
@@ -1361,7 +1379,7 @@ Your job is to reason deeply about these events and identify:
1361
1379
  From these observations, produce:
1362
1380
 
1363
1381
  ### claudeMdLearnedSection
1364
- A markdown section with concise, actionable bullet points that should be added to the project's CLAUDE.md file. Each bullet should be a concrete instruction that prevents a past mistake or encodes a discovered convention. Examples:
1382
+ A markdown section with concise, actionable bullet points that should be added to the project's primary instructions file (CLAUDE.md for Claude Code, AGENTS.md for Codex). Each bullet should be a concrete instruction that prevents a past mistake or encodes a discovered convention. Examples:
1365
1383
  - "Always run \`npm install\` before \`npm run build\` in this project"
1366
1384
  - "The test database requires \`DATABASE_URL\` to be set \u2014 use \`source .env.test\` first"
1367
1385
  - "TypeScript strict mode is enabled \u2014 never use \`any\`, use \`unknown\` with type guards"
@@ -1644,7 +1662,7 @@ function truncate(text, maxChars) {
1644
1662
  function buildGeneratePrompt(fingerprint, targetAgent, prompt, failingChecks, currentScore, passingChecks) {
1645
1663
  const parts = [];
1646
1664
  const existing = fingerprint.existingConfigs;
1647
- const hasExistingConfigs = !!(existing.claudeMd || existing.claudeSettings || existing.claudeSkills?.length || existing.readmeMd || existing.cursorrules || existing.cursorRules?.length);
1665
+ const hasExistingConfigs = !!(existing.claudeMd || existing.claudeSettings || existing.claudeSkills?.length || existing.readmeMd || existing.agentsMd || existing.cursorrules || existing.cursorRules?.length);
1648
1666
  const isTargetedFix = failingChecks && failingChecks.length > 0 && currentScore !== void 0 && currentScore >= 95;
1649
1667
  if (isTargetedFix) {
1650
1668
  parts.push(`TARGETED FIX MODE \u2014 current score: ${currentScore}/100, target: ${targetAgent}`);
@@ -1682,6 +1700,9 @@ ${tree.join("\n")}`);
1682
1700
  if (existing.claudeMd) parts.push(`
1683
1701
  Existing CLAUDE.md:
1684
1702
  ${truncate(existing.claudeMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
1703
+ if (existing.agentsMd) parts.push(`
1704
+ Existing AGENTS.md:
1705
+ ${truncate(existing.agentsMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
1685
1706
  if (existing.readmeMd) parts.push(`
1686
1707
  Existing README.md:
1687
1708
  ${truncate(existing.readmeMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
@@ -1817,7 +1838,7 @@ Return the complete updated AgentSetup JSON incorporating the user's changes. Re
1817
1838
  }
1818
1839
 
1819
1840
  // src/writers/index.ts
1820
- import fs11 from "fs";
1841
+ import fs12 from "fs";
1821
1842
 
1822
1843
  // src/writers/claude/index.ts
1823
1844
  import fs7 from "fs";
@@ -1911,77 +1932,106 @@ function writeCursorConfig(config) {
1911
1932
  return written;
1912
1933
  }
1913
1934
 
1935
+ // src/writers/codex/index.ts
1936
+ import fs9 from "fs";
1937
+ import path8 from "path";
1938
+ function writeCodexConfig(config) {
1939
+ const written = [];
1940
+ fs9.writeFileSync("AGENTS.md", config.agentsMd);
1941
+ written.push("AGENTS.md");
1942
+ if (config.skills?.length) {
1943
+ for (const skill of config.skills) {
1944
+ const skillDir = path8.join(".agents", "skills", skill.name);
1945
+ if (!fs9.existsSync(skillDir)) fs9.mkdirSync(skillDir, { recursive: true });
1946
+ const skillPath = path8.join(skillDir, "SKILL.md");
1947
+ const frontmatter = [
1948
+ "---",
1949
+ `name: ${skill.name}`,
1950
+ `description: ${skill.description}`,
1951
+ "---",
1952
+ ""
1953
+ ].join("\n");
1954
+ fs9.writeFileSync(skillPath, frontmatter + skill.content);
1955
+ written.push(skillPath);
1956
+ }
1957
+ }
1958
+ return written;
1959
+ }
1960
+
1914
1961
  // src/writers/backup.ts
1915
1962
  init_constants();
1916
- import fs9 from "fs";
1917
- import path9 from "path";
1963
+ import fs10 from "fs";
1964
+ import path10 from "path";
1918
1965
  function createBackup(files) {
1919
1966
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1920
- const backupDir = path9.join(BACKUPS_DIR, timestamp);
1967
+ const backupDir = path10.join(BACKUPS_DIR, timestamp);
1921
1968
  for (const file of files) {
1922
- if (!fs9.existsSync(file)) continue;
1923
- const dest = path9.join(backupDir, file);
1924
- const destDir = path9.dirname(dest);
1925
- if (!fs9.existsSync(destDir)) {
1926
- fs9.mkdirSync(destDir, { recursive: true });
1969
+ if (!fs10.existsSync(file)) continue;
1970
+ const dest = path10.join(backupDir, file);
1971
+ const destDir = path10.dirname(dest);
1972
+ if (!fs10.existsSync(destDir)) {
1973
+ fs10.mkdirSync(destDir, { recursive: true });
1927
1974
  }
1928
- fs9.copyFileSync(file, dest);
1975
+ fs10.copyFileSync(file, dest);
1929
1976
  }
1930
1977
  return backupDir;
1931
1978
  }
1932
1979
  function restoreBackup(backupDir, file) {
1933
- const backupFile = path9.join(backupDir, file);
1934
- if (!fs9.existsSync(backupFile)) return false;
1935
- const destDir = path9.dirname(file);
1936
- if (!fs9.existsSync(destDir)) {
1937
- fs9.mkdirSync(destDir, { recursive: true });
1980
+ const backupFile = path10.join(backupDir, file);
1981
+ if (!fs10.existsSync(backupFile)) return false;
1982
+ const destDir = path10.dirname(file);
1983
+ if (!fs10.existsSync(destDir)) {
1984
+ fs10.mkdirSync(destDir, { recursive: true });
1938
1985
  }
1939
- fs9.copyFileSync(backupFile, file);
1986
+ fs10.copyFileSync(backupFile, file);
1940
1987
  return true;
1941
1988
  }
1942
1989
 
1943
1990
  // src/writers/manifest.ts
1944
1991
  init_constants();
1945
- import fs10 from "fs";
1992
+ import fs11 from "fs";
1946
1993
  import crypto from "crypto";
1947
1994
  function readManifest() {
1948
1995
  try {
1949
- if (!fs10.existsSync(MANIFEST_FILE)) return null;
1950
- return JSON.parse(fs10.readFileSync(MANIFEST_FILE, "utf-8"));
1996
+ if (!fs11.existsSync(MANIFEST_FILE)) return null;
1997
+ return JSON.parse(fs11.readFileSync(MANIFEST_FILE, "utf-8"));
1951
1998
  } catch {
1952
1999
  return null;
1953
2000
  }
1954
2001
  }
1955
2002
  function writeManifest(manifest) {
1956
- if (!fs10.existsSync(CALIBER_DIR)) {
1957
- fs10.mkdirSync(CALIBER_DIR, { recursive: true });
2003
+ if (!fs11.existsSync(CALIBER_DIR)) {
2004
+ fs11.mkdirSync(CALIBER_DIR, { recursive: true });
1958
2005
  }
1959
- fs10.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
2006
+ fs11.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
1960
2007
  }
1961
2008
  function fileChecksum(filePath) {
1962
- const content = fs10.readFileSync(filePath);
2009
+ const content = fs11.readFileSync(filePath);
1963
2010
  return crypto.createHash("sha256").update(content).digest("hex");
1964
2011
  }
1965
2012
 
1966
2013
  // src/writers/index.ts
1967
2014
  function writeSetup(setup) {
1968
2015
  const filesToWrite = getFilesToWrite(setup);
1969
- const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs11.existsSync(f));
2016
+ const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs12.existsSync(f));
1970
2017
  const existingFiles = [
1971
- ...filesToWrite.filter((f) => fs11.existsSync(f)),
2018
+ ...filesToWrite.filter((f) => fs12.existsSync(f)),
1972
2019
  ...filesToDelete
1973
2020
  ];
1974
2021
  const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
1975
2022
  const written = [];
1976
- if ((setup.targetAgent === "claude" || setup.targetAgent === "both") && setup.claude) {
2023
+ if (setup.targetAgent.includes("claude") && setup.claude) {
1977
2024
  written.push(...writeClaudeConfig(setup.claude));
1978
2025
  }
1979
- if ((setup.targetAgent === "cursor" || setup.targetAgent === "both") && setup.cursor) {
2026
+ if (setup.targetAgent.includes("cursor") && setup.cursor) {
1980
2027
  written.push(...writeCursorConfig(setup.cursor));
1981
2028
  }
2029
+ if (setup.targetAgent.includes("codex") && setup.codex) {
2030
+ written.push(...writeCodexConfig(setup.codex));
2031
+ }
1982
2032
  const deleted = [];
1983
2033
  for (const filePath of filesToDelete) {
1984
- fs11.unlinkSync(filePath);
2034
+ fs12.unlinkSync(filePath);
1985
2035
  deleted.push(filePath);
1986
2036
  }
1987
2037
  ensureGitignore();
@@ -2011,8 +2061,8 @@ function undoSetup() {
2011
2061
  const removed = [];
2012
2062
  for (const entry of manifest.entries) {
2013
2063
  if (entry.action === "created") {
2014
- if (fs11.existsSync(entry.path)) {
2015
- fs11.unlinkSync(entry.path);
2064
+ if (fs12.existsSync(entry.path)) {
2065
+ fs12.unlinkSync(entry.path);
2016
2066
  removed.push(entry.path);
2017
2067
  }
2018
2068
  } else if ((entry.action === "modified" || entry.action === "deleted") && manifest.backupDir) {
@@ -2022,14 +2072,14 @@ function undoSetup() {
2022
2072
  }
2023
2073
  }
2024
2074
  const { MANIFEST_FILE: MANIFEST_FILE2 } = (init_constants(), __toCommonJS(constants_exports));
2025
- if (fs11.existsSync(MANIFEST_FILE2)) {
2026
- fs11.unlinkSync(MANIFEST_FILE2);
2075
+ if (fs12.existsSync(MANIFEST_FILE2)) {
2076
+ fs12.unlinkSync(MANIFEST_FILE2);
2027
2077
  }
2028
2078
  return { restored, removed };
2029
2079
  }
2030
2080
  function getFilesToWrite(setup) {
2031
2081
  const files = [];
2032
- if ((setup.targetAgent === "claude" || setup.targetAgent === "both") && setup.claude) {
2082
+ if (setup.targetAgent.includes("claude") && setup.claude) {
2033
2083
  files.push("CLAUDE.md");
2034
2084
  if (setup.claude.mcpServers) files.push(".mcp.json");
2035
2085
  if (setup.claude.skills) {
@@ -2038,7 +2088,7 @@ function getFilesToWrite(setup) {
2038
2088
  }
2039
2089
  }
2040
2090
  }
2041
- if ((setup.targetAgent === "cursor" || setup.targetAgent === "both") && setup.cursor) {
2091
+ if (setup.targetAgent.includes("cursor") && setup.cursor) {
2042
2092
  if (setup.cursor.cursorrules) files.push(".cursorrules");
2043
2093
  if (setup.cursor.rules) {
2044
2094
  for (const r of setup.cursor.rules) files.push(`.cursor/rules/${r.filename}`);
@@ -2048,27 +2098,33 @@ function getFilesToWrite(setup) {
2048
2098
  }
2049
2099
  if (setup.cursor.mcpServers) files.push(".cursor/mcp.json");
2050
2100
  }
2101
+ if (setup.targetAgent.includes("codex") && setup.codex) {
2102
+ files.push("AGENTS.md");
2103
+ if (setup.codex.skills) {
2104
+ for (const s of setup.codex.skills) files.push(`.agents/skills/${s.name}/SKILL.md`);
2105
+ }
2106
+ }
2051
2107
  return files;
2052
2108
  }
2053
2109
  function ensureGitignore() {
2054
2110
  const gitignorePath = ".gitignore";
2055
- if (fs11.existsSync(gitignorePath)) {
2056
- const content = fs11.readFileSync(gitignorePath, "utf-8");
2111
+ if (fs12.existsSync(gitignorePath)) {
2112
+ const content = fs12.readFileSync(gitignorePath, "utf-8");
2057
2113
  if (!content.includes(".caliber/")) {
2058
- fs11.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
2114
+ fs12.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
2059
2115
  }
2060
2116
  } else {
2061
- fs11.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
2117
+ fs12.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
2062
2118
  }
2063
2119
  }
2064
2120
 
2065
2121
  // src/writers/staging.ts
2066
2122
  init_constants();
2067
- import fs12 from "fs";
2068
- import path10 from "path";
2069
- var STAGED_DIR = path10.join(CALIBER_DIR, "staged");
2070
- var PROPOSED_DIR = path10.join(STAGED_DIR, "proposed");
2071
- var CURRENT_DIR = path10.join(STAGED_DIR, "current");
2123
+ import fs13 from "fs";
2124
+ import path11 from "path";
2125
+ var STAGED_DIR = path11.join(CALIBER_DIR, "staged");
2126
+ var PROPOSED_DIR = path11.join(STAGED_DIR, "proposed");
2127
+ var CURRENT_DIR = path11.join(STAGED_DIR, "current");
2072
2128
  function normalizeContent(content) {
2073
2129
  return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
2074
2130
  }
@@ -2078,20 +2134,20 @@ function stageFiles(files, projectDir) {
2078
2134
  let modifiedFiles = 0;
2079
2135
  const stagedFiles = [];
2080
2136
  for (const file of files) {
2081
- const originalPath = path10.join(projectDir, file.path);
2082
- if (fs12.existsSync(originalPath)) {
2083
- const existing = fs12.readFileSync(originalPath, "utf-8");
2137
+ const originalPath = path11.join(projectDir, file.path);
2138
+ if (fs13.existsSync(originalPath)) {
2139
+ const existing = fs13.readFileSync(originalPath, "utf-8");
2084
2140
  if (normalizeContent(existing) === normalizeContent(file.content)) {
2085
2141
  continue;
2086
2142
  }
2087
2143
  }
2088
- const proposedPath = path10.join(PROPOSED_DIR, file.path);
2089
- fs12.mkdirSync(path10.dirname(proposedPath), { recursive: true });
2090
- fs12.writeFileSync(proposedPath, file.content);
2091
- if (fs12.existsSync(originalPath)) {
2092
- const currentPath = path10.join(CURRENT_DIR, file.path);
2093
- fs12.mkdirSync(path10.dirname(currentPath), { recursive: true });
2094
- fs12.copyFileSync(originalPath, currentPath);
2144
+ const proposedPath = path11.join(PROPOSED_DIR, file.path);
2145
+ fs13.mkdirSync(path11.dirname(proposedPath), { recursive: true });
2146
+ fs13.writeFileSync(proposedPath, file.content);
2147
+ if (fs13.existsSync(originalPath)) {
2148
+ const currentPath = path11.join(CURRENT_DIR, file.path);
2149
+ fs13.mkdirSync(path11.dirname(currentPath), { recursive: true });
2150
+ fs13.copyFileSync(originalPath, currentPath);
2095
2151
  modifiedFiles++;
2096
2152
  stagedFiles.push({ relativePath: file.path, proposedPath, currentPath, originalPath, isNew: false });
2097
2153
  } else {
@@ -2102,8 +2158,8 @@ function stageFiles(files, projectDir) {
2102
2158
  return { newFiles, modifiedFiles, stagedFiles };
2103
2159
  }
2104
2160
  function cleanupStaging() {
2105
- if (fs12.existsSync(STAGED_DIR)) {
2106
- fs12.rmSync(STAGED_DIR, { recursive: true, force: true });
2161
+ if (fs13.existsSync(STAGED_DIR)) {
2162
+ fs13.rmSync(STAGED_DIR, { recursive: true, force: true });
2107
2163
  }
2108
2164
  }
2109
2165
 
@@ -2149,24 +2205,24 @@ function openDiffsInEditor(editor, files) {
2149
2205
  import { createTwoFilesPatch } from "diff";
2150
2206
 
2151
2207
  // src/lib/hooks.ts
2152
- import fs13 from "fs";
2153
- import path11 from "path";
2208
+ import fs14 from "fs";
2209
+ import path12 from "path";
2154
2210
  import { execSync as execSync5 } from "child_process";
2155
- var SETTINGS_PATH = path11.join(".claude", "settings.json");
2211
+ var SETTINGS_PATH = path12.join(".claude", "settings.json");
2156
2212
  var HOOK_COMMAND = "caliber refresh --quiet";
2157
2213
  var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
2158
2214
  function readSettings() {
2159
- if (!fs13.existsSync(SETTINGS_PATH)) return {};
2215
+ if (!fs14.existsSync(SETTINGS_PATH)) return {};
2160
2216
  try {
2161
- return JSON.parse(fs13.readFileSync(SETTINGS_PATH, "utf-8"));
2217
+ return JSON.parse(fs14.readFileSync(SETTINGS_PATH, "utf-8"));
2162
2218
  } catch {
2163
2219
  return {};
2164
2220
  }
2165
2221
  }
2166
2222
  function writeSettings(settings) {
2167
- const dir = path11.dirname(SETTINGS_PATH);
2168
- if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
2169
- fs13.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
2223
+ const dir = path12.dirname(SETTINGS_PATH);
2224
+ if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
2225
+ fs14.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
2170
2226
  }
2171
2227
  function findHookIndex(sessionEnd) {
2172
2228
  return sessionEnd.findIndex(
@@ -2224,19 +2280,19 @@ ${PRECOMMIT_END}`;
2224
2280
  function getGitHooksDir() {
2225
2281
  try {
2226
2282
  const gitDir = execSync5("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
2227
- return path11.join(gitDir, "hooks");
2283
+ return path12.join(gitDir, "hooks");
2228
2284
  } catch {
2229
2285
  return null;
2230
2286
  }
2231
2287
  }
2232
2288
  function getPreCommitPath() {
2233
2289
  const hooksDir = getGitHooksDir();
2234
- return hooksDir ? path11.join(hooksDir, "pre-commit") : null;
2290
+ return hooksDir ? path12.join(hooksDir, "pre-commit") : null;
2235
2291
  }
2236
2292
  function isPreCommitHookInstalled() {
2237
2293
  const hookPath = getPreCommitPath();
2238
- if (!hookPath || !fs13.existsSync(hookPath)) return false;
2239
- const content = fs13.readFileSync(hookPath, "utf-8");
2294
+ if (!hookPath || !fs14.existsSync(hookPath)) return false;
2295
+ const content = fs14.readFileSync(hookPath, "utf-8");
2240
2296
  return content.includes(PRECOMMIT_START);
2241
2297
  }
2242
2298
  function installPreCommitHook() {
@@ -2245,43 +2301,43 @@ function installPreCommitHook() {
2245
2301
  }
2246
2302
  const hookPath = getPreCommitPath();
2247
2303
  if (!hookPath) return { installed: false, alreadyInstalled: false };
2248
- const hooksDir = path11.dirname(hookPath);
2249
- if (!fs13.existsSync(hooksDir)) fs13.mkdirSync(hooksDir, { recursive: true });
2304
+ const hooksDir = path12.dirname(hookPath);
2305
+ if (!fs14.existsSync(hooksDir)) fs14.mkdirSync(hooksDir, { recursive: true });
2250
2306
  let content = "";
2251
- if (fs13.existsSync(hookPath)) {
2252
- content = fs13.readFileSync(hookPath, "utf-8");
2307
+ if (fs14.existsSync(hookPath)) {
2308
+ content = fs14.readFileSync(hookPath, "utf-8");
2253
2309
  if (!content.endsWith("\n")) content += "\n";
2254
2310
  content += "\n" + PRECOMMIT_BLOCK + "\n";
2255
2311
  } else {
2256
2312
  content = "#!/bin/sh\n\n" + PRECOMMIT_BLOCK + "\n";
2257
2313
  }
2258
- fs13.writeFileSync(hookPath, content);
2259
- fs13.chmodSync(hookPath, 493);
2314
+ fs14.writeFileSync(hookPath, content);
2315
+ fs14.chmodSync(hookPath, 493);
2260
2316
  return { installed: true, alreadyInstalled: false };
2261
2317
  }
2262
2318
  function removePreCommitHook() {
2263
2319
  const hookPath = getPreCommitPath();
2264
- if (!hookPath || !fs13.existsSync(hookPath)) {
2320
+ if (!hookPath || !fs14.existsSync(hookPath)) {
2265
2321
  return { removed: false, notFound: true };
2266
2322
  }
2267
- let content = fs13.readFileSync(hookPath, "utf-8");
2323
+ let content = fs14.readFileSync(hookPath, "utf-8");
2268
2324
  if (!content.includes(PRECOMMIT_START)) {
2269
2325
  return { removed: false, notFound: true };
2270
2326
  }
2271
2327
  const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
2272
2328
  content = content.replace(regex, "\n");
2273
2329
  if (content.trim() === "#!/bin/sh" || content.trim() === "") {
2274
- fs13.unlinkSync(hookPath);
2330
+ fs14.unlinkSync(hookPath);
2275
2331
  } else {
2276
- fs13.writeFileSync(hookPath, content);
2332
+ fs14.writeFileSync(hookPath, content);
2277
2333
  }
2278
2334
  return { removed: true, notFound: false };
2279
2335
  }
2280
2336
 
2281
2337
  // src/lib/learning-hooks.ts
2282
- import fs14 from "fs";
2283
- import path12 from "path";
2284
- var SETTINGS_PATH2 = path12.join(".claude", "settings.json");
2338
+ import fs15 from "fs";
2339
+ import path13 from "path";
2340
+ var SETTINGS_PATH2 = path13.join(".claude", "settings.json");
2285
2341
  var HOOK_CONFIGS = [
2286
2342
  {
2287
2343
  event: "PostToolUse",
@@ -2300,17 +2356,17 @@ var HOOK_CONFIGS = [
2300
2356
  }
2301
2357
  ];
2302
2358
  function readSettings2() {
2303
- if (!fs14.existsSync(SETTINGS_PATH2)) return {};
2359
+ if (!fs15.existsSync(SETTINGS_PATH2)) return {};
2304
2360
  try {
2305
- return JSON.parse(fs14.readFileSync(SETTINGS_PATH2, "utf-8"));
2361
+ return JSON.parse(fs15.readFileSync(SETTINGS_PATH2, "utf-8"));
2306
2362
  } catch {
2307
2363
  return {};
2308
2364
  }
2309
2365
  }
2310
2366
  function writeSettings2(settings) {
2311
- const dir = path12.dirname(SETTINGS_PATH2);
2312
- if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
2313
- fs14.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
2367
+ const dir = path13.dirname(SETTINGS_PATH2);
2368
+ if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
2369
+ fs15.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
2314
2370
  }
2315
2371
  function hasLearningHook(matchers, command) {
2316
2372
  return matchers.some((entry) => entry.hooks?.some((h) => h.command === command));
@@ -2367,23 +2423,33 @@ function removeLearningHooks() {
2367
2423
 
2368
2424
  // src/lib/state.ts
2369
2425
  init_constants();
2370
- import fs15 from "fs";
2371
- import path13 from "path";
2426
+ import fs16 from "fs";
2427
+ import path14 from "path";
2372
2428
  import { execSync as execSync6 } from "child_process";
2373
- var STATE_FILE = path13.join(CALIBER_DIR, ".caliber-state.json");
2429
+ var STATE_FILE = path14.join(CALIBER_DIR, ".caliber-state.json");
2430
+ function normalizeTargetAgent(value) {
2431
+ if (Array.isArray(value)) return value;
2432
+ if (typeof value === "string") {
2433
+ if (value === "both") return ["claude", "cursor"];
2434
+ if (["claude", "cursor", "codex"].includes(value)) return [value];
2435
+ }
2436
+ return void 0;
2437
+ }
2374
2438
  function readState() {
2375
2439
  try {
2376
- if (!fs15.existsSync(STATE_FILE)) return null;
2377
- return JSON.parse(fs15.readFileSync(STATE_FILE, "utf-8"));
2440
+ if (!fs16.existsSync(STATE_FILE)) return null;
2441
+ const raw = JSON.parse(fs16.readFileSync(STATE_FILE, "utf-8"));
2442
+ if (raw.targetAgent) raw.targetAgent = normalizeTargetAgent(raw.targetAgent);
2443
+ return raw;
2378
2444
  } catch {
2379
2445
  return null;
2380
2446
  }
2381
2447
  }
2382
2448
  function writeState(state) {
2383
- if (!fs15.existsSync(CALIBER_DIR)) {
2384
- fs15.mkdirSync(CALIBER_DIR, { recursive: true });
2449
+ if (!fs16.existsSync(CALIBER_DIR)) {
2450
+ fs16.mkdirSync(CALIBER_DIR, { recursive: true });
2385
2451
  }
2386
- fs15.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
2452
+ fs16.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
2387
2453
  }
2388
2454
  function getCurrentHeadSha() {
2389
2455
  try {
@@ -2649,6 +2715,9 @@ var BOTH_ONLY_CHECKS = /* @__PURE__ */ new Set([
2649
2715
  "cross_platform_parity",
2650
2716
  "no_duplicate_content"
2651
2717
  ]);
2718
+ var CODEX_ONLY_CHECKS = /* @__PURE__ */ new Set([
2719
+ "codex_agents_md_exists"
2720
+ ]);
2652
2721
  var GRADE_THRESHOLDS = [
2653
2722
  { minScore: 85, grade: "A" },
2654
2723
  { minScore: 70, grade: "B" },
@@ -2666,15 +2735,15 @@ function computeGrade(score) {
2666
2735
  // src/scoring/checks/coverage.ts
2667
2736
  import { readFileSync, readdirSync } from "fs";
2668
2737
  import { join } from "path";
2669
- function readFileOrNull(path23) {
2738
+ function readFileOrNull(path24) {
2670
2739
  try {
2671
- return readFileSync(path23, "utf-8");
2740
+ return readFileSync(path24, "utf-8");
2672
2741
  } catch {
2673
2742
  return null;
2674
2743
  }
2675
2744
  }
2676
- function readJsonOrNull(path23) {
2677
- const content = readFileOrNull(path23);
2745
+ function readJsonOrNull(path24) {
2746
+ const content = readFileOrNull(path24);
2678
2747
  if (!content) return null;
2679
2748
  try {
2680
2749
  return JSON.parse(content);
@@ -2970,8 +3039,20 @@ function checkExistence(dir) {
2970
3039
  detail: hasCursorrules ? ".cursorrules found" : cursorRulesDir ? ".cursor/rules/ found" : "No Cursor rules",
2971
3040
  suggestion: cursorRulesExist ? void 0 : "Add .cursor/rules/ for Cursor users on your team"
2972
3041
  });
3042
+ const agentsMdExists = existsSync3(join2(dir, "AGENTS.md"));
3043
+ checks.push({
3044
+ id: "codex_agents_md_exists",
3045
+ name: "AGENTS.md exists",
3046
+ category: "existence",
3047
+ maxPoints: POINTS_CLAUDE_MD_EXISTS,
3048
+ earnedPoints: agentsMdExists ? POINTS_CLAUDE_MD_EXISTS : 0,
3049
+ passed: agentsMdExists,
3050
+ detail: agentsMdExists ? "Found at project root" : "Not found",
3051
+ suggestion: agentsMdExists ? void 0 : "Create AGENTS.md with project context for Codex"
3052
+ });
2973
3053
  const claudeSkills = countFiles(join2(dir, ".claude", "skills"), /\.(md|SKILL\.md)$/);
2974
- const skillCount = claudeSkills.length;
3054
+ const codexSkills = countFiles(join2(dir, ".agents", "skills"), /SKILL\.md$/);
3055
+ const skillCount = claudeSkills.length + codexSkills.length;
2975
3056
  const skillBase = skillCount >= 1 ? POINTS_SKILLS_EXIST : 0;
2976
3057
  const skillBonus = Math.min((skillCount - 1) * POINTS_SKILLS_BONUS_PER_EXTRA, POINTS_SKILLS_BONUS_CAP);
2977
3058
  const skillPoints = skillCount >= 1 ? skillBase + Math.max(0, skillBonus) : 0;
@@ -3030,9 +3111,9 @@ function checkExistence(dir) {
3030
3111
  // src/scoring/checks/quality.ts
3031
3112
  import { readFileSync as readFileSync3 } from "fs";
3032
3113
  import { join as join3 } from "path";
3033
- function readFileOrNull2(path23) {
3114
+ function readFileOrNull2(path24) {
3034
3115
  try {
3035
- return readFileSync3(path23, "utf-8");
3116
+ return readFileSync3(path24, "utf-8");
3036
3117
  } catch {
3037
3118
  return null;
3038
3119
  }
@@ -3044,11 +3125,13 @@ function checkQuality(dir) {
3044
3125
  const checks = [];
3045
3126
  const claudeMd = readFileOrNull2(join3(dir, "CLAUDE.md"));
3046
3127
  const cursorrules = readFileOrNull2(join3(dir, ".cursorrules"));
3047
- const allContent = [claudeMd, cursorrules].filter(Boolean);
3128
+ const agentsMd = readFileOrNull2(join3(dir, "AGENTS.md"));
3129
+ const allContent = [claudeMd, cursorrules, agentsMd].filter(Boolean);
3048
3130
  const combinedContent = allContent.join("\n");
3049
- const hasCommands = claudeMd ? COMMAND_PATTERNS.some((p) => p.test(claudeMd)) : false;
3050
- const matchedCommands = claudeMd ? COMMAND_PATTERNS.filter((p) => p.test(claudeMd)).map((p) => {
3051
- const m = claudeMd.match(p);
3131
+ const primaryInstructions = claudeMd ?? agentsMd;
3132
+ const hasCommands = primaryInstructions ? COMMAND_PATTERNS.some((p) => p.test(primaryInstructions)) : false;
3133
+ const matchedCommands = primaryInstructions ? COMMAND_PATTERNS.filter((p) => p.test(primaryInstructions)).map((p) => {
3134
+ const m = primaryInstructions.match(p);
3052
3135
  return m ? m[0] : "";
3053
3136
  }).filter(Boolean) : [];
3054
3137
  checks.push({
@@ -3058,11 +3141,11 @@ function checkQuality(dir) {
3058
3141
  maxPoints: POINTS_HAS_COMMANDS,
3059
3142
  earnedPoints: hasCommands ? POINTS_HAS_COMMANDS : 0,
3060
3143
  passed: hasCommands,
3061
- detail: hasCommands ? `Found: ${matchedCommands.slice(0, 3).join(", ")}` : claudeMd ? "No build/test/lint commands detected" : "No CLAUDE.md to check",
3062
- suggestion: hasCommands ? void 0 : "Add build, test, and lint commands to CLAUDE.md"
3144
+ detail: hasCommands ? `Found: ${matchedCommands.slice(0, 3).join(", ")}` : primaryInstructions ? "No build/test/lint commands detected" : "No instructions file to check",
3145
+ suggestion: hasCommands ? void 0 : "Add build, test, and lint commands to your instructions file"
3063
3146
  });
3064
- const primaryFile = claudeMd ?? cursorrules;
3065
- const primaryName = claudeMd ? "CLAUDE.md" : cursorrules ? ".cursorrules" : null;
3147
+ const primaryFile = claudeMd ?? agentsMd ?? cursorrules;
3148
+ const primaryName = claudeMd ? "CLAUDE.md" : agentsMd ? "AGENTS.md" : cursorrules ? ".cursorrules" : null;
3066
3149
  let bloatPoints = 0;
3067
3150
  let lineCount = 0;
3068
3151
  if (primaryFile) {
@@ -3183,15 +3266,15 @@ function checkQuality(dir) {
3183
3266
  // src/scoring/checks/accuracy.ts
3184
3267
  import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
3185
3268
  import { join as join4 } from "path";
3186
- function readFileOrNull3(path23) {
3269
+ function readFileOrNull3(path24) {
3187
3270
  try {
3188
- return readFileSync4(path23, "utf-8");
3271
+ return readFileSync4(path24, "utf-8");
3189
3272
  } catch {
3190
3273
  return null;
3191
3274
  }
3192
3275
  }
3193
- function readJsonOrNull2(path23) {
3194
- const content = readFileOrNull3(path23);
3276
+ function readJsonOrNull2(path24) {
3277
+ const content = readFileOrNull3(path24);
3195
3278
  if (!content) return null;
3196
3279
  try {
3197
3280
  return JSON.parse(content);
@@ -3372,11 +3455,11 @@ function checkAccuracy(dir) {
3372
3455
  }
3373
3456
 
3374
3457
  // src/scoring/checks/freshness.ts
3375
- import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
3458
+ import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
3376
3459
  import { join as join5 } from "path";
3377
- function readFileOrNull4(path23) {
3460
+ function readFileOrNull4(path24) {
3378
3461
  try {
3379
- return readFileSync5(path23, "utf-8");
3462
+ return readFileSync5(path24, "utf-8");
3380
3463
  } catch {
3381
3464
  return null;
3382
3465
  }
@@ -3394,11 +3477,14 @@ function daysSinceModified(filePath) {
3394
3477
  function checkFreshness(dir) {
3395
3478
  const checks = [];
3396
3479
  const claudeMdPath = join5(dir, "CLAUDE.md");
3397
- const daysOld = daysSinceModified(claudeMdPath);
3480
+ const agentsMdPath = join5(dir, "AGENTS.md");
3481
+ const primaryPath = existsSync6(claudeMdPath) ? claudeMdPath : agentsMdPath;
3482
+ const primaryName = existsSync6(claudeMdPath) ? "CLAUDE.md" : "AGENTS.md";
3483
+ const daysOld = daysSinceModified(primaryPath);
3398
3484
  let freshnessPoints = 0;
3399
3485
  let freshnessDetail = "";
3400
3486
  if (daysOld === null) {
3401
- freshnessDetail = "No CLAUDE.md to check";
3487
+ freshnessDetail = "No instructions file to check";
3402
3488
  } else {
3403
3489
  const threshold = FRESHNESS_THRESHOLDS.find((t) => daysOld <= t.maxDaysOld);
3404
3490
  freshnessPoints = threshold ? threshold.points : 0;
@@ -3406,16 +3492,17 @@ function checkFreshness(dir) {
3406
3492
  }
3407
3493
  checks.push({
3408
3494
  id: "claude_md_freshness",
3409
- name: "CLAUDE.md freshness",
3495
+ name: `${primaryName} freshness`,
3410
3496
  category: "freshness",
3411
3497
  maxPoints: POINTS_FRESHNESS,
3412
3498
  earnedPoints: freshnessPoints,
3413
3499
  passed: freshnessPoints >= 4,
3414
3500
  detail: freshnessDetail,
3415
- suggestion: daysOld !== null && freshnessPoints < 4 ? `CLAUDE.md is ${daysOld} days old \u2014 run \`caliber refresh\` to update it` : void 0
3501
+ suggestion: daysOld !== null && freshnessPoints < 4 ? `${primaryName} is ${daysOld} days old \u2014 run \`caliber refresh\` to update it` : void 0
3416
3502
  });
3417
3503
  const filesToScan = [
3418
3504
  "CLAUDE.md",
3505
+ "AGENTS.md",
3419
3506
  ".cursorrules",
3420
3507
  ".claude/settings.json",
3421
3508
  ".claude/settings.local.json",
@@ -3486,9 +3573,9 @@ function checkFreshness(dir) {
3486
3573
  import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "fs";
3487
3574
  import { execSync as execSync7 } from "child_process";
3488
3575
  import { join as join6 } from "path";
3489
- function readFileOrNull5(path23) {
3576
+ function readFileOrNull5(path24) {
3490
3577
  try {
3491
- return readFileSync6(path23, "utf-8");
3578
+ return readFileSync6(path24, "utf-8");
3492
3579
  } catch {
3493
3580
  return null;
3494
3581
  }
@@ -3583,22 +3670,22 @@ function checkBonus(dir) {
3583
3670
 
3584
3671
  // src/scoring/dismissed.ts
3585
3672
  init_constants();
3586
- import fs16 from "fs";
3587
- import path14 from "path";
3588
- var DISMISSED_FILE = path14.join(CALIBER_DIR, "dismissed-checks.json");
3673
+ import fs17 from "fs";
3674
+ import path15 from "path";
3675
+ var DISMISSED_FILE = path15.join(CALIBER_DIR, "dismissed-checks.json");
3589
3676
  function readDismissedChecks() {
3590
3677
  try {
3591
- if (!fs16.existsSync(DISMISSED_FILE)) return [];
3592
- return JSON.parse(fs16.readFileSync(DISMISSED_FILE, "utf-8"));
3678
+ if (!fs17.existsSync(DISMISSED_FILE)) return [];
3679
+ return JSON.parse(fs17.readFileSync(DISMISSED_FILE, "utf-8"));
3593
3680
  } catch {
3594
3681
  return [];
3595
3682
  }
3596
3683
  }
3597
3684
  function writeDismissedChecks(checks) {
3598
- if (!fs16.existsSync(CALIBER_DIR)) {
3599
- fs16.mkdirSync(CALIBER_DIR, { recursive: true });
3685
+ if (!fs17.existsSync(CALIBER_DIR)) {
3686
+ fs17.mkdirSync(CALIBER_DIR, { recursive: true });
3600
3687
  }
3601
- fs16.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
3688
+ fs17.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
3602
3689
  }
3603
3690
  function getDismissedIds() {
3604
3691
  return new Set(readDismissedChecks().map((c) => c.id));
@@ -3614,21 +3701,19 @@ function sumCategory(checks, category) {
3614
3701
  }
3615
3702
  function filterChecksForTarget(checks, target) {
3616
3703
  return checks.filter((c) => {
3617
- if (target === "claude") {
3618
- return !CURSOR_ONLY_CHECKS.has(c.id) && !BOTH_ONLY_CHECKS.has(c.id);
3619
- }
3620
- if (target === "cursor") {
3621
- return !CLAUDE_ONLY_CHECKS.has(c.id) && !BOTH_ONLY_CHECKS.has(c.id);
3622
- }
3704
+ if (CLAUDE_ONLY_CHECKS.has(c.id)) return target.includes("claude");
3705
+ if (CURSOR_ONLY_CHECKS.has(c.id)) return target.includes("cursor");
3706
+ if (CODEX_ONLY_CHECKS.has(c.id)) return target.includes("codex");
3707
+ if (BOTH_ONLY_CHECKS.has(c.id)) return target.includes("claude") && target.includes("cursor");
3623
3708
  return true;
3624
3709
  });
3625
3710
  }
3626
3711
  function detectTargetAgent(dir) {
3627
- const hasClaude = existsSync8(join7(dir, "CLAUDE.md")) || existsSync8(join7(dir, ".claude", "skills"));
3628
- const hasCursor = existsSync8(join7(dir, ".cursorrules")) || existsSync8(join7(dir, ".cursor", "rules"));
3629
- if (hasClaude && hasCursor) return "both";
3630
- if (hasCursor) return "cursor";
3631
- return "claude";
3712
+ const agents = [];
3713
+ if (existsSync8(join7(dir, "CLAUDE.md")) || existsSync8(join7(dir, ".claude", "skills"))) agents.push("claude");
3714
+ if (existsSync8(join7(dir, ".cursorrules")) || existsSync8(join7(dir, ".cursor", "rules"))) agents.push("cursor");
3715
+ if (existsSync8(join7(dir, ".codex")) || existsSync8(join7(dir, ".agents", "skills"))) agents.push("codex");
3716
+ return agents.length > 0 ? agents : ["claude"];
3632
3717
  }
3633
3718
  function computeLocalScore(dir, targetAgent) {
3634
3719
  const target = targetAgent ?? detectTargetAgent(dir);
@@ -3665,6 +3750,11 @@ function computeLocalScore(dir, targetAgent) {
3665
3750
 
3666
3751
  // src/scoring/display.ts
3667
3752
  import chalk3 from "chalk";
3753
+ var AGENT_DISPLAY_NAMES = {
3754
+ claude: "Claude Code",
3755
+ cursor: "Cursor",
3756
+ codex: "Codex"
3757
+ };
3668
3758
  var CATEGORY_LABELS = {
3669
3759
  existence: "FILES & SETUP",
3670
3760
  quality: "QUALITY",
@@ -3707,7 +3797,7 @@ function formatCheck(check) {
3707
3797
  }
3708
3798
  function displayScore(result) {
3709
3799
  const gc = gradeColor(result.grade);
3710
- const agentLabel = result.targetAgent === "both" ? "Claude Code + Cursor" : result.targetAgent === "claude" ? "Claude Code" : "Cursor";
3800
+ const agentLabel = result.targetAgent.map((a) => AGENT_DISPLAY_NAMES[a] || a).join(" + ");
3711
3801
  console.log("");
3712
3802
  console.log(chalk3.gray(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
3713
3803
  console.log(chalk3.gray(" \u2502") + " " + chalk3.gray("\u2502"));
@@ -3733,7 +3823,7 @@ function displayScore(result) {
3733
3823
  }
3734
3824
  function displayScoreSummary(result) {
3735
3825
  const gc = gradeColor(result.grade);
3736
- const agentLabel = result.targetAgent === "both" ? "Claude Code + Cursor" : result.targetAgent === "claude" ? "Claude Code" : "Cursor";
3826
+ const agentLabel = result.targetAgent.map((a) => AGENT_DISPLAY_NAMES[a] || a).join(" + ");
3737
3827
  console.log("");
3738
3828
  console.log(
3739
3829
  chalk3.gray(" ") + gc(`${result.score}/${result.maxScore}`) + chalk3.gray(` (Grade ${result.grade})`) + chalk3.gray(` \xB7 ${agentLabel}`) + chalk3.gray(` \xB7 ${progressBar(result.score, result.maxScore, 20)}`)
@@ -3799,8 +3889,8 @@ function displayScoreDelta(before, after) {
3799
3889
  import chalk4 from "chalk";
3800
3890
  import ora from "ora";
3801
3891
  import readline3 from "readline";
3802
- import fs17 from "fs";
3803
- import path15 from "path";
3892
+ import fs18 from "fs";
3893
+ import path16 from "path";
3804
3894
 
3805
3895
  // src/mcp/search.ts
3806
3896
  var AWESOME_MCP_URL = "https://raw.githubusercontent.com/punkpeye/awesome-mcp-servers/main/README.md";
@@ -4147,36 +4237,36 @@ async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
4147
4237
  if (installedNames.length === 0) {
4148
4238
  return { installed: 0, names: [] };
4149
4239
  }
4150
- if (targetAgent === "claude" || targetAgent === "both") {
4151
- writeMcpJson(path15.join(dir, ".mcp.json"), mcpServers);
4240
+ if (targetAgent.includes("claude") || targetAgent.includes("codex")) {
4241
+ writeMcpJson(path16.join(dir, ".mcp.json"), mcpServers);
4152
4242
  }
4153
- if (targetAgent === "cursor" || targetAgent === "both") {
4154
- const cursorDir = path15.join(dir, ".cursor");
4155
- if (!fs17.existsSync(cursorDir)) fs17.mkdirSync(cursorDir, { recursive: true });
4156
- writeMcpJson(path15.join(cursorDir, "mcp.json"), mcpServers);
4243
+ if (targetAgent.includes("cursor")) {
4244
+ const cursorDir = path16.join(dir, ".cursor");
4245
+ if (!fs18.existsSync(cursorDir)) fs18.mkdirSync(cursorDir, { recursive: true });
4246
+ writeMcpJson(path16.join(cursorDir, "mcp.json"), mcpServers);
4157
4247
  }
4158
4248
  return { installed: installedNames.length, names: installedNames };
4159
4249
  }
4160
4250
  function writeMcpJson(filePath, mcpServers) {
4161
4251
  let existing = {};
4162
4252
  try {
4163
- if (fs17.existsSync(filePath)) {
4164
- const parsed = JSON.parse(fs17.readFileSync(filePath, "utf-8"));
4253
+ if (fs18.existsSync(filePath)) {
4254
+ const parsed = JSON.parse(fs18.readFileSync(filePath, "utf-8"));
4165
4255
  if (parsed.mcpServers) existing = parsed.mcpServers;
4166
4256
  }
4167
4257
  } catch {
4168
4258
  }
4169
4259
  const merged = { ...existing, ...mcpServers };
4170
- fs17.writeFileSync(filePath, JSON.stringify({ mcpServers: merged }, null, 2) + "\n");
4260
+ fs18.writeFileSync(filePath, JSON.stringify({ mcpServers: merged }, null, 2) + "\n");
4171
4261
  }
4172
4262
  function getExistingMcpNames(fingerprint, targetAgent) {
4173
4263
  const names = [];
4174
- if (targetAgent === "claude" || targetAgent === "both") {
4264
+ if (targetAgent.includes("claude")) {
4175
4265
  if (fingerprint.existingConfigs.claudeMcpServers) {
4176
4266
  names.push(...Object.keys(fingerprint.existingConfigs.claudeMcpServers).map((k) => k.toLowerCase()));
4177
4267
  }
4178
4268
  }
4179
- if (targetAgent === "cursor" || targetAgent === "both") {
4269
+ if (targetAgent.includes("cursor")) {
4180
4270
  if (fingerprint.existingConfigs.cursorMcpServers) {
4181
4271
  names.push(...Object.keys(fingerprint.existingConfigs.cursorMcpServers).map((k) => k.toLowerCase()));
4182
4272
  }
@@ -4355,7 +4445,7 @@ async function initCommand(options) {
4355
4445
  }
4356
4446
  const baselineScore = computeLocalScore(process.cwd(), targetAgent);
4357
4447
  displayScoreSummary(baselineScore);
4358
- const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length);
4448
+ const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length || fingerprint.existingConfigs.agentsMd);
4359
4449
  const NON_LLM_CHECKS = /* @__PURE__ */ new Set(["hooks_configured", "agents_md_exists", "permissions_configured", "mcp_servers"]);
4360
4450
  if (hasExistingConfig && baselineScore.score === 100) {
4361
4451
  console.log(chalk5.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
@@ -4656,7 +4746,7 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
4656
4746
  }
4657
4747
  function summarizeSetup(action, setup) {
4658
4748
  const descriptions = setup.fileDescriptions;
4659
- const files = descriptions ? Object.entries(descriptions).map(([path23, desc]) => ` ${path23}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
4749
+ const files = descriptions ? Object.entries(descriptions).map(([path24, desc]) => ` ${path24}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
4660
4750
  return `${action}. Files:
4661
4751
  ${files}`;
4662
4752
  }
@@ -4717,22 +4807,28 @@ function promptInput3(question) {
4717
4807
  });
4718
4808
  }
4719
4809
  async function promptAgent() {
4720
- return select2({
4721
- message: "Which coding agent are you using?",
4810
+ const selected = await checkbox({
4811
+ message: "Which coding agents do you use? (toggle with space)",
4722
4812
  choices: [
4723
4813
  { name: "Claude Code", value: "claude" },
4724
4814
  { name: "Cursor", value: "cursor" },
4725
- { name: "Both", value: "both" }
4726
- ]
4815
+ { name: "Codex (OpenAI)", value: "codex" }
4816
+ ],
4817
+ validate: (items) => {
4818
+ if (items.length === 0) return "At least one agent must be selected";
4819
+ return true;
4820
+ }
4727
4821
  });
4822
+ return selected;
4728
4823
  }
4729
4824
  async function promptHookType(targetAgent) {
4730
4825
  const choices = [];
4731
- if (targetAgent === "claude" || targetAgent === "both") {
4826
+ const hasClaude = targetAgent.includes("claude");
4827
+ if (hasClaude) {
4732
4828
  choices.push({ name: "Claude Code hook (auto-refresh on session end)", value: "claude" });
4733
4829
  }
4734
4830
  choices.push({ name: "Git pre-commit hook (refresh before each commit)", value: "precommit" });
4735
- if (targetAgent === "claude" || targetAgent === "both") {
4831
+ if (hasClaude) {
4736
4832
  choices.push({ name: "Both (Claude Code + pre-commit)", value: "both" });
4737
4833
  }
4738
4834
  choices.push({ name: "Skip for now", value: "skip" });
@@ -4776,8 +4872,8 @@ async function openReview(method, stagedFiles) {
4776
4872
  return;
4777
4873
  }
4778
4874
  const fileInfos = stagedFiles.map((file) => {
4779
- const proposed = fs18.readFileSync(file.proposedPath, "utf-8");
4780
- const current = file.currentPath ? fs18.readFileSync(file.currentPath, "utf-8") : "";
4875
+ const proposed = fs19.readFileSync(file.proposedPath, "utf-8");
4876
+ const current = file.currentPath ? fs19.readFileSync(file.currentPath, "utf-8") : "";
4781
4877
  const patch = createTwoFilesPatch(
4782
4878
  file.isNew ? "/dev/null" : file.relativePath,
4783
4879
  file.relativePath,
@@ -4960,7 +5056,7 @@ function printSetupSummary(setup) {
4960
5056
  };
4961
5057
  if (claude) {
4962
5058
  if (claude.claudeMd) {
4963
- const icon = fs18.existsSync("CLAUDE.md") ? chalk5.yellow("~") : chalk5.green("+");
5059
+ const icon = fs19.existsSync("CLAUDE.md") ? chalk5.yellow("~") : chalk5.green("+");
4964
5060
  const desc = getDescription("CLAUDE.md");
4965
5061
  console.log(` ${icon} ${chalk5.bold("CLAUDE.md")}`);
4966
5062
  if (desc) console.log(chalk5.dim(` ${desc}`));
@@ -4970,7 +5066,28 @@ function printSetupSummary(setup) {
4970
5066
  if (Array.isArray(skills) && skills.length > 0) {
4971
5067
  for (const skill of skills) {
4972
5068
  const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
4973
- const icon = fs18.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
5069
+ const icon = fs19.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
5070
+ const desc = getDescription(skillPath);
5071
+ console.log(` ${icon} ${chalk5.bold(skillPath)}`);
5072
+ console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
5073
+ console.log("");
5074
+ }
5075
+ }
5076
+ }
5077
+ const codex = setup.codex;
5078
+ if (codex) {
5079
+ if (codex.agentsMd) {
5080
+ const icon = fs19.existsSync("AGENTS.md") ? chalk5.yellow("~") : chalk5.green("+");
5081
+ const desc = getDescription("AGENTS.md");
5082
+ console.log(` ${icon} ${chalk5.bold("AGENTS.md")}`);
5083
+ if (desc) console.log(chalk5.dim(` ${desc}`));
5084
+ console.log("");
5085
+ }
5086
+ const codexSkills = codex.skills;
5087
+ if (Array.isArray(codexSkills) && codexSkills.length > 0) {
5088
+ for (const skill of codexSkills) {
5089
+ const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
5090
+ const icon = fs19.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
4974
5091
  const desc = getDescription(skillPath);
4975
5092
  console.log(` ${icon} ${chalk5.bold(skillPath)}`);
4976
5093
  console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
@@ -4980,7 +5097,7 @@ function printSetupSummary(setup) {
4980
5097
  }
4981
5098
  if (cursor) {
4982
5099
  if (cursor.cursorrules) {
4983
- const icon = fs18.existsSync(".cursorrules") ? chalk5.yellow("~") : chalk5.green("+");
5100
+ const icon = fs19.existsSync(".cursorrules") ? chalk5.yellow("~") : chalk5.green("+");
4984
5101
  const desc = getDescription(".cursorrules");
4985
5102
  console.log(` ${icon} ${chalk5.bold(".cursorrules")}`);
4986
5103
  if (desc) console.log(chalk5.dim(` ${desc}`));
@@ -4990,7 +5107,7 @@ function printSetupSummary(setup) {
4990
5107
  if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
4991
5108
  for (const skill of cursorSkills) {
4992
5109
  const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
4993
- const icon = fs18.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
5110
+ const icon = fs19.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
4994
5111
  const desc = getDescription(skillPath);
4995
5112
  console.log(` ${icon} ${chalk5.bold(skillPath)}`);
4996
5113
  console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
@@ -5001,7 +5118,7 @@ function printSetupSummary(setup) {
5001
5118
  if (Array.isArray(rules) && rules.length > 0) {
5002
5119
  for (const rule of rules) {
5003
5120
  const rulePath = `.cursor/rules/${rule.filename}`;
5004
- const icon = fs18.existsSync(rulePath) ? chalk5.yellow("~") : chalk5.green("+");
5121
+ const icon = fs19.existsSync(rulePath) ? chalk5.yellow("~") : chalk5.green("+");
5005
5122
  const desc = getDescription(rulePath);
5006
5123
  console.log(` ${icon} ${chalk5.bold(rulePath)}`);
5007
5124
  if (desc) {
@@ -5014,7 +5131,7 @@ function printSetupSummary(setup) {
5014
5131
  }
5015
5132
  }
5016
5133
  }
5017
- if (!fs18.existsSync("AGENTS.md")) {
5134
+ if (!codex && !fs19.existsSync("AGENTS.md")) {
5018
5135
  console.log(` ${chalk5.green("+")} ${chalk5.bold("AGENTS.md")}`);
5019
5136
  console.log(chalk5.dim(" Cross-agent coordination file"));
5020
5137
  console.log("");
@@ -5042,8 +5159,8 @@ function ensurePermissions() {
5042
5159
  const settingsPath = ".claude/settings.json";
5043
5160
  let settings = {};
5044
5161
  try {
5045
- if (fs18.existsSync(settingsPath)) {
5046
- settings = JSON.parse(fs18.readFileSync(settingsPath, "utf-8"));
5162
+ if (fs19.existsSync(settingsPath)) {
5163
+ settings = JSON.parse(fs19.readFileSync(settingsPath, "utf-8"));
5047
5164
  }
5048
5165
  } catch {
5049
5166
  }
@@ -5057,13 +5174,14 @@ function ensurePermissions() {
5057
5174
  "Bash(git *)"
5058
5175
  ];
5059
5176
  settings.permissions = permissions;
5060
- if (!fs18.existsSync(".claude")) fs18.mkdirSync(".claude", { recursive: true });
5061
- fs18.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5177
+ if (!fs19.existsSync(".claude")) fs19.mkdirSync(".claude", { recursive: true });
5178
+ fs19.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5062
5179
  }
5063
5180
  function collectSetupFiles(setup) {
5064
5181
  const files = [];
5065
5182
  const claude = setup.claude;
5066
5183
  const cursor = setup.cursor;
5184
+ const codex = setup.codex;
5067
5185
  if (claude) {
5068
5186
  if (claude.claudeMd) files.push({ path: "CLAUDE.md", content: claude.claudeMd });
5069
5187
  const skills = claude.skills;
@@ -5073,6 +5191,15 @@ function collectSetupFiles(setup) {
5073
5191
  }
5074
5192
  }
5075
5193
  }
5194
+ if (codex) {
5195
+ if (codex.agentsMd) files.push({ path: "AGENTS.md", content: codex.agentsMd });
5196
+ const codexSkills = codex.skills;
5197
+ if (Array.isArray(codexSkills)) {
5198
+ for (const skill of codexSkills) {
5199
+ files.push({ path: `.agents/skills/${skill.name}/SKILL.md`, content: buildSkillContent(skill) });
5200
+ }
5201
+ }
5202
+ }
5076
5203
  if (cursor) {
5077
5204
  if (cursor.cursorrules) files.push({ path: ".cursorrules", content: cursor.cursorrules });
5078
5205
  const cursorSkills = cursor.skills;
@@ -5088,7 +5215,8 @@ function collectSetupFiles(setup) {
5088
5215
  }
5089
5216
  }
5090
5217
  }
5091
- if (!fs18.existsSync("AGENTS.md")) {
5218
+ const hasCodexAgentsMd = codex && codex.agentsMd;
5219
+ if (!fs19.existsSync("AGENTS.md") && !hasCodexAgentsMd) {
5092
5220
  const agentRefs = [];
5093
5221
  if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
5094
5222
  if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
@@ -5139,7 +5267,7 @@ function undoCommand() {
5139
5267
 
5140
5268
  // src/commands/status.ts
5141
5269
  import chalk7 from "chalk";
5142
- import fs19 from "fs";
5270
+ import fs20 from "fs";
5143
5271
  async function statusCommand(options) {
5144
5272
  const config = loadConfig();
5145
5273
  const manifest = readManifest();
@@ -5165,7 +5293,7 @@ async function statusCommand(options) {
5165
5293
  }
5166
5294
  console.log(` Files managed: ${chalk7.cyan(manifest.entries.length.toString())}`);
5167
5295
  for (const entry of manifest.entries) {
5168
- const exists = fs19.existsSync(entry.path);
5296
+ const exists = fs20.existsSync(entry.path);
5169
5297
  const icon = exists ? chalk7.green("\u2713") : chalk7.red("\u2717");
5170
5298
  console.log(` ${icon} ${entry.path} (${entry.action})`);
5171
5299
  }
@@ -5195,7 +5323,7 @@ async function regenerateCommand(options) {
5195
5323
  genMessages.start();
5196
5324
  let generatedSetup = null;
5197
5325
  try {
5198
- const targetAgent = readState()?.targetAgent ?? "both";
5326
+ const targetAgent = readState()?.targetAgent ?? ["claude", "cursor"];
5199
5327
  const result2 = await generateSetup(
5200
5328
  fingerprint,
5201
5329
  targetAgent,
@@ -5252,13 +5380,13 @@ import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5,
5252
5380
  import { join as join8, dirname as dirname2 } from "path";
5253
5381
 
5254
5382
  // src/scanner/index.ts
5255
- import fs20 from "fs";
5256
- import path16 from "path";
5383
+ import fs21 from "fs";
5384
+ import path17 from "path";
5257
5385
  import crypto2 from "crypto";
5258
5386
  function scanLocalState(dir) {
5259
5387
  const items = [];
5260
- const claudeMdPath = path16.join(dir, "CLAUDE.md");
5261
- if (fs20.existsSync(claudeMdPath)) {
5388
+ const claudeMdPath = path17.join(dir, "CLAUDE.md");
5389
+ if (fs21.existsSync(claudeMdPath)) {
5262
5390
  items.push({
5263
5391
  type: "rule",
5264
5392
  platform: "claude",
@@ -5267,10 +5395,10 @@ function scanLocalState(dir) {
5267
5395
  path: claudeMdPath
5268
5396
  });
5269
5397
  }
5270
- const skillsDir = path16.join(dir, ".claude", "skills");
5271
- if (fs20.existsSync(skillsDir)) {
5272
- for (const file of fs20.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
5273
- const filePath = path16.join(skillsDir, file);
5398
+ const skillsDir = path17.join(dir, ".claude", "skills");
5399
+ if (fs21.existsSync(skillsDir)) {
5400
+ for (const file of fs21.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
5401
+ const filePath = path17.join(skillsDir, file);
5274
5402
  items.push({
5275
5403
  type: "skill",
5276
5404
  platform: "claude",
@@ -5280,10 +5408,10 @@ function scanLocalState(dir) {
5280
5408
  });
5281
5409
  }
5282
5410
  }
5283
- const mcpJsonPath = path16.join(dir, ".mcp.json");
5284
- if (fs20.existsSync(mcpJsonPath)) {
5411
+ const mcpJsonPath = path17.join(dir, ".mcp.json");
5412
+ if (fs21.existsSync(mcpJsonPath)) {
5285
5413
  try {
5286
- const mcpJson = JSON.parse(fs20.readFileSync(mcpJsonPath, "utf-8"));
5414
+ const mcpJson = JSON.parse(fs21.readFileSync(mcpJsonPath, "utf-8"));
5287
5415
  if (mcpJson.mcpServers) {
5288
5416
  for (const name of Object.keys(mcpJson.mcpServers)) {
5289
5417
  items.push({
@@ -5298,8 +5426,36 @@ function scanLocalState(dir) {
5298
5426
  } catch {
5299
5427
  }
5300
5428
  }
5301
- const cursorrulesPath = path16.join(dir, ".cursorrules");
5302
- if (fs20.existsSync(cursorrulesPath)) {
5429
+ const agentsMdPath = path17.join(dir, "AGENTS.md");
5430
+ if (fs21.existsSync(agentsMdPath)) {
5431
+ items.push({
5432
+ type: "rule",
5433
+ platform: "codex",
5434
+ name: "AGENTS.md",
5435
+ contentHash: hashFile(agentsMdPath),
5436
+ path: agentsMdPath
5437
+ });
5438
+ }
5439
+ const codexSkillsDir = path17.join(dir, ".agents", "skills");
5440
+ if (fs21.existsSync(codexSkillsDir)) {
5441
+ try {
5442
+ for (const name of fs21.readdirSync(codexSkillsDir)) {
5443
+ const skillFile = path17.join(codexSkillsDir, name, "SKILL.md");
5444
+ if (fs21.existsSync(skillFile)) {
5445
+ items.push({
5446
+ type: "skill",
5447
+ platform: "codex",
5448
+ name: `${name}/SKILL.md`,
5449
+ contentHash: hashFile(skillFile),
5450
+ path: skillFile
5451
+ });
5452
+ }
5453
+ }
5454
+ } catch {
5455
+ }
5456
+ }
5457
+ const cursorrulesPath = path17.join(dir, ".cursorrules");
5458
+ if (fs21.existsSync(cursorrulesPath)) {
5303
5459
  items.push({
5304
5460
  type: "rule",
5305
5461
  platform: "cursor",
@@ -5308,10 +5464,10 @@ function scanLocalState(dir) {
5308
5464
  path: cursorrulesPath
5309
5465
  });
5310
5466
  }
5311
- const cursorRulesDir = path16.join(dir, ".cursor", "rules");
5312
- if (fs20.existsSync(cursorRulesDir)) {
5313
- for (const file of fs20.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
5314
- const filePath = path16.join(cursorRulesDir, file);
5467
+ const cursorRulesDir = path17.join(dir, ".cursor", "rules");
5468
+ if (fs21.existsSync(cursorRulesDir)) {
5469
+ for (const file of fs21.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
5470
+ const filePath = path17.join(cursorRulesDir, file);
5315
5471
  items.push({
5316
5472
  type: "rule",
5317
5473
  platform: "cursor",
@@ -5321,12 +5477,12 @@ function scanLocalState(dir) {
5321
5477
  });
5322
5478
  }
5323
5479
  }
5324
- const cursorSkillsDir = path16.join(dir, ".cursor", "skills");
5325
- if (fs20.existsSync(cursorSkillsDir)) {
5480
+ const cursorSkillsDir = path17.join(dir, ".cursor", "skills");
5481
+ if (fs21.existsSync(cursorSkillsDir)) {
5326
5482
  try {
5327
- for (const name of fs20.readdirSync(cursorSkillsDir)) {
5328
- const skillFile = path16.join(cursorSkillsDir, name, "SKILL.md");
5329
- if (fs20.existsSync(skillFile)) {
5483
+ for (const name of fs21.readdirSync(cursorSkillsDir)) {
5484
+ const skillFile = path17.join(cursorSkillsDir, name, "SKILL.md");
5485
+ if (fs21.existsSync(skillFile)) {
5330
5486
  items.push({
5331
5487
  type: "skill",
5332
5488
  platform: "cursor",
@@ -5339,10 +5495,10 @@ function scanLocalState(dir) {
5339
5495
  } catch {
5340
5496
  }
5341
5497
  }
5342
- const cursorMcpPath = path16.join(dir, ".cursor", "mcp.json");
5343
- if (fs20.existsSync(cursorMcpPath)) {
5498
+ const cursorMcpPath = path17.join(dir, ".cursor", "mcp.json");
5499
+ if (fs21.existsSync(cursorMcpPath)) {
5344
5500
  try {
5345
- const mcpJson = JSON.parse(fs20.readFileSync(cursorMcpPath, "utf-8"));
5501
+ const mcpJson = JSON.parse(fs21.readFileSync(cursorMcpPath, "utf-8"));
5346
5502
  if (mcpJson.mcpServers) {
5347
5503
  for (const name of Object.keys(mcpJson.mcpServers)) {
5348
5504
  items.push({
@@ -5360,7 +5516,7 @@ function scanLocalState(dir) {
5360
5516
  return items;
5361
5517
  }
5362
5518
  function hashFile(filePath) {
5363
- const text = fs20.readFileSync(filePath, "utf-8");
5519
+ const text = fs21.readFileSync(filePath, "utf-8");
5364
5520
  return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
5365
5521
  }
5366
5522
  function hashJson(obj) {
@@ -5380,13 +5536,17 @@ function getSkillPath(platform, slug) {
5380
5536
  if (platform === "cursor") {
5381
5537
  return join8(".cursor", "skills", slug, "SKILL.md");
5382
5538
  }
5539
+ if (platform === "codex") {
5540
+ return join8(".agents", "skills", slug, "SKILL.md");
5541
+ }
5383
5542
  return join8(".claude", "skills", slug, "SKILL.md");
5384
5543
  }
5385
5544
  function getInstalledSkills() {
5386
5545
  const installed = /* @__PURE__ */ new Set();
5387
5546
  const dirs = [
5388
5547
  join8(process.cwd(), ".claude", "skills"),
5389
- join8(process.cwd(), ".cursor", "skills")
5548
+ join8(process.cwd(), ".cursor", "skills"),
5549
+ join8(process.cwd(), ".agents", "skills")
5390
5550
  ];
5391
5551
  for (const dir of dirs) {
5392
5552
  try {
@@ -5834,7 +5994,8 @@ async function fetchSkillContent(rec) {
5834
5994
  const candidates = [
5835
5995
  `https://raw.githubusercontent.com/${repoPath}/HEAD/skills/${rec.slug}/SKILL.md`,
5836
5996
  `https://raw.githubusercontent.com/${repoPath}/HEAD/${rec.slug}/SKILL.md`,
5837
- `https://raw.githubusercontent.com/${repoPath}/HEAD/.claude/skills/${rec.slug}/SKILL.md`
5997
+ `https://raw.githubusercontent.com/${repoPath}/HEAD/.claude/skills/${rec.slug}/SKILL.md`,
5998
+ `https://raw.githubusercontent.com/${repoPath}/HEAD/.agents/skills/${rec.slug}/SKILL.md`
5838
5999
  ];
5839
6000
  for (const url of candidates) {
5840
6001
  try {
@@ -5936,8 +6097,8 @@ async function scoreCommand(options) {
5936
6097
  }
5937
6098
 
5938
6099
  // src/commands/refresh.ts
5939
- import fs22 from "fs";
5940
- import path18 from "path";
6100
+ import fs23 from "fs";
6101
+ import path19 from "path";
5941
6102
  import chalk11 from "chalk";
5942
6103
  import ora6 from "ora";
5943
6104
 
@@ -6014,37 +6175,37 @@ function collectDiff(lastSha) {
6014
6175
  }
6015
6176
 
6016
6177
  // src/writers/refresh.ts
6017
- import fs21 from "fs";
6018
- import path17 from "path";
6178
+ import fs22 from "fs";
6179
+ import path18 from "path";
6019
6180
  function writeRefreshDocs(docs) {
6020
6181
  const written = [];
6021
6182
  if (docs.claudeMd) {
6022
- fs21.writeFileSync("CLAUDE.md", docs.claudeMd);
6183
+ fs22.writeFileSync("CLAUDE.md", docs.claudeMd);
6023
6184
  written.push("CLAUDE.md");
6024
6185
  }
6025
6186
  if (docs.readmeMd) {
6026
- fs21.writeFileSync("README.md", docs.readmeMd);
6187
+ fs22.writeFileSync("README.md", docs.readmeMd);
6027
6188
  written.push("README.md");
6028
6189
  }
6029
6190
  if (docs.cursorrules) {
6030
- fs21.writeFileSync(".cursorrules", docs.cursorrules);
6191
+ fs22.writeFileSync(".cursorrules", docs.cursorrules);
6031
6192
  written.push(".cursorrules");
6032
6193
  }
6033
6194
  if (docs.cursorRules) {
6034
- const rulesDir = path17.join(".cursor", "rules");
6035
- if (!fs21.existsSync(rulesDir)) fs21.mkdirSync(rulesDir, { recursive: true });
6195
+ const rulesDir = path18.join(".cursor", "rules");
6196
+ if (!fs22.existsSync(rulesDir)) fs22.mkdirSync(rulesDir, { recursive: true });
6036
6197
  for (const rule of docs.cursorRules) {
6037
- const filePath = path17.join(rulesDir, rule.filename);
6038
- fs21.writeFileSync(filePath, rule.content);
6198
+ const filePath = path18.join(rulesDir, rule.filename);
6199
+ fs22.writeFileSync(filePath, rule.content);
6039
6200
  written.push(filePath);
6040
6201
  }
6041
6202
  }
6042
6203
  if (docs.claudeSkills) {
6043
- const skillsDir = path17.join(".claude", "skills");
6044
- if (!fs21.existsSync(skillsDir)) fs21.mkdirSync(skillsDir, { recursive: true });
6204
+ const skillsDir = path18.join(".claude", "skills");
6205
+ if (!fs22.existsSync(skillsDir)) fs22.mkdirSync(skillsDir, { recursive: true });
6045
6206
  for (const skill of docs.claudeSkills) {
6046
- const filePath = path17.join(skillsDir, skill.filename);
6047
- fs21.writeFileSync(filePath, skill.content);
6207
+ const filePath = path18.join(skillsDir, skill.filename);
6208
+ fs22.writeFileSync(filePath, skill.content);
6048
6209
  written.push(filePath);
6049
6210
  }
6050
6211
  }
@@ -6119,11 +6280,11 @@ function log(quiet, ...args) {
6119
6280
  function discoverGitRepos(parentDir) {
6120
6281
  const repos = [];
6121
6282
  try {
6122
- const entries = fs22.readdirSync(parentDir, { withFileTypes: true });
6283
+ const entries = fs23.readdirSync(parentDir, { withFileTypes: true });
6123
6284
  for (const entry of entries) {
6124
6285
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
6125
- const childPath = path18.join(parentDir, entry.name);
6126
- if (fs22.existsSync(path18.join(childPath, ".git"))) {
6286
+ const childPath = path19.join(parentDir, entry.name);
6287
+ if (fs23.existsSync(path19.join(childPath, ".git"))) {
6127
6288
  repos.push(childPath);
6128
6289
  }
6129
6290
  }
@@ -6218,7 +6379,7 @@ async function refreshCommand(options) {
6218
6379
  `));
6219
6380
  const originalDir = process.cwd();
6220
6381
  for (const repo of repos) {
6221
- const repoName = path18.basename(repo);
6382
+ const repoName = path19.basename(repo);
6222
6383
  try {
6223
6384
  process.chdir(repo);
6224
6385
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -6465,8 +6626,8 @@ function readStdin() {
6465
6626
 
6466
6627
  // src/learner/storage.ts
6467
6628
  init_constants();
6468
- import fs23 from "fs";
6469
- import path19 from "path";
6629
+ import fs24 from "fs";
6630
+ import path20 from "path";
6470
6631
  var MAX_RESPONSE_LENGTH = 2e3;
6471
6632
  var DEFAULT_STATE = {
6472
6633
  sessionId: null,
@@ -6474,15 +6635,15 @@ var DEFAULT_STATE = {
6474
6635
  lastAnalysisTimestamp: null
6475
6636
  };
6476
6637
  function ensureLearningDir() {
6477
- if (!fs23.existsSync(LEARNING_DIR)) {
6478
- fs23.mkdirSync(LEARNING_DIR, { recursive: true });
6638
+ if (!fs24.existsSync(LEARNING_DIR)) {
6639
+ fs24.mkdirSync(LEARNING_DIR, { recursive: true });
6479
6640
  }
6480
6641
  }
6481
6642
  function sessionFilePath() {
6482
- return path19.join(LEARNING_DIR, LEARNING_SESSION_FILE);
6643
+ return path20.join(LEARNING_DIR, LEARNING_SESSION_FILE);
6483
6644
  }
6484
6645
  function stateFilePath() {
6485
- return path19.join(LEARNING_DIR, LEARNING_STATE_FILE);
6646
+ return path20.join(LEARNING_DIR, LEARNING_STATE_FILE);
6486
6647
  }
6487
6648
  function truncateResponse(response) {
6488
6649
  const str = JSON.stringify(response);
@@ -6493,50 +6654,50 @@ function appendEvent(event) {
6493
6654
  ensureLearningDir();
6494
6655
  const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
6495
6656
  const filePath = sessionFilePath();
6496
- fs23.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
6657
+ fs24.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
6497
6658
  const count = getEventCount();
6498
6659
  if (count > LEARNING_MAX_EVENTS) {
6499
- const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6660
+ const lines = fs24.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6500
6661
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
6501
- fs23.writeFileSync(filePath, kept.join("\n") + "\n");
6662
+ fs24.writeFileSync(filePath, kept.join("\n") + "\n");
6502
6663
  }
6503
6664
  }
6504
6665
  function readAllEvents() {
6505
6666
  const filePath = sessionFilePath();
6506
- if (!fs23.existsSync(filePath)) return [];
6507
- const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6667
+ if (!fs24.existsSync(filePath)) return [];
6668
+ const lines = fs24.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6508
6669
  return lines.map((line) => JSON.parse(line));
6509
6670
  }
6510
6671
  function getEventCount() {
6511
6672
  const filePath = sessionFilePath();
6512
- if (!fs23.existsSync(filePath)) return 0;
6513
- const content = fs23.readFileSync(filePath, "utf-8");
6673
+ if (!fs24.existsSync(filePath)) return 0;
6674
+ const content = fs24.readFileSync(filePath, "utf-8");
6514
6675
  return content.split("\n").filter(Boolean).length;
6515
6676
  }
6516
6677
  function clearSession() {
6517
6678
  const filePath = sessionFilePath();
6518
- if (fs23.existsSync(filePath)) fs23.unlinkSync(filePath);
6679
+ if (fs24.existsSync(filePath)) fs24.unlinkSync(filePath);
6519
6680
  }
6520
6681
  function readState2() {
6521
6682
  const filePath = stateFilePath();
6522
- if (!fs23.existsSync(filePath)) return { ...DEFAULT_STATE };
6683
+ if (!fs24.existsSync(filePath)) return { ...DEFAULT_STATE };
6523
6684
  try {
6524
- return JSON.parse(fs23.readFileSync(filePath, "utf-8"));
6685
+ return JSON.parse(fs24.readFileSync(filePath, "utf-8"));
6525
6686
  } catch {
6526
6687
  return { ...DEFAULT_STATE };
6527
6688
  }
6528
6689
  }
6529
6690
  function writeState2(state) {
6530
6691
  ensureLearningDir();
6531
- fs23.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
6692
+ fs24.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
6532
6693
  }
6533
6694
  function resetState() {
6534
6695
  writeState2({ ...DEFAULT_STATE });
6535
6696
  }
6536
6697
 
6537
6698
  // src/learner/writer.ts
6538
- import fs24 from "fs";
6539
- import path20 from "path";
6699
+ import fs25 from "fs";
6700
+ import path21 from "path";
6540
6701
  var LEARNED_START = "<!-- caliber:learned -->";
6541
6702
  var LEARNED_END = "<!-- /caliber:learned -->";
6542
6703
  function writeLearnedContent(update) {
@@ -6556,8 +6717,8 @@ function writeLearnedContent(update) {
6556
6717
  function writeLearnedSection(content) {
6557
6718
  const claudeMdPath = "CLAUDE.md";
6558
6719
  let existing = "";
6559
- if (fs24.existsSync(claudeMdPath)) {
6560
- existing = fs24.readFileSync(claudeMdPath, "utf-8");
6720
+ if (fs25.existsSync(claudeMdPath)) {
6721
+ existing = fs25.readFileSync(claudeMdPath, "utf-8");
6561
6722
  }
6562
6723
  const section = `${LEARNED_START}
6563
6724
  ${content}
@@ -6571,15 +6732,15 @@ ${LEARNED_END}`;
6571
6732
  const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
6572
6733
  updated = existing + separator + "\n" + section + "\n";
6573
6734
  }
6574
- fs24.writeFileSync(claudeMdPath, updated);
6735
+ fs25.writeFileSync(claudeMdPath, updated);
6575
6736
  }
6576
6737
  function writeLearnedSkill(skill) {
6577
- const skillDir = path20.join(".claude", "skills", skill.name);
6578
- if (!fs24.existsSync(skillDir)) fs24.mkdirSync(skillDir, { recursive: true });
6579
- const skillPath = path20.join(skillDir, "SKILL.md");
6580
- if (!skill.isNew && fs24.existsSync(skillPath)) {
6581
- const existing = fs24.readFileSync(skillPath, "utf-8");
6582
- fs24.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
6738
+ const skillDir = path21.join(".claude", "skills", skill.name);
6739
+ if (!fs25.existsSync(skillDir)) fs25.mkdirSync(skillDir, { recursive: true });
6740
+ const skillPath = path21.join(skillDir, "SKILL.md");
6741
+ if (!skill.isNew && fs25.existsSync(skillPath)) {
6742
+ const existing = fs25.readFileSync(skillPath, "utf-8");
6743
+ fs25.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
6583
6744
  } else {
6584
6745
  const frontmatter = [
6585
6746
  "---",
@@ -6588,14 +6749,14 @@ function writeLearnedSkill(skill) {
6588
6749
  "---",
6589
6750
  ""
6590
6751
  ].join("\n");
6591
- fs24.writeFileSync(skillPath, frontmatter + skill.content);
6752
+ fs25.writeFileSync(skillPath, frontmatter + skill.content);
6592
6753
  }
6593
6754
  return skillPath;
6594
6755
  }
6595
6756
  function readLearnedSection() {
6596
6757
  const claudeMdPath = "CLAUDE.md";
6597
- if (!fs24.existsSync(claudeMdPath)) return null;
6598
- const content = fs24.readFileSync(claudeMdPath, "utf-8");
6758
+ if (!fs25.existsSync(claudeMdPath)) return null;
6759
+ const content = fs25.readFileSync(claudeMdPath, "utf-8");
6599
6760
  const startIdx = content.indexOf(LEARNED_START);
6600
6761
  const endIdx = content.indexOf(LEARNED_END);
6601
6762
  if (startIdx === -1 || endIdx === -1) return null;
@@ -6779,20 +6940,31 @@ Learned items in CLAUDE.md: ${chalk14.cyan(String(lineCount))}`);
6779
6940
  }
6780
6941
 
6781
6942
  // src/cli.ts
6782
- var __dirname = path21.dirname(fileURLToPath(import.meta.url));
6943
+ var __dirname = path22.dirname(fileURLToPath(import.meta.url));
6783
6944
  var pkg = JSON.parse(
6784
- fs25.readFileSync(path21.resolve(__dirname, "..", "package.json"), "utf-8")
6945
+ fs26.readFileSync(path22.resolve(__dirname, "..", "package.json"), "utf-8")
6785
6946
  );
6786
6947
  var program = new Command();
6787
6948
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
6788
6949
  program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("Configure your coding agent environment").version(displayVersion);
6789
- program.command("onboard").alias("init").description("Onboard your project for AI-assisted development").option("--agent <type>", "Target agent: claude, cursor, or both").option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing setup without prompting").action(initCommand);
6950
+ function parseAgentOption(value) {
6951
+ if (value === "both") return ["claude", "cursor"];
6952
+ if (value === "all") return ["claude", "cursor", "codex"];
6953
+ const valid = ["claude", "cursor", "codex"];
6954
+ const agents = [...new Set(value.split(",").map((s) => s.trim().toLowerCase()).filter((a) => valid.includes(a)))];
6955
+ if (agents.length === 0) {
6956
+ console.error(`Invalid agent "${value}". Choose from: claude, cursor, codex (comma-separated for multiple)`);
6957
+ process.exit(1);
6958
+ }
6959
+ return agents;
6960
+ }
6961
+ program.command("onboard").alias("init").description("Onboard your project for AI-assisted development").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex", parseAgentOption).option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing setup without prompting").action(initCommand);
6790
6962
  program.command("undo").description("Revert all config changes made by Caliber").action(undoCommand);
6791
6963
  program.command("status").description("Show current Caliber setup status").option("--json", "Output as JSON").action(statusCommand);
6792
6964
  program.command("regenerate").alias("regen").alias("re").alias("update").description("Re-analyze project and regenerate setup").option("--dry-run", "Preview changes without writing files").action(regenerateCommand);
6793
6965
  program.command("config").description("Configure LLM provider, API key, and model").action(configCommand);
6794
6966
  program.command("recommend").description("Discover and install skill recommendations").option("--generate", "Force fresh recommendation search").action(recommendCommand);
6795
- program.command("score").description("Score your current agent config setup (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option("--agent <type>", "Target agent: claude, cursor, or both").action(scoreCommand);
6967
+ program.command("score").description("Score your current agent config setup (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex", parseAgentOption).action(scoreCommand);
6796
6968
  program.command("refresh").description("Update docs based on recent code changes").option("--quiet", "Suppress output (for use in hooks)").option("--dry-run", "Preview changes without writing files").action(refreshCommand);
6797
6969
  program.command("hooks").description("Manage auto-refresh hooks (toggle interactively)").option("--install", "Enable all hooks non-interactively").option("--remove", "Disable all hooks non-interactively").action(hooksCommand);
6798
6970
  var learn = program.command("learn").description("Session learning \u2014 observe tool usage and extract reusable instructions");
@@ -6803,22 +6975,22 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
6803
6975
  learn.command("status").description("Show learning system status").action(learnStatusCommand);
6804
6976
 
6805
6977
  // src/utils/version-check.ts
6806
- import fs26 from "fs";
6807
- import path22 from "path";
6978
+ import fs27 from "fs";
6979
+ import path23 from "path";
6808
6980
  import { fileURLToPath as fileURLToPath2 } from "url";
6809
6981
  import { execSync as execSync9 } from "child_process";
6810
6982
  import chalk15 from "chalk";
6811
6983
  import ora7 from "ora";
6812
6984
  import confirm2 from "@inquirer/confirm";
6813
- var __dirname_vc = path22.dirname(fileURLToPath2(import.meta.url));
6985
+ var __dirname_vc = path23.dirname(fileURLToPath2(import.meta.url));
6814
6986
  var pkg2 = JSON.parse(
6815
- fs26.readFileSync(path22.resolve(__dirname_vc, "..", "package.json"), "utf-8")
6987
+ fs27.readFileSync(path23.resolve(__dirname_vc, "..", "package.json"), "utf-8")
6816
6988
  );
6817
6989
  function getInstalledVersion() {
6818
6990
  try {
6819
6991
  const globalRoot = execSync9("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
6820
- const pkgPath = path22.join(globalRoot, "@rely-ai", "caliber", "package.json");
6821
- return JSON.parse(fs26.readFileSync(pkgPath, "utf-8")).version;
6992
+ const pkgPath = path23.join(globalRoot, "@rely-ai", "caliber", "package.json");
6993
+ return JSON.parse(fs27.readFileSync(pkgPath, "utf-8")).version;
6822
6994
  } catch {
6823
6995
  return null;
6824
6996
  }