@promptscript/cli 1.0.0-alpha.0 → 1.0.0-alpha.2

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/index.js CHANGED
@@ -617,6 +617,11 @@ var AI_TOOL_PATTERNS = [
617
617
  target: "cursor",
618
618
  files: [".cursor/rules/project.mdc", ".cursorrules", ".cursor/rules.md"],
619
619
  directories: [".cursor", ".cursor/rules"]
620
+ },
621
+ {
622
+ target: "antigravity",
623
+ files: [".agent/rules/project.md"],
624
+ directories: [".agent", ".agent/rules"]
620
625
  }
621
626
  ];
622
627
  async function directoryHasContent(dir, services) {
@@ -633,7 +638,8 @@ async function detectAITools(services = createDefaultServices()) {
633
638
  const details = {
634
639
  github: [],
635
640
  claude: [],
636
- cursor: []
641
+ cursor: [],
642
+ antigravity: []
637
643
  };
638
644
  for (const pattern of AI_TOOL_PATTERNS) {
639
645
  const foundFiles = [];
@@ -655,7 +661,7 @@ async function detectAITools(services = createDefaultServices()) {
655
661
  return { detected, details };
656
662
  }
657
663
  function getAllTargets() {
658
- return ["github", "claude", "cursor"];
664
+ return ["github", "claude", "cursor", "antigravity"];
659
665
  }
660
666
  function getSuggestedTargets(detection) {
661
667
  return detection.detected.length > 0 ? detection.detected : getAllTargets();
@@ -678,8 +684,8 @@ function formatDetectionResults(detection) {
678
684
  var __filename = fileURLToPath(import.meta.url);
679
685
  var __dirname = dirname(__filename);
680
686
  async function initCommand(options, services = createDefaultServices()) {
681
- const { fs } = services;
682
- if (fs.existsSync("promptscript.yaml") && !options.force) {
687
+ const { fs: fs4 } = services;
688
+ if (fs4.existsSync("promptscript.yaml") && !options.force) {
683
689
  ConsoleOutput.warn("PromptScript already initialized");
684
690
  ConsoleOutput.muted("Use --force to reinitialize");
685
691
  return;
@@ -689,11 +695,11 @@ async function initCommand(options, services = createDefaultServices()) {
689
695
  const aiToolsDetection = await detectAITools(services);
690
696
  const config = await resolveConfig(options, projectInfo, aiToolsDetection, services);
691
697
  const spinner = createSpinner("Creating PromptScript configuration...").start();
692
- await fs.mkdir(".promptscript", { recursive: true });
698
+ await fs4.mkdir(".promptscript", { recursive: true });
693
699
  const configContent = generateConfig(config);
694
- await fs.writeFile("promptscript.yaml", configContent, "utf-8");
700
+ await fs4.writeFile("promptscript.yaml", configContent, "utf-8");
695
701
  const projectPsContent = generateProjectPs(config, projectInfo);
696
- await fs.writeFile(".promptscript/project.prs", projectPsContent, "utf-8");
702
+ await fs4.writeFile(".promptscript/project.prs", projectPsContent, "utf-8");
697
703
  spinner.succeed("PromptScript initialized");
698
704
  ConsoleOutput.newline();
699
705
  console.log("Created:");
@@ -786,7 +792,7 @@ async function runInteractivePrompts(options, projectInfo, aiToolsDetection, ser
786
792
  }
787
793
  const wantsRegistry = await prompts2.confirm({
788
794
  message: "Do you want to configure a registry?",
789
- default: true
795
+ default: false
790
796
  });
791
797
  let registry;
792
798
  if (wantsRegistry) {
@@ -830,7 +836,8 @@ function formatTargetName(target) {
830
836
  const names = {
831
837
  github: "GitHub Copilot",
832
838
  claude: "Claude (Anthropic)",
833
- cursor: "Cursor"
839
+ cursor: "Cursor",
840
+ antigravity: "Antigravity (Google)"
834
841
  };
835
842
  return names[target] ?? target;
836
843
  }
@@ -907,7 +914,7 @@ ${frameworksLine}
907
914
  // packages/cli/src/commands/compile.ts
908
915
  import { resolve as resolve2, dirname as dirname3 } from "path";
909
916
  import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
910
- import { existsSync as existsSync3 } from "fs";
917
+ import { existsSync as existsSync6 } from "fs";
911
918
  import chokidar from "chokidar";
912
919
 
913
920
  // packages/cli/src/config/loader.ts
@@ -1962,21 +1969,13 @@ var GitHubFormatter = class extends BaseFormatter {
1962
1969
  const subsections = [];
1963
1970
  const eslint = configObj["eslint"];
1964
1971
  if (eslint) {
1965
- const items = [
1966
- "All package ESLint configs must inherit from `eslint.base.config.cjs` in the root",
1967
- "Package configs should use `createBaseConfig(__dirname)` from the base config",
1968
- "Do not duplicate ESLint rules in package configs - modify the base config instead"
1969
- ];
1970
- subsections.push(renderer.renderSection("eslint", renderer.renderList(items), 2));
1972
+ const value = this.valueToString(eslint);
1973
+ subsections.push(renderer.renderSection("eslint", `ESLint: ${value}`, 2));
1971
1974
  }
1972
1975
  const vite = configObj["viteRoot"];
1973
1976
  if (vite) {
1974
- const items = [
1975
- "Use `__dirname` for the `root` option in both `vite.config.ts` and `vitest.config.mts`",
1976
- "Do NOT use `import.meta.dirname` - it causes TypeScript errors with current tsconfig settings",
1977
- "Example: `root: __dirname,`"
1978
- ];
1979
- subsections.push(renderer.renderSection("vite-vitest", renderer.renderList(items), 2));
1977
+ const value = this.valueToString(vite);
1978
+ subsections.push(renderer.renderSection("vite-vitest", `Vite root: ${value}`, 2));
1980
1979
  }
1981
1980
  if (subsections.length === 0) return null;
1982
1981
  return renderer.renderSection("configuration-files", subsections.join("\n"));
@@ -2567,7 +2566,9 @@ var ClaudeFormatter = class extends BaseFormatter {
2567
2566
  const identity2 = this.findBlock(ast, "identity");
2568
2567
  if (!identity2) return null;
2569
2568
  const text = this.extractText(identity2.content);
2570
- const cleanText = text.split("\n").map((line) => line.trim()).filter((line) => line).join("\n");
2569
+ const cleanText = text.split(/\n{2,}/).map(
2570
+ (para) => para.split("\n").map((line) => line.trim()).filter((line) => line).join("\n")
2571
+ ).filter((para) => para).join("\n\n");
2571
2572
  return renderer.renderSection("Project", cleanText) + "\n";
2572
2573
  }
2573
2574
  techStack(ast, renderer) {
@@ -2707,7 +2708,7 @@ var ClaudeFormatter = class extends BaseFormatter {
2707
2708
  if (shortcuts) {
2708
2709
  const props = this.getProps(shortcuts.content);
2709
2710
  for (const [cmd, desc] of Object.entries(props)) {
2710
- const shortDesc = this.valueToString(desc).split("\n")[0]?.substring(0, 40) ?? "";
2711
+ const shortDesc = this.valueToString(desc).split("\n")[0] ?? "";
2711
2712
  commandLines.push(`${cmd.padEnd(10)} - ${shortDesc}`);
2712
2713
  }
2713
2714
  }
@@ -2796,21 +2797,21 @@ var ClaudeFormatter = class extends BaseFormatter {
2796
2797
  var CURSOR_VERSIONS = {
2797
2798
  modern: {
2798
2799
  name: "modern",
2799
- description: "MDC format with YAML frontmatter (.cursor/rules/project.mdc)",
2800
+ description: "MDC format with YAML frontmatter (.cursor/rules/project.mdc) + slash commands (.cursor/commands/)",
2800
2801
  outputPath: ".cursor/rules/project.mdc",
2801
2802
  cursorVersion: "0.45+",
2802
2803
  introduced: "2024-12"
2803
2804
  },
2804
2805
  frontmatter: {
2805
2806
  name: "frontmatter",
2806
- description: "Alias for modern format (MDC with YAML frontmatter)",
2807
+ description: "Alias for modern format (MDC with YAML frontmatter + slash commands)",
2807
2808
  outputPath: ".cursor/rules/project.mdc",
2808
2809
  cursorVersion: "0.45+",
2809
2810
  introduced: "2024-12"
2810
2811
  },
2811
2812
  multifile: {
2812
2813
  name: "multifile",
2813
- description: "Multiple MDC files with glob-based targeting",
2814
+ description: "Multiple MDC files with glob-based targeting + slash commands",
2814
2815
  outputPath: ".cursor/rules/project.mdc",
2815
2816
  cursorVersion: "0.45+",
2816
2817
  introduced: "2024-12"
@@ -2875,9 +2876,11 @@ var CursorFormatter = class extends BaseFormatter {
2875
2876
  const frontmatter = this.frontmatter(ast);
2876
2877
  sections.push(frontmatter);
2877
2878
  this.addCommonSections(ast, sections);
2879
+ const commandFiles = this.generateCommandFiles(ast);
2878
2880
  return {
2879
2881
  path: options?.outputPath ?? CURSOR_VERSIONS.modern.outputPath,
2880
- content: sections.join("\n\n") + "\n"
2882
+ content: sections.join("\n\n") + "\n",
2883
+ additionalFiles: commandFiles.length > 0 ? commandFiles : void 0
2881
2884
  };
2882
2885
  }
2883
2886
  /**
@@ -2909,6 +2912,8 @@ var CursorFormatter = class extends BaseFormatter {
2909
2912
  if (shortcutsFile) {
2910
2913
  additionalFiles.push(shortcutsFile);
2911
2914
  }
2915
+ const commandFiles = this.generateCommandFiles(ast);
2916
+ additionalFiles.push(...commandFiles);
2912
2917
  const mainSections = [];
2913
2918
  mainSections.push(this.frontmatter(ast));
2914
2919
  mainSections.push(this.intro(ast));
@@ -3046,6 +3051,34 @@ var CursorFormatter = class extends BaseFormatter {
3046
3051
  content: sections.join("\n\n") + "\n"
3047
3052
  };
3048
3053
  }
3054
+ /**
3055
+ * Generate command files for multi-line shortcuts.
3056
+ *
3057
+ * Shortcuts with multi-line content (containing newlines) are converted
3058
+ * to individual `.cursor/commands/*.md` files that can be invoked as
3059
+ * slash commands in Cursor 1.6+.
3060
+ *
3061
+ * Single-line shortcuts remain as documentation in the rules file.
3062
+ *
3063
+ * @see https://cursor.com/changelog/1-6
3064
+ */
3065
+ generateCommandFiles(ast) {
3066
+ const block = this.findBlock(ast, "shortcuts");
3067
+ if (!block) return [];
3068
+ const props = this.getProps(block.content);
3069
+ const commands = [];
3070
+ for (const [name, value] of Object.entries(props)) {
3071
+ const content = this.valueToString(value);
3072
+ if (content.includes("\n")) {
3073
+ const fileName = name.replace(/^\//, "");
3074
+ commands.push({
3075
+ path: `.cursor/commands/${fileName}.md`,
3076
+ content: content.trim()
3077
+ });
3078
+ }
3079
+ }
3080
+ return commands;
3081
+ }
3049
3082
  /**
3050
3083
  * Add common sections shared between modern and legacy formats.
3051
3084
  */
@@ -3350,24 +3383,24 @@ ${items.map((i) => "- " + i).join("\n")}`;
3350
3383
  ${lines.join("\n")}` : null;
3351
3384
  }
3352
3385
  devCommands(ast) {
3353
- const context = this.findBlock(ast, "context");
3354
- if (!context) return null;
3355
- const cmds = this.getProp(context.content, "commands");
3356
- if (!cmds) return null;
3357
- const text = typeof cmds === "string" ? cmds : this.valueToString(cmds);
3358
- if (!text.trim()) return null;
3386
+ const knowledge = this.findBlock(ast, "knowledge");
3387
+ if (!knowledge) return null;
3388
+ const text = this.extractText(knowledge.content);
3389
+ const match = this.extractSectionWithCodeBlock(text, "## Development Commands");
3390
+ if (!match) return null;
3391
+ const content = match.replace("## Development Commands", "").trim();
3359
3392
  return `Development Commands:
3360
- ${text.trim()}`;
3393
+ ${content}`;
3361
3394
  }
3362
3395
  postWork(ast) {
3363
- const context = this.findBlock(ast, "context");
3364
- if (!context) return null;
3365
- const post = this.getProp(context.content, "post-work-verification");
3366
- if (!post) return null;
3367
- const text = typeof post === "string" ? post : this.valueToString(post);
3368
- if (!text.trim()) return null;
3396
+ const knowledge = this.findBlock(ast, "knowledge");
3397
+ if (!knowledge) return null;
3398
+ const text = this.extractText(knowledge.content);
3399
+ const match = this.extractSectionWithCodeBlock(text, "## Post-Work Verification");
3400
+ if (!match) return null;
3401
+ const content = match.replace("## Post-Work Verification", "").trim();
3369
3402
  return `Post-Work Verification:
3370
- ${text.trim()}`;
3403
+ ${content}`;
3371
3404
  }
3372
3405
  documentation(ast) {
3373
3406
  const items = this.extractDocItems(ast);
@@ -3882,25 +3915,17 @@ ${items.map((i) => "- " + i).join("\n")}`;
3882
3915
  const subsections = [];
3883
3916
  const eslint = configObj["eslint"];
3884
3917
  if (eslint) {
3885
- const items = [
3886
- "All package ESLint configs must inherit from `eslint.base.config.cjs` in the root",
3887
- "Package configs should use `createBaseConfig(__dirname)` from the base config",
3888
- "Do not duplicate ESLint rules in package configs - modify the base config instead"
3889
- ];
3918
+ const value = this.valueToString(eslint);
3890
3919
  subsections.push(`### ESLint
3891
3920
 
3892
- ${items.map((i) => "- " + i).join("\n")}`);
3921
+ - ESLint: ${value}`);
3893
3922
  }
3894
3923
  const vite = configObj["viteRoot"];
3895
3924
  if (vite) {
3896
- const items = [
3897
- "Use `__dirname` for the `root` option in both `vite.config.ts` and `vitest.config.mts`",
3898
- "Do NOT use `import.meta.dirname` - it causes TypeScript errors with current tsconfig settings",
3899
- "Example: `root: __dirname,`"
3900
- ];
3925
+ const value = this.valueToString(vite);
3901
3926
  subsections.push(`### Vite/Vitest
3902
3927
 
3903
- ${items.map((i) => "- " + i).join("\n")}`);
3928
+ - Vite root: ${value}`);
3904
3929
  }
3905
3930
  if (subsections.length === 0) return null;
3906
3931
  return `## Configuration Files
@@ -14414,11 +14439,22 @@ function mergeBlockContent(parent, child) {
14414
14439
  return deepClone(child);
14415
14440
  }
14416
14441
  function mergeTextContent2(parent, child) {
14442
+ const parentVal = parent.value.trim();
14443
+ const childVal = child.value.trim();
14444
+ if (parentVal === childVal) {
14445
+ return { ...child, value: childVal };
14446
+ }
14447
+ if (childVal.includes(parentVal)) {
14448
+ return { ...child, value: childVal };
14449
+ }
14450
+ if (parentVal.includes(childVal)) {
14451
+ return { ...child, value: parentVal };
14452
+ }
14417
14453
  return {
14418
14454
  ...child,
14419
- value: `${parent.value}
14455
+ value: `${parentVal}
14420
14456
 
14421
- ${child.value}`
14457
+ ${childVal}`
14422
14458
  };
14423
14459
  }
14424
14460
  function mergeObjectContent(parent, child) {
@@ -14436,7 +14472,7 @@ function mergeProperties(parent, child) {
14436
14472
  } else if (Array.isArray(childVal) && Array.isArray(parentVal)) {
14437
14473
  result[key] = uniqueConcat(parentVal, childVal);
14438
14474
  } else if (isTextContent(childVal) && isTextContent(parentVal)) {
14439
- result[key] = mergeTextContent2(parentVal, childVal);
14475
+ result[key] = deepCloneValue(childVal);
14440
14476
  } else if (isPlainObject2(childVal) && isPlainObject2(parentVal)) {
14441
14477
  result[key] = mergeProperties(
14442
14478
  parentVal,
@@ -14493,36 +14529,189 @@ function deepCloneValue(value) {
14493
14529
  // packages/resolver/src/imports.ts
14494
14530
  var IMPORT_MARKER_PREFIX = "__import__";
14495
14531
  function resolveUses(target, use, source) {
14496
- const alias = use.alias ?? extractId(source) ?? "import";
14497
- const markerName = `${IMPORT_MARKER_PREFIX}${alias}`;
14498
- const marker = {
14499
- type: "Block",
14500
- name: markerName,
14501
- content: {
14502
- type: "ObjectContent",
14503
- properties: {
14504
- __source: use.path.raw,
14505
- __blocks: source.blocks.map((b) => b.name)
14532
+ const mergedBlocks = mergeBlocks2(target.blocks, source.blocks);
14533
+ const aliasedBlocks = [];
14534
+ if (use.alias) {
14535
+ const alias = use.alias;
14536
+ const markerName = `${IMPORT_MARKER_PREFIX}${alias}`;
14537
+ const marker = {
14538
+ type: "Block",
14539
+ name: markerName,
14540
+ content: {
14541
+ type: "ObjectContent",
14542
+ properties: {
14543
+ __source: use.path.raw,
14544
+ __blocks: source.blocks.map((b) => b.name)
14545
+ },
14546
+ loc: use.loc
14506
14547
  },
14507
14548
  loc: use.loc
14508
- },
14509
- loc: use.loc
14549
+ };
14550
+ aliasedBlocks.push(marker);
14551
+ for (const block of source.blocks) {
14552
+ aliasedBlocks.push({
14553
+ ...block,
14554
+ name: `${IMPORT_MARKER_PREFIX}${alias}.${block.name}`
14555
+ });
14556
+ }
14557
+ }
14558
+ return {
14559
+ ...target,
14560
+ blocks: [...mergedBlocks, ...aliasedBlocks]
14510
14561
  };
14511
- const aliasedBlocks = source.blocks.map((block) => ({
14512
- ...block,
14513
- name: `${IMPORT_MARKER_PREFIX}${alias}.${block.name}`
14514
- }));
14562
+ }
14563
+ function mergeBlocks2(target, source) {
14564
+ const targetMap = new Map(target.map((b) => [b.name, b]));
14565
+ const result = [];
14566
+ const seen = /* @__PURE__ */ new Set();
14567
+ for (const tb of target) {
14568
+ const sb = source.find((b) => b.name === tb.name);
14569
+ if (sb) {
14570
+ result.push(mergeBlock2(sb, tb));
14571
+ seen.add(tb.name);
14572
+ } else {
14573
+ result.push(deepClone(tb));
14574
+ }
14575
+ }
14576
+ for (const sb of source) {
14577
+ if (!seen.has(sb.name) && !targetMap.has(sb.name)) {
14578
+ result.push(deepClone(sb));
14579
+ }
14580
+ }
14581
+ return result;
14582
+ }
14583
+ function mergeBlock2(source, target) {
14515
14584
  return {
14516
14585
  ...target,
14517
- blocks: [...target.blocks, marker, ...aliasedBlocks]
14586
+ content: mergeBlockContent2(source.content, target.content)
14518
14587
  };
14519
14588
  }
14520
- function extractId(program2) {
14521
- if (!program2.meta?.fields?.["id"]) {
14522
- return void 0;
14589
+ function mergeBlockContent2(source, target) {
14590
+ if (source.type === target.type) {
14591
+ switch (target.type) {
14592
+ case "TextContent":
14593
+ return mergeTextContent3(source, target);
14594
+ case "ObjectContent":
14595
+ return mergeObjectContent2(source, target);
14596
+ case "ArrayContent":
14597
+ return mergeArrayContent2(source, target);
14598
+ case "MixedContent":
14599
+ return mergeMixedContent2(source, target);
14600
+ }
14601
+ }
14602
+ if (source.type === "MixedContent" && target.type === "TextContent") {
14603
+ return {
14604
+ ...source,
14605
+ text: source.text ? mergeTextContent3(source.text, target) : deepClone(target)
14606
+ };
14607
+ }
14608
+ if (source.type === "TextContent" && target.type === "MixedContent") {
14609
+ return {
14610
+ ...target,
14611
+ text: target.text ? mergeTextContent3(source, target.text) : deepClone(source)
14612
+ };
14613
+ }
14614
+ if (source.type === "MixedContent" && target.type === "ObjectContent") {
14615
+ return {
14616
+ ...source,
14617
+ properties: mergeProperties2(source.properties, target.properties)
14618
+ };
14619
+ }
14620
+ if (source.type === "ObjectContent" && target.type === "MixedContent") {
14621
+ return {
14622
+ ...target,
14623
+ properties: mergeProperties2(source.properties, target.properties)
14624
+ };
14625
+ }
14626
+ return deepClone(target);
14627
+ }
14628
+ function mergeTextContent3(source, target) {
14629
+ const sourceVal = source.value.trim();
14630
+ const targetVal = target.value.trim();
14631
+ if (sourceVal === targetVal) {
14632
+ return { ...target, value: targetVal };
14633
+ }
14634
+ if (targetVal.includes(sourceVal)) {
14635
+ return { ...target, value: targetVal };
14636
+ }
14637
+ if (sourceVal.includes(targetVal)) {
14638
+ return { ...target, value: sourceVal };
14639
+ }
14640
+ return {
14641
+ ...target,
14642
+ value: `${sourceVal}
14643
+
14644
+ ${targetVal}`
14645
+ };
14646
+ }
14647
+ function mergeObjectContent2(source, target) {
14648
+ return {
14649
+ ...target,
14650
+ properties: mergeProperties2(source.properties, target.properties)
14651
+ };
14652
+ }
14653
+ function mergeProperties2(source, target) {
14654
+ const result = { ...source };
14655
+ for (const [key, targetVal] of Object.entries(target)) {
14656
+ const sourceVal = result[key];
14657
+ if (sourceVal === void 0) {
14658
+ result[key] = deepCloneValue2(targetVal);
14659
+ } else if (Array.isArray(targetVal) && Array.isArray(sourceVal)) {
14660
+ result[key] = uniqueConcat2(sourceVal, targetVal);
14661
+ } else if (isTextContent(targetVal) && isTextContent(sourceVal)) {
14662
+ } else if (isTextContent(sourceVal) || isTextContent(targetVal)) {
14663
+ } else if (isPlainObject3(targetVal) && isPlainObject3(sourceVal)) {
14664
+ result[key] = mergeProperties2(
14665
+ sourceVal,
14666
+ targetVal
14667
+ );
14668
+ } else if (typeof sourceVal === typeof targetVal) {
14669
+ } else {
14670
+ result[key] = deepCloneValue2(targetVal);
14671
+ }
14672
+ }
14673
+ return result;
14674
+ }
14675
+ function mergeArrayContent2(source, target) {
14676
+ return {
14677
+ ...target,
14678
+ elements: uniqueConcat2(source.elements, target.elements)
14679
+ };
14680
+ }
14681
+ function mergeMixedContent2(source, target) {
14682
+ return {
14683
+ ...target,
14684
+ text: source.text && target.text ? mergeTextContent3(source.text, target.text) : target.text ?? source.text,
14685
+ properties: mergeProperties2(source.properties, target.properties)
14686
+ };
14687
+ }
14688
+ function uniqueConcat2(source, target) {
14689
+ const seen = /* @__PURE__ */ new Set();
14690
+ const result = [];
14691
+ for (const item of [...source, ...target]) {
14692
+ const key = typeof item === "object" && item !== null ? JSON.stringify(item) : String(item);
14693
+ if (!seen.has(key)) {
14694
+ seen.add(key);
14695
+ result.push(deepCloneValue2(item));
14696
+ }
14523
14697
  }
14524
- const id = program2.meta.fields["id"];
14525
- return typeof id === "string" ? id : void 0;
14698
+ return result;
14699
+ }
14700
+ function isPlainObject3(val) {
14701
+ return typeof val === "object" && val !== null && !Array.isArray(val) && Object.getPrototypeOf(val) === Object.prototype;
14702
+ }
14703
+ function deepCloneValue2(value) {
14704
+ if (value === null || typeof value !== "object") {
14705
+ return value;
14706
+ }
14707
+ if (Array.isArray(value)) {
14708
+ return value.map(deepCloneValue2);
14709
+ }
14710
+ const result = {};
14711
+ for (const [key, val] of Object.entries(value)) {
14712
+ result[key] = deepCloneValue2(val);
14713
+ }
14714
+ return result;
14526
14715
  }
14527
14716
 
14528
14717
  // packages/resolver/src/extensions.ts
@@ -14706,7 +14895,7 @@ function mergeValue2(existing, extContent) {
14706
14895
  return extractValue(extContent);
14707
14896
  }
14708
14897
  if (Array.isArray(existing) && extContent.type === "ArrayContent") {
14709
- return uniqueConcat2(existing, extContent.elements);
14898
+ return uniqueConcat3(existing, extContent.elements);
14710
14899
  }
14711
14900
  if (typeof existing === "object" && existing !== null && !Array.isArray(existing) && extContent.type === "ObjectContent") {
14712
14901
  const merged = deepMerge(
@@ -14742,13 +14931,13 @@ ${ext.value}`
14742
14931
  case "ArrayContent":
14743
14932
  return {
14744
14933
  ...ext,
14745
- elements: uniqueConcat2(target.elements, ext.elements)
14934
+ elements: uniqueConcat3(target.elements, ext.elements)
14746
14935
  };
14747
14936
  case "MixedContent":
14748
- return mergeMixedContent2(target, ext);
14937
+ return mergeMixedContent3(target, ext);
14749
14938
  }
14750
14939
  }
14751
- function mergeMixedContent2(target, ext) {
14940
+ function mergeMixedContent3(target, ext) {
14752
14941
  const mergedText = target.text && ext.text ? {
14753
14942
  ...ext.text,
14754
14943
  value: `${target.text.value}
@@ -14799,7 +14988,7 @@ ${ext.value}` } : ext
14799
14988
  }
14800
14989
  return deepClone(ext);
14801
14990
  }
14802
- function uniqueConcat2(parent, child) {
14991
+ function uniqueConcat3(parent, child) {
14803
14992
  const seen = /* @__PURE__ */ new Set();
14804
14993
  const result = [];
14805
14994
  for (const item of [...parent, ...child]) {
@@ -14956,6 +15145,874 @@ var Resolver = class {
14956
15145
  }
14957
15146
  };
14958
15147
 
15148
+ // packages/resolver/src/registry.ts
15149
+ import { existsSync as existsSync3, promises as fs } from "fs";
15150
+ import { join as join3 } from "path";
15151
+ var FileSystemRegistry = class {
15152
+ rootPath;
15153
+ constructor(options) {
15154
+ this.rootPath = options.rootPath;
15155
+ }
15156
+ /**
15157
+ * Resolve a path relative to the registry root.
15158
+ */
15159
+ resolvePath(path) {
15160
+ return join3(this.rootPath, path);
15161
+ }
15162
+ async fetch(path) {
15163
+ const fullPath = this.resolvePath(path);
15164
+ try {
15165
+ return await fs.readFile(fullPath, "utf-8");
15166
+ } catch (err) {
15167
+ const error = err;
15168
+ if (error.code === "ENOENT") {
15169
+ throw new FileNotFoundError(fullPath);
15170
+ }
15171
+ throw err;
15172
+ }
15173
+ }
15174
+ async exists(path) {
15175
+ const fullPath = this.resolvePath(path);
15176
+ return existsSync3(fullPath);
15177
+ }
15178
+ async list(path) {
15179
+ const fullPath = this.resolvePath(path);
15180
+ try {
15181
+ const entries = await fs.readdir(fullPath, { withFileTypes: true });
15182
+ return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name);
15183
+ } catch {
15184
+ return [];
15185
+ }
15186
+ }
15187
+ };
15188
+ var HttpRegistry = class {
15189
+ baseUrl;
15190
+ auth;
15191
+ cacheEnabled;
15192
+ cacheTtl;
15193
+ maxRetries;
15194
+ initialDelay;
15195
+ timeout;
15196
+ cache = /* @__PURE__ */ new Map();
15197
+ constructor(options) {
15198
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
15199
+ this.auth = options.auth;
15200
+ this.cacheEnabled = options.cache?.enabled ?? false;
15201
+ this.cacheTtl = options.cache?.ttl ?? 3e5;
15202
+ this.maxRetries = options.retry?.maxRetries ?? 3;
15203
+ this.initialDelay = options.retry?.initialDelay ?? 1e3;
15204
+ this.timeout = options.timeout ?? 3e4;
15205
+ }
15206
+ /**
15207
+ * Build full URL for a path.
15208
+ */
15209
+ buildUrl(path) {
15210
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
15211
+ return `${this.baseUrl}${normalizedPath}`;
15212
+ }
15213
+ /**
15214
+ * Build headers for request.
15215
+ */
15216
+ buildHeaders() {
15217
+ const headers = {};
15218
+ if (this.auth) {
15219
+ if (this.auth.type === "bearer") {
15220
+ headers["Authorization"] = `Bearer ${this.auth.token}`;
15221
+ } else if (this.auth.type === "basic") {
15222
+ const encoded = Buffer.from(this.auth.token).toString("base64");
15223
+ headers["Authorization"] = `Basic ${encoded}`;
15224
+ }
15225
+ }
15226
+ return headers;
15227
+ }
15228
+ /**
15229
+ * Sleep for specified milliseconds.
15230
+ */
15231
+ sleep(ms) {
15232
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
15233
+ }
15234
+ /**
15235
+ * Fetch with timeout.
15236
+ */
15237
+ async fetchWithTimeout(url, options) {
15238
+ const controller = new AbortController();
15239
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
15240
+ try {
15241
+ return await fetch(url, {
15242
+ ...options,
15243
+ signal: controller.signal
15244
+ });
15245
+ } finally {
15246
+ clearTimeout(timeoutId);
15247
+ }
15248
+ }
15249
+ /**
15250
+ * Check if an error should not be retried.
15251
+ */
15252
+ isNonRetryableError(error) {
15253
+ return error.isClientError === true || error.name === "AbortError";
15254
+ }
15255
+ /**
15256
+ * Handle HTTP response, throwing for errors.
15257
+ */
15258
+ handleResponse(response) {
15259
+ if (response.ok) {
15260
+ return response;
15261
+ }
15262
+ const errorMessage = `HTTP ${response.status}: ${response.statusText}`;
15263
+ const error = new Error(errorMessage);
15264
+ if (response.status >= 400 && response.status < 500) {
15265
+ error.isClientError = true;
15266
+ }
15267
+ throw error;
15268
+ }
15269
+ /**
15270
+ * Fetch with retry logic.
15271
+ */
15272
+ async fetchWithRetry(url) {
15273
+ let lastError;
15274
+ let delay = this.initialDelay;
15275
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
15276
+ try {
15277
+ const response = await this.fetchWithTimeout(url, {
15278
+ method: "GET",
15279
+ headers: this.buildHeaders()
15280
+ });
15281
+ return this.handleResponse(response);
15282
+ } catch (err) {
15283
+ const error = err;
15284
+ if (this.isNonRetryableError(error)) {
15285
+ if (error.name === "AbortError") {
15286
+ throw new Error(`Request timeout after ${this.timeout}ms`);
15287
+ }
15288
+ throw error;
15289
+ }
15290
+ lastError = error;
15291
+ }
15292
+ if (attempt < this.maxRetries) {
15293
+ await this.sleep(delay);
15294
+ delay *= 2;
15295
+ }
15296
+ }
15297
+ throw lastError ?? new Error("Unknown fetch error");
15298
+ }
15299
+ /**
15300
+ * Check if cached entry is still valid.
15301
+ */
15302
+ isCacheValid(entry) {
15303
+ return Date.now() - entry.timestamp < this.cacheTtl;
15304
+ }
15305
+ async fetch(path) {
15306
+ if (this.cacheEnabled) {
15307
+ const cached = this.cache.get(path);
15308
+ if (cached && this.isCacheValid(cached)) {
15309
+ return cached.content;
15310
+ }
15311
+ }
15312
+ const url = this.buildUrl(path);
15313
+ try {
15314
+ const response = await this.fetchWithRetry(url);
15315
+ const content = await response.text();
15316
+ if (this.cacheEnabled) {
15317
+ this.cache.set(path, {
15318
+ content,
15319
+ timestamp: Date.now()
15320
+ });
15321
+ }
15322
+ return content;
15323
+ } catch (err) {
15324
+ const error = err;
15325
+ if (error.message.includes("404")) {
15326
+ throw new FileNotFoundError(path);
15327
+ }
15328
+ throw new Error(`Failed to fetch ${path}: ${error.message}`);
15329
+ }
15330
+ }
15331
+ async exists(path) {
15332
+ try {
15333
+ const url = this.buildUrl(path);
15334
+ const response = await this.fetchWithTimeout(url, {
15335
+ method: "HEAD",
15336
+ headers: this.buildHeaders()
15337
+ });
15338
+ return response.ok;
15339
+ } catch {
15340
+ return false;
15341
+ }
15342
+ }
15343
+ async list(path) {
15344
+ const url = this.buildUrl(`${path}/.index.json`);
15345
+ try {
15346
+ const response = await this.fetchWithTimeout(url, {
15347
+ method: "GET",
15348
+ headers: this.buildHeaders()
15349
+ });
15350
+ if (!response.ok) {
15351
+ return [];
15352
+ }
15353
+ const content = await response.text();
15354
+ return JSON.parse(content);
15355
+ } catch {
15356
+ return [];
15357
+ }
15358
+ }
15359
+ /**
15360
+ * Clear the cache.
15361
+ */
15362
+ clearCache() {
15363
+ this.cache.clear();
15364
+ }
15365
+ };
15366
+ function createFileSystemRegistry(rootPath) {
15367
+ return new FileSystemRegistry({ rootPath });
15368
+ }
15369
+ function createHttpRegistry(options) {
15370
+ return new HttpRegistry(options);
15371
+ }
15372
+
15373
+ // packages/resolver/src/git-registry.ts
15374
+ import { existsSync as existsSync5, promises as fs3 } from "fs";
15375
+ import { join as join5 } from "path";
15376
+ import { simpleGit } from "simple-git";
15377
+
15378
+ // packages/resolver/src/git-cache-manager.ts
15379
+ import { existsSync as existsSync4, promises as fs2 } from "fs";
15380
+ import { join as join4 } from "path";
15381
+ import { homedir } from "os";
15382
+
15383
+ // packages/resolver/src/git-url-utils.ts
15384
+ import { createHash } from "crypto";
15385
+ var GIT_URL_PATTERNS = {
15386
+ // HTTPS: https://github.com/org/repo.git or https://github.com/org/repo
15387
+ https: /^https?:\/\/(?<host>[^/:]+)(?::(?<port>\d+))?\/(?<owner>[^/]+)\/(?<repo>[^/]+?)(?:\.git)?$/,
15388
+ // SSH: git@github.com:org/repo.git or git@github.com:org/repo
15389
+ ssh: /^git@(?<host>[^:]+):(?<owner>[^/]+)\/(?<repo>[^/]+?)(?:\.git)?$/,
15390
+ // Git protocol: git://github.com/org/repo.git
15391
+ git: /^git:\/\/(?<host>[^/:]+)(?::(?<port>\d+))?\/(?<owner>[^/]+)\/(?<repo>[^/]+?)(?:\.git)?$/
15392
+ };
15393
+ function parseGitUrl(url) {
15394
+ if (url.startsWith("git@")) {
15395
+ const match = GIT_URL_PATTERNS.ssh.exec(url);
15396
+ if (match?.groups) {
15397
+ const { host, owner, repo } = match.groups;
15398
+ if (host && owner && repo) {
15399
+ return {
15400
+ original: url,
15401
+ protocol: "ssh",
15402
+ host,
15403
+ owner,
15404
+ repo
15405
+ };
15406
+ }
15407
+ }
15408
+ return null;
15409
+ }
15410
+ if (url.startsWith("git://")) {
15411
+ const match = GIT_URL_PATTERNS.git.exec(url);
15412
+ if (match?.groups) {
15413
+ const { host, owner, repo, port } = match.groups;
15414
+ if (host && owner && repo) {
15415
+ return {
15416
+ original: url,
15417
+ protocol: "git",
15418
+ host,
15419
+ owner,
15420
+ repo,
15421
+ port: port ? parseInt(port, 10) : void 0
15422
+ };
15423
+ }
15424
+ }
15425
+ return null;
15426
+ }
15427
+ if (url.startsWith("https://") || url.startsWith("http://")) {
15428
+ const match = GIT_URL_PATTERNS.https.exec(url);
15429
+ if (match?.groups) {
15430
+ const { host, owner, repo, port } = match.groups;
15431
+ if (host && owner && repo) {
15432
+ return {
15433
+ original: url,
15434
+ protocol: "https",
15435
+ host,
15436
+ owner,
15437
+ repo,
15438
+ port: port ? parseInt(port, 10) : void 0
15439
+ };
15440
+ }
15441
+ }
15442
+ }
15443
+ return null;
15444
+ }
15445
+ function normalizeGitUrl(url) {
15446
+ const parsed = parseGitUrl(url);
15447
+ if (!parsed) {
15448
+ return url;
15449
+ }
15450
+ const portPart = parsed.port ? `:${parsed.port}` : "";
15451
+ return `https://${parsed.host}${portPart}/${parsed.owner}/${parsed.repo}.git`;
15452
+ }
15453
+ function buildAuthenticatedUrl(url, token) {
15454
+ const parsed = parseGitUrl(url);
15455
+ if (!parsed) {
15456
+ return url;
15457
+ }
15458
+ if (parsed.protocol === "ssh") {
15459
+ return url;
15460
+ }
15461
+ const portPart = parsed.port ? `:${parsed.port}` : "";
15462
+ return `https://${token}@${parsed.host}${portPart}/${parsed.owner}/${parsed.repo}.git`;
15463
+ }
15464
+ function getCacheKey(url, ref) {
15465
+ const parsed = parseGitUrl(url);
15466
+ if (!parsed) {
15467
+ const hash2 = createHash("sha256").update(url).digest("hex").slice(0, 8);
15468
+ return `unknown-${hash2}`;
15469
+ }
15470
+ const components = [parsed.host, parsed.owner, parsed.repo];
15471
+ if (ref) {
15472
+ components.push(ref);
15473
+ }
15474
+ const hash = createHash("sha256").update(components.join("/")).digest("hex").slice(0, 8);
15475
+ return `${parsed.host}-${parsed.owner}-${parsed.repo}${ref ? `-${ref}` : ""}-${hash}`;
15476
+ }
15477
+ function parseVersionedPath(path) {
15478
+ const versionMatch = /@(v?\d+(?:\.\d+)*(?:-[\w.]+)?)$/.exec(path);
15479
+ if (versionMatch) {
15480
+ const version = versionMatch[1];
15481
+ const basePath = path.slice(0, -versionMatch[0].length);
15482
+ return { path: basePath, version };
15483
+ }
15484
+ return { path, version: void 0 };
15485
+ }
15486
+
15487
+ // packages/resolver/src/git-cache-manager.ts
15488
+ var DEFAULT_CACHE_DIR = join4(homedir(), ".promptscript", ".cache", "git");
15489
+ var DEFAULT_TTL = 36e5;
15490
+ var METADATA_FILE = ".prs-cache-meta.json";
15491
+ var GitCacheManager = class {
15492
+ cacheDir;
15493
+ ttl;
15494
+ constructor(options = {}) {
15495
+ this.cacheDir = options.cacheDir ?? DEFAULT_CACHE_DIR;
15496
+ this.ttl = options.ttl ?? DEFAULT_TTL;
15497
+ }
15498
+ /**
15499
+ * Get the cache directory for a given URL and ref.
15500
+ *
15501
+ * @param url - Git repository URL
15502
+ * @param ref - Git ref (branch/tag/commit)
15503
+ * @returns Path to the cache directory
15504
+ */
15505
+ getCachePath(url, ref) {
15506
+ const cacheKey = getCacheKey(url, ref);
15507
+ return join4(this.cacheDir, cacheKey);
15508
+ }
15509
+ /**
15510
+ * Check if a cache entry exists and is not stale.
15511
+ *
15512
+ * @param url - Git repository URL
15513
+ * @param ref - Git ref (branch/tag/commit)
15514
+ * @returns True if cache is valid (exists and not stale)
15515
+ */
15516
+ async isValid(url, ref) {
15517
+ const entry = await this.get(url, ref);
15518
+ return entry !== null && !entry.isStale;
15519
+ }
15520
+ /**
15521
+ * Get a cache entry if it exists.
15522
+ *
15523
+ * @param url - Git repository URL
15524
+ * @param ref - Git ref (branch/tag/commit)
15525
+ * @returns Cache entry or null if not found
15526
+ */
15527
+ async get(url, ref) {
15528
+ const cachePath = this.getCachePath(url, ref);
15529
+ if (!existsSync4(cachePath)) {
15530
+ return null;
15531
+ }
15532
+ const metadata = await this.readMetadata(cachePath);
15533
+ if (!metadata) {
15534
+ return null;
15535
+ }
15536
+ const isStale = Date.now() - metadata.lastUpdated > this.ttl;
15537
+ return {
15538
+ path: cachePath,
15539
+ metadata,
15540
+ isStale
15541
+ };
15542
+ }
15543
+ /**
15544
+ * Create or update a cache entry.
15545
+ *
15546
+ * @param url - Git repository URL
15547
+ * @param ref - Git ref (branch/tag/commit)
15548
+ * @param commitHash - Current commit hash
15549
+ * @returns Path to the cache directory
15550
+ */
15551
+ async set(url, ref, commitHash) {
15552
+ const cachePath = this.getCachePath(url, ref);
15553
+ await fs2.mkdir(cachePath, { recursive: true });
15554
+ const existingMetadata = await this.readMetadata(cachePath);
15555
+ const now = Date.now();
15556
+ const metadata = {
15557
+ url,
15558
+ ref,
15559
+ commitHash,
15560
+ lastUpdated: now,
15561
+ createdAt: existingMetadata?.createdAt ?? now,
15562
+ version: 1
15563
+ };
15564
+ await this.writeMetadata(cachePath, metadata);
15565
+ return cachePath;
15566
+ }
15567
+ /**
15568
+ * Update the lastUpdated timestamp for an existing cache entry.
15569
+ *
15570
+ * @param url - Git repository URL
15571
+ * @param ref - Git ref (branch/tag/commit)
15572
+ * @param commitHash - Optional new commit hash (if fetch resulted in update)
15573
+ */
15574
+ async touch(url, ref, commitHash) {
15575
+ const cachePath = this.getCachePath(url, ref);
15576
+ const metadata = await this.readMetadata(cachePath);
15577
+ if (!metadata) {
15578
+ throw new Error(`Cache entry not found: ${cachePath}`);
15579
+ }
15580
+ metadata.lastUpdated = Date.now();
15581
+ if (commitHash) {
15582
+ metadata.commitHash = commitHash;
15583
+ }
15584
+ await this.writeMetadata(cachePath, metadata);
15585
+ }
15586
+ /**
15587
+ * Remove a cache entry.
15588
+ *
15589
+ * @param url - Git repository URL
15590
+ * @param ref - Git ref (branch/tag/commit)
15591
+ */
15592
+ async remove(url, ref) {
15593
+ const cachePath = this.getCachePath(url, ref);
15594
+ if (existsSync4(cachePath)) {
15595
+ await fs2.rm(cachePath, { recursive: true, force: true });
15596
+ }
15597
+ }
15598
+ /**
15599
+ * List all cache entries.
15600
+ *
15601
+ * @returns Array of cache entries
15602
+ */
15603
+ async list() {
15604
+ if (!existsSync4(this.cacheDir)) {
15605
+ return [];
15606
+ }
15607
+ const entries = [];
15608
+ const dirs = await fs2.readdir(this.cacheDir, { withFileTypes: true });
15609
+ for (const dir of dirs) {
15610
+ if (!dir.isDirectory()) {
15611
+ continue;
15612
+ }
15613
+ const cachePath = join4(this.cacheDir, dir.name);
15614
+ const metadata = await this.readMetadata(cachePath);
15615
+ if (metadata) {
15616
+ const isStale = Date.now() - metadata.lastUpdated > this.ttl;
15617
+ entries.push({ path: cachePath, metadata, isStale });
15618
+ }
15619
+ }
15620
+ return entries;
15621
+ }
15622
+ /**
15623
+ * Remove all stale cache entries.
15624
+ *
15625
+ * @returns Number of entries removed
15626
+ */
15627
+ async cleanupStale() {
15628
+ const entries = await this.list();
15629
+ const staleEntries = entries.filter((e) => e.isStale);
15630
+ for (const entry of staleEntries) {
15631
+ await fs2.rm(entry.path, { recursive: true, force: true });
15632
+ }
15633
+ return staleEntries.length;
15634
+ }
15635
+ /**
15636
+ * Remove all cache entries.
15637
+ */
15638
+ async clear() {
15639
+ if (existsSync4(this.cacheDir)) {
15640
+ await fs2.rm(this.cacheDir, { recursive: true, force: true });
15641
+ }
15642
+ }
15643
+ /**
15644
+ * Get the total size of the cache in bytes.
15645
+ *
15646
+ * @returns Total cache size in bytes
15647
+ */
15648
+ async getSize() {
15649
+ if (!existsSync4(this.cacheDir)) {
15650
+ return 0;
15651
+ }
15652
+ return this.calculateDirSize(this.cacheDir);
15653
+ }
15654
+ /**
15655
+ * Calculate the size of a directory recursively.
15656
+ */
15657
+ async calculateDirSize(dirPath) {
15658
+ let size = 0;
15659
+ const entries = await fs2.readdir(dirPath, { withFileTypes: true });
15660
+ for (const entry of entries) {
15661
+ const fullPath = join4(dirPath, entry.name);
15662
+ if (entry.isDirectory()) {
15663
+ size += await this.calculateDirSize(fullPath);
15664
+ } else {
15665
+ const stat = await fs2.stat(fullPath);
15666
+ size += stat.size;
15667
+ }
15668
+ }
15669
+ return size;
15670
+ }
15671
+ /**
15672
+ * Read metadata from a cache directory.
15673
+ */
15674
+ async readMetadata(cachePath) {
15675
+ const metadataPath = join4(cachePath, METADATA_FILE);
15676
+ if (!existsSync4(metadataPath)) {
15677
+ return null;
15678
+ }
15679
+ try {
15680
+ const content = await fs2.readFile(metadataPath, "utf-8");
15681
+ return JSON.parse(content);
15682
+ } catch {
15683
+ return null;
15684
+ }
15685
+ }
15686
+ /**
15687
+ * Write metadata to a cache directory.
15688
+ */
15689
+ async writeMetadata(cachePath, metadata) {
15690
+ const metadataPath = join4(cachePath, METADATA_FILE);
15691
+ await fs2.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
15692
+ }
15693
+ };
15694
+
15695
+ // packages/resolver/src/git-registry.ts
15696
+ var GitCloneError = class extends Error {
15697
+ constructor(message, url, cause) {
15698
+ super(message, { cause });
15699
+ this.url = url;
15700
+ this.name = "GitCloneError";
15701
+ this.cause = cause;
15702
+ }
15703
+ cause;
15704
+ };
15705
+ var GitAuthError = class extends Error {
15706
+ constructor(message, url, cause) {
15707
+ super(message, { cause });
15708
+ this.url = url;
15709
+ this.name = "GitAuthError";
15710
+ this.cause = cause;
15711
+ }
15712
+ cause;
15713
+ };
15714
+ var GitRefNotFoundError = class extends Error {
15715
+ constructor(ref, url) {
15716
+ super(`Git ref not found: ${ref} in ${url}`);
15717
+ this.ref = ref;
15718
+ this.url = url;
15719
+ this.name = "GitRefNotFoundError";
15720
+ }
15721
+ };
15722
+ var GitRegistry = class {
15723
+ url;
15724
+ originalUrl;
15725
+ defaultRef;
15726
+ subPath;
15727
+ auth;
15728
+ cacheEnabled;
15729
+ timeout;
15730
+ cacheManager;
15731
+ initialized = false;
15732
+ constructor(options) {
15733
+ this.originalUrl = options.url;
15734
+ this.url = normalizeGitUrl(options.url);
15735
+ this.defaultRef = options.ref ?? "main";
15736
+ this.subPath = options.path ?? "";
15737
+ this.auth = options.auth;
15738
+ this.cacheEnabled = options.cache?.enabled ?? true;
15739
+ this.timeout = options.timeout ?? 6e4;
15740
+ this.cacheManager = new GitCacheManager({
15741
+ cacheDir: options.cacheDir,
15742
+ ttl: options.cache?.ttl
15743
+ });
15744
+ }
15745
+ /**
15746
+ * Fetch the content of a file from the registry.
15747
+ *
15748
+ * @param path - Path to the file (may include version tag)
15749
+ * @returns File content as string
15750
+ * @throws FileNotFoundError if the file doesn't exist
15751
+ * @throws GitCloneError if cloning fails
15752
+ * @throws GitRefNotFoundError if the ref doesn't exist
15753
+ */
15754
+ async fetch(path) {
15755
+ const { path: basePath, version } = parseVersionedPath(path);
15756
+ const ref = version ?? this.defaultRef;
15757
+ const repoPath = await this.ensureCloned(ref);
15758
+ const filePath = this.resolveFilePath(repoPath, basePath);
15759
+ if (!existsSync5(filePath)) {
15760
+ throw new FileNotFoundError(path);
15761
+ }
15762
+ return fs3.readFile(filePath, "utf-8");
15763
+ }
15764
+ /**
15765
+ * Check if a file exists in the registry.
15766
+ *
15767
+ * @param path - Path to check (may include version tag)
15768
+ * @returns True if the file exists
15769
+ */
15770
+ async exists(path) {
15771
+ try {
15772
+ const { path: basePath, version } = parseVersionedPath(path);
15773
+ const ref = version ?? this.defaultRef;
15774
+ const repoPath = await this.ensureCloned(ref);
15775
+ const filePath = this.resolveFilePath(repoPath, basePath);
15776
+ return existsSync5(filePath);
15777
+ } catch {
15778
+ return false;
15779
+ }
15780
+ }
15781
+ /**
15782
+ * List files in a directory.
15783
+ *
15784
+ * @param path - Directory path (may include version tag)
15785
+ * @returns Array of file/directory names
15786
+ */
15787
+ async list(path) {
15788
+ try {
15789
+ const { path: basePath, version } = parseVersionedPath(path);
15790
+ const ref = version ?? this.defaultRef;
15791
+ const repoPath = await this.ensureCloned(ref);
15792
+ const dirPath = this.resolveDirectoryPath(repoPath, basePath);
15793
+ if (!existsSync5(dirPath)) {
15794
+ return [];
15795
+ }
15796
+ const entries = await fs3.readdir(dirPath, { withFileTypes: true });
15797
+ return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name);
15798
+ } catch {
15799
+ return [];
15800
+ }
15801
+ }
15802
+ /**
15803
+ * Force refresh the cache for a specific ref.
15804
+ *
15805
+ * @param ref - Git ref to refresh (defaults to defaultRef)
15806
+ */
15807
+ async refresh(ref) {
15808
+ const targetRef = ref ?? this.defaultRef;
15809
+ await this.cacheManager.remove(this.url, targetRef);
15810
+ await this.ensureCloned(targetRef);
15811
+ }
15812
+ /**
15813
+ * Get the current commit hash for a ref.
15814
+ *
15815
+ * @param ref - Git ref (defaults to defaultRef)
15816
+ * @returns Commit hash
15817
+ */
15818
+ async getCommitHash(ref) {
15819
+ const targetRef = ref ?? this.defaultRef;
15820
+ const repoPath = await this.ensureCloned(targetRef);
15821
+ const git = this.createGit(repoPath);
15822
+ const result = await git.revparse(["HEAD"]);
15823
+ return result.trim();
15824
+ }
15825
+ /**
15826
+ * Ensure the repository is cloned and at the correct ref.
15827
+ */
15828
+ async ensureCloned(ref) {
15829
+ if (this.cacheEnabled) {
15830
+ const entry = await this.cacheManager.get(this.url, ref);
15831
+ if (entry && !entry.isStale) {
15832
+ return entry.path;
15833
+ }
15834
+ if (entry) {
15835
+ try {
15836
+ await this.fetchUpdates(entry.path, ref);
15837
+ const commitHash2 = await this.getCurrentCommit(entry.path);
15838
+ await this.cacheManager.touch(this.url, ref, commitHash2);
15839
+ return entry.path;
15840
+ } catch {
15841
+ await this.cacheManager.remove(this.url, ref);
15842
+ }
15843
+ }
15844
+ }
15845
+ const cachePath = this.cacheManager.getCachePath(this.url, ref);
15846
+ await this.clone(cachePath, ref);
15847
+ const commitHash = await this.getCurrentCommit(cachePath);
15848
+ await this.cacheManager.set(this.url, ref, commitHash);
15849
+ return cachePath;
15850
+ }
15851
+ /**
15852
+ * Clone the repository to the specified path.
15853
+ */
15854
+ async clone(targetPath, ref) {
15855
+ if (existsSync5(targetPath)) {
15856
+ await fs3.rm(targetPath, { recursive: true, force: true });
15857
+ }
15858
+ await fs3.mkdir(targetPath, { recursive: true });
15859
+ const cloneUrl = this.getAuthenticatedUrl();
15860
+ const git = this.createGit();
15861
+ try {
15862
+ await git.clone(cloneUrl, targetPath, ["--depth=1", `--branch=${ref}`, "--single-branch"]);
15863
+ } catch (err) {
15864
+ const error = err;
15865
+ if (this.isAuthError(error)) {
15866
+ throw new GitAuthError(`Authentication failed for ${this.url}`, this.url, error);
15867
+ }
15868
+ if (this.isRefError(error)) {
15869
+ try {
15870
+ await git.clone(cloneUrl, targetPath, ["--depth=1"]);
15871
+ const repoGit = this.createGit(targetPath);
15872
+ await repoGit.fetch(["origin", ref, "--depth=1"]);
15873
+ await repoGit.checkout(ref);
15874
+ } catch (fetchErr) {
15875
+ const fetchError = fetchErr;
15876
+ if (this.isRefError(fetchError)) {
15877
+ throw new GitRefNotFoundError(ref, this.url);
15878
+ }
15879
+ throw new GitCloneError(
15880
+ `Failed to clone repository: ${fetchError.message}`,
15881
+ this.url,
15882
+ fetchError
15883
+ );
15884
+ }
15885
+ return;
15886
+ }
15887
+ throw new GitCloneError(`Failed to clone repository: ${error.message}`, this.url, error);
15888
+ }
15889
+ }
15890
+ /**
15891
+ * Fetch updates for an existing clone.
15892
+ */
15893
+ async fetchUpdates(repoPath, ref) {
15894
+ const git = this.createGit(repoPath);
15895
+ try {
15896
+ await git.fetch(["origin", ref, "--depth=1"]);
15897
+ await git.checkout(ref);
15898
+ await git.reset(["--hard", `origin/${ref}`]);
15899
+ } catch (err) {
15900
+ const error = err;
15901
+ if (this.isRefError(error)) {
15902
+ try {
15903
+ await git.fetch(["origin", "--tags", "--depth=1"]);
15904
+ await git.checkout(ref);
15905
+ return;
15906
+ } catch {
15907
+ throw new GitRefNotFoundError(ref, this.url);
15908
+ }
15909
+ }
15910
+ throw error;
15911
+ }
15912
+ }
15913
+ /**
15914
+ * Get the current commit hash of a repository.
15915
+ */
15916
+ async getCurrentCommit(repoPath) {
15917
+ const git = this.createGit(repoPath);
15918
+ const result = await git.revparse(["HEAD"]);
15919
+ return result.trim();
15920
+ }
15921
+ /**
15922
+ * Build the authenticated URL for cloning.
15923
+ */
15924
+ getAuthenticatedUrl() {
15925
+ if (!this.auth) {
15926
+ return this.url;
15927
+ }
15928
+ if (this.auth.type === "token") {
15929
+ const token = this.resolveToken();
15930
+ if (token) {
15931
+ return buildAuthenticatedUrl(this.url, token);
15932
+ }
15933
+ }
15934
+ return this.url;
15935
+ }
15936
+ /**
15937
+ * Resolve the authentication token.
15938
+ */
15939
+ resolveToken() {
15940
+ if (this.auth?.token) {
15941
+ return this.auth.token;
15942
+ }
15943
+ if (this.auth?.tokenEnvVar) {
15944
+ return process.env[this.auth.tokenEnvVar];
15945
+ }
15946
+ return void 0;
15947
+ }
15948
+ /**
15949
+ * Create a simple-git instance.
15950
+ */
15951
+ createGit(baseDir) {
15952
+ const options = {
15953
+ timeout: {
15954
+ block: this.timeout
15955
+ }
15956
+ };
15957
+ if (baseDir) {
15958
+ options.baseDir = baseDir;
15959
+ }
15960
+ const git = simpleGit(options);
15961
+ if (this.auth?.type === "ssh" && this.auth.sshKeyPath) {
15962
+ const parsed = parseGitUrl(this.originalUrl);
15963
+ if (parsed?.protocol === "ssh") {
15964
+ git.env("GIT_SSH_COMMAND", `ssh -i ${this.auth.sshKeyPath} -o StrictHostKeyChecking=no`);
15965
+ }
15966
+ }
15967
+ return git;
15968
+ }
15969
+ /**
15970
+ * Resolve a file path within the repository.
15971
+ *
15972
+ * Note: The @ prefix is preserved as it's part of the directory name
15973
+ * (e.g., @core/base.prs lives in a directory literally named "@core")
15974
+ */
15975
+ resolveFilePath(repoPath, relativePath) {
15976
+ let cleanPath = relativePath;
15977
+ if (!cleanPath.endsWith(".prs") && !cleanPath.endsWith("/")) {
15978
+ cleanPath += ".prs";
15979
+ }
15980
+ if (this.subPath) {
15981
+ return join5(repoPath, this.subPath, cleanPath);
15982
+ }
15983
+ return join5(repoPath, cleanPath);
15984
+ }
15985
+ /**
15986
+ * Resolve a directory path within the repository.
15987
+ *
15988
+ * Note: The @ prefix is preserved as it's part of the directory name
15989
+ * (e.g., @core lives in a directory literally named "@core")
15990
+ */
15991
+ resolveDirectoryPath(repoPath, relativePath) {
15992
+ if (this.subPath) {
15993
+ return join5(repoPath, this.subPath, relativePath);
15994
+ }
15995
+ return join5(repoPath, relativePath);
15996
+ }
15997
+ /**
15998
+ * Check if an error is an authentication error.
15999
+ */
16000
+ isAuthError(error) {
16001
+ const message = error.message.toLowerCase();
16002
+ return message.includes("authentication") || message.includes("permission denied") || message.includes("could not read from remote") || message.includes("invalid credentials") || message.includes("401") || message.includes("403");
16003
+ }
16004
+ /**
16005
+ * Check if an error is a ref not found error.
16006
+ */
16007
+ isRefError(error) {
16008
+ const message = error.message.toLowerCase();
16009
+ return message.includes("could not find remote branch") || message.includes("couldn't find remote ref") || message.includes("pathspec") || message.includes("did not match any") || message.includes("not found in upstream");
16010
+ }
16011
+ };
16012
+ function createGitRegistry(options) {
16013
+ return new GitRegistry(options);
16014
+ }
16015
+
14959
16016
  // packages/validator/src/rules/required-meta.ts
14960
16017
  var requiredMetaId = {
14961
16018
  id: "PS001",
@@ -15501,6 +16558,11 @@ var Compiler = class _Compiler {
15501
16558
  const formatOptions = this.getFormatOptionsForTarget(formatter.name, config);
15502
16559
  const output = formatter.format(resolved.ast, formatOptions);
15503
16560
  outputs.set(output.path, output);
16561
+ if (output.additionalFiles) {
16562
+ for (const additionalFile of output.additionalFiles) {
16563
+ outputs.set(additionalFile.path, additionalFile);
16564
+ }
16565
+ }
15504
16566
  } catch (err) {
15505
16567
  formatErrors.push({
15506
16568
  name: "FormatterError",
@@ -15793,7 +16855,7 @@ async function compileCommand(options) {
15793
16855
  customConventions: config.customConventions
15794
16856
  });
15795
16857
  const entryPath = resolve2("./.promptscript/project.prs");
15796
- if (!existsSync3(entryPath)) {
16858
+ if (!existsSync6(entryPath)) {
15797
16859
  spinner.fail("Entry file not found");
15798
16860
  ConsoleOutput.error(`File not found: ${entryPath}`);
15799
16861
  ConsoleOutput.muted("Run: prs init");
@@ -15858,7 +16920,7 @@ function watchForChanges(dir, callback) {
15858
16920
 
15859
16921
  // packages/cli/src/commands/validate.ts
15860
16922
  import { resolve as resolve3 } from "path";
15861
- import { existsSync as existsSync4 } from "fs";
16923
+ import { existsSync as existsSync7 } from "fs";
15862
16924
  function printValidationErrors(errors) {
15863
16925
  if (errors.length === 0) return;
15864
16926
  console.log(`Errors (${errors.length}):`);
@@ -15949,7 +17011,7 @@ async function validateCommand(options) {
15949
17011
  // No formatters needed for validation only
15950
17012
  });
15951
17013
  const entryPath = resolve3("./.promptscript/project.prs");
15952
- if (!existsSync4(entryPath)) {
17014
+ if (!existsSync7(entryPath)) {
15953
17015
  handleEntryNotFound(entryPath, isJsonFormat, spinner);
15954
17016
  }
15955
17017
  const result = await compiler.compile(entryPath);
@@ -15988,37 +17050,33 @@ function outputJsonResult(result) {
15988
17050
  }
15989
17051
 
15990
17052
  // packages/cli/src/commands/pull.ts
15991
- import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile4 } from "fs/promises";
15992
- import { existsSync as existsSync5 } from "fs";
17053
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
17054
+ import { existsSync as existsSync8 } from "fs";
15993
17055
  import { resolve as resolve4, dirname as dirname4 } from "path";
15994
17056
  async function pullCommand(options) {
15995
17057
  const spinner = createSpinner("Loading configuration...").start();
15996
17058
  try {
15997
17059
  const config = await loadConfig();
15998
- if (config.registry?.url) {
15999
- spinner.fail("Remote registry not yet supported");
16000
- ConsoleOutput.error("Remote registry support coming soon");
16001
- ConsoleOutput.muted("For now, use a local registry path in your config.");
16002
- process.exit(1);
16003
- }
16004
17060
  if (!config.inherit) {
16005
17061
  spinner.warn("No inheritance configured");
16006
17062
  ConsoleOutput.muted('Add "inherit" to your config to pull from registry');
16007
17063
  return;
16008
17064
  }
16009
17065
  spinner.text = `Pulling ${config.inherit}...`;
16010
- const registryPath = config.registry?.path ?? "./registry";
17066
+ const registry = await createRegistry(config, options, spinner);
16011
17067
  const inheritPath = parseInheritPath(config.inherit);
16012
- const sourcePath = resolve4(registryPath, inheritPath);
16013
- if (!existsSync5(sourcePath)) {
17068
+ spinner.text = `Checking ${inheritPath}...`;
17069
+ const exists = await registry.exists(inheritPath);
17070
+ if (!exists) {
16014
17071
  spinner.fail("Inheritance source not found");
16015
- ConsoleOutput.error(`Not found: ${sourcePath}`);
16016
- ConsoleOutput.muted("Make sure the registry path is correct and the file exists");
17072
+ ConsoleOutput.error(`Not found in registry: ${config.inherit}`);
17073
+ ConsoleOutput.muted("Make sure the registry is configured correctly and the file exists");
16017
17074
  process.exit(1);
16018
17075
  }
16019
- const content = await readFile4(sourcePath, "utf-8");
17076
+ spinner.text = `Fetching ${inheritPath}...`;
17077
+ const content = await registry.fetch(inheritPath);
16020
17078
  const destPath = resolve4("./.promptscript/.inherited", inheritPath);
16021
- if (existsSync5(destPath) && !options.force && !options.dryRun) {
17079
+ if (existsSync8(destPath) && !options.force && !options.dryRun) {
16022
17080
  spinner.warn("File already exists (use --force to overwrite)");
16023
17081
  ConsoleOutput.muted(destPath);
16024
17082
  return;
@@ -16026,9 +17084,9 @@ async function pullCommand(options) {
16026
17084
  if (options.dryRun) {
16027
17085
  spinner.succeed("Dry run completed");
16028
17086
  ConsoleOutput.newline();
16029
- ConsoleOutput.dryRun(`Would copy: ${sourcePath}`);
17087
+ ConsoleOutput.dryRun(`Would fetch: ${config.inherit}`);
16030
17088
  ConsoleOutput.dryRun(` to: ${destPath}`);
16031
- if (existsSync5(destPath)) {
17089
+ if (existsSync8(destPath)) {
16032
17090
  ConsoleOutput.dryRun("(would overwrite existing file)");
16033
17091
  }
16034
17092
  return;
@@ -16038,23 +17096,97 @@ async function pullCommand(options) {
16038
17096
  spinner.succeed("Pulled from registry");
16039
17097
  ConsoleOutput.success(destPath);
16040
17098
  } catch (error) {
16041
- spinner.fail("Error");
16042
- ConsoleOutput.error(error.message);
17099
+ handlePullError(error, spinner);
16043
17100
  process.exit(1);
16044
17101
  }
16045
17102
  }
17103
+ async function createRegistry(config, options, spinner) {
17104
+ const gitRef = options.commit ?? options.tag ?? options.branch;
17105
+ if (config.registry?.git) {
17106
+ const gitConfig = config.registry.git;
17107
+ spinner.text = `Connecting to Git registry: ${gitConfig.url}...`;
17108
+ return createGitRegistry({
17109
+ url: gitConfig.url,
17110
+ ref: gitRef ?? gitConfig.ref,
17111
+ path: gitConfig.path,
17112
+ auth: gitConfig.auth,
17113
+ cache: {
17114
+ enabled: !options.refresh && (config.registry.cache?.enabled ?? true),
17115
+ ttl: config.registry.cache?.ttl
17116
+ }
17117
+ });
17118
+ }
17119
+ if (config.registry?.url) {
17120
+ spinner.text = `Connecting to HTTP registry: ${config.registry.url}...`;
17121
+ const httpAuth = config.registry.auth;
17122
+ let token;
17123
+ if (httpAuth) {
17124
+ token = httpAuth.token ?? (httpAuth.tokenEnvVar ? process.env[httpAuth.tokenEnvVar] : void 0);
17125
+ }
17126
+ return createHttpRegistry({
17127
+ baseUrl: config.registry.url,
17128
+ auth: httpAuth && token ? { type: httpAuth.type, token } : void 0,
17129
+ cache: {
17130
+ enabled: config.registry.cache?.enabled ?? true,
17131
+ ttl: config.registry.cache?.ttl ?? 3e5
17132
+ }
17133
+ });
17134
+ }
17135
+ const registryPath = config.registry?.path ?? "./registry";
17136
+ spinner.text = `Using local registry: ${registryPath}...`;
17137
+ if (!existsSync8(registryPath)) {
17138
+ throw new Error(`Local registry path not found: ${registryPath}`);
17139
+ }
17140
+ return createFileSystemRegistry(resolve4(registryPath));
17141
+ }
16046
17142
  function parseInheritPath(inheritPath) {
16047
- let path = inheritPath.startsWith("@") ? inheritPath.slice(1) : inheritPath;
17143
+ const versionIndex = inheritPath.lastIndexOf("@");
17144
+ const atIndex = inheritPath.indexOf("@");
17145
+ let pathWithoutVersion = inheritPath;
17146
+ if (versionIndex > atIndex && versionIndex !== atIndex) {
17147
+ pathWithoutVersion = inheritPath.slice(0, versionIndex);
17148
+ }
17149
+ let path = pathWithoutVersion.startsWith("@") ? pathWithoutVersion.slice(1) : pathWithoutVersion;
16048
17150
  if (!path.endsWith(".prs")) {
16049
17151
  path += ".prs";
16050
17152
  }
16051
17153
  return path;
16052
17154
  }
17155
+ function handlePullError(error, spinner) {
17156
+ if (error instanceof GitAuthError) {
17157
+ spinner.fail("Git authentication failed");
17158
+ ConsoleOutput.error(error.message);
17159
+ ConsoleOutput.muted("Check your authentication configuration:");
17160
+ ConsoleOutput.muted(" - For token auth: ensure GITHUB_TOKEN or tokenEnvVar is set");
17161
+ ConsoleOutput.muted(" - For SSH auth: ensure SSH key is configured and accessible");
17162
+ return;
17163
+ }
17164
+ if (error instanceof GitRefNotFoundError) {
17165
+ spinner.fail("Git ref not found");
17166
+ ConsoleOutput.error(error.message);
17167
+ ConsoleOutput.muted("Available options:");
17168
+ ConsoleOutput.muted(" - Use --branch <name> to specify a branch");
17169
+ ConsoleOutput.muted(" - Use --tag <name> to specify a tag");
17170
+ ConsoleOutput.muted(" - Use --commit <hash> to specify a commit");
17171
+ return;
17172
+ }
17173
+ if (error instanceof GitCloneError) {
17174
+ spinner.fail("Git clone failed");
17175
+ ConsoleOutput.error(error.message);
17176
+ ConsoleOutput.muted("Possible causes:");
17177
+ ConsoleOutput.muted(" - Network connectivity issues");
17178
+ ConsoleOutput.muted(" - Invalid repository URL");
17179
+ ConsoleOutput.muted(" - Missing authentication for private repository");
17180
+ return;
17181
+ }
17182
+ spinner.fail("Error");
17183
+ ConsoleOutput.error(error.message);
17184
+ }
16053
17185
 
16054
17186
  // packages/cli/src/commands/diff.ts
16055
17187
  import { resolve as resolve5 } from "path";
16056
- import { readFile as readFile5 } from "fs/promises";
16057
- import { existsSync as existsSync6 } from "fs";
17188
+ import { readFile as readFile4 } from "fs/promises";
17189
+ import { existsSync as existsSync9 } from "fs";
16058
17190
 
16059
17191
  // packages/cli/src/output/pager.ts
16060
17192
  import { spawn } from "child_process";
@@ -16170,14 +17302,14 @@ function printNewFilePreview(content, showFull, pager) {
16170
17302
  async function compareOutput(_name, output, _config, showFull, pager) {
16171
17303
  const outputPath = resolve5(output.path);
16172
17304
  const newContent = output.content;
16173
- if (!existsSync6(outputPath)) {
17305
+ if (!existsSync9(outputPath)) {
16174
17306
  pager.write(chalk2.green(`+ ${outputPath} (new file)`));
16175
17307
  pager.write("");
16176
17308
  printNewFilePreview(newContent, showFull, pager);
16177
17309
  pager.write("");
16178
17310
  return true;
16179
17311
  }
16180
- const existingContent = await readFile5(outputPath, "utf-8");
17312
+ const existingContent = await readFile4(outputPath, "utf-8");
16181
17313
  if (existingContent === newContent) {
16182
17314
  pager.write(chalk2.gray(` ${outputPath} (no changes)`));
16183
17315
  return false;
@@ -16207,7 +17339,7 @@ async function diffCommand(options) {
16207
17339
  customConventions: config.customConventions
16208
17340
  });
16209
17341
  const entryPath = resolve5("./.promptscript/project.prs");
16210
- if (!existsSync6(entryPath)) {
17342
+ if (!existsSync9(entryPath)) {
16211
17343
  spinner.fail("Entry file not found");
16212
17344
  ConsoleOutput.error(`File not found: ${entryPath}`);
16213
17345
  ConsoleOutput.muted("Run: prs init");
@@ -16273,7 +17405,7 @@ function printSimpleDiff(existingLines, newLines, showFull, pager) {
16273
17405
  }
16274
17406
 
16275
17407
  // packages/cli/src/commands/check.ts
16276
- import { existsSync as existsSync7 } from "fs";
17408
+ import { existsSync as existsSync10 } from "fs";
16277
17409
  import { resolve as resolve6 } from "path";
16278
17410
  async function checkCommand(_options) {
16279
17411
  const spinner = createSpinner("Checking project health...").start();
@@ -16336,7 +17468,7 @@ async function checkCommand(_options) {
16336
17468
  });
16337
17469
  }
16338
17470
  const entryPath = config.input?.entry ?? ".promptscript/project.prs";
16339
- if (existsSync7(entryPath)) {
17471
+ if (existsSync10(entryPath)) {
16340
17472
  results.push({
16341
17473
  name: "Entry file",
16342
17474
  status: "ok",
@@ -16352,7 +17484,7 @@ async function checkCommand(_options) {
16352
17484
  }
16353
17485
  if (config.registry?.path) {
16354
17486
  const registryPath = resolve6(config.registry.path);
16355
- if (existsSync7(registryPath)) {
17487
+ if (existsSync10(registryPath)) {
16356
17488
  results.push({
16357
17489
  name: "Registry path",
16358
17490
  status: "ok",
@@ -16451,7 +17583,7 @@ program.name("prs").description("PromptScript CLI - Standardize AI instructions"
16451
17583
  program.command("init").description("Initialize PromptScript in current directory").option("-n, --name <name>", "Project name (auto-detected from package.json, etc.)").option("-t, --team <team>", "Team namespace").option("--inherit <path>", "Inheritance path (e.g., @company/team)").option("--registry <path>", "Registry path").option("--targets <targets...>", "Target AI tools (github, claude, cursor)").option("-i, --interactive", "Force interactive mode").option("-y, --yes", "Skip prompts, use defaults").option("-f, --force", "Force reinitialize even if already initialized").action((opts) => initCommand(opts));
16452
17584
  program.command("compile").description("Compile PromptScript to target formats").option("-t, --target <target>", "Specific target (github, claude, cursor)").option("-f, --format <format>", "Output format (alias for --target)").option("-a, --all", "All configured targets", true).option("-w, --watch", "Watch mode").option("-o, --output <dir>", "Output directory").option("--dry-run", "Preview changes").option("--registry <path>", "Registry path (overrides config)").option("-c, --config <path>", "Path to custom config file").action(compileCommand);
16453
17585
  program.command("validate").description("Validate PromptScript files").option("--strict", "Treat warnings as errors").option("--format <format>", "Output format (text, json)", "text").action(validateCommand);
16454
- program.command("pull").description("Pull updates from registry").option("-f, --force", "Force overwrite").option("--dry-run", "Preview changes without pulling").action(pullCommand);
17586
+ program.command("pull").description("Pull updates from registry").option("-f, --force", "Force overwrite").option("--dry-run", "Preview changes without pulling").option("-b, --branch <name>", "Git branch to pull from").option("--tag <name>", "Git tag to pull from").option("--commit <hash>", "Git commit to pull from").option("--refresh", "Force re-fetch from remote (ignore cache)").action(pullCommand);
16455
17587
  program.command("diff").description("Show diff for compiled output").option("-t, --target <target>", "Specific target").option("-a, --all", "Show diff for all targets at once").option("--full", "Show full diff without truncation").option("--no-pager", "Disable pager output").option("--color", "Force colored output").option("--no-color", "Disable colored output").action(diffCommand);
16456
17588
  program.command("check").description("Check configuration and dependencies health").option("--fix", "Attempt to fix issues").action(checkCommand);
16457
17589
  function run(args = process.argv) {