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/README.md
CHANGED
|
@@ -4,21 +4,23 @@
|
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://nodejs.org)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Centralize prompts, system instructions, tools, and model settings — without leaving your codebase.**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Your prompts are already in Git. PromptOpsKit makes them manageable. It replaces hardcoded strings and scattered provider-specific glue with structured Markdown files where prompt text, model settings, sampling parameters, tool bindings, environment overrides, and composable shared instructions all live together — diffable, reviewable, and release-aware.
|
|
10
10
|
|
|
11
11
|
Provider adapters for OpenAI, Anthropic, Gemini, and OpenRouter produce a ready-to-send **request body only** — no HTTP client, no auth, no headers. Your application owns transport, so PromptOpsKit slots into any stack without opinions about how you call the API.
|
|
12
12
|
|
|
13
13
|
### Why PromptOpsKit?
|
|
14
14
|
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
15
|
+
- **Centralized, not scattered** — each prompt is a single Markdown file that captures prompt text, model config, tool bindings, and context rules together.
|
|
16
|
+
- **Operational, not just templated** — model name, temperature, reasoning effort, tools, and response format are declared alongside the prompt they govern.
|
|
17
|
+
- **Reusable, not duplicated** — `includes` lets you define shared tone, policy, or safety instructions once and compose them into any prompt.
|
|
18
|
+
- **Layered defaults, not repetition** — `defaults.md` in any folder sets shared `metadata` and `# System instructions` for that subtree, with nearest-folder override behavior.
|
|
19
|
+
- **Release-aware, not ad hoc** — environment and tier overrides swap models and parameters without forking prompt files.
|
|
20
|
+
- **Provider-portable** — write once, render for OpenAI, Anthropic, Gemini, or OpenRouter with correct body shapes.
|
|
19
21
|
- **Validate early** — Zod schema validation, Levenshtein-based "did you mean?" suggestions for typos, and variable usage checks catch mistakes before runtime.
|
|
20
22
|
- **Compile for production** — pre-compile `.md` to JSON or ESM so deployments skip parsing entirely.
|
|
21
|
-
- **
|
|
23
|
+
- **Repo-native, not dashboard-native** — no hosted service, no external admin tool. Everything lives in source control.
|
|
22
24
|
|
|
23
25
|
## Install
|
|
24
26
|
|
|
@@ -59,13 +61,14 @@ sampling:
|
|
|
59
61
|
context:
|
|
60
62
|
inputs:
|
|
61
63
|
- user_message
|
|
64
|
+
- app_context
|
|
62
65
|
includes:
|
|
63
66
|
- ./shared/tone.md
|
|
64
67
|
---
|
|
65
68
|
|
|
66
69
|
# System instructions
|
|
67
70
|
|
|
68
|
-
You are a helpful support assistant.
|
|
71
|
+
You are a helpful support assistant working in {{ app_context }}.
|
|
69
72
|
|
|
70
73
|
# Prompt template
|
|
71
74
|
|
|
@@ -82,7 +85,10 @@ const kit = createPromptOpsKit({ sourceDir: './prompts' });
|
|
|
82
85
|
const result = await kit.renderPrompt({
|
|
83
86
|
path: 'support/reply',
|
|
84
87
|
provider: 'openai',
|
|
85
|
-
variables: {
|
|
88
|
+
variables: {
|
|
89
|
+
user_message: 'How do I reset my password?',
|
|
90
|
+
app_context: 'Account settings page',
|
|
91
|
+
},
|
|
86
92
|
});
|
|
87
93
|
|
|
88
94
|
// result.request.body is ready for fetch()
|
|
@@ -101,11 +107,12 @@ const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
|
101
107
|
- **Prompts as Markdown** — YAML front matter for settings, H1 headings for sections (`# System instructions`, `# Prompt template`, `# Notes`)
|
|
102
108
|
- **Variable interpolation** — `{{ variable }}` syntax with strict and permissive modes
|
|
103
109
|
- **Composition** — `includes` to share system instructions across prompts, with circular detection
|
|
110
|
+
- **Folder defaults** — `defaults.md` inheritance for shared metadata and system instructions
|
|
104
111
|
- **Overrides** — Environment and tier-based overrides (base → env → tier → runtime)
|
|
105
112
|
- **4 provider adapters** — OpenAI, Anthropic, Gemini, OpenRouter — body-only output
|
|
106
113
|
- **Validation** — Zod schema validation, Levenshtein-based "did you mean?" for typos, variable usage checks
|
|
107
114
|
- **Caching** — LRU cache with mtime-based invalidation
|
|
108
|
-
- **CLI** — init, validate, compile, render, inspect
|
|
115
|
+
- **CLI** — init, validate, compile, render, inspect, skill
|
|
109
116
|
- **Compiled artifacts** — Pre-compile `.md` → JSON or ESM for production
|
|
110
117
|
|
|
111
118
|
## Provider Adapters
|
|
@@ -116,19 +123,35 @@ Each adapter produces a `{ body, provider, model }` object shaped for the target
|
|
|
116
123
|
// OpenAI
|
|
117
124
|
import { createPromptOpsKit } from 'promptopskit';
|
|
118
125
|
const kit = createPromptOpsKit({ sourceDir: './prompts' });
|
|
119
|
-
const { request } = await kit.renderPrompt({
|
|
126
|
+
const { request } = await kit.renderPrompt({
|
|
127
|
+
path: 'hello',
|
|
128
|
+
provider: 'openai',
|
|
129
|
+
variables: { name: 'World', app_context: 'Welcome screen' },
|
|
130
|
+
});
|
|
120
131
|
// request.body → { model, messages, temperature, reasoning_effort, ... }
|
|
121
132
|
|
|
122
133
|
// Anthropic — system is a top-level field, max_tokens defaults to 4096
|
|
123
|
-
const { request } = await kit.renderPrompt({
|
|
134
|
+
const { request } = await kit.renderPrompt({
|
|
135
|
+
path: 'hello',
|
|
136
|
+
provider: 'anthropic',
|
|
137
|
+
variables: { name: 'World', app_context: 'Welcome screen' },
|
|
138
|
+
});
|
|
124
139
|
// request.body → { model, messages, system, max_tokens, ... }
|
|
125
140
|
|
|
126
141
|
// Gemini — contents/systemInstruction/generationConfig structure
|
|
127
|
-
const { request } = await kit.renderPrompt({
|
|
142
|
+
const { request } = await kit.renderPrompt({
|
|
143
|
+
path: 'hello',
|
|
144
|
+
provider: 'gemini',
|
|
145
|
+
variables: { name: 'World', app_context: 'Welcome screen' },
|
|
146
|
+
});
|
|
128
147
|
// request.body → { contents, systemInstruction, generationConfig, ... }
|
|
129
148
|
|
|
130
149
|
// OpenRouter — same shape as OpenAI, different provider label
|
|
131
|
-
const { request } = await kit.renderPrompt({
|
|
150
|
+
const { request } = await kit.renderPrompt({
|
|
151
|
+
path: 'hello',
|
|
152
|
+
provider: 'openrouter',
|
|
153
|
+
variables: { name: 'World', app_context: 'Welcome screen' },
|
|
154
|
+
});
|
|
132
155
|
```
|
|
133
156
|
|
|
134
157
|
Provider adapters are also available as direct imports:
|
|
@@ -194,6 +217,26 @@ includes:
|
|
|
194
217
|
Handle support requests carefully.
|
|
195
218
|
```
|
|
196
219
|
|
|
220
|
+
## Folder defaults
|
|
221
|
+
|
|
222
|
+
Define a `defaults.md` file in `prompts/` (and optional subfolders) to provide inherited defaults for prompts:
|
|
223
|
+
|
|
224
|
+
- Shared `metadata` defaults in front matter
|
|
225
|
+
- Shared `# System instructions` in body
|
|
226
|
+
- Nearest subfolder `defaults.md` overrides parent defaults
|
|
227
|
+
- Prompt-local values always win over defaults
|
|
228
|
+
- Included files (`includes`) are not affected by folder defaults
|
|
229
|
+
|
|
230
|
+
> `promptopskit init` scaffolds a starter `defaults.md` in the prompts root.
|
|
231
|
+
|
|
232
|
+
```text
|
|
233
|
+
prompts/
|
|
234
|
+
├── defaults.md
|
|
235
|
+
└── support/
|
|
236
|
+
├── defaults.md
|
|
237
|
+
└── reply.md
|
|
238
|
+
```
|
|
239
|
+
|
|
197
240
|
## CLI
|
|
198
241
|
|
|
199
242
|
```bash
|
|
@@ -211,8 +254,34 @@ promptopskit render <file> [--env <name>] [--tier <name>] [--vars <file>] [--jso
|
|
|
211
254
|
|
|
212
255
|
# Print normalized asset as JSON
|
|
213
256
|
promptopskit inspect <file>
|
|
257
|
+
|
|
258
|
+
# Deploy AI agent instructions into your project
|
|
259
|
+
promptopskit skill [--target copilot|cursor|generic] [--force]
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## AI Agent Instructions
|
|
263
|
+
|
|
264
|
+
The `skill` command deploys a comprehensive instructions file so AI coding assistants (GitHub Copilot, Cursor, etc.) automatically understand how to create and manage prompts with promptopskit.
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
# Deploy for GitHub Copilot (default)
|
|
268
|
+
promptopskit skill
|
|
269
|
+
# → .github/instructions/promptopskit.instructions.md
|
|
270
|
+
|
|
271
|
+
# Deploy for Cursor
|
|
272
|
+
promptopskit skill --target cursor
|
|
273
|
+
# → .cursor/rules/promptopskit.mdc
|
|
274
|
+
|
|
275
|
+
# Deploy a generic instructions file
|
|
276
|
+
promptopskit skill --target generic
|
|
277
|
+
# → .ai/promptopskit-skill.md
|
|
278
|
+
|
|
279
|
+
# Overwrite an existing instructions file
|
|
280
|
+
promptopskit skill --force
|
|
214
281
|
```
|
|
215
282
|
|
|
283
|
+
The deployed file covers the prompt format, front matter schema, variable interpolation, includes, overrides, the TypeScript API, provider adapters, and project conventions — everything an AI agent needs to write correct prompts on the first try.
|
|
284
|
+
|
|
216
285
|
## Inline Source
|
|
217
286
|
|
|
218
287
|
Render prompts from strings without files:
|
|
@@ -290,7 +359,7 @@ Prompt files use YAML front matter with these fields:
|
|
|
290
359
|
|-------|------|-------------|
|
|
291
360
|
| `id` | `string` | Unique prompt identifier (required) |
|
|
292
361
|
| `schema_version` | `number` | Schema version, currently `1` |
|
|
293
|
-
| `provider` | `string` | `openai`, `anthropic`, `google
|
|
362
|
+
| `provider` | `string` | `openai`, `anthropic`, `gemini` (or `google`), `openrouter`, `any` |
|
|
294
363
|
| `model` | `string` | Model name |
|
|
295
364
|
| `fallback_models` | `string[]` | Fallback model list |
|
|
296
365
|
| `reasoning` | `object` | `{ effort, budget_tokens }` |
|
|
@@ -304,6 +373,10 @@ Prompt files use YAML front matter with these fields:
|
|
|
304
373
|
| `tiers` | `object` | Named tier overrides |
|
|
305
374
|
| `metadata` | `object` | `{ owner, tags, review_required, stable }` |
|
|
306
375
|
|
|
376
|
+
## Website
|
|
377
|
+
|
|
378
|
+
The `website/` directory contains a standalone marketing website for PromptOpsKit.
|
|
379
|
+
|
|
307
380
|
## License
|
|
308
381
|
|
|
309
382
|
[MIT](LICENSE)
|
|
@@ -62,11 +62,17 @@ var SectionsSchema = z.object({
|
|
|
62
62
|
prompt_template: z.string().optional(),
|
|
63
63
|
notes: z.string().optional()
|
|
64
64
|
});
|
|
65
|
+
var PromptDefaultsSchema = z.object({
|
|
66
|
+
metadata: MetadataSchema.optional(),
|
|
67
|
+
sections: z.object({
|
|
68
|
+
system_instructions: z.string().optional()
|
|
69
|
+
}).optional()
|
|
70
|
+
});
|
|
65
71
|
var PromptAssetSchema = z.object({
|
|
66
72
|
id: z.string(),
|
|
67
73
|
schema_version: z.number().int().positive().default(1),
|
|
68
74
|
description: z.string().optional(),
|
|
69
|
-
provider: z.enum(["openai", "anthropic", "google", "openrouter", "any"]).optional(),
|
|
75
|
+
provider: z.enum(["openai", "anthropic", "google", "gemini", "openrouter", "any"]).optional(),
|
|
70
76
|
model: z.string().optional(),
|
|
71
77
|
fallback_models: z.array(z.string()).optional(),
|
|
72
78
|
reasoning: ReasoningSchema.optional(),
|
|
@@ -144,9 +150,90 @@ function parsePrompt(content, filePath) {
|
|
|
144
150
|
|
|
145
151
|
// src/parser/loader.ts
|
|
146
152
|
import { readFile } from "fs/promises";
|
|
147
|
-
|
|
153
|
+
import { dirname, join, resolve } from "path";
|
|
154
|
+
import matter2 from "gray-matter";
|
|
155
|
+
var DEFAULTS_FILE_NAME = "defaults.md";
|
|
156
|
+
async function loadPromptFile(filePath, options = {}) {
|
|
148
157
|
const content = await readFile(filePath, "utf-8");
|
|
149
|
-
|
|
158
|
+
const parsed = parsePrompt(content, filePath);
|
|
159
|
+
const root = options.defaultsRoot ?? dirname(filePath);
|
|
160
|
+
const defaults = await loadDefaultsForPath(filePath, root);
|
|
161
|
+
const asset = applyDefaults(parsed.asset, defaults);
|
|
162
|
+
return {
|
|
163
|
+
...parsed,
|
|
164
|
+
asset
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
async function loadDefaultsForPath(filePath, defaultsRoot) {
|
|
168
|
+
const directories = getDirectoriesToCheck(filePath, defaultsRoot);
|
|
169
|
+
let merged = {};
|
|
170
|
+
for (const dir of directories) {
|
|
171
|
+
const defaultsPath = join(dir, DEFAULTS_FILE_NAME);
|
|
172
|
+
try {
|
|
173
|
+
const defaultsContent = await readFile(defaultsPath, "utf-8");
|
|
174
|
+
const defaults = parseDefaults(defaultsContent);
|
|
175
|
+
merged = mergeDefaults(merged, defaults);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (error.code !== "ENOENT") {
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return merged;
|
|
183
|
+
}
|
|
184
|
+
function getDirectoriesToCheck(filePath, defaultsRoot) {
|
|
185
|
+
const dirs = [];
|
|
186
|
+
let current = resolve(dirname(filePath));
|
|
187
|
+
const boundary = defaultsRoot ? resolve(defaultsRoot) : void 0;
|
|
188
|
+
while (true) {
|
|
189
|
+
dirs.unshift(current);
|
|
190
|
+
if (boundary && current === boundary || current === dirname(current)) {
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
current = dirname(current);
|
|
194
|
+
}
|
|
195
|
+
return dirs;
|
|
196
|
+
}
|
|
197
|
+
function parseDefaults(content) {
|
|
198
|
+
const { data: frontMatter, content: body } = matter2(content);
|
|
199
|
+
const sections = extractSections(body);
|
|
200
|
+
return PromptDefaultsSchema.parse({
|
|
201
|
+
...frontMatter,
|
|
202
|
+
sections: {
|
|
203
|
+
system_instructions: sections.system_instructions
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function mergeDefaults(base, local) {
|
|
208
|
+
return {
|
|
209
|
+
metadata: {
|
|
210
|
+
...base.metadata ?? {},
|
|
211
|
+
...local.metadata ?? {}
|
|
212
|
+
},
|
|
213
|
+
sections: {
|
|
214
|
+
...base.sections ?? {},
|
|
215
|
+
...local.sections ?? {}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function applyDefaults(asset, defaults) {
|
|
220
|
+
const hasDefaultMetadata = defaults.metadata && Object.keys(defaults.metadata).length > 0;
|
|
221
|
+
const hasDefaultSystem = !!defaults.sections?.system_instructions;
|
|
222
|
+
if (!hasDefaultMetadata && !hasDefaultSystem) {
|
|
223
|
+
return asset;
|
|
224
|
+
}
|
|
225
|
+
const mergedMetadata = {
|
|
226
|
+
...defaults.metadata ?? {},
|
|
227
|
+
...asset.metadata ?? {}
|
|
228
|
+
};
|
|
229
|
+
const metadata = Object.keys(mergedMetadata).length > 0 ? mergedMetadata : void 0;
|
|
230
|
+
const systemInstructions = asset.sections?.system_instructions ?? defaults.sections?.system_instructions;
|
|
231
|
+
const sections = asset.sections ? { ...asset.sections, system_instructions: systemInstructions } : systemInstructions ? { system_instructions: systemInstructions } : void 0;
|
|
232
|
+
return {
|
|
233
|
+
...asset,
|
|
234
|
+
metadata,
|
|
235
|
+
sections
|
|
236
|
+
};
|
|
150
237
|
}
|
|
151
238
|
|
|
152
239
|
export {
|
|
@@ -156,4 +243,4 @@ export {
|
|
|
156
243
|
parsePrompt,
|
|
157
244
|
loadPromptFile
|
|
158
245
|
};
|
|
159
|
-
//# sourceMappingURL=chunk-
|
|
246
|
+
//# sourceMappingURL=chunk-HMYPMHTG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema/schema.ts","../src/parser/sections.ts","../src/parser/parser.ts","../src/parser/loader.ts"],"sourcesContent":["import { z } from 'zod';\r\n\r\n// --- Tool definitions ---\r\n\r\nexport const InlineToolDefSchema = z.object({\r\n name: z.string(),\r\n description: z.string().optional(),\r\n input_schema: z.record(z.unknown()).optional(),\r\n});\r\n\r\nexport type InlineToolDef = z.infer<typeof InlineToolDefSchema>;\r\n\r\nexport const ToolRefSchema = z.union([z.string(), InlineToolDefSchema]);\r\n\r\n// --- MCP ---\r\n\r\nexport const MCPServerRefSchema = z.union([\r\n z.string(),\r\n z.object({\r\n name: z.string(),\r\n config: z.record(z.unknown()).optional(),\r\n }),\r\n]);\r\n\r\nexport type MCPServerRef = z.infer<typeof MCPServerRefSchema>;\r\n\r\n// --- Reasoning ---\r\n\r\nexport const ReasoningSchema = z.object({\r\n effort: z.enum(['low', 'medium', 'high']).optional(),\r\n budget_tokens: z.number().int().positive().optional(),\r\n});\r\n\r\n// --- Sampling ---\r\n\r\nexport const SamplingSchema = z.object({\r\n temperature: z.number().min(0).max(2).optional(),\r\n top_p: z.number().min(0).max(1).optional(),\r\n frequency_penalty: z.number().optional(),\r\n presence_penalty: z.number().optional(),\r\n stop: z.array(z.string()).optional(),\r\n max_output_tokens: z.number().int().positive().optional(),\r\n});\r\n\r\n// --- Response ---\r\n\r\nexport const ResponseSchema = z.object({\r\n format: z.enum(['text', 'json', 'markdown']).optional(),\r\n stream: z.boolean().optional(),\r\n});\r\n\r\n// --- Context ---\r\n\r\nexport const HistorySchema = z.object({\r\n max_items: z.number().int().positive().optional(),\r\n});\r\n\r\nexport const ContextSchema = z.object({\r\n inputs: z.array(z.string()).optional(),\r\n history: HistorySchema.optional(),\r\n});\r\n\r\n// --- Metadata ---\r\n\r\nexport const MetadataSchema = z.object({\r\n owner: z.string().optional(),\r\n tags: z.array(z.string()).optional(),\r\n review_required: z.boolean().optional(),\r\n stable: z.boolean().optional(),\r\n});\r\n\r\n// --- MCP block ---\r\n\r\nexport const MCPSchema = z.object({\r\n servers: z.array(MCPServerRefSchema).optional(),\r\n});\r\n\r\n// --- Overrides (subset allowed in environments/tiers) ---\r\n\r\nexport const PromptAssetOverridesSchema = z.object({\r\n model: z.string().optional(),\r\n fallback_models: z.array(z.string()).optional(),\r\n reasoning: ReasoningSchema.optional(),\r\n sampling: SamplingSchema.optional(),\r\n response: ResponseSchema.optional(),\r\n tools: z.array(ToolRefSchema).optional(),\r\n});\r\n\r\nexport type PromptAssetOverrides = z.infer<typeof PromptAssetOverridesSchema>;\r\n\r\n// --- Source tracking ---\r\n\r\nexport const SourceSchema = z.object({\r\n file_path: z.string().optional(),\r\n checksum: z.string().optional(),\r\n});\r\n\r\n// --- Sections (populated by parser) ---\r\n\r\nexport const SectionsSchema = z.object({\r\n system_instructions: z.string().optional(),\r\n prompt_template: z.string().optional(),\r\n notes: z.string().optional(),\r\n});\r\n\r\n// --- Defaults files (folder-level inheritance) ---\r\n\r\nexport const PromptDefaultsSchema = z.object({\r\n metadata: MetadataSchema.optional(),\r\n sections: z.object({\r\n system_instructions: z.string().optional(),\r\n }).optional(),\r\n});\r\n\r\nexport type PromptDefaults = z.infer<typeof PromptDefaultsSchema>;\r\n\r\n// --- Top-level PromptAsset ---\r\n\r\nexport const PromptAssetSchema = z.object({\r\n id: z.string(),\r\n schema_version: z.number().int().positive().default(1),\r\n description: z.string().optional(),\r\n\r\n provider: z.enum(['openai', 'anthropic', 'google', 'gemini', 'openrouter', 'any']).optional(),\r\n model: z.string().optional(),\r\n fallback_models: z.array(z.string()).optional(),\r\n\r\n reasoning: ReasoningSchema.optional(),\r\n sampling: SamplingSchema.optional(),\r\n response: ResponseSchema.optional(),\r\n\r\n tools: z.array(ToolRefSchema).optional(),\r\n mcp: MCPSchema.optional(),\r\n\r\n context: ContextSchema.optional(),\r\n\r\n includes: z.array(z.string()).optional(),\r\n\r\n environments: z.record(PromptAssetOverridesSchema).optional(),\r\n tiers: z.record(PromptAssetOverridesSchema).optional(),\r\n\r\n metadata: MetadataSchema.optional(),\r\n\r\n // Populated by parser, not authored in YAML\r\n sections: SectionsSchema.optional(),\r\n source: SourceSchema.optional(),\r\n});\r\n\r\nexport type PromptAsset = z.infer<typeof PromptAssetSchema>;\r\n\r\n// --- Resolved asset (after includes, overrides applied) ---\r\n\r\nexport interface ResolvedPromptAsset extends PromptAsset {\r\n sections: {\r\n system_instructions?: string;\r\n prompt_template?: string;\r\n notes?: string;\r\n };\r\n source: {\r\n file_path?: string;\r\n checksum?: string;\r\n };\r\n}\r\n","export interface Sections {\r\n system_instructions?: string;\r\n prompt_template?: string;\r\n notes?: string;\r\n}\r\n\r\nconst SECTION_MAP: Record<string, keyof Sections> = {\r\n 'system instructions': 'system_instructions',\r\n 'prompt template': 'prompt_template',\r\n 'notes': 'notes',\r\n};\r\n\r\n/**\r\n * Extract named sections from the markdown body using H1 headings.\r\n * Case-insensitive. H2+ within a section is treated as content.\r\n * If no H1 headings are found, the entire body is treated as prompt_template.\r\n */\r\nexport function extractSections(body: string): Sections {\r\n const lines = body.split('\\n');\r\n const sections: Sections = {};\r\n\r\n let currentKey: keyof Sections | null = null;\r\n let currentLines: string[] = [];\r\n let foundAnyH1 = false;\r\n\r\n for (const line of lines) {\r\n const h1Match = line.match(/^#\\s+(.+)$/);\r\n if (h1Match) {\r\n // Flush previous section\r\n if (currentKey) {\r\n sections[currentKey] = currentLines.join('\\n').trim();\r\n }\r\n\r\n foundAnyH1 = true;\r\n const heading = h1Match[1].trim().toLowerCase();\r\n currentKey = SECTION_MAP[heading] ?? null;\r\n currentLines = [];\r\n } else {\r\n currentLines.push(line);\r\n }\r\n }\r\n\r\n // Flush last section\r\n if (currentKey) {\r\n sections[currentKey] = currentLines.join('\\n').trim();\r\n }\r\n\r\n // If no H1 headings found, treat entire body as prompt_template\r\n if (!foundAnyH1) {\r\n const trimmed = body.trim();\r\n if (trimmed) {\r\n sections.prompt_template = trimmed;\r\n }\r\n }\r\n\r\n return sections;\r\n}\r\n","import matter from 'gray-matter';\r\nimport { PromptAssetSchema } from '../schema/index.js';\r\nimport type { PromptAsset } from '../schema/index.js';\r\nimport { extractSections } from './sections.js';\r\n\r\nexport interface ParseResult {\r\n asset: PromptAsset;\r\n raw: {\r\n frontMatter: Record<string, unknown>;\r\n body: string;\r\n };\r\n}\r\n\r\n/**\r\n * Parse a prompt markdown string (YAML front matter + markdown body)\r\n * into a validated PromptAsset.\r\n */\r\nexport function parsePrompt(content: string, filePath?: string): ParseResult {\r\n const { data: frontMatter, content: body } = matter(content);\r\n\r\n const sections = extractSections(body);\r\n\r\n const raw = {\r\n ...frontMatter,\r\n sections,\r\n source: filePath ? { file_path: filePath } : undefined,\r\n };\r\n\r\n const asset = PromptAssetSchema.parse(raw);\r\n\r\n return {\r\n asset,\r\n raw: {\r\n frontMatter: frontMatter as Record<string, unknown>,\r\n body,\r\n },\r\n };\r\n}\r\n","import { readFile } from 'node:fs/promises';\r\nimport { dirname, join, resolve } from 'node:path';\r\nimport matter from 'gray-matter';\r\nimport { parsePrompt } from './parser.js';\r\nimport type { ParseResult } from './parser.js';\r\nimport { extractSections } from './sections.js';\r\nimport { PromptDefaultsSchema } from '../schema/index.js';\r\nimport type { PromptDefaults } from '../schema/index.js';\r\n\r\nconst DEFAULTS_FILE_NAME = 'defaults.md';\r\n\r\nexport interface LoadPromptOptions {\r\n /**\r\n * Optional boundary directory for defaults discovery.\r\n * If provided, defaults are loaded from this directory down to the prompt directory.\r\n */\r\n defaultsRoot?: string;\r\n}\r\n\r\n/**\r\n * Load and parse a prompt file from disk.\r\n */\r\nexport async function loadPromptFile(filePath: string, options: LoadPromptOptions = {}): Promise<ParseResult> {\r\n const content = await readFile(filePath, 'utf-8');\r\n const parsed = parsePrompt(content, filePath);\r\n // Default the boundary to the file's own directory so traversal never\r\n // walks above the prompt tree when no explicit root is provided.\r\n const root = options.defaultsRoot ?? dirname(filePath);\r\n const defaults = await loadDefaultsForPath(filePath, root);\r\n const asset = applyDefaults(parsed.asset, defaults);\r\n\r\n return {\r\n ...parsed,\r\n asset,\r\n };\r\n}\r\n\r\nasync function loadDefaultsForPath(filePath: string, defaultsRoot?: string): Promise<PromptDefaults> {\r\n const directories = getDirectoriesToCheck(filePath, defaultsRoot);\r\n let merged: PromptDefaults = {};\r\n\r\n for (const dir of directories) {\r\n const defaultsPath = join(dir, DEFAULTS_FILE_NAME);\r\n try {\r\n const defaultsContent = await readFile(defaultsPath, 'utf-8');\r\n const defaults = parseDefaults(defaultsContent);\r\n merged = mergeDefaults(merged, defaults);\r\n } catch (error) {\r\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\r\n throw error;\r\n }\r\n }\r\n }\r\n\r\n return merged;\r\n}\r\n\r\nfunction getDirectoriesToCheck(filePath: string, defaultsRoot?: string): string[] {\r\n const dirs: string[] = [];\r\n let current = resolve(dirname(filePath));\r\n const boundary = defaultsRoot ? resolve(defaultsRoot) : undefined;\r\n\r\n while (true) {\r\n dirs.unshift(current);\r\n if ((boundary && current === boundary) || current === dirname(current)) {\r\n break;\r\n }\r\n current = dirname(current);\r\n }\r\n\r\n return dirs;\r\n}\r\n\r\nfunction parseDefaults(content: string): PromptDefaults {\r\n const { data: frontMatter, content: body } = matter(content);\r\n const sections = extractSections(body);\r\n\r\n return PromptDefaultsSchema.parse({\r\n ...frontMatter,\r\n sections: {\r\n system_instructions: sections.system_instructions,\r\n },\r\n });\r\n}\r\n\r\nfunction mergeDefaults(base: PromptDefaults, local: PromptDefaults): PromptDefaults {\r\n return {\r\n metadata: {\r\n ...(base.metadata ?? {}),\r\n ...(local.metadata ?? {}),\r\n },\r\n sections: {\r\n ...(base.sections ?? {}),\r\n ...(local.sections ?? {}),\r\n },\r\n };\r\n}\r\n\r\nfunction applyDefaults(asset: ParseResult['asset'], defaults: PromptDefaults): ParseResult['asset'] {\r\n const hasDefaultMetadata = defaults.metadata && Object.keys(defaults.metadata).length > 0;\r\n const hasDefaultSystem = !!defaults.sections?.system_instructions;\r\n\r\n // Short-circuit: nothing to merge\r\n if (!hasDefaultMetadata && !hasDefaultSystem) {\r\n return asset;\r\n }\r\n\r\n const mergedMetadata = {\r\n ...(defaults.metadata ?? {}),\r\n ...(asset.metadata ?? {}),\r\n };\r\n const metadata = Object.keys(mergedMetadata).length > 0 ? mergedMetadata : undefined;\r\n\r\n const systemInstructions = asset.sections?.system_instructions ?? defaults.sections?.system_instructions;\r\n\r\n const sections = asset.sections\r\n ? { ...asset.sections, system_instructions: systemInstructions }\r\n : systemInstructions\r\n ? { system_instructions: systemInstructions }\r\n : undefined;\r\n\r\n return {\r\n ...asset,\r\n metadata,\r\n sections,\r\n };\r\n}\r\n"],"mappings":"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","names":["matter","matter"]}
|