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.
- package/LICENSE +211 -211
- package/README.md +335 -309
- package/dist/{chunk-FSOJVXC4.js → chunk-5BI5FP5L.js} +2 -2
- package/dist/chunk-5BI5FP5L.js.map +1 -0
- package/dist/{chunk-HEFOFQ2K.js → chunk-6KGBPHSY.js} +1 -1
- package/dist/chunk-6KGBPHSY.js.map +1 -0
- package/dist/{chunk-X64JII57.js → chunk-NIZENC7D.js} +2 -2
- package/dist/chunk-NIZENC7D.js.map +1 -0
- package/dist/{chunk-YCRWYE6N.js → chunk-PU3UPUND.js} +2 -2
- package/dist/chunk-PU3UPUND.js.map +1 -0
- package/dist/{chunk-V375NQ6C.js → chunk-UYTUGV7Z.js} +2 -2
- package/dist/chunk-UYTUGV7Z.js.map +1 -0
- package/dist/{chunk-B5UFGNDV.js → chunk-VYVEVJC3.js} +2 -2
- package/dist/chunk-VYVEVJC3.js.map +1 -0
- package/dist/cli/index.js +452 -25
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +963 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +184 -0
- package/dist/index.d.ts +10 -5
- package/dist/index.js +32 -7
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.cjs +151 -0
- package/dist/providers/anthropic.cjs.map +1 -0
- package/dist/providers/anthropic.d.cts +11 -0
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +2 -2
- package/dist/providers/gemini.cjs +155 -0
- package/dist/providers/gemini.cjs.map +1 -0
- package/dist/providers/gemini.d.cts +11 -0
- package/dist/providers/gemini.d.ts +2 -2
- package/dist/providers/gemini.js +2 -2
- package/dist/providers/openai.cjs +146 -0
- package/dist/providers/openai.cjs.map +1 -0
- package/dist/providers/openai.d.cts +11 -0
- package/dist/providers/openai.d.ts +2 -2
- package/dist/providers/openai.js +2 -2
- package/dist/providers/openrouter.cjs +161 -0
- package/dist/providers/openrouter.cjs.map +1 -0
- package/dist/providers/openrouter.d.cts +13 -0
- package/dist/providers/openrouter.d.ts +2 -2
- package/dist/providers/openrouter.js +3 -3
- package/dist/{schema-DHRI5Mzl.d.ts → schema-C6smABrt.d.cts} +3 -3
- package/dist/schema-C6smABrt.d.ts +695 -0
- package/dist/testing.cjs +228 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +17 -0
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/{types-D9hquVja.d.ts → types-D7lW5IYT.d.ts} +1 -1
- package/dist/types-S_c-ZEfK.d.cts +40 -0
- package/package.json +116 -79
- package/dist/chunk-B5UFGNDV.js.map +0 -1
- package/dist/chunk-FSOJVXC4.js.map +0 -1
- package/dist/chunk-HEFOFQ2K.js.map +0 -1
- package/dist/chunk-V375NQ6C.js.map +0 -1
- package/dist/chunk-X64JII57.js.map +0 -1
- 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
|
|
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
|
|
402
|
+
const content = await readFile3(file, "utf-8");
|
|
346
403
|
const { asset, raw } = parsePrompt(content, file);
|
|
347
|
-
const result =
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
559
|
-
const
|
|
616
|
+
const sidecarContent = await readFile5(sidecarPath, "utf-8");
|
|
617
|
+
const parsed2 = yaml(`---
|
|
560
618
|
${sidecarContent}---
|
|
561
619
|
`);
|
|
562
|
-
const 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
|
|
569
|
-
const { asset } = parsePrompt(content, file);
|
|
570
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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/
|
|
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(
|
|
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(
|
|
1204
|
+
console.log(HELP7);
|
|
778
1205
|
process.exit(1);
|
|
779
1206
|
}
|
|
780
1207
|
}
|