@kolbo/kolbo-code-linux-arm64-musl 1.1.74 → 2.0.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.
Files changed (239) hide show
  1. package/bin/kolbo +0 -0
  2. package/package.json +1 -1
  3. package/skills/brainstorming/SKILL.md +164 -0
  4. package/skills/brainstorming/scripts/frame-template.html +214 -0
  5. package/skills/brainstorming/scripts/helper.js +88 -0
  6. package/skills/brainstorming/scripts/server.cjs +354 -0
  7. package/skills/brainstorming/scripts/start-server.sh +148 -0
  8. package/skills/brainstorming/scripts/stop-server.sh +56 -0
  9. package/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  10. package/skills/brainstorming/visual-companion.md +287 -0
  11. package/skills/dispatching-parallel-agents/SKILL.md +182 -0
  12. package/skills/docx/.skillfish.json +10 -0
  13. package/skills/docx/SKILL.md +196 -0
  14. package/skills/docx/docx-js.md +350 -0
  15. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  16. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  17. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  18. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  19. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  20. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  21. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  22. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  23. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  24. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  25. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  26. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  27. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  28. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  29. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  30. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  31. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  32. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  33. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  34. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  35. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  36. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  37. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  38. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  39. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  40. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  41. package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  42. package/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  43. package/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  44. package/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  45. package/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  46. package/skills/docx/ooxml/schemas/mce/mc.xsd +75 -0
  47. package/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  48. package/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  49. package/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  50. package/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  51. package/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  52. package/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  53. package/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  54. package/skills/docx/ooxml/scripts/pack.py +159 -0
  55. package/skills/docx/ooxml/scripts/unpack.py +29 -0
  56. package/skills/docx/ooxml/scripts/validate.py +69 -0
  57. package/skills/docx/ooxml/scripts/validation/__init__.py +15 -0
  58. package/skills/docx/ooxml/scripts/validation/base.py +951 -0
  59. package/skills/docx/ooxml/scripts/validation/docx.py +274 -0
  60. package/skills/docx/ooxml/scripts/validation/pptx.py +315 -0
  61. package/skills/docx/ooxml/scripts/validation/redlining.py +279 -0
  62. package/skills/docx/ooxml.md +599 -0
  63. package/skills/docx/scripts/__init__.py +1 -0
  64. package/skills/docx/scripts/document.py +1272 -0
  65. package/skills/docx/scripts/templates/comments.xml +3 -0
  66. package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  67. package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  68. package/skills/docx/scripts/templates/commentsIds.xml +3 -0
  69. package/skills/docx/scripts/templates/people.xml +3 -0
  70. package/skills/docx/scripts/utilities.py +374 -0
  71. package/skills/executing-plans/SKILL.md +70 -0
  72. package/skills/finishing-a-development-branch/SKILL.md +200 -0
  73. package/skills/fullstack-app/SKILL.md +621 -0
  74. package/skills/kolbo/SKILL.md +19 -263
  75. package/skills/ollama-vision/SKILL.md +105 -0
  76. package/skills/pdf/.skillfish.json +10 -0
  77. package/skills/pdf/FORMS.md +205 -0
  78. package/skills/pdf/REFERENCE.md +612 -0
  79. package/skills/pdf/SKILL.md +293 -0
  80. package/skills/pdf/scripts/check_bounding_boxes.py +70 -0
  81. package/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
  82. package/skills/pdf/scripts/check_fillable_fields.py +12 -0
  83. package/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
  84. package/skills/pdf/scripts/create_validation_image.py +41 -0
  85. package/skills/pdf/scripts/extract_form_field_info.py +152 -0
  86. package/skills/pdf/scripts/fill_fillable_fields.py +114 -0
  87. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
  88. package/skills/photo-studio/SKILL.md +122 -0
  89. package/skills/pptx/.skillfish.json +10 -0
  90. package/skills/pptx/SKILL.md +483 -0
  91. package/skills/pptx/html2pptx.md +626 -0
  92. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  93. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  94. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  95. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  96. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  97. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  98. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  99. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  100. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  101. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  102. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  103. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  104. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  105. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  106. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  107. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  108. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  109. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  110. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  111. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  112. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  113. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  114. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  115. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  116. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  117. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  118. package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  119. package/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  120. package/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  121. package/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  122. package/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  123. package/skills/pptx/ooxml/schemas/mce/mc.xsd +75 -0
  124. package/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  125. package/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  126. package/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  127. package/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  128. package/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  129. package/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  130. package/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  131. package/skills/pptx/ooxml/scripts/pack.py +159 -0
  132. package/skills/pptx/ooxml/scripts/unpack.py +29 -0
  133. package/skills/pptx/ooxml/scripts/validate.py +69 -0
  134. package/skills/pptx/ooxml/scripts/validation/__init__.py +15 -0
  135. package/skills/pptx/ooxml/scripts/validation/base.py +951 -0
  136. package/skills/pptx/ooxml/scripts/validation/docx.py +274 -0
  137. package/skills/pptx/ooxml/scripts/validation/pptx.py +315 -0
  138. package/skills/pptx/ooxml/scripts/validation/redlining.py +279 -0
  139. package/skills/pptx/ooxml.md +427 -0
  140. package/skills/pptx/scripts/html2pptx.js +995 -0
  141. package/skills/pptx/scripts/inventory.py +1020 -0
  142. package/skills/pptx/scripts/rearrange.py +231 -0
  143. package/skills/pptx/scripts/replace.py +385 -0
  144. package/skills/pptx/scripts/thumbnail.py +450 -0
  145. package/skills/receiving-code-review/SKILL.md +213 -0
  146. package/skills/requesting-code-review/SKILL.md +105 -0
  147. package/skills/requesting-code-review/code-reviewer.md +146 -0
  148. package/skills/subagent-driven-development/SKILL.md +277 -0
  149. package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
  150. package/skills/subagent-driven-development/implementer-prompt.md +113 -0
  151. package/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
  152. package/skills/supabase/.skillfish.json +10 -0
  153. package/skills/supabase/SKILL.md +106 -0
  154. package/skills/supabase/assets/feedback-issue-template.md +17 -0
  155. package/skills/supabase/references/skill-feedback.md +17 -0
  156. package/skills/supabase-postgres-best-practices/.skillfish.json +10 -0
  157. package/skills/supabase-postgres-best-practices/SKILL.md +64 -0
  158. package/skills/supabase-postgres-best-practices/references/_contributing.md +170 -0
  159. package/skills/supabase-postgres-best-practices/references/_sections.md +39 -0
  160. package/skills/supabase-postgres-best-practices/references/_template.md +34 -0
  161. package/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
  162. package/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
  163. package/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
  164. package/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
  165. package/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
  166. package/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
  167. package/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
  168. package/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
  169. package/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
  170. package/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
  171. package/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
  172. package/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
  173. package/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
  174. package/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
  175. package/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
  176. package/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
  177. package/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
  178. package/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
  179. package/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
  180. package/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
  181. package/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
  182. package/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
  183. package/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
  184. package/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
  185. package/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
  186. package/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
  187. package/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
  188. package/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
  189. package/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
  190. package/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
  191. package/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
  192. package/skills/supabase-quickstart/SKILL.md +400 -0
  193. package/skills/systematic-debugging/CREATION-LOG.md +119 -0
  194. package/skills/systematic-debugging/SKILL.md +296 -0
  195. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  196. package/skills/systematic-debugging/condition-based-waiting.md +115 -0
  197. package/skills/systematic-debugging/defense-in-depth.md +122 -0
  198. package/skills/systematic-debugging/find-polluter.sh +63 -0
  199. package/skills/systematic-debugging/root-cause-tracing.md +169 -0
  200. package/skills/systematic-debugging/test-academic.md +14 -0
  201. package/skills/systematic-debugging/test-pressure-1.md +58 -0
  202. package/skills/systematic-debugging/test-pressure-2.md +68 -0
  203. package/skills/systematic-debugging/test-pressure-3.md +69 -0
  204. package/skills/test-driven-development/SKILL.md +371 -0
  205. package/skills/test-driven-development/testing-anti-patterns.md +299 -0
  206. package/skills/using-git-worktrees/SKILL.md +218 -0
  207. package/skills/using-superpowers/SKILL.md +115 -0
  208. package/skills/using-superpowers/references/codex-tools.md +100 -0
  209. package/skills/using-superpowers/references/gemini-tools.md +33 -0
  210. package/skills/verification-before-completion/SKILL.md +139 -0
  211. package/skills/video-production/SKILL.md +8 -7
  212. package/skills/writing-plans/SKILL.md +152 -0
  213. package/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
  214. package/skills/writing-skills/SKILL.md +655 -0
  215. package/skills/writing-skills/anthropic-best-practices.md +1150 -0
  216. package/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  217. package/skills/writing-skills/graphviz-conventions.dot +172 -0
  218. package/skills/writing-skills/persuasion-principles.md +187 -0
  219. package/skills/writing-skills/render-graphs.js +168 -0
  220. package/skills/writing-skills/testing-skills-with-subagents.md +384 -0
  221. package/skills/xlsx/.skillfish.json +10 -0
  222. package/skills/xlsx/SKILL.md +288 -0
  223. package/skills/xlsx/recalc.py +178 -0
  224. package/skills/color-grading/SKILL.md +0 -152
  225. package/skills/ffmpeg-patterns/SKILL.md +0 -240
  226. package/skills/image-prompting-guide/SKILL.md +0 -143
  227. package/skills/music-prompting/SKILL.md +0 -146
  228. package/skills/production-review/SKILL.md +0 -152
  229. package/skills/short-form-video/SKILL.md +0 -168
  230. package/skills/sound-design/SKILL.md +0 -154
  231. package/skills/storytelling/SKILL.md +0 -139
  232. package/skills/subtitle-production/SKILL.md +0 -244
  233. package/skills/subtitle-production/reference/burn_to_video.py +0 -222
  234. package/skills/subtitle-production/reference/export_srts.py +0 -127
  235. package/skills/subtitle-production/reference/gen_srt.py +0 -42
  236. package/skills/typography-video/SKILL.md +0 -182
  237. package/skills/typography-video/reference/KineticTitleScene.tsx +0 -345
  238. package/skills/video-editing/SKILL.md +0 -128
  239. package/skills/video-prompting-guide/SKILL.md +0 -268
@@ -0,0 +1,293 @@
1
+ ---
2
+ name: pdf
3
+ description: Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale.
4
+ ---
5
+
6
+ # PDF Processing Guide
7
+
8
+ ## Overview
9
+
10
+ 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.
11
+
12
+ ## Quick Start
13
+
14
+ ```python
15
+ from pypdf import PdfReader, PdfWriter
16
+
17
+ # Read a PDF
18
+ reader = PdfReader("document.pdf")
19
+ print(f"Pages: {len(reader.pages)}")
20
+
21
+ # Extract text
22
+ text = ""
23
+ for page in reader.pages:
24
+ text += page.extract_text()
25
+ ```
26
+
27
+ ## Python Libraries
28
+
29
+ ### pypdf - Basic Operations
30
+
31
+ #### Merge PDFs
32
+ ```python
33
+ from pypdf import PdfWriter, PdfReader
34
+
35
+ writer = PdfWriter()
36
+ for pdf_file in ["doc1.pdf", "doc2.pdf", "doc3.pdf"]:
37
+ reader = PdfReader(pdf_file)
38
+ for page in reader.pages:
39
+ writer.add_page(page)
40
+
41
+ with open("merged.pdf", "wb") as output:
42
+ writer.write(output)
43
+ ```
44
+
45
+ #### Split PDF
46
+ ```python
47
+ reader = PdfReader("input.pdf")
48
+ for i, page in enumerate(reader.pages):
49
+ writer = PdfWriter()
50
+ writer.add_page(page)
51
+ with open(f"page_{i+1}.pdf", "wb") as output:
52
+ writer.write(output)
53
+ ```
54
+
55
+ #### Extract Metadata
56
+ ```python
57
+ reader = PdfReader("document.pdf")
58
+ meta = reader.metadata
59
+ print(f"Title: {meta.title}")
60
+ print(f"Author: {meta.author}")
61
+ print(f"Subject: {meta.subject}")
62
+ print(f"Creator: {meta.creator}")
63
+ ```
64
+
65
+ #### Rotate Pages
66
+ ```python
67
+ reader = PdfReader("input.pdf")
68
+ writer = PdfWriter()
69
+
70
+ page = reader.pages[0]
71
+ page.rotate(90) # Rotate 90 degrees clockwise
72
+ writer.add_page(page)
73
+
74
+ with open("rotated.pdf", "wb") as output:
75
+ writer.write(output)
76
+ ```
77
+
78
+ ### pdfplumber - Text and Table Extraction
79
+
80
+ #### Extract Text with Layout
81
+ ```python
82
+ import pdfplumber
83
+
84
+ with pdfplumber.open("document.pdf") as pdf:
85
+ for page in pdf.pages:
86
+ text = page.extract_text()
87
+ print(text)
88
+ ```
89
+
90
+ #### Extract Tables
91
+ ```python
92
+ with pdfplumber.open("document.pdf") as pdf:
93
+ for i, page in enumerate(pdf.pages):
94
+ tables = page.extract_tables()
95
+ for j, table in enumerate(tables):
96
+ print(f"Table {j+1} on page {i+1}:")
97
+ for row in table:
98
+ print(row)
99
+ ```
100
+
101
+ #### Advanced Table Extraction
102
+ ```python
103
+ import pandas as pd
104
+
105
+ with pdfplumber.open("document.pdf") as pdf:
106
+ all_tables = []
107
+ for page in pdf.pages:
108
+ tables = page.extract_tables()
109
+ for table in tables:
110
+ if table: # Check if table is not empty
111
+ df = pd.DataFrame(table[1:], columns=table[0])
112
+ all_tables.append(df)
113
+
114
+ # Combine all tables
115
+ if all_tables:
116
+ combined_df = pd.concat(all_tables, ignore_index=True)
117
+ combined_df.to_excel("extracted_tables.xlsx", index=False)
118
+ ```
119
+
120
+ ### reportlab - Create PDFs
121
+
122
+ #### Basic PDF Creation
123
+ ```python
124
+ from reportlab.lib.pagesizes import letter
125
+ from reportlab.pdfgen import canvas
126
+
127
+ c = canvas.Canvas("hello.pdf", pagesize=letter)
128
+ width, height = letter
129
+
130
+ # Add text
131
+ c.drawString(100, height - 100, "Hello World!")
132
+ c.drawString(100, height - 120, "This is a PDF created with reportlab")
133
+
134
+ # Add a line
135
+ c.line(100, height - 140, 400, height - 140)
136
+
137
+ # Save
138
+ c.save()
139
+ ```
140
+
141
+ #### Create PDF with Multiple Pages
142
+ ```python
143
+ from reportlab.lib.pagesizes import letter
144
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
145
+ from reportlab.lib.styles import getSampleStyleSheet
146
+
147
+ doc = SimpleDocTemplate("report.pdf", pagesize=letter)
148
+ styles = getSampleStyleSheet()
149
+ story = []
150
+
151
+ # Add content
152
+ title = Paragraph("Report Title", styles['Title'])
153
+ story.append(title)
154
+ story.append(Spacer(1, 12))
155
+
156
+ body = Paragraph("This is the body of the report. " * 20, styles['Normal'])
157
+ story.append(body)
158
+ story.append(PageBreak())
159
+
160
+ # Page 2
161
+ story.append(Paragraph("Page 2", styles['Heading1']))
162
+ story.append(Paragraph("Content for page 2", styles['Normal']))
163
+
164
+ # Build PDF
165
+ doc.build(story)
166
+ ```
167
+
168
+ ## Command-Line Tools
169
+
170
+ ### pdftotext (poppler-utils)
171
+ ```bash
172
+ # Extract text
173
+ pdftotext input.pdf output.txt
174
+
175
+ # Extract text preserving layout
176
+ pdftotext -layout input.pdf output.txt
177
+
178
+ # Extract specific pages
179
+ pdftotext -f 1 -l 5 input.pdf output.txt # Pages 1-5
180
+ ```
181
+
182
+ ### qpdf
183
+ ```bash
184
+ # Merge PDFs
185
+ qpdf --empty --pages file1.pdf file2.pdf -- merged.pdf
186
+
187
+ # Split pages
188
+ qpdf input.pdf --pages . 1-5 -- pages1-5.pdf
189
+ qpdf input.pdf --pages . 6-10 -- pages6-10.pdf
190
+
191
+ # Rotate pages
192
+ qpdf input.pdf output.pdf --rotate=+90:1 # Rotate page 1 by 90 degrees
193
+
194
+ # Remove password
195
+ qpdf --password=mypassword --decrypt encrypted.pdf decrypted.pdf
196
+ ```
197
+
198
+ ### pdftk (if available)
199
+ ```bash
200
+ # Merge
201
+ pdftk file1.pdf file2.pdf cat output merged.pdf
202
+
203
+ # Split
204
+ pdftk input.pdf burst
205
+
206
+ # Rotate
207
+ pdftk input.pdf rotate 1east output rotated.pdf
208
+ ```
209
+
210
+ ## Common Tasks
211
+
212
+ ### Extract Text from Scanned PDFs
213
+ ```python
214
+ # Requires: pip install pytesseract pdf2image
215
+ import pytesseract
216
+ from pdf2image import convert_from_path
217
+
218
+ # Convert PDF to images
219
+ images = convert_from_path('scanned.pdf')
220
+
221
+ # OCR each page
222
+ text = ""
223
+ for i, image in enumerate(images):
224
+ text += f"Page {i+1}:\n"
225
+ text += pytesseract.image_to_string(image)
226
+ text += "\n\n"
227
+
228
+ print(text)
229
+ ```
230
+
231
+ ### Add Watermark
232
+ ```python
233
+ from pypdf import PdfReader, PdfWriter
234
+
235
+ # Create watermark (or load existing)
236
+ watermark = PdfReader("watermark.pdf").pages[0]
237
+
238
+ # Apply to all pages
239
+ reader = PdfReader("document.pdf")
240
+ writer = PdfWriter()
241
+
242
+ for page in reader.pages:
243
+ page.merge_page(watermark)
244
+ writer.add_page(page)
245
+
246
+ with open("watermarked.pdf", "wb") as output:
247
+ writer.write(output)
248
+ ```
249
+
250
+ ### Extract Images
251
+ ```bash
252
+ # Using pdfimages (poppler-utils)
253
+ pdfimages -j input.pdf output_prefix
254
+
255
+ # This extracts all images as output_prefix-000.jpg, output_prefix-001.jpg, etc.
256
+ ```
257
+
258
+ ### Password Protection
259
+ ```python
260
+ from pypdf import PdfReader, PdfWriter
261
+
262
+ reader = PdfReader("input.pdf")
263
+ writer = PdfWriter()
264
+
265
+ for page in reader.pages:
266
+ writer.add_page(page)
267
+
268
+ # Add password
269
+ writer.encrypt("userpassword", "ownerpassword")
270
+
271
+ with open("encrypted.pdf", "wb") as output:
272
+ writer.write(output)
273
+ ```
274
+
275
+ ## Quick Reference
276
+
277
+ | Task | Best Tool | Command/Code |
278
+ |------|-----------|--------------|
279
+ | Merge PDFs | pypdf | `writer.add_page(page)` |
280
+ | Split PDFs | pypdf | One page per file |
281
+ | Extract text | pdfplumber | `page.extract_text()` |
282
+ | Extract tables | pdfplumber | `page.extract_tables()` |
283
+ | Create PDFs | reportlab | Canvas or Platypus |
284
+ | Command line merge | qpdf | `qpdf --empty --pages ...` |
285
+ | OCR scanned PDFs | pytesseract | Convert to image first |
286
+ | Fill PDF forms | pdf-lib or pypdf (see FORMS.md) | See FORMS.md |
287
+
288
+ ## Next Steps
289
+
290
+ - For advanced pypdfium2 usage, see REFERENCE.md
291
+ - For JavaScript libraries (pdf-lib), see REFERENCE.md
292
+ - If you need to fill out a PDF form, follow the instructions in FORMS.md
293
+ - For troubleshooting guides, see REFERENCE.md
@@ -0,0 +1,70 @@
1
+ from dataclasses import dataclass
2
+ import json
3
+ import sys
4
+
5
+
6
+ # Script to check that the `fields.json` file that Claude creates when analyzing PDFs
7
+ # does not have overlapping bounding boxes. See FORMS.md.
8
+
9
+
10
+ @dataclass
11
+ class RectAndField:
12
+ rect: list[float]
13
+ rect_type: str
14
+ field: dict
15
+
16
+
17
+ # Returns a list of messages that are printed to stdout for Claude to read.
18
+ def get_bounding_box_messages(fields_json_stream) -> list[str]:
19
+ messages = []
20
+ fields = json.load(fields_json_stream)
21
+ messages.append(f"Read {len(fields['form_fields'])} fields")
22
+
23
+ def rects_intersect(r1, r2):
24
+ disjoint_horizontal = r1[0] >= r2[2] or r1[2] <= r2[0]
25
+ disjoint_vertical = r1[1] >= r2[3] or r1[3] <= r2[1]
26
+ return not (disjoint_horizontal or disjoint_vertical)
27
+
28
+ rects_and_fields = []
29
+ for f in fields["form_fields"]:
30
+ rects_and_fields.append(RectAndField(f["label_bounding_box"], "label", f))
31
+ rects_and_fields.append(RectAndField(f["entry_bounding_box"], "entry", f))
32
+
33
+ has_error = False
34
+ for i, ri in enumerate(rects_and_fields):
35
+ # This is O(N^2); we can optimize if it becomes a problem.
36
+ for j in range(i + 1, len(rects_and_fields)):
37
+ rj = rects_and_fields[j]
38
+ if ri.field["page_number"] == rj.field["page_number"] and rects_intersect(ri.rect, rj.rect):
39
+ has_error = True
40
+ if ri.field is rj.field:
41
+ messages.append(f"FAILURE: intersection between label and entry bounding boxes for `{ri.field['description']}` ({ri.rect}, {rj.rect})")
42
+ else:
43
+ messages.append(f"FAILURE: intersection between {ri.rect_type} bounding box for `{ri.field['description']}` ({ri.rect}) and {rj.rect_type} bounding box for `{rj.field['description']}` ({rj.rect})")
44
+ if len(messages) >= 20:
45
+ messages.append("Aborting further checks; fix bounding boxes and try again")
46
+ return messages
47
+ if ri.rect_type == "entry":
48
+ if "entry_text" in ri.field:
49
+ font_size = ri.field["entry_text"].get("font_size", 14)
50
+ entry_height = ri.rect[3] - ri.rect[1]
51
+ if entry_height < font_size:
52
+ has_error = True
53
+ messages.append(f"FAILURE: entry bounding box height ({entry_height}) for `{ri.field['description']}` is too short for the text content (font size: {font_size}). Increase the box height or decrease the font size.")
54
+ if len(messages) >= 20:
55
+ messages.append("Aborting further checks; fix bounding boxes and try again")
56
+ return messages
57
+
58
+ if not has_error:
59
+ messages.append("SUCCESS: All bounding boxes are valid")
60
+ return messages
61
+
62
+ if __name__ == "__main__":
63
+ if len(sys.argv) != 2:
64
+ print("Usage: check_bounding_boxes.py [fields.json]")
65
+ sys.exit(1)
66
+ # Input file should be in the `fields.json` format described in FORMS.md.
67
+ with open(sys.argv[1]) as f:
68
+ messages = get_bounding_box_messages(f)
69
+ for msg in messages:
70
+ print(msg)
@@ -0,0 +1,226 @@
1
+ import unittest
2
+ import json
3
+ import io
4
+ from check_bounding_boxes import get_bounding_box_messages
5
+
6
+
7
+ # Currently this is not run automatically in CI; it's just for documentation and manual checking.
8
+ class TestGetBoundingBoxMessages(unittest.TestCase):
9
+
10
+ def create_json_stream(self, data):
11
+ """Helper to create a JSON stream from data"""
12
+ return io.StringIO(json.dumps(data))
13
+
14
+ def test_no_intersections(self):
15
+ """Test case with no bounding box intersections"""
16
+ data = {
17
+ "form_fields": [
18
+ {
19
+ "description": "Name",
20
+ "page_number": 1,
21
+ "label_bounding_box": [10, 10, 50, 30],
22
+ "entry_bounding_box": [60, 10, 150, 30]
23
+ },
24
+ {
25
+ "description": "Email",
26
+ "page_number": 1,
27
+ "label_bounding_box": [10, 40, 50, 60],
28
+ "entry_bounding_box": [60, 40, 150, 60]
29
+ }
30
+ ]
31
+ }
32
+
33
+ stream = self.create_json_stream(data)
34
+ messages = get_bounding_box_messages(stream)
35
+ self.assertTrue(any("SUCCESS" in msg for msg in messages))
36
+ self.assertFalse(any("FAILURE" in msg for msg in messages))
37
+
38
+ def test_label_entry_intersection_same_field(self):
39
+ """Test intersection between label and entry of the same field"""
40
+ data = {
41
+ "form_fields": [
42
+ {
43
+ "description": "Name",
44
+ "page_number": 1,
45
+ "label_bounding_box": [10, 10, 60, 30],
46
+ "entry_bounding_box": [50, 10, 150, 30] # Overlaps with label
47
+ }
48
+ ]
49
+ }
50
+
51
+ stream = self.create_json_stream(data)
52
+ messages = get_bounding_box_messages(stream)
53
+ self.assertTrue(any("FAILURE" in msg and "intersection" in msg for msg in messages))
54
+ self.assertFalse(any("SUCCESS" in msg for msg in messages))
55
+
56
+ def test_intersection_between_different_fields(self):
57
+ """Test intersection between bounding boxes of different fields"""
58
+ data = {
59
+ "form_fields": [
60
+ {
61
+ "description": "Name",
62
+ "page_number": 1,
63
+ "label_bounding_box": [10, 10, 50, 30],
64
+ "entry_bounding_box": [60, 10, 150, 30]
65
+ },
66
+ {
67
+ "description": "Email",
68
+ "page_number": 1,
69
+ "label_bounding_box": [40, 20, 80, 40], # Overlaps with Name's boxes
70
+ "entry_bounding_box": [160, 10, 250, 30]
71
+ }
72
+ ]
73
+ }
74
+
75
+ stream = self.create_json_stream(data)
76
+ messages = get_bounding_box_messages(stream)
77
+ self.assertTrue(any("FAILURE" in msg and "intersection" in msg for msg in messages))
78
+ self.assertFalse(any("SUCCESS" in msg for msg in messages))
79
+
80
+ def test_different_pages_no_intersection(self):
81
+ """Test that boxes on different pages don't count as intersecting"""
82
+ data = {
83
+ "form_fields": [
84
+ {
85
+ "description": "Name",
86
+ "page_number": 1,
87
+ "label_bounding_box": [10, 10, 50, 30],
88
+ "entry_bounding_box": [60, 10, 150, 30]
89
+ },
90
+ {
91
+ "description": "Email",
92
+ "page_number": 2,
93
+ "label_bounding_box": [10, 10, 50, 30], # Same coordinates but different page
94
+ "entry_bounding_box": [60, 10, 150, 30]
95
+ }
96
+ ]
97
+ }
98
+
99
+ stream = self.create_json_stream(data)
100
+ messages = get_bounding_box_messages(stream)
101
+ self.assertTrue(any("SUCCESS" in msg for msg in messages))
102
+ self.assertFalse(any("FAILURE" in msg for msg in messages))
103
+
104
+ def test_entry_height_too_small(self):
105
+ """Test that entry box height is checked against font size"""
106
+ data = {
107
+ "form_fields": [
108
+ {
109
+ "description": "Name",
110
+ "page_number": 1,
111
+ "label_bounding_box": [10, 10, 50, 30],
112
+ "entry_bounding_box": [60, 10, 150, 20], # Height is 10
113
+ "entry_text": {
114
+ "font_size": 14 # Font size larger than height
115
+ }
116
+ }
117
+ ]
118
+ }
119
+
120
+ stream = self.create_json_stream(data)
121
+ messages = get_bounding_box_messages(stream)
122
+ self.assertTrue(any("FAILURE" in msg and "height" in msg for msg in messages))
123
+ self.assertFalse(any("SUCCESS" in msg for msg in messages))
124
+
125
+ def test_entry_height_adequate(self):
126
+ """Test that adequate entry box height passes"""
127
+ data = {
128
+ "form_fields": [
129
+ {
130
+ "description": "Name",
131
+ "page_number": 1,
132
+ "label_bounding_box": [10, 10, 50, 30],
133
+ "entry_bounding_box": [60, 10, 150, 30], # Height is 20
134
+ "entry_text": {
135
+ "font_size": 14 # Font size smaller than height
136
+ }
137
+ }
138
+ ]
139
+ }
140
+
141
+ stream = self.create_json_stream(data)
142
+ messages = get_bounding_box_messages(stream)
143
+ self.assertTrue(any("SUCCESS" in msg for msg in messages))
144
+ self.assertFalse(any("FAILURE" in msg for msg in messages))
145
+
146
+ def test_default_font_size(self):
147
+ """Test that default font size is used when not specified"""
148
+ data = {
149
+ "form_fields": [
150
+ {
151
+ "description": "Name",
152
+ "page_number": 1,
153
+ "label_bounding_box": [10, 10, 50, 30],
154
+ "entry_bounding_box": [60, 10, 150, 20], # Height is 10
155
+ "entry_text": {} # No font_size specified, should use default 14
156
+ }
157
+ ]
158
+ }
159
+
160
+ stream = self.create_json_stream(data)
161
+ messages = get_bounding_box_messages(stream)
162
+ self.assertTrue(any("FAILURE" in msg and "height" in msg for msg in messages))
163
+ self.assertFalse(any("SUCCESS" in msg for msg in messages))
164
+
165
+ def test_no_entry_text(self):
166
+ """Test that missing entry_text doesn't cause height check"""
167
+ data = {
168
+ "form_fields": [
169
+ {
170
+ "description": "Name",
171
+ "page_number": 1,
172
+ "label_bounding_box": [10, 10, 50, 30],
173
+ "entry_bounding_box": [60, 10, 150, 20] # Small height but no entry_text
174
+ }
175
+ ]
176
+ }
177
+
178
+ stream = self.create_json_stream(data)
179
+ messages = get_bounding_box_messages(stream)
180
+ self.assertTrue(any("SUCCESS" in msg for msg in messages))
181
+ self.assertFalse(any("FAILURE" in msg for msg in messages))
182
+
183
+ def test_multiple_errors_limit(self):
184
+ """Test that error messages are limited to prevent excessive output"""
185
+ fields = []
186
+ # Create many overlapping fields
187
+ for i in range(25):
188
+ fields.append({
189
+ "description": f"Field{i}",
190
+ "page_number": 1,
191
+ "label_bounding_box": [10, 10, 50, 30], # All overlap
192
+ "entry_bounding_box": [20, 15, 60, 35] # All overlap
193
+ })
194
+
195
+ data = {"form_fields": fields}
196
+
197
+ stream = self.create_json_stream(data)
198
+ messages = get_bounding_box_messages(stream)
199
+ # Should abort after ~20 messages
200
+ self.assertTrue(any("Aborting" in msg for msg in messages))
201
+ # Should have some FAILURE messages but not hundreds
202
+ failure_count = sum(1 for msg in messages if "FAILURE" in msg)
203
+ self.assertGreater(failure_count, 0)
204
+ self.assertLess(len(messages), 30) # Should be limited
205
+
206
+ def test_edge_touching_boxes(self):
207
+ """Test that boxes touching at edges don't count as intersecting"""
208
+ data = {
209
+ "form_fields": [
210
+ {
211
+ "description": "Name",
212
+ "page_number": 1,
213
+ "label_bounding_box": [10, 10, 50, 30],
214
+ "entry_bounding_box": [50, 10, 150, 30] # Touches at x=50
215
+ }
216
+ ]
217
+ }
218
+
219
+ stream = self.create_json_stream(data)
220
+ messages = get_bounding_box_messages(stream)
221
+ self.assertTrue(any("SUCCESS" in msg for msg in messages))
222
+ self.assertFalse(any("FAILURE" in msg for msg in messages))
223
+
224
+
225
+ if __name__ == '__main__':
226
+ unittest.main()
@@ -0,0 +1,12 @@
1
+ import sys
2
+ from pypdf import PdfReader
3
+
4
+
5
+ # Script for Claude to run to determine whether a PDF has fillable form fields. See FORMS.md.
6
+
7
+
8
+ reader = PdfReader(sys.argv[1])
9
+ if (reader.get_fields()):
10
+ print("This PDF has fillable form fields")
11
+ else:
12
+ print("This PDF does not have fillable form fields; you will need to visually determine where to enter data")
@@ -0,0 +1,35 @@
1
+ import os
2
+ import sys
3
+
4
+ from pdf2image import convert_from_path
5
+
6
+
7
+ # Converts each page of a PDF to a PNG image.
8
+
9
+
10
+ def convert(pdf_path, output_dir, max_dim=1000):
11
+ images = convert_from_path(pdf_path, dpi=200)
12
+
13
+ for i, image in enumerate(images):
14
+ # Scale image if needed to keep width/height under `max_dim`
15
+ width, height = image.size
16
+ if width > max_dim or height > max_dim:
17
+ scale_factor = min(max_dim / width, max_dim / height)
18
+ new_width = int(width * scale_factor)
19
+ new_height = int(height * scale_factor)
20
+ image = image.resize((new_width, new_height))
21
+
22
+ image_path = os.path.join(output_dir, f"page_{i+1}.png")
23
+ image.save(image_path)
24
+ print(f"Saved page {i+1} as {image_path} (size: {image.size})")
25
+
26
+ print(f"Converted {len(images)} pages to PNG images")
27
+
28
+
29
+ if __name__ == "__main__":
30
+ if len(sys.argv) != 3:
31
+ print("Usage: convert_pdf_to_images.py [input pdf] [output directory]")
32
+ sys.exit(1)
33
+ pdf_path = sys.argv[1]
34
+ output_directory = sys.argv[2]
35
+ convert(pdf_path, output_directory)
@@ -0,0 +1,41 @@
1
+ import json
2
+ import sys
3
+
4
+ from PIL import Image, ImageDraw
5
+
6
+
7
+ # Creates "validation" images with rectangles for the bounding box information that
8
+ # Claude creates when determining where to add text annotations in PDFs. See FORMS.md.
9
+
10
+
11
+ def create_validation_image(page_number, fields_json_path, input_path, output_path):
12
+ # Input file should be in the `fields.json` format described in FORMS.md.
13
+ with open(fields_json_path, 'r') as f:
14
+ data = json.load(f)
15
+
16
+ img = Image.open(input_path)
17
+ draw = ImageDraw.Draw(img)
18
+ num_boxes = 0
19
+
20
+ for field in data["form_fields"]:
21
+ if field["page_number"] == page_number:
22
+ entry_box = field['entry_bounding_box']
23
+ label_box = field['label_bounding_box']
24
+ # Draw red rectangle over entry bounding box and blue rectangle over the label.
25
+ draw.rectangle(entry_box, outline='red', width=2)
26
+ draw.rectangle(label_box, outline='blue', width=2)
27
+ num_boxes += 2
28
+
29
+ img.save(output_path)
30
+ print(f"Created validation image at {output_path} with {num_boxes} bounding boxes")
31
+
32
+
33
+ if __name__ == "__main__":
34
+ if len(sys.argv) != 5:
35
+ print("Usage: create_validation_image.py [page number] [fields.json file] [input image path] [output image path]")
36
+ sys.exit(1)
37
+ page_number = int(sys.argv[1])
38
+ fields_json_path = sys.argv[2]
39
+ input_image_path = sys.argv[3]
40
+ output_image_path = sys.argv[4]
41
+ create_validation_image(page_number, fields_json_path, input_image_path, output_image_path)