@riotprompt/riotprompt 0.0.11 → 0.0.13
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 +1 -1
- package/README.md +121 -21
- package/dist/builder.js +3 -0
- package/dist/builder.js.map +1 -1
- package/dist/cli.cjs +1519 -0
- package/dist/cli.d.ts +8 -0
- package/dist/config.d.ts +7 -0
- package/dist/conversation-logger.js +6 -1
- package/dist/conversation-logger.js.map +1 -1
- package/dist/execution/anthropic.d.ts +5 -0
- package/dist/execution/anthropic.js +46 -0
- package/dist/execution/anthropic.js.map +1 -0
- package/dist/execution/gemini.d.ts +5 -0
- package/dist/execution/gemini.js +78 -0
- package/dist/execution/gemini.js.map +1 -0
- package/dist/execution/index.d.ts +10 -0
- package/dist/execution/index.js +53 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/openai.d.ts +5 -0
- package/dist/execution/openai.js +44 -0
- package/dist/execution/openai.js.map +1 -0
- package/dist/execution/provider.d.ts +18 -0
- package/dist/loader.js +3 -0
- package/dist/loader.js.map +1 -1
- package/dist/model-config.d.ts +1 -0
- package/dist/model-config.js +19 -18
- package/dist/model-config.js.map +1 -1
- package/dist/override.js +3 -0
- package/dist/override.js.map +1 -1
- package/dist/recipes.js +3 -0
- package/dist/recipes.js.map +1 -1
- package/dist/riotprompt.cjs +612 -77
- package/dist/riotprompt.cjs.map +1 -1
- package/dist/riotprompt.d.ts +3 -0
- package/dist/riotprompt.js +6 -0
- package/dist/riotprompt.js.map +1 -1
- package/dist/serializer.d.ts +5 -0
- package/dist/serializer.js +220 -0
- package/dist/serializer.js.map +1 -0
- package/dist/writer.d.ts +2 -0
- package/dist/writer.js +91 -0
- package/dist/writer.js.map +1 -0
- package/guide/architecture.md +51 -0
- package/guide/configuration.md +51 -0
- package/guide/development.md +62 -0
- package/guide/index.md +55 -0
- package/guide/usage.md +99 -0
- package/package.json +14 -3
- package/vite.config.cli.ts +49 -0
- package/BUG-ANALYSIS.md +0 -523
- package/CODE-REVIEW-SUMMARY.md +0 -330
- package/FIXES-APPLIED.md +0 -437
package/dist/riotprompt.d.ts
CHANGED
|
@@ -22,6 +22,9 @@ export { ConversationLogger, ConversationReplayer } from './conversation-logger'
|
|
|
22
22
|
export { ToolRegistry } from './tools';
|
|
23
23
|
export { StrategyExecutor, IterationStrategyFactory } from './iteration-strategy';
|
|
24
24
|
export { MetricsCollector, ReflectionReportGenerator } from './reflection';
|
|
25
|
+
export * as Serializer from './serializer';
|
|
26
|
+
export * as Writer from './writer';
|
|
27
|
+
export * as Execution from './execution/index';
|
|
25
28
|
export { ModelRegistry, getModelRegistry, resetModelRegistry, getPersonaRole, getEncoding, supportsToolCalls, getModelFamily, configureModel } from './model-config';
|
|
26
29
|
export type { Content } from './items/content';
|
|
27
30
|
export type { Context } from './items/context';
|
package/dist/riotprompt.js
CHANGED
|
@@ -29,5 +29,11 @@ export { ConversationLogger, ConversationReplayer } from './conversation-logger.
|
|
|
29
29
|
export { ToolRegistry } from './tools.js';
|
|
30
30
|
export { IterationStrategyFactory, StrategyExecutor } from './iteration-strategy.js';
|
|
31
31
|
export { MetricsCollector, ReflectionReportGenerator } from './reflection.js';
|
|
32
|
+
import * as serializer from './serializer.js';
|
|
33
|
+
export { serializer as Serializer };
|
|
34
|
+
import * as writer from './writer.js';
|
|
35
|
+
export { writer as Writer };
|
|
36
|
+
import * as index from './execution/index.js';
|
|
37
|
+
export { index as Execution };
|
|
32
38
|
export { ModelRegistry, configureModel, getEncoding, getModelFamily, getModelRegistry, getPersonaRole, resetModelRegistry, supportsToolCalls } from './model-config.js';
|
|
33
39
|
//# sourceMappingURL=riotprompt.js.map
|
package/dist/riotprompt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"riotprompt.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"riotprompt.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Prompt } from './prompt';
|
|
2
|
+
export declare const toJSON: (prompt: Prompt) => string;
|
|
3
|
+
export declare const toXML: (prompt: Prompt) => string;
|
|
4
|
+
export declare const fromJSON: (jsonString: string) => Prompt;
|
|
5
|
+
export declare const fromXML: (xmlString: string) => Prompt;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
2
|
+
import { create as create$1 } from './prompt.js';
|
|
3
|
+
import { create } from './items/section.js';
|
|
4
|
+
|
|
5
|
+
const toJSON = (prompt)=>{
|
|
6
|
+
return JSON.stringify(prompt, null, 2);
|
|
7
|
+
};
|
|
8
|
+
const escapeXML = (str)=>{
|
|
9
|
+
return str.replace(/[<>&'"]/g, (c)=>{
|
|
10
|
+
switch(c){
|
|
11
|
+
case '<':
|
|
12
|
+
return '<';
|
|
13
|
+
case '>':
|
|
14
|
+
return '>';
|
|
15
|
+
case '&':
|
|
16
|
+
return '&';
|
|
17
|
+
case '\'':
|
|
18
|
+
return ''';
|
|
19
|
+
case '"':
|
|
20
|
+
return '"';
|
|
21
|
+
default:
|
|
22
|
+
return c;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
const itemToXML = (item)=>{
|
|
27
|
+
if (typeof item === 'string') {
|
|
28
|
+
return `<item>${escapeXML(item)}</item>`;
|
|
29
|
+
}
|
|
30
|
+
// Check if it's a section
|
|
31
|
+
if (item && typeof item === 'object' && 'items' in item) {
|
|
32
|
+
return sectionToXML(item);
|
|
33
|
+
}
|
|
34
|
+
// Check if it's a weighted item
|
|
35
|
+
if (item && typeof item === 'object' && 'text' in item) {
|
|
36
|
+
const weightAttr = item.weight !== undefined && item.weight !== null ? ` weight="${item.weight}"` : '';
|
|
37
|
+
return `<item${weightAttr}>${escapeXML(item.text)}</item>`;
|
|
38
|
+
}
|
|
39
|
+
return '';
|
|
40
|
+
};
|
|
41
|
+
const sectionToXML = (section, tagName = 'section')=>{
|
|
42
|
+
const titleAttr = section.title ? ` title="${escapeXML(section.title)}"` : '';
|
|
43
|
+
const weightAttr = section.weight ? ` weight="${section.weight}"` : '';
|
|
44
|
+
let xml = `<${tagName}${titleAttr}${weightAttr}>`;
|
|
45
|
+
if (section.items && Array.isArray(section.items)) {
|
|
46
|
+
xml += section.items.map((item)=>itemToXML(item)).join('');
|
|
47
|
+
}
|
|
48
|
+
xml += `</${tagName}>`;
|
|
49
|
+
return xml;
|
|
50
|
+
};
|
|
51
|
+
const toXML = (prompt)=>{
|
|
52
|
+
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<prompt>';
|
|
53
|
+
if (prompt.persona) {
|
|
54
|
+
xml += sectionToXML(prompt.persona, 'persona');
|
|
55
|
+
}
|
|
56
|
+
if (prompt.instructions) {
|
|
57
|
+
xml += sectionToXML(prompt.instructions, 'instructions');
|
|
58
|
+
}
|
|
59
|
+
if (prompt.contents) {
|
|
60
|
+
xml += sectionToXML(prompt.contents, 'contents');
|
|
61
|
+
}
|
|
62
|
+
if (prompt.contexts) {
|
|
63
|
+
xml += sectionToXML(prompt.contexts, 'contexts');
|
|
64
|
+
}
|
|
65
|
+
xml += '</prompt>';
|
|
66
|
+
return xml;
|
|
67
|
+
};
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// DESERIALIZATION
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// --- JSON Parsing ---
|
|
72
|
+
const parseSectionFromJSON = (jsonSection)=>{
|
|
73
|
+
if (!jsonSection || !jsonSection.items) {
|
|
74
|
+
throw new Error("Invalid section structure");
|
|
75
|
+
}
|
|
76
|
+
const section = create({
|
|
77
|
+
title: jsonSection.title,
|
|
78
|
+
weight: jsonSection.weight
|
|
79
|
+
});
|
|
80
|
+
for (const item of jsonSection.items){
|
|
81
|
+
if (typeof item === 'object' && 'items' in item) {
|
|
82
|
+
// It's a nested section
|
|
83
|
+
section.add(parseSectionFromJSON(item));
|
|
84
|
+
} else if (typeof item === 'object' && 'text' in item) {
|
|
85
|
+
section.add({
|
|
86
|
+
text: item.text,
|
|
87
|
+
weight: item.weight
|
|
88
|
+
});
|
|
89
|
+
} else if (typeof item === 'string') {
|
|
90
|
+
section.add(item);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return section;
|
|
94
|
+
};
|
|
95
|
+
const fromJSON = (jsonString)=>{
|
|
96
|
+
const json = JSON.parse(jsonString);
|
|
97
|
+
// We treat the root json object as matching Prompt interface
|
|
98
|
+
// But we need to convert plain objects back to Section instances with methods
|
|
99
|
+
const persona = json.persona ? parseSectionFromJSON(json.persona) : undefined;
|
|
100
|
+
const instructions = json.instructions ? parseSectionFromJSON(json.instructions) : create({
|
|
101
|
+
title: 'Instructions'
|
|
102
|
+
});
|
|
103
|
+
const contents = json.contents ? parseSectionFromJSON(json.contents) : undefined;
|
|
104
|
+
const contexts = json.contexts ? parseSectionFromJSON(json.contexts) : undefined;
|
|
105
|
+
return create$1({
|
|
106
|
+
persona,
|
|
107
|
+
instructions,
|
|
108
|
+
contents,
|
|
109
|
+
contexts
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
// --- XML Parsing ---
|
|
113
|
+
const parseNodeToSection = (node)=>{
|
|
114
|
+
// Node structure with preserveOrder: true
|
|
115
|
+
// It seems attributes can be a sibling property ":@" OR inside the children array depending on version/config/content.
|
|
116
|
+
// Children are in the array value of the key "section", "persona", etc.
|
|
117
|
+
const children = node.section || node.persona || node.instructions || node.contents || node.contexts || [];
|
|
118
|
+
let attributes = node[":@"] || {};
|
|
119
|
+
// Fallback: check if attributes are inside the children array (as seen in some tests/mocks)
|
|
120
|
+
if (!node[":@"] && Array.isArray(children)) {
|
|
121
|
+
for (const child of children){
|
|
122
|
+
if (child[":@"]) {
|
|
123
|
+
attributes = child[":@"];
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const title = attributes["@_title"];
|
|
129
|
+
const weight = attributes["@_weight"] ? Number(attributes["@_weight"]) : undefined;
|
|
130
|
+
const section = create({
|
|
131
|
+
title,
|
|
132
|
+
weight
|
|
133
|
+
});
|
|
134
|
+
if (Array.isArray(children)) {
|
|
135
|
+
for (const child of children){
|
|
136
|
+
const key = Object.keys(child)[0]; // "item" or "section" or ":@"
|
|
137
|
+
// console.log(`Processing child key: ${key}`);
|
|
138
|
+
if (key === ":@") continue; // Already handled or just attributes
|
|
139
|
+
if (key === "item") {
|
|
140
|
+
// Item structure: [ { "#text": "Value" }, { ":@": ... } ]
|
|
141
|
+
const itemContent = child.item;
|
|
142
|
+
let text = "";
|
|
143
|
+
let itemWeight = undefined;
|
|
144
|
+
for (const part of itemContent){
|
|
145
|
+
const keys = Object.keys(part);
|
|
146
|
+
// console.log('Processing item part keys:', keys);
|
|
147
|
+
if (keys.includes("#text")) {
|
|
148
|
+
text = part["#text"];
|
|
149
|
+
} else if (keys.includes(":@")) {
|
|
150
|
+
const attrs = part[":@"];
|
|
151
|
+
// console.log('Found attributes:', attrs);
|
|
152
|
+
// Check both with and without prefix just in case
|
|
153
|
+
const w = attrs["@_weight"] || attrs["weight"];
|
|
154
|
+
if (w !== undefined) itemWeight = Number(w);
|
|
155
|
+
} else {
|
|
156
|
+
// Fallback for cases where attributes might be directly on the part (unexpected but possible)
|
|
157
|
+
const w = part["@_weight"] || part["weight"];
|
|
158
|
+
if (w !== undefined) itemWeight = Number(w);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// console.log(`Adding item: ${text}`);
|
|
162
|
+
section.add({
|
|
163
|
+
text,
|
|
164
|
+
weight: itemWeight
|
|
165
|
+
});
|
|
166
|
+
} else if (key === "section") {
|
|
167
|
+
section.add(parseNodeToSection(child));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return section;
|
|
172
|
+
};
|
|
173
|
+
const fromXML = (xmlString)=>{
|
|
174
|
+
const parser = new XMLParser({
|
|
175
|
+
ignoreAttributes: false,
|
|
176
|
+
attributeNamePrefix: "@_",
|
|
177
|
+
preserveOrder: true,
|
|
178
|
+
trimValues: true
|
|
179
|
+
});
|
|
180
|
+
const parsed = parser.parse(xmlString);
|
|
181
|
+
// parsed is [ { "?xml": ... }, { "prompt": [ ... ] } ]
|
|
182
|
+
let promptNode = null;
|
|
183
|
+
for (const node of parsed){
|
|
184
|
+
if (node.prompt) {
|
|
185
|
+
promptNode = node.prompt;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (!promptNode) throw new Error("Invalid XML: missing <prompt> root");
|
|
190
|
+
let persona;
|
|
191
|
+
let instructions = create({
|
|
192
|
+
title: "Instructions"
|
|
193
|
+
});
|
|
194
|
+
let contents;
|
|
195
|
+
let contexts;
|
|
196
|
+
for (const child of promptNode){
|
|
197
|
+
if (child.persona) {
|
|
198
|
+
persona = parseNodeToSection(child);
|
|
199
|
+
persona.title = "Persona"; // Force title for standard sections?
|
|
200
|
+
} else if (child.instructions) {
|
|
201
|
+
instructions = parseNodeToSection(child);
|
|
202
|
+
instructions.title = "Instructions";
|
|
203
|
+
} else if (child.contents) {
|
|
204
|
+
contents = parseNodeToSection(child);
|
|
205
|
+
contents.title = "Contents";
|
|
206
|
+
} else if (child.contexts) {
|
|
207
|
+
contexts = parseNodeToSection(child);
|
|
208
|
+
contexts.title = "Contexts";
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return create$1({
|
|
212
|
+
persona,
|
|
213
|
+
instructions,
|
|
214
|
+
contents,
|
|
215
|
+
contexts
|
|
216
|
+
});
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export { fromJSON, fromXML, toJSON, toXML };
|
|
220
|
+
//# sourceMappingURL=serializer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializer.js","sources":["../src/serializer.ts"],"sourcesContent":["import { XMLParser } from \"fast-xml-parser\";\nimport { Prompt, create as createPrompt } from \"./prompt\";\nimport { Section, create as createSection } from \"./items/section\";\nimport { Instruction } from \"./items/instruction\";\nimport { Context } from \"./items/context\";\nimport { Content } from \"./items/content\";\nimport { Weighted } from \"./items/weighted\";\n\nexport const toJSON = (prompt: Prompt): string => {\n return JSON.stringify(prompt, null, 2);\n}\n\nconst escapeXML = (str: string): string => {\n return str.replace(/[<>&'\"]/g, (c) => {\n switch (c) {\n case '<': return '<';\n case '>': return '>';\n case '&': return '&';\n case '\\'': return ''';\n case '\"': return '"';\n default: return c;\n }\n });\n}\n\nconst itemToXML = (item: any): string => {\n if (typeof item === 'string') {\n return `<item>${escapeXML(item)}</item>`;\n }\n \n // Check if it's a section\n if (item && typeof item === 'object' && 'items' in item) {\n return sectionToXML(item);\n }\n\n // Check if it's a weighted item\n if (item && typeof item === 'object' && 'text' in item) {\n const weightAttr = (item.weight !== undefined && item.weight !== null) ? ` weight=\"${item.weight}\"` : '';\n return `<item${weightAttr}>${escapeXML(item.text)}</item>`;\n }\n\n return '';\n}\n\nconst sectionToXML = (section: any, tagName: string = 'section'): string => {\n const titleAttr = section.title ? ` title=\"${escapeXML(section.title)}\"` : '';\n const weightAttr = section.weight ? ` weight=\"${section.weight}\"` : '';\n \n let xml = `<${tagName}${titleAttr}${weightAttr}>`;\n \n if (section.items && Array.isArray(section.items)) {\n xml += section.items.map((item: any) => itemToXML(item)).join('');\n }\n \n xml += `</${tagName}>`;\n return xml;\n}\n\nexport const toXML = (prompt: Prompt): string => {\n let xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<prompt>';\n\n if (prompt.persona) {\n xml += sectionToXML(prompt.persona, 'persona');\n }\n\n if (prompt.instructions) {\n xml += sectionToXML(prompt.instructions, 'instructions');\n }\n\n if (prompt.contents) {\n xml += sectionToXML(prompt.contents, 'contents');\n }\n\n if (prompt.contexts) {\n xml += sectionToXML(prompt.contexts, 'contexts');\n }\n\n xml += '</prompt>';\n return xml;\n}\n\n// ============================================================================\n// DESERIALIZATION\n// ============================================================================\n\n// --- JSON Parsing ---\n\nconst parseSectionFromJSON = <T extends Weighted>(jsonSection: any): Section<T> => {\n if (!jsonSection || !jsonSection.items) {\n throw new Error(\"Invalid section structure\");\n }\n\n const section = createSection<T>({\n title: jsonSection.title,\n weight: jsonSection.weight\n });\n\n for (const item of jsonSection.items) {\n if (typeof item === 'object' && 'items' in item) {\n // It's a nested section\n section.add(parseSectionFromJSON<T>(item));\n } else if (typeof item === 'object' && 'text' in item) {\n section.add({\n text: item.text,\n weight: item.weight\n } as T);\n } else if (typeof item === 'string') {\n section.add(item);\n }\n }\n\n return section;\n}\n\nexport const fromJSON = (jsonString: string): Prompt => {\n const json = JSON.parse(jsonString);\n \n // We treat the root json object as matching Prompt interface\n // But we need to convert plain objects back to Section instances with methods\n \n const persona = json.persona ? parseSectionFromJSON<Instruction>(json.persona) : undefined;\n const instructions = json.instructions ? parseSectionFromJSON<Instruction>(json.instructions) : createSection<Instruction>({ title: 'Instructions' });\n \n const contents = json.contents ? parseSectionFromJSON<Content>(json.contents) : undefined;\n const contexts = json.contexts ? parseSectionFromJSON<Context>(json.contexts) : undefined;\n\n return createPrompt({\n persona,\n instructions,\n contents,\n contexts\n });\n}\n\n// --- XML Parsing ---\n\nconst parseNodeToSection = <T extends Weighted>(node: any): Section<T> => {\n // Node structure with preserveOrder: true\n // It seems attributes can be a sibling property \":@\" OR inside the children array depending on version/config/content.\n \n // Children are in the array value of the key \"section\", \"persona\", etc.\n const children = node.section || node.persona || node.instructions || node.contents || node.contexts || [];\n \n let attributes = node[\":@\"] || {};\n \n // Fallback: check if attributes are inside the children array (as seen in some tests/mocks)\n if (!node[\":@\"] && Array.isArray(children)) {\n for (const child of children) {\n if (child[\":@\"]) {\n attributes = child[\":@\"];\n break;\n }\n }\n }\n\n const title = attributes[\"@_title\"];\n const weight = attributes[\"@_weight\"] ? Number(attributes[\"@_weight\"]) : undefined;\n \n const section = createSection<T>({ title, weight });\n \n if (Array.isArray(children)) {\n for (const child of children) {\n const key = Object.keys(child)[0]; // \"item\" or \"section\" or \":@\"\n // console.log(`Processing child key: ${key}`);\n if (key === \":@\") continue; // Already handled or just attributes\n \n if (key === \"item\") {\n // Item structure: [ { \"#text\": \"Value\" }, { \":@\": ... } ]\n const itemContent = child.item;\n let text = \"\";\n let itemWeight = undefined;\n \n for (const part of itemContent) {\n const keys = Object.keys(part);\n // console.log('Processing item part keys:', keys);\n if (keys.includes(\"#text\")) {\n text = part[\"#text\"];\n } else if (keys.includes(\":@\")) {\n const attrs = part[\":@\"];\n // console.log('Found attributes:', attrs);\n // Check both with and without prefix just in case\n const w = attrs[\"@_weight\"] || attrs[\"weight\"];\n if (w !== undefined) itemWeight = Number(w);\n } else {\n // Fallback for cases where attributes might be directly on the part (unexpected but possible)\n const w = part[\"@_weight\"] || part[\"weight\"];\n if (w !== undefined) itemWeight = Number(w);\n }\n }\n // console.log(`Adding item: ${text}`);\n section.add({ text, weight: itemWeight } as T);\n } else if (key === \"section\") {\n section.add(parseNodeToSection<T>(child));\n }\n }\n }\n \n return section;\n}\n\nexport const fromXML = (xmlString: string): Prompt => {\n const parser = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: \"@_\",\n preserveOrder: true,\n trimValues: true\n });\n\n const parsed = parser.parse(xmlString);\n // parsed is [ { \"?xml\": ... }, { \"prompt\": [ ... ] } ]\n\n let promptNode = null;\n for (const node of parsed) {\n if (node.prompt) {\n promptNode = node.prompt;\n break;\n }\n }\n\n if (!promptNode) throw new Error(\"Invalid XML: missing <prompt> root\");\n\n let persona: Section<Instruction> | undefined;\n let instructions: Section<Instruction> = createSection({ title: \"Instructions\" });\n let contents: Section<Content> | undefined;\n let contexts: Section<Context> | undefined;\n\n for (const child of promptNode) {\n if (child.persona) {\n persona = parseNodeToSection<Instruction>(child);\n persona.title = \"Persona\"; // Force title for standard sections?\n } else if (child.instructions) {\n instructions = parseNodeToSection<Instruction>(child);\n instructions.title = \"Instructions\";\n } else if (child.contents) {\n contents = parseNodeToSection<Content>(child);\n contents.title = \"Contents\";\n } else if (child.contexts) {\n contexts = parseNodeToSection<Context>(child);\n contexts.title = \"Contexts\";\n }\n }\n\n return createPrompt({\n persona,\n instructions,\n contents,\n contexts\n });\n}\n"],"names":["toJSON","prompt","JSON","stringify","escapeXML","str","replace","c","itemToXML","item","sectionToXML","weightAttr","weight","undefined","text","section","tagName","titleAttr","title","xml","items","Array","isArray","map","join","toXML","persona","instructions","contents","contexts","parseSectionFromJSON","jsonSection","Error","createSection","add","fromJSON","jsonString","json","parse","createPrompt","parseNodeToSection","node","children","attributes","child","Number","key","Object","keys","itemContent","itemWeight","part","includes","attrs","w","fromXML","xmlString","parser","XMLParser","ignoreAttributes","attributeNamePrefix","preserveOrder","trimValues","parsed","promptNode"],"mappings":"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"}
|
package/dist/writer.d.ts
ADDED
package/dist/writer.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
const saveToDirectory = async (prompt, basePath)=>{
|
|
5
|
+
// Ensure base directory exists
|
|
6
|
+
await fs.mkdir(basePath, {
|
|
7
|
+
recursive: true
|
|
8
|
+
});
|
|
9
|
+
// 1. Save Persona
|
|
10
|
+
if (prompt.persona) {
|
|
11
|
+
await saveSection(prompt.persona, path.join(basePath, 'persona'));
|
|
12
|
+
}
|
|
13
|
+
// 2. Save Instructions
|
|
14
|
+
if (prompt.instructions) {
|
|
15
|
+
await saveSection(prompt.instructions, path.join(basePath, 'instructions'));
|
|
16
|
+
}
|
|
17
|
+
// 3. Save Context
|
|
18
|
+
if (prompt.contexts) {
|
|
19
|
+
await saveSection(prompt.contexts, path.join(basePath, 'context'));
|
|
20
|
+
}
|
|
21
|
+
// 4. Save Content (if any)
|
|
22
|
+
if (prompt.contents) {
|
|
23
|
+
await saveSection(prompt.contents, path.join(basePath, 'content'));
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const saveSection = async (section, targetPath)=>{
|
|
27
|
+
// If section has subsections, create a directory and recurse
|
|
28
|
+
// If section only has items, check if we can save as single file (e.g. name of section + .md)
|
|
29
|
+
// Simplification: We will use a mixed approach.
|
|
30
|
+
// If the section is "flat" (only items), we can write it to a file.
|
|
31
|
+
// If the section is "nested" (has subsections), we create a directory.
|
|
32
|
+
// However, to be consistent with Loader logic:
|
|
33
|
+
// Loader reads files in a directory as subsections.
|
|
34
|
+
// So if we have a section "Instructions", and we save it as a directory "instructions":
|
|
35
|
+
// Its items become content of files?
|
|
36
|
+
// Strategy:
|
|
37
|
+
// 1. Create directory `targetPath`.
|
|
38
|
+
// 2. If the section has items (text), verify if they have titles (subsections).
|
|
39
|
+
// 3. For each item:
|
|
40
|
+
// - If it's a string/Instruction/Content/Context (leaf):
|
|
41
|
+
// - If the parent section is the ROOT (e.g. 'persona'), and it's just text, maybe we prefer `persona.md`.
|
|
42
|
+
// - If it's a subsection:
|
|
43
|
+
// - Recurse into subdirectory.
|
|
44
|
+
// Let's refine based on typical usage:
|
|
45
|
+
// - Persona: usually one big text. -> `persona.md`
|
|
46
|
+
// - Instructions: usually one big text or list of files. -> `instructions.md` or `instructions/part1.md`
|
|
47
|
+
// - Context: usually list of files. -> `context/data.json`, `context/info.md`
|
|
48
|
+
// We need to differentiate based on the targetPath provided by caller.
|
|
49
|
+
// If targetPath ends in "persona" (directory name), we can choose to write `targetPath.md` instead if it's flat.
|
|
50
|
+
// But `saveToDirectory` created `basePath/persona` (directory path string).
|
|
51
|
+
// Let's check complexity:
|
|
52
|
+
const hasSubsections = section.items.some((item)=>typeof item === 'object' && 'items' in item);
|
|
53
|
+
if (!hasSubsections) {
|
|
54
|
+
// Flat section.
|
|
55
|
+
// If it has multiple items, we can join them? Or write as numbered files?
|
|
56
|
+
// Usually, a flat section implies content for a single file.
|
|
57
|
+
// We prefer to write to `targetPath.md`.
|
|
58
|
+
const content = section.items.map((item)=>{
|
|
59
|
+
if (typeof item === 'string') return item;
|
|
60
|
+
return item.text;
|
|
61
|
+
}).join('\n\n');
|
|
62
|
+
// Check if we should write to .md file instead of directory
|
|
63
|
+
// targetPath is e.g. ".../persona". We want ".../persona.md".
|
|
64
|
+
await fs.writeFile(`${targetPath}.md`, content);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Nested section
|
|
68
|
+
await fs.mkdir(targetPath, {
|
|
69
|
+
recursive: true
|
|
70
|
+
});
|
|
71
|
+
for(let i = 0; i < section.items.length; i++){
|
|
72
|
+
const item = section.items[i];
|
|
73
|
+
if (typeof item === 'object' && 'items' in item) {
|
|
74
|
+
// Subsection
|
|
75
|
+
// Use title as filename/dirname
|
|
76
|
+
const subTitle = item.title || `part-${i + 1}`;
|
|
77
|
+
const subPath = path.join(targetPath, subTitle);
|
|
78
|
+
await saveSection(item, subPath);
|
|
79
|
+
} else {
|
|
80
|
+
// Leaf item mixed with subsections.
|
|
81
|
+
// Write to a file.
|
|
82
|
+
// If it's just text, we need a filename.
|
|
83
|
+
const fileName = `item-${i + 1}.md`;
|
|
84
|
+
const content = typeof item === 'string' ? item : item.text;
|
|
85
|
+
await fs.writeFile(path.join(targetPath, fileName), content);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export { saveToDirectory };
|
|
91
|
+
//# sourceMappingURL=writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.js","sources":["../src/writer.ts"],"sourcesContent":["import * as fs from 'fs/promises';\nimport * as path from 'path';\nimport { Prompt } from './prompt';\nimport { Section } from './items/section';\nimport { Weighted } from './items/weighted';\n\nexport const saveToDirectory = async (prompt: Prompt, basePath: string) => {\n // Ensure base directory exists\n await fs.mkdir(basePath, { recursive: true });\n\n // 1. Save Persona\n if (prompt.persona) {\n await saveSection(prompt.persona, path.join(basePath, 'persona'));\n }\n\n // 2. Save Instructions\n if (prompt.instructions) {\n await saveSection(prompt.instructions, path.join(basePath, 'instructions'));\n }\n\n // 3. Save Context\n if (prompt.contexts) {\n await saveSection(prompt.contexts, path.join(basePath, 'context'));\n }\n\n // 4. Save Content (if any)\n if (prompt.contents) {\n await saveSection(prompt.contents, path.join(basePath, 'content'));\n }\n}\n\nconst saveSection = async (section: Section<Weighted>, targetPath: string) => {\n // If section has subsections, create a directory and recurse\n // If section only has items, check if we can save as single file (e.g. name of section + .md)\n \n // Simplification: We will use a mixed approach.\n // If the section is \"flat\" (only items), we can write it to a file.\n // If the section is \"nested\" (has subsections), we create a directory.\n \n // However, to be consistent with Loader logic:\n // Loader reads files in a directory as subsections.\n // So if we have a section \"Instructions\", and we save it as a directory \"instructions\":\n // Its items become content of files?\n \n // Strategy:\n // 1. Create directory `targetPath`.\n // 2. If the section has items (text), verify if they have titles (subsections).\n // 3. For each item:\n // - If it's a string/Instruction/Content/Context (leaf):\n // - If the parent section is the ROOT (e.g. 'persona'), and it's just text, maybe we prefer `persona.md`.\n // - If it's a subsection:\n // - Recurse into subdirectory.\n\n // Let's refine based on typical usage:\n // - Persona: usually one big text. -> `persona.md`\n // - Instructions: usually one big text or list of files. -> `instructions.md` or `instructions/part1.md`\n // - Context: usually list of files. -> `context/data.json`, `context/info.md`\n \n // We need to differentiate based on the targetPath provided by caller.\n // If targetPath ends in \"persona\" (directory name), we can choose to write `targetPath.md` instead if it's flat.\n \n // But `saveToDirectory` created `basePath/persona` (directory path string).\n \n // Let's check complexity:\n const hasSubsections = section.items.some(item => \n typeof item === 'object' && 'items' in item\n );\n\n if (!hasSubsections) {\n // Flat section. \n // If it has multiple items, we can join them? Or write as numbered files?\n // Usually, a flat section implies content for a single file.\n // We prefer to write to `targetPath.md`.\n const content = section.items.map(item => {\n if (typeof item === 'string') return item;\n return (item as Weighted).text;\n }).join('\\n\\n');\n \n // Check if we should write to .md file instead of directory\n // targetPath is e.g. \".../persona\". We want \".../persona.md\".\n await fs.writeFile(`${targetPath}.md`, content);\n return;\n }\n\n // Nested section\n await fs.mkdir(targetPath, { recursive: true });\n\n for (let i = 0; i < section.items.length; i++) {\n const item = section.items[i];\n \n if (typeof item === 'object' && 'items' in item) {\n // Subsection\n // Use title as filename/dirname\n const subTitle = (item as Section<Weighted>).title || `part-${i + 1}`;\n const subPath = path.join(targetPath, subTitle);\n await saveSection(item as Section<Weighted>, subPath);\n } else {\n // Leaf item mixed with subsections.\n // Write to a file. \n // If it's just text, we need a filename.\n const fileName = `item-${i + 1}.md`;\n const content = typeof item === 'string' ? item : (item as Weighted).text;\n await fs.writeFile(path.join(targetPath, fileName), content);\n }\n }\n}\n\n"],"names":["saveToDirectory","prompt","basePath","fs","mkdir","recursive","persona","saveSection","path","join","instructions","contexts","contents","section","targetPath","hasSubsections","items","some","item","content","map","text","writeFile","i","length","subTitle","title","subPath","fileName"],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
**Purpose**: High-level overview of the internal design of `riotprompt`.
|
|
4
|
+
|
|
5
|
+
## Core Concepts
|
|
6
|
+
|
|
7
|
+
### Prompt Structure
|
|
8
|
+
A `Prompt` in RiotPrompt is not a string; it is a structured object containing four main components:
|
|
9
|
+
|
|
10
|
+
1. **Persona**: Defines *who* the AI is (System Prompt).
|
|
11
|
+
2. **Instructions**: Defines *what* the AI should do (User Prompt / Task).
|
|
12
|
+
3. **Context**: Background information, data, or documents needed to perform the task.
|
|
13
|
+
4. **Content**: Specific input data to be processed in this execution (optional, often merged with Context in simpler use cases).
|
|
14
|
+
|
|
15
|
+
### Section System
|
|
16
|
+
The fundamental building block is the `Section<T>`.
|
|
17
|
+
* A `Section` contains a list of items (of type `T`) and can also contain nested `Sections`.
|
|
18
|
+
* This allows for recursive, hierarchical structures (e.g., a Context section containing a "Market Data" section, which contains a "Q1 Results" section).
|
|
19
|
+
* **Weighted Items**: Most items extend `Weighted`, allowing them to have associated weights or parameters for advanced optimization (though simple usage ignores this).
|
|
20
|
+
|
|
21
|
+
## Module Structure
|
|
22
|
+
|
|
23
|
+
The project is organized into distinct logical modules:
|
|
24
|
+
|
|
25
|
+
* **`src/riotprompt.ts`**: The main entry point. Exports all sub-modules.
|
|
26
|
+
* **`src/prompt.ts`**: Defines the `Prompt` interface and factory.
|
|
27
|
+
* **`src/items/`**: Contains definitions for `Section`, `Instruction`, `Context`, `Content`.
|
|
28
|
+
* **`src/loader.ts`**: Logic for loading prompt parts from the filesystem. It handles traversing directories and parsing Markdown files (extracting headers as section titles).
|
|
29
|
+
* **`src/formatter.ts`**: Responsible for taking a `Prompt` object and converting it into a specific format (e.g., a Chat Request object or a flat string). It handles model-specific nuances via adapters.
|
|
30
|
+
* **`src/serializer.ts`**: Handles converting the internal `Prompt` structure to portable formats like JSON and XML.
|
|
31
|
+
* **`src/cli.ts`**: The command-line interface implementation, using `commander` and `cardigantime` for config.
|
|
32
|
+
|
|
33
|
+
## Data Flow (CLI)
|
|
34
|
+
|
|
35
|
+
1. **Input**: User provides a directory path.
|
|
36
|
+
2. **Loader**: `loader.ts` scans the directory.
|
|
37
|
+
* `persona.md` or `persona/` -> `Section<Instruction>` (Persona)
|
|
38
|
+
* `instructions.md` or `instructions/` -> `Section<Instruction>` (Instructions)
|
|
39
|
+
* `context/` -> `Section<Context>` (Context)
|
|
40
|
+
3. **Assembly**: Parts are combined into a `Prompt` object.
|
|
41
|
+
4. **Processing**:
|
|
42
|
+
* If **Serialization** is requested: `Serializer` converts `Prompt` to JSON/XML.
|
|
43
|
+
* If **Formatting** is requested: `Formatter` applies model-specific rules (e.g., role assignment) to generate a Chat Request.
|
|
44
|
+
5. **Output**: Result is written to stdout or file.
|
|
45
|
+
|
|
46
|
+
## Design Decisions
|
|
47
|
+
|
|
48
|
+
* **Composition over Concatenation**: By keeping prompt parts separate until the final moment, we allow for dynamic injection, reordering, and model-specific formatting without string manipulation hell.
|
|
49
|
+
* **FileSystem as Source**: We treat the filesystem as a primary way to organize complex prompts. Folders represent Sections, files represent Items. This makes prompts version-controllable and easy to navigate.
|
|
50
|
+
* **Type Safety**: Extensive use of TypeScript generic types (`Section<T>`) ensures that we don't accidentally mix up Context (data) with Instructions (logic).
|
|
51
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Configuration Reference
|
|
2
|
+
|
|
3
|
+
**Purpose**: Detailed guide on configuring `riotprompt`.
|
|
4
|
+
|
|
5
|
+
## Configuration File
|
|
6
|
+
|
|
7
|
+
RiotPrompt uses `cardigantime` for configuration management. It looks for a `riotprompt.yaml` file in the current working directory or parent directories.
|
|
8
|
+
|
|
9
|
+
### Schema
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
# Default model to use if -m flag is not provided
|
|
13
|
+
defaultModel: "gpt-4"
|
|
14
|
+
|
|
15
|
+
# Base directory for prompts (optional, defaults to current dir)
|
|
16
|
+
promptsDir: "."
|
|
17
|
+
|
|
18
|
+
# Default output directory for processing (optional)
|
|
19
|
+
outputDir: "./output"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Model Configuration
|
|
23
|
+
|
|
24
|
+
RiotPrompt has an internal `ModelRegistry` that determines how prompts are formatted for different models.
|
|
25
|
+
|
|
26
|
+
* **Persona Role**:
|
|
27
|
+
* `gpt-4`, `claude` family -> `system` role
|
|
28
|
+
* `o1` (o-series) -> `developer` role
|
|
29
|
+
* **Encoding**: Used for token counting (if enabled).
|
|
30
|
+
|
|
31
|
+
Currently, these are hardcoded in `src/model-config.ts` but the library supports runtime configuration via `configureModel`.
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { configureModel } from '@riotprompt/riotprompt';
|
|
35
|
+
|
|
36
|
+
// Register a custom local model
|
|
37
|
+
configureModel({
|
|
38
|
+
exactMatch: 'my-local-llama',
|
|
39
|
+
personaRole: 'system',
|
|
40
|
+
encoding: 'cl100k_base',
|
|
41
|
+
family: 'llama'
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## CLI Flags
|
|
46
|
+
|
|
47
|
+
CLI flags always override configuration file settings.
|
|
48
|
+
|
|
49
|
+
* `-m, --model <name>`: Overrides `defaultModel`.
|
|
50
|
+
* `-o, --output <path>`: Overrides `outputDir`.
|
|
51
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Development Guide
|
|
2
|
+
|
|
3
|
+
**Purpose**: Instructions for contributing to and developing `riotprompt`.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
1. **Install Dependencies**:
|
|
8
|
+
```bash
|
|
9
|
+
npm install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
2. **Build**:
|
|
13
|
+
```bash
|
|
14
|
+
npm run build
|
|
15
|
+
```
|
|
16
|
+
This builds both the library (`dist/riotprompt.js`) and the CLI (`dist/cli.cjs`).
|
|
17
|
+
|
|
18
|
+
## Testing
|
|
19
|
+
|
|
20
|
+
We use **Vitest** for testing.
|
|
21
|
+
|
|
22
|
+
* **Run Tests**:
|
|
23
|
+
```bash
|
|
24
|
+
npm test
|
|
25
|
+
```
|
|
26
|
+
* **Run with Coverage**:
|
|
27
|
+
```bash
|
|
28
|
+
npm run test:coverage
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Test Structure
|
|
32
|
+
|
|
33
|
+
* `tests/unit/`: Tests for individual modules (Serializer, Writer, etc.).
|
|
34
|
+
* `tests/execution/`: Tests for LLM providers (mocked).
|
|
35
|
+
* `tests/integration/`: End-to-end tests for the CLI.
|
|
36
|
+
|
|
37
|
+
### Mocking
|
|
38
|
+
|
|
39
|
+
Tests that involve LLM APIs or filesystem operations MUST use mocks (`vi.mock`).
|
|
40
|
+
* **Filesystem**: Use `fs/promises` mocks or temporary directories (via `fs.mkdtemp`).
|
|
41
|
+
* **APIs**: Mock the SDKs (`openai`, `@anthropic-ai/sdk`) to avoid making real network calls and incurring costs.
|
|
42
|
+
|
|
43
|
+
## Project Structure
|
|
44
|
+
|
|
45
|
+
* `src/`: Source code.
|
|
46
|
+
* `items/`: Core data structures (Section, Instruction, etc.).
|
|
47
|
+
* `execution/`: LLM provider implementations.
|
|
48
|
+
* `util/`: Helpers (filesystem, text processing).
|
|
49
|
+
* `tests/`: Unit and integration tests.
|
|
50
|
+
* `input/`: Sample prompts for manual testing.
|
|
51
|
+
|
|
52
|
+
## Adding Features
|
|
53
|
+
|
|
54
|
+
1. **Serialization**: If adding a new format, update `src/serializer.ts` and add the option to `src/cli.ts`.
|
|
55
|
+
2. **New Item Types**: If adding a new prompt component (e.g., `Example`), create a new file in `src/items/` and update `Prompt` interface in `src/prompt.ts`.
|
|
56
|
+
3. **CLI Commands**: Update `src/cli.ts` using `commander` syntax.
|
|
57
|
+
4. **New Providers**: Implement the `Provider` interface in `src/execution/` and register it in `ExecutionManager`.
|
|
58
|
+
|
|
59
|
+
## Linting
|
|
60
|
+
|
|
61
|
+
* **Check**: `npm run lint`
|
|
62
|
+
* **Fix**: `npm run lint:fix`
|
package/guide/index.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# AI Agent Guide: RiotPrompt
|
|
2
|
+
|
|
3
|
+
**Role**: You are an AI assistant tasked with understanding, integrating, or extending `riotprompt`, a structured prompt engineering library.
|
|
4
|
+
|
|
5
|
+
**Goal**: Provide a comprehensive understanding of `riotprompt`'s capabilities, architecture, and usage patterns to facilitate correct implementation and extension.
|
|
6
|
+
|
|
7
|
+
## Core Capabilities
|
|
8
|
+
|
|
9
|
+
`riotprompt` is a library and CLI tool designed to treat LLM prompts as structured code objects rather than simple strings. It allows for the modular assembly, validation, and formatting of prompts.
|
|
10
|
+
|
|
11
|
+
* **Structured Prompts**: Prompts are composed of distinct sections: `Persona`, `Instructions`, `Context`, and `Content`.
|
|
12
|
+
* **Modular Assembly**: Sections can be loaded from separate files or directories and combined dynamically.
|
|
13
|
+
* **Model-Agnostic Formatting**: The library separates the *content* of a prompt from its *format*. It can output prompts optimized for different models (e.g., OpenAI, Claude) using adapters.
|
|
14
|
+
* **Serialization**: Prompts can be serialized to JSON or XML for storage or exchange.
|
|
15
|
+
* **CLI Tool**: A command-line interface allows for easy processing of prompt directories into formatted outputs.
|
|
16
|
+
|
|
17
|
+
## Quick Start Context
|
|
18
|
+
|
|
19
|
+
When analyzing or generating code using `riotprompt`, keep these patterns in mind:
|
|
20
|
+
|
|
21
|
+
1. **Define Sections**: Prompts are built from `Section<T>` objects.
|
|
22
|
+
2. **Create Prompt**: Use `createPrompt` to combine sections.
|
|
23
|
+
3. **Format**: Use `Formatter` to convert the structured prompt into a string or chat messages for a specific model.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import * as RiotPrompt from '@riotprompt/riotprompt';
|
|
27
|
+
|
|
28
|
+
// 1. Create Sections
|
|
29
|
+
const persona = RiotPrompt.createSection({ title: 'Persona' })
|
|
30
|
+
.add(RiotPrompt.createInstruction('You are a helpful assistant.'));
|
|
31
|
+
|
|
32
|
+
const instructions = RiotPrompt.createSection({ title: 'Instructions' })
|
|
33
|
+
.add(RiotPrompt.createInstruction('Summarize the following text.'));
|
|
34
|
+
|
|
35
|
+
// 2. Create Prompt
|
|
36
|
+
const prompt = RiotPrompt.createPrompt({
|
|
37
|
+
persona,
|
|
38
|
+
instructions
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// 3. Format
|
|
42
|
+
const formatter = RiotPrompt.Formatter.create();
|
|
43
|
+
// chatRequest will be a structured object suitable for API calls (e.g. OpenAI chat completion)
|
|
44
|
+
const chatRequest = formatter.formatPrompt('gpt-4', prompt);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Documentation Structure
|
|
48
|
+
|
|
49
|
+
This guide directory contains specialized documentation for different aspects of the system:
|
|
50
|
+
|
|
51
|
+
* [Architecture](./architecture.md): Internal design, module structure, and data flow.
|
|
52
|
+
* [Usage Patterns](./usage.md): Common patterns for CLI and library usage.
|
|
53
|
+
* [Configuration](./configuration.md): Deep dive into configuration options.
|
|
54
|
+
* [Development](./development.md): Guide for contributing to `riotprompt`.
|
|
55
|
+
|