mercury-agent 0.5.0 → 0.5.2

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.
Files changed (125) hide show
  1. package/README.md +452 -451
  2. package/container/Dockerfile +127 -127
  3. package/container/Dockerfile.base +109 -109
  4. package/container/Dockerfile.power +17 -17
  5. package/container/agent-package.json +8 -8
  6. package/container/build.sh +54 -54
  7. package/docs/ROADMAP.md +3 -4
  8. package/docs/archive/summarization/2026-07-02-recent-dev-summary.md +71 -0
  9. package/docs/auth/dashboard.md +28 -28
  10. package/docs/auth/overview.md +109 -109
  11. package/docs/auth/whatsapp.md +173 -173
  12. package/docs/authoring-profiles.md +174 -174
  13. package/docs/configuration.md +54 -54
  14. package/docs/container-lifecycle.md +349 -349
  15. package/docs/{bugs/bwrap-privileged-linux-docker.md → debug/major/2026-07-02-bwrap-privileged-linux-docker.md} +21 -1
  16. package/docs/{bugs/e2big-prompt-delivery-fix.md → debug/major/2026-07-02-e2big-prompt-delivery-fix.md} +27 -1
  17. package/docs/{bugs/registry-pull-no-local-fallback.md → debug/major/2026-07-02-registry-pull-no-local-fallback.md} +27 -1
  18. package/docs/debug/summarization/2026-07-02-common-bug-patterns.md +72 -0
  19. package/docs/deployment.md +199 -199
  20. package/docs/extensions.md +375 -375
  21. package/docs/graceful-shutdown.md +62 -62
  22. package/docs/kb-distillation.md +77 -77
  23. package/docs/media/overview.md +140 -140
  24. package/docs/media/whatsapp.md +171 -171
  25. package/docs/memory.md +137 -137
  26. package/docs/permissions.md +217 -217
  27. package/docs/pipeline.md +228 -228
  28. package/docs/prd-chat-memory.md +76 -76
  29. package/docs/prd-config-load.md +82 -82
  30. package/docs/rate-limiting.md +229 -229
  31. package/docs/runbooks/publish-checklist.md +9 -1
  32. package/docs/scheduler.md +288 -288
  33. package/docs/setup-discord.md +100 -100
  34. package/docs/setup-slack.md +119 -119
  35. package/docs/setup-whatsapp.md +94 -94
  36. package/docs/subagents.md +166 -166
  37. package/docs/web-search.md +62 -62
  38. package/examples/extensions/README.md +12 -12
  39. package/examples/extensions/charts/index.ts +13 -13
  40. package/examples/extensions/charts/skill/SKILL.md +98 -98
  41. package/examples/extensions/gws/README.md +52 -52
  42. package/examples/extensions/gws/skill/SKILL.md +57 -57
  43. package/examples/extensions/gws/skill/references/calendar.md +101 -101
  44. package/examples/extensions/gws/skill/references/docs.md +65 -65
  45. package/examples/extensions/gws/skill/references/drive.md +79 -79
  46. package/examples/extensions/gws/skill/references/gmail.md +85 -85
  47. package/examples/extensions/gws/skill/references/sheets.md +60 -60
  48. package/examples/extensions/napkin/skill/SKILL.md +728 -728
  49. package/examples/extensions/pdf/skill/LICENSE.txt +30 -30
  50. package/examples/extensions/pdf/skill/SKILL.md +314 -314
  51. package/examples/extensions/pdf/skill/forms.md +294 -294
  52. package/examples/extensions/pdf/skill/reference.md +611 -611
  53. package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -65
  54. package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -11
  55. package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -33
  56. package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -37
  57. package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -122
  58. package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -115
  59. package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -98
  60. package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -107
  61. package/examples/extensions/permission-guard/index.ts +65 -65
  62. package/examples/extensions/pinchtab/skill/SKILL.md +224 -224
  63. package/examples/extensions/pinchtab/skill/TRUST.md +69 -69
  64. package/examples/extensions/pinchtab/skill/references/api.md +297 -297
  65. package/examples/extensions/pinchtab/skill/references/env.md +45 -45
  66. package/examples/extensions/pinchtab/skill/references/profiles.md +107 -107
  67. package/examples/extensions/tradestation/host/refresh.ts +102 -102
  68. package/examples/extensions/tradestation/index.ts +153 -153
  69. package/examples/extensions/tradestation/skill/SKILL.md +67 -67
  70. package/examples/extensions/voice-synth/index.ts +94 -94
  71. package/examples/extensions/voice-synth/skill/SKILL.md +38 -38
  72. package/examples/extensions/voice-transcribe/requirements.txt +8 -8
  73. package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -179
  74. package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -53
  75. package/examples/extensions/yahoo-mail/cli/package.json +13 -13
  76. package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -78
  77. package/package.json +106 -106
  78. package/resources/agents/explore.md +50 -50
  79. package/resources/agents/worker.md +24 -24
  80. package/resources/connection-env-vars.json +25 -25
  81. package/resources/pi-extensions/subagent/agents.ts +126 -126
  82. package/resources/pi-extensions/subagent/index.ts +964 -964
  83. package/resources/profiles/coding/AGENTS.md +43 -43
  84. package/resources/profiles/coding/mercury-profile.yaml +15 -15
  85. package/resources/profiles/general/AGENTS.md +31 -31
  86. package/resources/profiles/general/mercury-profile.yaml +15 -15
  87. package/resources/profiles/research/AGENTS.md +40 -40
  88. package/resources/profiles/research/mercury-profile.yaml +15 -15
  89. package/resources/skills/config/SKILL.md +25 -25
  90. package/resources/skills/context/SKILL.md +33 -33
  91. package/resources/skills/conversation-recap/SKILL.md +19 -19
  92. package/resources/skills/mutes/SKILL.md +31 -31
  93. package/resources/skills/permissions/SKILL.md +19 -19
  94. package/resources/skills/preferences/SKILL.md +31 -31
  95. package/resources/skills/recall/SKILL.md +24 -24
  96. package/resources/skills/roles/SKILL.md +18 -18
  97. package/resources/skills/spaces/SKILL.md +18 -18
  98. package/resources/skills/tasks/SKILL.md +45 -45
  99. package/resources/templates/AGENTS.md +157 -157
  100. package/resources/templates/env.template +38 -38
  101. package/resources/templates/mercury.example.yaml +99 -99
  102. package/src/agent/container-entry.ts +1 -1
  103. package/src/agent/container-runner.ts +1345 -1346
  104. package/src/cli/mercury.ts +39 -7
  105. package/src/cli/mrctl.ts +636 -636
  106. package/src/config-file.ts +540 -540
  107. package/src/config.ts +339 -339
  108. package/src/core/api.ts +125 -125
  109. package/src/core/caller-token.ts +101 -101
  110. package/src/core/permissions.ts +228 -228
  111. package/src/core/profiles.ts +271 -271
  112. package/src/core/routes/capability.ts +70 -70
  113. package/src/core/routes/index.ts +15 -15
  114. package/src/core/runtime.ts +1530 -1530
  115. package/src/dashboard/index.html +729 -729
  116. package/src/extensions/api.ts +273 -273
  117. package/src/extensions/loader.ts +286 -286
  118. package/src/extensions/types.ts +517 -517
  119. package/src/main.ts +605 -605
  120. package/docs/pending-updates/applicative-profiles.md +0 -15
  121. package/docs/pending-updates/caller-bound-capability-token.md +0 -15
  122. package/docs/pending-updates/dm-auto-space.md +0 -15
  123. /package/docs/archive/{2026-07-01-applicative-profiles.md → applicative-profiles/2026-07-01-applicative-profiles.md} +0 -0
  124. /package/docs/archive/{2026-07-01-caller-bound-capability-token.md → applicative-profiles/2026-07-01-caller-bound-capability-token.md} +0 -0
  125. /package/docs/archive/{2026-07-01-dm-auto-space.md → applicative-profiles/2026-07-01-dm-auto-space.md} +0 -0
@@ -1,314 +1,314 @@
1
- ---
2
- name: pdf
3
- description: Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill.
4
- license: Proprietary. LICENSE.txt has complete terms
5
- ---
6
-
7
- # PDF Processing Guide
8
-
9
- ## Overview
10
-
11
- This guide covers essential PDF processing operations using Python libraries and command-line tools. For advanced features, JavaScript libraries, and detailed examples, see REFERENCE.md. If you need to fill out a PDF form, read FORMS.md and follow its instructions.
12
-
13
- ## Quick Start
14
-
15
- ```python
16
- from pypdf import PdfReader, PdfWriter
17
-
18
- # Read a PDF
19
- reader = PdfReader("document.pdf")
20
- print(f"Pages: {len(reader.pages)}")
21
-
22
- # Extract text
23
- text = ""
24
- for page in reader.pages:
25
- text += page.extract_text()
26
- ```
27
-
28
- ## Python Libraries
29
-
30
- ### pypdf - Basic Operations
31
-
32
- #### Merge PDFs
33
- ```python
34
- from pypdf import PdfWriter, PdfReader
35
-
36
- writer = PdfWriter()
37
- for pdf_file in ["doc1.pdf", "doc2.pdf", "doc3.pdf"]:
38
- reader = PdfReader(pdf_file)
39
- for page in reader.pages:
40
- writer.add_page(page)
41
-
42
- with open("merged.pdf", "wb") as output:
43
- writer.write(output)
44
- ```
45
-
46
- #### Split PDF
47
- ```python
48
- reader = PdfReader("input.pdf")
49
- for i, page in enumerate(reader.pages):
50
- writer = PdfWriter()
51
- writer.add_page(page)
52
- with open(f"page_{i+1}.pdf", "wb") as output:
53
- writer.write(output)
54
- ```
55
-
56
- #### Extract Metadata
57
- ```python
58
- reader = PdfReader("document.pdf")
59
- meta = reader.metadata
60
- print(f"Title: {meta.title}")
61
- print(f"Author: {meta.author}")
62
- print(f"Subject: {meta.subject}")
63
- print(f"Creator: {meta.creator}")
64
- ```
65
-
66
- #### Rotate Pages
67
- ```python
68
- reader = PdfReader("input.pdf")
69
- writer = PdfWriter()
70
-
71
- page = reader.pages[0]
72
- page.rotate(90) # Rotate 90 degrees clockwise
73
- writer.add_page(page)
74
-
75
- with open("rotated.pdf", "wb") as output:
76
- writer.write(output)
77
- ```
78
-
79
- ### pdfplumber - Text and Table Extraction
80
-
81
- #### Extract Text with Layout
82
- ```python
83
- import pdfplumber
84
-
85
- with pdfplumber.open("document.pdf") as pdf:
86
- for page in pdf.pages:
87
- text = page.extract_text()
88
- print(text)
89
- ```
90
-
91
- #### Extract Tables
92
- ```python
93
- with pdfplumber.open("document.pdf") as pdf:
94
- for i, page in enumerate(pdf.pages):
95
- tables = page.extract_tables()
96
- for j, table in enumerate(tables):
97
- print(f"Table {j+1} on page {i+1}:")
98
- for row in table:
99
- print(row)
100
- ```
101
-
102
- #### Advanced Table Extraction
103
- ```python
104
- import pandas as pd
105
-
106
- with pdfplumber.open("document.pdf") as pdf:
107
- all_tables = []
108
- for page in pdf.pages:
109
- tables = page.extract_tables()
110
- for table in tables:
111
- if table: # Check if table is not empty
112
- df = pd.DataFrame(table[1:], columns=table[0])
113
- all_tables.append(df)
114
-
115
- # Combine all tables
116
- if all_tables:
117
- combined_df = pd.concat(all_tables, ignore_index=True)
118
- combined_df.to_excel("extracted_tables.xlsx", index=False)
119
- ```
120
-
121
- ### reportlab - Create PDFs
122
-
123
- #### Basic PDF Creation
124
- ```python
125
- from reportlab.lib.pagesizes import letter
126
- from reportlab.pdfgen import canvas
127
-
128
- c = canvas.Canvas("hello.pdf", pagesize=letter)
129
- width, height = letter
130
-
131
- # Add text
132
- c.drawString(100, height - 100, "Hello World!")
133
- c.drawString(100, height - 120, "This is a PDF created with reportlab")
134
-
135
- # Add a line
136
- c.line(100, height - 140, 400, height - 140)
137
-
138
- # Save
139
- c.save()
140
- ```
141
-
142
- #### Create PDF with Multiple Pages
143
- ```python
144
- from reportlab.lib.pagesizes import letter
145
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
146
- from reportlab.lib.styles import getSampleStyleSheet
147
-
148
- doc = SimpleDocTemplate("report.pdf", pagesize=letter)
149
- styles = getSampleStyleSheet()
150
- story = []
151
-
152
- # Add content
153
- title = Paragraph("Report Title", styles['Title'])
154
- story.append(title)
155
- story.append(Spacer(1, 12))
156
-
157
- body = Paragraph("This is the body of the report. " * 20, styles['Normal'])
158
- story.append(body)
159
- story.append(PageBreak())
160
-
161
- # Page 2
162
- story.append(Paragraph("Page 2", styles['Heading1']))
163
- story.append(Paragraph("Content for page 2", styles['Normal']))
164
-
165
- # Build PDF
166
- doc.build(story)
167
- ```
168
-
169
- #### Subscripts and Superscripts
170
-
171
- **IMPORTANT**: Never use Unicode subscript/superscript characters (₀₁₂₃₄₅₆₇₈₉, ⁰¹²³⁴⁵⁶⁷⁸⁹) in ReportLab PDFs. The built-in fonts do not include these glyphs, causing them to render as solid black boxes.
172
-
173
- Instead, use ReportLab's XML markup tags in Paragraph objects:
174
- ```python
175
- from reportlab.platypus import Paragraph
176
- from reportlab.lib.styles import getSampleStyleSheet
177
-
178
- styles = getSampleStyleSheet()
179
-
180
- # Subscripts: use <sub> tag
181
- chemical = Paragraph("H<sub>2</sub>O", styles['Normal'])
182
-
183
- # Superscripts: use <super> tag
184
- squared = Paragraph("x<super>2</super> + y<super>2</super>", styles['Normal'])
185
- ```
186
-
187
- For canvas-drawn text (not Paragraph objects), manually adjust font the size and position rather than using Unicode subscripts/superscripts.
188
-
189
- ## Command-Line Tools
190
-
191
- ### pdftotext (poppler-utils)
192
- ```bash
193
- # Extract text
194
- pdftotext input.pdf output.txt
195
-
196
- # Extract text preserving layout
197
- pdftotext -layout input.pdf output.txt
198
-
199
- # Extract specific pages
200
- pdftotext -f 1 -l 5 input.pdf output.txt # Pages 1-5
201
- ```
202
-
203
- ### qpdf
204
- ```bash
205
- # Merge PDFs
206
- qpdf --empty --pages file1.pdf file2.pdf -- merged.pdf
207
-
208
- # Split pages
209
- qpdf input.pdf --pages . 1-5 -- pages1-5.pdf
210
- qpdf input.pdf --pages . 6-10 -- pages6-10.pdf
211
-
212
- # Rotate pages
213
- qpdf input.pdf output.pdf --rotate=+90:1 # Rotate page 1 by 90 degrees
214
-
215
- # Remove password
216
- qpdf --password=mypassword --decrypt encrypted.pdf decrypted.pdf
217
- ```
218
-
219
- ### pdftk (if available)
220
- ```bash
221
- # Merge
222
- pdftk file1.pdf file2.pdf cat output merged.pdf
223
-
224
- # Split
225
- pdftk input.pdf burst
226
-
227
- # Rotate
228
- pdftk input.pdf rotate 1east output rotated.pdf
229
- ```
230
-
231
- ## Common Tasks
232
-
233
- ### Extract Text from Scanned PDFs
234
- ```python
235
- # Requires: pip install pytesseract pdf2image
236
- import pytesseract
237
- from pdf2image import convert_from_path
238
-
239
- # Convert PDF to images
240
- images = convert_from_path('scanned.pdf')
241
-
242
- # OCR each page
243
- text = ""
244
- for i, image in enumerate(images):
245
- text += f"Page {i+1}:\n"
246
- text += pytesseract.image_to_string(image)
247
- text += "\n\n"
248
-
249
- print(text)
250
- ```
251
-
252
- ### Add Watermark
253
- ```python
254
- from pypdf import PdfReader, PdfWriter
255
-
256
- # Create watermark (or load existing)
257
- watermark = PdfReader("watermark.pdf").pages[0]
258
-
259
- # Apply to all pages
260
- reader = PdfReader("document.pdf")
261
- writer = PdfWriter()
262
-
263
- for page in reader.pages:
264
- page.merge_page(watermark)
265
- writer.add_page(page)
266
-
267
- with open("watermarked.pdf", "wb") as output:
268
- writer.write(output)
269
- ```
270
-
271
- ### Extract Images
272
- ```bash
273
- # Using pdfimages (poppler-utils)
274
- pdfimages -j input.pdf output_prefix
275
-
276
- # This extracts all images as output_prefix-000.jpg, output_prefix-001.jpg, etc.
277
- ```
278
-
279
- ### Password Protection
280
- ```python
281
- from pypdf import PdfReader, PdfWriter
282
-
283
- reader = PdfReader("input.pdf")
284
- writer = PdfWriter()
285
-
286
- for page in reader.pages:
287
- writer.add_page(page)
288
-
289
- # Add password
290
- writer.encrypt("userpassword", "ownerpassword")
291
-
292
- with open("encrypted.pdf", "wb") as output:
293
- writer.write(output)
294
- ```
295
-
296
- ## Quick Reference
297
-
298
- | Task | Best Tool | Command/Code |
299
- |------|-----------|--------------|
300
- | Merge PDFs | pypdf | `writer.add_page(page)` |
301
- | Split PDFs | pypdf | One page per file |
302
- | Extract text | pdfplumber | `page.extract_text()` |
303
- | Extract tables | pdfplumber | `page.extract_tables()` |
304
- | Create PDFs | reportlab | Canvas or Platypus |
305
- | Command line merge | qpdf | `qpdf --empty --pages ...` |
306
- | OCR scanned PDFs | pytesseract | Convert to image first |
307
- | Fill PDF forms | pdf-lib or pypdf (see FORMS.md) | See FORMS.md |
308
-
309
- ## Next Steps
310
-
311
- - For advanced pypdfium2 usage, see REFERENCE.md
312
- - For JavaScript libraries (pdf-lib), see REFERENCE.md
313
- - If you need to fill out a PDF form, follow the instructions in FORMS.md
314
- - For troubleshooting guides, see REFERENCE.md
1
+ ---
2
+ name: pdf
3
+ description: Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill.
4
+ license: Proprietary. LICENSE.txt has complete terms
5
+ ---
6
+
7
+ # PDF Processing Guide
8
+
9
+ ## Overview
10
+
11
+ This guide covers essential PDF processing operations using Python libraries and command-line tools. For advanced features, JavaScript libraries, and detailed examples, see REFERENCE.md. If you need to fill out a PDF form, read FORMS.md and follow its instructions.
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from pypdf import PdfReader, PdfWriter
17
+
18
+ # Read a PDF
19
+ reader = PdfReader("document.pdf")
20
+ print(f"Pages: {len(reader.pages)}")
21
+
22
+ # Extract text
23
+ text = ""
24
+ for page in reader.pages:
25
+ text += page.extract_text()
26
+ ```
27
+
28
+ ## Python Libraries
29
+
30
+ ### pypdf - Basic Operations
31
+
32
+ #### Merge PDFs
33
+ ```python
34
+ from pypdf import PdfWriter, PdfReader
35
+
36
+ writer = PdfWriter()
37
+ for pdf_file in ["doc1.pdf", "doc2.pdf", "doc3.pdf"]:
38
+ reader = PdfReader(pdf_file)
39
+ for page in reader.pages:
40
+ writer.add_page(page)
41
+
42
+ with open("merged.pdf", "wb") as output:
43
+ writer.write(output)
44
+ ```
45
+
46
+ #### Split PDF
47
+ ```python
48
+ reader = PdfReader("input.pdf")
49
+ for i, page in enumerate(reader.pages):
50
+ writer = PdfWriter()
51
+ writer.add_page(page)
52
+ with open(f"page_{i+1}.pdf", "wb") as output:
53
+ writer.write(output)
54
+ ```
55
+
56
+ #### Extract Metadata
57
+ ```python
58
+ reader = PdfReader("document.pdf")
59
+ meta = reader.metadata
60
+ print(f"Title: {meta.title}")
61
+ print(f"Author: {meta.author}")
62
+ print(f"Subject: {meta.subject}")
63
+ print(f"Creator: {meta.creator}")
64
+ ```
65
+
66
+ #### Rotate Pages
67
+ ```python
68
+ reader = PdfReader("input.pdf")
69
+ writer = PdfWriter()
70
+
71
+ page = reader.pages[0]
72
+ page.rotate(90) # Rotate 90 degrees clockwise
73
+ writer.add_page(page)
74
+
75
+ with open("rotated.pdf", "wb") as output:
76
+ writer.write(output)
77
+ ```
78
+
79
+ ### pdfplumber - Text and Table Extraction
80
+
81
+ #### Extract Text with Layout
82
+ ```python
83
+ import pdfplumber
84
+
85
+ with pdfplumber.open("document.pdf") as pdf:
86
+ for page in pdf.pages:
87
+ text = page.extract_text()
88
+ print(text)
89
+ ```
90
+
91
+ #### Extract Tables
92
+ ```python
93
+ with pdfplumber.open("document.pdf") as pdf:
94
+ for i, page in enumerate(pdf.pages):
95
+ tables = page.extract_tables()
96
+ for j, table in enumerate(tables):
97
+ print(f"Table {j+1} on page {i+1}:")
98
+ for row in table:
99
+ print(row)
100
+ ```
101
+
102
+ #### Advanced Table Extraction
103
+ ```python
104
+ import pandas as pd
105
+
106
+ with pdfplumber.open("document.pdf") as pdf:
107
+ all_tables = []
108
+ for page in pdf.pages:
109
+ tables = page.extract_tables()
110
+ for table in tables:
111
+ if table: # Check if table is not empty
112
+ df = pd.DataFrame(table[1:], columns=table[0])
113
+ all_tables.append(df)
114
+
115
+ # Combine all tables
116
+ if all_tables:
117
+ combined_df = pd.concat(all_tables, ignore_index=True)
118
+ combined_df.to_excel("extracted_tables.xlsx", index=False)
119
+ ```
120
+
121
+ ### reportlab - Create PDFs
122
+
123
+ #### Basic PDF Creation
124
+ ```python
125
+ from reportlab.lib.pagesizes import letter
126
+ from reportlab.pdfgen import canvas
127
+
128
+ c = canvas.Canvas("hello.pdf", pagesize=letter)
129
+ width, height = letter
130
+
131
+ # Add text
132
+ c.drawString(100, height - 100, "Hello World!")
133
+ c.drawString(100, height - 120, "This is a PDF created with reportlab")
134
+
135
+ # Add a line
136
+ c.line(100, height - 140, 400, height - 140)
137
+
138
+ # Save
139
+ c.save()
140
+ ```
141
+
142
+ #### Create PDF with Multiple Pages
143
+ ```python
144
+ from reportlab.lib.pagesizes import letter
145
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
146
+ from reportlab.lib.styles import getSampleStyleSheet
147
+
148
+ doc = SimpleDocTemplate("report.pdf", pagesize=letter)
149
+ styles = getSampleStyleSheet()
150
+ story = []
151
+
152
+ # Add content
153
+ title = Paragraph("Report Title", styles['Title'])
154
+ story.append(title)
155
+ story.append(Spacer(1, 12))
156
+
157
+ body = Paragraph("This is the body of the report. " * 20, styles['Normal'])
158
+ story.append(body)
159
+ story.append(PageBreak())
160
+
161
+ # Page 2
162
+ story.append(Paragraph("Page 2", styles['Heading1']))
163
+ story.append(Paragraph("Content for page 2", styles['Normal']))
164
+
165
+ # Build PDF
166
+ doc.build(story)
167
+ ```
168
+
169
+ #### Subscripts and Superscripts
170
+
171
+ **IMPORTANT**: Never use Unicode subscript/superscript characters (₀₁₂₃₄₅₆₇₈₉, ⁰¹²³⁴⁵⁶⁷⁸⁹) in ReportLab PDFs. The built-in fonts do not include these glyphs, causing them to render as solid black boxes.
172
+
173
+ Instead, use ReportLab's XML markup tags in Paragraph objects:
174
+ ```python
175
+ from reportlab.platypus import Paragraph
176
+ from reportlab.lib.styles import getSampleStyleSheet
177
+
178
+ styles = getSampleStyleSheet()
179
+
180
+ # Subscripts: use <sub> tag
181
+ chemical = Paragraph("H<sub>2</sub>O", styles['Normal'])
182
+
183
+ # Superscripts: use <super> tag
184
+ squared = Paragraph("x<super>2</super> + y<super>2</super>", styles['Normal'])
185
+ ```
186
+
187
+ For canvas-drawn text (not Paragraph objects), manually adjust font the size and position rather than using Unicode subscripts/superscripts.
188
+
189
+ ## Command-Line Tools
190
+
191
+ ### pdftotext (poppler-utils)
192
+ ```bash
193
+ # Extract text
194
+ pdftotext input.pdf output.txt
195
+
196
+ # Extract text preserving layout
197
+ pdftotext -layout input.pdf output.txt
198
+
199
+ # Extract specific pages
200
+ pdftotext -f 1 -l 5 input.pdf output.txt # Pages 1-5
201
+ ```
202
+
203
+ ### qpdf
204
+ ```bash
205
+ # Merge PDFs
206
+ qpdf --empty --pages file1.pdf file2.pdf -- merged.pdf
207
+
208
+ # Split pages
209
+ qpdf input.pdf --pages . 1-5 -- pages1-5.pdf
210
+ qpdf input.pdf --pages . 6-10 -- pages6-10.pdf
211
+
212
+ # Rotate pages
213
+ qpdf input.pdf output.pdf --rotate=+90:1 # Rotate page 1 by 90 degrees
214
+
215
+ # Remove password
216
+ qpdf --password=mypassword --decrypt encrypted.pdf decrypted.pdf
217
+ ```
218
+
219
+ ### pdftk (if available)
220
+ ```bash
221
+ # Merge
222
+ pdftk file1.pdf file2.pdf cat output merged.pdf
223
+
224
+ # Split
225
+ pdftk input.pdf burst
226
+
227
+ # Rotate
228
+ pdftk input.pdf rotate 1east output rotated.pdf
229
+ ```
230
+
231
+ ## Common Tasks
232
+
233
+ ### Extract Text from Scanned PDFs
234
+ ```python
235
+ # Requires: pip install pytesseract pdf2image
236
+ import pytesseract
237
+ from pdf2image import convert_from_path
238
+
239
+ # Convert PDF to images
240
+ images = convert_from_path('scanned.pdf')
241
+
242
+ # OCR each page
243
+ text = ""
244
+ for i, image in enumerate(images):
245
+ text += f"Page {i+1}:\n"
246
+ text += pytesseract.image_to_string(image)
247
+ text += "\n\n"
248
+
249
+ print(text)
250
+ ```
251
+
252
+ ### Add Watermark
253
+ ```python
254
+ from pypdf import PdfReader, PdfWriter
255
+
256
+ # Create watermark (or load existing)
257
+ watermark = PdfReader("watermark.pdf").pages[0]
258
+
259
+ # Apply to all pages
260
+ reader = PdfReader("document.pdf")
261
+ writer = PdfWriter()
262
+
263
+ for page in reader.pages:
264
+ page.merge_page(watermark)
265
+ writer.add_page(page)
266
+
267
+ with open("watermarked.pdf", "wb") as output:
268
+ writer.write(output)
269
+ ```
270
+
271
+ ### Extract Images
272
+ ```bash
273
+ # Using pdfimages (poppler-utils)
274
+ pdfimages -j input.pdf output_prefix
275
+
276
+ # This extracts all images as output_prefix-000.jpg, output_prefix-001.jpg, etc.
277
+ ```
278
+
279
+ ### Password Protection
280
+ ```python
281
+ from pypdf import PdfReader, PdfWriter
282
+
283
+ reader = PdfReader("input.pdf")
284
+ writer = PdfWriter()
285
+
286
+ for page in reader.pages:
287
+ writer.add_page(page)
288
+
289
+ # Add password
290
+ writer.encrypt("userpassword", "ownerpassword")
291
+
292
+ with open("encrypted.pdf", "wb") as output:
293
+ writer.write(output)
294
+ ```
295
+
296
+ ## Quick Reference
297
+
298
+ | Task | Best Tool | Command/Code |
299
+ |------|-----------|--------------|
300
+ | Merge PDFs | pypdf | `writer.add_page(page)` |
301
+ | Split PDFs | pypdf | One page per file |
302
+ | Extract text | pdfplumber | `page.extract_text()` |
303
+ | Extract tables | pdfplumber | `page.extract_tables()` |
304
+ | Create PDFs | reportlab | Canvas or Platypus |
305
+ | Command line merge | qpdf | `qpdf --empty --pages ...` |
306
+ | OCR scanned PDFs | pytesseract | Convert to image first |
307
+ | Fill PDF forms | pdf-lib or pypdf (see FORMS.md) | See FORMS.md |
308
+
309
+ ## Next Steps
310
+
311
+ - For advanced pypdfium2 usage, see REFERENCE.md
312
+ - For JavaScript libraries (pdf-lib), see REFERENCE.md
313
+ - If you need to fill out a PDF form, follow the instructions in FORMS.md
314
+ - For troubleshooting guides, see REFERENCE.md