@promptscript/cli 1.5.0 → 1.6.0

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 (34) hide show
  1. package/index.js +911 -69
  2. package/package.json +1 -1
  3. package/src/cli.d.ts.map +1 -1
  4. package/src/commands/hook.d.ts +15 -0
  5. package/src/commands/hook.d.ts.map +1 -0
  6. package/src/commands/hooks.d.ts +12 -0
  7. package/src/commands/hooks.d.ts.map +1 -0
  8. package/src/commands/init.d.ts.map +1 -1
  9. package/src/hooks/debounce-lock.d.ts +4 -0
  10. package/src/hooks/debounce-lock.d.ts.map +1 -0
  11. package/src/hooks/detect-marker.d.ts +7 -0
  12. package/src/hooks/detect-marker.d.ts.map +1 -0
  13. package/src/hooks/extract-file-path.d.ts +12 -0
  14. package/src/hooks/extract-file-path.d.ts.map +1 -0
  15. package/src/hooks/tool-configs/claude.d.ts +8 -0
  16. package/src/hooks/tool-configs/claude.d.ts.map +1 -0
  17. package/src/hooks/tool-configs/cline.d.ts +3 -0
  18. package/src/hooks/tool-configs/cline.d.ts.map +1 -0
  19. package/src/hooks/tool-configs/copilot.d.ts +3 -0
  20. package/src/hooks/tool-configs/copilot.d.ts.map +1 -0
  21. package/src/hooks/tool-configs/cursor.d.ts +3 -0
  22. package/src/hooks/tool-configs/cursor.d.ts.map +1 -0
  23. package/src/hooks/tool-configs/factory.d.ts +3 -0
  24. package/src/hooks/tool-configs/factory.d.ts.map +1 -0
  25. package/src/hooks/tool-configs/gemini.d.ts +3 -0
  26. package/src/hooks/tool-configs/gemini.d.ts.map +1 -0
  27. package/src/hooks/tool-configs/index.d.ts +12 -0
  28. package/src/hooks/tool-configs/index.d.ts.map +1 -0
  29. package/src/hooks/tool-configs/types.d.ts +11 -0
  30. package/src/hooks/tool-configs/types.d.ts.map +1 -0
  31. package/src/hooks/tool-configs/windsurf.d.ts +3 -0
  32. package/src/hooks/tool-configs/windsurf.d.ts.map +1 -0
  33. package/src/types.d.ts +13 -0
  34. package/src/types.d.ts.map +1 -1
package/index.js CHANGED
@@ -19761,20 +19761,20 @@ var init_factory = __esm({
19761
19761
  return commands;
19762
19762
  }
19763
19763
  generateCommandFile(config) {
19764
- const factoryConfig = config;
19764
+ const factoryConfig2 = config;
19765
19765
  const lines = [];
19766
19766
  lines.push("---");
19767
- lines.push(`description: ${this.yamlString(factoryConfig.description)}`);
19768
- if (factoryConfig.agent) {
19769
- lines.push(`agent: ${factoryConfig.agent}`);
19767
+ lines.push(`description: ${this.yamlString(factoryConfig2.description)}`);
19768
+ if (factoryConfig2.agent) {
19769
+ lines.push(`agent: ${factoryConfig2.agent}`);
19770
19770
  }
19771
- if (factoryConfig.tools && factoryConfig.tools.length > 0) {
19772
- const toolsArray = factoryConfig.tools.map((t) => `'${t}'`).join(", ");
19771
+ if (factoryConfig2.tools && factoryConfig2.tools.length > 0) {
19772
+ const toolsArray = factoryConfig2.tools.map((t) => `'${t}'`).join(", ");
19773
19773
  lines.push(`tools: [${toolsArray}]`);
19774
19774
  }
19775
- if (factoryConfig.handoffs && factoryConfig.handoffs.length > 0) {
19775
+ if (factoryConfig2.handoffs && factoryConfig2.handoffs.length > 0) {
19776
19776
  lines.push("handoffs: ");
19777
- for (const handoff of factoryConfig.handoffs) {
19777
+ for (const handoff of factoryConfig2.handoffs) {
19778
19778
  lines.push(` - label: ${this.yamlString(handoff.label)}`);
19779
19779
  lines.push(` agent: ${handoff.agent}`);
19780
19780
  if (handoff.prompt) {
@@ -19787,12 +19787,12 @@ var init_factory = __esm({
19787
19787
  }
19788
19788
  lines.push("---");
19789
19789
  lines.push("");
19790
- if (factoryConfig.content) {
19791
- const dedentedContent = this.dedent(factoryConfig.content);
19790
+ if (factoryConfig2.content) {
19791
+ const dedentedContent = this.dedent(factoryConfig2.content);
19792
19792
  lines.push(dedentedContent);
19793
19793
  }
19794
19794
  return {
19795
- path: `.factory/commands/${factoryConfig.name}.md`,
19795
+ path: `.factory/commands/${factoryConfig2.name}.md`,
19796
19796
  content: lines.join("\n") + "\n"
19797
19797
  };
19798
19798
  }
@@ -19825,30 +19825,30 @@ var init_factory = __esm({
19825
19825
  return skills;
19826
19826
  }
19827
19827
  generateSkillFile(config) {
19828
- const factoryConfig = config;
19828
+ const factoryConfig2 = config;
19829
19829
  const lines = [];
19830
- const skillName = factoryConfig.name.replace(/\./g, "-");
19830
+ const skillName = factoryConfig2.name.replace(/\./g, "-");
19831
19831
  lines.push("---");
19832
19832
  lines.push(`name: ${skillName}`);
19833
- lines.push(`description: ${this.yamlString(factoryConfig.description)}`);
19834
- if (factoryConfig.userInvocable === false) {
19833
+ lines.push(`description: ${this.yamlString(factoryConfig2.description)}`);
19834
+ if (factoryConfig2.userInvocable === false) {
19835
19835
  lines.push("user-invocable: false");
19836
19836
  }
19837
- if (factoryConfig.disableModelInvocation === true) {
19837
+ if (factoryConfig2.disableModelInvocation === true) {
19838
19838
  lines.push("disable-model-invocation: true");
19839
19839
  }
19840
- if (factoryConfig.allowedTools && factoryConfig.allowedTools.length > 0) {
19841
- const toolsArray = factoryConfig.allowedTools.map((t) => `"${t}"`).join(", ");
19840
+ if (factoryConfig2.allowedTools && factoryConfig2.allowedTools.length > 0) {
19841
+ const toolsArray = factoryConfig2.allowedTools.map((t) => `"${t}"`).join(", ");
19842
19842
  lines.push(`allowed-tools: [${toolsArray}]`);
19843
19843
  }
19844
19844
  lines.push("---");
19845
19845
  lines.push("");
19846
- if (factoryConfig.content) {
19847
- const dedentedContent = this.dedent(factoryConfig.content);
19846
+ if (factoryConfig2.content) {
19847
+ const dedentedContent = this.dedent(factoryConfig2.content);
19848
19848
  lines.push(dedentedContent);
19849
19849
  }
19850
- const skillDirPath = `.factory/skills/${factoryConfig.name}`;
19851
- const resourceFiles = this.sanitizeResourceFiles(factoryConfig.resources, skillDirPath);
19850
+ const skillDirPath = `.factory/skills/${factoryConfig2.name}`;
19851
+ const resourceFiles = this.sanitizeResourceFiles(factoryConfig2.resources, skillDirPath);
19852
19852
  return {
19853
19853
  path: `${skillDirPath}/SKILL.md`,
19854
19854
  content: lines.join("\n") + "\n",
@@ -21991,6 +21991,9 @@ async function initCommand(options, services = createDefaultServices()) {
21991
21991
  }
21992
21992
  }
21993
21993
  }
21994
+ ConsoleOutput.muted(
21995
+ "\n Tip: Run prs hooks install to set up auto-compilation hooks for your AI tools.\n"
21996
+ );
21994
21997
  } catch (error) {
21995
21998
  if (error instanceof Error && error.name === "ExitPromptError") {
21996
21999
  ConsoleOutput.newline();
@@ -23622,9 +23625,9 @@ var require_rate_limit = __commonJS({
23622
23625
  let ttl = 0;
23623
23626
  let ttlInSeconds = 0;
23624
23627
  try {
23625
- const res = await new Promise((resolve17, reject2) => {
23628
+ const res = await new Promise((resolve18, reject2) => {
23626
23629
  store.incr(key, (err, res2) => {
23627
- err ? reject2(err) : resolve17(res2);
23630
+ err ? reject2(err) : resolve18(res2);
23628
23631
  }, timeWindow, max);
23629
23632
  });
23630
23633
  current = res.current;
@@ -23730,13 +23733,13 @@ var init_health = __esm({
23730
23733
  });
23731
23734
 
23732
23735
  // packages/server/src/source-dirs.ts
23733
- import { readFile as readFile18 } from "fs/promises";
23734
- import { join as join19, dirname as dirname10 } from "path";
23736
+ import { readFile as readFile20 } from "fs/promises";
23737
+ import { join as join20, dirname as dirname11 } from "path";
23735
23738
  import { parse as parseYaml12 } from "yaml";
23736
23739
  async function readProjectConfig(workspace) {
23737
23740
  for (const filename of CONFIG_FILENAMES) {
23738
23741
  try {
23739
- const raw = await readFile18(join19(workspace, filename), "utf-8");
23742
+ const raw = await readFile20(join20(workspace, filename), "utf-8");
23740
23743
  const config = parseYaml12(raw);
23741
23744
  if (config) return config;
23742
23745
  } catch {
@@ -23747,10 +23750,10 @@ async function readProjectConfig(workspace) {
23747
23750
  async function resolveSourceDir(workspace) {
23748
23751
  for (const filename of CONFIG_FILENAMES) {
23749
23752
  try {
23750
- const raw = await readFile18(join19(workspace, filename), "utf-8");
23753
+ const raw = await readFile20(join20(workspace, filename), "utf-8");
23751
23754
  const config = parseYaml12(raw);
23752
23755
  if (config?.input?.entry) {
23753
- return dirname10(config.input.entry);
23756
+ return dirname11(config.input.entry);
23754
23757
  }
23755
23758
  } catch {
23756
23759
  }
@@ -23764,9 +23767,9 @@ async function resolveFileGlobs(workspace) {
23764
23767
  async function resolveWatchPaths(workspace) {
23765
23768
  const sourceDir = await resolveSourceDir(workspace);
23766
23769
  return [
23767
- join19(workspace, sourceDir),
23768
- join19(workspace, "promptscript.yaml"),
23769
- join19(workspace, "promptscript.yml")
23770
+ join20(workspace, sourceDir),
23771
+ join20(workspace, "promptscript.yaml"),
23772
+ join20(workspace, "promptscript.yml")
23770
23773
  ];
23771
23774
  }
23772
23775
  var DEFAULT_SOURCE_DIR, CONFIG_FILENAMES;
@@ -23793,13 +23796,13 @@ var init_config2 = __esm({
23793
23796
  });
23794
23797
 
23795
23798
  // packages/server/src/path-guard.ts
23796
- import { resolve as resolve16, relative as relative2, isAbsolute as isAbsolute4 } from "path";
23799
+ import { resolve as resolve17, relative as relative2, isAbsolute as isAbsolute4 } from "path";
23797
23800
  function resolveSafePath(workspace, requestedPath) {
23798
23801
  const decoded = decodeURIComponent(requestedPath);
23799
23802
  if (isAbsolute4(decoded)) {
23800
23803
  throw new PathTraversalError(requestedPath);
23801
23804
  }
23802
- const resolved = resolve16(workspace, decoded);
23805
+ const resolved = resolve17(workspace, decoded);
23803
23806
  const rel = relative2(workspace, resolved);
23804
23807
  if (rel.startsWith("..") || isAbsolute4(rel)) {
23805
23808
  throw new PathTraversalError(requestedPath);
@@ -23820,8 +23823,8 @@ var init_path_guard = __esm({
23820
23823
  });
23821
23824
 
23822
23825
  // packages/server/src/routes/files.ts
23823
- import { readFile as readFile19, writeFile as writeFile10, unlink, stat as stat2, mkdir as mkdir8 } from "fs/promises";
23824
- import { dirname as dirname11 } from "path";
23826
+ import { readFile as readFile21, writeFile as writeFile11, unlink as unlink2, stat as stat2, mkdir as mkdir9 } from "fs/promises";
23827
+ import { dirname as dirname12 } from "path";
23825
23828
  import fg from "fast-glob";
23826
23829
  function registerRoutes(app, workspace, readOnly) {
23827
23830
  app.get("/api/files", async () => {
@@ -23841,7 +23844,7 @@ function registerRoutes(app, workspace, readOnly) {
23841
23844
  const filePath = request.params["*"];
23842
23845
  try {
23843
23846
  const resolved = resolveSafePath(workspace, filePath);
23844
- const content = await readFile19(resolved, "utf-8");
23847
+ const content = await readFile21(resolved, "utf-8");
23845
23848
  return { path: filePath, content };
23846
23849
  } catch (err) {
23847
23850
  if (err instanceof PathTraversalError) {
@@ -23858,7 +23861,7 @@ function registerRoutes(app, workspace, readOnly) {
23858
23861
  const filePath = request.params["*"];
23859
23862
  try {
23860
23863
  const resolved = resolveSafePath(workspace, filePath);
23861
- await writeFile10(resolved, request.body.content, "utf-8");
23864
+ await writeFile11(resolved, request.body.content, "utf-8");
23862
23865
  return { path: filePath, status: "updated" };
23863
23866
  } catch (err) {
23864
23867
  if (err instanceof PathTraversalError) {
@@ -23881,8 +23884,8 @@ function registerRoutes(app, workspace, readOnly) {
23881
23884
  return reply.status(409).send({ error: "File already exists" });
23882
23885
  } catch {
23883
23886
  }
23884
- await mkdir8(dirname11(resolved), { recursive: true });
23885
- await writeFile10(resolved, request.body.content, "utf-8");
23887
+ await mkdir9(dirname12(resolved), { recursive: true });
23888
+ await writeFile11(resolved, request.body.content, "utf-8");
23886
23889
  return reply.status(201).send({ path: filePath, status: "created" });
23887
23890
  } catch (err) {
23888
23891
  if (err instanceof PathTraversalError) {
@@ -23897,7 +23900,7 @@ function registerRoutes(app, workspace, readOnly) {
23897
23900
  const filePath = request.params["*"];
23898
23901
  try {
23899
23902
  const resolved = resolveSafePath(workspace, filePath);
23900
- await unlink(resolved);
23903
+ await unlink2(resolved);
23901
23904
  return { path: filePath, status: "deleted" };
23902
23905
  } catch (err) {
23903
23906
  if (err instanceof PathTraversalError) {
@@ -24107,7 +24110,7 @@ init_src();
24107
24110
  init_init();
24108
24111
  import { Command } from "commander";
24109
24112
  import { fileURLToPath as fileURLToPath4 } from "url";
24110
- import { dirname as dirname12 } from "path";
24113
+ import { dirname as dirname13 } from "path";
24111
24114
 
24112
24115
  // packages/cli/src/commands/compile.ts
24113
24116
  init_src();
@@ -27460,7 +27463,7 @@ var HttpRegistry = class {
27460
27463
  * Sleep for specified milliseconds.
27461
27464
  */
27462
27465
  sleep(ms) {
27463
- return new Promise((resolve17) => setTimeout(resolve17, ms));
27466
+ return new Promise((resolve18) => setTimeout(resolve18, ms));
27464
27467
  }
27465
27468
  /**
27466
27469
  * Fetch with timeout.
@@ -29938,15 +29941,15 @@ var SECURITY_STRICT_MULTILINGUAL = {
29938
29941
  };
29939
29942
 
29940
29943
  // packages/compiler/src/compiler.ts
29941
- function generateHtmlMarker() {
29944
+ function generateHtmlMarker(source, target) {
29942
29945
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
29943
- return `<!-- PromptScript ${timestamp} - do not edit -->`;
29946
+ return `<!-- PromptScript ${timestamp} | source: ${source} | target: ${target} - do not edit -->`;
29944
29947
  }
29945
- function generateYamlMarker() {
29948
+ function generateYamlMarker(source, target) {
29946
29949
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
29947
- return `# promptscript-generated: ${timestamp}`;
29950
+ return `# promptscript-generated: ${timestamp} | source: ${source} | target: ${target}`;
29948
29951
  }
29949
- function addMarkerToOutput(output) {
29952
+ function addMarkerToOutput(output, source, target) {
29950
29953
  const { content, path } = output;
29951
29954
  if (content.includes("<!-- PromptScript") || content.includes("# promptscript-generated:")) {
29952
29955
  return output;
@@ -29959,11 +29962,11 @@ function addMarkerToOutput(output) {
29959
29962
  if (closingIndex !== -1) {
29960
29963
  const afterOpening = content.slice(3);
29961
29964
  const newContent = `---
29962
- ${generateYamlMarker()}${afterOpening}`;
29965
+ ${generateYamlMarker(source, target)}${afterOpening}`;
29963
29966
  return { ...output, content: newContent };
29964
29967
  }
29965
29968
  }
29966
- const marker = generateHtmlMarker();
29969
+ const marker = generateHtmlMarker(source, target);
29967
29970
  const lines = content.split("\n");
29968
29971
  if (lines[0]?.startsWith("# ")) {
29969
29972
  lines.splice(1, 0, "", marker);
@@ -30077,7 +30080,7 @@ var Compiler = class _Compiler {
30077
30080
  });
30078
30081
  }
30079
30082
  outputPathOwners.set(output.path, formatter.name);
30080
- outputs.set(output.path, addMarkerToOutput(output));
30083
+ outputs.set(output.path, addMarkerToOutput(output, entryPath, formatter.name));
30081
30084
  if (output.additionalFiles) {
30082
30085
  const queue = [...output.additionalFiles];
30083
30086
  while (queue.length > 0) {
@@ -30098,7 +30101,10 @@ var Compiler = class _Compiler {
30098
30101
  continue;
30099
30102
  }
30100
30103
  outputPathOwners.set(additionalFile.path, formatter.name);
30101
- outputs.set(additionalFile.path, addMarkerToOutput(additionalFile));
30104
+ outputs.set(
30105
+ additionalFile.path,
30106
+ addMarkerToOutput(additionalFile, entryPath, formatter.name)
30107
+ );
30102
30108
  if (additionalFile.additionalFiles) {
30103
30109
  queue.push(...additionalFile.additionalFiles);
30104
30110
  }
@@ -30123,13 +30129,19 @@ var Compiler = class _Compiler {
30123
30129
  const skillPath = `${skillBasePath}/promptscript/${skillFileName}`;
30124
30130
  const existingOwner = outputPathOwners.get(skillPath);
30125
30131
  if (existingOwner) {
30126
- formatWarnings.push({
30127
- ruleId: "PS4001",
30128
- ruleName: "output-path-collision",
30129
- severity: "warning",
30130
- message: `Output path '${skillPath}' is already written by '${existingOwner}'. Skipping auto-injected PromptScript skill for '${formatter.name}'.`,
30131
- suggestion: `The user-defined skill takes precedence. To use the bundled skill, remove the custom one or rename it.`
30132
- });
30132
+ if (existingOwner !== formatter.name) {
30133
+ formatWarnings.push({
30134
+ ruleId: "PS4001",
30135
+ ruleName: "output-path-collision",
30136
+ severity: "warning",
30137
+ message: `Output path '${skillPath}' is already written by '${existingOwner}'. Skipping auto-injected PromptScript skill for '${formatter.name}'.`,
30138
+ suggestion: `The user-defined skill takes precedence. To use the bundled skill, remove the custom one or rename it.`
30139
+ });
30140
+ } else {
30141
+ this.logger.debug(
30142
+ ` Skill path '${skillPath}' already output by '${formatter.name}' (auto-discovered). Skipping auto-injection.`
30143
+ );
30144
+ }
30133
30145
  continue;
30134
30146
  }
30135
30147
  outputPathOwners.set(skillPath, `${formatter.name}:promptscript-skill`);
@@ -30137,7 +30149,7 @@ var Compiler = class _Compiler {
30137
30149
  path: skillPath,
30138
30150
  content: this.options.skillContent
30139
30151
  };
30140
- outputs.set(skillPath, addMarkerToOutput(skillOutput));
30152
+ outputs.set(skillPath, addMarkerToOutput(skillOutput, entryPath, "promptscript"));
30141
30153
  this.logger.verbose(` \u2192 ${skillPath} (auto-injected promptscript skill)`);
30142
30154
  }
30143
30155
  }
@@ -30216,12 +30228,12 @@ var Compiler = class _Compiler {
30216
30228
  */
30217
30229
  async watch(entryPath, options = {}) {
30218
30230
  const { default: chokidar2 } = await import("chokidar");
30219
- const { dirname: dirname13, resolve: resolve17 } = await import("path");
30220
- const baseDir = dirname13(resolve17(entryPath));
30231
+ const { dirname: dirname14, resolve: resolve18 } = await import("path");
30232
+ const baseDir = dirname14(resolve18(entryPath));
30221
30233
  const includePatterns = options.include ?? ["**/*.prs"];
30222
30234
  const excludePatterns = options.exclude ?? ["**/node_modules/**"];
30223
30235
  const debounceMs = options.debounce ?? 300;
30224
- const watchPatterns = includePatterns.map((p) => resolve17(baseDir, p));
30236
+ const watchPatterns = includePatterns.map((p) => resolve18(baseDir, p));
30225
30237
  let debounceTimer = null;
30226
30238
  let pendingChanges = [];
30227
30239
  const handleChange = async (changedFiles) => {
@@ -30395,7 +30407,7 @@ var Pager = class {
30395
30407
  }
30396
30408
  return;
30397
30409
  }
30398
- return new Promise((resolve17) => {
30410
+ return new Promise((resolve18) => {
30399
30411
  const pagerCmd = getPagerCommand();
30400
30412
  const [cmd, ...args] = pagerCmd.split(" ");
30401
30413
  if (cmd === "less" && !args.includes("-R")) {
@@ -30403,7 +30415,7 @@ var Pager = class {
30403
30415
  }
30404
30416
  if (!cmd) {
30405
30417
  console.log(content);
30406
- resolve17();
30418
+ resolve18();
30407
30419
  return;
30408
30420
  }
30409
30421
  const pager = spawn(cmd, args, {
@@ -30416,17 +30428,17 @@ var Pager = class {
30416
30428
  });
30417
30429
  pager.on("error", (_err) => {
30418
30430
  console.log(content);
30419
- resolve17();
30431
+ resolve18();
30420
30432
  });
30421
30433
  pager.on("close", () => {
30422
- resolve17();
30434
+ resolve18();
30423
30435
  });
30424
30436
  if (pager.stdin) {
30425
30437
  pager.stdin.write(content);
30426
30438
  pager.stdin.end();
30427
30439
  } else {
30428
30440
  console.log(content);
30429
- resolve17();
30441
+ resolve18();
30430
30442
  }
30431
30443
  });
30432
30444
  }
@@ -33126,9 +33138,837 @@ async function upgradeCommand(options) {
33126
33138
  ${upgradedCount} file(s) ${verb}, ${skippedCount} skipped.`);
33127
33139
  }
33128
33140
 
33141
+ // packages/cli/src/hooks/extract-file-path.ts
33142
+ function extractFilePath(input2) {
33143
+ if (typeof input2 !== "object" || input2 === null) return null;
33144
+ const obj = input2;
33145
+ const toolInput = obj["tool_input"];
33146
+ if (typeof toolInput === "object" && toolInput !== null) {
33147
+ const filePath = toolInput["file_path"];
33148
+ if (typeof filePath === "string") return filePath;
33149
+ }
33150
+ const toolName = obj["tool_name"];
33151
+ if (typeof toolName === "string" && /^write_|^edit_/.test(toolName)) {
33152
+ const args = obj["arguments"];
33153
+ if (typeof args === "object" && args !== null) {
33154
+ const filePath = args["file_path"];
33155
+ if (typeof filePath === "string") return filePath;
33156
+ }
33157
+ }
33158
+ if (typeof obj["filePath"] === "string") return obj["filePath"];
33159
+ if (typeof obj["file_path"] === "string" && !toolInput) return obj["file_path"];
33160
+ if (typeof obj["file"] === "string") return obj["file"];
33161
+ return null;
33162
+ }
33163
+
33164
+ // packages/cli/src/hooks/detect-marker.ts
33165
+ import { readFile as readFile18 } from "node:fs/promises";
33166
+ var MARKER_PATTERNS = [
33167
+ "<!-- PromptScript",
33168
+ "# promptscript-generated:",
33169
+ "> Auto-generated by PromptScript"
33170
+ ];
33171
+ var SOURCE_TARGET_RE = /\|\s*source:\s*(\S+)\s*\|\s*target:\s*(\S+)/;
33172
+ async function detectMarker(filePath) {
33173
+ let content;
33174
+ try {
33175
+ content = await readFile18(filePath, "utf-8");
33176
+ } catch {
33177
+ return { isGenerated: false, source: null, target: null };
33178
+ }
33179
+ const lines = content.split("\n").slice(0, 50);
33180
+ for (const line of lines) {
33181
+ if (MARKER_PATTERNS.some((pattern) => line.includes(pattern))) {
33182
+ const match = SOURCE_TARGET_RE.exec(line);
33183
+ return {
33184
+ isGenerated: true,
33185
+ source: match?.[1] ?? null,
33186
+ target: match?.[2] ?? null
33187
+ };
33188
+ }
33189
+ }
33190
+ return { isGenerated: false, source: null, target: null };
33191
+ }
33192
+
33193
+ // packages/cli/src/hooks/debounce-lock.ts
33194
+ import { createHash as createHash2 } from "node:crypto";
33195
+ import { existsSync as existsSync21, writeFileSync as writeFileSync4, unlinkSync, statSync } from "node:fs";
33196
+ import { tmpdir } from "node:os";
33197
+ import { join as join19 } from "node:path";
33198
+ var STALE_THRESHOLD_MS = 3e4;
33199
+ function getLockPath(projectDir) {
33200
+ const hash = createHash2("md5").update(projectDir).digest("hex").slice(0, 12);
33201
+ return join19(tmpdir(), `prs-compile-${hash}.lock`);
33202
+ }
33203
+ function acquireLock(projectDir) {
33204
+ const lockPath = getLockPath(projectDir);
33205
+ if (existsSync21(lockPath)) {
33206
+ try {
33207
+ const stat3 = statSync(lockPath);
33208
+ const age = Date.now() - stat3.mtimeMs;
33209
+ if (age < STALE_THRESHOLD_MS) return false;
33210
+ unlinkSync(lockPath);
33211
+ } catch {
33212
+ }
33213
+ }
33214
+ try {
33215
+ writeFileSync4(lockPath, String(process.pid), { flag: "wx" });
33216
+ return true;
33217
+ } catch {
33218
+ return false;
33219
+ }
33220
+ }
33221
+ function releaseLock(projectDir) {
33222
+ try {
33223
+ unlinkSync(getLockPath(projectDir));
33224
+ } catch {
33225
+ }
33226
+ }
33227
+
33228
+ // packages/cli/src/commands/hook.ts
33229
+ async function readStdin() {
33230
+ const chunks = [];
33231
+ for await (const chunk of process.stdin) {
33232
+ chunks.push(chunk);
33233
+ }
33234
+ return Buffer.concat(chunks).toString("utf-8");
33235
+ }
33236
+ function parseInput(raw) {
33237
+ if (raw.trim() === "") return null;
33238
+ try {
33239
+ const parsed = JSON.parse(raw);
33240
+ if (typeof parsed !== "object" || parsed === null) return null;
33241
+ return parsed;
33242
+ } catch {
33243
+ process.exitCode = 1;
33244
+ process.stderr.write("prs hook: malformed JSON on stdin\n");
33245
+ return null;
33246
+ }
33247
+ }
33248
+ async function handlePreEdit(payload) {
33249
+ const filePath = extractFilePath(payload);
33250
+ if (!filePath) return;
33251
+ const marker = await detectMarker(filePath);
33252
+ if (!marker.isGenerated) return;
33253
+ process.exitCode = 2;
33254
+ if (marker.source) {
33255
+ process.stderr.write(
33256
+ `prs hook: ${filePath} is generated by PromptScript from ${marker.source} \u2014 edit the source instead
33257
+ `
33258
+ );
33259
+ } else {
33260
+ process.stderr.write(
33261
+ `prs hook: ${filePath} is generated by PromptScript \u2014 run prs compile after editing the source
33262
+ `
33263
+ );
33264
+ }
33265
+ }
33266
+ async function handlePostEdit(payload) {
33267
+ const filePath = extractFilePath(payload);
33268
+ if (!filePath) return;
33269
+ if (!filePath.endsWith(".prs")) return;
33270
+ const cwd = typeof payload["cwd"] === "string" ? payload["cwd"] : process.cwd();
33271
+ if (!acquireLock(cwd)) return;
33272
+ try {
33273
+ await compileCommand({ cwd });
33274
+ } catch (error) {
33275
+ const message = error instanceof Error ? error.message : String(error);
33276
+ process.stderr.write(`prs hook: compile error: ${message}
33277
+ `);
33278
+ } finally {
33279
+ releaseLock(cwd);
33280
+ }
33281
+ }
33282
+ async function hookCommand(action, internalOpts) {
33283
+ const raw = internalOpts?.stdin !== void 0 ? internalOpts.stdin : await readStdin();
33284
+ const payload = parseInput(raw);
33285
+ if (!payload) return;
33286
+ switch (action) {
33287
+ case "pre-edit":
33288
+ await handlePreEdit(payload);
33289
+ break;
33290
+ case "post-edit":
33291
+ await handlePostEdit(payload);
33292
+ break;
33293
+ default:
33294
+ process.exitCode = 1;
33295
+ process.stderr.write(`prs hook: unknown action "${action}"
33296
+ `);
33297
+ }
33298
+ }
33299
+
33300
+ // packages/cli/src/commands/hooks.ts
33301
+ init_console();
33302
+ import { existsSync as existsSync22 } from "node:fs";
33303
+ import { readFile as readFile19, writeFile as writeFile10, mkdir as mkdir8, chmod, unlink } from "node:fs/promises";
33304
+ import { dirname as dirname10, resolve as resolve16 } from "node:path";
33305
+
33306
+ // packages/cli/src/hooks/tool-configs/claude.ts
33307
+ function isPrsHookEntry(entry) {
33308
+ if (typeof entry !== "object" || entry === null) {
33309
+ return false;
33310
+ }
33311
+ const obj = entry;
33312
+ const hooks = obj["hooks"];
33313
+ if (!Array.isArray(hooks)) {
33314
+ return false;
33315
+ }
33316
+ return hooks.some((h) => {
33317
+ if (typeof h !== "object" || h === null) return false;
33318
+ const hook = h;
33319
+ return typeof hook["command"] === "string" && hook["command"].includes("prs hook");
33320
+ });
33321
+ }
33322
+ function getHooksSection(existing) {
33323
+ const hooks = existing["hooks"];
33324
+ if (typeof hooks === "object" && hooks !== null && !Array.isArray(hooks)) {
33325
+ return hooks;
33326
+ }
33327
+ return {};
33328
+ }
33329
+ function getHookArray(hooksSection, key) {
33330
+ const arr = hooksSection[key];
33331
+ return Array.isArray(arr) ? arr : [];
33332
+ }
33333
+ var claudeConfig = {
33334
+ name: "claude",
33335
+ detectPaths: [".claude"],
33336
+ settingsPath: ".claude/settings.json",
33337
+ timeoutUnit: "seconds",
33338
+ generatePreEditHook(prsPath) {
33339
+ return {
33340
+ matcher: "Edit|Write",
33341
+ hooks: [
33342
+ {
33343
+ type: "command",
33344
+ command: `${prsPath} hook pre-edit`,
33345
+ timeout: 5,
33346
+ statusMessage: "PromptScript: checking generated files..."
33347
+ }
33348
+ ]
33349
+ };
33350
+ },
33351
+ generatePostEditHook(prsPath) {
33352
+ return {
33353
+ matcher: "Edit|Write",
33354
+ hooks: [
33355
+ {
33356
+ type: "command",
33357
+ command: `${prsPath} hook post-edit`,
33358
+ timeout: 15,
33359
+ statusMessage: "PromptScript: compiling..."
33360
+ }
33361
+ ]
33362
+ };
33363
+ },
33364
+ mergeIntoSettings(existing, prsPath) {
33365
+ const hooksSection = getHooksSection(existing);
33366
+ const preToolUse = getHookArray(hooksSection, "PreToolUse");
33367
+ const postToolUse = getHookArray(hooksSection, "PostToolUse");
33368
+ const preAlreadyInstalled = preToolUse.some(isPrsHookEntry);
33369
+ const postAlreadyInstalled = postToolUse.some(isPrsHookEntry);
33370
+ const newPreToolUse = preAlreadyInstalled ? preToolUse : [...preToolUse, this.generatePreEditHook(prsPath)];
33371
+ const newPostToolUse = postAlreadyInstalled ? postToolUse : [...postToolUse, this.generatePostEditHook(prsPath)];
33372
+ return {
33373
+ ...existing,
33374
+ hooks: {
33375
+ ...hooksSection,
33376
+ PreToolUse: newPreToolUse,
33377
+ PostToolUse: newPostToolUse
33378
+ }
33379
+ };
33380
+ },
33381
+ removeFromSettings(existing) {
33382
+ const hooksSection = getHooksSection(existing);
33383
+ const preToolUse = getHookArray(hooksSection, "PreToolUse");
33384
+ const postToolUse = getHookArray(hooksSection, "PostToolUse");
33385
+ return {
33386
+ ...existing,
33387
+ hooks: {
33388
+ ...hooksSection,
33389
+ PreToolUse: preToolUse.filter((e) => !isPrsHookEntry(e)),
33390
+ PostToolUse: postToolUse.filter((e) => !isPrsHookEntry(e))
33391
+ }
33392
+ };
33393
+ }
33394
+ };
33395
+
33396
+ // packages/cli/src/hooks/tool-configs/factory.ts
33397
+ function getHooksSection2(existing) {
33398
+ const hooks = existing["hooks"];
33399
+ if (typeof hooks === "object" && hooks !== null && !Array.isArray(hooks)) {
33400
+ return hooks;
33401
+ }
33402
+ return {};
33403
+ }
33404
+ function getHookArray2(hooksSection, key) {
33405
+ const arr = hooksSection[key];
33406
+ return Array.isArray(arr) ? arr : [];
33407
+ }
33408
+ var factoryConfig = {
33409
+ name: "factory",
33410
+ detectPaths: [".factory"],
33411
+ settingsPath: ".factory/settings.json",
33412
+ timeoutUnit: "seconds",
33413
+ generatePreEditHook(prsPath) {
33414
+ return {
33415
+ matcher: "Create|Edit",
33416
+ hooks: [
33417
+ {
33418
+ type: "command",
33419
+ command: `${prsPath} hook pre-edit`,
33420
+ timeout: 5,
33421
+ statusMessage: "PromptScript: checking generated files..."
33422
+ }
33423
+ ]
33424
+ };
33425
+ },
33426
+ generatePostEditHook(prsPath) {
33427
+ return {
33428
+ matcher: "Create|Edit",
33429
+ hooks: [
33430
+ {
33431
+ type: "command",
33432
+ command: `${prsPath} hook post-edit`,
33433
+ timeout: 15,
33434
+ statusMessage: "PromptScript: compiling..."
33435
+ }
33436
+ ]
33437
+ };
33438
+ },
33439
+ mergeIntoSettings(existing, prsPath) {
33440
+ const hooksSection = getHooksSection2(existing);
33441
+ const preToolUse = getHookArray2(hooksSection, "PreToolUse");
33442
+ const postToolUse = getHookArray2(hooksSection, "PostToolUse");
33443
+ const newPreToolUse = preToolUse.some(isPrsHookEntry) ? preToolUse : [...preToolUse, this.generatePreEditHook(prsPath)];
33444
+ const newPostToolUse = postToolUse.some(isPrsHookEntry) ? postToolUse : [...postToolUse, this.generatePostEditHook(prsPath)];
33445
+ return {
33446
+ ...existing,
33447
+ hooks: {
33448
+ ...hooksSection,
33449
+ PreToolUse: newPreToolUse,
33450
+ PostToolUse: newPostToolUse
33451
+ }
33452
+ };
33453
+ },
33454
+ removeFromSettings(existing) {
33455
+ const hooksSection = getHooksSection2(existing);
33456
+ const preToolUse = getHookArray2(hooksSection, "PreToolUse");
33457
+ const postToolUse = getHookArray2(hooksSection, "PostToolUse");
33458
+ return {
33459
+ ...existing,
33460
+ hooks: {
33461
+ ...hooksSection,
33462
+ PreToolUse: preToolUse.filter((e) => !isPrsHookEntry(e)),
33463
+ PostToolUse: postToolUse.filter((e) => !isPrsHookEntry(e))
33464
+ }
33465
+ };
33466
+ }
33467
+ };
33468
+
33469
+ // packages/cli/src/hooks/tool-configs/cursor.ts
33470
+ function getHooksSection3(existing) {
33471
+ const hooks = existing["hooks"];
33472
+ if (typeof hooks === "object" && hooks !== null && !Array.isArray(hooks)) {
33473
+ return hooks;
33474
+ }
33475
+ return {};
33476
+ }
33477
+ function getHookArray3(hooksSection, key) {
33478
+ const arr = hooksSection[key];
33479
+ return Array.isArray(arr) ? arr : [];
33480
+ }
33481
+ function isPrsCursorHookEntry(entry) {
33482
+ if (typeof entry !== "object" || entry === null) {
33483
+ return false;
33484
+ }
33485
+ const obj = entry;
33486
+ const command = obj["command"];
33487
+ if (!Array.isArray(command)) {
33488
+ return false;
33489
+ }
33490
+ return command.some((part) => typeof part === "string" && part.includes("prs hook"));
33491
+ }
33492
+ var cursorConfig = {
33493
+ name: "cursor",
33494
+ detectPaths: [".cursor"],
33495
+ settingsPath: ".cursor/hooks.json",
33496
+ timeoutUnit: "milliseconds",
33497
+ generatePreEditHook(prsPath) {
33498
+ return {
33499
+ hooks: {
33500
+ beforeFileEdit: [
33501
+ {
33502
+ command: ["bash", "-c", `${prsPath} hook pre-edit`],
33503
+ timeout_ms: 5e3
33504
+ }
33505
+ ]
33506
+ }
33507
+ };
33508
+ },
33509
+ generatePostEditHook(prsPath) {
33510
+ return {
33511
+ hooks: {
33512
+ afterFileEdit: [
33513
+ {
33514
+ command: ["bash", "-c", `${prsPath} hook post-edit`],
33515
+ timeout_ms: 15e3
33516
+ }
33517
+ ]
33518
+ }
33519
+ };
33520
+ },
33521
+ mergeIntoSettings(existing, prsPath) {
33522
+ const hooksSection = getHooksSection3(existing);
33523
+ const beforeFileEdit = getHookArray3(hooksSection, "beforeFileEdit");
33524
+ const afterFileEdit = getHookArray3(hooksSection, "afterFileEdit");
33525
+ const newBeforeFileEdit = beforeFileEdit.some(isPrsCursorHookEntry) ? beforeFileEdit : [
33526
+ ...beforeFileEdit,
33527
+ {
33528
+ command: ["bash", "-c", `${prsPath} hook pre-edit`],
33529
+ timeout_ms: 5e3
33530
+ }
33531
+ ];
33532
+ const newAfterFileEdit = afterFileEdit.some(isPrsCursorHookEntry) ? afterFileEdit : [
33533
+ ...afterFileEdit,
33534
+ {
33535
+ command: ["bash", "-c", `${prsPath} hook post-edit`],
33536
+ timeout_ms: 15e3
33537
+ }
33538
+ ];
33539
+ return {
33540
+ ...existing,
33541
+ hooks: {
33542
+ ...hooksSection,
33543
+ beforeFileEdit: newBeforeFileEdit,
33544
+ afterFileEdit: newAfterFileEdit
33545
+ }
33546
+ };
33547
+ },
33548
+ removeFromSettings(existing) {
33549
+ const hooksSection = getHooksSection3(existing);
33550
+ const beforeFileEdit = getHookArray3(hooksSection, "beforeFileEdit");
33551
+ const afterFileEdit = getHookArray3(hooksSection, "afterFileEdit");
33552
+ return {
33553
+ ...existing,
33554
+ hooks: {
33555
+ ...hooksSection,
33556
+ beforeFileEdit: beforeFileEdit.filter((e) => !isPrsCursorHookEntry(e)),
33557
+ afterFileEdit: afterFileEdit.filter((e) => !isPrsCursorHookEntry(e))
33558
+ }
33559
+ };
33560
+ }
33561
+ };
33562
+
33563
+ // packages/cli/src/hooks/tool-configs/windsurf.ts
33564
+ function getHooksSection4(existing) {
33565
+ const hooks = existing["hooks"];
33566
+ if (typeof hooks === "object" && hooks !== null && !Array.isArray(hooks)) {
33567
+ return hooks;
33568
+ }
33569
+ return {};
33570
+ }
33571
+ function getHookArray4(hooksSection, key) {
33572
+ const arr = hooksSection[key];
33573
+ return Array.isArray(arr) ? arr : [];
33574
+ }
33575
+ var windsurfConfig = {
33576
+ name: "windsurf",
33577
+ detectPaths: [".windsurf"],
33578
+ settingsPath: ".windsurf/hooks.json",
33579
+ timeoutUnit: "milliseconds",
33580
+ generatePreEditHook(prsPath) {
33581
+ return {
33582
+ matcher: "Edit|Write",
33583
+ hooks: [
33584
+ {
33585
+ type: "command",
33586
+ command: `${prsPath} hook pre-edit`,
33587
+ timeout: 5e3,
33588
+ statusMessage: "PromptScript: checking generated files..."
33589
+ }
33590
+ ]
33591
+ };
33592
+ },
33593
+ generatePostEditHook(prsPath) {
33594
+ return {
33595
+ matcher: "Edit|Write",
33596
+ hooks: [
33597
+ {
33598
+ type: "command",
33599
+ command: `${prsPath} hook post-edit`,
33600
+ timeout: 15e3,
33601
+ statusMessage: "PromptScript: compiling..."
33602
+ }
33603
+ ]
33604
+ };
33605
+ },
33606
+ mergeIntoSettings(existing, prsPath) {
33607
+ const hooksSection = getHooksSection4(existing);
33608
+ const preWriteCode = getHookArray4(hooksSection, "pre_write_code");
33609
+ const postWriteCode = getHookArray4(hooksSection, "post_write_code");
33610
+ const newPreWriteCode = preWriteCode.some(isPrsHookEntry) ? preWriteCode : [...preWriteCode, this.generatePreEditHook(prsPath)];
33611
+ const newPostWriteCode = postWriteCode.some(isPrsHookEntry) ? postWriteCode : [...postWriteCode, this.generatePostEditHook(prsPath)];
33612
+ return {
33613
+ ...existing,
33614
+ hooks: {
33615
+ ...hooksSection,
33616
+ pre_write_code: newPreWriteCode,
33617
+ post_write_code: newPostWriteCode
33618
+ }
33619
+ };
33620
+ },
33621
+ removeFromSettings(existing) {
33622
+ const hooksSection = getHooksSection4(existing);
33623
+ const preWriteCode = getHookArray4(hooksSection, "pre_write_code");
33624
+ const postWriteCode = getHookArray4(hooksSection, "post_write_code");
33625
+ return {
33626
+ ...existing,
33627
+ hooks: {
33628
+ ...hooksSection,
33629
+ pre_write_code: preWriteCode.filter((e) => !isPrsHookEntry(e)),
33630
+ post_write_code: postWriteCode.filter((e) => !isPrsHookEntry(e))
33631
+ }
33632
+ };
33633
+ }
33634
+ };
33635
+
33636
+ // packages/cli/src/hooks/tool-configs/cline.ts
33637
+ var clineConfig = {
33638
+ name: "cline",
33639
+ detectPaths: [".clinerules"],
33640
+ settingsPath: ".clinerules/hooks/",
33641
+ timeoutUnit: "n/a",
33642
+ generatePreEditHook(prsPath) {
33643
+ return {
33644
+ scriptPath: ".clinerules/hooks/prs-pre-edit.sh",
33645
+ content: `#!/bin/bash
33646
+ ${prsPath} hook pre-edit`
33647
+ };
33648
+ },
33649
+ generatePostEditHook(prsPath) {
33650
+ return {
33651
+ scriptPath: ".clinerules/hooks/prs-post-edit.sh",
33652
+ content: `#!/bin/bash
33653
+ ${prsPath} hook post-edit`
33654
+ };
33655
+ },
33656
+ mergeIntoSettings(existing, _prsPath) {
33657
+ return existing;
33658
+ },
33659
+ removeFromSettings(existing) {
33660
+ return existing;
33661
+ }
33662
+ };
33663
+
33664
+ // packages/cli/src/hooks/tool-configs/copilot.ts
33665
+ function getHooksSection5(existing) {
33666
+ const hooks = existing["hooks"];
33667
+ if (typeof hooks === "object" && hooks !== null && !Array.isArray(hooks)) {
33668
+ return hooks;
33669
+ }
33670
+ return {};
33671
+ }
33672
+ function getHookArray5(hooksSection, key) {
33673
+ const arr = hooksSection[key];
33674
+ return Array.isArray(arr) ? arr : [];
33675
+ }
33676
+ var copilotConfig = {
33677
+ name: "copilot",
33678
+ detectPaths: [".vscode", ".github/copilot-instructions.md"],
33679
+ settingsPath: ".vscode/hooks.json",
33680
+ timeoutUnit: "seconds",
33681
+ generatePreEditHook(prsPath) {
33682
+ return {
33683
+ matcher: "Edit|Write",
33684
+ hooks: [
33685
+ {
33686
+ type: "command",
33687
+ command: `${prsPath} hook pre-edit`,
33688
+ timeout: 5,
33689
+ statusMessage: "PromptScript: checking generated files..."
33690
+ }
33691
+ ]
33692
+ };
33693
+ },
33694
+ generatePostEditHook(prsPath) {
33695
+ return {
33696
+ matcher: "Edit|Write",
33697
+ hooks: [
33698
+ {
33699
+ type: "command",
33700
+ command: `${prsPath} hook post-edit`,
33701
+ timeout: 15,
33702
+ statusMessage: "PromptScript: compiling..."
33703
+ }
33704
+ ]
33705
+ };
33706
+ },
33707
+ mergeIntoSettings(existing, prsPath) {
33708
+ const hooksSection = getHooksSection5(existing);
33709
+ const preToolUse = getHookArray5(hooksSection, "PreToolUse");
33710
+ const postToolUse = getHookArray5(hooksSection, "PostToolUse");
33711
+ const newPreToolUse = preToolUse.some(isPrsHookEntry) ? preToolUse : [...preToolUse, this.generatePreEditHook(prsPath)];
33712
+ const newPostToolUse = postToolUse.some(isPrsHookEntry) ? postToolUse : [...postToolUse, this.generatePostEditHook(prsPath)];
33713
+ return {
33714
+ ...existing,
33715
+ hooks: {
33716
+ ...hooksSection,
33717
+ PreToolUse: newPreToolUse,
33718
+ PostToolUse: newPostToolUse
33719
+ }
33720
+ };
33721
+ },
33722
+ removeFromSettings(existing) {
33723
+ const hooksSection = getHooksSection5(existing);
33724
+ const preToolUse = getHookArray5(hooksSection, "PreToolUse");
33725
+ const postToolUse = getHookArray5(hooksSection, "PostToolUse");
33726
+ return {
33727
+ ...existing,
33728
+ hooks: {
33729
+ ...hooksSection,
33730
+ PreToolUse: preToolUse.filter((e) => !isPrsHookEntry(e)),
33731
+ PostToolUse: postToolUse.filter((e) => !isPrsHookEntry(e))
33732
+ }
33733
+ };
33734
+ }
33735
+ };
33736
+
33737
+ // packages/cli/src/hooks/tool-configs/gemini.ts
33738
+ function getHooksSection6(existing) {
33739
+ const hooks = existing["hooks"];
33740
+ if (typeof hooks === "object" && hooks !== null && !Array.isArray(hooks)) {
33741
+ return hooks;
33742
+ }
33743
+ return {};
33744
+ }
33745
+ function getHookArray6(hooksSection, key) {
33746
+ const arr = hooksSection[key];
33747
+ return Array.isArray(arr) ? arr : [];
33748
+ }
33749
+ function isPrsGeminiHookEntry(entry) {
33750
+ if (typeof entry !== "object" || entry === null) {
33751
+ return false;
33752
+ }
33753
+ const obj = entry;
33754
+ const hooks = obj["hooks"];
33755
+ if (!Array.isArray(hooks)) {
33756
+ return false;
33757
+ }
33758
+ return hooks.some((h) => {
33759
+ if (typeof h !== "object" || h === null) return false;
33760
+ const hook = h;
33761
+ return typeof hook["command"] === "string" && hook["command"].includes("prs hook");
33762
+ });
33763
+ }
33764
+ var geminiConfig = {
33765
+ name: "gemini",
33766
+ detectPaths: [".gemini"],
33767
+ settingsPath: ".gemini/settings.json",
33768
+ timeoutUnit: "milliseconds",
33769
+ generatePreEditHook(prsPath) {
33770
+ return {
33771
+ matcher: "write_.*|edit_.*",
33772
+ hooks: [
33773
+ {
33774
+ type: "command",
33775
+ command: `${prsPath} hook pre-edit`,
33776
+ timeout: 5e3
33777
+ }
33778
+ ]
33779
+ };
33780
+ },
33781
+ generatePostEditHook(prsPath) {
33782
+ return {
33783
+ matcher: "write_.*|edit_.*",
33784
+ hooks: [
33785
+ {
33786
+ type: "command",
33787
+ command: `${prsPath} hook post-edit`,
33788
+ timeout: 15e3
33789
+ }
33790
+ ]
33791
+ };
33792
+ },
33793
+ mergeIntoSettings(existing, prsPath) {
33794
+ const hooksSection = getHooksSection6(existing);
33795
+ const beforeTool = getHookArray6(hooksSection, "BeforeTool");
33796
+ const afterTool = getHookArray6(hooksSection, "AfterTool");
33797
+ const newBeforeTool = beforeTool.some(isPrsGeminiHookEntry) ? beforeTool : [...beforeTool, this.generatePreEditHook(prsPath)];
33798
+ const newAfterTool = afterTool.some(isPrsGeminiHookEntry) ? afterTool : [...afterTool, this.generatePostEditHook(prsPath)];
33799
+ return {
33800
+ ...existing,
33801
+ hooks: {
33802
+ ...hooksSection,
33803
+ BeforeTool: newBeforeTool,
33804
+ AfterTool: newAfterTool
33805
+ }
33806
+ };
33807
+ },
33808
+ removeFromSettings(existing) {
33809
+ const hooksSection = getHooksSection6(existing);
33810
+ const beforeTool = getHookArray6(hooksSection, "BeforeTool");
33811
+ const afterTool = getHookArray6(hooksSection, "AfterTool");
33812
+ return {
33813
+ ...existing,
33814
+ hooks: {
33815
+ ...hooksSection,
33816
+ BeforeTool: beforeTool.filter((e) => !isPrsGeminiHookEntry(e)),
33817
+ AfterTool: afterTool.filter((e) => !isPrsGeminiHookEntry(e))
33818
+ }
33819
+ };
33820
+ }
33821
+ };
33822
+
33823
+ // packages/cli/src/hooks/tool-configs/index.ts
33824
+ var ALL_TOOL_CONFIGS = [
33825
+ claudeConfig,
33826
+ factoryConfig,
33827
+ cursorConfig,
33828
+ windsurfConfig,
33829
+ clineConfig,
33830
+ copilotConfig,
33831
+ geminiConfig
33832
+ ];
33833
+ function getToolConfig(name) {
33834
+ return ALL_TOOL_CONFIGS.find((c) => c.name === name);
33835
+ }
33836
+
33837
+ // packages/cli/src/commands/hooks.ts
33838
+ function resolvePrsPath() {
33839
+ const scriptPath = process.argv[1];
33840
+ if (scriptPath && !scriptPath.includes("node_modules/.bin") && !scriptPath.includes("pnpm")) {
33841
+ return scriptPath;
33842
+ }
33843
+ if (scriptPath) {
33844
+ ConsoleOutput.warning(
33845
+ `Detected ephemeral script path (${scriptPath}), using bare "prs" instead`
33846
+ );
33847
+ }
33848
+ return "prs";
33849
+ }
33850
+ function detectTools() {
33851
+ const cwd = process.cwd();
33852
+ return ALL_TOOL_CONFIGS.filter(
33853
+ (config) => config.detectPaths.some((p) => existsSync22(resolve16(cwd, p)))
33854
+ );
33855
+ }
33856
+ async function readSettingsFile(path) {
33857
+ try {
33858
+ const content = await readFile19(path, "utf-8");
33859
+ return JSON.parse(content);
33860
+ } catch {
33861
+ return {};
33862
+ }
33863
+ }
33864
+ async function writeSettingsFile(path, data) {
33865
+ await mkdir8(dirname10(path), { recursive: true });
33866
+ await writeFile10(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
33867
+ }
33868
+ function settingsUnchanged(original, merged) {
33869
+ return JSON.stringify(original) === JSON.stringify(merged);
33870
+ }
33871
+ async function installForTool(config, prsPath) {
33872
+ const cwd = process.cwd();
33873
+ const settingsPath = resolve16(cwd, config.settingsPath);
33874
+ if (config.name === "cline") {
33875
+ await installCline(config, prsPath, cwd);
33876
+ return;
33877
+ }
33878
+ const existing = await readSettingsFile(settingsPath);
33879
+ const merged = config.mergeIntoSettings(existing, prsPath);
33880
+ if (settingsUnchanged(existing, merged)) {
33881
+ ConsoleOutput.info(`${config.name}: hooks already installed`);
33882
+ return;
33883
+ }
33884
+ await writeSettingsFile(settingsPath, merged);
33885
+ ConsoleOutput.success(`${config.name}: hooks installed (${config.settingsPath})`);
33886
+ }
33887
+ async function installCline(config, prsPath, cwd) {
33888
+ const preHook = config.generatePreEditHook(prsPath);
33889
+ const postHook = config.generatePostEditHook(prsPath);
33890
+ for (const hook of [preHook, postHook]) {
33891
+ const fullPath = resolve16(cwd, hook.scriptPath);
33892
+ await mkdir8(dirname10(fullPath), { recursive: true });
33893
+ await writeFile10(fullPath, hook.content, "utf-8");
33894
+ await chmod(fullPath, 493);
33895
+ }
33896
+ ConsoleOutput.success(`${config.name}: hooks installed`);
33897
+ }
33898
+ async function uninstallForTool(config) {
33899
+ const cwd = process.cwd();
33900
+ const settingsPath = resolve16(cwd, config.settingsPath);
33901
+ if (config.name === "cline") {
33902
+ await uninstallCline(config, cwd);
33903
+ return;
33904
+ }
33905
+ let existing;
33906
+ try {
33907
+ const content = await readFile19(settingsPath, "utf-8");
33908
+ existing = JSON.parse(content);
33909
+ } catch {
33910
+ ConsoleOutput.info(`${config.name}: hooks not installed (no settings file)`);
33911
+ return;
33912
+ }
33913
+ const cleaned = config.removeFromSettings(existing);
33914
+ await writeSettingsFile(settingsPath, cleaned);
33915
+ ConsoleOutput.success(`${config.name}: hooks uninstalled`);
33916
+ }
33917
+ async function uninstallCline(config, cwd) {
33918
+ const prsPath = "prs";
33919
+ const preHook = config.generatePreEditHook(prsPath);
33920
+ const postHook = config.generatePostEditHook(prsPath);
33921
+ for (const hook of [preHook, postHook]) {
33922
+ const fullPath = resolve16(cwd, hook.scriptPath);
33923
+ if (existsSync22(fullPath)) {
33924
+ await unlink(fullPath);
33925
+ }
33926
+ }
33927
+ ConsoleOutput.success(`${config.name}: hooks uninstalled`);
33928
+ }
33929
+ function resolveTools(tool, _options) {
33930
+ if (tool) {
33931
+ const config = getToolConfig(tool);
33932
+ if (!config) {
33933
+ ConsoleOutput.error(
33934
+ `Unknown tool "${tool}". Available: ${ALL_TOOL_CONFIGS.map((c) => c.name).join(", ")}`
33935
+ );
33936
+ process.exitCode = 1;
33937
+ return null;
33938
+ }
33939
+ return [config];
33940
+ }
33941
+ const detected = detectTools();
33942
+ if (detected.length === 0) {
33943
+ ConsoleOutput.error(
33944
+ "No AI tools detected. Specify a tool name or create the tool config directory first."
33945
+ );
33946
+ process.exitCode = 1;
33947
+ return null;
33948
+ }
33949
+ return detected;
33950
+ }
33951
+ async function hooksCommand(action, tool, options = {}) {
33952
+ if (action !== "install" && action !== "uninstall") {
33953
+ ConsoleOutput.error(`Unknown action "${action}". Use "install" or "uninstall".`);
33954
+ process.exitCode = 1;
33955
+ return;
33956
+ }
33957
+ const tools = resolveTools(tool, options);
33958
+ if (!tools) return;
33959
+ const prsPath = resolvePrsPath();
33960
+ for (const config of tools) {
33961
+ if (action === "install") {
33962
+ await installForTool(config, prsPath);
33963
+ } else {
33964
+ await uninstallForTool(config);
33965
+ }
33966
+ }
33967
+ }
33968
+
33129
33969
  // packages/cli/src/cli.ts
33130
33970
  var __filename4 = fileURLToPath4(import.meta.url);
33131
- var __dirname4 = dirname12(__filename4);
33971
+ var __dirname4 = dirname13(__filename4);
33132
33972
  var program = new Command();
33133
33973
  program.name("prs").description("PromptScript CLI - Standardize AI instructions").version(getPackageVersion(__dirname3, "./package.json")).option("--verbose", "Enable verbose output").option("--debug", "Enable debug output (includes verbose)").option("--quiet", "Suppress non-error output").hook("preAction", async (thisCommand, actionCommand) => {
33134
33974
  const opts = thisCommand.opts();
@@ -33182,6 +34022,8 @@ var vendor = program.command("vendor").description("Manage vendored dependencies
33182
34022
  vendor.command("sync").description("Copy all cached dependencies to .promptscript/vendor/").option("--dry-run", "Preview without copying files").action((opts) => vendorSyncCommand(opts));
33183
34023
  vendor.command("check").description("Verify vendor directory matches lockfile").action((opts) => vendorCheckCommand(opts));
33184
34024
  program.command("resolve <import>").description("Show full resolution chain for an import path (debug)").action((importPath, opts) => resolveCommand(importPath, opts));
34025
+ program.command("hook").argument("<action>", "Hook action: pre-edit or post-edit").description("Hook handler invoked by AI tools (not for direct use)").action(hookCommand);
34026
+ program.command("hooks").argument("<action>", "Action: install or uninstall").argument("[tool]", "Tool: claude, gemini, copilot, cursor, windsurf, factory, cline").option("--all", "Install/uninstall for all detected tools").description("Manage PromptScript hooks for AI tools").action(hooksCommand);
33185
34027
  var registry = program.command("registry").description("Manage PromptScript registries");
33186
34028
  registerRegistryCommands(registry);
33187
34029
  function run(args = process.argv) {