@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 +137 -57
- package/dist/index.cjs +210 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +80 -9
- package/dist/index.d.ts +80 -9
- package/dist/index.js +209 -29
- package/dist/index.js.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{types-DXgCksVT.d.cts → types--sRnfw6Q.d.cts} +2 -2
- package/dist/{types-DXgCksVT.d.ts → types--sRnfw6Q.d.ts} +2 -2
- package/package.json +19 -18
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# teleprompt
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Composable, section-based LLM system prompts.
|
|
4
4
|
|
|
5
|
-
|
|
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,
|
|
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
|
-
//
|
|
22
|
-
const
|
|
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
|
-
//
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
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
|
-
##
|
|
128
|
+
## XML Format
|
|
95
129
|
|
|
96
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
citationEnabled: boolean;
|
|
102
|
-
};
|
|
133
|
+
builder.build(ctx, { format: 'xml' })
|
|
134
|
+
```
|
|
103
135
|
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
174
|
+
Groups can be nested:
|
|
115
175
|
|
|
116
176
|
```ts
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
122
|
-
.without(section)
|
|
189
|
+
## Mutually Exclusive Sections
|
|
123
190
|
|
|
124
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
131
|
-
.fork()
|
|
199
|
+
const noTasks = section('no-tasks', () => '## Active Tasks\n\nNo tasks currently running.');
|
|
132
200
|
|
|
133
|
-
|
|
134
|
-
|
|
201
|
+
builder.useOneOf(hasTasks, noTasks);
|
|
202
|
+
```
|
|
135
203
|
|
|
136
|
-
|
|
137
|
-
|
|
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.
|
|
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(
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
14
|
-
this.
|
|
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.
|
|
28
|
+
this.nodes.push(section2);
|
|
17
29
|
}
|
|
18
30
|
return this;
|
|
19
31
|
}
|
|
20
|
-
/**
|
|
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.
|
|
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
|
|
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.
|
|
79
|
+
return this.hasNode(this.nodes, id);
|
|
30
80
|
}
|
|
31
|
-
/** Get
|
|
81
|
+
/** Get all ids (sections, groups, and oneOf candidates) in order. */
|
|
32
82
|
ids() {
|
|
33
|
-
return this.
|
|
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.
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
212
|
+
for (const c of node.candidates) {
|
|
213
|
+
if (c.id === id) return true;
|
|
214
|
+
}
|
|
86
215
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/asurve/dev/teleprompt/dist/index.cjs","../src/builder.ts"],"names":[],"mappings":"AAAA;ACkBO,IAAM,cAAA,YAAN,MAAM,eAEX;AAAA,iBACQ,SAAA,EAAkC,CAAC,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS3C,GAAA,CAAI,OAAA,EAAoC;AACtC,IAAA,MAAM,YAAA,EAAc,IAAA,CAAK,QAAA,CAAS,SAAA,CAAU,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,GAAA,IAAO,OAAA,CAAQ,EAAE,CAAA;AACtE,IAAA,GAAA,CAAI,YAAA,GAAe,CAAA,EAAG;AACpB,MAAA,IAAA,CAAK,QAAA,CAAS,WAAW,EAAA,EAAI,OAAA;AAAA,IAC/B,EAAA,KAAO;AACL,MAAA,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,OAAO,CAAA;AAAA,IAC5B;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA,EAGA,OAAA,CAAQ,GAAA,EAAoC;AAC1C,IAAA,MAAM,GAAA,EAAK,OAAO,IAAA,IAAQ,SAAA,EAAW,IAAA,EAAM,GAAA,CAAI,EAAA;AAC/C,IAAA,IAAA,CAAK,SAAA,EAAW,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,GAAA,IAAO,EAAE,CAAA;AACvD,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA,EAGA,GAAA,CAAI,GAAA,EAAuC;AACzC,IAAA,MAAM,GAAA,EAAK,OAAO,IAAA,IAAQ,SAAA,EAAW,IAAA,EAAM,GAAA,CAAI,EAAA;AAC/C,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,GAAA,IAAO,EAAE,CAAA;AAAA,EAC9C;AAAA;AAAA,EAGA,GAAA,CAAA,EAAgB;AACd,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,EAAE,CAAA;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,IAAA,CAAA,EAA4B;AAC1B,IAAA,MAAM,OAAA,EAAS,IAAI,cAAA,CAAoB,CAAA;AACvC,IAAA,MAAA,CAAO,SAAA,EAAW,CAAC,GAAG,IAAA,CAAK,QAAQ,CAAA;AACnC,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,KAAA,CAAM,GAAA,EAAmB;AACvB,IAAA,OAAO,IAAA,CAAK,aAAA,CAAc,GAAG,CAAA,CAAE,MAAA;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,aAAA,CAAc,GAAA,EAIZ;AACA,IAAA,MAAM,SAAA,EAAqB,CAAC,CAAA;AAC5B,IAAA,MAAM,SAAA,EAAqB,CAAC,CAAA;AAE5B,IAAA,MAAM,SAAA,EAAW,IAAA,CAAK,QAAA,CACnB,MAAA,CAAO,CAAC,CAAA,EAAA,GAAM;AACb,MAAA,GAAA,CAAI,CAAA,CAAE,KAAA,GAAQ,CAAC,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA,EAAG;AAC1B,QAAA,QAAA,CAAS,IAAA,CAAK,CAAA,CAAE,EAAE,CAAA;AAClB,QAAA,OAAO,KAAA;AAAA,MACT;AACA,MAAA,OAAO,IAAA;AAAA,IACT,CAAC,CAAA,CACA,GAAA,CAAI,CAAC,CAAA,EAAA,GAAM;AACV,MAAA,MAAM,OAAA,EAAS,CAAA,CAAE,MAAA,CAAO,GAAG,CAAA;AAC3B,MAAA,GAAA,CAAI,MAAA,EAAQ;AACV,QAAA,QAAA,CAAS,IAAA,CAAK,CAAA,CAAE,EAAE,CAAA;AAAA,MACpB,EAAA,KAAO;AACL,QAAA,QAAA,CAAS,IAAA,CAAK,CAAA,CAAE,EAAE,CAAA;AAAA,MACpB;AACA,MAAA,OAAO,MAAA;AAAA,IACT,CAAC,CAAA,CACA,MAAA,CAAO,OAAO,CAAA,CACd,IAAA,CAAK,MAAM,CAAA,CACX,IAAA,CAAK,CAAA;AAER,IAAA,OAAO,EAAE,MAAA,EAAQ,QAAA,EAAU,QAAA,EAAU,SAAS,CAAA;AAAA,EAChD;AACF,UAAA;ADpCA;AACE;AACF,sCAAC","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
|
|
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
|
|
20
|
-
private
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
20
|
-
private
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
14
|
-
this.
|
|
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.
|
|
28
|
+
this.nodes.push(section2);
|
|
17
29
|
}
|
|
18
30
|
return this;
|
|
19
31
|
}
|
|
20
|
-
/**
|
|
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.
|
|
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
|
|
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.
|
|
79
|
+
return this.hasNode(this.nodes, id);
|
|
30
80
|
}
|
|
31
|
-
/** Get
|
|
81
|
+
/** Get all ids (sections, groups, and oneOf candidates) in order. */
|
|
32
82
|
ids() {
|
|
33
|
-
return this.
|
|
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.
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
|
88
|
-
})
|
|
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"]}
|
package/dist/testing.d.cts
CHANGED
package/dist/testing.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
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
|
+
}
|