@anythingai/teleprompt 0.1.0 → 0.2.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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # teleprompt
2
2
 
3
- Compose LLM system prompts from discrete sections instead of monolithic template literals.
3
+ Composable, section-based LLM system prompts.
4
4
 
5
- Conditional logic, variants, and prompt changes stay co-located with their content. Adding a new flag is one section in one file, not a boolean threaded through 15 function signatures.
5
+ Each section owns its content and conditional logic. Sections can be reused across prompt variants, conditionally included, and rendered as plain text or XML.
6
6
 
7
7
  ```bash
8
8
  pnpm add @anythingai/teleprompt
@@ -11,35 +11,30 @@ pnpm add @anythingai/teleprompt
11
11
  ## Quick Start
12
12
 
13
13
  ```ts
14
- import { PromptBuilder, type PromptContext, type PromptSection } from '@anythingai/teleprompt';
14
+ import { PromptBuilder, section, type PromptContext } from '@anythingai/teleprompt';
15
15
 
16
16
  // Define your context shape
17
17
  type MyFlags = { webSearchEnabled: boolean };
18
18
  type MyVars = { assistantName: string };
19
19
  type MyContext = PromptContext<MyFlags, MyVars>;
20
20
 
21
- // Sections are objects with an id and a render function
22
- const identity: PromptSection<MyContext> = {
23
- id: 'identity',
24
- render: (ctx) => `You are ${ctx.vars.assistantName}, a helpful AI assistant.`,
25
- };
26
-
27
- // This is a static section with no context dependencies
28
- const guidelines: PromptSection<MyContext> = {
29
- id: 'guidelines',
30
- render: () => `# Guidelines
21
+ // Static section no context needed, no type parameter
22
+ const guidelines = section('guidelines', () => `# Guidelines
31
23
  - Be concise and direct.
32
24
  - Cite sources when making factual claims.
33
- - Ask for clarification when a request is ambiguous.`,
34
- };
25
+ - Ask for clarification when a request is ambiguous.`);
35
26
 
36
- // Conditional logic lives in the section, not threaded through function signatures
37
- const webSearch: PromptSection<MyContext> = {
38
- id: 'web-search',
39
- when: (ctx) => ctx.flags.webSearchEnabled,
40
- render: () => `You have access to web search. Use it when the user asks about
41
- current events or information that may have changed after your training cutoff.`,
42
- };
27
+ // Dynamic section uses context
28
+ const identity = section('identity', (ctx: MyContext) =>
29
+ `You are ${ctx.vars.assistantName}, a helpful AI assistant.`
30
+ );
31
+
32
+ // Conditional section return null to exclude
33
+ const webSearch = section('web-search', (ctx: MyContext) => {
34
+ if (!ctx.flags.webSearchEnabled) return null;
35
+ return `You have access to web search. Use it when the user asks about
36
+ current events or information that may have changed after your training cutoff.`;
37
+ });
43
38
 
44
39
  // Compose and build
45
40
  const prompt = new PromptBuilder<MyContext>()
@@ -54,21 +49,60 @@ const prompt = new PromptBuilder<MyContext>()
54
49
 
55
50
  ## Sections
56
51
 
57
- A section has an `id`, a `render` function, and optionally a `when` guard:
52
+ `section(id, render)` takes an id and a render function. Return a string to include, `null` to exclude:
58
53
 
59
54
  ```ts
60
- const citation: PromptSection<MyContext> = {
61
- id: 'citation',
62
- when: (ctx) => ctx.flags.citationEnabled, // excluded when false
63
- render: () => 'Always include citations with links when referencing external sources.',
64
- };
55
+ // Always included
56
+ const rules = section('rules', () => 'Be helpful and concise.');
57
+
58
+ // Conditional null means excluded
59
+ const citation = section('citation', (ctx: MyContext) => {
60
+ if (!ctx.flags.citationEnabled) return null;
61
+ return 'Always include citations with links when referencing external sources.';
62
+ });
65
63
  ```
66
64
 
67
65
  Sections render in the order you call `.use()`. To reorder, change the call order.
68
66
 
67
+ Static sections (no type parameter) work in any builder:
68
+
69
+ ```ts
70
+ const disclaimer = section('disclaimer', () => 'Responses are not legal advice.');
71
+
72
+ // Works in any builder regardless of context type
73
+ new PromptBuilder<MyContext>().use(disclaimer)
74
+ new PromptBuilder<OtherContext>().use(disclaimer)
75
+ ```
76
+
77
+ ## Context
78
+
79
+ Sections receive a typed context with boolean flags and arbitrary variables:
80
+
81
+ ```ts
82
+ type MyFlags = {
83
+ webSearchEnabled: boolean;
84
+ citationEnabled: boolean;
85
+ };
86
+
87
+ type MyVars = {
88
+ assistantName: string;
89
+ language: string;
90
+ };
91
+
92
+ type MyContext = PromptContext<MyFlags, MyVars>;
93
+ ```
94
+
95
+ `PromptContext` and `PromptBuilder` have defaults, so the type parameter is optional:
96
+
97
+ ```ts
98
+ const builder = new PromptBuilder();
99
+ ```
100
+
101
+ Build the context once and pass it to `.build(ctx)`. Every section receives the same object.
102
+
69
103
  ## Forking
70
104
 
71
- Create variants without duplicating prompt code:
105
+ Create variants from a shared base:
72
106
 
73
107
  ```ts
74
108
  const base = new PromptBuilder<MyContext>()
@@ -91,50 +125,96 @@ const codeAssistant = base.fork()
91
125
 
92
126
  Each fork is independent. Modifying one doesn't affect the others.
93
127
 
94
- ## Context
128
+ ## XML Format
95
129
 
96
- Sections receive a typed context with boolean flags and arbitrary variables:
130
+ Both Claude and Gemini recommend structuring prompts with XML tags. Pass `{ format: 'xml' }` to `.build()` to wrap each section in `<id>` tags:
97
131
 
98
132
  ```ts
99
- type MyFlags = {
100
- webSearchEnabled: boolean;
101
- citationEnabled: boolean;
102
- };
133
+ builder.build(ctx, { format: 'xml' })
134
+ ```
103
135
 
104
- type MyVars = {
105
- assistantName: string;
106
- language: string;
107
- };
136
+ The section id becomes the tag name. Content is left as-is inside the tags.
108
137
 
109
- type MyContext = PromptContext<MyFlags, MyVars>;
138
+ ## Groups
139
+
140
+ Group related sections together. In text mode, groups are transparent. In XML mode, they wrap children in tags:
141
+
142
+ ```ts
143
+ const prompt = new PromptBuilder<MyContext>()
144
+ .use(identity)
145
+ .group('tools', b => b
146
+ .use(webSearch)
147
+ .use(calculator)
148
+ )
149
+ .use(guidelines)
150
+ .build(ctx, { format: 'xml' });
110
151
  ```
111
152
 
112
- You build the context once and pass it to `.build(ctx)`. Every section receives the same object — no threading booleans through function signatures.
153
+ ```xml
154
+ <identity>
155
+ You are Daniel, a helpful AI assistant.
156
+ </identity>
157
+
158
+ <tools>
159
+ <web-search>
160
+ You have access to web search...
161
+ </web-search>
162
+
163
+ <calculator>
164
+ You can evaluate math expressions...
165
+ </calculator>
166
+ </tools>
167
+
168
+ <guidelines>
169
+ # Guidelines
170
+ ...
171
+ </guidelines>
172
+ ```
113
173
 
114
- ## Builder API
174
+ Groups can be nested:
115
175
 
116
176
  ```ts
117
- new PromptBuilder<MyContext>()
118
- // append a section (replaces if same id exists)
119
- .use(section)
177
+ builder.group('capabilities', b => b
178
+ .group('tools', b => b
179
+ .use(webSearch)
180
+ .use(calculator)
181
+ )
182
+ .group('integrations', b => b
183
+ .use(slack)
184
+ .use(linear)
185
+ )
186
+ )
187
+ ```
120
188
 
121
- // remove by section object or string id
122
- .without(section)
189
+ ## Mutually Exclusive Sections
123
190
 
124
- // check existence by section object or string id
125
- .has(section)
191
+ Use `.useOneOf()` when exactly one of several sections should render. The first candidate that returns a non-empty string wins:
126
192
 
127
- // list all section ids
128
- .ids()
193
+ ```ts
194
+ const hasTasks = section('has-tasks', (ctx: MyContext) => {
195
+ if (ctx.vars.tasks.length === 0) return null;
196
+ return `## Active Tasks\n\n${ctx.vars.tasks.map(t => `- ${t.title}`).join('\n')}`;
197
+ });
129
198
 
130
- // independent copy
131
- .fork()
199
+ const noTasks = section('no-tasks', () => '## Active Tasks\n\nNo tasks currently running.');
132
200
 
133
- // render to string
134
- .build(ctx)
201
+ builder.useOneOf(hasTasks, noTasks);
202
+ ```
135
203
 
136
- // render + debug info: { included: string[], excluded: string[] }
137
- .buildWithMeta(ctx)
204
+ ## Builder API
205
+
206
+ ```ts
207
+ new PromptBuilder<MyContext>()
208
+ .use(section) // append (replaces if same id)
209
+ .useOneOf(sectionA, sectionB) // first match wins
210
+ .group('name', b => b.use(...)) // named group (XML wrapper)
211
+ .without(section) // remove by object or string id
212
+ .has(section) // check existence
213
+ .ids() // list all section ids
214
+ .fork() // independent copy
215
+ .build(ctx) // render to string
216
+ .build(ctx, { format: 'xml' }) // render with XML tags
217
+ .buildWithMeta(ctx) // render + { included, excluded }
138
218
  ```
139
219
 
140
220
  ## Testing
package/dist/index.cjs CHANGED
@@ -1,6 +1,18 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); var _class;// src/builder.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/builder.ts
2
+ function assertNever(value) {
3
+ throw new Error(`Unexpected format: ${value}`);
4
+ }
5
+ function isSection(node) {
6
+ return "render" in node;
7
+ }
8
+ function isGroup(node) {
9
+ return "children" in node;
10
+ }
11
+ function isOneOf(node) {
12
+ return "candidates" in node;
13
+ }
2
14
  var PromptBuilder = (_class = class _PromptBuilder {constructor() { _class.prototype.__init.call(this); }
3
- __init() {this.sections = []}
15
+ __init() {this.nodes = []}
4
16
  /**
5
17
  * Add a section to the prompt.
6
18
  *
@@ -8,29 +20,67 @@ var PromptBuilder = (_class = class _PromptBuilder {constructor() { _class.proto
8
20
  * This makes `.use()` idempotent — you can safely call it multiple
9
21
  * times with the same section without creating duplicates.
10
22
  */
11
- use(section) {
12
- const existingIdx = this.sections.findIndex((s) => s.id === section.id);
13
- if (existingIdx >= 0) {
14
- this.sections[existingIdx] = section;
23
+ use(section2) {
24
+ const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === section2.id);
25
+ if (idx >= 0) {
26
+ this.nodes[idx] = section2;
15
27
  } else {
16
- this.sections.push(section);
28
+ this.nodes.push(section2);
17
29
  }
18
30
  return this;
19
31
  }
20
- /** Remove a section. Accepts an id string or a section object. */
32
+ /**
33
+ * Add mutually exclusive sections. The first candidate that renders
34
+ * a non-empty string wins — the rest are excluded.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * builder.useOneOf(activeTasks, noActiveTasks)
39
+ * ```
40
+ */
41
+ useOneOf(...candidates) {
42
+ this.nodes.push({ candidates });
43
+ return this;
44
+ }
45
+ /**
46
+ * Add a named group of sections. In `xml` format, children are
47
+ * wrapped in `<id>...</id>`. In `text` format, groups are transparent
48
+ * and children render as if they were top-level sections.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * builder.group('tools', b => b
53
+ * .use(bashSection)
54
+ * .use(gitSection)
55
+ * )
56
+ * ```
57
+ */
58
+ group(id, configure) {
59
+ const inner = new _PromptBuilder();
60
+ configure(inner);
61
+ const group = { id, children: inner.nodes };
62
+ const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === id);
63
+ if (idx >= 0) {
64
+ this.nodes[idx] = group;
65
+ } else {
66
+ this.nodes.push(group);
67
+ }
68
+ return this;
69
+ }
70
+ /** Remove a section or group. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */
21
71
  without(ref) {
22
72
  const id = typeof ref === "string" ? ref : ref.id;
23
- this.sections = this.sections.filter((s) => s.id !== id);
73
+ this.nodes = this.removeNode(this.nodes, id);
24
74
  return this;
25
75
  }
26
- /** Check if a section exists. Accepts an id string or a section object. */
76
+ /** Check if a section or group exists. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */
27
77
  has(ref) {
28
78
  const id = typeof ref === "string" ? ref : ref.id;
29
- return this.sections.some((s) => s.id === id);
79
+ return this.hasNode(this.nodes, id);
30
80
  }
31
- /** Get the ids of all registered sections (in insertion order). */
81
+ /** Get all ids (sections, groups, and oneOf candidates) in order. */
32
82
  ids() {
33
- return this.sections.map((s) => s.id);
83
+ return this.collectIds(this.nodes);
34
84
  }
35
85
  /**
36
86
  * Create an independent copy of this builder.
@@ -46,7 +96,7 @@ var PromptBuilder = (_class = class _PromptBuilder {constructor() { _class.proto
46
96
  */
47
97
  fork() {
48
98
  const forked = new _PromptBuilder();
49
- forked.sections = [...this.sections];
99
+ forked.nodes = this.deepCopy(this.nodes);
50
100
  return forked;
51
101
  }
52
102
  /**
@@ -56,9 +106,11 @@ var PromptBuilder = (_class = class _PromptBuilder {constructor() { _class.proto
56
106
  * 2. Renders each section
57
107
  * 3. Filters out empty strings
58
108
  * 4. Joins with separator and trims
109
+ *
110
+ * Pass `{ format: 'xml' }` to wrap each section in `<id>` tags.
59
111
  */
60
- build(ctx) {
61
- return this.buildWithMeta(ctx).prompt;
112
+ build(ctx, options) {
113
+ return this.buildWithMeta(ctx, options).prompt;
62
114
  }
63
115
  /**
64
116
  * Build the prompt and return metadata about which sections were
@@ -68,28 +120,156 @@ var PromptBuilder = (_class = class _PromptBuilder {constructor() { _class.proto
68
120
  * A section is "included" only if it passes the guard and renders
69
121
  * a non-empty string.
70
122
  */
71
- buildWithMeta(ctx) {
123
+ buildWithMeta(ctx, options) {
72
124
  const included = [];
73
125
  const excluded = [];
74
- const rendered = this.sections.filter((s) => {
75
- if (s.when && !s.when(ctx)) {
76
- excluded.push(s.id);
77
- return false;
126
+ const format = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _ => _.format]), () => ( "text"));
127
+ const parts = this.renderNodes(this.nodes, ctx, format, included, excluded);
128
+ const prompt = parts.join("\n\n").trim();
129
+ return { prompt, included, excluded };
130
+ }
131
+ // --- Private ---
132
+ renderNodes(nodes, ctx, format, included, excluded) {
133
+ const parts = [];
134
+ for (const node of nodes) {
135
+ if (isSection(node)) {
136
+ this.renderSection(node, ctx, format, parts, included, excluded);
137
+ } else if (isGroup(node)) {
138
+ const childParts = this.renderNodes(node.children, ctx, format, included, excluded);
139
+ if (childParts.length > 0) {
140
+ parts.push(...this.formatGroup(node.id, childParts, format));
141
+ }
142
+ } else {
143
+ this.renderOneOf(node, ctx, format, parts, included, excluded);
78
144
  }
145
+ }
146
+ return parts;
147
+ }
148
+ renderSection(section2, ctx, format, parts, included, excluded) {
149
+ if (section2.when && !section2.when(ctx)) {
150
+ excluded.push(section2.id);
151
+ return false;
152
+ }
153
+ const output = section2.render(ctx);
154
+ if (output) {
155
+ included.push(section2.id);
156
+ parts.push(this.formatSection(section2.id, output, format));
79
157
  return true;
80
- }).map((s) => {
81
- const output = s.render(ctx);
82
- if (output) {
83
- included.push(s.id);
158
+ }
159
+ excluded.push(section2.id);
160
+ return false;
161
+ }
162
+ renderOneOf(node, ctx, format, parts, included, excluded) {
163
+ let found = false;
164
+ for (const candidate of node.candidates) {
165
+ if (found) {
166
+ excluded.push(candidate.id);
167
+ continue;
168
+ }
169
+ if (this.renderSection(candidate, ctx, format, parts, included, excluded)) {
170
+ found = true;
171
+ }
172
+ }
173
+ }
174
+ /** Wrap a single section's rendered content according to the output format. */
175
+ formatSection(id, content, format) {
176
+ switch (format) {
177
+ case "text":
178
+ return content;
179
+ case "xml":
180
+ return `<${id}>
181
+ ${content}
182
+ </${id}>`;
183
+ default:
184
+ return assertNever(format);
185
+ }
186
+ }
187
+ /**
188
+ * Wrap a group's rendered children according to the output format.
189
+ * Returns an array — in text mode the children are spread inline,
190
+ * in xml mode they are joined and wrapped in a single entry.
191
+ */
192
+ formatGroup(id, childParts, format) {
193
+ switch (format) {
194
+ case "text":
195
+ return childParts;
196
+ case "xml":
197
+ return [`<${id}>
198
+ ${childParts.join("\n\n")}
199
+ </${id}>`];
200
+ default:
201
+ return assertNever(format);
202
+ }
203
+ }
204
+ hasNode(nodes, id) {
205
+ for (const node of nodes) {
206
+ if (isSection(node)) {
207
+ if (node.id === id) return true;
208
+ } else if (isGroup(node)) {
209
+ if (node.id === id) return true;
210
+ if (this.hasNode(node.children, id)) return true;
84
211
  } else {
85
- excluded.push(s.id);
212
+ for (const c of node.candidates) {
213
+ if (c.id === id) return true;
214
+ }
86
215
  }
87
- return output;
88
- }).filter(Boolean).join("\n\n").trim();
89
- return { prompt: rendered, included, excluded };
216
+ }
217
+ return false;
218
+ }
219
+ removeNode(nodes, id) {
220
+ const result = [];
221
+ for (const n of nodes) {
222
+ if (isSection(n)) {
223
+ if (n.id !== id) result.push(n);
224
+ } else if (isGroup(n)) {
225
+ if (n.id !== id) {
226
+ result.push({ ...n, children: this.removeNode(n.children, id) });
227
+ }
228
+ } else {
229
+ const remaining = n.candidates.filter((c) => c.id !== id);
230
+ if (remaining.length > 0) {
231
+ result.push({ candidates: remaining });
232
+ }
233
+ }
234
+ }
235
+ return result;
236
+ }
237
+ collectIds(nodes) {
238
+ const ids = [];
239
+ for (const node of nodes) {
240
+ if (isSection(node)) {
241
+ ids.push(node.id);
242
+ } else if (isGroup(node)) {
243
+ ids.push(node.id);
244
+ ids.push(...this.collectIds(node.children));
245
+ } else {
246
+ ids.push(...node.candidates.map((c) => c.id));
247
+ }
248
+ }
249
+ return ids;
250
+ }
251
+ deepCopy(nodes) {
252
+ return nodes.map((n) => {
253
+ if (isGroup(n)) {
254
+ return { ...n, children: this.deepCopy(n.children) };
255
+ }
256
+ if (isOneOf(n)) {
257
+ return { candidates: [...n.candidates] };
258
+ }
259
+ return n;
260
+ });
90
261
  }
91
262
  }, _class);
92
263
 
264
+ // src/section.ts
265
+ function section(id, render) {
266
+ return {
267
+ id,
268
+ render: (ctx) => _nullishCoalesce(render(ctx), () => ( ""))
269
+ };
270
+ }
271
+
272
+
93
273
 
94
- exports.PromptBuilder = PromptBuilder;
274
+ exports.PromptBuilder = PromptBuilder; exports.section = section;
95
275
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/asurve/dev/teleprompt/dist/index.cjs","../src/builder.ts"],"names":[],"mappings":"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","file":"/Users/asurve/dev/teleprompt/dist/index.cjs","sourcesContent":[null,"import type { PromptContext, PromptSection } from './types';\n\n/**\n * Declarative, composable prompt builder.\n *\n * Prompts are composed from discrete {@link PromptSection}s that are\n * independently testable pure functions. The builder handles ordering,\n * conditional inclusion, and final assembly.\n *\n * @example\n * ```ts\n * const prompt = new PromptBuilder()\n * .use(identitySection)\n * .use(rulesSection)\n * .use(featureSection)\n * .build(ctx);\n * ```\n */\nexport class PromptBuilder<\n TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>,\n> {\n private sections: PromptSection<TCtx>[] = [];\n\n /**\n * Add a section to the prompt.\n *\n * If a section with the same `id` already exists, it is replaced.\n * This makes `.use()` idempotent — you can safely call it multiple\n * times with the same section without creating duplicates.\n */\n use(section: PromptSection<TCtx>): this {\n const existingIdx = this.sections.findIndex((s) => s.id === section.id);\n if (existingIdx >= 0) {\n this.sections[existingIdx] = section;\n } else {\n this.sections.push(section);\n }\n return this;\n }\n\n /** Remove a section. Accepts an id string or a section object. */\n without(ref: string | { id: string }): this {\n const id = typeof ref === 'string' ? ref : ref.id;\n this.sections = this.sections.filter((s) => s.id !== id);\n return this;\n }\n\n /** Check if a section exists. Accepts an id string or a section object. */\n has(ref: string | { id: string }): boolean {\n const id = typeof ref === 'string' ? ref : ref.id;\n return this.sections.some((s) => s.id === id);\n }\n\n /** Get the ids of all registered sections (in insertion order). */\n ids(): string[] {\n return this.sections.map((s) => s.id);\n }\n\n /**\n * Create an independent copy of this builder.\n *\n * Use this to create mode-specific or model-specific variants\n * without mutating the base builder.\n *\n * @example\n * ```ts\n * const base = new PromptBuilder().use(a).use(b);\n * const variant = base.fork().use(c); // base is unchanged\n * ```\n */\n fork(): PromptBuilder<TCtx> {\n const forked = new PromptBuilder<TCtx>();\n forked.sections = [...this.sections];\n return forked;\n }\n\n /**\n * Build the final prompt string.\n *\n * 1. Filters out sections whose `when` guard returns false\n * 2. Renders each section\n * 3. Filters out empty strings\n * 4. Joins with separator and trims\n */\n build(ctx: TCtx): string {\n return this.buildWithMeta(ctx).prompt;\n }\n\n /**\n * Build the prompt and return metadata about which sections were\n * included/excluded. Useful for debugging and logging.\n *\n * A section is \"excluded\" if its `when` guard returns false.\n * A section is \"included\" only if it passes the guard and renders\n * a non-empty string.\n */\n buildWithMeta(ctx: TCtx): {\n prompt: string;\n included: string[];\n excluded: string[];\n } {\n const included: string[] = [];\n const excluded: string[] = [];\n\n const rendered = this.sections\n .filter((s) => {\n if (s.when && !s.when(ctx)) {\n excluded.push(s.id);\n return false;\n }\n return true;\n })\n .map((s) => {\n const output = s.render(ctx);\n if (output) {\n included.push(s.id);\n } else {\n excluded.push(s.id);\n }\n return output;\n })\n .filter(Boolean)\n .join('\\n\\n')\n .trim();\n\n return { prompt: rendered, included, excluded };\n }\n}\n"]}
1
+ {"version":3,"sources":["/Users/asurve/dev/teleprompt/dist/index.cjs","../src/builder.ts","../src/section.ts"],"names":["section"],"mappings":"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","file":"/Users/asurve/dev/teleprompt/dist/index.cjs","sourcesContent":[null,"import type { PromptContext, PromptSection } from './types';\n\nfunction assertNever(value: never): never {\n throw new Error(`Unexpected format: ${value}`);\n}\n\n/**\n * A named group of prompt nodes, rendered as an XML wrapper\n * in `xml` format and transparently in `text` format.\n */\ninterface PromptGroup<TCtx extends PromptContext> {\n id: string;\n children: PromptNode<TCtx>[];\n}\n\n/** Mutually exclusive sections — the first candidate that renders wins. */\ninterface PromptOneOf<TCtx extends PromptContext> {\n candidates: PromptSection<TCtx>[];\n}\n\ntype PromptNode<TCtx extends PromptContext> =\n | PromptSection<TCtx>\n | PromptGroup<TCtx>\n | PromptOneOf<TCtx>;\n\nfunction isSection<TCtx extends PromptContext>(\n node: PromptNode<TCtx>,\n): node is PromptSection<TCtx> {\n return 'render' in node;\n}\n\nfunction isGroup<TCtx extends PromptContext>(node: PromptNode<TCtx>): node is PromptGroup<TCtx> {\n return 'children' in node;\n}\n\nfunction isOneOf<TCtx extends PromptContext>(node: PromptNode<TCtx>): node is PromptOneOf<TCtx> {\n return 'candidates' in node;\n}\n\n/** Supported output formats for prompt rendering. */\nexport type PromptFormat = 'text' | 'xml';\n\n/** Options for {@link PromptBuilder.build} and {@link PromptBuilder.buildWithMeta}. */\nexport interface BuildOptions {\n /**\n * Output format.\n *\n * - `'text'` (default) — sections joined with `\\n\\n`, groups are transparent.\n * - `'xml'` — each section wrapped in `<id>...</id>` tags, groups\n * wrapped in `<id>...</id>` containing their children.\n */\n format?: PromptFormat;\n}\n\n/**\n * Declarative, composable prompt builder.\n *\n * Prompts are composed from discrete {@link PromptSection}s that are\n * independently testable pure functions. The builder handles ordering,\n * conditional inclusion, and final assembly.\n *\n * @example\n * ```ts\n * const prompt = new PromptBuilder()\n * .use(identitySection)\n * .use(rulesSection)\n * .use(featureSection)\n * .build(ctx);\n * ```\n */\nexport class PromptBuilder<TCtx extends PromptContext = PromptContext> {\n private nodes: PromptNode<TCtx>[] = [];\n\n /**\n * Add a section to the prompt.\n *\n * If a section with the same `id` already exists, it is replaced.\n * This makes `.use()` idempotent — you can safely call it multiple\n * times with the same section without creating duplicates.\n */\n use(section: PromptSection<TCtx>): this {\n const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === section.id);\n if (idx >= 0) {\n this.nodes[idx] = section;\n } else {\n this.nodes.push(section);\n }\n return this;\n }\n\n /**\n * Add mutually exclusive sections. The first candidate that renders\n * a non-empty string wins — the rest are excluded.\n *\n * @example\n * ```ts\n * builder.useOneOf(activeTasks, noActiveTasks)\n * ```\n */\n useOneOf(...candidates: PromptSection<TCtx>[]): this {\n this.nodes.push({ candidates });\n return this;\n }\n\n /**\n * Add a named group of sections. In `xml` format, children are\n * wrapped in `<id>...</id>`. In `text` format, groups are transparent\n * and children render as if they were top-level sections.\n *\n * @example\n * ```ts\n * builder.group('tools', b => b\n * .use(bashSection)\n * .use(gitSection)\n * )\n * ```\n */\n group(id: string, configure: (builder: PromptBuilder<TCtx>) => void): this {\n const inner = new PromptBuilder<TCtx>();\n configure(inner);\n const group: PromptGroup<TCtx> = { id, children: inner.nodes };\n const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === id);\n if (idx >= 0) {\n this.nodes[idx] = group;\n } else {\n this.nodes.push(group);\n }\n return this;\n }\n\n /** Remove a section or group. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */\n without(ref: string | { id: string }): this {\n const id = typeof ref === 'string' ? ref : ref.id;\n this.nodes = this.removeNode(this.nodes, id);\n return this;\n }\n\n /** Check if a section or group exists. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */\n has(ref: string | { id: string }): boolean {\n const id = typeof ref === 'string' ? ref : ref.id;\n return this.hasNode(this.nodes, id);\n }\n\n /** Get all ids (sections, groups, and oneOf candidates) in order. */\n ids(): string[] {\n return this.collectIds(this.nodes);\n }\n\n /**\n * Create an independent copy of this builder.\n *\n * Use this to create mode-specific or model-specific variants\n * without mutating the base builder.\n *\n * @example\n * ```ts\n * const base = new PromptBuilder().use(a).use(b);\n * const variant = base.fork().use(c); // base is unchanged\n * ```\n */\n fork(): PromptBuilder<TCtx> {\n const forked = new PromptBuilder<TCtx>();\n forked.nodes = this.deepCopy(this.nodes);\n return forked;\n }\n\n /**\n * Build the final prompt string.\n *\n * 1. Filters out sections whose `when` guard returns false\n * 2. Renders each section\n * 3. Filters out empty strings\n * 4. Joins with separator and trims\n *\n * Pass `{ format: 'xml' }` to wrap each section in `<id>` tags.\n */\n build(ctx: TCtx, options?: BuildOptions): string {\n return this.buildWithMeta(ctx, options).prompt;\n }\n\n /**\n * Build the prompt and return metadata about which sections were\n * included/excluded. Useful for debugging and logging.\n *\n * A section is \"excluded\" if its `when` guard returns false.\n * A section is \"included\" only if it passes the guard and renders\n * a non-empty string.\n */\n buildWithMeta(\n ctx: TCtx,\n options?: BuildOptions,\n ): {\n prompt: string;\n included: string[];\n excluded: string[];\n } {\n const included: string[] = [];\n const excluded: string[] = [];\n const format = options?.format ?? 'text';\n\n const parts = this.renderNodes(this.nodes, ctx, format, included, excluded);\n const prompt = parts.join('\\n\\n').trim();\n\n return { prompt, included, excluded };\n }\n\n // --- Private ---\n\n private renderNodes(\n nodes: PromptNode<TCtx>[],\n ctx: TCtx,\n format: PromptFormat,\n included: string[],\n excluded: string[],\n ): string[] {\n const parts: string[] = [];\n\n for (const node of nodes) {\n if (isSection(node)) {\n this.renderSection(node, ctx, format, parts, included, excluded);\n } else if (isGroup(node)) {\n const childParts = this.renderNodes(node.children, ctx, format, included, excluded);\n if (childParts.length > 0) {\n parts.push(...this.formatGroup(node.id, childParts, format));\n }\n } else {\n this.renderOneOf(node, ctx, format, parts, included, excluded);\n }\n }\n\n return parts;\n }\n\n private renderSection(\n section: PromptSection<TCtx>,\n ctx: TCtx,\n format: PromptFormat,\n parts: string[],\n included: string[],\n excluded: string[],\n ): boolean {\n if (section.when && !section.when(ctx)) {\n excluded.push(section.id);\n return false;\n }\n const output = section.render(ctx);\n if (output) {\n included.push(section.id);\n parts.push(this.formatSection(section.id, output, format));\n return true;\n }\n excluded.push(section.id);\n return false;\n }\n\n private renderOneOf(\n node: PromptOneOf<TCtx>,\n ctx: TCtx,\n format: PromptFormat,\n parts: string[],\n included: string[],\n excluded: string[],\n ): void {\n let found = false;\n for (const candidate of node.candidates) {\n if (found) {\n excluded.push(candidate.id);\n continue;\n }\n if (this.renderSection(candidate, ctx, format, parts, included, excluded)) {\n found = true;\n }\n }\n }\n\n /** Wrap a single section's rendered content according to the output format. */\n private formatSection(id: string, content: string, format: PromptFormat): string {\n switch (format) {\n case 'text':\n return content;\n case 'xml':\n return `<${id}>\\n${content}\\n</${id}>`;\n default:\n return assertNever(format);\n }\n }\n\n /**\n * Wrap a group's rendered children according to the output format.\n * Returns an array — in text mode the children are spread inline,\n * in xml mode they are joined and wrapped in a single entry.\n */\n private formatGroup(id: string, childParts: string[], format: PromptFormat): string[] {\n switch (format) {\n case 'text':\n return childParts;\n case 'xml':\n return [`<${id}>\\n${childParts.join('\\n\\n')}\\n</${id}>`];\n default:\n return assertNever(format);\n }\n }\n\n private hasNode(nodes: PromptNode<TCtx>[], id: string): boolean {\n for (const node of nodes) {\n if (isSection(node)) {\n if (node.id === id) return true;\n } else if (isGroup(node)) {\n if (node.id === id) return true;\n if (this.hasNode(node.children, id)) return true;\n } else {\n for (const c of node.candidates) {\n if (c.id === id) return true;\n }\n }\n }\n return false;\n }\n\n private removeNode(nodes: PromptNode<TCtx>[], id: string): PromptNode<TCtx>[] {\n const result: PromptNode<TCtx>[] = [];\n for (const n of nodes) {\n if (isSection(n)) {\n if (n.id !== id) result.push(n);\n } else if (isGroup(n)) {\n if (n.id !== id) {\n result.push({ ...n, children: this.removeNode(n.children, id) });\n }\n } else {\n const remaining = n.candidates.filter((c) => c.id !== id);\n if (remaining.length > 0) {\n result.push({ candidates: remaining });\n }\n }\n }\n return result;\n }\n\n private collectIds(nodes: PromptNode<TCtx>[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (isSection(node)) {\n ids.push(node.id);\n } else if (isGroup(node)) {\n ids.push(node.id);\n ids.push(...this.collectIds(node.children));\n } else {\n ids.push(...node.candidates.map((c) => c.id));\n }\n }\n return ids;\n }\n\n private deepCopy(nodes: PromptNode<TCtx>[]): PromptNode<TCtx>[] {\n return nodes.map((n) => {\n if (isGroup(n)) {\n return { ...n, children: this.deepCopy(n.children) };\n }\n if (isOneOf(n)) {\n return { candidates: [...n.candidates] };\n }\n return n;\n });\n }\n}\n","import type { PromptContext, PromptSection } from './types';\n\n/**\n * Create a prompt section. Return a string to include, `null` to exclude.\n *\n * @example\n * ```ts\n * // Static\n * section('identity', () => 'You are Coworker.')\n *\n * // Conditional — return null to exclude\n * section('prod', (ctx: SandboxCtx) => {\n * if (ctx.vars.prodContext == null) return null;\n * return `## Prod\\n\\n${ctx.vars.prodContext}`;\n * })\n * ```\n */\nexport function section<TCtx extends PromptContext = PromptContext>(\n id: string,\n render: (ctx: TCtx) => string | null,\n): PromptSection<TCtx> {\n return {\n id,\n render: (ctx) => render(ctx) ?? '',\n };\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,18 @@
1
- import { P as PromptContext, a as PromptSection } from './types-DXgCksVT.cjs';
1
+ import { P as PromptContext, a as PromptSection } from './types--sRnfw6Q.cjs';
2
2
 
3
+ /** Supported output formats for prompt rendering. */
4
+ type PromptFormat = 'text' | 'xml';
5
+ /** Options for {@link PromptBuilder.build} and {@link PromptBuilder.buildWithMeta}. */
6
+ interface BuildOptions {
7
+ /**
8
+ * Output format.
9
+ *
10
+ * - `'text'` (default) — sections joined with `\n\n`, groups are transparent.
11
+ * - `'xml'` — each section wrapped in `<id>...</id>` tags, groups
12
+ * wrapped in `<id>...</id>` containing their children.
13
+ */
14
+ format?: PromptFormat;
15
+ }
3
16
  /**
4
17
  * Declarative, composable prompt builder.
5
18
  *
@@ -16,8 +29,8 @@ import { P as PromptContext, a as PromptSection } from './types-DXgCksVT.cjs';
16
29
  * .build(ctx);
17
30
  * ```
18
31
  */
19
- declare class PromptBuilder<TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>> {
20
- private sections;
32
+ declare class PromptBuilder<TCtx extends PromptContext = PromptContext> {
33
+ private nodes;
21
34
  /**
22
35
  * Add a section to the prompt.
23
36
  *
@@ -26,15 +39,39 @@ declare class PromptBuilder<TCtx extends PromptContext<Record<string, boolean>,
26
39
  * times with the same section without creating duplicates.
27
40
  */
28
41
  use(section: PromptSection<TCtx>): this;
29
- /** Remove a section. Accepts an id string or a section object. */
42
+ /**
43
+ * Add mutually exclusive sections. The first candidate that renders
44
+ * a non-empty string wins — the rest are excluded.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * builder.useOneOf(activeTasks, noActiveTasks)
49
+ * ```
50
+ */
51
+ useOneOf(...candidates: PromptSection<TCtx>[]): this;
52
+ /**
53
+ * Add a named group of sections. In `xml` format, children are
54
+ * wrapped in `<id>...</id>`. In `text` format, groups are transparent
55
+ * and children render as if they were top-level sections.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * builder.group('tools', b => b
60
+ * .use(bashSection)
61
+ * .use(gitSection)
62
+ * )
63
+ * ```
64
+ */
65
+ group(id: string, configure: (builder: PromptBuilder<TCtx>) => void): this;
66
+ /** Remove a section or group. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */
30
67
  without(ref: string | {
31
68
  id: string;
32
69
  }): this;
33
- /** Check if a section exists. Accepts an id string or a section object. */
70
+ /** Check if a section or group exists. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */
34
71
  has(ref: string | {
35
72
  id: string;
36
73
  }): boolean;
37
- /** Get the ids of all registered sections (in insertion order). */
74
+ /** Get all ids (sections, groups, and oneOf candidates) in order. */
38
75
  ids(): string[];
39
76
  /**
40
77
  * Create an independent copy of this builder.
@@ -56,8 +93,10 @@ declare class PromptBuilder<TCtx extends PromptContext<Record<string, boolean>,
56
93
  * 2. Renders each section
57
94
  * 3. Filters out empty strings
58
95
  * 4. Joins with separator and trims
96
+ *
97
+ * Pass `{ format: 'xml' }` to wrap each section in `<id>` tags.
59
98
  */
60
- build(ctx: TCtx): string;
99
+ build(ctx: TCtx, options?: BuildOptions): string;
61
100
  /**
62
101
  * Build the prompt and return metadata about which sections were
63
102
  * included/excluded. Useful for debugging and logging.
@@ -66,11 +105,43 @@ declare class PromptBuilder<TCtx extends PromptContext<Record<string, boolean>,
66
105
  * A section is "included" only if it passes the guard and renders
67
106
  * a non-empty string.
68
107
  */
69
- buildWithMeta(ctx: TCtx): {
108
+ buildWithMeta(ctx: TCtx, options?: BuildOptions): {
70
109
  prompt: string;
71
110
  included: string[];
72
111
  excluded: string[];
73
112
  };
113
+ private renderNodes;
114
+ private renderSection;
115
+ private renderOneOf;
116
+ /** Wrap a single section's rendered content according to the output format. */
117
+ private formatSection;
118
+ /**
119
+ * Wrap a group's rendered children according to the output format.
120
+ * Returns an array — in text mode the children are spread inline,
121
+ * in xml mode they are joined and wrapped in a single entry.
122
+ */
123
+ private formatGroup;
124
+ private hasNode;
125
+ private removeNode;
126
+ private collectIds;
127
+ private deepCopy;
74
128
  }
75
129
 
76
- export { PromptBuilder, PromptContext, PromptSection };
130
+ /**
131
+ * Create a prompt section. Return a string to include, `null` to exclude.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * // Static
136
+ * section('identity', () => 'You are Coworker.')
137
+ *
138
+ * // Conditional — return null to exclude
139
+ * section('prod', (ctx: SandboxCtx) => {
140
+ * if (ctx.vars.prodContext == null) return null;
141
+ * return `## Prod\n\n${ctx.vars.prodContext}`;
142
+ * })
143
+ * ```
144
+ */
145
+ declare function section<TCtx extends PromptContext = PromptContext>(id: string, render: (ctx: TCtx) => string | null): PromptSection<TCtx>;
146
+
147
+ export { type BuildOptions, PromptBuilder, PromptContext, type PromptFormat, PromptSection, section };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,18 @@
1
- import { P as PromptContext, a as PromptSection } from './types-DXgCksVT.js';
1
+ import { P as PromptContext, a as PromptSection } from './types--sRnfw6Q.js';
2
2
 
3
+ /** Supported output formats for prompt rendering. */
4
+ type PromptFormat = 'text' | 'xml';
5
+ /** Options for {@link PromptBuilder.build} and {@link PromptBuilder.buildWithMeta}. */
6
+ interface BuildOptions {
7
+ /**
8
+ * Output format.
9
+ *
10
+ * - `'text'` (default) — sections joined with `\n\n`, groups are transparent.
11
+ * - `'xml'` — each section wrapped in `<id>...</id>` tags, groups
12
+ * wrapped in `<id>...</id>` containing their children.
13
+ */
14
+ format?: PromptFormat;
15
+ }
3
16
  /**
4
17
  * Declarative, composable prompt builder.
5
18
  *
@@ -16,8 +29,8 @@ import { P as PromptContext, a as PromptSection } from './types-DXgCksVT.js';
16
29
  * .build(ctx);
17
30
  * ```
18
31
  */
19
- declare class PromptBuilder<TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>> {
20
- private sections;
32
+ declare class PromptBuilder<TCtx extends PromptContext = PromptContext> {
33
+ private nodes;
21
34
  /**
22
35
  * Add a section to the prompt.
23
36
  *
@@ -26,15 +39,39 @@ declare class PromptBuilder<TCtx extends PromptContext<Record<string, boolean>,
26
39
  * times with the same section without creating duplicates.
27
40
  */
28
41
  use(section: PromptSection<TCtx>): this;
29
- /** Remove a section. Accepts an id string or a section object. */
42
+ /**
43
+ * Add mutually exclusive sections. The first candidate that renders
44
+ * a non-empty string wins — the rest are excluded.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * builder.useOneOf(activeTasks, noActiveTasks)
49
+ * ```
50
+ */
51
+ useOneOf(...candidates: PromptSection<TCtx>[]): this;
52
+ /**
53
+ * Add a named group of sections. In `xml` format, children are
54
+ * wrapped in `<id>...</id>`. In `text` format, groups are transparent
55
+ * and children render as if they were top-level sections.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * builder.group('tools', b => b
60
+ * .use(bashSection)
61
+ * .use(gitSection)
62
+ * )
63
+ * ```
64
+ */
65
+ group(id: string, configure: (builder: PromptBuilder<TCtx>) => void): this;
66
+ /** Remove a section or group. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */
30
67
  without(ref: string | {
31
68
  id: string;
32
69
  }): this;
33
- /** Check if a section exists. Accepts an id string or a section object. */
70
+ /** Check if a section or group exists. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */
34
71
  has(ref: string | {
35
72
  id: string;
36
73
  }): boolean;
37
- /** Get the ids of all registered sections (in insertion order). */
74
+ /** Get all ids (sections, groups, and oneOf candidates) in order. */
38
75
  ids(): string[];
39
76
  /**
40
77
  * Create an independent copy of this builder.
@@ -56,8 +93,10 @@ declare class PromptBuilder<TCtx extends PromptContext<Record<string, boolean>,
56
93
  * 2. Renders each section
57
94
  * 3. Filters out empty strings
58
95
  * 4. Joins with separator and trims
96
+ *
97
+ * Pass `{ format: 'xml' }` to wrap each section in `<id>` tags.
59
98
  */
60
- build(ctx: TCtx): string;
99
+ build(ctx: TCtx, options?: BuildOptions): string;
61
100
  /**
62
101
  * Build the prompt and return metadata about which sections were
63
102
  * included/excluded. Useful for debugging and logging.
@@ -66,11 +105,43 @@ declare class PromptBuilder<TCtx extends PromptContext<Record<string, boolean>,
66
105
  * A section is "included" only if it passes the guard and renders
67
106
  * a non-empty string.
68
107
  */
69
- buildWithMeta(ctx: TCtx): {
108
+ buildWithMeta(ctx: TCtx, options?: BuildOptions): {
70
109
  prompt: string;
71
110
  included: string[];
72
111
  excluded: string[];
73
112
  };
113
+ private renderNodes;
114
+ private renderSection;
115
+ private renderOneOf;
116
+ /** Wrap a single section's rendered content according to the output format. */
117
+ private formatSection;
118
+ /**
119
+ * Wrap a group's rendered children according to the output format.
120
+ * Returns an array — in text mode the children are spread inline,
121
+ * in xml mode they are joined and wrapped in a single entry.
122
+ */
123
+ private formatGroup;
124
+ private hasNode;
125
+ private removeNode;
126
+ private collectIds;
127
+ private deepCopy;
74
128
  }
75
129
 
76
- export { PromptBuilder, PromptContext, PromptSection };
130
+ /**
131
+ * Create a prompt section. Return a string to include, `null` to exclude.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * // Static
136
+ * section('identity', () => 'You are Coworker.')
137
+ *
138
+ * // Conditional — return null to exclude
139
+ * section('prod', (ctx: SandboxCtx) => {
140
+ * if (ctx.vars.prodContext == null) return null;
141
+ * return `## Prod\n\n${ctx.vars.prodContext}`;
142
+ * })
143
+ * ```
144
+ */
145
+ declare function section<TCtx extends PromptContext = PromptContext>(id: string, render: (ctx: TCtx) => string | null): PromptSection<TCtx>;
146
+
147
+ export { type BuildOptions, PromptBuilder, PromptContext, type PromptFormat, PromptSection, section };
package/dist/index.js CHANGED
@@ -1,6 +1,18 @@
1
1
  // src/builder.ts
2
+ function assertNever(value) {
3
+ throw new Error(`Unexpected format: ${value}`);
4
+ }
5
+ function isSection(node) {
6
+ return "render" in node;
7
+ }
8
+ function isGroup(node) {
9
+ return "children" in node;
10
+ }
11
+ function isOneOf(node) {
12
+ return "candidates" in node;
13
+ }
2
14
  var PromptBuilder = class _PromptBuilder {
3
- sections = [];
15
+ nodes = [];
4
16
  /**
5
17
  * Add a section to the prompt.
6
18
  *
@@ -8,29 +20,67 @@ var PromptBuilder = class _PromptBuilder {
8
20
  * This makes `.use()` idempotent — you can safely call it multiple
9
21
  * times with the same section without creating duplicates.
10
22
  */
11
- use(section) {
12
- const existingIdx = this.sections.findIndex((s) => s.id === section.id);
13
- if (existingIdx >= 0) {
14
- this.sections[existingIdx] = section;
23
+ use(section2) {
24
+ const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === section2.id);
25
+ if (idx >= 0) {
26
+ this.nodes[idx] = section2;
15
27
  } else {
16
- this.sections.push(section);
28
+ this.nodes.push(section2);
17
29
  }
18
30
  return this;
19
31
  }
20
- /** Remove a section. Accepts an id string or a section object. */
32
+ /**
33
+ * Add mutually exclusive sections. The first candidate that renders
34
+ * a non-empty string wins — the rest are excluded.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * builder.useOneOf(activeTasks, noActiveTasks)
39
+ * ```
40
+ */
41
+ useOneOf(...candidates) {
42
+ this.nodes.push({ candidates });
43
+ return this;
44
+ }
45
+ /**
46
+ * Add a named group of sections. In `xml` format, children are
47
+ * wrapped in `<id>...</id>`. In `text` format, groups are transparent
48
+ * and children render as if they were top-level sections.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * builder.group('tools', b => b
53
+ * .use(bashSection)
54
+ * .use(gitSection)
55
+ * )
56
+ * ```
57
+ */
58
+ group(id, configure) {
59
+ const inner = new _PromptBuilder();
60
+ configure(inner);
61
+ const group = { id, children: inner.nodes };
62
+ const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === id);
63
+ if (idx >= 0) {
64
+ this.nodes[idx] = group;
65
+ } else {
66
+ this.nodes.push(group);
67
+ }
68
+ return this;
69
+ }
70
+ /** Remove a section or group. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */
21
71
  without(ref) {
22
72
  const id = typeof ref === "string" ? ref : ref.id;
23
- this.sections = this.sections.filter((s) => s.id !== id);
73
+ this.nodes = this.removeNode(this.nodes, id);
24
74
  return this;
25
75
  }
26
- /** Check if a section exists. Accepts an id string or a section object. */
76
+ /** Check if a section or group exists. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */
27
77
  has(ref) {
28
78
  const id = typeof ref === "string" ? ref : ref.id;
29
- return this.sections.some((s) => s.id === id);
79
+ return this.hasNode(this.nodes, id);
30
80
  }
31
- /** Get the ids of all registered sections (in insertion order). */
81
+ /** Get all ids (sections, groups, and oneOf candidates) in order. */
32
82
  ids() {
33
- return this.sections.map((s) => s.id);
83
+ return this.collectIds(this.nodes);
34
84
  }
35
85
  /**
36
86
  * Create an independent copy of this builder.
@@ -46,7 +96,7 @@ var PromptBuilder = class _PromptBuilder {
46
96
  */
47
97
  fork() {
48
98
  const forked = new _PromptBuilder();
49
- forked.sections = [...this.sections];
99
+ forked.nodes = this.deepCopy(this.nodes);
50
100
  return forked;
51
101
  }
52
102
  /**
@@ -56,9 +106,11 @@ var PromptBuilder = class _PromptBuilder {
56
106
  * 2. Renders each section
57
107
  * 3. Filters out empty strings
58
108
  * 4. Joins with separator and trims
109
+ *
110
+ * Pass `{ format: 'xml' }` to wrap each section in `<id>` tags.
59
111
  */
60
- build(ctx) {
61
- return this.buildWithMeta(ctx).prompt;
112
+ build(ctx, options) {
113
+ return this.buildWithMeta(ctx, options).prompt;
62
114
  }
63
115
  /**
64
116
  * Build the prompt and return metadata about which sections were
@@ -68,28 +120,156 @@ var PromptBuilder = class _PromptBuilder {
68
120
  * A section is "included" only if it passes the guard and renders
69
121
  * a non-empty string.
70
122
  */
71
- buildWithMeta(ctx) {
123
+ buildWithMeta(ctx, options) {
72
124
  const included = [];
73
125
  const excluded = [];
74
- const rendered = this.sections.filter((s) => {
75
- if (s.when && !s.when(ctx)) {
76
- excluded.push(s.id);
77
- return false;
126
+ const format = options?.format ?? "text";
127
+ const parts = this.renderNodes(this.nodes, ctx, format, included, excluded);
128
+ const prompt = parts.join("\n\n").trim();
129
+ return { prompt, included, excluded };
130
+ }
131
+ // --- Private ---
132
+ renderNodes(nodes, ctx, format, included, excluded) {
133
+ const parts = [];
134
+ for (const node of nodes) {
135
+ if (isSection(node)) {
136
+ this.renderSection(node, ctx, format, parts, included, excluded);
137
+ } else if (isGroup(node)) {
138
+ const childParts = this.renderNodes(node.children, ctx, format, included, excluded);
139
+ if (childParts.length > 0) {
140
+ parts.push(...this.formatGroup(node.id, childParts, format));
141
+ }
142
+ } else {
143
+ this.renderOneOf(node, ctx, format, parts, included, excluded);
78
144
  }
145
+ }
146
+ return parts;
147
+ }
148
+ renderSection(section2, ctx, format, parts, included, excluded) {
149
+ if (section2.when && !section2.when(ctx)) {
150
+ excluded.push(section2.id);
151
+ return false;
152
+ }
153
+ const output = section2.render(ctx);
154
+ if (output) {
155
+ included.push(section2.id);
156
+ parts.push(this.formatSection(section2.id, output, format));
79
157
  return true;
80
- }).map((s) => {
81
- const output = s.render(ctx);
82
- if (output) {
83
- included.push(s.id);
158
+ }
159
+ excluded.push(section2.id);
160
+ return false;
161
+ }
162
+ renderOneOf(node, ctx, format, parts, included, excluded) {
163
+ let found = false;
164
+ for (const candidate of node.candidates) {
165
+ if (found) {
166
+ excluded.push(candidate.id);
167
+ continue;
168
+ }
169
+ if (this.renderSection(candidate, ctx, format, parts, included, excluded)) {
170
+ found = true;
171
+ }
172
+ }
173
+ }
174
+ /** Wrap a single section's rendered content according to the output format. */
175
+ formatSection(id, content, format) {
176
+ switch (format) {
177
+ case "text":
178
+ return content;
179
+ case "xml":
180
+ return `<${id}>
181
+ ${content}
182
+ </${id}>`;
183
+ default:
184
+ return assertNever(format);
185
+ }
186
+ }
187
+ /**
188
+ * Wrap a group's rendered children according to the output format.
189
+ * Returns an array — in text mode the children are spread inline,
190
+ * in xml mode they are joined and wrapped in a single entry.
191
+ */
192
+ formatGroup(id, childParts, format) {
193
+ switch (format) {
194
+ case "text":
195
+ return childParts;
196
+ case "xml":
197
+ return [`<${id}>
198
+ ${childParts.join("\n\n")}
199
+ </${id}>`];
200
+ default:
201
+ return assertNever(format);
202
+ }
203
+ }
204
+ hasNode(nodes, id) {
205
+ for (const node of nodes) {
206
+ if (isSection(node)) {
207
+ if (node.id === id) return true;
208
+ } else if (isGroup(node)) {
209
+ if (node.id === id) return true;
210
+ if (this.hasNode(node.children, id)) return true;
84
211
  } else {
85
- excluded.push(s.id);
212
+ for (const c of node.candidates) {
213
+ if (c.id === id) return true;
214
+ }
215
+ }
216
+ }
217
+ return false;
218
+ }
219
+ removeNode(nodes, id) {
220
+ const result = [];
221
+ for (const n of nodes) {
222
+ if (isSection(n)) {
223
+ if (n.id !== id) result.push(n);
224
+ } else if (isGroup(n)) {
225
+ if (n.id !== id) {
226
+ result.push({ ...n, children: this.removeNode(n.children, id) });
227
+ }
228
+ } else {
229
+ const remaining = n.candidates.filter((c) => c.id !== id);
230
+ if (remaining.length > 0) {
231
+ result.push({ candidates: remaining });
232
+ }
233
+ }
234
+ }
235
+ return result;
236
+ }
237
+ collectIds(nodes) {
238
+ const ids = [];
239
+ for (const node of nodes) {
240
+ if (isSection(node)) {
241
+ ids.push(node.id);
242
+ } else if (isGroup(node)) {
243
+ ids.push(node.id);
244
+ ids.push(...this.collectIds(node.children));
245
+ } else {
246
+ ids.push(...node.candidates.map((c) => c.id));
247
+ }
248
+ }
249
+ return ids;
250
+ }
251
+ deepCopy(nodes) {
252
+ return nodes.map((n) => {
253
+ if (isGroup(n)) {
254
+ return { ...n, children: this.deepCopy(n.children) };
255
+ }
256
+ if (isOneOf(n)) {
257
+ return { candidates: [...n.candidates] };
86
258
  }
87
- return output;
88
- }).filter(Boolean).join("\n\n").trim();
89
- return { prompt: rendered, included, excluded };
259
+ return n;
260
+ });
90
261
  }
91
262
  };
263
+
264
+ // src/section.ts
265
+ function section(id, render) {
266
+ return {
267
+ id,
268
+ render: (ctx) => render(ctx) ?? ""
269
+ };
270
+ }
92
271
  export {
93
- PromptBuilder
272
+ PromptBuilder,
273
+ section
94
274
  };
95
275
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/builder.ts"],"sourcesContent":["import type { PromptContext, PromptSection } from './types';\n\n/**\n * Declarative, composable prompt builder.\n *\n * Prompts are composed from discrete {@link PromptSection}s that are\n * independently testable pure functions. The builder handles ordering,\n * conditional inclusion, and final assembly.\n *\n * @example\n * ```ts\n * const prompt = new PromptBuilder()\n * .use(identitySection)\n * .use(rulesSection)\n * .use(featureSection)\n * .build(ctx);\n * ```\n */\nexport class PromptBuilder<\n TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>,\n> {\n private sections: PromptSection<TCtx>[] = [];\n\n /**\n * Add a section to the prompt.\n *\n * If a section with the same `id` already exists, it is replaced.\n * This makes `.use()` idempotent — you can safely call it multiple\n * times with the same section without creating duplicates.\n */\n use(section: PromptSection<TCtx>): this {\n const existingIdx = this.sections.findIndex((s) => s.id === section.id);\n if (existingIdx >= 0) {\n this.sections[existingIdx] = section;\n } else {\n this.sections.push(section);\n }\n return this;\n }\n\n /** Remove a section. Accepts an id string or a section object. */\n without(ref: string | { id: string }): this {\n const id = typeof ref === 'string' ? ref : ref.id;\n this.sections = this.sections.filter((s) => s.id !== id);\n return this;\n }\n\n /** Check if a section exists. Accepts an id string or a section object. */\n has(ref: string | { id: string }): boolean {\n const id = typeof ref === 'string' ? ref : ref.id;\n return this.sections.some((s) => s.id === id);\n }\n\n /** Get the ids of all registered sections (in insertion order). */\n ids(): string[] {\n return this.sections.map((s) => s.id);\n }\n\n /**\n * Create an independent copy of this builder.\n *\n * Use this to create mode-specific or model-specific variants\n * without mutating the base builder.\n *\n * @example\n * ```ts\n * const base = new PromptBuilder().use(a).use(b);\n * const variant = base.fork().use(c); // base is unchanged\n * ```\n */\n fork(): PromptBuilder<TCtx> {\n const forked = new PromptBuilder<TCtx>();\n forked.sections = [...this.sections];\n return forked;\n }\n\n /**\n * Build the final prompt string.\n *\n * 1. Filters out sections whose `when` guard returns false\n * 2. Renders each section\n * 3. Filters out empty strings\n * 4. Joins with separator and trims\n */\n build(ctx: TCtx): string {\n return this.buildWithMeta(ctx).prompt;\n }\n\n /**\n * Build the prompt and return metadata about which sections were\n * included/excluded. Useful for debugging and logging.\n *\n * A section is \"excluded\" if its `when` guard returns false.\n * A section is \"included\" only if it passes the guard and renders\n * a non-empty string.\n */\n buildWithMeta(ctx: TCtx): {\n prompt: string;\n included: string[];\n excluded: string[];\n } {\n const included: string[] = [];\n const excluded: string[] = [];\n\n const rendered = this.sections\n .filter((s) => {\n if (s.when && !s.when(ctx)) {\n excluded.push(s.id);\n return false;\n }\n return true;\n })\n .map((s) => {\n const output = s.render(ctx);\n if (output) {\n included.push(s.id);\n } else {\n excluded.push(s.id);\n }\n return output;\n })\n .filter(Boolean)\n .join('\\n\\n')\n .trim();\n\n return { prompt: rendered, included, excluded };\n }\n}\n"],"mappings":";AAkBO,IAAM,gBAAN,MAAM,eAEX;AAAA,EACQ,WAAkC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS3C,IAAI,SAAoC;AACtC,UAAM,cAAc,KAAK,SAAS,UAAU,CAAC,MAAM,EAAE,OAAO,QAAQ,EAAE;AACtE,QAAI,eAAe,GAAG;AACpB,WAAK,SAAS,WAAW,IAAI;AAAA,IAC/B,OAAO;AACL,WAAK,SAAS,KAAK,OAAO;AAAA,IAC5B;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,QAAQ,KAAoC;AAC1C,UAAM,KAAK,OAAO,QAAQ,WAAW,MAAM,IAAI;AAC/C,SAAK,WAAW,KAAK,SAAS,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AACvD,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,IAAI,KAAuC;AACzC,UAAM,KAAK,OAAO,QAAQ,WAAW,MAAM,IAAI;AAC/C,WAAO,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAAA,EAC9C;AAAA;AAAA,EAGA,MAAgB;AACd,WAAO,KAAK,SAAS,IAAI,CAAC,MAAM,EAAE,EAAE;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,OAA4B;AAC1B,UAAM,SAAS,IAAI,eAAoB;AACvC,WAAO,WAAW,CAAC,GAAG,KAAK,QAAQ;AACnC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,KAAmB;AACvB,WAAO,KAAK,cAAc,GAAG,EAAE;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,cAAc,KAIZ;AACA,UAAM,WAAqB,CAAC;AAC5B,UAAM,WAAqB,CAAC;AAE5B,UAAM,WAAW,KAAK,SACnB,OAAO,CAAC,MAAM;AACb,UAAI,EAAE,QAAQ,CAAC,EAAE,KAAK,GAAG,GAAG;AAC1B,iBAAS,KAAK,EAAE,EAAE;AAClB,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,CAAC,EACA,IAAI,CAAC,MAAM;AACV,YAAM,SAAS,EAAE,OAAO,GAAG;AAC3B,UAAI,QAAQ;AACV,iBAAS,KAAK,EAAE,EAAE;AAAA,MACpB,OAAO;AACL,iBAAS,KAAK,EAAE,EAAE;AAAA,MACpB;AACA,aAAO;AAAA,IACT,CAAC,EACA,OAAO,OAAO,EACd,KAAK,MAAM,EACX,KAAK;AAER,WAAO,EAAE,QAAQ,UAAU,UAAU,SAAS;AAAA,EAChD;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/builder.ts","../src/section.ts"],"sourcesContent":["import type { PromptContext, PromptSection } from './types';\n\nfunction assertNever(value: never): never {\n throw new Error(`Unexpected format: ${value}`);\n}\n\n/**\n * A named group of prompt nodes, rendered as an XML wrapper\n * in `xml` format and transparently in `text` format.\n */\ninterface PromptGroup<TCtx extends PromptContext> {\n id: string;\n children: PromptNode<TCtx>[];\n}\n\n/** Mutually exclusive sections — the first candidate that renders wins. */\ninterface PromptOneOf<TCtx extends PromptContext> {\n candidates: PromptSection<TCtx>[];\n}\n\ntype PromptNode<TCtx extends PromptContext> =\n | PromptSection<TCtx>\n | PromptGroup<TCtx>\n | PromptOneOf<TCtx>;\n\nfunction isSection<TCtx extends PromptContext>(\n node: PromptNode<TCtx>,\n): node is PromptSection<TCtx> {\n return 'render' in node;\n}\n\nfunction isGroup<TCtx extends PromptContext>(node: PromptNode<TCtx>): node is PromptGroup<TCtx> {\n return 'children' in node;\n}\n\nfunction isOneOf<TCtx extends PromptContext>(node: PromptNode<TCtx>): node is PromptOneOf<TCtx> {\n return 'candidates' in node;\n}\n\n/** Supported output formats for prompt rendering. */\nexport type PromptFormat = 'text' | 'xml';\n\n/** Options for {@link PromptBuilder.build} and {@link PromptBuilder.buildWithMeta}. */\nexport interface BuildOptions {\n /**\n * Output format.\n *\n * - `'text'` (default) — sections joined with `\\n\\n`, groups are transparent.\n * - `'xml'` — each section wrapped in `<id>...</id>` tags, groups\n * wrapped in `<id>...</id>` containing their children.\n */\n format?: PromptFormat;\n}\n\n/**\n * Declarative, composable prompt builder.\n *\n * Prompts are composed from discrete {@link PromptSection}s that are\n * independently testable pure functions. The builder handles ordering,\n * conditional inclusion, and final assembly.\n *\n * @example\n * ```ts\n * const prompt = new PromptBuilder()\n * .use(identitySection)\n * .use(rulesSection)\n * .use(featureSection)\n * .build(ctx);\n * ```\n */\nexport class PromptBuilder<TCtx extends PromptContext = PromptContext> {\n private nodes: PromptNode<TCtx>[] = [];\n\n /**\n * Add a section to the prompt.\n *\n * If a section with the same `id` already exists, it is replaced.\n * This makes `.use()` idempotent — you can safely call it multiple\n * times with the same section without creating duplicates.\n */\n use(section: PromptSection<TCtx>): this {\n const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === section.id);\n if (idx >= 0) {\n this.nodes[idx] = section;\n } else {\n this.nodes.push(section);\n }\n return this;\n }\n\n /**\n * Add mutually exclusive sections. The first candidate that renders\n * a non-empty string wins — the rest are excluded.\n *\n * @example\n * ```ts\n * builder.useOneOf(activeTasks, noActiveTasks)\n * ```\n */\n useOneOf(...candidates: PromptSection<TCtx>[]): this {\n this.nodes.push({ candidates });\n return this;\n }\n\n /**\n * Add a named group of sections. In `xml` format, children are\n * wrapped in `<id>...</id>`. In `text` format, groups are transparent\n * and children render as if they were top-level sections.\n *\n * @example\n * ```ts\n * builder.group('tools', b => b\n * .use(bashSection)\n * .use(gitSection)\n * )\n * ```\n */\n group(id: string, configure: (builder: PromptBuilder<TCtx>) => void): this {\n const inner = new PromptBuilder<TCtx>();\n configure(inner);\n const group: PromptGroup<TCtx> = { id, children: inner.nodes };\n const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === id);\n if (idx >= 0) {\n this.nodes[idx] = group;\n } else {\n this.nodes.push(group);\n }\n return this;\n }\n\n /** Remove a section or group. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */\n without(ref: string | { id: string }): this {\n const id = typeof ref === 'string' ? ref : ref.id;\n this.nodes = this.removeNode(this.nodes, id);\n return this;\n }\n\n /** Check if a section or group exists. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */\n has(ref: string | { id: string }): boolean {\n const id = typeof ref === 'string' ? ref : ref.id;\n return this.hasNode(this.nodes, id);\n }\n\n /** Get all ids (sections, groups, and oneOf candidates) in order. */\n ids(): string[] {\n return this.collectIds(this.nodes);\n }\n\n /**\n * Create an independent copy of this builder.\n *\n * Use this to create mode-specific or model-specific variants\n * without mutating the base builder.\n *\n * @example\n * ```ts\n * const base = new PromptBuilder().use(a).use(b);\n * const variant = base.fork().use(c); // base is unchanged\n * ```\n */\n fork(): PromptBuilder<TCtx> {\n const forked = new PromptBuilder<TCtx>();\n forked.nodes = this.deepCopy(this.nodes);\n return forked;\n }\n\n /**\n * Build the final prompt string.\n *\n * 1. Filters out sections whose `when` guard returns false\n * 2. Renders each section\n * 3. Filters out empty strings\n * 4. Joins with separator and trims\n *\n * Pass `{ format: 'xml' }` to wrap each section in `<id>` tags.\n */\n build(ctx: TCtx, options?: BuildOptions): string {\n return this.buildWithMeta(ctx, options).prompt;\n }\n\n /**\n * Build the prompt and return metadata about which sections were\n * included/excluded. Useful for debugging and logging.\n *\n * A section is \"excluded\" if its `when` guard returns false.\n * A section is \"included\" only if it passes the guard and renders\n * a non-empty string.\n */\n buildWithMeta(\n ctx: TCtx,\n options?: BuildOptions,\n ): {\n prompt: string;\n included: string[];\n excluded: string[];\n } {\n const included: string[] = [];\n const excluded: string[] = [];\n const format = options?.format ?? 'text';\n\n const parts = this.renderNodes(this.nodes, ctx, format, included, excluded);\n const prompt = parts.join('\\n\\n').trim();\n\n return { prompt, included, excluded };\n }\n\n // --- Private ---\n\n private renderNodes(\n nodes: PromptNode<TCtx>[],\n ctx: TCtx,\n format: PromptFormat,\n included: string[],\n excluded: string[],\n ): string[] {\n const parts: string[] = [];\n\n for (const node of nodes) {\n if (isSection(node)) {\n this.renderSection(node, ctx, format, parts, included, excluded);\n } else if (isGroup(node)) {\n const childParts = this.renderNodes(node.children, ctx, format, included, excluded);\n if (childParts.length > 0) {\n parts.push(...this.formatGroup(node.id, childParts, format));\n }\n } else {\n this.renderOneOf(node, ctx, format, parts, included, excluded);\n }\n }\n\n return parts;\n }\n\n private renderSection(\n section: PromptSection<TCtx>,\n ctx: TCtx,\n format: PromptFormat,\n parts: string[],\n included: string[],\n excluded: string[],\n ): boolean {\n if (section.when && !section.when(ctx)) {\n excluded.push(section.id);\n return false;\n }\n const output = section.render(ctx);\n if (output) {\n included.push(section.id);\n parts.push(this.formatSection(section.id, output, format));\n return true;\n }\n excluded.push(section.id);\n return false;\n }\n\n private renderOneOf(\n node: PromptOneOf<TCtx>,\n ctx: TCtx,\n format: PromptFormat,\n parts: string[],\n included: string[],\n excluded: string[],\n ): void {\n let found = false;\n for (const candidate of node.candidates) {\n if (found) {\n excluded.push(candidate.id);\n continue;\n }\n if (this.renderSection(candidate, ctx, format, parts, included, excluded)) {\n found = true;\n }\n }\n }\n\n /** Wrap a single section's rendered content according to the output format. */\n private formatSection(id: string, content: string, format: PromptFormat): string {\n switch (format) {\n case 'text':\n return content;\n case 'xml':\n return `<${id}>\\n${content}\\n</${id}>`;\n default:\n return assertNever(format);\n }\n }\n\n /**\n * Wrap a group's rendered children according to the output format.\n * Returns an array — in text mode the children are spread inline,\n * in xml mode they are joined and wrapped in a single entry.\n */\n private formatGroup(id: string, childParts: string[], format: PromptFormat): string[] {\n switch (format) {\n case 'text':\n return childParts;\n case 'xml':\n return [`<${id}>\\n${childParts.join('\\n\\n')}\\n</${id}>`];\n default:\n return assertNever(format);\n }\n }\n\n private hasNode(nodes: PromptNode<TCtx>[], id: string): boolean {\n for (const node of nodes) {\n if (isSection(node)) {\n if (node.id === id) return true;\n } else if (isGroup(node)) {\n if (node.id === id) return true;\n if (this.hasNode(node.children, id)) return true;\n } else {\n for (const c of node.candidates) {\n if (c.id === id) return true;\n }\n }\n }\n return false;\n }\n\n private removeNode(nodes: PromptNode<TCtx>[], id: string): PromptNode<TCtx>[] {\n const result: PromptNode<TCtx>[] = [];\n for (const n of nodes) {\n if (isSection(n)) {\n if (n.id !== id) result.push(n);\n } else if (isGroup(n)) {\n if (n.id !== id) {\n result.push({ ...n, children: this.removeNode(n.children, id) });\n }\n } else {\n const remaining = n.candidates.filter((c) => c.id !== id);\n if (remaining.length > 0) {\n result.push({ candidates: remaining });\n }\n }\n }\n return result;\n }\n\n private collectIds(nodes: PromptNode<TCtx>[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (isSection(node)) {\n ids.push(node.id);\n } else if (isGroup(node)) {\n ids.push(node.id);\n ids.push(...this.collectIds(node.children));\n } else {\n ids.push(...node.candidates.map((c) => c.id));\n }\n }\n return ids;\n }\n\n private deepCopy(nodes: PromptNode<TCtx>[]): PromptNode<TCtx>[] {\n return nodes.map((n) => {\n if (isGroup(n)) {\n return { ...n, children: this.deepCopy(n.children) };\n }\n if (isOneOf(n)) {\n return { candidates: [...n.candidates] };\n }\n return n;\n });\n }\n}\n","import type { PromptContext, PromptSection } from './types';\n\n/**\n * Create a prompt section. Return a string to include, `null` to exclude.\n *\n * @example\n * ```ts\n * // Static\n * section('identity', () => 'You are Coworker.')\n *\n * // Conditional — return null to exclude\n * section('prod', (ctx: SandboxCtx) => {\n * if (ctx.vars.prodContext == null) return null;\n * return `## Prod\\n\\n${ctx.vars.prodContext}`;\n * })\n * ```\n */\nexport function section<TCtx extends PromptContext = PromptContext>(\n id: string,\n render: (ctx: TCtx) => string | null,\n): PromptSection<TCtx> {\n return {\n id,\n render: (ctx) => render(ctx) ?? '',\n };\n}\n"],"mappings":"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","names":["section"]}
@@ -1,4 +1,4 @@
1
- import { P as PromptContext, a as PromptSection } from './types-DXgCksVT.cjs';
1
+ import { P as PromptContext, a as PromptSection } from './types--sRnfw6Q.cjs';
2
2
 
3
3
  /**
4
4
  * Create a minimal PromptContext for testing.
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { P as PromptContext, a as PromptSection } from './types-DXgCksVT.js';
1
+ import { P as PromptContext, a as PromptSection } from './types--sRnfw6Q.js';
2
2
 
3
3
  /**
4
4
  * Create a minimal PromptContext for testing.
@@ -4,7 +4,7 @@
4
4
  * @typeParam TFlags - Shape of the boolean flags object
5
5
  * @typeParam TVars - Shape of the runtime variables object
6
6
  */
7
- interface PromptContext<TFlags extends Record<string, boolean>, TVars extends Record<string, unknown>> {
7
+ interface PromptContext<TFlags extends Record<string, boolean> = Record<string, boolean>, TVars extends Record<string, unknown> = Record<string, unknown>> {
8
8
  /** Boolean flags that control which sections are included and how they render */
9
9
  flags: TFlags;
10
10
  /** Runtime data sections can read from (model, mode, integrations, etc.) */
@@ -15,7 +15,7 @@ interface PromptContext<TFlags extends Record<string, boolean>, TVars extends Re
15
15
  *
16
16
  * @typeParam TCtx - The prompt context type this section operates on
17
17
  */
18
- interface PromptSection<TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>> {
18
+ interface PromptSection<TCtx extends PromptContext = PromptContext> {
19
19
  /** Unique identifier. Used by `use()`, `without()`, and `buildWithMeta()`. */
20
20
  id: string;
21
21
  /**
@@ -4,7 +4,7 @@
4
4
  * @typeParam TFlags - Shape of the boolean flags object
5
5
  * @typeParam TVars - Shape of the runtime variables object
6
6
  */
7
- interface PromptContext<TFlags extends Record<string, boolean>, TVars extends Record<string, unknown>> {
7
+ interface PromptContext<TFlags extends Record<string, boolean> = Record<string, boolean>, TVars extends Record<string, unknown> = Record<string, unknown>> {
8
8
  /** Boolean flags that control which sections are included and how they render */
9
9
  flags: TFlags;
10
10
  /** Runtime data sections can read from (model, mode, integrations, etc.) */
@@ -15,7 +15,7 @@ interface PromptContext<TFlags extends Record<string, boolean>, TVars extends Re
15
15
  *
16
16
  * @typeParam TCtx - The prompt context type this section operates on
17
17
  */
18
- interface PromptSection<TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>> {
18
+ interface PromptSection<TCtx extends PromptContext = PromptContext> {
19
19
  /** Unique identifier. Used by `use()`, `without()`, and `buildWithMeta()`. */
20
20
  id: string;
21
21
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anythingai/teleprompt",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Composable, declarative prompt builder for LLM system prompts",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -29,11 +29,19 @@
29
29
  }
30
30
  }
31
31
  },
32
- "files": [
33
- "dist",
34
- "README.md",
35
- "LICENSE"
36
- ],
32
+ "files": ["dist", "README.md", "LICENSE"],
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "check": "pnpm lint && pnpm typecheck && pnpm test && pnpm publint && pnpm attw",
36
+ "lint": "biome check .",
37
+ "lint:fix": "biome check --write .",
38
+ "typecheck": "tsc --noEmit",
39
+ "test": "vitest run",
40
+ "test:watch": "vitest",
41
+ "publint": "publint",
42
+ "attw": "attw --pack . --profile node16",
43
+ "prepublishOnly": "pnpm check && pnpm build"
44
+ },
37
45
  "keywords": [
38
46
  "prompt",
39
47
  "llm",
@@ -48,9 +56,13 @@
48
56
  "type": "git",
49
57
  "url": "https://github.com/Create-Inc/teleprompt.git"
50
58
  },
59
+ "packageManager": "pnpm@10.30.0+sha512.2b5753de015d480eeb88f5b5b61e0051f05b4301808a82ec8b840c9d2adf7748eb352c83f5c1593ca703ff1017295bc3fdd3119abb9686efc96b9fcb18200937",
51
60
  "engines": {
52
61
  "node": ">=20"
53
62
  },
63
+ "pnpm": {
64
+ "onlyBuiltDependencies": ["@biomejs/biome", "esbuild"]
65
+ },
54
66
  "devDependencies": {
55
67
  "@arethetypeswrong/cli": "^0.17.0",
56
68
  "@biomejs/biome": "^1.9.0",
@@ -59,16 +71,5 @@
59
71
  "tsx": "^4.21.0",
60
72
  "typescript": "^5.7.0",
61
73
  "vitest": "^3.0.0"
62
- },
63
- "scripts": {
64
- "build": "tsup",
65
- "check": "pnpm lint && pnpm typecheck && pnpm test && pnpm publint && pnpm attw",
66
- "lint": "biome check .",
67
- "lint:fix": "biome check --write .",
68
- "typecheck": "tsc --noEmit",
69
- "test": "vitest run",
70
- "test:watch": "vitest",
71
- "publint": "publint",
72
- "attw": "attw --pack . --profile node16"
73
74
  }
74
- }
75
+ }