@kood/claude-code 0.7.8 → 0.7.10

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/index.js +522 -56
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ var banner = () => {
24
24
  // src/commands/init.ts
25
25
  import fs9 from "fs-extra";
26
26
  import os2 from "os";
27
+ import path11 from "path";
27
28
 
28
29
  // src/features/templates/template-path-resolver.ts
29
30
  import path2 from "path";
@@ -689,7 +690,7 @@ async function promptScopeSelection(options) {
689
690
  return { scope: response.value };
690
691
  }
691
692
  async function promptCodexSync(options) {
692
- const { providedSyncCodex } = options;
693
+ const { providedSyncCodex, codexPath } = options;
693
694
  if (providedSyncCodex !== void 0) {
694
695
  return { syncCodex: providedSyncCodex };
695
696
  }
@@ -697,8 +698,9 @@ async function promptCodexSync(options) {
697
698
  return { syncCodex: false };
698
699
  }
699
700
  logger.blank();
701
+ const pathHint = codexPath ? ` (${codexPath})` : "";
700
702
  const result = await promptConfirm(
701
- "Also sync with Codex now? (~/.codex/skills/)",
703
+ `Also sync with Codex now?${pathHint}`,
702
704
  false
703
705
  );
704
706
  return { syncCodex: result.confirmed };
@@ -720,6 +722,7 @@ var CLAUDE_GENERATED_FOLDERS = [
720
722
  ".claude/research/",
721
723
  ".claude/validation-results/"
722
724
  ];
725
+ var CODEX_GENERATED_FOLDERS = [".codex/"];
723
726
  function normalizeIgnorePattern(pattern) {
724
727
  return pattern.replace(/\\/g, "/").replace(/^\.?\//, "").replace(/\/+$/, "").trim();
725
728
  }
@@ -732,9 +735,11 @@ function parseIgnoreLine(line) {
732
735
  const normalized = normalizeIgnorePattern(rawPattern);
733
736
  return normalized || null;
734
737
  }
735
- async function updateGitignore(targetDir) {
738
+ async function updateGitignore(targetDir, options = {}) {
739
+ const { includeCodex = false } = options;
736
740
  const gitignorePath = path9.join(targetDir, ".gitignore");
737
741
  const sectionComment = "# Claude Code generated files";
742
+ const targetFolders = includeCodex ? [...CLAUDE_GENERATED_FOLDERS, ...CODEX_GENERATED_FOLDERS] : CLAUDE_GENERATED_FOLDERS;
738
743
  let content = "";
739
744
  let hasGitignore = false;
740
745
  try {
@@ -748,7 +753,7 @@ async function updateGitignore(targetDir) {
748
753
  const existingPatterns = new Set(
749
754
  lines.map((line) => parseIgnoreLine(line)).filter((pattern) => Boolean(pattern))
750
755
  );
751
- const linesToAdd = CLAUDE_GENERATED_FOLDERS.filter(
756
+ const linesToAdd = targetFolders.filter(
752
757
  (folder) => !existingPatterns.has(normalizeIgnorePattern(folder))
753
758
  );
754
759
  if (linesToAdd.length === 0) {
@@ -783,13 +788,13 @@ async function updateGitignore(targetDir) {
783
788
  import fs8 from "fs-extra";
784
789
  import os from "os";
785
790
  import path10 from "path";
786
- function resolveCodexHome() {
787
- const envCodexHome = process.env.CODEX_HOME?.trim();
788
- if (envCodexHome) {
789
- return path10.resolve(envCodexHome);
790
- }
791
- return path10.join(os.homedir(), ".codex");
792
- }
791
+ var RESERVED_CODEX_SKILL_DIRS = /* @__PURE__ */ new Set([".system", "claude-commands"]);
792
+ var COMMAND_INSTRUCTION_PREFIX_REGEX = /^@\.\.\/instructions\//gm;
793
+ var COMMAND_INSTRUCTION_PREFIX_REPLACEMENT = "@../../../instructions/";
794
+ var MAX_REFERENCE_ISSUE_SAMPLES = 10;
795
+ var CLAUDE_STATE_FILE = ".claude.json";
796
+ var CLAUDE_LOCAL_MCP_FILE = ".mcp.json";
797
+ var CODEX_CONFIG_FILE = "config.toml";
793
798
  function normalizeLineEndings(content) {
794
799
  return content.replace(/\r\n/g, "\n");
795
800
  }
@@ -818,6 +823,10 @@ function extractDescriptionFromFrontmatter(markdown) {
818
823
  function buildCommandSkillContent(commandPath, commandRaw) {
819
824
  const commandName = path10.basename(commandPath, ".md");
820
825
  const normalizedCommand = normalizeLineEndings(commandRaw).trim();
826
+ const rewrittenCommand = normalizedCommand.replace(
827
+ COMMAND_INSTRUCTION_PREFIX_REGEX,
828
+ COMMAND_INSTRUCTION_PREFIX_REPLACEMENT
829
+ );
821
830
  const sourcePath = path10.resolve(commandPath);
822
831
  const description = extractDescriptionFromFrontmatter(normalizedCommand) || `${commandName} command imported from .claude/commands`;
823
832
  return `---
@@ -832,39 +841,374 @@ resolve them from the current workspace first; if missing, ask for the intended
832
841
 
833
842
  ---
834
843
 
835
- ${normalizedCommand}
844
+ ${rewrittenCommand}
836
845
  `;
837
846
  }
838
- async function syncSkills(claudeSkillsDir, codexSkillsDir) {
839
- if (!await fs8.pathExists(claudeSkillsDir)) {
840
- return 0;
847
+ function isRecord(value) {
848
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
849
+ }
850
+ function asStringArray(value) {
851
+ if (!Array.isArray(value)) {
852
+ return void 0;
841
853
  }
842
- const entries = await fs8.readdir(claudeSkillsDir, { withFileTypes: true });
843
- let syncedSkills = 0;
844
- for (const entry of entries) {
854
+ const items = value.filter(
855
+ (item) => typeof item === "string"
856
+ );
857
+ return items.length > 0 ? items : void 0;
858
+ }
859
+ function asStringRecord(value) {
860
+ if (!isRecord(value)) {
861
+ return void 0;
862
+ }
863
+ const entries = Object.entries(value).filter(
864
+ (entry) => typeof entry[1] === "string"
865
+ );
866
+ if (entries.length === 0) {
867
+ return void 0;
868
+ }
869
+ return Object.fromEntries(entries);
870
+ }
871
+ function normalizeClaudeMcpServer(value) {
872
+ if (!isRecord(value)) {
873
+ return void 0;
874
+ }
875
+ const type = typeof value.type === "string" ? value.type : void 0;
876
+ const command = typeof value.command === "string" ? value.command : void 0;
877
+ const args = asStringArray(value.args);
878
+ const env = asStringRecord(value.env);
879
+ const cwd = typeof value.cwd === "string" ? value.cwd : void 0;
880
+ const url = typeof value.url === "string" ? value.url : void 0;
881
+ const headers = asStringRecord(value.headers);
882
+ const bearerTokenEnvVar = typeof value.bearerTokenEnvVar === "string" ? value.bearerTokenEnvVar : typeof value.bearer_token_env_var === "string" ? value.bearer_token_env_var : void 0;
883
+ const envHttpHeaders = asStringRecord(value.envHttpHeaders) || asStringRecord(value.env_http_headers);
884
+ return {
885
+ type,
886
+ command,
887
+ args,
888
+ env,
889
+ cwd,
890
+ url,
891
+ headers,
892
+ bearerTokenEnvVar,
893
+ envHttpHeaders
894
+ };
895
+ }
896
+ function normalizeClaudeMcpServers(value) {
897
+ if (!isRecord(value)) {
898
+ return {};
899
+ }
900
+ const normalizedEntries = Object.entries(value).map(([name, server]) => [name, normalizeClaudeMcpServer(server)]).filter((entry) => Boolean(entry[1]));
901
+ return Object.fromEntries(normalizedEntries);
902
+ }
903
+ function getProjectState(claudeState, targetDir) {
904
+ const projects = claudeState.projects;
905
+ if (!isRecord(projects)) {
906
+ return void 0;
907
+ }
908
+ const normalizedTarget = path10.resolve(targetDir);
909
+ for (const [projectPath, projectState] of Object.entries(projects)) {
910
+ if (path10.resolve(projectPath) !== normalizedTarget) {
911
+ continue;
912
+ }
913
+ if (isRecord(projectState)) {
914
+ return projectState;
915
+ }
916
+ }
917
+ return void 0;
918
+ }
919
+ function filterMcpJsonServersByProjectState(servers, projectState) {
920
+ if (!projectState) {
921
+ return servers;
922
+ }
923
+ const enabled = asStringArray(projectState.enabledMcpjsonServers) || [];
924
+ const disabled = new Set(
925
+ asStringArray(projectState.disabledMcpjsonServers) || []
926
+ );
927
+ const enabledSet = new Set(enabled);
928
+ const hasEnabledFilter = enabledSet.size > 0;
929
+ const filteredEntries = Object.entries(servers).filter(([name]) => {
930
+ if (hasEnabledFilter && !enabledSet.has(name)) {
931
+ return false;
932
+ }
933
+ if (disabled.has(name)) {
934
+ return false;
935
+ }
936
+ return true;
937
+ });
938
+ return Object.fromEntries(filteredEntries);
939
+ }
940
+ async function readJsonFile(filePath) {
941
+ if (!await fs8.pathExists(filePath)) {
942
+ return void 0;
943
+ }
944
+ try {
945
+ const raw = await fs8.readFile(filePath, "utf8");
946
+ const parsed = JSON.parse(raw);
947
+ if (!isRecord(parsed)) {
948
+ return void 0;
949
+ }
950
+ return parsed;
951
+ } catch {
952
+ return void 0;
953
+ }
954
+ }
955
+ function toCodexMcpServer(server) {
956
+ const serverType = server.type?.toLowerCase();
957
+ const isHttp = serverType === "http" || Boolean(server.url);
958
+ if (isHttp) {
959
+ if (!server.url) {
960
+ return void 0;
961
+ }
962
+ return {
963
+ kind: "http",
964
+ url: server.url,
965
+ bearerTokenEnvVar: server.bearerTokenEnvVar,
966
+ httpHeaders: server.headers,
967
+ envHttpHeaders: server.envHttpHeaders
968
+ };
969
+ }
970
+ if (!server.command) {
971
+ return void 0;
972
+ }
973
+ return {
974
+ kind: "stdio",
975
+ command: server.command,
976
+ args: server.args,
977
+ env: server.env,
978
+ cwd: server.cwd
979
+ };
980
+ }
981
+ function tomlQuote(value) {
982
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
983
+ }
984
+ function tomlKey(value) {
985
+ return /^[A-Za-z0-9_-]+$/.test(value) ? value : tomlQuote(value);
986
+ }
987
+ function parseTomlPath(input) {
988
+ const pathInput = input.trim();
989
+ if (!pathInput) {
990
+ return void 0;
991
+ }
992
+ const segments = [];
993
+ let current = "";
994
+ let inQuotes = false;
995
+ let escaped = false;
996
+ for (const char of pathInput) {
997
+ if (inQuotes) {
998
+ if (escaped) {
999
+ current += char;
1000
+ escaped = false;
1001
+ continue;
1002
+ }
1003
+ if (char === "\\") {
1004
+ escaped = true;
1005
+ continue;
1006
+ }
1007
+ if (char === '"') {
1008
+ inQuotes = false;
1009
+ continue;
1010
+ }
1011
+ current += char;
1012
+ continue;
1013
+ }
1014
+ if (char === '"') {
1015
+ inQuotes = true;
1016
+ continue;
1017
+ }
1018
+ if (char === ".") {
1019
+ const value = current.trim();
1020
+ if (!value) {
1021
+ return void 0;
1022
+ }
1023
+ segments.push(value);
1024
+ current = "";
1025
+ continue;
1026
+ }
1027
+ current += char;
1028
+ }
1029
+ if (inQuotes || escaped) {
1030
+ return void 0;
1031
+ }
1032
+ const tail = current.trim();
1033
+ if (!tail) {
1034
+ return void 0;
1035
+ }
1036
+ segments.push(tail);
1037
+ return segments;
1038
+ }
1039
+ function stripCodexMcpSections(configToml) {
1040
+ const lines = normalizeLineEndings(configToml).split("\n");
1041
+ const kept = [];
1042
+ let droppingMcpSection = false;
1043
+ for (const line of lines) {
1044
+ const headerMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
1045
+ if (headerMatch) {
1046
+ const headerPath = parseTomlPath(headerMatch[1]);
1047
+ droppingMcpSection = Boolean(
1048
+ headerPath && headerPath.length > 0 && headerPath[0] === "mcp_servers"
1049
+ );
1050
+ }
1051
+ if (!droppingMcpSection) {
1052
+ kept.push(line);
1053
+ }
1054
+ }
1055
+ return kept.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
1056
+ }
1057
+ function renderTomlStringArray(values) {
1058
+ if (!values || values.length === 0) {
1059
+ return void 0;
1060
+ }
1061
+ return `[${values.map((value) => tomlQuote(value)).join(", ")}]`;
1062
+ }
1063
+ function renderTomlStringTable(header, values) {
1064
+ if (!values || Object.keys(values).length === 0) {
1065
+ return void 0;
1066
+ }
1067
+ const lines = Object.entries(values).sort((a, b) => a[0].localeCompare(b[0])).map(([key, value]) => `${tomlKey(key)} = ${tomlQuote(value)}`);
1068
+ return `${header}
1069
+ ${lines.join("\n")}`;
1070
+ }
1071
+ function renderCodexMcpServer(name, server) {
1072
+ const sectionRoot = `[mcp_servers.${tomlKey(name)}]`;
1073
+ const lines = [sectionRoot];
1074
+ if (server.kind === "http") {
1075
+ lines.push(`url = ${tomlQuote(server.url ?? "")}`);
1076
+ if (server.bearerTokenEnvVar) {
1077
+ lines.push(
1078
+ `bearer_token_env_var = ${tomlQuote(server.bearerTokenEnvVar)}`
1079
+ );
1080
+ }
1081
+ } else {
1082
+ lines.push(`command = ${tomlQuote(server.command ?? "")}`);
1083
+ const args = renderTomlStringArray(server.args);
1084
+ if (args) {
1085
+ lines.push(`args = ${args}`);
1086
+ }
1087
+ if (server.cwd) {
1088
+ lines.push(`cwd = ${tomlQuote(server.cwd)}`);
1089
+ }
1090
+ }
1091
+ const envSection = server.kind === "stdio" ? renderTomlStringTable(`[mcp_servers.${tomlKey(name)}.env]`, server.env) : void 0;
1092
+ const httpHeadersSection = server.kind === "http" ? renderTomlStringTable(
1093
+ `[mcp_servers.${tomlKey(name)}.http_headers]`,
1094
+ server.httpHeaders
1095
+ ) : void 0;
1096
+ const envHttpHeadersSection = server.kind === "http" ? renderTomlStringTable(
1097
+ `[mcp_servers.${tomlKey(name)}.env_http_headers]`,
1098
+ server.envHttpHeaders
1099
+ ) : void 0;
1100
+ const sections = [
1101
+ lines.join("\n"),
1102
+ envSection,
1103
+ httpHeadersSection,
1104
+ envHttpHeadersSection
1105
+ ].filter((section) => Boolean(section));
1106
+ return sections.join("\n\n");
1107
+ }
1108
+ function mergeCodexConfigToml(existingToml, renderedMcpBlocks) {
1109
+ const base = stripCodexMcpSections(existingToml);
1110
+ const mcp = renderedMcpBlocks.trim();
1111
+ if (!base) {
1112
+ return mcp;
1113
+ }
1114
+ if (!mcp) {
1115
+ return base;
1116
+ }
1117
+ return `${base}
1118
+
1119
+ ${mcp}`;
1120
+ }
1121
+ async function loadClaudeMcpServers(targetDir) {
1122
+ const homeDir = path10.resolve(os.homedir());
1123
+ const normalizedTarget = path10.resolve(targetDir);
1124
+ const isUserScope = normalizedTarget === homeDir;
1125
+ const claudeStatePath = path10.join(homeDir, CLAUDE_STATE_FILE);
1126
+ const claudeState = await readJsonFile(claudeStatePath);
1127
+ if (isUserScope) {
1128
+ return {
1129
+ scope: "global",
1130
+ servers: normalizeClaudeMcpServers(claudeState?.mcpServers)
1131
+ };
1132
+ }
1133
+ const projectState = claudeState ? getProjectState(claudeState, normalizedTarget) : void 0;
1134
+ const projectServers = normalizeClaudeMcpServers(projectState?.mcpServers);
1135
+ const localMcpPath = path10.join(normalizedTarget, CLAUDE_LOCAL_MCP_FILE);
1136
+ const localMcpState = await readJsonFile(localMcpPath);
1137
+ const localMcpServers = filterMcpJsonServersByProjectState(
1138
+ normalizeClaudeMcpServers(localMcpState?.mcpServers),
1139
+ projectState
1140
+ );
1141
+ return {
1142
+ scope: "local",
1143
+ servers: {
1144
+ ...localMcpServers,
1145
+ ...projectServers
1146
+ }
1147
+ };
1148
+ }
1149
+ async function syncMcpServers(targetDir) {
1150
+ const homeDir = path10.resolve(os.homedir());
1151
+ const normalizedTarget = path10.resolve(targetDir);
1152
+ const isUserScope = normalizedTarget === homeDir;
1153
+ const codexBaseDir = isUserScope ? path10.join(homeDir, ".codex") : path10.join(normalizedTarget, ".codex");
1154
+ const codexMcpConfigPath = path10.join(codexBaseDir, CODEX_CONFIG_FILE);
1155
+ const { scope, servers } = await loadClaudeMcpServers(normalizedTarget);
1156
+ const codexServers = Object.entries(servers).map(([name, server]) => [name, toCodexMcpServer(server)]).filter((entry) => Boolean(entry[1])).sort((a, b) => a[0].localeCompare(b[0]));
1157
+ const renderedMcpBlocks = codexServers.map(([name, server]) => renderCodexMcpServer(name, server)).join("\n\n");
1158
+ const existingConfig = await fs8.pathExists(codexMcpConfigPath) ? await fs8.readFile(codexMcpConfigPath, "utf8") : "";
1159
+ const nextConfig = mergeCodexConfigToml(existingConfig, renderedMcpBlocks);
1160
+ await fs8.ensureDir(codexBaseDir);
1161
+ await fs8.writeFile(codexMcpConfigPath, `${nextConfig.trimEnd()}
1162
+ `);
1163
+ return {
1164
+ codexMcpConfigPath,
1165
+ syncedMcpServers: codexServers.length,
1166
+ scope
1167
+ };
1168
+ }
1169
+ async function syncSkills(claudeSkillsDir, codexSkillsDir) {
1170
+ await fs8.ensureDir(codexSkillsDir);
1171
+ let sourceSkillNames = [];
1172
+ if (await fs8.pathExists(claudeSkillsDir)) {
1173
+ const sourceEntries = await fs8.readdir(claudeSkillsDir, {
1174
+ withFileTypes: true
1175
+ });
1176
+ sourceSkillNames = sourceEntries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => name !== ".system");
1177
+ }
1178
+ const sourceSkillSet = new Set(sourceSkillNames);
1179
+ const codexEntries = await fs8.readdir(codexSkillsDir, {
1180
+ withFileTypes: true
1181
+ });
1182
+ for (const entry of codexEntries) {
845
1183
  if (!entry.isDirectory()) {
846
1184
  continue;
847
1185
  }
848
- if (entry.name === ".system") {
1186
+ if (RESERVED_CODEX_SKILL_DIRS.has(entry.name)) {
849
1187
  continue;
850
1188
  }
851
- const src = path10.join(claudeSkillsDir, entry.name);
852
- const dest = path10.join(codexSkillsDir, entry.name);
1189
+ if (!sourceSkillSet.has(entry.name)) {
1190
+ await fs8.remove(path10.join(codexSkillsDir, entry.name));
1191
+ }
1192
+ }
1193
+ for (const skillName of sourceSkillNames) {
1194
+ const src = path10.join(claudeSkillsDir, skillName);
1195
+ const dest = path10.join(codexSkillsDir, skillName);
853
1196
  if (await fs8.pathExists(dest)) {
854
1197
  await fs8.remove(dest);
855
1198
  }
856
1199
  await copyRecursive(src, dest, { files: 0, directories: 0 });
857
- syncedSkills++;
858
1200
  }
859
- return syncedSkills;
1201
+ return sourceSkillNames.length;
860
1202
  }
861
1203
  async function syncCommands(claudeCommandsDir, codexCommandsDir) {
862
1204
  if (!await fs8.pathExists(claudeCommandsDir)) {
1205
+ await fs8.remove(codexCommandsDir);
863
1206
  return 0;
864
1207
  }
865
1208
  const entries = await fs8.readdir(claudeCommandsDir, { withFileTypes: true });
866
1209
  const commandFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
867
1210
  if (commandFiles.length === 0) {
1211
+ await fs8.remove(codexCommandsDir);
868
1212
  return 0;
869
1213
  }
870
1214
  await fs8.remove(codexCommandsDir);
@@ -876,26 +1220,126 @@ async function syncCommands(claudeCommandsDir, codexCommandsDir) {
876
1220
  const skillDir = path10.join(codexCommandsDir, commandName);
877
1221
  const skillPath = path10.join(skillDir, "SKILL.md");
878
1222
  await fs8.ensureDir(skillDir);
879
- await fs8.writeFile(skillPath, buildCommandSkillContent(commandPath, commandRaw));
1223
+ await fs8.writeFile(
1224
+ skillPath,
1225
+ buildCommandSkillContent(commandPath, commandRaw)
1226
+ );
880
1227
  }
881
1228
  return commandFiles.length;
882
1229
  }
1230
+ async function syncInstructions(claudeInstructionsDir, codexInstructionsDir) {
1231
+ if (!await fs8.pathExists(claudeInstructionsDir)) {
1232
+ await fs8.remove(codexInstructionsDir);
1233
+ return 0;
1234
+ }
1235
+ await fs8.remove(codexInstructionsDir);
1236
+ await fs8.ensureDir(codexInstructionsDir);
1237
+ const counter = { files: 0, directories: 0 };
1238
+ await copyRecursive(claudeInstructionsDir, codexInstructionsDir, counter);
1239
+ return counter.files;
1240
+ }
1241
+ function extractReferenceCandidate(line) {
1242
+ const trimmed = line.trim();
1243
+ if (!trimmed.startsWith("@")) {
1244
+ return void 0;
1245
+ }
1246
+ const [candidate] = trimmed.slice(1).split(/\s+/);
1247
+ if (!candidate || candidate.startsWith("#") || candidate.includes("://")) {
1248
+ return void 0;
1249
+ }
1250
+ const looksLikePath = candidate.startsWith("./") || candidate.startsWith("../") || candidate.startsWith("/") || candidate.includes("/") || /\.[a-z0-9]+$/i.test(candidate);
1251
+ if (!looksLikePath) {
1252
+ return void 0;
1253
+ }
1254
+ return candidate;
1255
+ }
1256
+ async function collectSkillMarkdownFiles(rootDir, collector = []) {
1257
+ if (!await fs8.pathExists(rootDir)) {
1258
+ return collector;
1259
+ }
1260
+ const entries = await fs8.readdir(rootDir, { withFileTypes: true });
1261
+ for (const entry of entries) {
1262
+ const fullPath = path10.join(rootDir, entry.name);
1263
+ if (entry.isDirectory()) {
1264
+ await collectSkillMarkdownFiles(fullPath, collector);
1265
+ continue;
1266
+ }
1267
+ if (entry.isFile() && entry.name === "SKILL.md") {
1268
+ collector.push(fullPath);
1269
+ }
1270
+ }
1271
+ return collector;
1272
+ }
1273
+ async function validateSkillReferences(codexSkillsDir) {
1274
+ const skillFiles = await collectSkillMarkdownFiles(codexSkillsDir);
1275
+ let count = 0;
1276
+ const samples = [];
1277
+ for (const skillFile of skillFiles) {
1278
+ const content = await fs8.readFile(skillFile, "utf8");
1279
+ const lines = normalizeLineEndings(content).split("\n");
1280
+ for (const line of lines) {
1281
+ const candidate = extractReferenceCandidate(line);
1282
+ if (!candidate) {
1283
+ continue;
1284
+ }
1285
+ const referencePath = candidate.split(/[?#]/, 1)[0];
1286
+ if (!referencePath) {
1287
+ continue;
1288
+ }
1289
+ const resolvedPath = path10.resolve(path10.dirname(skillFile), referencePath);
1290
+ if (await fs8.pathExists(resolvedPath)) {
1291
+ continue;
1292
+ }
1293
+ count += 1;
1294
+ if (samples.length < MAX_REFERENCE_ISSUE_SAMPLES) {
1295
+ samples.push({
1296
+ skillPath: skillFile,
1297
+ reference: candidate,
1298
+ resolvedPath
1299
+ });
1300
+ }
1301
+ }
1302
+ }
1303
+ return { count, samples };
1304
+ }
883
1305
  async function syncWithCodex(targetDir) {
884
- const codexHome = resolveCodexHome();
1306
+ const codexHome = path10.resolve(targetDir, ".codex");
885
1307
  const codexSkillsDir = path10.join(codexHome, "skills");
886
1308
  const codexCommandsDir = path10.join(codexSkillsDir, "claude-commands");
1309
+ const codexInstructionsDir = path10.join(codexHome, "instructions");
887
1310
  await fs8.ensureDir(codexSkillsDir);
888
1311
  const claudeRootDir = path10.join(targetDir, ".claude");
889
1312
  const claudeSkillsDir = path10.join(claudeRootDir, "skills");
890
1313
  const claudeCommandsDir = path10.join(claudeRootDir, "commands");
1314
+ const claudeInstructionsDir = path10.join(claudeRootDir, "instructions");
891
1315
  const syncedSkills = await syncSkills(claudeSkillsDir, codexSkillsDir);
892
- const syncedCommands = await syncCommands(claudeCommandsDir, codexCommandsDir);
1316
+ const syncedInstructions = await syncInstructions(
1317
+ claudeInstructionsDir,
1318
+ codexInstructionsDir
1319
+ );
1320
+ const syncedCommands = await syncCommands(
1321
+ claudeCommandsDir,
1322
+ codexCommandsDir
1323
+ );
1324
+ const {
1325
+ codexMcpConfigPath,
1326
+ syncedMcpServers,
1327
+ scope: mcpScope
1328
+ } = await syncMcpServers(targetDir);
1329
+ const { count: referenceIssueCount, samples: referenceIssueSamples } = await validateSkillReferences(codexSkillsDir);
893
1330
  return {
894
1331
  codexHome,
895
1332
  codexSkillsDir,
896
1333
  codexCommandsDir,
1334
+ codexInstructionsDir,
897
1335
  syncedSkills,
898
- syncedCommands
1336
+ syncedCommands,
1337
+ syncedInstructions,
1338
+ codexMcpConfigPath,
1339
+ syncedMcpServers,
1340
+ mcpScope,
1341
+ referenceIssueCount,
1342
+ referenceIssueSamples
899
1343
  };
900
1344
  }
901
1345
 
@@ -1095,54 +1539,76 @@ var init = async (options) => {
1095
1539
  hasScripts,
1096
1540
  scope
1097
1541
  );
1098
- if (!isUserScope) {
1542
+ const codexSyncPath = path11.join(targetDir, ".codex");
1543
+ const { syncCodex } = await promptCodexSync({
1544
+ providedSyncCodex: options.syncCodex,
1545
+ codexPath: codexSyncPath
1546
+ });
1547
+ if (syncCodex) {
1548
+ logger.blank();
1549
+ logger.info("Syncing .claude skills/commands/instructions/MCP to Codex...");
1099
1550
  try {
1100
- await updateGitignore(targetDir);
1551
+ const result = await syncWithCodex(targetDir);
1552
+ if (result.syncedSkills > 0) {
1553
+ logger.step(`Skills synced: ${result.syncedSkills}`);
1554
+ }
1555
+ if (result.syncedCommands > 0) {
1556
+ logger.step(`Commands synced: ${result.syncedCommands}`);
1557
+ }
1558
+ if (result.syncedInstructions > 0) {
1559
+ logger.step(`Instructions synced: ${result.syncedInstructions}`);
1560
+ }
1561
+ if (result.syncedMcpServers > 0) {
1562
+ logger.step(
1563
+ `MCP servers synced (${result.mcpScope}): ${result.syncedMcpServers}`
1564
+ );
1565
+ }
1566
+ if (result.syncedSkills === 0 && result.syncedCommands === 0 && result.syncedInstructions === 0 && result.syncedMcpServers === 0) {
1567
+ logger.warn(
1568
+ "Nothing was synced. .claude/skills, .claude/commands, .claude/instructions, and Claude MCP settings were not found."
1569
+ );
1570
+ } else {
1571
+ logger.success(`Codex sync complete: ${result.codexSkillsDir}`);
1572
+ logger.step(`Codex MCP config: ${result.codexMcpConfigPath}`);
1573
+ if (result.referenceIssueCount > 0) {
1574
+ logger.warn(
1575
+ `Codex skill reference issues found: ${result.referenceIssueCount} (showing up to ${result.referenceIssueSamples.length})`
1576
+ );
1577
+ for (const issue of result.referenceIssueSamples) {
1578
+ const skillPath = path11.relative(targetDir, issue.skillPath);
1579
+ const resolvedPath = path11.relative(targetDir, issue.resolvedPath);
1580
+ logger.step(
1581
+ `${skillPath} -> @${issue.reference} (missing: ${resolvedPath})`
1582
+ );
1583
+ }
1584
+ }
1585
+ }
1101
1586
  } catch (error) {
1102
1587
  logger.warn(
1103
- `Failed to update .gitignore: ${error instanceof Error ? error.message : "Unknown error"}`
1588
+ `Codex sync failed: ${error instanceof Error ? error.message : "Unknown error"}`
1104
1589
  );
1105
1590
  }
1106
1591
  }
1107
- const { syncCodex } = await promptCodexSync({
1108
- providedSyncCodex: options.syncCodex
1109
- });
1110
- if (!syncCodex) {
1111
- return;
1112
- }
1113
- logger.blank();
1114
- logger.info("Syncing .claude skills/commands to Codex...");
1115
- try {
1116
- const result = await syncWithCodex(targetDir);
1117
- if (result.syncedSkills > 0) {
1118
- logger.step(`Skills synced: ${result.syncedSkills}`);
1119
- }
1120
- if (result.syncedCommands > 0) {
1121
- logger.step(`Commands synced: ${result.syncedCommands}`);
1122
- }
1123
- if (result.syncedSkills === 0 && result.syncedCommands === 0) {
1592
+ if (!isUserScope) {
1593
+ try {
1594
+ await updateGitignore(targetDir, { includeCodex: syncCodex });
1595
+ } catch (error) {
1124
1596
  logger.warn(
1125
- "Nothing was synced. .claude/skills and .claude/commands were not found."
1597
+ `Failed to update .gitignore: ${error instanceof Error ? error.message : "Unknown error"}`
1126
1598
  );
1127
- } else {
1128
- logger.success(`Codex sync complete: ${result.codexSkillsDir}`);
1129
1599
  }
1130
- } catch (error) {
1131
- logger.warn(
1132
- `Codex sync failed: ${error instanceof Error ? error.message : "Unknown error"}`
1133
- );
1134
1600
  }
1135
1601
  };
1136
1602
 
1137
1603
  // src/index.ts
1138
1604
  var program = new Command();
1139
- program.name("claude-code").description("Claude Code documentation installer for projects").version("0.7.8");
1605
+ program.name("claude-code").description("Claude Code documentation installer for projects").version("0.7.10");
1140
1606
  program.option(
1141
1607
  "-t, --template <names>",
1142
1608
  "template names (comma-separated: tanstack-start,hono)"
1143
1609
  ).option("-f, --force", "overwrite existing files without prompting").option("--cwd <path>", "target directory (default: current directory)").option("--list", "list available templates").option("-s, --skills", "install skills to .claude/skills/").option("-c, --commands", "install commands to .claude/commands/").option("-a, --agents", "install agents to .claude/agents/").option(
1144
1610
  "--sync-codex",
1145
- "sync installed .claude skills/commands to Codex (~/.codex/skills/)"
1611
+ "sync installed .claude skills/commands/instructions and Claude MCP settings to selected-scope .codex/"
1146
1612
  ).option("--scope <scope>", "installation scope (project|user)").action(async (options) => {
1147
1613
  banner();
1148
1614
  if (options.list) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kood/claude-code",
3
- "version": "0.7.8",
3
+ "version": "0.7.10",
4
4
  "description": "Claude Code documentation installer for projects",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.js",