prose-writer 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 +21 -0
- package/README.md +1341 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +10 -0
- package/dist/prose-writer.d.ts +290 -0
- package/package.json +105 -0
package/README.md
ADDED
|
@@ -0,0 +1,1341 @@
|
|
|
1
|
+
# Prose Writer
|
|
2
|
+
|
|
3
|
+
A zero-dependency, chainable TypeScript library for building formatted text and markdown strings. Perfect for constructing LLM prompts, generating documentation, or any scenario where you need to programmatically build structured text.
|
|
4
|
+
|
|
5
|
+
## Why Prose Writer?
|
|
6
|
+
|
|
7
|
+
Building prompts for LLMs in code is _painful_. You end up with wild stuff like this.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// The mess we've all written
|
|
11
|
+
const prompt = `You are a ${role}.
|
|
12
|
+
|
|
13
|
+
## Guidelines
|
|
14
|
+
${guidelines.map((g) => `- ${g}`).join('\n')}
|
|
15
|
+
|
|
16
|
+
## Examples
|
|
17
|
+
${examples.map((ex, i) => `### Example ${i + 1}\n${ex}`).join('\n\n')}
|
|
18
|
+
|
|
19
|
+
<context>
|
|
20
|
+
${context}
|
|
21
|
+
</context>`;
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Template literals become unreadable. String concatenation is worse. And when you need conditionals, variables, or reusable pieces? Good luck.
|
|
25
|
+
|
|
26
|
+
**prose-writer** fixes this:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
const prompt = write(`You are a ${role}.`)
|
|
30
|
+
.section('Guidelines', (w) => w.list(...guidelines))
|
|
31
|
+
.section('Examples', (w) => {
|
|
32
|
+
w.each(examples, (ex, w, i) => w.heading(3, `Example ${i + 1}`).write(ex));
|
|
33
|
+
})
|
|
34
|
+
.tag('context', context)
|
|
35
|
+
.toString();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### What makes it special
|
|
39
|
+
|
|
40
|
+
- **Zero dependencies** - Keep your project lean and fast
|
|
41
|
+
- **Chainable API** - Fluent interface that reads like prose, not code
|
|
42
|
+
- **Composable** - Build prompts from reusable pieces with `.append()` and `.clone()`
|
|
43
|
+
- **Logical grouping** - Use `.with(builder)` to group related operations
|
|
44
|
+
- **Conditional logic** - Add sections conditionally with `.when(condition, builder)`
|
|
45
|
+
- **Template variables** - Use `{{placeholders}}` and `.fill()` for dynamic content
|
|
46
|
+
- **LLM-optimized** - Built-in `.tag()` for XML delimiters (Claude loves these), `.json()` and `.yaml()` for structured output instructions
|
|
47
|
+
- **Batch operations** - Iterate with `.each()` instead of awkward `.map().join()` chains
|
|
48
|
+
- **Token awareness** - Estimate prompt size with `.tokens()`
|
|
49
|
+
- **Plain text export** - Strip formatting with `.toPlainText()` when you need raw text
|
|
50
|
+
- **100% TypeScript** - Full type safety out of the box
|
|
51
|
+
|
|
52
|
+
### Real-world example
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { write, bold, code } from 'prose-writer';
|
|
56
|
+
|
|
57
|
+
// Define reusable components
|
|
58
|
+
const codeReviewPersona = write('You are a', bold('senior software engineer.')).write(
|
|
59
|
+
'Review code for bugs, security issues, and best practices.',
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const outputFormat = write('').definitions({
|
|
63
|
+
summary: 'Brief overview of findings',
|
|
64
|
+
issues: 'List of problems found',
|
|
65
|
+
suggestions: 'Recommended improvements',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Build the prompt
|
|
69
|
+
const reviewPrompt = write('')
|
|
70
|
+
.append(codeReviewPersona)
|
|
71
|
+
.section('Guidelines', (w) => {
|
|
72
|
+
w.list(
|
|
73
|
+
'Focus on correctness over style',
|
|
74
|
+
'Flag security vulnerabilities as critical',
|
|
75
|
+
'Suggest modern alternatives to deprecated patterns',
|
|
76
|
+
);
|
|
77
|
+
})
|
|
78
|
+
.when(strictMode, (w) => w.write('Be extremely thorough. Miss nothing.'))
|
|
79
|
+
.section('Output Format', (w) => w.append(outputFormat))
|
|
80
|
+
.tag('code', userCode)
|
|
81
|
+
.fill({ language: 'TypeScript', framework: 'React' });
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Stop fighting with template strings. Start writing prompts that are readable, maintainable, and composable.
|
|
85
|
+
|
|
86
|
+
## Installation
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Using bun
|
|
90
|
+
bun add prose-writer
|
|
91
|
+
|
|
92
|
+
# Using npm
|
|
93
|
+
npm install prose-writer
|
|
94
|
+
|
|
95
|
+
# Using pnpm
|
|
96
|
+
pnpm add prose-writer
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Quick Start
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { write, bold } from 'prose-writer';
|
|
103
|
+
|
|
104
|
+
const prompt = write('You are a', bold('helpful assistant.'))
|
|
105
|
+
.write('Please help the user with their request.')
|
|
106
|
+
.toString();
|
|
107
|
+
|
|
108
|
+
console.log(prompt);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Output:
|
|
112
|
+
|
|
113
|
+
```markdown
|
|
114
|
+
You are a **helpful assistant.**
|
|
115
|
+
Please help the user with their request.
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## API Reference
|
|
119
|
+
|
|
120
|
+
### `write(...content: string[])`
|
|
121
|
+
|
|
122
|
+
Creates a new `ProseWriter` instance. Multiple arguments are joined with a space, and a newline is added at the end. Can be called with zero arguments to add a blank line between other `write()` calls.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { write, bold, code } from 'prose-writer';
|
|
126
|
+
|
|
127
|
+
const text = write('Hello', bold('World')).toString();
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Output:
|
|
131
|
+
|
|
132
|
+
```markdown
|
|
133
|
+
Hello **World**
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
You can also start a chain using `write.with()` if you want to use the builder pattern immediately:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const text = write
|
|
140
|
+
.with((w) => {
|
|
141
|
+
w.write('Hello', w.bold('World'));
|
|
142
|
+
})
|
|
143
|
+
.toString();
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Output:
|
|
147
|
+
|
|
148
|
+
```markdown
|
|
149
|
+
Hello **World**
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const multiLine = write('First line').write('Second line').toString();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Output:
|
|
157
|
+
|
|
158
|
+
```markdown
|
|
159
|
+
First line
|
|
160
|
+
Second line
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Adding a blank line between paragraphs:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const multiParagraph = write('Paragraph 1').write().write('Paragraph 2').toString();
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Output:
|
|
170
|
+
|
|
171
|
+
```markdown
|
|
172
|
+
Paragraph 1
|
|
173
|
+
|
|
174
|
+
Paragraph 2
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### `.write(...content: string[])`
|
|
178
|
+
|
|
179
|
+
Appends content to the prose. Multiple arguments are joined with a space, and a newline is added at the end. Returns `this` for chaining.
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
write('User:').write('Hello', 'Assistant').toString();
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Output:
|
|
186
|
+
|
|
187
|
+
```markdown
|
|
188
|
+
User:
|
|
189
|
+
Hello Assistant
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Inline Utilities
|
|
193
|
+
|
|
194
|
+
The following utilities return formatted strings and can be used within `write()` calls or anywhere else.
|
|
195
|
+
|
|
196
|
+
- `bold(content: string)` - `**content**`
|
|
197
|
+
- `italic(content: string)` - `*content*`
|
|
198
|
+
- `strike(content: string)` - `~~content~~`
|
|
199
|
+
- `code(content: string)` - `` `content` ``
|
|
200
|
+
- `inline(content: string)` - Alias for `code()`
|
|
201
|
+
- `link(text: string, url: string)` - `[text](url)`
|
|
202
|
+
- `image(alt: string, url: string)` - ``
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import { write, bold, italic, code, link } from 'prose-writer';
|
|
206
|
+
|
|
207
|
+
write(
|
|
208
|
+
'Check out',
|
|
209
|
+
link('this repo', 'https://github.com...'),
|
|
210
|
+
'for',
|
|
211
|
+
bold('amazing'),
|
|
212
|
+
italic('results'),
|
|
213
|
+
'and see the',
|
|
214
|
+
code('README'),
|
|
215
|
+
'for details.',
|
|
216
|
+
);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Obviously, you can just write regular Markdown here as well.
|
|
220
|
+
|
|
221
|
+
### `.nextLine()`
|
|
222
|
+
|
|
223
|
+
Prevents the next block element or `write()` call from adding a paragraph break. Useful for placing content on consecutive lines.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
write('Line 1').nextLine().write('Line 2').toString();
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Output:
|
|
230
|
+
|
|
231
|
+
```markdown
|
|
232
|
+
Line 1
|
|
233
|
+
Line 2
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### `.unorderedList(...items: string[] | number[] | boolean[] | ProseWriter[])`
|
|
237
|
+
|
|
238
|
+
### `.list(...items: string[] | number[] | boolean[] | ProseWriter[])`
|
|
239
|
+
|
|
240
|
+
Appends an unordered markdown list. Each item is prefixed with `- `. The list is surrounded by double newlines. Supports nesting by passing another `ProseWriter` instance as an item.
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
write('Features:').unorderedList('Fast', 'Reliable', 'Easy to use').toString();
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Output:
|
|
247
|
+
|
|
248
|
+
```markdown
|
|
249
|
+
Features:
|
|
250
|
+
|
|
251
|
+
- Fast
|
|
252
|
+
- Reliable
|
|
253
|
+
- Easy to use
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### Nested Lists
|
|
257
|
+
|
|
258
|
+
You can create nested lists using a builder function:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
write('Project Plan:').unorderedList((l) => {
|
|
262
|
+
l.item('Setup');
|
|
263
|
+
l.unorderedList((sl) => {
|
|
264
|
+
sl.item('Install dependencies');
|
|
265
|
+
sl.item('Configure tools');
|
|
266
|
+
});
|
|
267
|
+
l.item('Development');
|
|
268
|
+
l.item('Deployment');
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Output:
|
|
273
|
+
|
|
274
|
+
```markdown
|
|
275
|
+
Project Plan:
|
|
276
|
+
|
|
277
|
+
- Setup
|
|
278
|
+
- Install dependencies
|
|
279
|
+
- Configure tools
|
|
280
|
+
- Development
|
|
281
|
+
- Deployment
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### `.orderedList(...items: string[] | number[] | boolean[] | ProseWriter[])`
|
|
285
|
+
|
|
286
|
+
Appends an ordered markdown list. Each item is prefixed with its number. The list is surrounded by double newlines. Supports nesting by passing another `ProseWriter` instance as an item.
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
write('Steps:').orderedList('Install', 'Configure', 'Run').toString();
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Output:
|
|
293
|
+
|
|
294
|
+
```markdown
|
|
295
|
+
Steps:
|
|
296
|
+
|
|
297
|
+
1. Install
|
|
298
|
+
2. Configure
|
|
299
|
+
3. Run
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
#### Nested Ordered Lists
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
write('Recipe:').orderedList((l) => {
|
|
306
|
+
l.item('Prepare ingredients');
|
|
307
|
+
l.orderedList((sl) => {
|
|
308
|
+
sl.item('Chop onions');
|
|
309
|
+
sl.item('Mince garlic');
|
|
310
|
+
});
|
|
311
|
+
l.item('Cook');
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Output:
|
|
316
|
+
|
|
317
|
+
```markdown
|
|
318
|
+
Recipe:
|
|
319
|
+
|
|
320
|
+
1. Prepare ingredients
|
|
321
|
+
1. Chop onions
|
|
322
|
+
1. Mince garlic
|
|
323
|
+
1. Cook
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### `.heading(level: 1 | 2 | 3 | 4 | 5 | 6, ...content: string[])`
|
|
327
|
+
|
|
328
|
+
Appends a markdown heading at the specified level. Multiple arguments are joined with a space. Surrounded by double newlines.
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
write('Intro').heading(1, 'Main', 'Title').write('Content').toString();
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Output:
|
|
335
|
+
|
|
336
|
+
```markdown
|
|
337
|
+
Intro
|
|
338
|
+
|
|
339
|
+
# Main Title
|
|
340
|
+
|
|
341
|
+
Content
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
write('').heading(2, 'Section').toString();
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Output:
|
|
349
|
+
|
|
350
|
+
```markdown
|
|
351
|
+
## Section
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### `.blockquote(...lines: string[])`
|
|
355
|
+
|
|
356
|
+
Appends a markdown blockquote. Multiple lines are separated by empty blockquote lines. Surrounded by double newlines.
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
write('Note:').blockquote('This is important').toString();
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Output:
|
|
363
|
+
|
|
364
|
+
```markdown
|
|
365
|
+
Note:
|
|
366
|
+
|
|
367
|
+
> This is important
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
write('Quote:').blockquote('First line', 'Second line').toString();
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Output:
|
|
375
|
+
|
|
376
|
+
```markdown
|
|
377
|
+
Quote:
|
|
378
|
+
|
|
379
|
+
> First line
|
|
380
|
+
>
|
|
381
|
+
> Second line
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### `.codeblock(language: string, content: string | builder)`
|
|
385
|
+
|
|
386
|
+
Appends a fenced markdown code block with the specified language. Surrounded by double newlines. The second argument can be a string or a builder function.
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
write('Example:').codeblock('typescript', 'const x = 1;').toString();
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Output:
|
|
393
|
+
|
|
394
|
+
````markdown
|
|
395
|
+
Example:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
const x = 1;
|
|
399
|
+
```
|
|
400
|
+
````
|
|
401
|
+
|
|
402
|
+
You can also use a builder to construct complex code blocks:
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
write('Setup:').codeblock('bash', (w) => {
|
|
406
|
+
w.write('npm install');
|
|
407
|
+
w.write('npm run build');
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Output:
|
|
412
|
+
|
|
413
|
+
````markdown
|
|
414
|
+
Setup:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
npm install
|
|
418
|
+
npm run build
|
|
419
|
+
```
|
|
420
|
+
````
|
|
421
|
+
|
|
422
|
+
### `.tasks(builder: (l: ListBuilder) => void)`
|
|
423
|
+
|
|
424
|
+
### `.tasks(...items: (string | [string, boolean])[])`
|
|
425
|
+
|
|
426
|
+
Appends a task list with GitHub-style checkboxes.
|
|
427
|
+
|
|
428
|
+
#### Using a Builder
|
|
429
|
+
|
|
430
|
+
The builder receives a `ListBuilder` with `task(checked, content)`, `todo(content)`, and `done(content)` methods.
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
write('Todo:').tasks((l) => {
|
|
434
|
+
l.done('Initialize repository');
|
|
435
|
+
l.todo('Implement core logic');
|
|
436
|
+
l.task(false, 'Write documentation');
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Output:
|
|
441
|
+
|
|
442
|
+
```markdown
|
|
443
|
+
Todo:
|
|
444
|
+
|
|
445
|
+
- [x] Initialize repository
|
|
446
|
+
- [ ] Implement core logic
|
|
447
|
+
- [ ] Write documentation
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
#### Using Items
|
|
451
|
+
|
|
452
|
+
You can also pass a list of items directly. Use a string for an unchecked task, or a pair `[content, checked]` for explicit control.
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
write('Todo:').tasks(
|
|
456
|
+
'Default unchecked',
|
|
457
|
+
['Explicit checked', true],
|
|
458
|
+
['Explicit unchecked', false],
|
|
459
|
+
);
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Output:
|
|
463
|
+
|
|
464
|
+
```markdown
|
|
465
|
+
Todo:
|
|
466
|
+
|
|
467
|
+
- [ ] Default unchecked
|
|
468
|
+
- [x] Explicit checked
|
|
469
|
+
- [ ] Explicit unchecked
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
You can also start a task list immediately:
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
const text = write
|
|
476
|
+
.tasks((l) => {
|
|
477
|
+
l.task(false, 'First task');
|
|
478
|
+
})
|
|
479
|
+
.toString();
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Output:
|
|
483
|
+
|
|
484
|
+
```markdown
|
|
485
|
+
- [ ] First task
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### `.callout(type: 'NOTE' | 'TIP' | 'IMPORTANT' | 'WARNING' | 'CAUTION', content: string | builder)`
|
|
489
|
+
|
|
490
|
+
Appends a GitHub-style alert (callout). The content can be a string or a builder function.
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
write('Important Info:').callout('NOTE', 'This is a helpful note.');
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
Output:
|
|
497
|
+
|
|
498
|
+
```markdown
|
|
499
|
+
Important Info:
|
|
500
|
+
|
|
501
|
+
> [!NOTE]
|
|
502
|
+
> This is a helpful note.
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
Using a builder for complex callouts:
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
write().callout('WARNING', (w) => {
|
|
509
|
+
w.write('System maintenance scheduled.');
|
|
510
|
+
w.list('Date: Sunday', 'Time: 2:00 AM UTC');
|
|
511
|
+
});
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
Output:
|
|
515
|
+
|
|
516
|
+
```markdown
|
|
517
|
+
> [!WARNING]
|
|
518
|
+
> System maintenance scheduled.
|
|
519
|
+
>
|
|
520
|
+
> - Date: Sunday
|
|
521
|
+
> - Time: 2:00 AM UTC
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
You can also start a callout immediately:
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
const text = write.callout('TIP', 'Use shortcuts to save time.').toString();
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Output:
|
|
531
|
+
|
|
532
|
+
```markdown
|
|
533
|
+
> [!TIP]
|
|
534
|
+
> Use shortcuts to save time.
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### `.separator`
|
|
538
|
+
|
|
539
|
+
A getter that appends a markdown horizontal rule (`---`). Surrounded by double newlines.
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
write('Section 1').separator.write('Section 2').toString();
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
Output:
|
|
546
|
+
|
|
547
|
+
```markdown
|
|
548
|
+
Section 1
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
Section 2
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### `.json(data: unknown)`
|
|
556
|
+
|
|
557
|
+
Appends a JSON code block. If the data is not a string, it will be stringified with formatting.
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
write('Config:').json({ key: 'value', count: 42 }).toString();
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
Output:
|
|
564
|
+
|
|
565
|
+
````markdown
|
|
566
|
+
Config:
|
|
567
|
+
|
|
568
|
+
```json
|
|
569
|
+
{
|
|
570
|
+
"key": "value",
|
|
571
|
+
"count": 42
|
|
572
|
+
}
|
|
573
|
+
```
|
|
574
|
+
````
|
|
575
|
+
|
|
576
|
+
// Also accepts pre-formatted JSON strings
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
write('').json('{"raw": "json"}').toString();
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
Output:
|
|
583
|
+
|
|
584
|
+
````markdown
|
|
585
|
+
```json
|
|
586
|
+
{ "raw": "json" }
|
|
587
|
+
```
|
|
588
|
+
````
|
|
589
|
+
|
|
590
|
+
### `.append(writer: ProseWriter)`
|
|
591
|
+
|
|
592
|
+
Appends the content from another `ProseWriter` instance. Enables composition of prompts from reusable pieces.
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
const persona = write('You are an expert TypeScript developer.');
|
|
596
|
+
const guidelines = write('').list('Be concise', 'Be accurate');
|
|
597
|
+
|
|
598
|
+
const prompt = write('System Prompt')
|
|
599
|
+
.heading(2, 'Role')
|
|
600
|
+
.append(persona)
|
|
601
|
+
.heading(2, 'Guidelines')
|
|
602
|
+
.append(guidelines)
|
|
603
|
+
.toString();
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### `.when(condition: unknown, builder: (writer) => void)`
|
|
607
|
+
|
|
608
|
+
Conditionally executes a builder function. If the condition is truthy, the builder is called with the writer instance.
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
const includeExamples = true;
|
|
612
|
+
const strictMode = false;
|
|
613
|
+
|
|
614
|
+
write('You are a coding assistant.')
|
|
615
|
+
.when(strictMode, (w) => w.write('Be extremely precise.'))
|
|
616
|
+
.when(includeExamples, (w) => {
|
|
617
|
+
w.heading(2, 'Examples').list('Example 1', 'Example 2');
|
|
618
|
+
})
|
|
619
|
+
.toString();
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### `.with(builder: (writer) => void)`
|
|
623
|
+
|
|
624
|
+
Executes a builder function with the writer instance. Useful for logical grouping in chains. The writer passed to the builder has all inline formatters (like `bold`, `italic`, etc.) available as methods, so you don't need to import them.
|
|
625
|
+
|
|
626
|
+
```typescript
|
|
627
|
+
write('Report')
|
|
628
|
+
.with((w) => {
|
|
629
|
+
w.write('Section 1');
|
|
630
|
+
w.write('This is', w.bold('important'));
|
|
631
|
+
})
|
|
632
|
+
.toString();
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### `.tag(name: string, content: string | ProseWriter | builder)`
|
|
636
|
+
|
|
637
|
+
Wraps content in XML-style tags. Useful for Claude and other models that respond well to XML delimiters. The second argument can be a string, another `ProseWriter` instance, or a builder function.
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
write('Analyze this document:')
|
|
641
|
+
.tag('document', 'The content to analyze goes here.')
|
|
642
|
+
.tag('instructions', 'Summarize the key points.')
|
|
643
|
+
.toString();
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
Output:
|
|
647
|
+
|
|
648
|
+
```markdown
|
|
649
|
+
Analyze this document:
|
|
650
|
+
|
|
651
|
+
<document>
|
|
652
|
+
The content to analyze goes here.
|
|
653
|
+
</document>
|
|
654
|
+
|
|
655
|
+
<instructions>
|
|
656
|
+
Summarize the key points.
|
|
657
|
+
</instructions>
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
You can also use a builder for complex nested content:
|
|
661
|
+
|
|
662
|
+
```typescript
|
|
663
|
+
write('').tag('system', (w) => {
|
|
664
|
+
w.write('Be helpful');
|
|
665
|
+
w.write('Be concise');
|
|
666
|
+
});
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
Output:
|
|
670
|
+
|
|
671
|
+
```markdown
|
|
672
|
+
<system>
|
|
673
|
+
Be helpful
|
|
674
|
+
Be concise
|
|
675
|
+
</system>
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### `.code(content: string)`
|
|
679
|
+
|
|
680
|
+
Appends inline code wrapped in backticks. Returns `this` for chaining. Use the `code()` or `inline()` utility for cleaner inline usage.
|
|
681
|
+
|
|
682
|
+
```typescript
|
|
683
|
+
write('Use the').code('calculateTotal').write('function.').toString();
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
Output:
|
|
687
|
+
|
|
688
|
+
```markdown
|
|
689
|
+
Use the
|
|
690
|
+
`calculateTotal`
|
|
691
|
+
function.
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
// Recommended:
|
|
695
|
+
|
|
696
|
+
```typescript
|
|
697
|
+
import { write, code } from 'prose-writer';
|
|
698
|
+
write('Use the', code('calculateTotal'), 'function.').toString();
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
Output:
|
|
702
|
+
|
|
703
|
+
```markdown
|
|
704
|
+
Use the `calculateTotal` function.
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
### `.fill(variables: Record<string, string>)`
|
|
708
|
+
|
|
709
|
+
Replaces template variables in the format `{{variableName}}` with provided values. Returns a new `ProseWriter` with the substitutions applied (does not modify the original).
|
|
710
|
+
|
|
711
|
+
```typescript
|
|
712
|
+
const template = write('Hello, {{name}}! Welcome to {{place}}.');
|
|
713
|
+
const result = template.fill({ name: 'Alice', place: 'Wonderland' }).toString();
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
Output:
|
|
717
|
+
|
|
718
|
+
```markdown
|
|
719
|
+
Hello, Alice! Welcome to Wonderland.
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
### `.section(name: string, builder: (writer) => void, level?: 1-6)`
|
|
723
|
+
|
|
724
|
+
Creates a semantic section with a heading and content built by the builder function. The optional `level` parameter defaults to 2.
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
write('Document')
|
|
728
|
+
.section('Introduction', (w) => {
|
|
729
|
+
w.write('This is the intro paragraph.');
|
|
730
|
+
})
|
|
731
|
+
.section('Features', (w) => {
|
|
732
|
+
w.list('Fast', 'Reliable', 'Easy to use');
|
|
733
|
+
})
|
|
734
|
+
.toString();
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### `.clone()`
|
|
738
|
+
|
|
739
|
+
Creates a copy of the `ProseWriter` with the same content. Useful for creating variations of a base prompt without modifying the original.
|
|
740
|
+
|
|
741
|
+
```typescript
|
|
742
|
+
const base = write('You are an assistant.').heading(2, 'Guidelines');
|
|
743
|
+
|
|
744
|
+
const verbose = base.clone().write('Be very detailed and thorough.');
|
|
745
|
+
const concise = base.clone().write('Be brief and to the point.');
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
### `.table(headers: string[], rows: string[][])`
|
|
749
|
+
|
|
750
|
+
Appends a markdown table with headers and rows. Surrounded by double newlines.
|
|
751
|
+
|
|
752
|
+
```typescript
|
|
753
|
+
write('User Data:')
|
|
754
|
+
.table(
|
|
755
|
+
['Name', 'Role', 'Status'],
|
|
756
|
+
[
|
|
757
|
+
['Alice', 'Admin', 'Active'],
|
|
758
|
+
['Bob', 'User', 'Pending'],
|
|
759
|
+
['Charlie', 'User', 'Active'],
|
|
760
|
+
],
|
|
761
|
+
)
|
|
762
|
+
.toString();
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
Output:
|
|
766
|
+
|
|
767
|
+
```markdown
|
|
768
|
+
User Data:
|
|
769
|
+
|
|
770
|
+
| Name | Role | Status |
|
|
771
|
+
| ------- | ----- | ------- |
|
|
772
|
+
| Alice | Admin | Active |
|
|
773
|
+
| Bob | User | Pending |
|
|
774
|
+
| Charlie | User | Active |
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### `.definitions(obj: Record<string, string>)`
|
|
778
|
+
|
|
779
|
+
Appends a definition list with key-value pairs. Each key is bolded.
|
|
780
|
+
|
|
781
|
+
```typescript
|
|
782
|
+
write('Parameters:')
|
|
783
|
+
.definitions({
|
|
784
|
+
temperature: 'Controls randomness (0-1)',
|
|
785
|
+
maxTokens: 'Maximum response length',
|
|
786
|
+
})
|
|
787
|
+
.toString();
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
Output:
|
|
791
|
+
|
|
792
|
+
```markdown
|
|
793
|
+
Parameters:
|
|
794
|
+
|
|
795
|
+
**temperature**: Controls randomness (0-1)
|
|
796
|
+
**maxTokens**: Maximum response length
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
### `.bold(content: string)`
|
|
800
|
+
|
|
801
|
+
Appends bold text wrapped in double asterisks. Returns `this` for chaining. Use the `bold()` utility for cleaner inline usage.
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
write('This is').bold('important').write('information.').toString();
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
Output:
|
|
808
|
+
|
|
809
|
+
```markdown
|
|
810
|
+
This is
|
|
811
|
+
**important**
|
|
812
|
+
information.
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
// Recommended:
|
|
816
|
+
|
|
817
|
+
```typescript
|
|
818
|
+
import { write, bold } from 'prose-writer';
|
|
819
|
+
write('This is', bold('important'), 'information.').toString();
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
Output:
|
|
823
|
+
|
|
824
|
+
```markdown
|
|
825
|
+
This is **important** information.
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
### `.italic(content: string)`
|
|
829
|
+
|
|
830
|
+
Appends italic text wrapped in single asterisks. Returns `this` for chaining. Use the `italic()` utility for cleaner inline usage.
|
|
831
|
+
|
|
832
|
+
```typescript
|
|
833
|
+
write('Please').italic('note').write('the following.').toString();
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
Output:
|
|
837
|
+
|
|
838
|
+
```markdown
|
|
839
|
+
Please
|
|
840
|
+
_note_
|
|
841
|
+
the following.
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
// Recommended:
|
|
845
|
+
|
|
846
|
+
```typescript
|
|
847
|
+
import { write, italic } from 'prose-writer';
|
|
848
|
+
write('Please', italic('note'), 'the following.').toString();
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
Output:
|
|
852
|
+
|
|
853
|
+
```markdown
|
|
854
|
+
Please _note_ the following.
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
### `.comment(content: string)`
|
|
858
|
+
|
|
859
|
+
Appends an HTML comment. Useful for adding notes to LLM prompts that shouldn't appear in rendered output.
|
|
860
|
+
|
|
861
|
+
```typescript
|
|
862
|
+
write('Prompt').comment('This is a hidden note').write('End');
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
Output:
|
|
866
|
+
|
|
867
|
+
```markdown
|
|
868
|
+
Prompt
|
|
869
|
+
|
|
870
|
+
<!-- This is a hidden note -->
|
|
871
|
+
|
|
872
|
+
End
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
### `.strike(content: string)`
|
|
876
|
+
|
|
877
|
+
Appends strikethrough text. Returns `this` for chaining. Use the `strike()` utility for cleaner inline usage.
|
|
878
|
+
|
|
879
|
+
```typescript
|
|
880
|
+
write('Old').strike('deprecated').write('New').toString();
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
Output:
|
|
884
|
+
|
|
885
|
+
```markdown
|
|
886
|
+
Old
|
|
887
|
+
|
|
888
|
+
~~deprecated~~
|
|
889
|
+
|
|
890
|
+
New
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
// Recommended:
|
|
894
|
+
|
|
895
|
+
```typescript
|
|
896
|
+
import { write, strike } from 'prose-writer';
|
|
897
|
+
write('Price:', strike('$100'), '$80').toString();
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
Output:
|
|
901
|
+
|
|
902
|
+
```markdown
|
|
903
|
+
Price: ~~$100~~ $80
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
### `.image(alt: string, url: string)`
|
|
907
|
+
|
|
908
|
+
Appends a markdown image. Returns `this` for chaining. Use the `image()` utility for cleaner inline usage.
|
|
909
|
+
|
|
910
|
+
```typescript
|
|
911
|
+
write('Photo:').image('A cat', 'https://example.com/cat.jpg').toString();
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
Output:
|
|
915
|
+
|
|
916
|
+
```markdown
|
|
917
|
+
Photo:
|
|
918
|
+
|
|
919
|
+

|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
Appends content without any processing. Useful for injecting pre-formatted content.
|
|
923
|
+
|
|
924
|
+
```typescript
|
|
925
|
+
const preformatted = ' indented\n\ttabbed\n\n\nmultiple newlines';
|
|
926
|
+
write('').raw(preformatted).toString();
|
|
927
|
+
// Preserves all whitespace exactly as provided
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
### `.link(text: string, url: string)`
|
|
931
|
+
|
|
932
|
+
Appends a markdown link. Returns `this` for chaining. Use the `link()` utility for cleaner inline usage.
|
|
933
|
+
|
|
934
|
+
```typescript
|
|
935
|
+
write('See the')
|
|
936
|
+
.link('documentation', 'https://example.com')
|
|
937
|
+
.write('for details.')
|
|
938
|
+
.toString();
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
Output:
|
|
942
|
+
|
|
943
|
+
```markdown
|
|
944
|
+
See the
|
|
945
|
+
[documentation](https://example.com)
|
|
946
|
+
for details.
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
// Recommended:
|
|
950
|
+
|
|
951
|
+
```typescript
|
|
952
|
+
import { write, link } from 'prose-writer';
|
|
953
|
+
write('See the', link('documentation', 'https://example.com'), 'for details.').toString();
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
Output:
|
|
957
|
+
|
|
958
|
+
```markdown
|
|
959
|
+
See the [documentation](https://example.com) for details.
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
### `.yaml(data: unknown)`
|
|
963
|
+
|
|
964
|
+
Appends a YAML code block. If data is not a string, it will be converted to YAML format.
|
|
965
|
+
|
|
966
|
+
```typescript
|
|
967
|
+
write('Configuration:')
|
|
968
|
+
.yaml({
|
|
969
|
+
model: 'gpt-4',
|
|
970
|
+
temperature: 0.7,
|
|
971
|
+
settings: {
|
|
972
|
+
stream: true,
|
|
973
|
+
},
|
|
974
|
+
})
|
|
975
|
+
.toString();
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
Output:
|
|
979
|
+
|
|
980
|
+
````markdown
|
|
981
|
+
Configuration:
|
|
982
|
+
|
|
983
|
+
```yaml
|
|
984
|
+
model: gpt-4
|
|
985
|
+
temperature: 0.7
|
|
986
|
+
settings:
|
|
987
|
+
stream: true
|
|
988
|
+
```
|
|
989
|
+
````
|
|
990
|
+
|
|
991
|
+
### `.delimit(open: string, close: string, content: string | ProseWriter)`
|
|
992
|
+
|
|
993
|
+
Wraps content with custom delimiters. Useful for models that respond to specific delimiter patterns.
|
|
994
|
+
|
|
995
|
+
```typescript
|
|
996
|
+
write('Input:').delimit('###', '###', 'content here').toString();
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
### `.compact()`
|
|
1000
|
+
|
|
1001
|
+
Returns a new ProseWriter with consecutive newlines (3+) collapsed to double newlines.
|
|
1002
|
+
|
|
1003
|
+
```typescript
|
|
1004
|
+
const prose = write('A').raw('\n\n\n\n\n').write('B');
|
|
1005
|
+
prose.compact().toString();
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
Output:
|
|
1009
|
+
|
|
1010
|
+
```markdown
|
|
1011
|
+
A
|
|
1012
|
+
|
|
1013
|
+
B
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
### `.trim()`
|
|
1017
|
+
|
|
1018
|
+
Returns a new ProseWriter with leading and trailing whitespace removed.
|
|
1019
|
+
|
|
1020
|
+
```typescript
|
|
1021
|
+
write(' hello ').trim().toString();
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
Output:
|
|
1025
|
+
|
|
1026
|
+
```markdown
|
|
1027
|
+
hello
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
### `.tokens(counter?: (content: string) => number)`
|
|
1031
|
+
|
|
1032
|
+
Returns an estimated token count. By default, it uses a rough approximation of ~4 characters per token. You can provide an optional counter function for more accurate counting (e.g., using a library like `tiktoken`).
|
|
1033
|
+
|
|
1034
|
+
```typescript
|
|
1035
|
+
const prompt = write('Hello world, this is a test.');
|
|
1036
|
+
console.log(prompt.tokens());
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
Output:
|
|
1040
|
+
|
|
1041
|
+
```markdown
|
|
1042
|
+
8
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
Using a custom counter:
|
|
1046
|
+
|
|
1047
|
+
```typescript
|
|
1048
|
+
import { countTokens } from 'some-token-library';
|
|
1049
|
+
const prompt = write('Hello world');
|
|
1050
|
+
const tokenCount = prompt.tokens((content) => countTokens(content));
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
### `.each<T>(items: T[], builder: (item, writer, index) => void)`
|
|
1054
|
+
|
|
1055
|
+
Iterates over items and applies a builder function for each.
|
|
1056
|
+
|
|
1057
|
+
```typescript
|
|
1058
|
+
const features = ['Fast', 'Reliable', 'Easy'];
|
|
1059
|
+
write('Features:')
|
|
1060
|
+
.each(features, (feature, w, i) => {
|
|
1061
|
+
w.heading(3, `${i + 1}. ${feature}`);
|
|
1062
|
+
})
|
|
1063
|
+
.toString();
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
Output:
|
|
1067
|
+
|
|
1068
|
+
```markdown
|
|
1069
|
+
Features:
|
|
1070
|
+
|
|
1071
|
+
### 1. Fast
|
|
1072
|
+
|
|
1073
|
+
### 2. Reliable
|
|
1074
|
+
|
|
1075
|
+
### 3. Easy
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
### `.toPlainText()`
|
|
1079
|
+
|
|
1080
|
+
Converts the prose to plain text by stripping all markdown formatting.
|
|
1081
|
+
|
|
1082
|
+
```typescript
|
|
1083
|
+
import { write, bold } from 'prose-writer';
|
|
1084
|
+
|
|
1085
|
+
const prose = write('')
|
|
1086
|
+
.heading(1, 'Title')
|
|
1087
|
+
.write('Some', bold('bold'), 'text.')
|
|
1088
|
+
.list('Item 1', 'Item 2');
|
|
1089
|
+
|
|
1090
|
+
prose.toPlainText();
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
### `ProseWriter.empty()`
|
|
1094
|
+
|
|
1095
|
+
Static method that creates an empty ProseWriter instance.
|
|
1096
|
+
|
|
1097
|
+
```typescript
|
|
1098
|
+
const writer = ProseWriter.empty();
|
|
1099
|
+
writer.write('Content').heading(1, 'Title');
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
### `ProseWriter.join(...writers: ProseWriter[])`
|
|
1103
|
+
|
|
1104
|
+
Static method that joins multiple ProseWriter instances into one.
|
|
1105
|
+
|
|
1106
|
+
```typescript
|
|
1107
|
+
const intro = write('Introduction');
|
|
1108
|
+
const body = write('').heading(2, 'Body').list('Point 1', 'Point 2');
|
|
1109
|
+
const conclusion = write('').heading(2, 'Conclusion').write('Summary');
|
|
1110
|
+
|
|
1111
|
+
const document = ProseWriter.join(intro, body, conclusion);
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
### `ProseWriter.fromTemplate(template: string)`
|
|
1115
|
+
|
|
1116
|
+
Static method that creates a ProseWriter from a template string.
|
|
1117
|
+
|
|
1118
|
+
```typescript
|
|
1119
|
+
const prompt = ProseWriter.fromTemplate('Hello {{name}}, your role is {{role}}.').fill({
|
|
1120
|
+
name: 'Alice',
|
|
1121
|
+
role: 'developer',
|
|
1122
|
+
});
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
### `.toString()`
|
|
1126
|
+
|
|
1127
|
+
Converts the accumulated prose to a string.
|
|
1128
|
+
|
|
1129
|
+
```typescript
|
|
1130
|
+
const text = write('Hello', 'World').toString();
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
Output:
|
|
1134
|
+
|
|
1135
|
+
```markdown
|
|
1136
|
+
Hello World
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
### String Coercion
|
|
1140
|
+
|
|
1141
|
+
`ProseWriter` instances can be converted to strings using `String()` or template literals:
|
|
1142
|
+
|
|
1143
|
+
```typescript
|
|
1144
|
+
const prose = write('Hello', 'World');
|
|
1145
|
+
|
|
1146
|
+
String(prose);
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
Output:
|
|
1150
|
+
|
|
1151
|
+
```markdown
|
|
1152
|
+
Hello World
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
```typescript
|
|
1156
|
+
`${prose}`;
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
Output:
|
|
1160
|
+
|
|
1161
|
+
```markdown
|
|
1162
|
+
Hello World
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
## Building Complex Documents
|
|
1166
|
+
|
|
1167
|
+
Chain methods together to build structured documents:
|
|
1168
|
+
|
|
1169
|
+
```typescript
|
|
1170
|
+
import { write } from 'prose-writer';
|
|
1171
|
+
|
|
1172
|
+
const documentation = write('Welcome to MyAPI')
|
|
1173
|
+
.heading(1, 'API Documentation')
|
|
1174
|
+
.write('This document describes how to use MyAPI.')
|
|
1175
|
+
.heading(2, 'Features')
|
|
1176
|
+
.list('RESTful endpoints', 'JSON responses', 'Authentication support')
|
|
1177
|
+
.heading(2, 'Getting Started')
|
|
1178
|
+
.orderedList('Sign up for an API key', 'Install the SDK', 'Make your first request')
|
|
1179
|
+
.heading(2, 'Example Request')
|
|
1180
|
+
.codeblock(
|
|
1181
|
+
'typescript',
|
|
1182
|
+
`import { MyAPI } from 'myapi';
|
|
1183
|
+
|
|
1184
|
+
const api = new MyAPI({ key: 'your-api-key' });
|
|
1185
|
+
const result = await api.getData();`,
|
|
1186
|
+
)
|
|
1187
|
+
.heading(2, 'Response Format')
|
|
1188
|
+
.json({
|
|
1189
|
+
success: true,
|
|
1190
|
+
data: {
|
|
1191
|
+
id: 123,
|
|
1192
|
+
name: 'Example',
|
|
1193
|
+
},
|
|
1194
|
+
})
|
|
1195
|
+
.heading(2, 'Important Notes')
|
|
1196
|
+
.blockquote('Always keep your API key secure.', 'Rate limits apply to all endpoints.')
|
|
1197
|
+
.separator.write('For more information, visit our website.')
|
|
1198
|
+
.toString();
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1201
|
+
## LLM Prompt Building
|
|
1202
|
+
|
|
1203
|
+
`prose-writer` is particularly useful for building LLM prompts with composable, reusable pieces:
|
|
1204
|
+
|
|
1205
|
+
```typescript
|
|
1206
|
+
import { write } from 'prose-writer';
|
|
1207
|
+
|
|
1208
|
+
// Define reusable prompt components
|
|
1209
|
+
const persona = write('You are a helpful coding assistant.').write(
|
|
1210
|
+
'Your role is to help users write clean, maintainable code.',
|
|
1211
|
+
);
|
|
1212
|
+
|
|
1213
|
+
const guidelines = write('').list(
|
|
1214
|
+
'Provide clear explanations',
|
|
1215
|
+
'Include code examples when helpful',
|
|
1216
|
+
'Suggest best practices',
|
|
1217
|
+
'Point out potential issues',
|
|
1218
|
+
);
|
|
1219
|
+
|
|
1220
|
+
// Build the system prompt with composition and conditionals
|
|
1221
|
+
const includeExamples = true;
|
|
1222
|
+
const useStructuredOutput = true;
|
|
1223
|
+
|
|
1224
|
+
const systemPrompt = write('')
|
|
1225
|
+
.append(persona)
|
|
1226
|
+
.heading(2, 'Guidelines')
|
|
1227
|
+
.append(guidelines)
|
|
1228
|
+
.when(includeExamples, (w) => {
|
|
1229
|
+
w.heading(2, 'Example')
|
|
1230
|
+
.write('When asked about the ')
|
|
1231
|
+
.code('map')
|
|
1232
|
+
.write(' function, explain its purpose and show usage.');
|
|
1233
|
+
})
|
|
1234
|
+
.when(useStructuredOutput, (w) => {
|
|
1235
|
+
w.tag('output_format', 'Respond in markdown with code examples.');
|
|
1236
|
+
})
|
|
1237
|
+
.toString();
|
|
1238
|
+
|
|
1239
|
+
// Build a user prompt with XML tags (great for Claude)
|
|
1240
|
+
const userPrompt = write('Please review the following code:')
|
|
1241
|
+
.tag(
|
|
1242
|
+
'code',
|
|
1243
|
+
`function add(a, b) {
|
|
1244
|
+
return a + b;
|
|
1245
|
+
}`,
|
|
1246
|
+
)
|
|
1247
|
+
.tag('question', 'Is this good TypeScript code? What improvements would you suggest?')
|
|
1248
|
+
.toString();
|
|
1249
|
+
```
|
|
1250
|
+
|
|
1251
|
+
## Prompt Templates and Variations
|
|
1252
|
+
|
|
1253
|
+
Use `fill()` for variable interpolation and `clone()` to create prompt variations:
|
|
1254
|
+
|
|
1255
|
+
```typescript
|
|
1256
|
+
import { write } from 'prose-writer';
|
|
1257
|
+
|
|
1258
|
+
// Create a reusable template
|
|
1259
|
+
const promptTemplate = write('You are a {{role}}.')
|
|
1260
|
+
.section('Task', (w) => {
|
|
1261
|
+
w.write('Help the user with {{task}}.');
|
|
1262
|
+
})
|
|
1263
|
+
.section('Guidelines', (w) => {
|
|
1264
|
+
w.list('Be {{style}}', 'Focus on {{focus}}');
|
|
1265
|
+
})
|
|
1266
|
+
.section('Output', (w) => {
|
|
1267
|
+
w.table(
|
|
1268
|
+
['Format', 'When to use'],
|
|
1269
|
+
[
|
|
1270
|
+
['Code blocks', 'For code examples'],
|
|
1271
|
+
['Bullet points', 'For lists of items'],
|
|
1272
|
+
['Tables', 'For structured data'],
|
|
1273
|
+
],
|
|
1274
|
+
);
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
// Create variations for different use cases
|
|
1278
|
+
const codeReviewPrompt = promptTemplate.fill({
|
|
1279
|
+
role: 'senior software engineer',
|
|
1280
|
+
task: 'code review',
|
|
1281
|
+
style: 'thorough and constructive',
|
|
1282
|
+
focus: 'code quality and best practices',
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
const debuggingPrompt = promptTemplate.fill({
|
|
1286
|
+
role: 'debugging expert',
|
|
1287
|
+
task: 'finding and fixing bugs',
|
|
1288
|
+
style: 'systematic and methodical',
|
|
1289
|
+
focus: 'root cause analysis',
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
// Or use clone() for structural variations
|
|
1293
|
+
const basePrompt = write('You are an AI assistant.').section('Core Guidelines', (w) => {
|
|
1294
|
+
w.list('Be helpful', 'Be accurate');
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
const verbosePrompt = basePrompt
|
|
1298
|
+
.clone()
|
|
1299
|
+
.section('Style', (w) => w.write('Provide detailed explanations.'));
|
|
1300
|
+
|
|
1301
|
+
const concisePrompt = basePrompt
|
|
1302
|
+
.clone()
|
|
1303
|
+
.section('Style', (w) => w.write('Be brief and to the point.'));
|
|
1304
|
+
```
|
|
1305
|
+
|
|
1306
|
+
## Using the Class Directly
|
|
1307
|
+
|
|
1308
|
+
You can also use the `ProseWriter` class directly:
|
|
1309
|
+
|
|
1310
|
+
```typescript
|
|
1311
|
+
import { ProseWriter } from 'prose-writer';
|
|
1312
|
+
|
|
1313
|
+
const writer = new ProseWriter('Initial content');
|
|
1314
|
+
writer.write(' more content');
|
|
1315
|
+
writer.heading(1, 'Title');
|
|
1316
|
+
|
|
1317
|
+
console.log(writer.toString());
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
Or create an empty instance:
|
|
1321
|
+
|
|
1322
|
+
```typescript
|
|
1323
|
+
const writer = new ProseWriter();
|
|
1324
|
+
writer.heading(1, 'Document Title');
|
|
1325
|
+
writer.write('Content goes here.');
|
|
1326
|
+
```
|
|
1327
|
+
|
|
1328
|
+
## TypeScript Support
|
|
1329
|
+
|
|
1330
|
+
`prose-writer` is written in TypeScript and provides full type definitions out of the box.
|
|
1331
|
+
|
|
1332
|
+
```typescript
|
|
1333
|
+
import { write, ProseWriter } from 'prose-writer';
|
|
1334
|
+
|
|
1335
|
+
// Type-safe heading levels
|
|
1336
|
+
write('').heading(1, 'Valid'); // OK
|
|
1337
|
+
write('').heading(7, 'Invalid'); // Type error: Argument of type '7' is not assignable
|
|
1338
|
+
|
|
1339
|
+
// ProseWriter type
|
|
1340
|
+
const myWriter: ProseWriter = write('Hello');
|
|
1341
|
+
```
|