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/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)` - `![alt](url)`
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
+ ![A cat](https://example.com/cat.jpg)
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
+ ```