@kood/claude-code 0.7.7 → 0.7.9
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 +192 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,8 +22,9 @@ var banner = () => {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
// src/commands/init.ts
|
|
25
|
-
import
|
|
25
|
+
import fs9 from "fs-extra";
|
|
26
26
|
import os from "os";
|
|
27
|
+
import path11 from "path";
|
|
27
28
|
|
|
28
29
|
// src/features/templates/template-path-resolver.ts
|
|
29
30
|
import path2 from "path";
|
|
@@ -688,16 +689,40 @@ async function promptScopeSelection(options) {
|
|
|
688
689
|
}
|
|
689
690
|
return { scope: response.value };
|
|
690
691
|
}
|
|
692
|
+
async function promptCodexSync(options) {
|
|
693
|
+
const { providedSyncCodex, codexSkillsPath } = options;
|
|
694
|
+
if (providedSyncCodex !== void 0) {
|
|
695
|
+
return { syncCodex: providedSyncCodex };
|
|
696
|
+
}
|
|
697
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
698
|
+
return { syncCodex: false };
|
|
699
|
+
}
|
|
700
|
+
logger.blank();
|
|
701
|
+
const pathHint = codexSkillsPath ? ` (${codexSkillsPath})` : "";
|
|
702
|
+
const result = await promptConfirm(
|
|
703
|
+
`Also sync with Codex now?${pathHint}`,
|
|
704
|
+
false
|
|
705
|
+
);
|
|
706
|
+
return { syncCodex: result.confirmed };
|
|
707
|
+
}
|
|
691
708
|
|
|
692
709
|
// src/shared/gitignore-manager.ts
|
|
693
710
|
import fs7 from "fs-extra";
|
|
694
711
|
import path9 from "path";
|
|
695
712
|
var CLAUDE_GENERATED_FOLDERS = [
|
|
713
|
+
".claude/brainstorms/",
|
|
714
|
+
".claude/crawler/",
|
|
715
|
+
".claude/first-principles/",
|
|
716
|
+
".claude/genius-ideas/",
|
|
696
717
|
".claude/plan/",
|
|
718
|
+
".claude/plans/",
|
|
697
719
|
".claude/ralph/",
|
|
698
720
|
".claude/refactor/",
|
|
699
|
-
".claude/prd/"
|
|
721
|
+
".claude/prd/",
|
|
722
|
+
".claude/research/",
|
|
723
|
+
".claude/validation-results/"
|
|
700
724
|
];
|
|
725
|
+
var CODEX_GENERATED_FOLDERS = [".codex/"];
|
|
701
726
|
function normalizeIgnorePattern(pattern) {
|
|
702
727
|
return pattern.replace(/\\/g, "/").replace(/^\.?\//, "").replace(/\/+$/, "").trim();
|
|
703
728
|
}
|
|
@@ -710,9 +735,11 @@ function parseIgnoreLine(line) {
|
|
|
710
735
|
const normalized = normalizeIgnorePattern(rawPattern);
|
|
711
736
|
return normalized || null;
|
|
712
737
|
}
|
|
713
|
-
async function updateGitignore(targetDir) {
|
|
738
|
+
async function updateGitignore(targetDir, options = {}) {
|
|
739
|
+
const { includeCodex = false } = options;
|
|
714
740
|
const gitignorePath = path9.join(targetDir, ".gitignore");
|
|
715
741
|
const sectionComment = "# Claude Code generated files";
|
|
742
|
+
const targetFolders = includeCodex ? [...CLAUDE_GENERATED_FOLDERS, ...CODEX_GENERATED_FOLDERS] : CLAUDE_GENERATED_FOLDERS;
|
|
716
743
|
let content = "";
|
|
717
744
|
let hasGitignore = false;
|
|
718
745
|
try {
|
|
@@ -726,7 +753,7 @@ async function updateGitignore(targetDir) {
|
|
|
726
753
|
const existingPatterns = new Set(
|
|
727
754
|
lines.map((line) => parseIgnoreLine(line)).filter((pattern) => Boolean(pattern))
|
|
728
755
|
);
|
|
729
|
-
const linesToAdd =
|
|
756
|
+
const linesToAdd = targetFolders.filter(
|
|
730
757
|
(folder) => !existingPatterns.has(normalizeIgnorePattern(folder))
|
|
731
758
|
);
|
|
732
759
|
if (linesToAdd.length === 0) {
|
|
@@ -757,6 +784,128 @@ async function updateGitignore(targetDir) {
|
|
|
757
784
|
}
|
|
758
785
|
}
|
|
759
786
|
|
|
787
|
+
// src/features/codex-sync/codex-sync.ts
|
|
788
|
+
import fs8 from "fs-extra";
|
|
789
|
+
import path10 from "path";
|
|
790
|
+
var RESERVED_CODEX_SKILL_DIRS = /* @__PURE__ */ new Set([".system", "claude-commands"]);
|
|
791
|
+
function normalizeLineEndings(content) {
|
|
792
|
+
return content.replace(/\r\n/g, "\n");
|
|
793
|
+
}
|
|
794
|
+
function yamlQuote(value) {
|
|
795
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
796
|
+
}
|
|
797
|
+
function extractDescriptionFromFrontmatter(markdown) {
|
|
798
|
+
const frontmatterMatch = markdown.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
799
|
+
if (!frontmatterMatch) {
|
|
800
|
+
return void 0;
|
|
801
|
+
}
|
|
802
|
+
const lines = frontmatterMatch[1].split("\n");
|
|
803
|
+
for (const rawLine of lines) {
|
|
804
|
+
const line = rawLine.trim();
|
|
805
|
+
if (!line.startsWith("description:")) {
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
const value = line.slice("description:".length).trim();
|
|
809
|
+
if (!value) {
|
|
810
|
+
return void 0;
|
|
811
|
+
}
|
|
812
|
+
return value.replace(/^['"]|['"]$/g, "");
|
|
813
|
+
}
|
|
814
|
+
return void 0;
|
|
815
|
+
}
|
|
816
|
+
function buildCommandSkillContent(commandPath, commandRaw) {
|
|
817
|
+
const commandName = path10.basename(commandPath, ".md");
|
|
818
|
+
const normalizedCommand = normalizeLineEndings(commandRaw).trim();
|
|
819
|
+
const sourcePath = path10.resolve(commandPath);
|
|
820
|
+
const description = extractDescriptionFromFrontmatter(normalizedCommand) || `${commandName} command imported from .claude/commands`;
|
|
821
|
+
return `---
|
|
822
|
+
name: ${yamlQuote(commandName)}
|
|
823
|
+
description: ${yamlQuote(description)}
|
|
824
|
+
---
|
|
825
|
+
|
|
826
|
+
Imported command source: ${sourcePath}
|
|
827
|
+
|
|
828
|
+
Use this as a command playbook. If scripts are referenced with .claude-relative paths,
|
|
829
|
+
resolve them from the current workspace first; if missing, ask for the intended repo root.
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
${normalizedCommand}
|
|
834
|
+
`;
|
|
835
|
+
}
|
|
836
|
+
async function syncSkills(claudeSkillsDir, codexSkillsDir) {
|
|
837
|
+
await fs8.ensureDir(codexSkillsDir);
|
|
838
|
+
let sourceSkillNames = [];
|
|
839
|
+
if (await fs8.pathExists(claudeSkillsDir)) {
|
|
840
|
+
const sourceEntries = await fs8.readdir(claudeSkillsDir, { withFileTypes: true });
|
|
841
|
+
sourceSkillNames = sourceEntries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => name !== ".system");
|
|
842
|
+
}
|
|
843
|
+
const sourceSkillSet = new Set(sourceSkillNames);
|
|
844
|
+
const codexEntries = await fs8.readdir(codexSkillsDir, { withFileTypes: true });
|
|
845
|
+
for (const entry of codexEntries) {
|
|
846
|
+
if (!entry.isDirectory()) {
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
if (RESERVED_CODEX_SKILL_DIRS.has(entry.name)) {
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (!sourceSkillSet.has(entry.name)) {
|
|
853
|
+
await fs8.remove(path10.join(codexSkillsDir, entry.name));
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
for (const skillName of sourceSkillNames) {
|
|
857
|
+
const src = path10.join(claudeSkillsDir, skillName);
|
|
858
|
+
const dest = path10.join(codexSkillsDir, skillName);
|
|
859
|
+
if (await fs8.pathExists(dest)) {
|
|
860
|
+
await fs8.remove(dest);
|
|
861
|
+
}
|
|
862
|
+
await copyRecursive(src, dest, { files: 0, directories: 0 });
|
|
863
|
+
}
|
|
864
|
+
return sourceSkillNames.length;
|
|
865
|
+
}
|
|
866
|
+
async function syncCommands(claudeCommandsDir, codexCommandsDir) {
|
|
867
|
+
if (!await fs8.pathExists(claudeCommandsDir)) {
|
|
868
|
+
await fs8.remove(codexCommandsDir);
|
|
869
|
+
return 0;
|
|
870
|
+
}
|
|
871
|
+
const entries = await fs8.readdir(claudeCommandsDir, { withFileTypes: true });
|
|
872
|
+
const commandFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
|
|
873
|
+
if (commandFiles.length === 0) {
|
|
874
|
+
await fs8.remove(codexCommandsDir);
|
|
875
|
+
return 0;
|
|
876
|
+
}
|
|
877
|
+
await fs8.remove(codexCommandsDir);
|
|
878
|
+
await fs8.ensureDir(codexCommandsDir);
|
|
879
|
+
for (const commandFile of commandFiles) {
|
|
880
|
+
const commandPath = path10.join(claudeCommandsDir, commandFile);
|
|
881
|
+
const commandRaw = await fs8.readFile(commandPath, "utf8");
|
|
882
|
+
const commandName = path10.basename(commandFile, ".md");
|
|
883
|
+
const skillDir = path10.join(codexCommandsDir, commandName);
|
|
884
|
+
const skillPath = path10.join(skillDir, "SKILL.md");
|
|
885
|
+
await fs8.ensureDir(skillDir);
|
|
886
|
+
await fs8.writeFile(skillPath, buildCommandSkillContent(commandPath, commandRaw));
|
|
887
|
+
}
|
|
888
|
+
return commandFiles.length;
|
|
889
|
+
}
|
|
890
|
+
async function syncWithCodex(targetDir) {
|
|
891
|
+
const codexHome = path10.resolve(targetDir, ".codex");
|
|
892
|
+
const codexSkillsDir = path10.join(codexHome, "skills");
|
|
893
|
+
const codexCommandsDir = path10.join(codexSkillsDir, "claude-commands");
|
|
894
|
+
await fs8.ensureDir(codexSkillsDir);
|
|
895
|
+
const claudeRootDir = path10.join(targetDir, ".claude");
|
|
896
|
+
const claudeSkillsDir = path10.join(claudeRootDir, "skills");
|
|
897
|
+
const claudeCommandsDir = path10.join(claudeRootDir, "commands");
|
|
898
|
+
const syncedSkills = await syncSkills(claudeSkillsDir, codexSkillsDir);
|
|
899
|
+
const syncedCommands = await syncCommands(claudeCommandsDir, codexCommandsDir);
|
|
900
|
+
return {
|
|
901
|
+
codexHome,
|
|
902
|
+
codexSkillsDir,
|
|
903
|
+
codexCommandsDir,
|
|
904
|
+
syncedSkills,
|
|
905
|
+
syncedCommands
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
|
|
760
909
|
// src/commands/init.ts
|
|
761
910
|
var TEMPLATE_DESCRIPTIONS = {
|
|
762
911
|
"tanstack-start": "TanStack Start + React \uD480\uC2A4\uD0DD \uD504\uB85C\uC81D\uD2B8",
|
|
@@ -766,7 +915,7 @@ var TEMPLATE_DESCRIPTIONS = {
|
|
|
766
915
|
};
|
|
767
916
|
async function validateTargetDirectory(targetDir) {
|
|
768
917
|
try {
|
|
769
|
-
const stat = await
|
|
918
|
+
const stat = await fs9.stat(targetDir);
|
|
770
919
|
if (!stat.isDirectory()) {
|
|
771
920
|
logger.error(`Target is not a directory: ${targetDir}`);
|
|
772
921
|
process.exit(1);
|
|
@@ -780,7 +929,7 @@ async function validateTargetDirectory(targetDir) {
|
|
|
780
929
|
process.exit(1);
|
|
781
930
|
}
|
|
782
931
|
try {
|
|
783
|
-
await
|
|
932
|
+
await fs9.access(targetDir, fs9.constants.W_OK);
|
|
784
933
|
} catch {
|
|
785
934
|
logger.error(`No write permission for: ${targetDir}`);
|
|
786
935
|
process.exit(1);
|
|
@@ -953,9 +1102,38 @@ var init = async (options) => {
|
|
|
953
1102
|
hasScripts,
|
|
954
1103
|
scope
|
|
955
1104
|
);
|
|
1105
|
+
const codexSkillsPath = path11.join(targetDir, ".codex", "skills");
|
|
1106
|
+
const { syncCodex } = await promptCodexSync({
|
|
1107
|
+
providedSyncCodex: options.syncCodex,
|
|
1108
|
+
codexSkillsPath
|
|
1109
|
+
});
|
|
1110
|
+
if (syncCodex) {
|
|
1111
|
+
logger.blank();
|
|
1112
|
+
logger.info("Syncing .claude skills/commands to Codex...");
|
|
1113
|
+
try {
|
|
1114
|
+
const result = await syncWithCodex(targetDir);
|
|
1115
|
+
if (result.syncedSkills > 0) {
|
|
1116
|
+
logger.step(`Skills synced: ${result.syncedSkills}`);
|
|
1117
|
+
}
|
|
1118
|
+
if (result.syncedCommands > 0) {
|
|
1119
|
+
logger.step(`Commands synced: ${result.syncedCommands}`);
|
|
1120
|
+
}
|
|
1121
|
+
if (result.syncedSkills === 0 && result.syncedCommands === 0) {
|
|
1122
|
+
logger.warn(
|
|
1123
|
+
"Nothing was synced. .claude/skills and .claude/commands were not found."
|
|
1124
|
+
);
|
|
1125
|
+
} else {
|
|
1126
|
+
logger.success(`Codex sync complete: ${result.codexSkillsDir}`);
|
|
1127
|
+
}
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
logger.warn(
|
|
1130
|
+
`Codex sync failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
956
1134
|
if (!isUserScope) {
|
|
957
1135
|
try {
|
|
958
|
-
await updateGitignore(targetDir);
|
|
1136
|
+
await updateGitignore(targetDir, { includeCodex: syncCodex });
|
|
959
1137
|
} catch (error) {
|
|
960
1138
|
logger.warn(
|
|
961
1139
|
`Failed to update .gitignore: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
@@ -966,11 +1144,14 @@ var init = async (options) => {
|
|
|
966
1144
|
|
|
967
1145
|
// src/index.ts
|
|
968
1146
|
var program = new Command();
|
|
969
|
-
program.name("claude-code").description("Claude Code documentation installer for projects").version("0.7.
|
|
1147
|
+
program.name("claude-code").description("Claude Code documentation installer for projects").version("0.7.9");
|
|
970
1148
|
program.option(
|
|
971
1149
|
"-t, --template <names>",
|
|
972
1150
|
"template names (comma-separated: tanstack-start,hono)"
|
|
973
|
-
).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(
|
|
1151
|
+
).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
|
+
"--sync-codex",
|
|
1153
|
+
"sync installed .claude skills/commands to selected-scope .codex/skills/"
|
|
1154
|
+
).option("--scope <scope>", "installation scope (project|user)").action(async (options) => {
|
|
974
1155
|
banner();
|
|
975
1156
|
if (options.list) {
|
|
976
1157
|
const templates = await listAvailableTemplates();
|
|
@@ -986,7 +1167,8 @@ program.option(
|
|
|
986
1167
|
scope: options.scope,
|
|
987
1168
|
skills: options.skills,
|
|
988
1169
|
commands: options.commands,
|
|
989
|
-
agents: options.agents
|
|
1170
|
+
agents: options.agents,
|
|
1171
|
+
syncCodex: options.syncCodex
|
|
990
1172
|
});
|
|
991
1173
|
});
|
|
992
1174
|
program.parse();
|