@anythingai/teleprompt 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Create Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # teleprompt
2
+
3
+ Compose LLM system prompts from discrete sections instead of monolithic template literals.
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.
6
+
7
+ ```bash
8
+ pnpm add @anythingai/teleprompt
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import { PromptBuilder, type PromptContext, type PromptSection } from '@anythingai/teleprompt';
15
+
16
+ // Define your context shape
17
+ type MyFlags = { webSearchEnabled: boolean };
18
+ type MyVars = { assistantName: string };
19
+ type MyContext = PromptContext<MyFlags, MyVars>;
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
31
+ - Be concise and direct.
32
+ - Cite sources when making factual claims.
33
+ - Ask for clarification when a request is ambiguous.`,
34
+ };
35
+
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
+ };
43
+
44
+ // Compose and build
45
+ const prompt = new PromptBuilder<MyContext>()
46
+ .use(identity)
47
+ .use(guidelines)
48
+ .use(webSearch)
49
+ .build({
50
+ flags: { webSearchEnabled: true },
51
+ vars: { assistantName: 'Daniel' },
52
+ });
53
+ ```
54
+
55
+ ## Sections
56
+
57
+ A section has an `id`, a `render` function, and optionally a `when` guard:
58
+
59
+ ```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
+ };
65
+ ```
66
+
67
+ Sections render in the order you call `.use()`. To reorder, change the call order.
68
+
69
+ ## Forking
70
+
71
+ Create variants without duplicating prompt code:
72
+
73
+ ```ts
74
+ const base = new PromptBuilder<MyContext>()
75
+ .use(identity)
76
+ .use(guidelines)
77
+ .use(tone);
78
+
79
+ // Customer support agent — adds escalation rules
80
+ const supportAgent = base.fork()
81
+ .use(escalationPolicy)
82
+ .use(ticketFormat);
83
+
84
+ // Code assistant — swaps guidelines, drops tone
85
+ const codeAssistant = base.fork()
86
+ .without(guidelines)
87
+ .without(tone)
88
+ .use(codingGuidelines)
89
+ .use(outputFormat);
90
+ ```
91
+
92
+ Each fork is independent. Modifying one doesn't affect the others.
93
+
94
+ ## Context
95
+
96
+ Sections receive a typed context with boolean flags and arbitrary variables:
97
+
98
+ ```ts
99
+ type MyFlags = {
100
+ webSearchEnabled: boolean;
101
+ citationEnabled: boolean;
102
+ };
103
+
104
+ type MyVars = {
105
+ assistantName: string;
106
+ language: string;
107
+ };
108
+
109
+ type MyContext = PromptContext<MyFlags, MyVars>;
110
+ ```
111
+
112
+ You build the context once and pass it to `.build(ctx)`. Every section receives the same object — no threading booleans through function signatures.
113
+
114
+ ## Builder API
115
+
116
+ ```ts
117
+ new PromptBuilder<MyContext>()
118
+ // append a section (replaces if same id exists)
119
+ .use(section)
120
+
121
+ // remove by section object or string id
122
+ .without(section)
123
+
124
+ // check existence by section object or string id
125
+ .has(section)
126
+
127
+ // list all section ids
128
+ .ids()
129
+
130
+ // independent copy
131
+ .fork()
132
+
133
+ // render to string
134
+ .build(ctx)
135
+
136
+ // render + debug info: { included: string[], excluded: string[] }
137
+ .buildWithMeta(ctx)
138
+ ```
139
+
140
+ ## Testing
141
+
142
+ ```ts
143
+ import { mockContext, renderSection } from '@anythingai/teleprompt/testing';
144
+
145
+ // Render a section in isolation
146
+ const output = renderSection(webSearch, { flags: { webSearchEnabled: true } });
147
+ expect(output).toContain('web search');
148
+
149
+ // Assert on prompt structure
150
+ const { included, excluded } = builder.buildWithMeta(ctx);
151
+ expect(included).toContain('web-search');
152
+ expect(excluded).toContain('citation');
153
+ ```
154
+
155
+ ## License
156
+
157
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,95 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); var _class;// src/builder.ts
2
+ var PromptBuilder = (_class = class _PromptBuilder {constructor() { _class.prototype.__init.call(this); }
3
+ __init() {this.sections = []}
4
+ /**
5
+ * Add a section to the prompt.
6
+ *
7
+ * If a section with the same `id` already exists, it is replaced.
8
+ * This makes `.use()` idempotent — you can safely call it multiple
9
+ * times with the same section without creating duplicates.
10
+ */
11
+ use(section) {
12
+ const existingIdx = this.sections.findIndex((s) => s.id === section.id);
13
+ if (existingIdx >= 0) {
14
+ this.sections[existingIdx] = section;
15
+ } else {
16
+ this.sections.push(section);
17
+ }
18
+ return this;
19
+ }
20
+ /** Remove a section. Accepts an id string or a section object. */
21
+ without(ref) {
22
+ const id = typeof ref === "string" ? ref : ref.id;
23
+ this.sections = this.sections.filter((s) => s.id !== id);
24
+ return this;
25
+ }
26
+ /** Check if a section exists. Accepts an id string or a section object. */
27
+ has(ref) {
28
+ const id = typeof ref === "string" ? ref : ref.id;
29
+ return this.sections.some((s) => s.id === id);
30
+ }
31
+ /** Get the ids of all registered sections (in insertion order). */
32
+ ids() {
33
+ return this.sections.map((s) => s.id);
34
+ }
35
+ /**
36
+ * Create an independent copy of this builder.
37
+ *
38
+ * Use this to create mode-specific or model-specific variants
39
+ * without mutating the base builder.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const base = new PromptBuilder().use(a).use(b);
44
+ * const variant = base.fork().use(c); // base is unchanged
45
+ * ```
46
+ */
47
+ fork() {
48
+ const forked = new _PromptBuilder();
49
+ forked.sections = [...this.sections];
50
+ return forked;
51
+ }
52
+ /**
53
+ * Build the final prompt string.
54
+ *
55
+ * 1. Filters out sections whose `when` guard returns false
56
+ * 2. Renders each section
57
+ * 3. Filters out empty strings
58
+ * 4. Joins with separator and trims
59
+ */
60
+ build(ctx) {
61
+ return this.buildWithMeta(ctx).prompt;
62
+ }
63
+ /**
64
+ * Build the prompt and return metadata about which sections were
65
+ * included/excluded. Useful for debugging and logging.
66
+ *
67
+ * A section is "excluded" if its `when` guard returns false.
68
+ * A section is "included" only if it passes the guard and renders
69
+ * a non-empty string.
70
+ */
71
+ buildWithMeta(ctx) {
72
+ const included = [];
73
+ const excluded = [];
74
+ const rendered = this.sections.filter((s) => {
75
+ if (s.when && !s.when(ctx)) {
76
+ excluded.push(s.id);
77
+ return false;
78
+ }
79
+ return true;
80
+ }).map((s) => {
81
+ const output = s.render(ctx);
82
+ if (output) {
83
+ included.push(s.id);
84
+ } else {
85
+ excluded.push(s.id);
86
+ }
87
+ return output;
88
+ }).filter(Boolean).join("\n\n").trim();
89
+ return { prompt: rendered, included, excluded };
90
+ }
91
+ }, _class);
92
+
93
+
94
+ exports.PromptBuilder = PromptBuilder;
95
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,76 @@
1
+ import { P as PromptContext, a as PromptSection } from './types-DXgCksVT.cjs';
2
+
3
+ /**
4
+ * Declarative, composable prompt builder.
5
+ *
6
+ * Prompts are composed from discrete {@link PromptSection}s that are
7
+ * independently testable pure functions. The builder handles ordering,
8
+ * conditional inclusion, and final assembly.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const prompt = new PromptBuilder()
13
+ * .use(identitySection)
14
+ * .use(rulesSection)
15
+ * .use(featureSection)
16
+ * .build(ctx);
17
+ * ```
18
+ */
19
+ declare class PromptBuilder<TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>> {
20
+ private sections;
21
+ /**
22
+ * Add a section to the prompt.
23
+ *
24
+ * If a section with the same `id` already exists, it is replaced.
25
+ * This makes `.use()` idempotent — you can safely call it multiple
26
+ * times with the same section without creating duplicates.
27
+ */
28
+ use(section: PromptSection<TCtx>): this;
29
+ /** Remove a section. Accepts an id string or a section object. */
30
+ without(ref: string | {
31
+ id: string;
32
+ }): this;
33
+ /** Check if a section exists. Accepts an id string or a section object. */
34
+ has(ref: string | {
35
+ id: string;
36
+ }): boolean;
37
+ /** Get the ids of all registered sections (in insertion order). */
38
+ ids(): string[];
39
+ /**
40
+ * Create an independent copy of this builder.
41
+ *
42
+ * Use this to create mode-specific or model-specific variants
43
+ * without mutating the base builder.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const base = new PromptBuilder().use(a).use(b);
48
+ * const variant = base.fork().use(c); // base is unchanged
49
+ * ```
50
+ */
51
+ fork(): PromptBuilder<TCtx>;
52
+ /**
53
+ * Build the final prompt string.
54
+ *
55
+ * 1. Filters out sections whose `when` guard returns false
56
+ * 2. Renders each section
57
+ * 3. Filters out empty strings
58
+ * 4. Joins with separator and trims
59
+ */
60
+ build(ctx: TCtx): string;
61
+ /**
62
+ * Build the prompt and return metadata about which sections were
63
+ * included/excluded. Useful for debugging and logging.
64
+ *
65
+ * A section is "excluded" if its `when` guard returns false.
66
+ * A section is "included" only if it passes the guard and renders
67
+ * a non-empty string.
68
+ */
69
+ buildWithMeta(ctx: TCtx): {
70
+ prompt: string;
71
+ included: string[];
72
+ excluded: string[];
73
+ };
74
+ }
75
+
76
+ export { PromptBuilder, PromptContext, PromptSection };
@@ -0,0 +1,76 @@
1
+ import { P as PromptContext, a as PromptSection } from './types-DXgCksVT.js';
2
+
3
+ /**
4
+ * Declarative, composable prompt builder.
5
+ *
6
+ * Prompts are composed from discrete {@link PromptSection}s that are
7
+ * independently testable pure functions. The builder handles ordering,
8
+ * conditional inclusion, and final assembly.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const prompt = new PromptBuilder()
13
+ * .use(identitySection)
14
+ * .use(rulesSection)
15
+ * .use(featureSection)
16
+ * .build(ctx);
17
+ * ```
18
+ */
19
+ declare class PromptBuilder<TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>> {
20
+ private sections;
21
+ /**
22
+ * Add a section to the prompt.
23
+ *
24
+ * If a section with the same `id` already exists, it is replaced.
25
+ * This makes `.use()` idempotent — you can safely call it multiple
26
+ * times with the same section without creating duplicates.
27
+ */
28
+ use(section: PromptSection<TCtx>): this;
29
+ /** Remove a section. Accepts an id string or a section object. */
30
+ without(ref: string | {
31
+ id: string;
32
+ }): this;
33
+ /** Check if a section exists. Accepts an id string or a section object. */
34
+ has(ref: string | {
35
+ id: string;
36
+ }): boolean;
37
+ /** Get the ids of all registered sections (in insertion order). */
38
+ ids(): string[];
39
+ /**
40
+ * Create an independent copy of this builder.
41
+ *
42
+ * Use this to create mode-specific or model-specific variants
43
+ * without mutating the base builder.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const base = new PromptBuilder().use(a).use(b);
48
+ * const variant = base.fork().use(c); // base is unchanged
49
+ * ```
50
+ */
51
+ fork(): PromptBuilder<TCtx>;
52
+ /**
53
+ * Build the final prompt string.
54
+ *
55
+ * 1. Filters out sections whose `when` guard returns false
56
+ * 2. Renders each section
57
+ * 3. Filters out empty strings
58
+ * 4. Joins with separator and trims
59
+ */
60
+ build(ctx: TCtx): string;
61
+ /**
62
+ * Build the prompt and return metadata about which sections were
63
+ * included/excluded. Useful for debugging and logging.
64
+ *
65
+ * A section is "excluded" if its `when` guard returns false.
66
+ * A section is "included" only if it passes the guard and renders
67
+ * a non-empty string.
68
+ */
69
+ buildWithMeta(ctx: TCtx): {
70
+ prompt: string;
71
+ included: string[];
72
+ excluded: string[];
73
+ };
74
+ }
75
+
76
+ export { PromptBuilder, PromptContext, PromptSection };
package/dist/index.js ADDED
@@ -0,0 +1,95 @@
1
+ // src/builder.ts
2
+ var PromptBuilder = class _PromptBuilder {
3
+ sections = [];
4
+ /**
5
+ * Add a section to the prompt.
6
+ *
7
+ * If a section with the same `id` already exists, it is replaced.
8
+ * This makes `.use()` idempotent — you can safely call it multiple
9
+ * times with the same section without creating duplicates.
10
+ */
11
+ use(section) {
12
+ const existingIdx = this.sections.findIndex((s) => s.id === section.id);
13
+ if (existingIdx >= 0) {
14
+ this.sections[existingIdx] = section;
15
+ } else {
16
+ this.sections.push(section);
17
+ }
18
+ return this;
19
+ }
20
+ /** Remove a section. Accepts an id string or a section object. */
21
+ without(ref) {
22
+ const id = typeof ref === "string" ? ref : ref.id;
23
+ this.sections = this.sections.filter((s) => s.id !== id);
24
+ return this;
25
+ }
26
+ /** Check if a section exists. Accepts an id string or a section object. */
27
+ has(ref) {
28
+ const id = typeof ref === "string" ? ref : ref.id;
29
+ return this.sections.some((s) => s.id === id);
30
+ }
31
+ /** Get the ids of all registered sections (in insertion order). */
32
+ ids() {
33
+ return this.sections.map((s) => s.id);
34
+ }
35
+ /**
36
+ * Create an independent copy of this builder.
37
+ *
38
+ * Use this to create mode-specific or model-specific variants
39
+ * without mutating the base builder.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const base = new PromptBuilder().use(a).use(b);
44
+ * const variant = base.fork().use(c); // base is unchanged
45
+ * ```
46
+ */
47
+ fork() {
48
+ const forked = new _PromptBuilder();
49
+ forked.sections = [...this.sections];
50
+ return forked;
51
+ }
52
+ /**
53
+ * Build the final prompt string.
54
+ *
55
+ * 1. Filters out sections whose `when` guard returns false
56
+ * 2. Renders each section
57
+ * 3. Filters out empty strings
58
+ * 4. Joins with separator and trims
59
+ */
60
+ build(ctx) {
61
+ return this.buildWithMeta(ctx).prompt;
62
+ }
63
+ /**
64
+ * Build the prompt and return metadata about which sections were
65
+ * included/excluded. Useful for debugging and logging.
66
+ *
67
+ * A section is "excluded" if its `when` guard returns false.
68
+ * A section is "included" only if it passes the guard and renders
69
+ * a non-empty string.
70
+ */
71
+ buildWithMeta(ctx) {
72
+ const included = [];
73
+ const excluded = [];
74
+ const rendered = this.sections.filter((s) => {
75
+ if (s.when && !s.when(ctx)) {
76
+ excluded.push(s.id);
77
+ return false;
78
+ }
79
+ return true;
80
+ }).map((s) => {
81
+ const output = s.render(ctx);
82
+ if (output) {
83
+ included.push(s.id);
84
+ } else {
85
+ excluded.push(s.id);
86
+ }
87
+ return output;
88
+ }).filter(Boolean).join("\n\n").trim();
89
+ return { prompt: rendered, included, excluded };
90
+ }
91
+ };
92
+ export {
93
+ PromptBuilder
94
+ };
95
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":[]}
@@ -0,0 +1,20 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/testing.ts
2
+ function mockContext(overrides) {
3
+ return {
4
+ flags: {},
5
+ vars: {},
6
+ ...overrides
7
+ };
8
+ }
9
+ function renderSection(section, contextOverrides) {
10
+ const ctx = mockContext(contextOverrides);
11
+ if (section.when && !section.when(ctx)) {
12
+ return null;
13
+ }
14
+ return section.render(ctx);
15
+ }
16
+
17
+
18
+
19
+ exports.mockContext = mockContext; exports.renderSection = renderSection;
20
+ //# sourceMappingURL=testing.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/asurve/dev/teleprompt/dist/testing.cjs","../src/testing.ts"],"names":[],"mappings":"AAAA;ACYO,SAAS,WAAA,CAGd,SAAA,EAAiF;AACjF,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,CAAC,CAAA;AAAA,IACR,IAAA,EAAM,CAAC,CAAA;AAAA,IACP,GAAG;AAAA,EACL,CAAA;AACF;AAYO,SAAS,aAAA,CAEd,OAAA,EAA8B,gBAAA,EAAiD;AAC/E,EAAA,MAAM,IAAA,EAAM,WAAA,CAAY,gBAAgB,CAAA;AACxC,EAAA,GAAA,CAAI,OAAA,CAAQ,KAAA,GAAQ,CAAC,OAAA,CAAQ,IAAA,CAAK,GAAG,CAAA,EAAG;AACtC,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,GAAG,CAAA;AAC3B;AD1BA;AACE;AACA;AACF,yEAAC","file":"/Users/asurve/dev/teleprompt/dist/testing.cjs","sourcesContent":[null,"import type { PromptContext, PromptSection } from './types';\n\n/**\n * Create a minimal PromptContext for testing.\n * All fields have sensible defaults that can be overridden.\n *\n * @example\n * ```ts\n * const ctx = mockContext({ flags: { myFlag: true } });\n * const output = mySection.render(ctx);\n * ```\n */\nexport function mockContext<\n TFlags extends Record<string, boolean> = Record<string, boolean>,\n TVars extends Record<string, unknown> = Record<string, unknown>,\n>(overrides?: Partial<PromptContext<TFlags, TVars>>): PromptContext<TFlags, TVars> {\n return {\n flags: {} as TFlags,\n vars: {} as TVars,\n ...overrides,\n };\n}\n\n/**\n * Render a single section in isolation with a mock context.\n * Respects the section's `when` guard — returns `null` if excluded.\n *\n * @example\n * ```ts\n * const output = renderSection(mySection, { flags: { enabled: true } });\n * expect(output).toContain('expected text');\n * ```\n */\nexport function renderSection<\n TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>,\n>(section: PromptSection<TCtx>, contextOverrides?: Partial<TCtx>): string | null {\n const ctx = mockContext(contextOverrides) as TCtx;\n if (section.when && !section.when(ctx)) {\n return null;\n }\n return section.render(ctx);\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import { P as PromptContext, a as PromptSection } from './types-DXgCksVT.cjs';
2
+
3
+ /**
4
+ * Create a minimal PromptContext for testing.
5
+ * All fields have sensible defaults that can be overridden.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * const ctx = mockContext({ flags: { myFlag: true } });
10
+ * const output = mySection.render(ctx);
11
+ * ```
12
+ */
13
+ declare function mockContext<TFlags extends Record<string, boolean> = Record<string, boolean>, TVars extends Record<string, unknown> = Record<string, unknown>>(overrides?: Partial<PromptContext<TFlags, TVars>>): PromptContext<TFlags, TVars>;
14
+ /**
15
+ * Render a single section in isolation with a mock context.
16
+ * Respects the section's `when` guard — returns `null` if excluded.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const output = renderSection(mySection, { flags: { enabled: true } });
21
+ * expect(output).toContain('expected text');
22
+ * ```
23
+ */
24
+ declare function renderSection<TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>>(section: PromptSection<TCtx>, contextOverrides?: Partial<TCtx>): string | null;
25
+
26
+ export { mockContext, renderSection };
@@ -0,0 +1,26 @@
1
+ import { P as PromptContext, a as PromptSection } from './types-DXgCksVT.js';
2
+
3
+ /**
4
+ * Create a minimal PromptContext for testing.
5
+ * All fields have sensible defaults that can be overridden.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * const ctx = mockContext({ flags: { myFlag: true } });
10
+ * const output = mySection.render(ctx);
11
+ * ```
12
+ */
13
+ declare function mockContext<TFlags extends Record<string, boolean> = Record<string, boolean>, TVars extends Record<string, unknown> = Record<string, unknown>>(overrides?: Partial<PromptContext<TFlags, TVars>>): PromptContext<TFlags, TVars>;
14
+ /**
15
+ * Render a single section in isolation with a mock context.
16
+ * Respects the section's `when` guard — returns `null` if excluded.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const output = renderSection(mySection, { flags: { enabled: true } });
21
+ * expect(output).toContain('expected text');
22
+ * ```
23
+ */
24
+ declare function renderSection<TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>>(section: PromptSection<TCtx>, contextOverrides?: Partial<TCtx>): string | null;
25
+
26
+ export { mockContext, renderSection };
@@ -0,0 +1,20 @@
1
+ // src/testing.ts
2
+ function mockContext(overrides) {
3
+ return {
4
+ flags: {},
5
+ vars: {},
6
+ ...overrides
7
+ };
8
+ }
9
+ function renderSection(section, contextOverrides) {
10
+ const ctx = mockContext(contextOverrides);
11
+ if (section.when && !section.when(ctx)) {
12
+ return null;
13
+ }
14
+ return section.render(ctx);
15
+ }
16
+ export {
17
+ mockContext,
18
+ renderSection
19
+ };
20
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/testing.ts"],"sourcesContent":["import type { PromptContext, PromptSection } from './types';\n\n/**\n * Create a minimal PromptContext for testing.\n * All fields have sensible defaults that can be overridden.\n *\n * @example\n * ```ts\n * const ctx = mockContext({ flags: { myFlag: true } });\n * const output = mySection.render(ctx);\n * ```\n */\nexport function mockContext<\n TFlags extends Record<string, boolean> = Record<string, boolean>,\n TVars extends Record<string, unknown> = Record<string, unknown>,\n>(overrides?: Partial<PromptContext<TFlags, TVars>>): PromptContext<TFlags, TVars> {\n return {\n flags: {} as TFlags,\n vars: {} as TVars,\n ...overrides,\n };\n}\n\n/**\n * Render a single section in isolation with a mock context.\n * Respects the section's `when` guard — returns `null` if excluded.\n *\n * @example\n * ```ts\n * const output = renderSection(mySection, { flags: { enabled: true } });\n * expect(output).toContain('expected text');\n * ```\n */\nexport function renderSection<\n TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>,\n>(section: PromptSection<TCtx>, contextOverrides?: Partial<TCtx>): string | null {\n const ctx = mockContext(contextOverrides) as TCtx;\n if (section.when && !section.when(ctx)) {\n return null;\n }\n return section.render(ctx);\n}\n"],"mappings":";AAYO,SAAS,YAGd,WAAiF;AACjF,SAAO;AAAA,IACL,OAAO,CAAC;AAAA,IACR,MAAM,CAAC;AAAA,IACP,GAAG;AAAA,EACL;AACF;AAYO,SAAS,cAEd,SAA8B,kBAAiD;AAC/E,QAAM,MAAM,YAAY,gBAAgB;AACxC,MAAI,QAAQ,QAAQ,CAAC,QAAQ,KAAK,GAAG,GAAG;AACtC,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,OAAO,GAAG;AAC3B;","names":[]}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The context passed to every section's `when` and `render` functions.
3
+ *
4
+ * @typeParam TFlags - Shape of the boolean flags object
5
+ * @typeParam TVars - Shape of the runtime variables object
6
+ */
7
+ interface PromptContext<TFlags extends Record<string, boolean>, TVars extends Record<string, unknown>> {
8
+ /** Boolean flags that control which sections are included and how they render */
9
+ flags: TFlags;
10
+ /** Runtime data sections can read from (model, mode, integrations, etc.) */
11
+ vars: TVars;
12
+ }
13
+ /**
14
+ * A single composable unit of prompt content.
15
+ *
16
+ * @typeParam TCtx - The prompt context type this section operates on
17
+ */
18
+ interface PromptSection<TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>> {
19
+ /** Unique identifier. Used by `use()`, `without()`, and `buildWithMeta()`. */
20
+ id: string;
21
+ /**
22
+ * Guard function. Return `false` to exclude this section from the output.
23
+ * If omitted, the section is always included.
24
+ */
25
+ when?: (ctx: TCtx) => boolean;
26
+ /**
27
+ * Render the section content. Receives the full prompt context.
28
+ * Return an empty string to include nothing (different from `when: false`
29
+ * which removes the section entirely including any separator).
30
+ */
31
+ render: (ctx: TCtx) => string;
32
+ }
33
+
34
+ export type { PromptContext as P, PromptSection as a };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The context passed to every section's `when` and `render` functions.
3
+ *
4
+ * @typeParam TFlags - Shape of the boolean flags object
5
+ * @typeParam TVars - Shape of the runtime variables object
6
+ */
7
+ interface PromptContext<TFlags extends Record<string, boolean>, TVars extends Record<string, unknown>> {
8
+ /** Boolean flags that control which sections are included and how they render */
9
+ flags: TFlags;
10
+ /** Runtime data sections can read from (model, mode, integrations, etc.) */
11
+ vars: TVars;
12
+ }
13
+ /**
14
+ * A single composable unit of prompt content.
15
+ *
16
+ * @typeParam TCtx - The prompt context type this section operates on
17
+ */
18
+ interface PromptSection<TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>> {
19
+ /** Unique identifier. Used by `use()`, `without()`, and `buildWithMeta()`. */
20
+ id: string;
21
+ /**
22
+ * Guard function. Return `false` to exclude this section from the output.
23
+ * If omitted, the section is always included.
24
+ */
25
+ when?: (ctx: TCtx) => boolean;
26
+ /**
27
+ * Render the section content. Receives the full prompt context.
28
+ * Return an empty string to include nothing (different from `when: false`
29
+ * which removes the section entirely including any separator).
30
+ */
31
+ render: (ctx: TCtx) => string;
32
+ }
33
+
34
+ export type { PromptContext as P, PromptSection as a };
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@anythingai/teleprompt",
3
+ "version": "0.1.0",
4
+ "description": "Composable, declarative prompt builder for LLM system prompts",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "sideEffects": false,
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.cts",
18
+ "default": "./dist/index.cjs"
19
+ }
20
+ },
21
+ "./testing": {
22
+ "import": {
23
+ "types": "./dist/testing.d.ts",
24
+ "default": "./dist/testing.js"
25
+ },
26
+ "require": {
27
+ "types": "./dist/testing.d.cts",
28
+ "default": "./dist/testing.cjs"
29
+ }
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "keywords": [
38
+ "prompt",
39
+ "llm",
40
+ "ai",
41
+ "system-prompt",
42
+ "prompt-builder",
43
+ "prompt-composition",
44
+ "composable"
45
+ ],
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/Create-Inc/teleprompt.git"
50
+ },
51
+ "engines": {
52
+ "node": ">=20"
53
+ },
54
+ "devDependencies": {
55
+ "@arethetypeswrong/cli": "^0.17.0",
56
+ "@biomejs/biome": "^1.9.0",
57
+ "publint": "^0.3.0",
58
+ "tsup": "^8.4.0",
59
+ "tsx": "^4.21.0",
60
+ "typescript": "^5.7.0",
61
+ "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
+ }