@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.
Files changed (2) hide show
  1. package/dist/index.js +192 -10
  2. 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 fs8 from "fs-extra";
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 = CLAUDE_GENERATED_FOLDERS.filter(
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 fs8.stat(targetDir);
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 fs8.access(targetDir, fs8.constants.W_OK);
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.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("--scope <scope>", "installation scope (project|user)").action(async (options) => {
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kood/claude-code",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
4
4
  "description": "Claude Code documentation installer for projects",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.js",