promptopskit 0.0.1
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 -0
- package/README.md +309 -0
- package/dist/chunk-B5UFGNDV.js +88 -0
- package/dist/chunk-B5UFGNDV.js.map +1 -0
- package/dist/chunk-FSOJVXC4.js +92 -0
- package/dist/chunk-FSOJVXC4.js.map +1 -0
- package/dist/chunk-HEFOFQ2K.js +56 -0
- package/dist/chunk-HEFOFQ2K.js.map +1 -0
- package/dist/chunk-V375NQ6C.js +83 -0
- package/dist/chunk-V375NQ6C.js.map +1 -0
- package/dist/chunk-X64JII57.js +159 -0
- package/dist/chunk-X64JII57.js.map +1 -0
- package/dist/chunk-YCRWYE6N.js +23 -0
- package/dist/chunk-YCRWYE6N.js.map +1 -0
- package/dist/cli/index.js +785 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +179 -0
- package/dist/index.js +452 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/anthropic.d.ts +11 -0
- package/dist/providers/anthropic.js +8 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/gemini.d.ts +11 -0
- package/dist/providers/gemini.js +8 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/openai.d.ts +11 -0
- package/dist/providers/openai.js +8 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openrouter.d.ts +13 -0
- package/dist/providers/openrouter.js +9 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/schema-DHRI5Mzl.d.ts +695 -0
- package/dist/testing.d.ts +17 -0
- package/dist/testing.js +44 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-D9hquVja.d.ts +40 -0
- package/package.json +79 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/commands/validate.ts
|
|
4
|
+
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
5
|
+
import { join, extname } from "path";
|
|
6
|
+
|
|
7
|
+
// src/parser/parser.ts
|
|
8
|
+
import matter from "gray-matter";
|
|
9
|
+
|
|
10
|
+
// src/schema/schema.ts
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
var InlineToolDefSchema = z.object({
|
|
13
|
+
name: z.string(),
|
|
14
|
+
description: z.string().optional(),
|
|
15
|
+
input_schema: z.record(z.unknown()).optional()
|
|
16
|
+
});
|
|
17
|
+
var ToolRefSchema = z.union([z.string(), InlineToolDefSchema]);
|
|
18
|
+
var MCPServerRefSchema = z.union([
|
|
19
|
+
z.string(),
|
|
20
|
+
z.object({
|
|
21
|
+
name: z.string(),
|
|
22
|
+
config: z.record(z.unknown()).optional()
|
|
23
|
+
})
|
|
24
|
+
]);
|
|
25
|
+
var ReasoningSchema = z.object({
|
|
26
|
+
effort: z.enum(["low", "medium", "high"]).optional(),
|
|
27
|
+
budget_tokens: z.number().int().positive().optional()
|
|
28
|
+
});
|
|
29
|
+
var SamplingSchema = z.object({
|
|
30
|
+
temperature: z.number().min(0).max(2).optional(),
|
|
31
|
+
top_p: z.number().min(0).max(1).optional(),
|
|
32
|
+
frequency_penalty: z.number().optional(),
|
|
33
|
+
presence_penalty: z.number().optional(),
|
|
34
|
+
stop: z.array(z.string()).optional(),
|
|
35
|
+
max_output_tokens: z.number().int().positive().optional()
|
|
36
|
+
});
|
|
37
|
+
var ResponseSchema = z.object({
|
|
38
|
+
format: z.enum(["text", "json", "markdown"]).optional(),
|
|
39
|
+
stream: z.boolean().optional()
|
|
40
|
+
});
|
|
41
|
+
var HistorySchema = z.object({
|
|
42
|
+
max_items: z.number().int().positive().optional()
|
|
43
|
+
});
|
|
44
|
+
var ContextSchema = z.object({
|
|
45
|
+
inputs: z.array(z.string()).optional(),
|
|
46
|
+
history: HistorySchema.optional()
|
|
47
|
+
});
|
|
48
|
+
var MetadataSchema = z.object({
|
|
49
|
+
owner: z.string().optional(),
|
|
50
|
+
tags: z.array(z.string()).optional(),
|
|
51
|
+
review_required: z.boolean().optional(),
|
|
52
|
+
stable: z.boolean().optional()
|
|
53
|
+
});
|
|
54
|
+
var MCPSchema = z.object({
|
|
55
|
+
servers: z.array(MCPServerRefSchema).optional()
|
|
56
|
+
});
|
|
57
|
+
var PromptAssetOverridesSchema = z.object({
|
|
58
|
+
model: z.string().optional(),
|
|
59
|
+
fallback_models: z.array(z.string()).optional(),
|
|
60
|
+
reasoning: ReasoningSchema.optional(),
|
|
61
|
+
sampling: SamplingSchema.optional(),
|
|
62
|
+
response: ResponseSchema.optional(),
|
|
63
|
+
tools: z.array(ToolRefSchema).optional()
|
|
64
|
+
});
|
|
65
|
+
var SourceSchema = z.object({
|
|
66
|
+
file_path: z.string().optional(),
|
|
67
|
+
checksum: z.string().optional()
|
|
68
|
+
});
|
|
69
|
+
var SectionsSchema = z.object({
|
|
70
|
+
system_instructions: z.string().optional(),
|
|
71
|
+
prompt_template: z.string().optional(),
|
|
72
|
+
notes: z.string().optional()
|
|
73
|
+
});
|
|
74
|
+
var PromptAssetSchema = z.object({
|
|
75
|
+
id: z.string(),
|
|
76
|
+
schema_version: z.number().int().positive().default(1),
|
|
77
|
+
description: z.string().optional(),
|
|
78
|
+
provider: z.enum(["openai", "anthropic", "google", "openrouter", "any"]).optional(),
|
|
79
|
+
model: z.string().optional(),
|
|
80
|
+
fallback_models: z.array(z.string()).optional(),
|
|
81
|
+
reasoning: ReasoningSchema.optional(),
|
|
82
|
+
sampling: SamplingSchema.optional(),
|
|
83
|
+
response: ResponseSchema.optional(),
|
|
84
|
+
tools: z.array(ToolRefSchema).optional(),
|
|
85
|
+
mcp: MCPSchema.optional(),
|
|
86
|
+
context: ContextSchema.optional(),
|
|
87
|
+
includes: z.array(z.string()).optional(),
|
|
88
|
+
environments: z.record(PromptAssetOverridesSchema).optional(),
|
|
89
|
+
tiers: z.record(PromptAssetOverridesSchema).optional(),
|
|
90
|
+
metadata: MetadataSchema.optional(),
|
|
91
|
+
// Populated by parser, not authored in YAML
|
|
92
|
+
sections: SectionsSchema.optional(),
|
|
93
|
+
source: SourceSchema.optional()
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// src/parser/sections.ts
|
|
97
|
+
var SECTION_MAP = {
|
|
98
|
+
"system instructions": "system_instructions",
|
|
99
|
+
"prompt template": "prompt_template",
|
|
100
|
+
"notes": "notes"
|
|
101
|
+
};
|
|
102
|
+
function extractSections(body) {
|
|
103
|
+
const lines = body.split("\n");
|
|
104
|
+
const sections = {};
|
|
105
|
+
let currentKey = null;
|
|
106
|
+
let currentLines = [];
|
|
107
|
+
let foundAnyH1 = false;
|
|
108
|
+
for (const line of lines) {
|
|
109
|
+
const h1Match = line.match(/^#\s+(.+)$/);
|
|
110
|
+
if (h1Match) {
|
|
111
|
+
if (currentKey) {
|
|
112
|
+
sections[currentKey] = currentLines.join("\n").trim();
|
|
113
|
+
}
|
|
114
|
+
foundAnyH1 = true;
|
|
115
|
+
const heading = h1Match[1].trim().toLowerCase();
|
|
116
|
+
currentKey = SECTION_MAP[heading] ?? null;
|
|
117
|
+
currentLines = [];
|
|
118
|
+
} else {
|
|
119
|
+
currentLines.push(line);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (currentKey) {
|
|
123
|
+
sections[currentKey] = currentLines.join("\n").trim();
|
|
124
|
+
}
|
|
125
|
+
if (!foundAnyH1) {
|
|
126
|
+
const trimmed = body.trim();
|
|
127
|
+
if (trimmed) {
|
|
128
|
+
sections.prompt_template = trimmed;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return sections;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/parser/parser.ts
|
|
135
|
+
function parsePrompt(content, filePath) {
|
|
136
|
+
const { data: frontMatter, content: body } = matter(content);
|
|
137
|
+
const sections = extractSections(body);
|
|
138
|
+
const raw = {
|
|
139
|
+
...frontMatter,
|
|
140
|
+
sections,
|
|
141
|
+
source: filePath ? { file_path: filePath } : void 0
|
|
142
|
+
};
|
|
143
|
+
const asset = PromptAssetSchema.parse(raw);
|
|
144
|
+
return {
|
|
145
|
+
asset,
|
|
146
|
+
raw: {
|
|
147
|
+
frontMatter,
|
|
148
|
+
body
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/parser/loader.ts
|
|
154
|
+
import { readFile } from "fs/promises";
|
|
155
|
+
|
|
156
|
+
// src/renderer/interpolate.ts
|
|
157
|
+
var VARIABLE_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
|
|
158
|
+
var ESCAPED_OPEN = /\\\{\\\{/g;
|
|
159
|
+
var ESCAPE_PLACEHOLDER = "\0ESCAPED_OPEN\0";
|
|
160
|
+
function interpolate(template, variables, options = {}) {
|
|
161
|
+
const { strict = false } = options;
|
|
162
|
+
let result = template.replace(ESCAPED_OPEN, ESCAPE_PLACEHOLDER);
|
|
163
|
+
result = result.replace(VARIABLE_RE, (match, name) => {
|
|
164
|
+
if (name in variables) {
|
|
165
|
+
return variables[name];
|
|
166
|
+
}
|
|
167
|
+
if (strict) {
|
|
168
|
+
throw new Error(`Missing required variable: "${name}"`);
|
|
169
|
+
}
|
|
170
|
+
return match;
|
|
171
|
+
});
|
|
172
|
+
result = result.replaceAll(ESCAPE_PLACEHOLDER, "{{");
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
function extractVariables(template) {
|
|
176
|
+
const vars = /* @__PURE__ */ new Set();
|
|
177
|
+
let match;
|
|
178
|
+
const re = new RegExp(VARIABLE_RE.source, "g");
|
|
179
|
+
while ((match = re.exec(template)) !== null) {
|
|
180
|
+
vars.add(match[1]);
|
|
181
|
+
}
|
|
182
|
+
return [...vars];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/validation/levenshtein.ts
|
|
186
|
+
function levenshtein(a, b) {
|
|
187
|
+
const m = a.length;
|
|
188
|
+
const n = b.length;
|
|
189
|
+
if (m === 0) return n;
|
|
190
|
+
if (n === 0) return m;
|
|
191
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
192
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
193
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
194
|
+
for (let i = 1; i <= m; i++) {
|
|
195
|
+
for (let j = 1; j <= n; j++) {
|
|
196
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
197
|
+
dp[i][j] = Math.min(
|
|
198
|
+
dp[i - 1][j] + 1,
|
|
199
|
+
dp[i][j - 1] + 1,
|
|
200
|
+
dp[i - 1][j - 1] + cost
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return dp[m][n];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/validation/validate.ts
|
|
208
|
+
var KNOWN_FRONT_MATTER_KEYS = /* @__PURE__ */ new Set([
|
|
209
|
+
"id",
|
|
210
|
+
"schema_version",
|
|
211
|
+
"description",
|
|
212
|
+
"provider",
|
|
213
|
+
"model",
|
|
214
|
+
"fallback_models",
|
|
215
|
+
"reasoning",
|
|
216
|
+
"sampling",
|
|
217
|
+
"response",
|
|
218
|
+
"tools",
|
|
219
|
+
"mcp",
|
|
220
|
+
"context",
|
|
221
|
+
"includes",
|
|
222
|
+
"environments",
|
|
223
|
+
"tiers",
|
|
224
|
+
"metadata"
|
|
225
|
+
]);
|
|
226
|
+
function validateAsset(asset, frontMatterKeys, filePath) {
|
|
227
|
+
const errors = [];
|
|
228
|
+
const warnings = [];
|
|
229
|
+
const result = PromptAssetSchema.safeParse(asset);
|
|
230
|
+
if (!result.success) {
|
|
231
|
+
for (const issue of result.error.issues) {
|
|
232
|
+
errors.push({
|
|
233
|
+
code: "POK001",
|
|
234
|
+
message: `Schema error at ${issue.path.join(".")}: ${issue.message}`,
|
|
235
|
+
filePath
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (!asset.id) {
|
|
240
|
+
errors.push({
|
|
241
|
+
code: "POK002",
|
|
242
|
+
message: 'Missing required field: "id"',
|
|
243
|
+
filePath
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (!asset.sections?.system_instructions && !asset.sections?.prompt_template) {
|
|
247
|
+
errors.push({
|
|
248
|
+
code: "POK003",
|
|
249
|
+
message: "Prompt must have at least one body section (System instructions or Prompt template)",
|
|
250
|
+
filePath
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
if (frontMatterKeys) {
|
|
254
|
+
for (const key of frontMatterKeys) {
|
|
255
|
+
if (!KNOWN_FRONT_MATTER_KEYS.has(key)) {
|
|
256
|
+
const suggestion = findClosestMatch(key, KNOWN_FRONT_MATTER_KEYS);
|
|
257
|
+
warnings.push({
|
|
258
|
+
code: "POK010",
|
|
259
|
+
message: `Unknown front matter field: "${key}"`,
|
|
260
|
+
filePath,
|
|
261
|
+
suggestion: suggestion ? `Did you mean "${suggestion}"?` : void 0
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const declaredInputs = new Set(asset.context?.inputs ?? []);
|
|
267
|
+
const usedVars = /* @__PURE__ */ new Set();
|
|
268
|
+
if (asset.sections?.system_instructions) {
|
|
269
|
+
for (const v of extractVariables(asset.sections.system_instructions)) {
|
|
270
|
+
usedVars.add(v);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (asset.sections?.prompt_template) {
|
|
274
|
+
for (const v of extractVariables(asset.sections.prompt_template)) {
|
|
275
|
+
usedVars.add(v);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
for (const v of usedVars) {
|
|
279
|
+
if (declaredInputs.size > 0 && !declaredInputs.has(v)) {
|
|
280
|
+
warnings.push({
|
|
281
|
+
code: "POK011",
|
|
282
|
+
message: `Variable "{{ ${v} }}" is used but not declared in context.inputs`,
|
|
283
|
+
filePath
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
for (const v of declaredInputs) {
|
|
288
|
+
if (!usedVars.has(v)) {
|
|
289
|
+
warnings.push({
|
|
290
|
+
code: "POK012",
|
|
291
|
+
message: `Variable "${v}" is declared in context.inputs but never used`,
|
|
292
|
+
filePath
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
valid: errors.length === 0,
|
|
298
|
+
errors,
|
|
299
|
+
warnings
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function findClosestMatch(input, candidates) {
|
|
303
|
+
let best;
|
|
304
|
+
let bestDist = Infinity;
|
|
305
|
+
for (const candidate of candidates) {
|
|
306
|
+
const dist = levenshtein(input.toLowerCase(), candidate.toLowerCase());
|
|
307
|
+
if (dist < bestDist && dist <= 3) {
|
|
308
|
+
bestDist = dist;
|
|
309
|
+
best = candidate;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return best;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/cli/commands/validate.ts
|
|
316
|
+
var HELP = `
|
|
317
|
+
promptopskit validate <dir>
|
|
318
|
+
|
|
319
|
+
Validate all prompt .md files in a directory.
|
|
320
|
+
|
|
321
|
+
Options:
|
|
322
|
+
--strict Treat warnings as errors
|
|
323
|
+
--help, -h Show this help
|
|
324
|
+
`.trim();
|
|
325
|
+
async function validate(args) {
|
|
326
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
327
|
+
console.log(HELP);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const dir = args.find((a) => !a.startsWith("--"));
|
|
331
|
+
if (!dir) {
|
|
332
|
+
console.error("Error: Please provide a directory to validate.");
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
const strict = args.includes("--strict");
|
|
336
|
+
const files = await collectPromptFiles(dir);
|
|
337
|
+
if (files.length === 0) {
|
|
338
|
+
console.log(`No .md prompt files found in ${dir}`);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
let errorCount = 0;
|
|
342
|
+
let warnCount = 0;
|
|
343
|
+
for (const file of files) {
|
|
344
|
+
try {
|
|
345
|
+
const content = await readFile2(file, "utf-8");
|
|
346
|
+
const { asset, raw } = parsePrompt(content, file);
|
|
347
|
+
const result = validateAsset(asset, Object.keys(raw.frontMatter), file);
|
|
348
|
+
if (result.errors.length > 0) {
|
|
349
|
+
errorCount += result.errors.length;
|
|
350
|
+
console.error(` \u2717 ${file}`);
|
|
351
|
+
for (const err of result.errors) {
|
|
352
|
+
console.error(` ${err.code}: ${err.message}`);
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
console.log(` \u2713 ${file}`);
|
|
356
|
+
}
|
|
357
|
+
if (result.warnings.length > 0) {
|
|
358
|
+
warnCount += result.warnings.length;
|
|
359
|
+
for (const warn of result.warnings) {
|
|
360
|
+
const suggestion = warn.suggestion ? ` (${warn.suggestion})` : "";
|
|
361
|
+
console.warn(` \u26A0 ${warn.code}: ${warn.message}${suggestion}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
} catch (err) {
|
|
365
|
+
errorCount++;
|
|
366
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
367
|
+
console.error(` \u2717 ${file}`);
|
|
368
|
+
console.error(` ${message}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
console.log();
|
|
372
|
+
console.log(`Validated ${files.length} file(s): ${errorCount} error(s), ${warnCount} warning(s)`);
|
|
373
|
+
if (errorCount > 0 || strict && warnCount > 0) {
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async function collectPromptFiles(dir) {
|
|
378
|
+
const results = [];
|
|
379
|
+
const entries = await readdir(dir, { withFileTypes: true, recursive: true });
|
|
380
|
+
for (const entry of entries) {
|
|
381
|
+
if (entry.isFile() && extname(entry.name) === ".md" && !entry.name.endsWith(".test.md")) {
|
|
382
|
+
results.push(join(entry.parentPath ?? dir, entry.name));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return results.sort();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// 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";
|
|
391
|
+
var HELP2 = `
|
|
392
|
+
promptopskit compile <sourceDir> <outputDir> [options]
|
|
393
|
+
|
|
394
|
+
Compile .md prompt files to JSON artifacts.
|
|
395
|
+
|
|
396
|
+
Options:
|
|
397
|
+
--no-clean Don't clear the output directory before compiling
|
|
398
|
+
--dry-run Show what would be compiled without writing files
|
|
399
|
+
--format Output format: json (default) or esm
|
|
400
|
+
--help, -h Show this help
|
|
401
|
+
`.trim();
|
|
402
|
+
async function compile(args) {
|
|
403
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
404
|
+
console.log(HELP2);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const positional = args.filter((a) => !a.startsWith("--"));
|
|
408
|
+
const sourceDir = positional[0];
|
|
409
|
+
const outputDir = positional[1];
|
|
410
|
+
if (!sourceDir || !outputDir) {
|
|
411
|
+
console.error("Error: Please provide source and output directories.");
|
|
412
|
+
console.error("Usage: promptopskit compile <sourceDir> <outputDir>");
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
const dryRun = args.includes("--dry-run");
|
|
416
|
+
const noClean = args.includes("--no-clean");
|
|
417
|
+
const format = getFlag(args, "--format") ?? "json";
|
|
418
|
+
if (format !== "json" && format !== "esm") {
|
|
419
|
+
console.error(`Error: Unknown format "${format}". Use "json" or "esm".`);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
const files = await collectPromptFiles2(sourceDir);
|
|
423
|
+
if (files.length === 0) {
|
|
424
|
+
console.log(`No .md prompt files found in ${sourceDir}`);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (!noClean && !dryRun) {
|
|
428
|
+
await rm(outputDir, { recursive: true, force: true });
|
|
429
|
+
}
|
|
430
|
+
let compiled = 0;
|
|
431
|
+
let errors = 0;
|
|
432
|
+
for (const file of files) {
|
|
433
|
+
const rel = relative(sourceDir, file).replace(/\.md$/, "");
|
|
434
|
+
const outExt = format === "esm" ? ".mjs" : ".json";
|
|
435
|
+
const outPath = join2(outputDir, rel + outExt);
|
|
436
|
+
try {
|
|
437
|
+
const content = await readFile3(file, "utf-8");
|
|
438
|
+
const { asset } = parsePrompt(content, file);
|
|
439
|
+
if (dryRun) {
|
|
440
|
+
console.log(` Would create: ${outPath}`);
|
|
441
|
+
} else {
|
|
442
|
+
await mkdir(dirname(outPath), { recursive: true });
|
|
443
|
+
if (format === "esm") {
|
|
444
|
+
const esmContent = `export default ${JSON.stringify(asset, null, 2)};
|
|
445
|
+
`;
|
|
446
|
+
await writeFile(outPath, esmContent, "utf-8");
|
|
447
|
+
} else {
|
|
448
|
+
await writeFile(outPath, JSON.stringify(asset, null, 2) + "\n", "utf-8");
|
|
449
|
+
}
|
|
450
|
+
console.log(` \u2713 ${outPath}`);
|
|
451
|
+
}
|
|
452
|
+
compiled++;
|
|
453
|
+
} catch (err) {
|
|
454
|
+
errors++;
|
|
455
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
456
|
+
console.error(` \u2717 ${file}`);
|
|
457
|
+
console.error(` ${message}`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
console.log();
|
|
461
|
+
if (dryRun) {
|
|
462
|
+
console.log(`Dry run: ${compiled} file(s) would be compiled, ${errors} error(s)`);
|
|
463
|
+
} else {
|
|
464
|
+
console.log(`Compiled ${compiled} file(s), ${errors} error(s)`);
|
|
465
|
+
}
|
|
466
|
+
if (errors > 0) {
|
|
467
|
+
process.exit(1);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function getFlag(args, flag) {
|
|
471
|
+
const idx = args.indexOf(flag);
|
|
472
|
+
if (idx >= 0 && idx + 1 < args.length) {
|
|
473
|
+
return args[idx + 1];
|
|
474
|
+
}
|
|
475
|
+
return void 0;
|
|
476
|
+
}
|
|
477
|
+
async function collectPromptFiles2(dir) {
|
|
478
|
+
const results = [];
|
|
479
|
+
const entries = await readdir2(dir, { withFileTypes: true, recursive: true });
|
|
480
|
+
for (const entry of entries) {
|
|
481
|
+
if (entry.isFile() && extname2(entry.name) === ".md" && !entry.name.endsWith(".test.md")) {
|
|
482
|
+
results.push(join2(entry.parentPath ?? dir, entry.name));
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return results.sort();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// src/cli/commands/render.ts
|
|
489
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
490
|
+
import { existsSync } from "fs";
|
|
491
|
+
|
|
492
|
+
// src/overrides/apply-overrides.ts
|
|
493
|
+
function applyOverrides(asset, options = {}) {
|
|
494
|
+
let result = { ...asset };
|
|
495
|
+
if (options.environment && result.environments?.[options.environment]) {
|
|
496
|
+
result = mergeOverride(result, result.environments[options.environment]);
|
|
497
|
+
}
|
|
498
|
+
if (options.tier && result.tiers?.[options.tier]) {
|
|
499
|
+
result = mergeOverride(result, result.tiers[options.tier]);
|
|
500
|
+
}
|
|
501
|
+
if (options.runtime) {
|
|
502
|
+
result = mergeOverride(result, options.runtime);
|
|
503
|
+
}
|
|
504
|
+
return result;
|
|
505
|
+
}
|
|
506
|
+
function mergeOverride(base, override) {
|
|
507
|
+
const result = { ...base };
|
|
508
|
+
if (override.model !== void 0) result.model = override.model;
|
|
509
|
+
if (override.fallback_models !== void 0) result.fallback_models = override.fallback_models;
|
|
510
|
+
if (override.tools !== void 0) result.tools = override.tools;
|
|
511
|
+
if (override.reasoning !== void 0) {
|
|
512
|
+
result.reasoning = { ...result.reasoning, ...override.reasoning };
|
|
513
|
+
}
|
|
514
|
+
if (override.sampling !== void 0) {
|
|
515
|
+
result.sampling = { ...result.sampling, ...override.sampling };
|
|
516
|
+
}
|
|
517
|
+
if (override.response !== void 0) {
|
|
518
|
+
result.response = { ...result.response, ...override.response };
|
|
519
|
+
}
|
|
520
|
+
return result;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/cli/commands/render.ts
|
|
524
|
+
var HELP3 = `
|
|
525
|
+
promptopskit render <file> [options]
|
|
526
|
+
|
|
527
|
+
Render a prompt preview with variables.
|
|
528
|
+
|
|
529
|
+
Options:
|
|
530
|
+
--env <name> Environment override
|
|
531
|
+
--tier <name> Tier override
|
|
532
|
+
--vars <file> JSON file with variables
|
|
533
|
+
--json Output raw JSON instead of readable format
|
|
534
|
+
--help, -h Show this help
|
|
535
|
+
`.trim();
|
|
536
|
+
async function render(args) {
|
|
537
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
538
|
+
console.log(HELP3);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const file = args.find((a) => !a.startsWith("--"));
|
|
542
|
+
if (!file) {
|
|
543
|
+
console.error("Error: Please provide a prompt file to render.");
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
const env = getFlag2(args, "--env");
|
|
547
|
+
const tier = getFlag2(args, "--tier");
|
|
548
|
+
const varsFile = getFlag2(args, "--vars");
|
|
549
|
+
const jsonOutput = args.includes("--json");
|
|
550
|
+
let variables = {};
|
|
551
|
+
if (varsFile) {
|
|
552
|
+
const varsContent = await readFile4(varsFile, "utf-8");
|
|
553
|
+
variables = JSON.parse(varsContent);
|
|
554
|
+
} else {
|
|
555
|
+
const sidecarPath = file.replace(/\.md$/, ".test.yaml");
|
|
556
|
+
if (existsSync(sidecarPath)) {
|
|
557
|
+
const { default: yaml } = await import("gray-matter");
|
|
558
|
+
const sidecarContent = await readFile4(sidecarPath, "utf-8");
|
|
559
|
+
const parsed = yaml(`---
|
|
560
|
+
${sidecarContent}---
|
|
561
|
+
`);
|
|
562
|
+
const data = parsed.data;
|
|
563
|
+
if (data.cases?.[0]?.variables) {
|
|
564
|
+
variables = data.cases[0].variables;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
const content = await readFile4(file, "utf-8");
|
|
569
|
+
const { asset } = parsePrompt(content, file);
|
|
570
|
+
const overridden = applyOverrides(asset, {
|
|
571
|
+
environment: env,
|
|
572
|
+
tier
|
|
573
|
+
});
|
|
574
|
+
const renderedSystem = overridden.sections?.system_instructions ? interpolate(overridden.sections.system_instructions, variables) : void 0;
|
|
575
|
+
const renderedPrompt = overridden.sections?.prompt_template ? interpolate(overridden.sections.prompt_template, variables) : void 0;
|
|
576
|
+
if (jsonOutput) {
|
|
577
|
+
console.log(JSON.stringify({
|
|
578
|
+
id: overridden.id,
|
|
579
|
+
provider: overridden.provider,
|
|
580
|
+
model: overridden.model,
|
|
581
|
+
system_instructions: renderedSystem,
|
|
582
|
+
prompt_template: renderedPrompt,
|
|
583
|
+
tools: overridden.tools
|
|
584
|
+
}, null, 2));
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const label = [
|
|
588
|
+
overridden.provider,
|
|
589
|
+
overridden.model,
|
|
590
|
+
[env, tier].filter(Boolean).join("/")
|
|
591
|
+
].filter(Boolean).join(", ");
|
|
592
|
+
console.log(`\u2500\u2500 ${overridden.id} (${label}) ${"\u2500".repeat(Math.max(0, 50 - overridden.id.length - label.length))}`);
|
|
593
|
+
if (renderedSystem) {
|
|
594
|
+
console.log(`System: ${renderedSystem.split("\n")[0]}${renderedSystem.includes("\n") ? "..." : ""}`);
|
|
595
|
+
}
|
|
596
|
+
if (renderedPrompt) {
|
|
597
|
+
console.log(`User: ${renderedPrompt.split("\n")[0]}${renderedPrompt.includes("\n") ? "..." : ""}`);
|
|
598
|
+
}
|
|
599
|
+
if (overridden.tools?.length) {
|
|
600
|
+
const toolNames = overridden.tools.map((t) => typeof t === "string" ? t : t.name);
|
|
601
|
+
console.log(`Tools: ${toolNames.join(", ")}`);
|
|
602
|
+
}
|
|
603
|
+
console.log(`Model: ${overridden.model ?? "not set"}`);
|
|
604
|
+
console.log("\u2500".repeat(60));
|
|
605
|
+
}
|
|
606
|
+
function getFlag2(args, flag) {
|
|
607
|
+
const idx = args.indexOf(flag);
|
|
608
|
+
if (idx >= 0 && idx + 1 < args.length) {
|
|
609
|
+
return args[idx + 1];
|
|
610
|
+
}
|
|
611
|
+
return void 0;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// src/cli/commands/inspect.ts
|
|
615
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
616
|
+
var HELP4 = `
|
|
617
|
+
promptopskit inspect <file>
|
|
618
|
+
|
|
619
|
+
Print the normalized prompt asset as JSON.
|
|
620
|
+
|
|
621
|
+
Options:
|
|
622
|
+
--help, -h Show this help
|
|
623
|
+
`.trim();
|
|
624
|
+
async function inspect(args) {
|
|
625
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
626
|
+
console.log(HELP4);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const file = args.find((a) => !a.startsWith("--"));
|
|
630
|
+
if (!file) {
|
|
631
|
+
console.error("Error: Please provide a prompt file to inspect.");
|
|
632
|
+
process.exit(1);
|
|
633
|
+
}
|
|
634
|
+
const content = await readFile5(file, "utf-8");
|
|
635
|
+
const { asset } = parsePrompt(content, file);
|
|
636
|
+
console.log(JSON.stringify(asset, null, 2));
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/cli/commands/init.ts
|
|
640
|
+
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
641
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
642
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
643
|
+
var HELP5 = `
|
|
644
|
+
promptopskit init [dir]
|
|
645
|
+
|
|
646
|
+
Scaffold a prompts directory with starter files.
|
|
647
|
+
|
|
648
|
+
Options:
|
|
649
|
+
--help, -h Show this help
|
|
650
|
+
`.trim();
|
|
651
|
+
var HELLO_PROMPT = `---
|
|
652
|
+
id: hello
|
|
653
|
+
schema_version: 1
|
|
654
|
+
provider: openai
|
|
655
|
+
model: gpt-5.4
|
|
656
|
+
context:
|
|
657
|
+
inputs:
|
|
658
|
+
- name
|
|
659
|
+
includes:
|
|
660
|
+
- ./shared/tone.md
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
# System instructions
|
|
664
|
+
|
|
665
|
+
You are a friendly assistant. Be helpful and concise.
|
|
666
|
+
|
|
667
|
+
# Prompt template
|
|
668
|
+
|
|
669
|
+
Say hello to {{ name }} and ask how you can help them today.
|
|
670
|
+
`.trimStart();
|
|
671
|
+
var TONE_INCLUDE = `---
|
|
672
|
+
id: shared.tone
|
|
673
|
+
schema_version: 1
|
|
674
|
+
---
|
|
675
|
+
|
|
676
|
+
# System instructions
|
|
677
|
+
|
|
678
|
+
Always be polite, professional, and concise. Avoid jargon unless the user uses it first.
|
|
679
|
+
`.trimStart();
|
|
680
|
+
var TEST_SIDECAR = `cases:
|
|
681
|
+
- name: basic-greeting
|
|
682
|
+
variables:
|
|
683
|
+
name: "World"
|
|
684
|
+
- name: named-greeting
|
|
685
|
+
variables:
|
|
686
|
+
name: "Alice"
|
|
687
|
+
`;
|
|
688
|
+
async function init(args) {
|
|
689
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
690
|
+
console.log(HELP5);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const dir = args.find((a) => !a.startsWith("--")) ?? "./prompts";
|
|
694
|
+
const files = [
|
|
695
|
+
{ path: join3(dir, "hello.md"), content: HELLO_PROMPT },
|
|
696
|
+
{ path: join3(dir, "hello.test.yaml"), content: TEST_SIDECAR },
|
|
697
|
+
{ path: join3(dir, "shared", "tone.md"), content: TONE_INCLUDE }
|
|
698
|
+
];
|
|
699
|
+
let created = 0;
|
|
700
|
+
let skipped = 0;
|
|
701
|
+
for (const file of files) {
|
|
702
|
+
if (existsSync2(file.path)) {
|
|
703
|
+
console.log(` skip ${file.path} (already exists)`);
|
|
704
|
+
skipped++;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
await mkdir2(dirname2(file.path), { recursive: true });
|
|
708
|
+
await writeFile2(file.path, file.content, "utf-8");
|
|
709
|
+
console.log(` \u2713 ${file.path}`);
|
|
710
|
+
created++;
|
|
711
|
+
}
|
|
712
|
+
console.log();
|
|
713
|
+
console.log(`Created ${created} file(s), skipped ${skipped} existing.`);
|
|
714
|
+
if (existsSync2("package.json")) {
|
|
715
|
+
try {
|
|
716
|
+
const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
|
|
717
|
+
if (!pkg.scripts?.["build:prompts"]) {
|
|
718
|
+
console.log();
|
|
719
|
+
console.log(`Tip: Add to your package.json scripts:`);
|
|
720
|
+
console.log(` "build:prompts": "promptopskit compile ${dir} ./dist/prompts"`);
|
|
721
|
+
}
|
|
722
|
+
} catch {
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/cli/index.ts
|
|
728
|
+
var HELP6 = `
|
|
729
|
+
promptopskit \u2014 Manage prompts, system instructions, tools, and model settings as code
|
|
730
|
+
|
|
731
|
+
Usage:
|
|
732
|
+
promptopskit <command> [options]
|
|
733
|
+
|
|
734
|
+
Commands:
|
|
735
|
+
init [dir] Scaffold a prompts directory with starter files
|
|
736
|
+
validate <dir> Validate prompt files
|
|
737
|
+
compile <src> <out> [options] Compile .md prompts to JSON/ESM artifacts
|
|
738
|
+
render <file> [options] Render a prompt preview
|
|
739
|
+
inspect <file> Print normalized prompt asset
|
|
740
|
+
|
|
741
|
+
Options:
|
|
742
|
+
--help, -h Show this help message
|
|
743
|
+
--version, -v Show version
|
|
744
|
+
|
|
745
|
+
Run promptopskit <command> --help for command-specific help.
|
|
746
|
+
`.trim();
|
|
747
|
+
async function main() {
|
|
748
|
+
const args = process.argv.slice(2);
|
|
749
|
+
const command = args[0];
|
|
750
|
+
if (!command || command === "--help" || command === "-h") {
|
|
751
|
+
console.log(HELP6);
|
|
752
|
+
process.exit(0);
|
|
753
|
+
}
|
|
754
|
+
if (command === "--version" || command === "-v") {
|
|
755
|
+
console.log("0.0.1");
|
|
756
|
+
process.exit(0);
|
|
757
|
+
}
|
|
758
|
+
const commandArgs = args.slice(1);
|
|
759
|
+
switch (command) {
|
|
760
|
+
case "init":
|
|
761
|
+
await init(commandArgs);
|
|
762
|
+
break;
|
|
763
|
+
case "validate":
|
|
764
|
+
await validate(commandArgs);
|
|
765
|
+
break;
|
|
766
|
+
case "compile":
|
|
767
|
+
await compile(commandArgs);
|
|
768
|
+
break;
|
|
769
|
+
case "render":
|
|
770
|
+
await render(commandArgs);
|
|
771
|
+
break;
|
|
772
|
+
case "inspect":
|
|
773
|
+
await inspect(commandArgs);
|
|
774
|
+
break;
|
|
775
|
+
default:
|
|
776
|
+
console.error(`Unknown command: ${command}`);
|
|
777
|
+
console.log(HELP6);
|
|
778
|
+
process.exit(1);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
main().catch((err) => {
|
|
782
|
+
console.error(err.message);
|
|
783
|
+
process.exit(1);
|
|
784
|
+
});
|
|
785
|
+
//# sourceMappingURL=index.js.map
|