@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 CHANGED
@@ -22,9 +22,9 @@ var banner = () => {
22
22
  };
23
23
 
24
24
  // src/commands/init.ts
25
- import fs9 from "fs-extra";
25
+ import fs10 from "fs-extra";
26
26
  import os2 from "os";
27
- import path11 from "path";
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
- return { hasSkills, hasCommands, hasAgents, hasInstructions, hasScripts };
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 totalFiles = skillsResult.files + commandsResult.files + agentsResult.files + instructionsResult.files + scriptsResult.files;
538
- const totalDirectories = skillsResult.directories + commandsResult.directories + agentsResult.directories + instructionsResult.directories + scriptsResult.directories;
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 noOptionsProvided = skills === void 0 && commands === void 0 && agents === void 0 && instructions === void 0 && scripts === void 0;
635
- if (noOptionsProvided && (hasSkills || hasCommands || hasAgents || hasInstructions || hasScripts)) {
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 fs7 from "fs-extra";
711
- import path9 from "path";
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 = path9.join(targetDir, ".gitignore");
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 fs7.readFile(gitignorePath, "utf-8");
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 fs7.writeFile(gitignorePath, newContent, "utf-8");
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 fs8 from "fs-extra";
864
+ import fs9 from "fs-extra";
789
865
  import os from "os";
790
- import path10 from "path";
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 = path10.basename(commandPath, ".md");
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 = path10.resolve(commandPath);
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 = path10.resolve(targetDir);
984
+ const normalizedTarget = path11.resolve(targetDir);
909
985
  for (const [projectPath, projectState] of Object.entries(projects)) {
910
- if (path10.resolve(projectPath) !== normalizedTarget) {
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 fs8.pathExists(filePath)) {
1017
+ if (!await fs9.pathExists(filePath)) {
942
1018
  return void 0;
943
1019
  }
944
1020
  try {
945
- const raw = await fs8.readFile(filePath, "utf8");
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 = path10.resolve(os.homedir());
1123
- const normalizedTarget = path10.resolve(targetDir);
1198
+ const homeDir = path11.resolve(os.homedir());
1199
+ const normalizedTarget = path11.resolve(targetDir);
1124
1200
  const isUserScope = normalizedTarget === homeDir;
1125
- const claudeStatePath = path10.join(homeDir, CLAUDE_STATE_FILE);
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 = path10.join(normalizedTarget, CLAUDE_LOCAL_MCP_FILE);
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 = path10.resolve(os.homedir());
1151
- const normalizedTarget = path10.resolve(targetDir);
1226
+ const homeDir = path11.resolve(os.homedir());
1227
+ const normalizedTarget = path11.resolve(targetDir);
1152
1228
  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);
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 fs8.pathExists(codexMcpConfigPath) ? await fs8.readFile(codexMcpConfigPath, "utf8") : "";
1234
+ const existingConfig = await fs9.pathExists(codexMcpConfigPath) ? await fs9.readFile(codexMcpConfigPath, "utf8") : "";
1159
1235
  const nextConfig = mergeCodexConfigToml(existingConfig, renderedMcpBlocks);
1160
- await fs8.ensureDir(codexBaseDir);
1161
- await fs8.writeFile(codexMcpConfigPath, `${nextConfig.trimEnd()}
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 fs8.ensureDir(codexSkillsDir);
1246
+ await fs9.ensureDir(codexSkillsDir);
1171
1247
  let sourceSkillNames = [];
1172
- if (await fs8.pathExists(claudeSkillsDir)) {
1173
- const sourceEntries = await fs8.readdir(claudeSkillsDir, {
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 fs8.readdir(codexSkillsDir, {
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 fs8.remove(path10.join(codexSkillsDir, entry.name));
1266
+ await fs9.remove(path11.join(codexSkillsDir, entry.name));
1191
1267
  }
1192
1268
  }
1193
1269
  for (const skillName of sourceSkillNames) {
1194
- const src = path10.join(claudeSkillsDir, skillName);
1195
- const dest = path10.join(codexSkillsDir, skillName);
1196
- if (await fs8.pathExists(dest)) {
1197
- await fs8.remove(dest);
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 fs8.pathExists(claudeCommandsDir)) {
1205
- await fs8.remove(codexCommandsDir);
1280
+ if (!await fs9.pathExists(claudeCommandsDir)) {
1281
+ await fs9.remove(codexCommandsDir);
1206
1282
  return 0;
1207
1283
  }
1208
- const entries = await fs8.readdir(claudeCommandsDir, { withFileTypes: true });
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 fs8.remove(codexCommandsDir);
1287
+ await fs9.remove(codexCommandsDir);
1212
1288
  return 0;
1213
1289
  }
1214
- await fs8.remove(codexCommandsDir);
1215
- await fs8.ensureDir(codexCommandsDir);
1290
+ await fs9.remove(codexCommandsDir);
1291
+ await fs9.ensureDir(codexCommandsDir);
1216
1292
  for (const commandFile of commandFiles) {
1217
- const commandPath = path10.join(claudeCommandsDir, commandFile);
1218
- const commandRaw = await fs8.readFile(commandPath, "utf8");
1219
- const commandName = path10.basename(commandFile, ".md");
1220
- const skillDir = path10.join(codexCommandsDir, commandName);
1221
- const skillPath = path10.join(skillDir, "SKILL.md");
1222
- await fs8.ensureDir(skillDir);
1223
- await fs8.writeFile(
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 fs8.pathExists(claudeInstructionsDir)) {
1232
- await fs8.remove(codexInstructionsDir);
1307
+ if (!await fs9.pathExists(claudeInstructionsDir)) {
1308
+ await fs9.remove(codexInstructionsDir);
1233
1309
  return 0;
1234
1310
  }
1235
- await fs8.remove(codexInstructionsDir);
1236
- await fs8.ensureDir(codexInstructionsDir);
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 fs8.pathExists(rootDir)) {
1333
+ if (!await fs9.pathExists(rootDir)) {
1258
1334
  return collector;
1259
1335
  }
1260
- const entries = await fs8.readdir(rootDir, { withFileTypes: true });
1336
+ const entries = await fs9.readdir(rootDir, { withFileTypes: true });
1261
1337
  for (const entry of entries) {
1262
- const fullPath = path10.join(rootDir, entry.name);
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 fs8.readFile(skillFile, "utf8");
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 = path10.resolve(path10.dirname(skillFile), referencePath);
1290
- if (await fs8.pathExists(resolvedPath)) {
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 = path10.resolve(targetDir, ".codex");
1307
- const codexSkillsDir = path10.join(codexHome, "skills");
1308
- const codexCommandsDir = path10.join(codexSkillsDir, "claude-commands");
1309
- const codexInstructionsDir = path10.join(codexHome, "instructions");
1310
- await fs8.ensureDir(codexSkillsDir);
1311
- const claudeRootDir = path10.join(targetDir, ".claude");
1312
- const claudeSkillsDir = path10.join(claudeRootDir, "skills");
1313
- const claudeCommandsDir = path10.join(claudeRootDir, "commands");
1314
- const claudeInstructionsDir = path10.join(claudeRootDir, "instructions");
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 fs9.stat(targetDir);
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 fs9.access(targetDir, fs9.constants.W_OK);
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 { hasSkills, hasCommands, hasAgents, hasInstructions, hasScripts } = await checkAllExtrasExist(templatesToCheck);
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 = path11.join(targetDir, ".codex");
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 = path11.relative(targetDir, issue.skillPath);
1579
- const resolvedPath = path11.relative(targetDir, issue.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.10");
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)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kood/claude-code",
3
- "version": "0.7.10",
3
+ "version": "0.7.12",
4
4
  "description": "Claude Code documentation installer for projects",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.js",