promptopskit 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -15
- package/dist/{chunk-X64JII57.js → chunk-HMYPMHTG.js} +91 -4
- package/dist/chunk-HMYPMHTG.js.map +1 -0
- package/dist/cli/index.js +593 -35
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +1049 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +191 -0
- package/dist/index.d.ts +18 -6
- package/dist/index.js +28 -4
- 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/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/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/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/{schema-DHRI5Mzl.d.ts → schema-C6smABrt.d.cts} +3 -3
- package/dist/schema-C6smABrt.d.ts +695 -0
- package/dist/testing.cjs +236 -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/{types-D9hquVja.d.ts → types-D7lW5IYT.d.ts} +1 -1
- package/dist/types-S_c-ZEfK.d.cts +40 -0
- package/package.json +54 -16
- package/dist/chunk-X64JII57.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/commands/validate.ts
|
|
4
|
-
import { readdir
|
|
5
|
-
import { join, extname } from "path";
|
|
4
|
+
import { readdir } from "fs/promises";
|
|
5
|
+
import { join as join2, extname } from "path";
|
|
6
6
|
|
|
7
7
|
// src/parser/parser.ts
|
|
8
8
|
import matter from "gray-matter";
|
|
@@ -71,11 +71,17 @@ var SectionsSchema = z.object({
|
|
|
71
71
|
prompt_template: z.string().optional(),
|
|
72
72
|
notes: z.string().optional()
|
|
73
73
|
});
|
|
74
|
+
var PromptDefaultsSchema = z.object({
|
|
75
|
+
metadata: MetadataSchema.optional(),
|
|
76
|
+
sections: z.object({
|
|
77
|
+
system_instructions: z.string().optional()
|
|
78
|
+
}).optional()
|
|
79
|
+
});
|
|
74
80
|
var PromptAssetSchema = z.object({
|
|
75
81
|
id: z.string(),
|
|
76
82
|
schema_version: z.number().int().positive().default(1),
|
|
77
83
|
description: z.string().optional(),
|
|
78
|
-
provider: z.enum(["openai", "anthropic", "google", "openrouter", "any"]).optional(),
|
|
84
|
+
provider: z.enum(["openai", "anthropic", "google", "gemini", "openrouter", "any"]).optional(),
|
|
79
85
|
model: z.string().optional(),
|
|
80
86
|
fallback_models: z.array(z.string()).optional(),
|
|
81
87
|
reasoning: ReasoningSchema.optional(),
|
|
@@ -152,6 +158,91 @@ function parsePrompt(content, filePath) {
|
|
|
152
158
|
|
|
153
159
|
// src/parser/loader.ts
|
|
154
160
|
import { readFile } from "fs/promises";
|
|
161
|
+
import { dirname, join, resolve } from "path";
|
|
162
|
+
import matter2 from "gray-matter";
|
|
163
|
+
var DEFAULTS_FILE_NAME = "defaults.md";
|
|
164
|
+
async function loadPromptFile(filePath, options = {}) {
|
|
165
|
+
const content = await readFile(filePath, "utf-8");
|
|
166
|
+
const parsed = parsePrompt(content, filePath);
|
|
167
|
+
const root = options.defaultsRoot ?? dirname(filePath);
|
|
168
|
+
const defaults = await loadDefaultsForPath(filePath, root);
|
|
169
|
+
const asset = applyDefaults(parsed.asset, defaults);
|
|
170
|
+
return {
|
|
171
|
+
...parsed,
|
|
172
|
+
asset
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
async function loadDefaultsForPath(filePath, defaultsRoot) {
|
|
176
|
+
const directories = getDirectoriesToCheck(filePath, defaultsRoot);
|
|
177
|
+
let merged = {};
|
|
178
|
+
for (const dir of directories) {
|
|
179
|
+
const defaultsPath = join(dir, DEFAULTS_FILE_NAME);
|
|
180
|
+
try {
|
|
181
|
+
const defaultsContent = await readFile(defaultsPath, "utf-8");
|
|
182
|
+
const defaults = parseDefaults(defaultsContent);
|
|
183
|
+
merged = mergeDefaults(merged, defaults);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (error.code !== "ENOENT") {
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return merged;
|
|
191
|
+
}
|
|
192
|
+
function getDirectoriesToCheck(filePath, defaultsRoot) {
|
|
193
|
+
const dirs = [];
|
|
194
|
+
let current = resolve(dirname(filePath));
|
|
195
|
+
const boundary = defaultsRoot ? resolve(defaultsRoot) : void 0;
|
|
196
|
+
while (true) {
|
|
197
|
+
dirs.unshift(current);
|
|
198
|
+
if (boundary && current === boundary || current === dirname(current)) {
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
current = dirname(current);
|
|
202
|
+
}
|
|
203
|
+
return dirs;
|
|
204
|
+
}
|
|
205
|
+
function parseDefaults(content) {
|
|
206
|
+
const { data: frontMatter, content: body } = matter2(content);
|
|
207
|
+
const sections = extractSections(body);
|
|
208
|
+
return PromptDefaultsSchema.parse({
|
|
209
|
+
...frontMatter,
|
|
210
|
+
sections: {
|
|
211
|
+
system_instructions: sections.system_instructions
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function mergeDefaults(base, local) {
|
|
216
|
+
return {
|
|
217
|
+
metadata: {
|
|
218
|
+
...base.metadata ?? {},
|
|
219
|
+
...local.metadata ?? {}
|
|
220
|
+
},
|
|
221
|
+
sections: {
|
|
222
|
+
...base.sections ?? {},
|
|
223
|
+
...local.sections ?? {}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function applyDefaults(asset, defaults) {
|
|
228
|
+
const hasDefaultMetadata = defaults.metadata && Object.keys(defaults.metadata).length > 0;
|
|
229
|
+
const hasDefaultSystem = !!defaults.sections?.system_instructions;
|
|
230
|
+
if (!hasDefaultMetadata && !hasDefaultSystem) {
|
|
231
|
+
return asset;
|
|
232
|
+
}
|
|
233
|
+
const mergedMetadata = {
|
|
234
|
+
...defaults.metadata ?? {},
|
|
235
|
+
...asset.metadata ?? {}
|
|
236
|
+
};
|
|
237
|
+
const metadata = Object.keys(mergedMetadata).length > 0 ? mergedMetadata : void 0;
|
|
238
|
+
const systemInstructions = asset.sections?.system_instructions ?? defaults.sections?.system_instructions;
|
|
239
|
+
const sections = asset.sections ? { ...asset.sections, system_instructions: systemInstructions } : systemInstructions ? { system_instructions: systemInstructions } : void 0;
|
|
240
|
+
return {
|
|
241
|
+
...asset,
|
|
242
|
+
metadata,
|
|
243
|
+
sections
|
|
244
|
+
};
|
|
245
|
+
}
|
|
155
246
|
|
|
156
247
|
// src/renderer/interpolate.ts
|
|
157
248
|
var VARIABLE_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
|
|
@@ -182,6 +273,45 @@ function extractVariables(template) {
|
|
|
182
273
|
return [...vars];
|
|
183
274
|
}
|
|
184
275
|
|
|
276
|
+
// src/composition/resolve-includes.ts
|
|
277
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
278
|
+
import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
279
|
+
async function resolveIncludes(asset, basePath, visited = /* @__PURE__ */ new Set()) {
|
|
280
|
+
if (!asset.includes || asset.includes.length === 0) {
|
|
281
|
+
return asset;
|
|
282
|
+
}
|
|
283
|
+
const baseDir = dirname2(basePath);
|
|
284
|
+
const resolvedPath = resolve2(basePath);
|
|
285
|
+
if (visited.has(resolvedPath)) {
|
|
286
|
+
throw new Error(`Circular include detected: ${resolvedPath}`);
|
|
287
|
+
}
|
|
288
|
+
visited.add(resolvedPath);
|
|
289
|
+
let mergedSystemInstructions = "";
|
|
290
|
+
for (const includePath of asset.includes) {
|
|
291
|
+
const fullPath = resolve2(baseDir, includePath);
|
|
292
|
+
if (visited.has(fullPath)) {
|
|
293
|
+
throw new Error(`Circular include detected: ${fullPath} (included from ${basePath})`);
|
|
294
|
+
}
|
|
295
|
+
const content = await readFile2(fullPath, "utf-8");
|
|
296
|
+
const { asset: includedAsset } = parsePrompt(content, fullPath);
|
|
297
|
+
const resolved = await resolveIncludes(includedAsset, fullPath, new Set(visited));
|
|
298
|
+
if (resolved.sections?.system_instructions) {
|
|
299
|
+
mergedSystemInstructions += resolved.sections.system_instructions + "\n\n";
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const localSystem = asset.sections?.system_instructions ?? "";
|
|
303
|
+
const combinedSystem = (mergedSystemInstructions + localSystem).trim() || void 0;
|
|
304
|
+
return {
|
|
305
|
+
...asset,
|
|
306
|
+
sections: {
|
|
307
|
+
...asset.sections,
|
|
308
|
+
system_instructions: combinedSystem
|
|
309
|
+
},
|
|
310
|
+
// Drop includes from the resolved asset — they've been inlined
|
|
311
|
+
includes: void 0
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
185
315
|
// src/validation/levenshtein.ts
|
|
186
316
|
function levenshtein(a, b) {
|
|
187
317
|
const m = a.length;
|
|
@@ -299,6 +429,24 @@ function validateAsset(asset, frontMatterKeys, filePath) {
|
|
|
299
429
|
warnings
|
|
300
430
|
};
|
|
301
431
|
}
|
|
432
|
+
async function validateAssetWithIncludes(asset, filePath, frontMatterKeys) {
|
|
433
|
+
const result = validateAsset(asset, frontMatterKeys, filePath);
|
|
434
|
+
if (asset.includes && asset.includes.length > 0) {
|
|
435
|
+
try {
|
|
436
|
+
await resolveIncludes(asset, filePath);
|
|
437
|
+
} catch (err) {
|
|
438
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
439
|
+
const isCircular = message.includes("Circular include");
|
|
440
|
+
result.errors.push({
|
|
441
|
+
code: isCircular ? "POK021" : "POK020",
|
|
442
|
+
message: isCircular ? `Circular include detected: ${message}` : `Include resolution failed: ${message}`,
|
|
443
|
+
filePath
|
|
444
|
+
});
|
|
445
|
+
result.valid = false;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return result;
|
|
449
|
+
}
|
|
302
450
|
function findClosestMatch(input, candidates) {
|
|
303
451
|
let best;
|
|
304
452
|
let bestDist = Infinity;
|
|
@@ -342,9 +490,8 @@ async function validate(args) {
|
|
|
342
490
|
let warnCount = 0;
|
|
343
491
|
for (const file of files) {
|
|
344
492
|
try {
|
|
345
|
-
const
|
|
346
|
-
const
|
|
347
|
-
const result = validateAsset(asset, Object.keys(raw.frontMatter), file);
|
|
493
|
+
const { asset, raw } = await loadPromptFile(file, { defaultsRoot: dir });
|
|
494
|
+
const result = await validateAssetWithIncludes(asset, file, Object.keys(raw.frontMatter));
|
|
348
495
|
if (result.errors.length > 0) {
|
|
349
496
|
errorCount += result.errors.length;
|
|
350
497
|
console.error(` \u2717 ${file}`);
|
|
@@ -378,16 +525,16 @@ async function collectPromptFiles(dir) {
|
|
|
378
525
|
const results = [];
|
|
379
526
|
const entries = await readdir(dir, { withFileTypes: true, recursive: true });
|
|
380
527
|
for (const entry of entries) {
|
|
381
|
-
if (entry.isFile() && extname(entry.name) === ".md" && !entry.name.endsWith(".test.md")) {
|
|
382
|
-
results.push(
|
|
528
|
+
if (entry.isFile() && extname(entry.name) === ".md" && !entry.name.endsWith(".test.md") && entry.name !== "defaults.md") {
|
|
529
|
+
results.push(join2(entry.parentPath ?? dir, entry.name));
|
|
383
530
|
}
|
|
384
531
|
}
|
|
385
532
|
return results.sort();
|
|
386
533
|
}
|
|
387
534
|
|
|
388
535
|
// src/cli/commands/compile.ts
|
|
389
|
-
import { readdir as readdir2,
|
|
390
|
-
import { join as
|
|
536
|
+
import { readdir as readdir2, writeFile, mkdir, rm } from "fs/promises";
|
|
537
|
+
import { join as join3, extname as extname2, relative, dirname as dirname3 } from "path";
|
|
391
538
|
var HELP2 = `
|
|
392
539
|
promptopskit compile <sourceDir> <outputDir> [options]
|
|
393
540
|
|
|
@@ -432,14 +579,14 @@ async function compile(args) {
|
|
|
432
579
|
for (const file of files) {
|
|
433
580
|
const rel = relative(sourceDir, file).replace(/\.md$/, "");
|
|
434
581
|
const outExt = format === "esm" ? ".mjs" : ".json";
|
|
435
|
-
const outPath =
|
|
582
|
+
const outPath = join3(outputDir, rel + outExt);
|
|
436
583
|
try {
|
|
437
|
-
const
|
|
438
|
-
const
|
|
584
|
+
const { asset: parsed } = await loadPromptFile(file, { defaultsRoot: sourceDir });
|
|
585
|
+
const asset = parsed.includes && parsed.includes.length > 0 ? await resolveIncludes(parsed, file) : parsed;
|
|
439
586
|
if (dryRun) {
|
|
440
587
|
console.log(` Would create: ${outPath}`);
|
|
441
588
|
} else {
|
|
442
|
-
await mkdir(
|
|
589
|
+
await mkdir(dirname3(outPath), { recursive: true });
|
|
443
590
|
if (format === "esm") {
|
|
444
591
|
const esmContent = `export default ${JSON.stringify(asset, null, 2)};
|
|
445
592
|
`;
|
|
@@ -478,15 +625,15 @@ async function collectPromptFiles2(dir) {
|
|
|
478
625
|
const results = [];
|
|
479
626
|
const entries = await readdir2(dir, { withFileTypes: true, recursive: true });
|
|
480
627
|
for (const entry of entries) {
|
|
481
|
-
if (entry.isFile() && extname2(entry.name) === ".md" && !entry.name.endsWith(".test.md")) {
|
|
482
|
-
results.push(
|
|
628
|
+
if (entry.isFile() && extname2(entry.name) === ".md" && !entry.name.endsWith(".test.md") && entry.name !== "defaults.md") {
|
|
629
|
+
results.push(join3(entry.parentPath ?? dir, entry.name));
|
|
483
630
|
}
|
|
484
631
|
}
|
|
485
632
|
return results.sort();
|
|
486
633
|
}
|
|
487
634
|
|
|
488
635
|
// src/cli/commands/render.ts
|
|
489
|
-
import { readFile as
|
|
636
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
490
637
|
import { existsSync } from "fs";
|
|
491
638
|
|
|
492
639
|
// src/overrides/apply-overrides.ts
|
|
@@ -549,25 +696,25 @@ async function render(args) {
|
|
|
549
696
|
const jsonOutput = args.includes("--json");
|
|
550
697
|
let variables = {};
|
|
551
698
|
if (varsFile) {
|
|
552
|
-
const varsContent = await
|
|
699
|
+
const varsContent = await readFile3(varsFile, "utf-8");
|
|
553
700
|
variables = JSON.parse(varsContent);
|
|
554
701
|
} else {
|
|
555
702
|
const sidecarPath = file.replace(/\.md$/, ".test.yaml");
|
|
556
703
|
if (existsSync(sidecarPath)) {
|
|
557
704
|
const { default: yaml } = await import("gray-matter");
|
|
558
|
-
const sidecarContent = await
|
|
559
|
-
const
|
|
705
|
+
const sidecarContent = await readFile3(sidecarPath, "utf-8");
|
|
706
|
+
const parsed2 = yaml(`---
|
|
560
707
|
${sidecarContent}---
|
|
561
708
|
`);
|
|
562
|
-
const data =
|
|
709
|
+
const data = parsed2.data;
|
|
563
710
|
if (data.cases?.[0]?.variables) {
|
|
564
711
|
variables = data.cases[0].variables;
|
|
565
712
|
}
|
|
566
713
|
}
|
|
567
714
|
}
|
|
568
|
-
const
|
|
569
|
-
const
|
|
570
|
-
const overridden = applyOverrides(
|
|
715
|
+
const { asset: parsed } = await loadPromptFile(file);
|
|
716
|
+
const resolved = parsed.includes && parsed.includes.length > 0 ? await resolveIncludes(parsed, file) : parsed;
|
|
717
|
+
const overridden = applyOverrides(resolved, {
|
|
571
718
|
environment: env,
|
|
572
719
|
tier
|
|
573
720
|
});
|
|
@@ -612,7 +759,6 @@ function getFlag2(args, flag) {
|
|
|
612
759
|
}
|
|
613
760
|
|
|
614
761
|
// src/cli/commands/inspect.ts
|
|
615
|
-
import { readFile as readFile5 } from "fs/promises";
|
|
616
762
|
var HELP4 = `
|
|
617
763
|
promptopskit inspect <file>
|
|
618
764
|
|
|
@@ -631,14 +777,14 @@ async function inspect(args) {
|
|
|
631
777
|
console.error("Error: Please provide a prompt file to inspect.");
|
|
632
778
|
process.exit(1);
|
|
633
779
|
}
|
|
634
|
-
const
|
|
635
|
-
const
|
|
780
|
+
const { asset: parsed } = await loadPromptFile(file);
|
|
781
|
+
const asset = parsed.includes && parsed.includes.length > 0 ? await resolveIncludes(parsed, file) : parsed;
|
|
636
782
|
console.log(JSON.stringify(asset, null, 2));
|
|
637
783
|
}
|
|
638
784
|
|
|
639
785
|
// src/cli/commands/init.ts
|
|
640
786
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
641
|
-
import { join as
|
|
787
|
+
import { join as join4, dirname as dirname4 } from "path";
|
|
642
788
|
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
643
789
|
var HELP5 = `
|
|
644
790
|
promptopskit init [dir]
|
|
@@ -656,6 +802,7 @@ model: gpt-5.4
|
|
|
656
802
|
context:
|
|
657
803
|
inputs:
|
|
658
804
|
- name
|
|
805
|
+
- app_context
|
|
659
806
|
includes:
|
|
660
807
|
- ./shared/tone.md
|
|
661
808
|
---
|
|
@@ -663,6 +810,7 @@ includes:
|
|
|
663
810
|
# System instructions
|
|
664
811
|
|
|
665
812
|
You are a friendly assistant. Be helpful and concise.
|
|
813
|
+
Current app context: {{ app_context }}.
|
|
666
814
|
|
|
667
815
|
# Prompt template
|
|
668
816
|
|
|
@@ -677,13 +825,25 @@ schema_version: 1
|
|
|
677
825
|
|
|
678
826
|
Always be polite, professional, and concise. Avoid jargon unless the user uses it first.
|
|
679
827
|
`.trimStart();
|
|
828
|
+
var DEFAULTS = `---
|
|
829
|
+
metadata:
|
|
830
|
+
owner: my-team
|
|
831
|
+
review_required: true
|
|
832
|
+
---
|
|
833
|
+
|
|
834
|
+
# System instructions
|
|
835
|
+
|
|
836
|
+
You are a helpful AI assistant. Follow company guidelines at all times.
|
|
837
|
+
`.trimStart();
|
|
680
838
|
var TEST_SIDECAR = `cases:
|
|
681
839
|
- name: basic-greeting
|
|
682
840
|
variables:
|
|
683
841
|
name: "World"
|
|
842
|
+
app_context: "Welcome screen"
|
|
684
843
|
- name: named-greeting
|
|
685
844
|
variables:
|
|
686
845
|
name: "Alice"
|
|
846
|
+
app_context: "Settings page"
|
|
687
847
|
`;
|
|
688
848
|
async function init(args) {
|
|
689
849
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -692,9 +852,10 @@ async function init(args) {
|
|
|
692
852
|
}
|
|
693
853
|
const dir = args.find((a) => !a.startsWith("--")) ?? "./prompts";
|
|
694
854
|
const files = [
|
|
695
|
-
{ path:
|
|
696
|
-
{ path:
|
|
697
|
-
{ path:
|
|
855
|
+
{ path: join4(dir, "defaults.md"), content: DEFAULTS },
|
|
856
|
+
{ path: join4(dir, "hello.md"), content: HELLO_PROMPT },
|
|
857
|
+
{ path: join4(dir, "hello.test.yaml"), content: TEST_SIDECAR },
|
|
858
|
+
{ path: join4(dir, "shared", "tone.md"), content: TONE_INCLUDE }
|
|
698
859
|
];
|
|
699
860
|
let created = 0;
|
|
700
861
|
let skipped = 0;
|
|
@@ -704,7 +865,7 @@ async function init(args) {
|
|
|
704
865
|
skipped++;
|
|
705
866
|
continue;
|
|
706
867
|
}
|
|
707
|
-
await mkdir2(
|
|
868
|
+
await mkdir2(dirname4(file.path), { recursive: true });
|
|
708
869
|
await writeFile2(file.path, file.content, "utf-8");
|
|
709
870
|
console.log(` \u2713 ${file.path}`);
|
|
710
871
|
created++;
|
|
@@ -724,8 +885,401 @@ async function init(args) {
|
|
|
724
885
|
}
|
|
725
886
|
}
|
|
726
887
|
|
|
727
|
-
// src/cli/
|
|
888
|
+
// src/cli/commands/skill.ts
|
|
889
|
+
import { writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
890
|
+
import { dirname as dirname5 } from "path";
|
|
891
|
+
import { existsSync as existsSync3 } from "fs";
|
|
728
892
|
var HELP6 = `
|
|
893
|
+
promptopskit skill [--target <target>] [--force]
|
|
894
|
+
|
|
895
|
+
Deploy AI agent instructions so coding assistants know how to
|
|
896
|
+
create and manage prompts using promptopskit.
|
|
897
|
+
|
|
898
|
+
Targets:
|
|
899
|
+
copilot (default) .github/instructions/promptopskit.instructions.md
|
|
900
|
+
cursor .cursor/rules/promptopskit.mdc
|
|
901
|
+
generic .ai/promptopskit-skill.md
|
|
902
|
+
|
|
903
|
+
Options:
|
|
904
|
+
--target, -t Target AI tool (copilot, cursor, generic)
|
|
905
|
+
--force, -f Overwrite existing file
|
|
906
|
+
--help, -h Show this help
|
|
907
|
+
`.trim();
|
|
908
|
+
var TARGETS = {
|
|
909
|
+
copilot: {
|
|
910
|
+
path: ".github/instructions/promptopskit.instructions.md",
|
|
911
|
+
wrap: (content) => `---
|
|
912
|
+
applyTo: "**"
|
|
913
|
+
---
|
|
914
|
+
|
|
915
|
+
${content}`
|
|
916
|
+
},
|
|
917
|
+
cursor: {
|
|
918
|
+
path: ".cursor/rules/promptopskit.mdc",
|
|
919
|
+
wrap: (content) => `---
|
|
920
|
+
description: How to create and manage prompts using promptopskit
|
|
921
|
+
globs: "**"
|
|
922
|
+
alwaysApply: true
|
|
923
|
+
---
|
|
924
|
+
|
|
925
|
+
${content}`
|
|
926
|
+
},
|
|
927
|
+
generic: {
|
|
928
|
+
path: ".ai/promptopskit-skill.md",
|
|
929
|
+
wrap: (content) => content
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
async function skill(args) {
|
|
933
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
934
|
+
console.log(HELP6);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
const force = args.includes("--force") || args.includes("-f");
|
|
938
|
+
let target = "copilot";
|
|
939
|
+
const targetIdx = args.findIndex((a) => a === "--target" || a === "-t");
|
|
940
|
+
if (targetIdx !== -1 && args[targetIdx + 1]) {
|
|
941
|
+
target = args[targetIdx + 1];
|
|
942
|
+
}
|
|
943
|
+
const config = TARGETS[target];
|
|
944
|
+
if (!config) {
|
|
945
|
+
console.error(`Unknown target: ${target}`);
|
|
946
|
+
console.error(`Valid targets: ${Object.keys(TARGETS).join(", ")}`);
|
|
947
|
+
process.exit(1);
|
|
948
|
+
}
|
|
949
|
+
const filePath = config.path;
|
|
950
|
+
if (existsSync3(filePath) && !force) {
|
|
951
|
+
console.log(` skip ${filePath} (already exists, use --force to overwrite)`);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
const content = config.wrap(SKILL_CONTENT);
|
|
955
|
+
await mkdir3(dirname5(filePath), { recursive: true });
|
|
956
|
+
await writeFile3(filePath, content, "utf-8");
|
|
957
|
+
console.log(` \u2713 ${filePath}`);
|
|
958
|
+
console.log();
|
|
959
|
+
console.log(`AI agents will now understand how to create and manage prompts with promptopskit.`);
|
|
960
|
+
}
|
|
961
|
+
var SKILL_CONTENT = `# promptopskit \u2014 Prompt Engineering Skill
|
|
962
|
+
|
|
963
|
+
This project uses **promptopskit** to manage LLM prompts as code.
|
|
964
|
+
Prompts live in markdown files with YAML front matter, are validated against
|
|
965
|
+
a schema, and render into provider-specific request bodies (OpenAI, Anthropic,
|
|
966
|
+
Gemini, OpenRouter). Follow these instructions when creating or editing prompts.
|
|
967
|
+
|
|
968
|
+
---
|
|
969
|
+
|
|
970
|
+
## Prompt file format
|
|
971
|
+
|
|
972
|
+
Every prompt is a \`.md\` file with two parts:
|
|
973
|
+
|
|
974
|
+
1. **YAML front matter** \u2014 model settings, provider config, variables, overrides
|
|
975
|
+
2. **Markdown body** \u2014 sections separated by H1 headings
|
|
976
|
+
|
|
977
|
+
### Minimal example
|
|
978
|
+
|
|
979
|
+
\`\`\`markdown
|
|
980
|
+
---
|
|
981
|
+
id: greeting
|
|
982
|
+
schema_version: 1
|
|
983
|
+
provider: openai
|
|
984
|
+
model: gpt-5.4
|
|
985
|
+
context:
|
|
986
|
+
inputs:
|
|
987
|
+
- name
|
|
988
|
+
---
|
|
989
|
+
|
|
990
|
+
# System instructions
|
|
991
|
+
|
|
992
|
+
You are a helpful assistant.
|
|
993
|
+
|
|
994
|
+
# Prompt template
|
|
995
|
+
|
|
996
|
+
Hello {{ name }}, how can I help you?
|
|
997
|
+
\`\`\`
|
|
998
|
+
|
|
999
|
+
---
|
|
1000
|
+
|
|
1001
|
+
## Front matter reference
|
|
1002
|
+
|
|
1003
|
+
| Field | Type | Required | Description |
|
|
1004
|
+
|-------|------|----------|-------------|
|
|
1005
|
+
| \`id\` | string | **yes** | Unique identifier for the prompt |
|
|
1006
|
+
| \`schema_version\` | number | yes | Always \`1\` |
|
|
1007
|
+
| \`description\` | string | no | Human-readable description |
|
|
1008
|
+
| \`provider\` | enum | no | \`openai\`, \`anthropic\`, \`google\`, \`gemini\`, \`openrouter\`, or \`any\` |
|
|
1009
|
+
| \`model\` | string | no | Model identifier (e.g. \`gpt-5.4\`, \`claude-sonnet-4-20250514\`) |
|
|
1010
|
+
| \`fallback_models\` | string[] | no | Ordered fallback model list |
|
|
1011
|
+
| \`reasoning\` | object | no | \`{ effort: low|medium|high, budget_tokens: number }\` |
|
|
1012
|
+
| \`sampling\` | object | no | \`{ temperature, top_p, frequency_penalty, presence_penalty, stop, max_output_tokens }\` |
|
|
1013
|
+
| \`response\` | object | no | \`{ format: text|json|markdown, stream: boolean }\` |
|
|
1014
|
+
| \`tools\` | array | no | Tool names (strings) or inline definitions with \`{ name, description, input_schema }\` |
|
|
1015
|
+
| \`mcp\` | object | no | \`{ servers: [string | { name, config }] }\` |
|
|
1016
|
+
| \`context.inputs\` | string[] | no | Declared variable names used in templates |
|
|
1017
|
+
| \`context.history\` | object | no | \`{ max_items: number }\` |
|
|
1018
|
+
| \`includes\` | string[] | no | Relative paths to other prompt files to include |
|
|
1019
|
+
| \`environments\` | object | no | Per-environment overrides (see Overrides) |
|
|
1020
|
+
| \`tiers\` | object | no | Per-tier overrides (see Overrides) |
|
|
1021
|
+
| \`metadata\` | object | no | \`{ owner, tags, review_required, stable }\` |
|
|
1022
|
+
|
|
1023
|
+
---
|
|
1024
|
+
|
|
1025
|
+
## Sections (markdown body)
|
|
1026
|
+
|
|
1027
|
+
Use H1 headings to define sections. The parser recognizes these headings
|
|
1028
|
+
(case-insensitive):
|
|
1029
|
+
|
|
1030
|
+
| Heading | Maps to | Purpose |
|
|
1031
|
+
|---------|---------|---------|
|
|
1032
|
+
| \`# System instructions\` | \`system_instructions\` | System/developer message |
|
|
1033
|
+
| \`# Prompt template\` | \`prompt_template\` | User message template |
|
|
1034
|
+
| \`# Notes\` | \`notes\` | Documentation only \u2014 not sent to the model |
|
|
1035
|
+
|
|
1036
|
+
If the body has **no H1 headings**, the entire body becomes the \`prompt_template\`.
|
|
1037
|
+
|
|
1038
|
+
---
|
|
1039
|
+
|
|
1040
|
+
## Variable interpolation
|
|
1041
|
+
|
|
1042
|
+
Use \`{{ variable_name }}\` syntax in system instructions and prompt template
|
|
1043
|
+
sections. Variables are replaced at render time.
|
|
1044
|
+
|
|
1045
|
+
Rules:
|
|
1046
|
+
- Declare all variables in \`context.inputs\` \u2014 validation warns on undeclared usage
|
|
1047
|
+
- Escape literal braces with \`\\\\{{\` and \`\\\\}}\`
|
|
1048
|
+
- In strict mode, missing variables throw an error
|
|
1049
|
+
- In permissive mode, unresolved placeholders are left intact
|
|
1050
|
+
|
|
1051
|
+
---
|
|
1052
|
+
|
|
1053
|
+
## Includes (composition)
|
|
1054
|
+
|
|
1055
|
+
Compose prompts from shared fragments:
|
|
1056
|
+
|
|
1057
|
+
\`\`\`yaml
|
|
1058
|
+
includes:
|
|
1059
|
+
- ./shared/tone.md
|
|
1060
|
+
- ./shared/safety.md
|
|
1061
|
+
\`\`\`
|
|
1062
|
+
|
|
1063
|
+
Included files are parsed and their \`system_instructions\` are **prepended**
|
|
1064
|
+
before the including file's own system instructions. Includes resolve
|
|
1065
|
+
recursively. Circular includes are detected and rejected.
|
|
1066
|
+
|
|
1067
|
+
> **Note:** Included files do not inherit folder defaults. Only the top-level
|
|
1068
|
+
> prompt that is loaded via \`loadPromptFile\` receives defaults.
|
|
1069
|
+
|
|
1070
|
+
---
|
|
1071
|
+
|
|
1072
|
+
## Folder defaults (\`defaults.md\`)
|
|
1073
|
+
|
|
1074
|
+
Define shared defaults for a prompt tree by adding a \`defaults.md\` file in any
|
|
1075
|
+
folder:
|
|
1076
|
+
|
|
1077
|
+
\`\`\`text
|
|
1078
|
+
prompts/
|
|
1079
|
+
\u251C\u2500\u2500 defaults.md # global metadata + system instructions
|
|
1080
|
+
\u2514\u2500\u2500 support/
|
|
1081
|
+
\u251C\u2500\u2500 defaults.md # overrides for support/*
|
|
1082
|
+
\u2514\u2500\u2500 reply.md # inherits from support/defaults.md
|
|
1083
|
+
\`\`\`
|
|
1084
|
+
|
|
1085
|
+
Supported default fields:
|
|
1086
|
+
- \`metadata\` (front matter) \u2014 merged with prompt-local metadata
|
|
1087
|
+
- \`# System instructions\` (body section) \u2014 used when the prompt has none
|
|
1088
|
+
|
|
1089
|
+
Rules:
|
|
1090
|
+
- Nearest subfolder \`defaults.md\` overrides parent defaults
|
|
1091
|
+
- Prompt-local values always take precedence over defaults
|
|
1092
|
+
- \`defaults.md\` files are skipped during compilation and validation
|
|
1093
|
+
- \`loadPromptFile\` defaults the search boundary to the file's own directory;
|
|
1094
|
+
pass \`defaultsRoot\` to enable ancestor traversal
|
|
1095
|
+
|
|
1096
|
+
---
|
|
1097
|
+
|
|
1098
|
+
## Environment & tier overrides
|
|
1099
|
+
|
|
1100
|
+
Override model settings per environment or tier:
|
|
1101
|
+
|
|
1102
|
+
\`\`\`yaml
|
|
1103
|
+
environments:
|
|
1104
|
+
development:
|
|
1105
|
+
model: gpt-4.1-mini
|
|
1106
|
+
sampling:
|
|
1107
|
+
temperature: 0.9
|
|
1108
|
+
production:
|
|
1109
|
+
model: gpt-5.4
|
|
1110
|
+
sampling:
|
|
1111
|
+
temperature: 0.3
|
|
1112
|
+
|
|
1113
|
+
tiers:
|
|
1114
|
+
free:
|
|
1115
|
+
model: gpt-4.1-mini
|
|
1116
|
+
sampling:
|
|
1117
|
+
max_output_tokens: 500
|
|
1118
|
+
premium:
|
|
1119
|
+
model: gpt-5.4
|
|
1120
|
+
\`\`\`
|
|
1121
|
+
|
|
1122
|
+
Overridable fields: \`model\`, \`fallback_models\`, \`reasoning\`, \`sampling\`,
|
|
1123
|
+
\`response\`, \`tools\`.
|
|
1124
|
+
|
|
1125
|
+
Override application order: **base \u2192 environment \u2192 tier \u2192 runtime**.
|
|
1126
|
+
|
|
1127
|
+
---
|
|
1128
|
+
|
|
1129
|
+
## Test sidecars
|
|
1130
|
+
|
|
1131
|
+
Create a \`.test.yaml\` file alongside a prompt to define test cases:
|
|
1132
|
+
|
|
1133
|
+
\`\`\`yaml
|
|
1134
|
+
# greeting.test.yaml
|
|
1135
|
+
cases:
|
|
1136
|
+
- name: basic
|
|
1137
|
+
variables:
|
|
1138
|
+
name: "World"
|
|
1139
|
+
- name: formal
|
|
1140
|
+
variables:
|
|
1141
|
+
name: "Dr. Smith"
|
|
1142
|
+
\`\`\`
|
|
1143
|
+
|
|
1144
|
+
---
|
|
1145
|
+
|
|
1146
|
+
## Using the library (TypeScript / JavaScript)
|
|
1147
|
+
|
|
1148
|
+
### Quick start
|
|
1149
|
+
|
|
1150
|
+
\`\`\`typescript
|
|
1151
|
+
import { createPromptOpsKit } from 'promptopskit';
|
|
1152
|
+
|
|
1153
|
+
const kit = createPromptOpsKit({ sourceDir: './prompts' });
|
|
1154
|
+
|
|
1155
|
+
// Load \u2192 resolve includes \u2192 apply overrides \u2192 render
|
|
1156
|
+
const request = await kit.renderPrompt('greeting', {
|
|
1157
|
+
variables: { name: 'Alice' },
|
|
1158
|
+
environment: 'production',
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
// request.body is ready for the provider's API
|
|
1162
|
+
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
1163
|
+
method: 'POST',
|
|
1164
|
+
headers: {
|
|
1165
|
+
'Content-Type': 'application/json',
|
|
1166
|
+
Authorization: \`Bearer \${apiKey}\`,
|
|
1167
|
+
},
|
|
1168
|
+
body: JSON.stringify(request.body),
|
|
1169
|
+
});
|
|
1170
|
+
\`\`\`
|
|
1171
|
+
|
|
1172
|
+
### Step-by-step API
|
|
1173
|
+
|
|
1174
|
+
\`\`\`typescript
|
|
1175
|
+
import {
|
|
1176
|
+
parsePrompt,
|
|
1177
|
+
resolveIncludes,
|
|
1178
|
+
applyOverrides,
|
|
1179
|
+
getAdapter,
|
|
1180
|
+
} from 'promptopskit';
|
|
1181
|
+
import { readFileSync } from 'fs';
|
|
1182
|
+
|
|
1183
|
+
// 1. Parse a prompt file
|
|
1184
|
+
const source = readFileSync('./prompts/greeting.md', 'utf-8');
|
|
1185
|
+
const asset = parsePrompt(source, 'greeting.md');
|
|
1186
|
+
|
|
1187
|
+
// 2. Resolve includes
|
|
1188
|
+
const resolved = await resolveIncludes(asset, './prompts');
|
|
1189
|
+
|
|
1190
|
+
// 3. Apply overrides
|
|
1191
|
+
const configured = applyOverrides(resolved, {
|
|
1192
|
+
environment: 'production',
|
|
1193
|
+
tier: 'premium',
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
// 4. Get provider adapter and render
|
|
1197
|
+
const adapter = getAdapter(configured.provider ?? 'openai');
|
|
1198
|
+
const request = adapter.render(configured, {
|
|
1199
|
+
variables: { name: 'Alice' },
|
|
1200
|
+
history: [
|
|
1201
|
+
{ role: 'user', content: 'Previous message' },
|
|
1202
|
+
{ role: 'assistant', content: 'Previous response' },
|
|
1203
|
+
],
|
|
1204
|
+
});
|
|
1205
|
+
\`\`\`
|
|
1206
|
+
|
|
1207
|
+
### Available provider adapters
|
|
1208
|
+
|
|
1209
|
+
| Provider | Import path | Provider request format |
|
|
1210
|
+
|----------|------------|----------------------|
|
|
1211
|
+
| OpenAI | \`promptopskit\` or \`promptopskit/openai\` | Chat Completions API |
|
|
1212
|
+
| Anthropic | \`promptopskit/anthropic\` | Messages API |
|
|
1213
|
+
| Gemini | \`promptopskit/gemini\` | GenerateContent API |
|
|
1214
|
+
| OpenRouter | \`promptopskit/openrouter\` | OpenAI-compatible + extras |
|
|
1215
|
+
|
|
1216
|
+
### Validation
|
|
1217
|
+
|
|
1218
|
+
\`\`\`typescript
|
|
1219
|
+
import { validateAsset, parsePrompt } from 'promptopskit';
|
|
1220
|
+
|
|
1221
|
+
const asset = parsePrompt(source);
|
|
1222
|
+
const result = validateAsset(asset);
|
|
1223
|
+
|
|
1224
|
+
if (!result.valid) {
|
|
1225
|
+
console.error(result.errors); // Error codes: POK001-POK021
|
|
1226
|
+
}
|
|
1227
|
+
\`\`\`
|
|
1228
|
+
|
|
1229
|
+
### Testing helpers
|
|
1230
|
+
|
|
1231
|
+
\`\`\`typescript
|
|
1232
|
+
import { createMockAsset, parseTestPrompt } from 'promptopskit/testing';
|
|
1233
|
+
|
|
1234
|
+
// Create a mock asset for unit tests
|
|
1235
|
+
const mock = createMockAsset({ model: 'gpt-4.1-mini' });
|
|
1236
|
+
|
|
1237
|
+
// Parse an inline prompt string for tests
|
|
1238
|
+
const asset = parseTestPrompt(\`
|
|
1239
|
+
---
|
|
1240
|
+
id: test
|
|
1241
|
+
schema_version: 1
|
|
1242
|
+
provider: openai
|
|
1243
|
+
model: gpt-5.4
|
|
1244
|
+
---
|
|
1245
|
+
|
|
1246
|
+
# Prompt template
|
|
1247
|
+
|
|
1248
|
+
Hello {{ name }}
|
|
1249
|
+
\`);
|
|
1250
|
+
\`\`\`
|
|
1251
|
+
|
|
1252
|
+
---
|
|
1253
|
+
|
|
1254
|
+
## CLI commands
|
|
1255
|
+
|
|
1256
|
+
| Command | Description |
|
|
1257
|
+
|---------|-------------|
|
|
1258
|
+
| \`promptopskit init [dir]\` | Scaffold a prompts directory with starter files (including \`defaults.md\`) |
|
|
1259
|
+
| \`promptopskit validate <dir>\` | Validate all prompt files in a directory |
|
|
1260
|
+
| \`promptopskit compile <src> <out>\` | Compile .md prompts to JSON artifacts |
|
|
1261
|
+
| \`promptopskit render <file> [--set key=value]\` | Render a prompt preview |
|
|
1262
|
+
| \`promptopskit inspect <file>\` | Print the normalized prompt asset |
|
|
1263
|
+
|
|
1264
|
+
---
|
|
1265
|
+
|
|
1266
|
+
## Conventions to follow
|
|
1267
|
+
|
|
1268
|
+
1. **One prompt per file** \u2014 each \`.md\` file is a single prompt asset
|
|
1269
|
+
2. **Always set \`id\` and \`schema_version: 1\`** in front matter
|
|
1270
|
+
3. **Declare all variables** in \`context.inputs\` that appear in templates
|
|
1271
|
+
4. **Use includes** for shared system instructions (tone, safety, formatting)
|
|
1272
|
+
5. **Keep prompt templates focused** \u2014 compose behavior via includes, not duplication
|
|
1273
|
+
6. **Use environment overrides** for dev/staging/prod model differences
|
|
1274
|
+
7. **Add test sidecars** (\`.test.yaml\`) for critical prompts
|
|
1275
|
+
8. **Run \`promptopskit validate\`** before committing changes
|
|
1276
|
+
9. **Use \`defaults.md\`** to share metadata and system instructions across a folder
|
|
1277
|
+
10. **Variable names** should be \`snake_case\`
|
|
1278
|
+
11. **Prompt file names** should be \`kebab-case.md\`
|
|
1279
|
+
`.trimEnd();
|
|
1280
|
+
|
|
1281
|
+
// src/cli/index.ts
|
|
1282
|
+
var HELP7 = `
|
|
729
1283
|
promptopskit \u2014 Manage prompts, system instructions, tools, and model settings as code
|
|
730
1284
|
|
|
731
1285
|
Usage:
|
|
@@ -737,6 +1291,7 @@ Commands:
|
|
|
737
1291
|
compile <src> <out> [options] Compile .md prompts to JSON/ESM artifacts
|
|
738
1292
|
render <file> [options] Render a prompt preview
|
|
739
1293
|
inspect <file> Print normalized prompt asset
|
|
1294
|
+
skill [options] Deploy AI agent instructions into your project
|
|
740
1295
|
|
|
741
1296
|
Options:
|
|
742
1297
|
--help, -h Show this help message
|
|
@@ -748,7 +1303,7 @@ async function main() {
|
|
|
748
1303
|
const args = process.argv.slice(2);
|
|
749
1304
|
const command = args[0];
|
|
750
1305
|
if (!command || command === "--help" || command === "-h") {
|
|
751
|
-
console.log(
|
|
1306
|
+
console.log(HELP7);
|
|
752
1307
|
process.exit(0);
|
|
753
1308
|
}
|
|
754
1309
|
if (command === "--version" || command === "-v") {
|
|
@@ -772,9 +1327,12 @@ async function main() {
|
|
|
772
1327
|
case "inspect":
|
|
773
1328
|
await inspect(commandArgs);
|
|
774
1329
|
break;
|
|
1330
|
+
case "skill":
|
|
1331
|
+
await skill(commandArgs);
|
|
1332
|
+
break;
|
|
775
1333
|
default:
|
|
776
1334
|
console.error(`Unknown command: ${command}`);
|
|
777
|
-
console.log(
|
|
1335
|
+
console.log(HELP7);
|
|
778
1336
|
process.exit(1);
|
|
779
1337
|
}
|
|
780
1338
|
}
|