@kood/claude-code 0.7.10 → 0.7.12
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 +182 -90
- package/package.json +1 -1
- package/templates/.claude/agents/codex.md +40 -270
- package/templates/.claude/commands/cancel-ralph.md +18 -0
- package/templates/.claude/commands/git-all.md +12 -12
- package/templates/.claude/commands/git-merge.md +67 -0
- package/templates/.claude/commands/git-session.md +10 -10
- package/templates/.claude/commands/lint-fix.md +5 -5
- package/templates/.claude/commands/pre-deploy.md +7 -7
- package/templates/.claude/commands/ralph-loop.md +18 -0
- package/templates/.claude/commands/version-update.md +7 -7
- package/templates/.claude/hooks/ralph-stop-hook.sh +124 -0
- package/templates/.claude/hooks/session-env-setup.sh +33 -0
- package/templates/.claude/scripts/agent-teams/check-availability.sh +1 -1
- package/templates/.claude/scripts/agent-teams/setup-tmux.sh +1 -1
- package/templates/.claude/scripts/git/git-merge.sh +73 -0
- package/templates/.claude/scripts/setup-ralph-loop.sh +161 -0
- package/templates/.claude/skills/codex/SKILL.md +94 -469
- package/templates/.claude/skills/teams/SKILL.md +1 -1
- package/templates/.claude/skills/teams/references/fallback-strategy.md +1 -1
package/dist/index.js
CHANGED
|
@@ -22,9 +22,9 @@ var banner = () => {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
// src/commands/init.ts
|
|
25
|
-
import
|
|
25
|
+
import fs10 from "fs-extra";
|
|
26
26
|
import os2 from "os";
|
|
27
|
-
import
|
|
27
|
+
import path12 from "path";
|
|
28
28
|
|
|
29
29
|
// src/features/templates/template-path-resolver.ts
|
|
30
30
|
import path2 from "path";
|
|
@@ -327,6 +327,7 @@ var copyCommands = createExtrasCopier("commands");
|
|
|
327
327
|
var copyAgents = createExtrasCopier("agents");
|
|
328
328
|
var copyInstructions = createExtrasCopier("instructions");
|
|
329
329
|
var copyScripts = createExtrasCopier("scripts");
|
|
330
|
+
var copyHooks = createExtrasCopier("hooks");
|
|
330
331
|
var getSkillsToInstall = async (skillsSrc, templates) => {
|
|
331
332
|
const metadataMap = await loadAllSkillMetadata(skillsSrc);
|
|
332
333
|
const isNonUITemplate = templates.some((t) => NON_UI_TEMPLATES.includes(t));
|
|
@@ -372,6 +373,7 @@ var checkExistingClaudeFiles = async (targetDir) => {
|
|
|
372
373
|
const agentsDir = path8.join(targetDir, ".claude", "agents");
|
|
373
374
|
const instructionsDir = path8.join(targetDir, ".claude", "instructions");
|
|
374
375
|
const scriptsDir = path8.join(targetDir, ".claude", "scripts");
|
|
376
|
+
const hooksDir = path8.join(targetDir, ".claude", "hooks");
|
|
375
377
|
if (await fs6.pathExists(skillsDir)) {
|
|
376
378
|
existingFiles.push(".claude/skills/");
|
|
377
379
|
}
|
|
@@ -387,6 +389,9 @@ var checkExistingClaudeFiles = async (targetDir) => {
|
|
|
387
389
|
if (await fs6.pathExists(scriptsDir)) {
|
|
388
390
|
existingFiles.push(".claude/scripts/");
|
|
389
391
|
}
|
|
392
|
+
if (await fs6.pathExists(hooksDir)) {
|
|
393
|
+
existingFiles.push(".claude/hooks/");
|
|
394
|
+
}
|
|
390
395
|
return existingFiles;
|
|
391
396
|
};
|
|
392
397
|
var checkAllExtrasExist = async (_templates) => {
|
|
@@ -396,15 +401,56 @@ var checkAllExtrasExist = async (_templates) => {
|
|
|
396
401
|
const agentsSrc = path8.join(claudeDir, "agents");
|
|
397
402
|
const instructionsSrc = path8.join(claudeDir, "instructions");
|
|
398
403
|
const scriptsSrc = path8.join(claudeDir, "scripts");
|
|
404
|
+
const hooksSrc = path8.join(claudeDir, "hooks");
|
|
399
405
|
const hasSkills = await hasFiles(skillsSrc);
|
|
400
406
|
const hasCommands = await hasFiles(commandsSrc);
|
|
401
407
|
const hasAgents = await hasFiles(agentsSrc);
|
|
402
408
|
const hasInstructions = await hasFiles(instructionsSrc);
|
|
403
409
|
const hasScripts = await hasFiles(scriptsSrc);
|
|
404
|
-
|
|
410
|
+
const hasHooks = await hasFiles(hooksSrc);
|
|
411
|
+
return {
|
|
412
|
+
hasSkills,
|
|
413
|
+
hasCommands,
|
|
414
|
+
hasAgents,
|
|
415
|
+
hasInstructions,
|
|
416
|
+
hasScripts,
|
|
417
|
+
hasHooks
|
|
418
|
+
};
|
|
405
419
|
};
|
|
406
420
|
|
|
407
421
|
// src/features/extras/extras-installer.ts
|
|
422
|
+
import fs7 from "fs-extra";
|
|
423
|
+
import path9 from "path";
|
|
424
|
+
async function registerSessionStartHook(targetDir) {
|
|
425
|
+
const settingsPath = path9.join(targetDir, ".claude", "settings.local.json");
|
|
426
|
+
const hookCommand = ".claude/hooks/session-env-setup.sh";
|
|
427
|
+
let config = {};
|
|
428
|
+
if (await fs7.pathExists(settingsPath)) {
|
|
429
|
+
try {
|
|
430
|
+
config = await fs7.readJson(settingsPath);
|
|
431
|
+
} catch {
|
|
432
|
+
config = {};
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (!config.hooks) {
|
|
436
|
+
config.hooks = {};
|
|
437
|
+
}
|
|
438
|
+
if (!config.hooks.SessionStart) {
|
|
439
|
+
config.hooks.SessionStart = [];
|
|
440
|
+
}
|
|
441
|
+
const alreadyRegistered = config.hooks.SessionStart.some(
|
|
442
|
+
(hook) => hook.type === "command" && hook.command === hookCommand
|
|
443
|
+
);
|
|
444
|
+
if (!alreadyRegistered) {
|
|
445
|
+
config.hooks.SessionStart.push({
|
|
446
|
+
type: "command",
|
|
447
|
+
command: hookCommand
|
|
448
|
+
});
|
|
449
|
+
await fs7.ensureDir(path9.dirname(settingsPath));
|
|
450
|
+
await fs7.writeJson(settingsPath, config, { spaces: 2 });
|
|
451
|
+
logger.step("Registered SessionStart hook for CLAUDE_SCRIPTS_ROOT");
|
|
452
|
+
}
|
|
453
|
+
}
|
|
408
454
|
function logExistingFilesUpdate(existingClaudeFiles) {
|
|
409
455
|
if (existingClaudeFiles.length > 0) {
|
|
410
456
|
logger.info("Updating existing extras:");
|
|
@@ -475,6 +521,22 @@ async function installScriptsIfNeeded(templates, targetDir, shouldInstall, hasSc
|
|
|
475
521
|
);
|
|
476
522
|
return scriptsResult;
|
|
477
523
|
}
|
|
524
|
+
async function installHooksIfNeeded(templates, targetDir, shouldInstall, hasHooks) {
|
|
525
|
+
if (!shouldInstall) {
|
|
526
|
+
return { files: 0, directories: 0 };
|
|
527
|
+
}
|
|
528
|
+
if (!hasHooks) {
|
|
529
|
+
logger.warn("No hooks found in selected templates.");
|
|
530
|
+
return { files: 0, directories: 0 };
|
|
531
|
+
}
|
|
532
|
+
logger.blank();
|
|
533
|
+
logger.info("Installing hooks...");
|
|
534
|
+
const hooksResult = await copyHooks(templates, targetDir);
|
|
535
|
+
logger.success(
|
|
536
|
+
`Hooks: ${hooksResult.files} files, ${hooksResult.directories} directories`
|
|
537
|
+
);
|
|
538
|
+
return hooksResult;
|
|
539
|
+
}
|
|
478
540
|
async function installInstructionsIfNeeded(templates, targetDir, shouldInstall, hasInstructions) {
|
|
479
541
|
if (!shouldInstall) {
|
|
480
542
|
return { files: 0, directories: 0 };
|
|
@@ -497,9 +559,10 @@ async function installExtras(templates, targetDir, flags, availability) {
|
|
|
497
559
|
installCommands,
|
|
498
560
|
installAgents,
|
|
499
561
|
installInstructions,
|
|
500
|
-
installScripts
|
|
562
|
+
installScripts,
|
|
563
|
+
installHooks
|
|
501
564
|
} = flags;
|
|
502
|
-
if (!installSkills && !installCommands && !installAgents && !installInstructions && !installScripts) {
|
|
565
|
+
if (!installSkills && !installCommands && !installAgents && !installInstructions && !installScripts && !installHooks) {
|
|
503
566
|
return { files: 0, directories: 0 };
|
|
504
567
|
}
|
|
505
568
|
const existingClaudeFiles = await checkExistingClaudeFiles(targetDir);
|
|
@@ -534,8 +597,17 @@ async function installExtras(templates, targetDir, flags, availability) {
|
|
|
534
597
|
installScripts,
|
|
535
598
|
availability.hasScripts
|
|
536
599
|
);
|
|
537
|
-
const
|
|
538
|
-
|
|
600
|
+
const hooksResult = await installHooksIfNeeded(
|
|
601
|
+
templates,
|
|
602
|
+
targetDir,
|
|
603
|
+
installHooks,
|
|
604
|
+
availability.hasHooks
|
|
605
|
+
);
|
|
606
|
+
if (installHooks && hooksResult.files > 0) {
|
|
607
|
+
await registerSessionStartHook(targetDir);
|
|
608
|
+
}
|
|
609
|
+
const totalFiles = skillsResult.files + commandsResult.files + agentsResult.files + instructionsResult.files + scriptsResult.files + hooksResult.files;
|
|
610
|
+
const totalDirectories = skillsResult.directories + commandsResult.directories + agentsResult.directories + instructionsResult.directories + scriptsResult.directories + hooksResult.directories;
|
|
539
611
|
return { files: totalFiles, directories: totalDirectories };
|
|
540
612
|
}
|
|
541
613
|
|
|
@@ -620,19 +692,22 @@ async function promptExtrasSelection(options) {
|
|
|
620
692
|
agents,
|
|
621
693
|
instructions,
|
|
622
694
|
scripts,
|
|
695
|
+
hooks,
|
|
623
696
|
hasSkills,
|
|
624
697
|
hasCommands,
|
|
625
698
|
hasAgents,
|
|
626
699
|
hasInstructions,
|
|
627
|
-
hasScripts
|
|
700
|
+
hasScripts,
|
|
701
|
+
hasHooks
|
|
628
702
|
} = options;
|
|
629
703
|
let installSkills = skills ?? false;
|
|
630
704
|
let installCommands = commands ?? false;
|
|
631
705
|
const installAgents = agents ?? hasAgents;
|
|
632
706
|
const installInstructions = instructions ?? hasInstructions;
|
|
633
707
|
const installScripts = scripts ?? hasScripts;
|
|
634
|
-
const
|
|
635
|
-
|
|
708
|
+
const installHooks = hooks ?? hasHooks;
|
|
709
|
+
const noOptionsProvided = skills === void 0 && commands === void 0 && agents === void 0 && instructions === void 0 && scripts === void 0 && hooks === void 0;
|
|
710
|
+
if (noOptionsProvided && (hasSkills || hasCommands || hasAgents || hasInstructions || hasScripts || hasHooks)) {
|
|
636
711
|
logger.blank();
|
|
637
712
|
if (hasSkills) {
|
|
638
713
|
const result = await promptConfirm(
|
|
@@ -654,7 +729,8 @@ async function promptExtrasSelection(options) {
|
|
|
654
729
|
installCommands,
|
|
655
730
|
installAgents,
|
|
656
731
|
installInstructions,
|
|
657
|
-
installScripts
|
|
732
|
+
installScripts,
|
|
733
|
+
installHooks
|
|
658
734
|
};
|
|
659
735
|
}
|
|
660
736
|
async function promptScopeSelection(options) {
|
|
@@ -707,8 +783,8 @@ async function promptCodexSync(options) {
|
|
|
707
783
|
}
|
|
708
784
|
|
|
709
785
|
// src/shared/gitignore-manager.ts
|
|
710
|
-
import
|
|
711
|
-
import
|
|
786
|
+
import fs8 from "fs-extra";
|
|
787
|
+
import path10 from "path";
|
|
712
788
|
var CLAUDE_GENERATED_FOLDERS = [
|
|
713
789
|
".claude/brainstorms/",
|
|
714
790
|
".claude/crawler/",
|
|
@@ -737,13 +813,13 @@ function parseIgnoreLine(line) {
|
|
|
737
813
|
}
|
|
738
814
|
async function updateGitignore(targetDir, options = {}) {
|
|
739
815
|
const { includeCodex = false } = options;
|
|
740
|
-
const gitignorePath =
|
|
816
|
+
const gitignorePath = path10.join(targetDir, ".gitignore");
|
|
741
817
|
const sectionComment = "# Claude Code generated files";
|
|
742
818
|
const targetFolders = includeCodex ? [...CLAUDE_GENERATED_FOLDERS, ...CODEX_GENERATED_FOLDERS] : CLAUDE_GENERATED_FOLDERS;
|
|
743
819
|
let content = "";
|
|
744
820
|
let hasGitignore = false;
|
|
745
821
|
try {
|
|
746
|
-
content = await
|
|
822
|
+
content = await fs8.readFile(gitignorePath, "utf-8");
|
|
747
823
|
hasGitignore = true;
|
|
748
824
|
} catch {
|
|
749
825
|
content = "";
|
|
@@ -776,7 +852,7 @@ async function updateGitignore(targetDir, options = {}) {
|
|
|
776
852
|
if (!newContent.endsWith(eol)) {
|
|
777
853
|
newContent += eol;
|
|
778
854
|
}
|
|
779
|
-
await
|
|
855
|
+
await fs8.writeFile(gitignorePath, newContent, "utf-8");
|
|
780
856
|
if (hasGitignore) {
|
|
781
857
|
logger.success(`.gitignore updated with ${linesToAdd.length} patterns`);
|
|
782
858
|
} else {
|
|
@@ -785,9 +861,9 @@ async function updateGitignore(targetDir, options = {}) {
|
|
|
785
861
|
}
|
|
786
862
|
|
|
787
863
|
// src/features/codex-sync/codex-sync.ts
|
|
788
|
-
import
|
|
864
|
+
import fs9 from "fs-extra";
|
|
789
865
|
import os from "os";
|
|
790
|
-
import
|
|
866
|
+
import path11 from "path";
|
|
791
867
|
var RESERVED_CODEX_SKILL_DIRS = /* @__PURE__ */ new Set([".system", "claude-commands"]);
|
|
792
868
|
var COMMAND_INSTRUCTION_PREFIX_REGEX = /^@\.\.\/instructions\//gm;
|
|
793
869
|
var COMMAND_INSTRUCTION_PREFIX_REPLACEMENT = "@../../../instructions/";
|
|
@@ -821,13 +897,13 @@ function extractDescriptionFromFrontmatter(markdown) {
|
|
|
821
897
|
return void 0;
|
|
822
898
|
}
|
|
823
899
|
function buildCommandSkillContent(commandPath, commandRaw) {
|
|
824
|
-
const commandName =
|
|
900
|
+
const commandName = path11.basename(commandPath, ".md");
|
|
825
901
|
const normalizedCommand = normalizeLineEndings(commandRaw).trim();
|
|
826
902
|
const rewrittenCommand = normalizedCommand.replace(
|
|
827
903
|
COMMAND_INSTRUCTION_PREFIX_REGEX,
|
|
828
904
|
COMMAND_INSTRUCTION_PREFIX_REPLACEMENT
|
|
829
905
|
);
|
|
830
|
-
const sourcePath =
|
|
906
|
+
const sourcePath = path11.resolve(commandPath);
|
|
831
907
|
const description = extractDescriptionFromFrontmatter(normalizedCommand) || `${commandName} command imported from .claude/commands`;
|
|
832
908
|
return `---
|
|
833
909
|
name: ${yamlQuote(commandName)}
|
|
@@ -905,9 +981,9 @@ function getProjectState(claudeState, targetDir) {
|
|
|
905
981
|
if (!isRecord(projects)) {
|
|
906
982
|
return void 0;
|
|
907
983
|
}
|
|
908
|
-
const normalizedTarget =
|
|
984
|
+
const normalizedTarget = path11.resolve(targetDir);
|
|
909
985
|
for (const [projectPath, projectState] of Object.entries(projects)) {
|
|
910
|
-
if (
|
|
986
|
+
if (path11.resolve(projectPath) !== normalizedTarget) {
|
|
911
987
|
continue;
|
|
912
988
|
}
|
|
913
989
|
if (isRecord(projectState)) {
|
|
@@ -938,11 +1014,11 @@ function filterMcpJsonServersByProjectState(servers, projectState) {
|
|
|
938
1014
|
return Object.fromEntries(filteredEntries);
|
|
939
1015
|
}
|
|
940
1016
|
async function readJsonFile(filePath) {
|
|
941
|
-
if (!await
|
|
1017
|
+
if (!await fs9.pathExists(filePath)) {
|
|
942
1018
|
return void 0;
|
|
943
1019
|
}
|
|
944
1020
|
try {
|
|
945
|
-
const raw = await
|
|
1021
|
+
const raw = await fs9.readFile(filePath, "utf8");
|
|
946
1022
|
const parsed = JSON.parse(raw);
|
|
947
1023
|
if (!isRecord(parsed)) {
|
|
948
1024
|
return void 0;
|
|
@@ -1119,10 +1195,10 @@ function mergeCodexConfigToml(existingToml, renderedMcpBlocks) {
|
|
|
1119
1195
|
${mcp}`;
|
|
1120
1196
|
}
|
|
1121
1197
|
async function loadClaudeMcpServers(targetDir) {
|
|
1122
|
-
const homeDir =
|
|
1123
|
-
const normalizedTarget =
|
|
1198
|
+
const homeDir = path11.resolve(os.homedir());
|
|
1199
|
+
const normalizedTarget = path11.resolve(targetDir);
|
|
1124
1200
|
const isUserScope = normalizedTarget === homeDir;
|
|
1125
|
-
const claudeStatePath =
|
|
1201
|
+
const claudeStatePath = path11.join(homeDir, CLAUDE_STATE_FILE);
|
|
1126
1202
|
const claudeState = await readJsonFile(claudeStatePath);
|
|
1127
1203
|
if (isUserScope) {
|
|
1128
1204
|
return {
|
|
@@ -1132,7 +1208,7 @@ async function loadClaudeMcpServers(targetDir) {
|
|
|
1132
1208
|
}
|
|
1133
1209
|
const projectState = claudeState ? getProjectState(claudeState, normalizedTarget) : void 0;
|
|
1134
1210
|
const projectServers = normalizeClaudeMcpServers(projectState?.mcpServers);
|
|
1135
|
-
const localMcpPath =
|
|
1211
|
+
const localMcpPath = path11.join(normalizedTarget, CLAUDE_LOCAL_MCP_FILE);
|
|
1136
1212
|
const localMcpState = await readJsonFile(localMcpPath);
|
|
1137
1213
|
const localMcpServers = filterMcpJsonServersByProjectState(
|
|
1138
1214
|
normalizeClaudeMcpServers(localMcpState?.mcpServers),
|
|
@@ -1147,18 +1223,18 @@ async function loadClaudeMcpServers(targetDir) {
|
|
|
1147
1223
|
};
|
|
1148
1224
|
}
|
|
1149
1225
|
async function syncMcpServers(targetDir) {
|
|
1150
|
-
const homeDir =
|
|
1151
|
-
const normalizedTarget =
|
|
1226
|
+
const homeDir = path11.resolve(os.homedir());
|
|
1227
|
+
const normalizedTarget = path11.resolve(targetDir);
|
|
1152
1228
|
const isUserScope = normalizedTarget === homeDir;
|
|
1153
|
-
const codexBaseDir = isUserScope ?
|
|
1154
|
-
const codexMcpConfigPath =
|
|
1229
|
+
const codexBaseDir = isUserScope ? path11.join(homeDir, ".codex") : path11.join(normalizedTarget, ".codex");
|
|
1230
|
+
const codexMcpConfigPath = path11.join(codexBaseDir, CODEX_CONFIG_FILE);
|
|
1155
1231
|
const { scope, servers } = await loadClaudeMcpServers(normalizedTarget);
|
|
1156
1232
|
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
1233
|
const renderedMcpBlocks = codexServers.map(([name, server]) => renderCodexMcpServer(name, server)).join("\n\n");
|
|
1158
|
-
const existingConfig = await
|
|
1234
|
+
const existingConfig = await fs9.pathExists(codexMcpConfigPath) ? await fs9.readFile(codexMcpConfigPath, "utf8") : "";
|
|
1159
1235
|
const nextConfig = mergeCodexConfigToml(existingConfig, renderedMcpBlocks);
|
|
1160
|
-
await
|
|
1161
|
-
await
|
|
1236
|
+
await fs9.ensureDir(codexBaseDir);
|
|
1237
|
+
await fs9.writeFile(codexMcpConfigPath, `${nextConfig.trimEnd()}
|
|
1162
1238
|
`);
|
|
1163
1239
|
return {
|
|
1164
1240
|
codexMcpConfigPath,
|
|
@@ -1167,16 +1243,16 @@ async function syncMcpServers(targetDir) {
|
|
|
1167
1243
|
};
|
|
1168
1244
|
}
|
|
1169
1245
|
async function syncSkills(claudeSkillsDir, codexSkillsDir) {
|
|
1170
|
-
await
|
|
1246
|
+
await fs9.ensureDir(codexSkillsDir);
|
|
1171
1247
|
let sourceSkillNames = [];
|
|
1172
|
-
if (await
|
|
1173
|
-
const sourceEntries = await
|
|
1248
|
+
if (await fs9.pathExists(claudeSkillsDir)) {
|
|
1249
|
+
const sourceEntries = await fs9.readdir(claudeSkillsDir, {
|
|
1174
1250
|
withFileTypes: true
|
|
1175
1251
|
});
|
|
1176
1252
|
sourceSkillNames = sourceEntries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => name !== ".system");
|
|
1177
1253
|
}
|
|
1178
1254
|
const sourceSkillSet = new Set(sourceSkillNames);
|
|
1179
|
-
const codexEntries = await
|
|
1255
|
+
const codexEntries = await fs9.readdir(codexSkillsDir, {
|
|
1180
1256
|
withFileTypes: true
|
|
1181
1257
|
});
|
|
1182
1258
|
for (const entry of codexEntries) {
|
|
@@ -1187,40 +1263,40 @@ async function syncSkills(claudeSkillsDir, codexSkillsDir) {
|
|
|
1187
1263
|
continue;
|
|
1188
1264
|
}
|
|
1189
1265
|
if (!sourceSkillSet.has(entry.name)) {
|
|
1190
|
-
await
|
|
1266
|
+
await fs9.remove(path11.join(codexSkillsDir, entry.name));
|
|
1191
1267
|
}
|
|
1192
1268
|
}
|
|
1193
1269
|
for (const skillName of sourceSkillNames) {
|
|
1194
|
-
const src =
|
|
1195
|
-
const dest =
|
|
1196
|
-
if (await
|
|
1197
|
-
await
|
|
1270
|
+
const src = path11.join(claudeSkillsDir, skillName);
|
|
1271
|
+
const dest = path11.join(codexSkillsDir, skillName);
|
|
1272
|
+
if (await fs9.pathExists(dest)) {
|
|
1273
|
+
await fs9.remove(dest);
|
|
1198
1274
|
}
|
|
1199
1275
|
await copyRecursive(src, dest, { files: 0, directories: 0 });
|
|
1200
1276
|
}
|
|
1201
1277
|
return sourceSkillNames.length;
|
|
1202
1278
|
}
|
|
1203
1279
|
async function syncCommands(claudeCommandsDir, codexCommandsDir) {
|
|
1204
|
-
if (!await
|
|
1205
|
-
await
|
|
1280
|
+
if (!await fs9.pathExists(claudeCommandsDir)) {
|
|
1281
|
+
await fs9.remove(codexCommandsDir);
|
|
1206
1282
|
return 0;
|
|
1207
1283
|
}
|
|
1208
|
-
const entries = await
|
|
1284
|
+
const entries = await fs9.readdir(claudeCommandsDir, { withFileTypes: true });
|
|
1209
1285
|
const commandFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
|
|
1210
1286
|
if (commandFiles.length === 0) {
|
|
1211
|
-
await
|
|
1287
|
+
await fs9.remove(codexCommandsDir);
|
|
1212
1288
|
return 0;
|
|
1213
1289
|
}
|
|
1214
|
-
await
|
|
1215
|
-
await
|
|
1290
|
+
await fs9.remove(codexCommandsDir);
|
|
1291
|
+
await fs9.ensureDir(codexCommandsDir);
|
|
1216
1292
|
for (const commandFile of commandFiles) {
|
|
1217
|
-
const commandPath =
|
|
1218
|
-
const commandRaw = await
|
|
1219
|
-
const commandName =
|
|
1220
|
-
const skillDir =
|
|
1221
|
-
const skillPath =
|
|
1222
|
-
await
|
|
1223
|
-
await
|
|
1293
|
+
const commandPath = path11.join(claudeCommandsDir, commandFile);
|
|
1294
|
+
const commandRaw = await fs9.readFile(commandPath, "utf8");
|
|
1295
|
+
const commandName = path11.basename(commandFile, ".md");
|
|
1296
|
+
const skillDir = path11.join(codexCommandsDir, commandName);
|
|
1297
|
+
const skillPath = path11.join(skillDir, "SKILL.md");
|
|
1298
|
+
await fs9.ensureDir(skillDir);
|
|
1299
|
+
await fs9.writeFile(
|
|
1224
1300
|
skillPath,
|
|
1225
1301
|
buildCommandSkillContent(commandPath, commandRaw)
|
|
1226
1302
|
);
|
|
@@ -1228,12 +1304,12 @@ async function syncCommands(claudeCommandsDir, codexCommandsDir) {
|
|
|
1228
1304
|
return commandFiles.length;
|
|
1229
1305
|
}
|
|
1230
1306
|
async function syncInstructions(claudeInstructionsDir, codexInstructionsDir) {
|
|
1231
|
-
if (!await
|
|
1232
|
-
await
|
|
1307
|
+
if (!await fs9.pathExists(claudeInstructionsDir)) {
|
|
1308
|
+
await fs9.remove(codexInstructionsDir);
|
|
1233
1309
|
return 0;
|
|
1234
1310
|
}
|
|
1235
|
-
await
|
|
1236
|
-
await
|
|
1311
|
+
await fs9.remove(codexInstructionsDir);
|
|
1312
|
+
await fs9.ensureDir(codexInstructionsDir);
|
|
1237
1313
|
const counter = { files: 0, directories: 0 };
|
|
1238
1314
|
await copyRecursive(claudeInstructionsDir, codexInstructionsDir, counter);
|
|
1239
1315
|
return counter.files;
|
|
@@ -1254,12 +1330,12 @@ function extractReferenceCandidate(line) {
|
|
|
1254
1330
|
return candidate;
|
|
1255
1331
|
}
|
|
1256
1332
|
async function collectSkillMarkdownFiles(rootDir, collector = []) {
|
|
1257
|
-
if (!await
|
|
1333
|
+
if (!await fs9.pathExists(rootDir)) {
|
|
1258
1334
|
return collector;
|
|
1259
1335
|
}
|
|
1260
|
-
const entries = await
|
|
1336
|
+
const entries = await fs9.readdir(rootDir, { withFileTypes: true });
|
|
1261
1337
|
for (const entry of entries) {
|
|
1262
|
-
const fullPath =
|
|
1338
|
+
const fullPath = path11.join(rootDir, entry.name);
|
|
1263
1339
|
if (entry.isDirectory()) {
|
|
1264
1340
|
await collectSkillMarkdownFiles(fullPath, collector);
|
|
1265
1341
|
continue;
|
|
@@ -1275,7 +1351,7 @@ async function validateSkillReferences(codexSkillsDir) {
|
|
|
1275
1351
|
let count = 0;
|
|
1276
1352
|
const samples = [];
|
|
1277
1353
|
for (const skillFile of skillFiles) {
|
|
1278
|
-
const content = await
|
|
1354
|
+
const content = await fs9.readFile(skillFile, "utf8");
|
|
1279
1355
|
const lines = normalizeLineEndings(content).split("\n");
|
|
1280
1356
|
for (const line of lines) {
|
|
1281
1357
|
const candidate = extractReferenceCandidate(line);
|
|
@@ -1286,8 +1362,8 @@ async function validateSkillReferences(codexSkillsDir) {
|
|
|
1286
1362
|
if (!referencePath) {
|
|
1287
1363
|
continue;
|
|
1288
1364
|
}
|
|
1289
|
-
const resolvedPath =
|
|
1290
|
-
if (await
|
|
1365
|
+
const resolvedPath = path11.resolve(path11.dirname(skillFile), referencePath);
|
|
1366
|
+
if (await fs9.pathExists(resolvedPath)) {
|
|
1291
1367
|
continue;
|
|
1292
1368
|
}
|
|
1293
1369
|
count += 1;
|
|
@@ -1303,15 +1379,15 @@ async function validateSkillReferences(codexSkillsDir) {
|
|
|
1303
1379
|
return { count, samples };
|
|
1304
1380
|
}
|
|
1305
1381
|
async function syncWithCodex(targetDir) {
|
|
1306
|
-
const codexHome =
|
|
1307
|
-
const codexSkillsDir =
|
|
1308
|
-
const codexCommandsDir =
|
|
1309
|
-
const codexInstructionsDir =
|
|
1310
|
-
await
|
|
1311
|
-
const claudeRootDir =
|
|
1312
|
-
const claudeSkillsDir =
|
|
1313
|
-
const claudeCommandsDir =
|
|
1314
|
-
const claudeInstructionsDir =
|
|
1382
|
+
const codexHome = path11.resolve(targetDir, ".codex");
|
|
1383
|
+
const codexSkillsDir = path11.join(codexHome, "skills");
|
|
1384
|
+
const codexCommandsDir = path11.join(codexSkillsDir, "claude-commands");
|
|
1385
|
+
const codexInstructionsDir = path11.join(codexHome, "instructions");
|
|
1386
|
+
await fs9.ensureDir(codexSkillsDir);
|
|
1387
|
+
const claudeRootDir = path11.join(targetDir, ".claude");
|
|
1388
|
+
const claudeSkillsDir = path11.join(claudeRootDir, "skills");
|
|
1389
|
+
const claudeCommandsDir = path11.join(claudeRootDir, "commands");
|
|
1390
|
+
const claudeInstructionsDir = path11.join(claudeRootDir, "instructions");
|
|
1315
1391
|
const syncedSkills = await syncSkills(claudeSkillsDir, codexSkillsDir);
|
|
1316
1392
|
const syncedInstructions = await syncInstructions(
|
|
1317
1393
|
claudeInstructionsDir,
|
|
@@ -1352,7 +1428,7 @@ var TEMPLATE_DESCRIPTIONS = {
|
|
|
1352
1428
|
};
|
|
1353
1429
|
async function validateTargetDirectory(targetDir) {
|
|
1354
1430
|
try {
|
|
1355
|
-
const stat = await
|
|
1431
|
+
const stat = await fs10.stat(targetDir);
|
|
1356
1432
|
if (!stat.isDirectory()) {
|
|
1357
1433
|
logger.error(`Target is not a directory: ${targetDir}`);
|
|
1358
1434
|
process.exit(1);
|
|
@@ -1366,7 +1442,7 @@ async function validateTargetDirectory(targetDir) {
|
|
|
1366
1442
|
process.exit(1);
|
|
1367
1443
|
}
|
|
1368
1444
|
try {
|
|
1369
|
-
await
|
|
1445
|
+
await fs10.access(targetDir, fs10.constants.W_OK);
|
|
1370
1446
|
} catch {
|
|
1371
1447
|
logger.error(`No write permission for: ${targetDir}`);
|
|
1372
1448
|
process.exit(1);
|
|
@@ -1420,29 +1496,32 @@ async function installTemplates(templates, targetDir) {
|
|
|
1420
1496
|
logger.success(`Total: ${totalFiles} files, ${totalDirectories} directories`);
|
|
1421
1497
|
return { files: totalFiles, directories: totalDirectories };
|
|
1422
1498
|
}
|
|
1423
|
-
async function promptForExtrasInstallation(options, hasSkills, hasCommands, hasAgents, hasInstructions, hasScripts) {
|
|
1499
|
+
async function promptForExtrasInstallation(options, hasSkills, hasCommands, hasAgents, hasInstructions, hasScripts, hasHooks) {
|
|
1424
1500
|
return await promptExtrasSelection({
|
|
1425
1501
|
skills: options.skills,
|
|
1426
1502
|
commands: options.commands,
|
|
1427
1503
|
agents: options.agents,
|
|
1428
1504
|
instructions: options.instructions,
|
|
1429
1505
|
scripts: options.scripts,
|
|
1506
|
+
hooks: options.hooks,
|
|
1430
1507
|
hasSkills,
|
|
1431
1508
|
hasCommands,
|
|
1432
1509
|
hasAgents,
|
|
1433
1510
|
hasInstructions,
|
|
1434
|
-
hasScripts
|
|
1511
|
+
hasScripts,
|
|
1512
|
+
hasHooks
|
|
1435
1513
|
});
|
|
1436
1514
|
}
|
|
1437
|
-
function showInstallationSummary(templates, flags, hasSkills, hasCommands, hasAgents, hasInstructions, hasScripts, scope) {
|
|
1515
|
+
function showInstallationSummary(templates, flags, hasSkills, hasCommands, hasAgents, hasInstructions, hasScripts, hasHooks, scope) {
|
|
1438
1516
|
const {
|
|
1439
1517
|
installSkills,
|
|
1440
1518
|
installCommands,
|
|
1441
1519
|
installAgents,
|
|
1442
1520
|
installInstructions,
|
|
1443
|
-
installScripts
|
|
1521
|
+
installScripts,
|
|
1522
|
+
installHooks
|
|
1444
1523
|
} = flags;
|
|
1445
|
-
const hasExtrasInstalled = installSkills && hasSkills || installCommands && hasCommands || installAgents && hasAgents || installInstructions && hasInstructions || installScripts && hasScripts;
|
|
1524
|
+
const hasExtrasInstalled = installSkills && hasSkills || installCommands && hasCommands || installAgents && hasAgents || installInstructions && hasInstructions || installScripts && hasScripts || installHooks && hasHooks;
|
|
1446
1525
|
if (templates.length === 0 && !hasExtrasInstalled) {
|
|
1447
1526
|
logger.blank();
|
|
1448
1527
|
logger.info("No templates or extras installed.");
|
|
@@ -1475,6 +1554,9 @@ function showInstallationSummary(templates, flags, hasSkills, hasCommands, hasAg
|
|
|
1475
1554
|
if (installScripts && hasScripts) {
|
|
1476
1555
|
logger.step("Scripts \u2192 .claude/scripts/");
|
|
1477
1556
|
}
|
|
1557
|
+
if (installHooks && hasHooks) {
|
|
1558
|
+
logger.step("Hooks \u2192 .claude/hooks/");
|
|
1559
|
+
}
|
|
1478
1560
|
}
|
|
1479
1561
|
logger.blank();
|
|
1480
1562
|
logger.info("Next steps:");
|
|
@@ -1513,21 +1595,30 @@ var init = async (options) => {
|
|
|
1513
1595
|
}
|
|
1514
1596
|
}
|
|
1515
1597
|
const templatesToCheck = templates.length > 0 ? templates : availableTemplates;
|
|
1516
|
-
const {
|
|
1598
|
+
const {
|
|
1599
|
+
hasSkills,
|
|
1600
|
+
hasCommands,
|
|
1601
|
+
hasAgents,
|
|
1602
|
+
hasInstructions,
|
|
1603
|
+
hasScripts,
|
|
1604
|
+
hasHooks
|
|
1605
|
+
} = await checkAllExtrasExist(templatesToCheck);
|
|
1517
1606
|
const flags = await promptForExtrasInstallation(
|
|
1518
1607
|
options,
|
|
1519
1608
|
hasSkills,
|
|
1520
1609
|
hasCommands,
|
|
1521
1610
|
hasAgents,
|
|
1522
1611
|
hasInstructions,
|
|
1523
|
-
hasScripts
|
|
1612
|
+
hasScripts,
|
|
1613
|
+
hasHooks
|
|
1524
1614
|
);
|
|
1525
1615
|
await installExtras(templatesToCheck, targetDir, flags, {
|
|
1526
1616
|
hasSkills,
|
|
1527
1617
|
hasCommands,
|
|
1528
1618
|
hasAgents,
|
|
1529
1619
|
hasInstructions,
|
|
1530
|
-
hasScripts
|
|
1620
|
+
hasScripts,
|
|
1621
|
+
hasHooks
|
|
1531
1622
|
});
|
|
1532
1623
|
showInstallationSummary(
|
|
1533
1624
|
templates,
|
|
@@ -1537,9 +1628,10 @@ var init = async (options) => {
|
|
|
1537
1628
|
hasAgents,
|
|
1538
1629
|
hasInstructions,
|
|
1539
1630
|
hasScripts,
|
|
1631
|
+
hasHooks,
|
|
1540
1632
|
scope
|
|
1541
1633
|
);
|
|
1542
|
-
const codexSyncPath =
|
|
1634
|
+
const codexSyncPath = path12.join(targetDir, ".codex");
|
|
1543
1635
|
const { syncCodex } = await promptCodexSync({
|
|
1544
1636
|
providedSyncCodex: options.syncCodex,
|
|
1545
1637
|
codexPath: codexSyncPath
|
|
@@ -1575,8 +1667,8 @@ var init = async (options) => {
|
|
|
1575
1667
|
`Codex skill reference issues found: ${result.referenceIssueCount} (showing up to ${result.referenceIssueSamples.length})`
|
|
1576
1668
|
);
|
|
1577
1669
|
for (const issue of result.referenceIssueSamples) {
|
|
1578
|
-
const skillPath =
|
|
1579
|
-
const resolvedPath =
|
|
1670
|
+
const skillPath = path12.relative(targetDir, issue.skillPath);
|
|
1671
|
+
const resolvedPath = path12.relative(targetDir, issue.resolvedPath);
|
|
1580
1672
|
logger.step(
|
|
1581
1673
|
`${skillPath} -> @${issue.reference} (missing: ${resolvedPath})`
|
|
1582
1674
|
);
|
|
@@ -1602,7 +1694,7 @@ var init = async (options) => {
|
|
|
1602
1694
|
|
|
1603
1695
|
// src/index.ts
|
|
1604
1696
|
var program = new Command();
|
|
1605
|
-
program.name("claude-code").description("Claude Code documentation installer for projects").version("0.7.
|
|
1697
|
+
program.name("claude-code").description("Claude Code documentation installer for projects").version("0.7.12");
|
|
1606
1698
|
program.option(
|
|
1607
1699
|
"-t, --template <names>",
|
|
1608
1700
|
"template names (comma-separated: tanstack-start,hono)"
|