@kood/claude-code 0.7.9 → 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.
- package/dist/index.js +475 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ var banner = () => {
|
|
|
23
23
|
|
|
24
24
|
// src/commands/init.ts
|
|
25
25
|
import fs9 from "fs-extra";
|
|
26
|
-
import
|
|
26
|
+
import os2 from "os";
|
|
27
27
|
import path11 from "path";
|
|
28
28
|
|
|
29
29
|
// src/features/templates/template-path-resolver.ts
|
|
@@ -690,7 +690,7 @@ async function promptScopeSelection(options) {
|
|
|
690
690
|
return { scope: response.value };
|
|
691
691
|
}
|
|
692
692
|
async function promptCodexSync(options) {
|
|
693
|
-
const { providedSyncCodex,
|
|
693
|
+
const { providedSyncCodex, codexPath } = options;
|
|
694
694
|
if (providedSyncCodex !== void 0) {
|
|
695
695
|
return { syncCodex: providedSyncCodex };
|
|
696
696
|
}
|
|
@@ -698,7 +698,7 @@ async function promptCodexSync(options) {
|
|
|
698
698
|
return { syncCodex: false };
|
|
699
699
|
}
|
|
700
700
|
logger.blank();
|
|
701
|
-
const pathHint =
|
|
701
|
+
const pathHint = codexPath ? ` (${codexPath})` : "";
|
|
702
702
|
const result = await promptConfirm(
|
|
703
703
|
`Also sync with Codex now?${pathHint}`,
|
|
704
704
|
false
|
|
@@ -786,8 +786,15 @@ async function updateGitignore(targetDir, options = {}) {
|
|
|
786
786
|
|
|
787
787
|
// src/features/codex-sync/codex-sync.ts
|
|
788
788
|
import fs8 from "fs-extra";
|
|
789
|
+
import os from "os";
|
|
789
790
|
import path10 from "path";
|
|
790
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";
|
|
791
798
|
function normalizeLineEndings(content) {
|
|
792
799
|
return content.replace(/\r\n/g, "\n");
|
|
793
800
|
}
|
|
@@ -816,6 +823,10 @@ function extractDescriptionFromFrontmatter(markdown) {
|
|
|
816
823
|
function buildCommandSkillContent(commandPath, commandRaw) {
|
|
817
824
|
const commandName = path10.basename(commandPath, ".md");
|
|
818
825
|
const normalizedCommand = normalizeLineEndings(commandRaw).trim();
|
|
826
|
+
const rewrittenCommand = normalizedCommand.replace(
|
|
827
|
+
COMMAND_INSTRUCTION_PREFIX_REGEX,
|
|
828
|
+
COMMAND_INSTRUCTION_PREFIX_REPLACEMENT
|
|
829
|
+
);
|
|
819
830
|
const sourcePath = path10.resolve(commandPath);
|
|
820
831
|
const description = extractDescriptionFromFrontmatter(normalizedCommand) || `${commandName} command imported from .claude/commands`;
|
|
821
832
|
return `---
|
|
@@ -830,18 +841,344 @@ resolve them from the current workspace first; if missing, ask for the intended
|
|
|
830
841
|
|
|
831
842
|
---
|
|
832
843
|
|
|
833
|
-
${
|
|
844
|
+
${rewrittenCommand}
|
|
834
845
|
`;
|
|
835
846
|
}
|
|
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;
|
|
853
|
+
}
|
|
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
|
+
}
|
|
836
1169
|
async function syncSkills(claudeSkillsDir, codexSkillsDir) {
|
|
837
1170
|
await fs8.ensureDir(codexSkillsDir);
|
|
838
1171
|
let sourceSkillNames = [];
|
|
839
1172
|
if (await fs8.pathExists(claudeSkillsDir)) {
|
|
840
|
-
const sourceEntries = await fs8.readdir(claudeSkillsDir, {
|
|
1173
|
+
const sourceEntries = await fs8.readdir(claudeSkillsDir, {
|
|
1174
|
+
withFileTypes: true
|
|
1175
|
+
});
|
|
841
1176
|
sourceSkillNames = sourceEntries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => name !== ".system");
|
|
842
1177
|
}
|
|
843
1178
|
const sourceSkillSet = new Set(sourceSkillNames);
|
|
844
|
-
const codexEntries = await fs8.readdir(codexSkillsDir, {
|
|
1179
|
+
const codexEntries = await fs8.readdir(codexSkillsDir, {
|
|
1180
|
+
withFileTypes: true
|
|
1181
|
+
});
|
|
845
1182
|
for (const entry of codexEntries) {
|
|
846
1183
|
if (!entry.isDirectory()) {
|
|
847
1184
|
continue;
|
|
@@ -883,26 +1220,126 @@ async function syncCommands(claudeCommandsDir, codexCommandsDir) {
|
|
|
883
1220
|
const skillDir = path10.join(codexCommandsDir, commandName);
|
|
884
1221
|
const skillPath = path10.join(skillDir, "SKILL.md");
|
|
885
1222
|
await fs8.ensureDir(skillDir);
|
|
886
|
-
await fs8.writeFile(
|
|
1223
|
+
await fs8.writeFile(
|
|
1224
|
+
skillPath,
|
|
1225
|
+
buildCommandSkillContent(commandPath, commandRaw)
|
|
1226
|
+
);
|
|
887
1227
|
}
|
|
888
1228
|
return commandFiles.length;
|
|
889
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
|
+
}
|
|
890
1305
|
async function syncWithCodex(targetDir) {
|
|
891
1306
|
const codexHome = path10.resolve(targetDir, ".codex");
|
|
892
1307
|
const codexSkillsDir = path10.join(codexHome, "skills");
|
|
893
1308
|
const codexCommandsDir = path10.join(codexSkillsDir, "claude-commands");
|
|
1309
|
+
const codexInstructionsDir = path10.join(codexHome, "instructions");
|
|
894
1310
|
await fs8.ensureDir(codexSkillsDir);
|
|
895
1311
|
const claudeRootDir = path10.join(targetDir, ".claude");
|
|
896
1312
|
const claudeSkillsDir = path10.join(claudeRootDir, "skills");
|
|
897
1313
|
const claudeCommandsDir = path10.join(claudeRootDir, "commands");
|
|
1314
|
+
const claudeInstructionsDir = path10.join(claudeRootDir, "instructions");
|
|
898
1315
|
const syncedSkills = await syncSkills(claudeSkillsDir, codexSkillsDir);
|
|
899
|
-
const
|
|
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);
|
|
900
1330
|
return {
|
|
901
1331
|
codexHome,
|
|
902
1332
|
codexSkillsDir,
|
|
903
1333
|
codexCommandsDir,
|
|
1334
|
+
codexInstructionsDir,
|
|
904
1335
|
syncedSkills,
|
|
905
|
-
syncedCommands
|
|
1336
|
+
syncedCommands,
|
|
1337
|
+
syncedInstructions,
|
|
1338
|
+
codexMcpConfigPath,
|
|
1339
|
+
syncedMcpServers,
|
|
1340
|
+
mcpScope,
|
|
1341
|
+
referenceIssueCount,
|
|
1342
|
+
referenceIssueSamples
|
|
906
1343
|
};
|
|
907
1344
|
}
|
|
908
1345
|
|
|
@@ -1056,7 +1493,7 @@ var init = async (options) => {
|
|
|
1056
1493
|
const { scope } = await promptScopeSelection({
|
|
1057
1494
|
providedScope: options.scope
|
|
1058
1495
|
});
|
|
1059
|
-
const targetDir = scope === "user" ?
|
|
1496
|
+
const targetDir = scope === "user" ? os2.homedir() : projectDir;
|
|
1060
1497
|
const isUserScope = scope === "user";
|
|
1061
1498
|
if (isUserScope) {
|
|
1062
1499
|
logger.blank();
|
|
@@ -1102,14 +1539,14 @@ var init = async (options) => {
|
|
|
1102
1539
|
hasScripts,
|
|
1103
1540
|
scope
|
|
1104
1541
|
);
|
|
1105
|
-
const
|
|
1542
|
+
const codexSyncPath = path11.join(targetDir, ".codex");
|
|
1106
1543
|
const { syncCodex } = await promptCodexSync({
|
|
1107
1544
|
providedSyncCodex: options.syncCodex,
|
|
1108
|
-
|
|
1545
|
+
codexPath: codexSyncPath
|
|
1109
1546
|
});
|
|
1110
1547
|
if (syncCodex) {
|
|
1111
1548
|
logger.blank();
|
|
1112
|
-
logger.info("Syncing .claude skills/commands to Codex...");
|
|
1549
|
+
logger.info("Syncing .claude skills/commands/instructions/MCP to Codex...");
|
|
1113
1550
|
try {
|
|
1114
1551
|
const result = await syncWithCodex(targetDir);
|
|
1115
1552
|
if (result.syncedSkills > 0) {
|
|
@@ -1118,12 +1555,33 @@ var init = async (options) => {
|
|
|
1118
1555
|
if (result.syncedCommands > 0) {
|
|
1119
1556
|
logger.step(`Commands synced: ${result.syncedCommands}`);
|
|
1120
1557
|
}
|
|
1121
|
-
if (result.
|
|
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) {
|
|
1122
1567
|
logger.warn(
|
|
1123
|
-
"Nothing was synced. .claude/skills
|
|
1568
|
+
"Nothing was synced. .claude/skills, .claude/commands, .claude/instructions, and Claude MCP settings were not found."
|
|
1124
1569
|
);
|
|
1125
1570
|
} else {
|
|
1126
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
|
+
}
|
|
1127
1585
|
}
|
|
1128
1586
|
} catch (error) {
|
|
1129
1587
|
logger.warn(
|
|
@@ -1144,13 +1602,13 @@ var init = async (options) => {
|
|
|
1144
1602
|
|
|
1145
1603
|
// src/index.ts
|
|
1146
1604
|
var program = new Command();
|
|
1147
|
-
program.name("claude-code").description("Claude Code documentation installer for projects").version("0.7.
|
|
1605
|
+
program.name("claude-code").description("Claude Code documentation installer for projects").version("0.7.10");
|
|
1148
1606
|
program.option(
|
|
1149
1607
|
"-t, --template <names>",
|
|
1150
1608
|
"template names (comma-separated: tanstack-start,hono)"
|
|
1151
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(
|
|
1152
1610
|
"--sync-codex",
|
|
1153
|
-
"sync installed .claude skills/commands to selected-scope .codex/
|
|
1611
|
+
"sync installed .claude skills/commands/instructions and Claude MCP settings to selected-scope .codex/"
|
|
1154
1612
|
).option("--scope <scope>", "installation scope (project|user)").action(async (options) => {
|
|
1155
1613
|
banner();
|
|
1156
1614
|
if (options.list) {
|