promptopskit 0.0.1 → 0.0.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.
Files changed (59) hide show
  1. package/LICENSE +211 -211
  2. package/README.md +335 -309
  3. package/dist/{chunk-FSOJVXC4.js → chunk-5BI5FP5L.js} +2 -2
  4. package/dist/chunk-5BI5FP5L.js.map +1 -0
  5. package/dist/{chunk-HEFOFQ2K.js → chunk-6KGBPHSY.js} +1 -1
  6. package/dist/chunk-6KGBPHSY.js.map +1 -0
  7. package/dist/{chunk-X64JII57.js → chunk-NIZENC7D.js} +2 -2
  8. package/dist/chunk-NIZENC7D.js.map +1 -0
  9. package/dist/{chunk-YCRWYE6N.js → chunk-PU3UPUND.js} +2 -2
  10. package/dist/chunk-PU3UPUND.js.map +1 -0
  11. package/dist/{chunk-V375NQ6C.js → chunk-UYTUGV7Z.js} +2 -2
  12. package/dist/chunk-UYTUGV7Z.js.map +1 -0
  13. package/dist/{chunk-B5UFGNDV.js → chunk-VYVEVJC3.js} +2 -2
  14. package/dist/chunk-VYVEVJC3.js.map +1 -0
  15. package/dist/cli/index.js +452 -25
  16. package/dist/cli/index.js.map +1 -1
  17. package/dist/index.cjs +963 -0
  18. package/dist/index.cjs.map +1 -0
  19. package/dist/index.d.cts +184 -0
  20. package/dist/index.d.ts +10 -5
  21. package/dist/index.js +32 -7
  22. package/dist/index.js.map +1 -1
  23. package/dist/providers/anthropic.cjs +151 -0
  24. package/dist/providers/anthropic.cjs.map +1 -0
  25. package/dist/providers/anthropic.d.cts +11 -0
  26. package/dist/providers/anthropic.d.ts +2 -2
  27. package/dist/providers/anthropic.js +2 -2
  28. package/dist/providers/gemini.cjs +155 -0
  29. package/dist/providers/gemini.cjs.map +1 -0
  30. package/dist/providers/gemini.d.cts +11 -0
  31. package/dist/providers/gemini.d.ts +2 -2
  32. package/dist/providers/gemini.js +2 -2
  33. package/dist/providers/openai.cjs +146 -0
  34. package/dist/providers/openai.cjs.map +1 -0
  35. package/dist/providers/openai.d.cts +11 -0
  36. package/dist/providers/openai.d.ts +2 -2
  37. package/dist/providers/openai.js +2 -2
  38. package/dist/providers/openrouter.cjs +161 -0
  39. package/dist/providers/openrouter.cjs.map +1 -0
  40. package/dist/providers/openrouter.d.cts +13 -0
  41. package/dist/providers/openrouter.d.ts +2 -2
  42. package/dist/providers/openrouter.js +3 -3
  43. package/dist/{schema-DHRI5Mzl.d.ts → schema-C6smABrt.d.cts} +3 -3
  44. package/dist/schema-C6smABrt.d.ts +695 -0
  45. package/dist/testing.cjs +228 -0
  46. package/dist/testing.cjs.map +1 -0
  47. package/dist/testing.d.cts +17 -0
  48. package/dist/testing.d.ts +1 -1
  49. package/dist/testing.js +1 -1
  50. package/dist/testing.js.map +1 -1
  51. package/dist/{types-D9hquVja.d.ts → types-D7lW5IYT.d.ts} +1 -1
  52. package/dist/types-S_c-ZEfK.d.cts +40 -0
  53. package/package.json +116 -79
  54. package/dist/chunk-B5UFGNDV.js.map +0 -1
  55. package/dist/chunk-FSOJVXC4.js.map +0 -1
  56. package/dist/chunk-HEFOFQ2K.js.map +0 -1
  57. package/dist/chunk-V375NQ6C.js.map +0 -1
  58. package/dist/chunk-X64JII57.js.map +0 -1
  59. package/dist/chunk-YCRWYE6N.js.map +0 -1
package/dist/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/commands/validate.ts
4
- import { readdir, readFile as readFile2 } from "fs/promises";
4
+ import { readdir, readFile as readFile3 } from "fs/promises";
5
5
  import { join, extname } from "path";
6
6
 
7
7
  // src/parser/parser.ts
@@ -75,7 +75,7 @@ var PromptAssetSchema = z.object({
75
75
  id: z.string(),
76
76
  schema_version: z.number().int().positive().default(1),
77
77
  description: z.string().optional(),
78
- provider: z.enum(["openai", "anthropic", "google", "openrouter", "any"]).optional(),
78
+ provider: z.enum(["openai", "anthropic", "google", "gemini", "openrouter", "any"]).optional(),
79
79
  model: z.string().optional(),
80
80
  fallback_models: z.array(z.string()).optional(),
81
81
  reasoning: ReasoningSchema.optional(),
@@ -182,6 +182,45 @@ function extractVariables(template) {
182
182
  return [...vars];
183
183
  }
184
184
 
185
+ // src/composition/resolve-includes.ts
186
+ import { readFile as readFile2 } from "fs/promises";
187
+ import { resolve, dirname } from "path";
188
+ async function resolveIncludes(asset, basePath, visited = /* @__PURE__ */ new Set()) {
189
+ if (!asset.includes || asset.includes.length === 0) {
190
+ return asset;
191
+ }
192
+ const baseDir = dirname(basePath);
193
+ const resolvedPath = resolve(basePath);
194
+ if (visited.has(resolvedPath)) {
195
+ throw new Error(`Circular include detected: ${resolvedPath}`);
196
+ }
197
+ visited.add(resolvedPath);
198
+ let mergedSystemInstructions = "";
199
+ for (const includePath of asset.includes) {
200
+ const fullPath = resolve(baseDir, includePath);
201
+ if (visited.has(fullPath)) {
202
+ throw new Error(`Circular include detected: ${fullPath} (included from ${basePath})`);
203
+ }
204
+ const content = await readFile2(fullPath, "utf-8");
205
+ const { asset: includedAsset } = parsePrompt(content, fullPath);
206
+ const resolved = await resolveIncludes(includedAsset, fullPath, new Set(visited));
207
+ if (resolved.sections?.system_instructions) {
208
+ mergedSystemInstructions += resolved.sections.system_instructions + "\n\n";
209
+ }
210
+ }
211
+ const localSystem = asset.sections?.system_instructions ?? "";
212
+ const combinedSystem = (mergedSystemInstructions + localSystem).trim() || void 0;
213
+ return {
214
+ ...asset,
215
+ sections: {
216
+ ...asset.sections,
217
+ system_instructions: combinedSystem
218
+ },
219
+ // Drop includes from the resolved asset — they've been inlined
220
+ includes: void 0
221
+ };
222
+ }
223
+
185
224
  // src/validation/levenshtein.ts
186
225
  function levenshtein(a, b) {
187
226
  const m = a.length;
@@ -299,6 +338,24 @@ function validateAsset(asset, frontMatterKeys, filePath) {
299
338
  warnings
300
339
  };
301
340
  }
341
+ async function validateAssetWithIncludes(asset, filePath, frontMatterKeys) {
342
+ const result = validateAsset(asset, frontMatterKeys, filePath);
343
+ if (asset.includes && asset.includes.length > 0) {
344
+ try {
345
+ await resolveIncludes(asset, filePath);
346
+ } catch (err) {
347
+ const message = err instanceof Error ? err.message : String(err);
348
+ const isCircular = message.includes("Circular include");
349
+ result.errors.push({
350
+ code: isCircular ? "POK021" : "POK020",
351
+ message: isCircular ? `Circular include detected: ${message}` : `Include resolution failed: ${message}`,
352
+ filePath
353
+ });
354
+ result.valid = false;
355
+ }
356
+ }
357
+ return result;
358
+ }
302
359
  function findClosestMatch(input, candidates) {
303
360
  let best;
304
361
  let bestDist = Infinity;
@@ -342,9 +399,9 @@ async function validate(args) {
342
399
  let warnCount = 0;
343
400
  for (const file of files) {
344
401
  try {
345
- const content = await readFile2(file, "utf-8");
402
+ const content = await readFile3(file, "utf-8");
346
403
  const { asset, raw } = parsePrompt(content, file);
347
- const result = validateAsset(asset, Object.keys(raw.frontMatter), file);
404
+ const result = await validateAssetWithIncludes(asset, file, Object.keys(raw.frontMatter));
348
405
  if (result.errors.length > 0) {
349
406
  errorCount += result.errors.length;
350
407
  console.error(` \u2717 ${file}`);
@@ -386,8 +443,8 @@ async function collectPromptFiles(dir) {
386
443
  }
387
444
 
388
445
  // src/cli/commands/compile.ts
389
- import { readdir as readdir2, readFile as readFile3, writeFile, mkdir, rm } from "fs/promises";
390
- import { join as join2, extname as extname2, relative, dirname } from "path";
446
+ import { readdir as readdir2, readFile as readFile4, writeFile, mkdir, rm } from "fs/promises";
447
+ import { join as join2, extname as extname2, relative, dirname as dirname2 } from "path";
391
448
  var HELP2 = `
392
449
  promptopskit compile <sourceDir> <outputDir> [options]
393
450
 
@@ -434,12 +491,13 @@ async function compile(args) {
434
491
  const outExt = format === "esm" ? ".mjs" : ".json";
435
492
  const outPath = join2(outputDir, rel + outExt);
436
493
  try {
437
- const content = await readFile3(file, "utf-8");
438
- const { asset } = parsePrompt(content, file);
494
+ const content = await readFile4(file, "utf-8");
495
+ const { asset: parsed } = parsePrompt(content, file);
496
+ const asset = parsed.includes && parsed.includes.length > 0 ? await resolveIncludes(parsed, file) : parsed;
439
497
  if (dryRun) {
440
498
  console.log(` Would create: ${outPath}`);
441
499
  } else {
442
- await mkdir(dirname(outPath), { recursive: true });
500
+ await mkdir(dirname2(outPath), { recursive: true });
443
501
  if (format === "esm") {
444
502
  const esmContent = `export default ${JSON.stringify(asset, null, 2)};
445
503
  `;
@@ -486,7 +544,7 @@ async function collectPromptFiles2(dir) {
486
544
  }
487
545
 
488
546
  // src/cli/commands/render.ts
489
- import { readFile as readFile4 } from "fs/promises";
547
+ import { readFile as readFile5 } from "fs/promises";
490
548
  import { existsSync } from "fs";
491
549
 
492
550
  // src/overrides/apply-overrides.ts
@@ -549,25 +607,26 @@ async function render(args) {
549
607
  const jsonOutput = args.includes("--json");
550
608
  let variables = {};
551
609
  if (varsFile) {
552
- const varsContent = await readFile4(varsFile, "utf-8");
610
+ const varsContent = await readFile5(varsFile, "utf-8");
553
611
  variables = JSON.parse(varsContent);
554
612
  } else {
555
613
  const sidecarPath = file.replace(/\.md$/, ".test.yaml");
556
614
  if (existsSync(sidecarPath)) {
557
615
  const { default: yaml } = await import("gray-matter");
558
- const sidecarContent = await readFile4(sidecarPath, "utf-8");
559
- const parsed = yaml(`---
616
+ const sidecarContent = await readFile5(sidecarPath, "utf-8");
617
+ const parsed2 = yaml(`---
560
618
  ${sidecarContent}---
561
619
  `);
562
- const data = parsed.data;
620
+ const data = parsed2.data;
563
621
  if (data.cases?.[0]?.variables) {
564
622
  variables = data.cases[0].variables;
565
623
  }
566
624
  }
567
625
  }
568
- const content = await readFile4(file, "utf-8");
569
- const { asset } = parsePrompt(content, file);
570
- const overridden = applyOverrides(asset, {
626
+ const content = await readFile5(file, "utf-8");
627
+ const { asset: parsed } = parsePrompt(content, file);
628
+ const resolved = parsed.includes && parsed.includes.length > 0 ? await resolveIncludes(parsed, file) : parsed;
629
+ const overridden = applyOverrides(resolved, {
571
630
  environment: env,
572
631
  tier
573
632
  });
@@ -612,7 +671,7 @@ function getFlag2(args, flag) {
612
671
  }
613
672
 
614
673
  // src/cli/commands/inspect.ts
615
- import { readFile as readFile5 } from "fs/promises";
674
+ import { readFile as readFile6 } from "fs/promises";
616
675
  var HELP4 = `
617
676
  promptopskit inspect <file>
618
677
 
@@ -631,14 +690,15 @@ async function inspect(args) {
631
690
  console.error("Error: Please provide a prompt file to inspect.");
632
691
  process.exit(1);
633
692
  }
634
- const content = await readFile5(file, "utf-8");
635
- const { asset } = parsePrompt(content, file);
693
+ const content = await readFile6(file, "utf-8");
694
+ const { asset: parsed } = parsePrompt(content, file);
695
+ const asset = parsed.includes && parsed.includes.length > 0 ? await resolveIncludes(parsed, file) : parsed;
636
696
  console.log(JSON.stringify(asset, null, 2));
637
697
  }
638
698
 
639
699
  // src/cli/commands/init.ts
640
700
  import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
641
- import { join as join3, dirname as dirname2 } from "path";
701
+ import { join as join3, dirname as dirname3 } from "path";
642
702
  import { existsSync as existsSync2, readFileSync } from "fs";
643
703
  var HELP5 = `
644
704
  promptopskit init [dir]
@@ -704,7 +764,7 @@ async function init(args) {
704
764
  skipped++;
705
765
  continue;
706
766
  }
707
- await mkdir2(dirname2(file.path), { recursive: true });
767
+ await mkdir2(dirname3(file.path), { recursive: true });
708
768
  await writeFile2(file.path, file.content, "utf-8");
709
769
  console.log(` \u2713 ${file.path}`);
710
770
  created++;
@@ -724,8 +784,371 @@ async function init(args) {
724
784
  }
725
785
  }
726
786
 
727
- // src/cli/index.ts
787
+ // src/cli/commands/skill.ts
788
+ import { writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
789
+ import { dirname as dirname4 } from "path";
790
+ import { existsSync as existsSync3 } from "fs";
728
791
  var HELP6 = `
792
+ promptopskit skill [--target <target>] [--force]
793
+
794
+ Deploy AI agent instructions so coding assistants know how to
795
+ create and manage prompts using promptopskit.
796
+
797
+ Targets:
798
+ copilot (default) .github/instructions/promptopskit.instructions.md
799
+ cursor .cursor/rules/promptopskit.mdc
800
+ generic .ai/promptopskit-skill.md
801
+
802
+ Options:
803
+ --target, -t Target AI tool (copilot, cursor, generic)
804
+ --force, -f Overwrite existing file
805
+ --help, -h Show this help
806
+ `.trim();
807
+ var TARGETS = {
808
+ copilot: {
809
+ path: ".github/instructions/promptopskit.instructions.md",
810
+ wrap: (content) => `---
811
+ applyTo: "**"
812
+ ---
813
+
814
+ ${content}`
815
+ },
816
+ cursor: {
817
+ path: ".cursor/rules/promptopskit.mdc",
818
+ wrap: (content) => `---
819
+ description: How to create and manage prompts using promptopskit
820
+ globs: "**"
821
+ alwaysApply: true
822
+ ---
823
+
824
+ ${content}`
825
+ },
826
+ generic: {
827
+ path: ".ai/promptopskit-skill.md",
828
+ wrap: (content) => content
829
+ }
830
+ };
831
+ async function skill(args) {
832
+ if (args.includes("--help") || args.includes("-h")) {
833
+ console.log(HELP6);
834
+ return;
835
+ }
836
+ const force = args.includes("--force") || args.includes("-f");
837
+ let target = "copilot";
838
+ const targetIdx = args.findIndex((a) => a === "--target" || a === "-t");
839
+ if (targetIdx !== -1 && args[targetIdx + 1]) {
840
+ target = args[targetIdx + 1];
841
+ }
842
+ const config = TARGETS[target];
843
+ if (!config) {
844
+ console.error(`Unknown target: ${target}`);
845
+ console.error(`Valid targets: ${Object.keys(TARGETS).join(", ")}`);
846
+ process.exit(1);
847
+ }
848
+ const filePath = config.path;
849
+ if (existsSync3(filePath) && !force) {
850
+ console.log(` skip ${filePath} (already exists, use --force to overwrite)`);
851
+ return;
852
+ }
853
+ const content = config.wrap(SKILL_CONTENT);
854
+ await mkdir3(dirname4(filePath), { recursive: true });
855
+ await writeFile3(filePath, content, "utf-8");
856
+ console.log(` \u2713 ${filePath}`);
857
+ console.log();
858
+ console.log(`AI agents will now understand how to create and manage prompts with promptopskit.`);
859
+ }
860
+ var SKILL_CONTENT = `# promptopskit \u2014 Prompt Engineering Skill
861
+
862
+ This project uses **promptopskit** to manage LLM prompts as code.
863
+ Prompts live in markdown files with YAML front matter, are validated against
864
+ a schema, and render into provider-specific request bodies (OpenAI, Anthropic,
865
+ Gemini, OpenRouter). Follow these instructions when creating or editing prompts.
866
+
867
+ ---
868
+
869
+ ## Prompt file format
870
+
871
+ Every prompt is a \`.md\` file with two parts:
872
+
873
+ 1. **YAML front matter** \u2014 model settings, provider config, variables, overrides
874
+ 2. **Markdown body** \u2014 sections separated by H1 headings
875
+
876
+ ### Minimal example
877
+
878
+ \`\`\`markdown
879
+ ---
880
+ id: greeting
881
+ schema_version: 1
882
+ provider: openai
883
+ model: gpt-5.4
884
+ context:
885
+ inputs:
886
+ - name
887
+ ---
888
+
889
+ # System instructions
890
+
891
+ You are a helpful assistant.
892
+
893
+ # Prompt template
894
+
895
+ Hello {{ name }}, how can I help you?
896
+ \`\`\`
897
+
898
+ ---
899
+
900
+ ## Front matter reference
901
+
902
+ | Field | Type | Required | Description |
903
+ |-------|------|----------|-------------|
904
+ | \`id\` | string | **yes** | Unique identifier for the prompt |
905
+ | \`schema_version\` | number | yes | Always \`1\` |
906
+ | \`description\` | string | no | Human-readable description |
907
+ | \`provider\` | enum | no | \`openai\`, \`anthropic\`, \`google\`, \`gemini\`, \`openrouter\`, or \`any\` |
908
+ | \`model\` | string | no | Model identifier (e.g. \`gpt-5.4\`, \`claude-sonnet-4-20250514\`) |
909
+ | \`fallback_models\` | string[] | no | Ordered fallback model list |
910
+ | \`reasoning\` | object | no | \`{ effort: low|medium|high, budget_tokens: number }\` |
911
+ | \`sampling\` | object | no | \`{ temperature, top_p, frequency_penalty, presence_penalty, stop, max_output_tokens }\` |
912
+ | \`response\` | object | no | \`{ format: text|json|markdown, stream: boolean }\` |
913
+ | \`tools\` | array | no | Tool names (strings) or inline definitions with \`{ name, description, input_schema }\` |
914
+ | \`mcp\` | object | no | \`{ servers: [string | { name, config }] }\` |
915
+ | \`context.inputs\` | string[] | no | Declared variable names used in templates |
916
+ | \`context.history\` | object | no | \`{ max_items: number }\` |
917
+ | \`includes\` | string[] | no | Relative paths to other prompt files to include |
918
+ | \`environments\` | object | no | Per-environment overrides (see Overrides) |
919
+ | \`tiers\` | object | no | Per-tier overrides (see Overrides) |
920
+ | \`metadata\` | object | no | \`{ owner, tags, review_required, stable }\` |
921
+
922
+ ---
923
+
924
+ ## Sections (markdown body)
925
+
926
+ Use H1 headings to define sections. The parser recognizes these headings
927
+ (case-insensitive):
928
+
929
+ | Heading | Maps to | Purpose |
930
+ |---------|---------|---------|
931
+ | \`# System instructions\` | \`system_instructions\` | System/developer message |
932
+ | \`# Prompt template\` | \`prompt_template\` | User message template |
933
+ | \`# Notes\` | \`notes\` | Documentation only \u2014 not sent to the model |
934
+
935
+ If the body has **no H1 headings**, the entire body becomes the \`prompt_template\`.
936
+
937
+ ---
938
+
939
+ ## Variable interpolation
940
+
941
+ Use \`{{ variable_name }}\` syntax in system instructions and prompt template
942
+ sections. Variables are replaced at render time.
943
+
944
+ Rules:
945
+ - Declare all variables in \`context.inputs\` \u2014 validation warns on undeclared usage
946
+ - Escape literal braces with \`\\\\{{\` and \`\\\\}}\`
947
+ - In strict mode, missing variables throw an error
948
+ - In permissive mode, unresolved placeholders are left intact
949
+
950
+ ---
951
+
952
+ ## Includes (composition)
953
+
954
+ Compose prompts from shared fragments:
955
+
956
+ \`\`\`yaml
957
+ includes:
958
+ - ./shared/tone.md
959
+ - ./shared/safety.md
960
+ \`\`\`
961
+
962
+ Included files are parsed and their \`system_instructions\` are **prepended**
963
+ before the including file's own system instructions. Includes resolve
964
+ recursively. Circular includes are detected and rejected.
965
+
966
+ ---
967
+
968
+ ## Environment & tier overrides
969
+
970
+ Override model settings per environment or tier:
971
+
972
+ \`\`\`yaml
973
+ environments:
974
+ development:
975
+ model: gpt-4.1-mini
976
+ sampling:
977
+ temperature: 0.9
978
+ production:
979
+ model: gpt-5.4
980
+ sampling:
981
+ temperature: 0.3
982
+
983
+ tiers:
984
+ free:
985
+ model: gpt-4.1-mini
986
+ sampling:
987
+ max_output_tokens: 500
988
+ premium:
989
+ model: gpt-5.4
990
+ \`\`\`
991
+
992
+ Overridable fields: \`model\`, \`fallback_models\`, \`reasoning\`, \`sampling\`,
993
+ \`response\`, \`tools\`.
994
+
995
+ Override application order: **base \u2192 environment \u2192 tier \u2192 runtime**.
996
+
997
+ ---
998
+
999
+ ## Test sidecars
1000
+
1001
+ Create a \`.test.yaml\` file alongside a prompt to define test cases:
1002
+
1003
+ \`\`\`yaml
1004
+ # greeting.test.yaml
1005
+ cases:
1006
+ - name: basic
1007
+ variables:
1008
+ name: "World"
1009
+ - name: formal
1010
+ variables:
1011
+ name: "Dr. Smith"
1012
+ \`\`\`
1013
+
1014
+ ---
1015
+
1016
+ ## Using the library (TypeScript / JavaScript)
1017
+
1018
+ ### Quick start
1019
+
1020
+ \`\`\`typescript
1021
+ import { createPromptOpsKit } from 'promptopskit';
1022
+
1023
+ const kit = createPromptOpsKit({ sourceDir: './prompts' });
1024
+
1025
+ // Load \u2192 resolve includes \u2192 apply overrides \u2192 render
1026
+ const request = await kit.renderPrompt('greeting', {
1027
+ variables: { name: 'Alice' },
1028
+ environment: 'production',
1029
+ });
1030
+
1031
+ // request.body is ready for the provider's API
1032
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
1033
+ method: 'POST',
1034
+ headers: {
1035
+ 'Content-Type': 'application/json',
1036
+ Authorization: \`Bearer \${apiKey}\`,
1037
+ },
1038
+ body: JSON.stringify(request.body),
1039
+ });
1040
+ \`\`\`
1041
+
1042
+ ### Step-by-step API
1043
+
1044
+ \`\`\`typescript
1045
+ import {
1046
+ parsePrompt,
1047
+ resolveIncludes,
1048
+ applyOverrides,
1049
+ getAdapter,
1050
+ } from 'promptopskit';
1051
+ import { readFileSync } from 'fs';
1052
+
1053
+ // 1. Parse a prompt file
1054
+ const source = readFileSync('./prompts/greeting.md', 'utf-8');
1055
+ const asset = parsePrompt(source, 'greeting.md');
1056
+
1057
+ // 2. Resolve includes
1058
+ const resolved = await resolveIncludes(asset, './prompts');
1059
+
1060
+ // 3. Apply overrides
1061
+ const configured = applyOverrides(resolved, {
1062
+ environment: 'production',
1063
+ tier: 'premium',
1064
+ });
1065
+
1066
+ // 4. Get provider adapter and render
1067
+ const adapter = getAdapter(configured.provider ?? 'openai');
1068
+ const request = adapter.render(configured, {
1069
+ variables: { name: 'Alice' },
1070
+ history: [
1071
+ { role: 'user', content: 'Previous message' },
1072
+ { role: 'assistant', content: 'Previous response' },
1073
+ ],
1074
+ });
1075
+ \`\`\`
1076
+
1077
+ ### Available provider adapters
1078
+
1079
+ | Provider | Import path | Provider request format |
1080
+ |----------|------------|----------------------|
1081
+ | OpenAI | \`promptopskit\` or \`promptopskit/openai\` | Chat Completions API |
1082
+ | Anthropic | \`promptopskit/anthropic\` | Messages API |
1083
+ | Gemini | \`promptopskit/gemini\` | GenerateContent API |
1084
+ | OpenRouter | \`promptopskit/openrouter\` | OpenAI-compatible + extras |
1085
+
1086
+ ### Validation
1087
+
1088
+ \`\`\`typescript
1089
+ import { validateAsset, parsePrompt } from 'promptopskit';
1090
+
1091
+ const asset = parsePrompt(source);
1092
+ const result = validateAsset(asset);
1093
+
1094
+ if (!result.valid) {
1095
+ console.error(result.errors); // Error codes: POK001-POK021
1096
+ }
1097
+ \`\`\`
1098
+
1099
+ ### Testing helpers
1100
+
1101
+ \`\`\`typescript
1102
+ import { createMockAsset, parseTestPrompt } from 'promptopskit/testing';
1103
+
1104
+ // Create a mock asset for unit tests
1105
+ const mock = createMockAsset({ model: 'gpt-4.1-mini' });
1106
+
1107
+ // Parse an inline prompt string for tests
1108
+ const asset = parseTestPrompt(\`
1109
+ ---
1110
+ id: test
1111
+ schema_version: 1
1112
+ provider: openai
1113
+ model: gpt-5.4
1114
+ ---
1115
+
1116
+ # Prompt template
1117
+
1118
+ Hello {{ name }}
1119
+ \`);
1120
+ \`\`\`
1121
+
1122
+ ---
1123
+
1124
+ ## CLI commands
1125
+
1126
+ | Command | Description |
1127
+ |---------|-------------|
1128
+ | \`promptopskit init [dir]\` | Scaffold a prompts directory with starter files |
1129
+ | \`promptopskit validate <dir>\` | Validate all prompt files in a directory |
1130
+ | \`promptopskit compile <src> <out>\` | Compile .md prompts to JSON artifacts |
1131
+ | \`promptopskit render <file> [--set key=value]\` | Render a prompt preview |
1132
+ | \`promptopskit inspect <file>\` | Print the normalized prompt asset |
1133
+
1134
+ ---
1135
+
1136
+ ## Conventions to follow
1137
+
1138
+ 1. **One prompt per file** \u2014 each \`.md\` file is a single prompt asset
1139
+ 2. **Always set \`id\` and \`schema_version: 1\`** in front matter
1140
+ 3. **Declare all variables** in \`context.inputs\` that appear in templates
1141
+ 4. **Use includes** for shared system instructions (tone, safety, formatting)
1142
+ 5. **Keep prompt templates focused** \u2014 compose behavior via includes, not duplication
1143
+ 6. **Use environment overrides** for dev/staging/prod model differences
1144
+ 7. **Add test sidecars** (\`.test.yaml\`) for critical prompts
1145
+ 8. **Run \`promptopskit validate\`** before committing changes
1146
+ 9. **Variable names** should be \`snake_case\`
1147
+ 10. **Prompt file names** should be \`kebab-case.md\`
1148
+ `.trimEnd();
1149
+
1150
+ // src/cli/index.ts
1151
+ var HELP7 = `
729
1152
  promptopskit \u2014 Manage prompts, system instructions, tools, and model settings as code
730
1153
 
731
1154
  Usage:
@@ -737,6 +1160,7 @@ Commands:
737
1160
  compile <src> <out> [options] Compile .md prompts to JSON/ESM artifacts
738
1161
  render <file> [options] Render a prompt preview
739
1162
  inspect <file> Print normalized prompt asset
1163
+ skill [options] Deploy AI agent instructions into your project
740
1164
 
741
1165
  Options:
742
1166
  --help, -h Show this help message
@@ -748,7 +1172,7 @@ async function main() {
748
1172
  const args = process.argv.slice(2);
749
1173
  const command = args[0];
750
1174
  if (!command || command === "--help" || command === "-h") {
751
- console.log(HELP6);
1175
+ console.log(HELP7);
752
1176
  process.exit(0);
753
1177
  }
754
1178
  if (command === "--version" || command === "-v") {
@@ -772,9 +1196,12 @@ async function main() {
772
1196
  case "inspect":
773
1197
  await inspect(commandArgs);
774
1198
  break;
1199
+ case "skill":
1200
+ await skill(commandArgs);
1201
+ break;
775
1202
  default:
776
1203
  console.error(`Unknown command: ${command}`);
777
- console.log(HELP6);
1204
+ console.log(HELP7);
778
1205
  process.exit(1);
779
1206
  }
780
1207
  }