@riotprompt/riotprompt 0.0.2 → 0.0.3
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 +54 -470
- package/dist/builder.d.ts +3 -3
- package/dist/builder.js +7 -2
- package/dist/builder.js.map +1 -1
- package/dist/chat.js.map +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/formatter.js.map +1 -1
- package/dist/items/content.js.map +1 -1
- package/dist/items/context.js.map +1 -1
- package/dist/items/instruction.js.map +1 -1
- package/dist/items/parameters.js.map +1 -1
- package/dist/items/section.js.map +1 -1
- package/dist/items/trait.js.map +1 -1
- package/dist/items/weighted.js.map +1 -1
- package/dist/loader.js +1 -0
- package/dist/loader.js.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/override.d.ts +5 -5
- package/dist/override.js +47 -30
- package/dist/override.js.map +1 -1
- package/dist/parse/markdown.js.map +1 -1
- package/dist/parse/text.js.map +1 -1
- package/dist/parser.js.map +1 -1
- package/dist/prompt.js.map +1 -1
- package/dist/recipes.d.ts +405 -0
- package/dist/recipes.js +424 -0
- package/dist/recipes.js.map +1 -0
- package/dist/riotprompt.cjs +484 -32
- package/dist/riotprompt.cjs.map +1 -1
- package/dist/riotprompt.d.ts +3 -0
- package/dist/riotprompt.js +3 -0
- package/dist/riotprompt.js.map +1 -1
- package/dist/util/general.js.map +1 -1
- package/dist/util/markdown.js.map +1 -1
- package/dist/util/storage.js.map +1 -1
- package/dist/util/text.js.map +1 -1
- package/package.json +29 -24
- package/.gitcarve/config.yaml +0 -10
- package/.gitcarve/context/content.md +0 -11
- package/.markdown-doctest-setup.mjs +0 -23
- package/.nvmrc +0 -1
- package/docs/loader.md +0 -237
- package/docs/override.md +0 -323
- package/docs/parser.md +0 -130
- package/eslint.config.mjs +0 -82
- package/nodemon.json +0 -14
- package/vite.config.ts +0 -114
- package/vitest.config.ts +0 -25
package/docs/override.md
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
# riotprompt Override Utility
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
The **Override** utility in riotprompt allows you to customize or replace parts of a prompt without altering the original prompt files. This is particularly useful in larger applications or frameworks (like Cortalyne) where you have default prompt templates but want to adjust certain sections for specific use cases, users, or environments. By using overrides, you can maintain a clean separation between **core prompt content** and **custom modifications**.
|
|
6
|
-
|
|
7
|
-
In essence, overrides let you **selectively replace or augment prompt sections**:
|
|
8
|
-
|
|
9
|
-
* You can completely **override** a section (replace it entirely with new content).
|
|
10
|
-
* You can **prepend** content (insert additional text before the original content).
|
|
11
|
-
* You can **append** content (insert additional text after the original content).
|
|
12
|
-
|
|
13
|
-
All of this is done without modifying the original prompt source file; instead, riotprompt will detect override files and merge or replace content accordingly when building the final prompt.
|
|
14
|
-
|
|
15
|
-
## How Overrides Work
|
|
16
|
-
|
|
17
|
-
riotprompt's override system works by looking for specially-named files in an "override" directory that correspond to your prompt files. When you build a prompt (for example, using the Builder), you can specify an `overridePath` where your override files live and enable overrides.
|
|
18
|
-
|
|
19
|
-
For each prompt file loaded from the base path, riotprompt will check if there is a corresponding override file. The correspondence is determined by **filename and path**:
|
|
20
|
-
|
|
21
|
-
* If an override file with the *exact same name* exists in the override directory (mirroring the relative path of the original file), riotprompt will treat that as a **full override** for that prompt file.
|
|
22
|
-
* Additionally, riotprompt recognizes two suffix conventions for partial overrides:
|
|
23
|
-
|
|
24
|
-
* A file ending in **`-pre.md`** is treated as content to **prepend** (placed before the base content).
|
|
25
|
-
* A file ending in **`-post.md`** is treated as content to **append** (placed after the base content).
|
|
26
|
-
|
|
27
|
-
This naming scheme allows you to choose the override mode by how you name the file, without needing additional configuration in code for each file.
|
|
28
|
-
|
|
29
|
-
**Example:**
|
|
30
|
-
|
|
31
|
-
Suppose you have a base prompt file `prompts/instructions/email.md` that defines instructions for drafting an email. To modify this via overrides:
|
|
32
|
-
|
|
33
|
-
* Place a file at `overrides/instructions/email.md` – this will completely replace the content of `email.md` when overrides are applied.
|
|
34
|
-
* Place a file at `overrides/instructions/email-pre.md` – this will be inserted *before* the content of the base `email.md`.
|
|
35
|
-
* Place a file at `overrides/instructions/email-post.md` – this will be inserted *after* the content of the base `email.md`.
|
|
36
|
-
|
|
37
|
-
You can use none, one, or all of these in combination as needed. For instance, you might only have a `email-post.md` to add a few extra instructions at the end of the default email instructions, leaving the original content intact.
|
|
38
|
-
|
|
39
|
-
## Note on Path Configuration
|
|
40
|
-
|
|
41
|
-
When implementing overrides in a real-world application, the paths are often configured strategically:
|
|
42
|
-
|
|
43
|
-
* **basePath** is frequently constructed to point to content from a package, often using variables like `__dirname` to reference the location of the installed package. For example:
|
|
44
|
-
|
|
45
|
-
```ts
|
|
46
|
-
import path from 'path';
|
|
47
|
-
import { fileURLToPath } from 'url';
|
|
48
|
-
|
|
49
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
50
|
-
const __dirname = path.dirname(__filename);
|
|
51
|
-
|
|
52
|
-
const basePath = path.join(__dirname, '../prompts');
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
* **overridePath** is typically configured using a project's configuration directory, which might be in the user's workspace or a designated config location:
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
const overridePath = path.join(process.cwd(), 'config/prompts');
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
This setup creates a powerful pattern where library authors can ship applications with a set of default prompts (in the package's prompts directory), while allowing users to customize those prompts by:
|
|
62
|
-
|
|
63
|
-
1. **Completely replacing** prompt content by providing override files with the same name
|
|
64
|
-
2. **Extending** the default prompts by adding content before or after using the `-pre.md` and `-post.md` conventions
|
|
65
|
-
|
|
66
|
-
This approach maintains a clean separation between the library's default content and user customizations, making it easier to update the library without losing custom modifications. It's particularly valuable in frameworks and tools that rely heavily on prompt engineering but need to support user-specific adaptations.
|
|
67
|
-
|
|
68
|
-
**Directory Structure:**
|
|
69
|
-
|
|
70
|
-
The override directory should mirror the structure of the base prompt directory. For example:
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
prompts/ (base prompt files)
|
|
74
|
-
instructions/
|
|
75
|
-
email.md
|
|
76
|
-
meeting.md
|
|
77
|
-
|
|
78
|
-
overrides/ (override files)
|
|
79
|
-
instructions/
|
|
80
|
-
email-pre.md (prepends content to email.md instructions)
|
|
81
|
-
email-post.md (appends content to email.md instructions)
|
|
82
|
-
meeting.md (completely overrides meeting.md instructions)
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
When a prompt is configured, the Builder and Override utility will take the paths "instructions/email.md" and "instructions/meeting.md", and the library will change the extension from ".md" to "-pre.md" and "-post.md" when it is looking for override content.
|
|
86
|
-
|
|
87
|
-
In this scenario:
|
|
88
|
-
|
|
89
|
-
* For `email.md`, the builder will find an `email-pre.md` and `email-post.md`. It will prepend and append their content around `email.md`'s content. There is no `email.md` full override in the overrides directory, so the base content is still used (with the additions).
|
|
90
|
-
* For `meeting.md`, a full override file exists in overrides. That means the original `meeting.md` content will be entirely replaced by the content from the override file. (If there were also `meeting-pre.md` or `meeting-post.md`, those would be applied as well, but typically if you're fully overriding, you may not use pre/post for that file.)
|
|
91
|
-
|
|
92
|
-
## Enabling Overrides in Builder
|
|
93
|
-
|
|
94
|
-
If you are using the `Builder` to assemble prompts, you need to tell it to use the overrides. This is done via the `overridePath` and `overrides` options in `Builder.create()`:
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
const builder = Builder.create({
|
|
98
|
-
basePath: './prompts',
|
|
99
|
-
overridePath: './overrides',
|
|
100
|
-
overrides: true
|
|
101
|
-
});
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
* `basePath` is where your base prompt files are located.
|
|
105
|
-
* `overridePath` is where your override files are located.
|
|
106
|
-
* `overrides: true` enables the override functionality. (By default, riotprompt might ignore override files unless this flag is set. This is a safety feature to prevent accidental override of content.)
|
|
107
|
-
|
|
108
|
-
When `overrides` is true, the builder will incorporate any found override files as it loads prompt files. If you set an override path but `overrides` is false (or omitted), riotprompt will likely skip applying full overrides. (Prepend/append might still be applied, or they might also be ignored – typically you enable the flag when you intend to use any overrides.)
|
|
109
|
-
|
|
110
|
-
In applications like **Cortalyne**, a command-line flag is used (e.g. `--overrides`) to toggle this behavior. This maps to the `overrides: true` setting in the riotprompt builder.
|
|
111
|
-
|
|
112
|
-
## Using Override Utility Programmatically
|
|
113
|
-
|
|
114
|
-
While the common usage is via Builder configuration, riotprompt also provides lower-level capabilities to apply overrides if needed.
|
|
115
|
-
|
|
116
|
-
For example, there may be an `Override` class or methods that you can use on Section objects:
|
|
117
|
-
|
|
118
|
-
```ts
|
|
119
|
-
import { Override, createSection } from '@riotprompt';
|
|
120
|
-
|
|
121
|
-
// Suppose we have a base section created or loaded:
|
|
122
|
-
const baseSection = createSection("Instructions");
|
|
123
|
-
baseSection.add("Follow the company style guide.");
|
|
124
|
-
baseSection.add("Keep the email concise.");
|
|
125
|
-
|
|
126
|
-
// Create an Override instance
|
|
127
|
-
const override = Override.create({
|
|
128
|
-
configDir: './overrides',
|
|
129
|
-
overrides: true
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// Customize the section using an override file path
|
|
133
|
-
await override.customize('instructions/email.md', baseSection);
|
|
134
|
-
|
|
135
|
-
// Now baseSection might have content prepended, appended, or completely replaced
|
|
136
|
-
// depending on what override files exist in the './overrides' directory:
|
|
137
|
-
// - ./overrides/instructions/email.md (full override)
|
|
138
|
-
// - ./overrides/instructions/email-pre.md (prepend)
|
|
139
|
-
// - ./overrides/instructions/email-post.md (append)
|
|
140
|
-
|
|
141
|
-
console.log(baseSection.items[0].text);
|
|
142
|
-
// Output depends on what override files exist
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
In this example, `override.customize` takes a file path string ('instructions/email.md'), a section object (baseSection), and optionally section options. Under the hood, this is what Builder would do when it finds override files.
|
|
146
|
-
|
|
147
|
-
Even if you don't call `Override` methods directly, understanding this helps – basically riotprompt will inject the override content at the appropriate place.
|
|
148
|
-
|
|
149
|
-
## Guidance for Structuring Overrides
|
|
150
|
-
|
|
151
|
-
When using overrides in your project, consider the following best practices:
|
|
152
|
-
|
|
153
|
-
* **Mirror File Structure**: Keep the override files in a parallel structure to the base prompt files. This makes it clear which base file each override is targeting. For example, if the base has `personas/agent.md`, put the override in `overrides/personas/agent.md` (or `agent-pre.md`/`agent-post.md` as needed).
|
|
154
|
-
* **Minimize Full Overrides**: Whenever possible, use prepend (`-pre.md`) or append (`-post.md`) files to adjust content, rather than completely overriding. Prepending/appending is less likely to break core functionality because you are adding to the existing prompt rather than replacing it. Use a full override (`same-name.md`) only when you need to substantially change or replace the entire content.
|
|
155
|
-
* **Use Overrides for Environment-Specific Tweaks**: If you have multiple deployment environments or user customizations, you can maintain different override directories or files for each case. For example, one override file could tweak the tone of instructions for a specific client without affecting the base prompt shared by others.
|
|
156
|
-
* **Test Overrides Thoroughly**: Because overrides can change the behavior of prompts, test with and without overrides enabled (if your app allows). Tools like Cortalyne have a debug mode to show final prompts after overrides – use that to ensure your override content is merging correctly.
|
|
157
|
-
* **Document Your Overrides**: Within your team or project, document what each override file is intended to do. Since they change default behavior, it's good to have a note (even within the file as a comment) about why the override exists.
|
|
158
|
-
|
|
159
|
-
## Example: Combining Base and Override Content
|
|
160
|
-
|
|
161
|
-
Let's walk through a concrete example. Imagine a base persona file `prompts/personas/assistant.md`:
|
|
162
|
-
|
|
163
|
-
```markdown
|
|
164
|
-
# Assistant Persona
|
|
165
|
-
- You are a helpful assistant with expertise in marketing.
|
|
166
|
-
- Your tone is friendly and professional.
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Now suppose for a specific scenario we want the assistant to adopt a more formal tone, but we don't want to change the base file (since that is the default for other scenarios). We create an override file `overrides/personas/assistant-pre.md` with:
|
|
170
|
-
|
|
171
|
-
```markdown
|
|
172
|
-
- (Formal Override) Your tone is formal and courteous.
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
We also create `overrides/personas/assistant-post.md` with:
|
|
176
|
-
|
|
177
|
-
```markdown
|
|
178
|
-
- Always adhere to corporate communication guidelines.
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
We run our builder with overrides enabled. What happens?
|
|
182
|
-
|
|
183
|
-
* riotprompt loads the base `assistant.md` persona Section:
|
|
184
|
-
|
|
185
|
-
* Title: "Assistant Persona"
|
|
186
|
-
* Items:
|
|
187
|
-
|
|
188
|
-
1. "You are a helpful assistant with expertise in marketing."
|
|
189
|
-
2. "Your tone is friendly and professional."
|
|
190
|
-
* It finds `assistant-pre.md` and `assistant-post.md` in overrides for that path.
|
|
191
|
-
* The content from `assistant-pre.md` ("Your tone is formal and courteous.") is inserted at the **beginning** of the assistant persona's items (before the original items).
|
|
192
|
-
* The content from `assistant-post.md` ("Always adhere to corporate communication guidelines.") is inserted at the **end** of the items list.
|
|
193
|
-
* The resulting Section "Assistant Persona" now has:
|
|
194
|
-
|
|
195
|
-
1. "(Formal Override) Your tone is formal and courteous."
|
|
196
|
-
2. "You are a helpful assistant with expertise in marketing."
|
|
197
|
-
3. "Your tone is friendly and professional."
|
|
198
|
-
4. "Always adhere to corporate communication guidelines."
|
|
199
|
-
|
|
200
|
-
Notice how the original content was not removed – we only added to it in this case. If we had provided an `assistant.md` in the override directory (a full override), then none of the original items would be kept (they would be replaced entirely by whatever is in that override file).
|
|
201
|
-
|
|
202
|
-
This mechanism is powerful for tweaking behavior on the fly.
|
|
203
|
-
|
|
204
|
-
## Using Parameters with Overrides
|
|
205
|
-
|
|
206
|
-
The Override utility supports dynamic content customization through parameters. Parameters allow you to define placeholders in your prompt text (e.g., `{{variable}}`) that get replaced with specific values when the prompt is processed. This creates even more flexibility in your override files.
|
|
207
|
-
|
|
208
|
-
### Example: Dynamic Overrides with Parameters
|
|
209
|
-
|
|
210
|
-
Consider a scenario where you want to customize a chatbot persona for different clients while maintaining a consistent base structure. You can use parameters in your override files to achieve this:
|
|
211
|
-
|
|
212
|
-
```ts
|
|
213
|
-
import { Override, createSection, createParameters } from '@riotprompt';
|
|
214
|
-
|
|
215
|
-
// Create a base assistant persona
|
|
216
|
-
const assistantPersona = createSection("Assistant Persona");
|
|
217
|
-
assistantPersona.add("You are a helpful customer service AI.");
|
|
218
|
-
assistantPersona.add("You provide clear, accurate information.");
|
|
219
|
-
|
|
220
|
-
// Create parameters for client-specific customization
|
|
221
|
-
const clientParameters = createParameters({
|
|
222
|
-
clientName: "Acme Corporation",
|
|
223
|
-
industry: "manufacturing",
|
|
224
|
-
supportEmail: "support@acme.com"
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
// Create an override instance with parameters
|
|
228
|
-
const override = Override.create({
|
|
229
|
-
configDir: './overrides',
|
|
230
|
-
overrides: true,
|
|
231
|
-
parameters: clientParameters
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
// Apply client-specific overrides with parameter substitution
|
|
235
|
-
await override.customize('personas/assistant.md', assistantPersona);
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
Now in your override file `./overrides/personas/assistant-post.md`, you can use those parameters:
|
|
239
|
-
|
|
240
|
-
```markdown
|
|
241
|
-
- You are assisting {{clientName}}, a company in the {{industry}} industry.
|
|
242
|
-
- For additional support, direct customers to {{supportEmail}}.
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
When processed, the placeholders will be replaced with the actual values provided in the parameters, resulting in:
|
|
246
|
-
|
|
247
|
-
```
|
|
248
|
-
# Assistant Persona
|
|
249
|
-
- You are a helpful customer service AI.
|
|
250
|
-
- You provide clear, accurate information.
|
|
251
|
-
- You are assisting Acme Corporation, a company in the manufacturing industry.
|
|
252
|
-
- For additional support, direct customers to support@acme.com.
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
This approach allows you to:
|
|
256
|
-
1. Maintain a clean separation between core prompt content and customizations
|
|
257
|
-
2. Dynamically update client-specific information without editing the override files
|
|
258
|
-
3. Reuse the same override files with different parameter sets for various clients or environments
|
|
259
|
-
|
|
260
|
-
When combined with environment variables or configuration files, this creates a powerful system for maintaining context-aware prompts that can be tailored for different scenarios without duplicating content.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
## Configuring Logging
|
|
264
|
-
|
|
265
|
-
The Override utility supports custom logging to help debug override operations. When creating an Override instance, you can supply a `logger` property in the options:
|
|
266
|
-
|
|
267
|
-
```ts
|
|
268
|
-
import { Override, Logger } from '@riotprompt';
|
|
269
|
-
|
|
270
|
-
// Create a custom logger
|
|
271
|
-
const myLogger: Logger = {
|
|
272
|
-
debug: (message, ...args) => myCustomLogging.debug(message, args),
|
|
273
|
-
info: (message, ...args) => myCustomLogging.info(message, args),
|
|
274
|
-
warn: (message, ...args) => myCustomLogging.warn(message, args),
|
|
275
|
-
error: (message, ...args) => myCustomLogging.error(message, args),
|
|
276
|
-
verbose: (message, ...args) => myCustomLogging.verbose(message, args),
|
|
277
|
-
silly: (message, ...args) => myCustomLogging.silly(message, args)
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
// Use custom logger with Override
|
|
281
|
-
const override = Override.create({
|
|
282
|
-
configDir: './overrides',
|
|
283
|
-
overrides: true,
|
|
284
|
-
logger: myLogger
|
|
285
|
-
});
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
> **Note:** You don't have to create your own logger. This interface matches the default Winston logger interface, so you can directly use Winston loggers with riotprompt.
|
|
289
|
-
|
|
290
|
-
The `Logger` interface requires six methods:
|
|
291
|
-
|
|
292
|
-
```ts
|
|
293
|
-
interface Logger {
|
|
294
|
-
debug: (message: string, ...args: any[]) => void;
|
|
295
|
-
info: (message: string, ...args: any[]) => void;
|
|
296
|
-
warn: (message: string, ...args: any[]) => void;
|
|
297
|
-
error: (message: string, ...args: any[]) => void;
|
|
298
|
-
verbose: (message: string, ...args: any[]) => void;
|
|
299
|
-
silly: (message: string, ...args: any[]) => void;
|
|
300
|
-
}
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
If you don't provide a logger, riotprompt uses a default logger that maps these methods to the corresponding `console` methods (`console.debug`, `console.info`, etc.), with `verbose` and `silly` both mapping to `console.log`.
|
|
304
|
-
|
|
305
|
-
The logger helps you track:
|
|
306
|
-
- When override files are found
|
|
307
|
-
- Which override files are being applied
|
|
308
|
-
- When content is being prepended, appended, or replaced
|
|
309
|
-
- The final content after all overrides are applied
|
|
310
|
-
|
|
311
|
-
This is particularly useful during development and troubleshooting to understand exactly how your overrides are being applied to your prompt content.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
## Conclusion
|
|
315
|
-
|
|
316
|
-
The Override utility makes riotprompt flexible and extensible:
|
|
317
|
-
|
|
318
|
-
* It enables on-the-fly customization of prompt content.
|
|
319
|
-
* It supports complex use cases like user-specific or context-specific prompt adjustments, all while keeping base prompts clean and general.
|
|
320
|
-
* When building applications (like Cortalyne) on top of riotprompt, consider exposing an override mechanism to your end users or for your configurations. This way, you can ship a set of robust default prompts and still allow modifications without editing those core files.
|
|
321
|
-
|
|
322
|
-
By following a consistent override pattern (filename matching and pre/post suffixes) and using the Builder's override support, you can manage prompt variations systematically and safely.
|
|
323
|
-
|
package/docs/parser.md
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
# riotprompt Parser Utility
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
The **Parser** utility in riotprompt allows you to convert Markdown content into riotprompt's structured prompt format. This means you can write prompt sections, instructions, and context in a Markdown file (using headings, lists, etc.) and then use the Parser to turn that into riotprompt Section objects. By using the Parser, you focus on writing clear prompt text in Markdown, while riotprompt handles building the internal structure (sections and items) for you.
|
|
6
|
-
|
|
7
|
-
Key capabilities of the Parser include:
|
|
8
|
-
|
|
9
|
-
* **Markdown to Sections**: Automatically interpreting markdown headers as section titles and text as section content.
|
|
10
|
-
* **Nested Structure**: Handling nested headers (e.g. `#`, `##`, `###`) by creating nested Section structures.
|
|
11
|
-
* **List Items**: Treating bullet or numbered list items as individual items in a section.
|
|
12
|
-
* **Paragraph Separation**: Treating separate paragraphs (separated by blank lines) as distinct items.
|
|
13
|
-
|
|
14
|
-
This makes it easy to draft complex prompts in a markdown document and programmatically load them into your application.
|
|
15
|
-
|
|
16
|
-
## Creating a Parser
|
|
17
|
-
|
|
18
|
-
To use the Parser, import it from the riotprompt library and create an instance. riotprompt uses a factory method pattern; you create a Parser via the static `Parser.create()` method. The `create` method can accept an optional configuration object to adjust parsing behavior.
|
|
19
|
-
|
|
20
|
-
**Syntax:**
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
const parser = Parser.create(options?);
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
If no options are provided, the parser will use default behavior that suits most use cases (detailed below).
|
|
27
|
-
|
|
28
|
-
### Parser.create Options
|
|
29
|
-
|
|
30
|
-
When calling `Parser.create`, you can supply an object to customize its behavior. All options are optional. Here are the options available for the Parser's creation:
|
|
31
|
-
|
|
32
|
-
* **`logger`** (Logger): An optional logger object for receiving information about the parsing process.
|
|
33
|
-
* **`parameters`** (Parameters object): A **Parameters** object containing placeholder values to substitute into the text while parsing. If provided, any placeholders in the form `{{variable}}` found in the markdown content will be replaced with the corresponding values from this object as the parser constructs the items.
|
|
34
|
-
|
|
35
|
-
### Using the Parser
|
|
36
|
-
|
|
37
|
-
Once you have a Parser instance, use the `parse` method to convert a markdown string (or file content) into a Section structure. The result of `parser.parse(...)` is usually a `Section` object representing the top-level section (or a container section) of the parsed content.
|
|
38
|
-
|
|
39
|
-
**Example:** Parse a markdown string with multiple sections.
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
import { Parser, Section } from '@riotprompt';
|
|
43
|
-
|
|
44
|
-
const markdownContent = `
|
|
45
|
-
# Instructions
|
|
46
|
-
Follow these guidelines when writing code.
|
|
47
|
-
|
|
48
|
-
## Best Practices
|
|
49
|
-
- Keep functions small and focused
|
|
50
|
-
- Use meaningful variable names
|
|
51
|
-
|
|
52
|
-
## Documentation
|
|
53
|
-
- Comment complex logic
|
|
54
|
-
- Document public APIs thoroughly
|
|
55
|
-
`;
|
|
56
|
-
|
|
57
|
-
// Create a Parser (with default options)
|
|
58
|
-
const parser = Parser.create();
|
|
59
|
-
|
|
60
|
-
// Parse the markdown into a Section structure
|
|
61
|
-
const rootSection: Section = parser.parse(markdownContent);
|
|
62
|
-
|
|
63
|
-
// The rootSection now represents "Instructions" with two subsections inside.
|
|
64
|
-
console.log(rootSection.title);
|
|
65
|
-
// Output: "Instructions"
|
|
66
|
-
|
|
67
|
-
// Accessing subsections by index:
|
|
68
|
-
const bestPractices = rootSection.items[1] as Section;
|
|
69
|
-
console.log(bestPractices.title);
|
|
70
|
-
// Output: "Best Practices"
|
|
71
|
-
|
|
72
|
-
// Items within a section:
|
|
73
|
-
for (const item of bestPractices.items) {
|
|
74
|
-
console.log(item.text);
|
|
75
|
-
}
|
|
76
|
-
// Output:
|
|
77
|
-
// "Keep functions small and focused"
|
|
78
|
-
// "Use meaningful variable names"
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
In this example:
|
|
82
|
-
|
|
83
|
-
* The top-level `# Instructions` became a Section titled "Instructions" (`rootSection`).
|
|
84
|
-
* The content under "Instructions" before any sub-heading (`"Follow these guidelines when writing code."`) became the first item of `rootSection`.
|
|
85
|
-
* The `## Best Practices` line created a subsection (another Section) within Instructions.
|
|
86
|
-
* Each bullet point under "Best Practices" became a separate item in that subsection.
|
|
87
|
-
* Similarly, `## Documentation` became another subsection with its own list of items.
|
|
88
|
-
|
|
89
|
-
You can now manipulate these sections in code. For instance, you might add another item:
|
|
90
|
-
|
|
91
|
-
```ts
|
|
92
|
-
bestPractices.add("- Write tests for your code");
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
This appends a new bullet item under "Best Practices". The parser makes it easy to initialize complex structures, which you can then modify or combine with other sections.
|
|
96
|
-
|
|
97
|
-
### Parsing Files Directly
|
|
98
|
-
|
|
99
|
-
The Parser provides a convenient `parseFile` method to directly parse Markdown files from disk:
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
const section = parser.parseFile('./prompts/instructions/code-style.md');
|
|
103
|
-
console.log(`Parsed section titled: ${section.title}`);
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
This method handles reading the file and parsing its content in a single step, making it easier to load prompt content from files.
|
|
107
|
-
|
|
108
|
-
If you prefer to read files manually, you can still use the standard `parse` method:
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
import * as fs from 'fs';
|
|
112
|
-
const fileText = fs.readFileSync('./prompts/instructions/code-style.md', 'utf-8');
|
|
113
|
-
const parser = Parser.create({
|
|
114
|
-
logger: console
|
|
115
|
-
});
|
|
116
|
-
const section = parser.parse(fileText);
|
|
117
|
-
console.log(`Parsed section titled: ${section.title}`);
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
This will read a markdown file from the filesystem and parse it into a Section. The example provides a simple logger to track the parsing process.
|
|
121
|
-
|
|
122
|
-
## Notes and Tips
|
|
123
|
-
|
|
124
|
-
* **Headings as Structure**: The Parser treats Markdown headings (`#`, `##`, etc.) as structural markers. Ensure your markdown content uses headings for logical sections you want to separate in the prompt. Text that is not under any heading will be grouped under an implicit section.
|
|
125
|
-
* **Lists and Paragraphs**: Bullet lists (`- ` or `* ` or numbered lists) are split into individual items. Normal paragraphs separated by blank lines are also treated as separate items. This means in the final Section object, each bullet or paragraph is one `Instruction`, `Content` or `Context` item (depending on usage).
|
|
126
|
-
* **Markdown Formatting**: The parser handles basic Markdown syntax, converting it into an internal structure. The formatting style (whether using tags or markdown) will be handled by the Formatter when producing the final prompt string.
|
|
127
|
-
* **Combining with Formatter**: The Parser simply builds the internal representation. To get a prompt string to send to an LLM, you would pass the resulting Section into a `Formatter`. Formatter will apply formatting options (like using XML-style tags or Markdown headings) to produce the final prompt text. For example, `Formatter.create().format(section)`.
|
|
128
|
-
|
|
129
|
-
Using the Parser utility, you can maintain your prompt content in easy-to-edit markdown files or strings, and reliably construct structured prompt objects for further manipulation or formatting.
|
|
130
|
-
|
package/eslint.config.mjs
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
-
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
|
3
|
-
import importPlugin from "eslint-plugin-import";
|
|
4
|
-
import globals from "globals";
|
|
5
|
-
import tsParser from "@typescript-eslint/parser";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
-
import js from "@eslint/js";
|
|
9
|
-
import { FlatCompat } from "@eslint/eslintrc";
|
|
10
|
-
|
|
11
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
-
const __dirname = path.dirname(__filename);
|
|
13
|
-
const compat = new FlatCompat({
|
|
14
|
-
baseDirectory: __dirname,
|
|
15
|
-
recommendedConfig: js.configs.recommended,
|
|
16
|
-
allConfig: js.configs.all
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
export default defineConfig([
|
|
20
|
-
globalIgnores([
|
|
21
|
-
"dist/**",
|
|
22
|
-
"node_modules/**",
|
|
23
|
-
"**/*.test.ts",
|
|
24
|
-
]),
|
|
25
|
-
{
|
|
26
|
-
extends: compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"),
|
|
27
|
-
|
|
28
|
-
plugins: {
|
|
29
|
-
"@typescript-eslint": typescriptEslint,
|
|
30
|
-
"import": importPlugin,
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
languageOptions: {
|
|
34
|
-
globals: {
|
|
35
|
-
...globals.node,
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
parser: tsParser,
|
|
39
|
-
ecmaVersion: "latest",
|
|
40
|
-
sourceType: "module",
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
rules: {
|
|
44
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
45
|
-
"@typescript-eslint/explicit-function-return-type": "off",
|
|
46
|
-
|
|
47
|
-
"@typescript-eslint/no-unused-vars": ["warn", {
|
|
48
|
-
argsIgnorePattern: "^_",
|
|
49
|
-
}],
|
|
50
|
-
|
|
51
|
-
indent: ["error", 4, {
|
|
52
|
-
SwitchCase: 1,
|
|
53
|
-
}],
|
|
54
|
-
|
|
55
|
-
"import/extensions": ["error", "never", {
|
|
56
|
-
ignorePackages: true,
|
|
57
|
-
pattern: {
|
|
58
|
-
"js": "never",
|
|
59
|
-
"ts": "never",
|
|
60
|
-
"d": "always"
|
|
61
|
-
}
|
|
62
|
-
}],
|
|
63
|
-
|
|
64
|
-
"import/no-extraneous-dependencies": ["error", {
|
|
65
|
-
devDependencies: true,
|
|
66
|
-
optionalDependencies: false,
|
|
67
|
-
peerDependencies: false,
|
|
68
|
-
}],
|
|
69
|
-
|
|
70
|
-
"no-console": ["error"],
|
|
71
|
-
|
|
72
|
-
"no-restricted-imports": ["error", {
|
|
73
|
-
paths: ["dayjs", "fs", "moment-timezone"],
|
|
74
|
-
patterns: [
|
|
75
|
-
{
|
|
76
|
-
group: ["src/**"],
|
|
77
|
-
message: "Use absolute imports instead of relative imports"
|
|
78
|
-
}
|
|
79
|
-
]
|
|
80
|
-
}]
|
|
81
|
-
},
|
|
82
|
-
}]);
|
package/nodemon.json
DELETED
package/vite.config.ts
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vite';
|
|
2
|
-
import { VitePluginNode } from 'vite-plugin-node';
|
|
3
|
-
import replace from '@rollup/plugin-replace';
|
|
4
|
-
// import { visualizer } from 'rollup-plugin-visualizer';
|
|
5
|
-
import { execSync } from 'child_process';
|
|
6
|
-
import shebang from 'rollup-plugin-preserve-shebang';
|
|
7
|
-
import dts from 'vite-plugin-dts';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
|
|
10
|
-
let gitInfo = {
|
|
11
|
-
branch: '',
|
|
12
|
-
commit: '',
|
|
13
|
-
tags: '',
|
|
14
|
-
commitDate: '',
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
gitInfo = {
|
|
19
|
-
branch: execSync('git rev-parse --abbrev-ref HEAD').toString().trim(),
|
|
20
|
-
commit: execSync('git rev-parse --short HEAD').toString().trim(),
|
|
21
|
-
tags: '',
|
|
22
|
-
commitDate: execSync('git log -1 --format=%cd --date=iso').toString().trim(),
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
gitInfo.tags = execSync('git tag --points-at HEAD | paste -sd "," -').toString().trim();
|
|
27
|
-
} catch {
|
|
28
|
-
gitInfo.tags = '';
|
|
29
|
-
}
|
|
30
|
-
} catch {
|
|
31
|
-
// eslint-disable-next-line no-console
|
|
32
|
-
console.log('Directory does not have a Git repository, skipping git info');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
export default defineConfig({
|
|
37
|
-
server: {
|
|
38
|
-
port: 3000
|
|
39
|
-
},
|
|
40
|
-
resolve: {
|
|
41
|
-
alias: {
|
|
42
|
-
'@': path.resolve(__dirname, './src')
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
plugins: [
|
|
46
|
-
...VitePluginNode({
|
|
47
|
-
adapter: 'express',
|
|
48
|
-
appPath: './src/riotprompt.ts',
|
|
49
|
-
exportName: 'viteNodeApp',
|
|
50
|
-
tsCompiler: 'swc',
|
|
51
|
-
swcOptions: {
|
|
52
|
-
sourceMaps: true,
|
|
53
|
-
},
|
|
54
|
-
}),
|
|
55
|
-
// visualizer({
|
|
56
|
-
// template: 'network',
|
|
57
|
-
// filename: 'network.html',
|
|
58
|
-
// projectRoot: process.cwd(),
|
|
59
|
-
// }),
|
|
60
|
-
replace({
|
|
61
|
-
'__VERSION__': process.env.npm_package_version,
|
|
62
|
-
'__GIT_BRANCH__': gitInfo.branch,
|
|
63
|
-
'__GIT_COMMIT__': gitInfo.commit,
|
|
64
|
-
'__GIT_TAGS__': gitInfo.tags === '' ? '' : `T:${gitInfo.tags}`,
|
|
65
|
-
'__GIT_COMMIT_DATE__': gitInfo.commitDate,
|
|
66
|
-
'__SYSTEM_INFO__': `${process.platform} ${process.arch} ${process.version}`,
|
|
67
|
-
preventAssignment: true,
|
|
68
|
-
}),
|
|
69
|
-
dts({
|
|
70
|
-
entryRoot: 'src',
|
|
71
|
-
outDir: 'dist',
|
|
72
|
-
exclude: ['**/*.test.ts'],
|
|
73
|
-
include: ['**/*.ts'],
|
|
74
|
-
}),
|
|
75
|
-
],
|
|
76
|
-
build: {
|
|
77
|
-
target: 'esnext',
|
|
78
|
-
outDir: 'dist',
|
|
79
|
-
lib: {
|
|
80
|
-
entry: './src/riotprompt.ts',
|
|
81
|
-
formats: ['es', 'cjs'],
|
|
82
|
-
fileName: (format) => `riotprompt.${format === 'es' ? 'js' : 'cjs'}`,
|
|
83
|
-
},
|
|
84
|
-
rollupOptions: {
|
|
85
|
-
external: [
|
|
86
|
-
'@riotprompt',
|
|
87
|
-
'@riotprompt/formatter',
|
|
88
|
-
'@riotprompt/chat'
|
|
89
|
-
],
|
|
90
|
-
output: [
|
|
91
|
-
{
|
|
92
|
-
format: 'esm',
|
|
93
|
-
entryFileNames: '[name].js',
|
|
94
|
-
preserveModules: true,
|
|
95
|
-
exports: 'named',
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
format: 'cjs',
|
|
99
|
-
entryFileNames: 'riotprompt.cjs',
|
|
100
|
-
preserveModules: false,
|
|
101
|
-
exports: 'named',
|
|
102
|
-
}
|
|
103
|
-
],
|
|
104
|
-
plugins: [
|
|
105
|
-
shebang({
|
|
106
|
-
shebang: '#!/usr/bin/env node',
|
|
107
|
-
}),
|
|
108
|
-
],
|
|
109
|
-
},
|
|
110
|
-
modulePreload: false,
|
|
111
|
-
minify: false,
|
|
112
|
-
sourcemap: true
|
|
113
|
-
},
|
|
114
|
-
});
|