ima-claude 2.9.0 → 2.10.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/dist/cli.js +1 -1
- package/package.json +1 -1
- package/plugins/ima-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/ima-claude/skills/ima-doc2pdf/SKILL.md +242 -0
- package/plugins/ima-claude/skills/ima-doc2pdf/references/formatting-spec.md +88 -0
- package/plugins/ima-claude/skills/ima-doc2pdf/scripts/docx_utils.py +21 -0
- package/plugins/ima-claude/skills/ima-doc2pdf/scripts/extract_docx.py +384 -0
- package/plugins/ima-claude/skills/ima-doc2pdf/scripts/generate_pdf.py +663 -0
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ var HOOKS_DIR = join(CLAUDE_DIR, "hooks");
|
|
|
11
11
|
var COMMANDS_DIR = join(CLAUDE_DIR, "commands");
|
|
12
12
|
var RULES_DIR = join(CLAUDE_DIR, "rules");
|
|
13
13
|
var SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
|
|
14
|
-
var VERSION = "2.
|
|
14
|
+
var VERSION = "2.10.0";
|
|
15
15
|
var colors = {
|
|
16
16
|
reset: "\x1B[0m",
|
|
17
17
|
bright: "\x1B[1m",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ima-claude",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "IMA's Claude Code skills for functional programming, architecture, and team standards. 47 skills, 24 hooks, default persona, 3-tier memory system.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "IMA",
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ima-doc2pdf
|
|
3
|
+
description: >-
|
|
4
|
+
Convert DOCX content into branded IMA PDF documents using ReportLab with Lato
|
|
5
|
+
typography, navy headings, justified body text, running footers, and embedded
|
|
6
|
+
images. Produces branded PDF documents with content pages. Generates a placeholder
|
|
7
|
+
cover that can be replaced by ima-cover-creator for production output. Use when: converting a Word doc to branded PDF,
|
|
8
|
+
creating PDF content pages for Canva import, generating an IMA branded document PDF,
|
|
9
|
+
or when the user says "convert this docx to PDF," "branded PDF," "content pages,"
|
|
10
|
+
"PDF for Canva," or "doc to PDF." Also triggers on: "make a PDF from this Word
|
|
11
|
+
file," "export to PDF," "generate branded PDF," "IMA branded document PDF."
|
|
12
|
+
Always load ima-brand alongside for color/typography authority.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# IMA DOCX → Branded PDF
|
|
16
|
+
|
|
17
|
+
Extracts content from any Word document and generates a branded IMA PDF with
|
|
18
|
+
Lato typography, navy/gold colors, and IMA layout standards. Works with guides,
|
|
19
|
+
reports, white papers, and other IMA documents. Outputs content pages only
|
|
20
|
+
(no cover page) — the cover is handled by `ima-cover-creator` and merged via pypdf.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Why ReportLab for Content
|
|
25
|
+
|
|
26
|
+
Content pages are pure document flow: headings, body paragraphs, bullet lists,
|
|
27
|
+
inline bold/italic, embedded images, and running footers. ReportLab handles all of
|
|
28
|
+
this with proper Lato font registration, automatic page breaks, and precise
|
|
29
|
+
typographic control. No coordinate math needed — the flow engine does the work.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### 1. Install dependencies (once)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install python-docx reportlab Pillow pypdf --break-system-packages
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Generate content PDF
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
python3 scripts/generate_pdf.py path/to/document.docx --out content.pdf
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 3. Merge with cover (from ima-cover-creator)
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from pypdf import PdfReader, PdfWriter
|
|
51
|
+
|
|
52
|
+
cover = PdfReader("cover.pdf")
|
|
53
|
+
content = PdfReader("content.pdf")
|
|
54
|
+
|
|
55
|
+
writer = PdfWriter()
|
|
56
|
+
writer.add_page(cover.pages[0])
|
|
57
|
+
|
|
58
|
+
# Skip the first 2 pages (ReportLab's placeholder cover + overflow)
|
|
59
|
+
for page in content.pages[2:]:
|
|
60
|
+
writer.add_page(page)
|
|
61
|
+
|
|
62
|
+
with open("final.pdf", "wb") as f:
|
|
63
|
+
writer.write(f)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Pipeline
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
DOCX
|
|
72
|
+
↓ extract_docx.py (text, structure, metadata)
|
|
73
|
+
↓ generate_pdf.py (ReportLab → branded PDF)
|
|
74
|
+
↓
|
|
75
|
+
content.pdf (N pages, no cover)
|
|
76
|
+
+
|
|
77
|
+
cover.pdf (from ima-cover-creator)
|
|
78
|
+
↓ pypdf merge
|
|
79
|
+
↓
|
|
80
|
+
final.pdf → Canva import
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Typography Spec (Canva-confirmed)
|
|
86
|
+
|
|
87
|
+
All values confirmed from Canva design data. Font: Lato (Google Fonts).
|
|
88
|
+
|
|
89
|
+
### Headings
|
|
90
|
+
|
|
91
|
+
| Element | Size | Weight | Color | Align |
|
|
92
|
+
|---------|------|--------|-------|-------|
|
|
93
|
+
| Section heading (h2) | 15pt | Bold | #00066F | Center |
|
|
94
|
+
| Sub-heading (h3) | 13pt | Bold | #00066F | Left |
|
|
95
|
+
| Intro heading | 15pt | Bold | #00066F | Center |
|
|
96
|
+
|
|
97
|
+
### Body Text
|
|
98
|
+
|
|
99
|
+
| Element | Size | Weight | Color | Align |
|
|
100
|
+
|---------|------|--------|-------|-------|
|
|
101
|
+
| Body paragraph | 12pt | Regular | #000000 | Justify |
|
|
102
|
+
| Body bold inline | 12pt | Bold | #000000 | Justify |
|
|
103
|
+
| Body bold navy | 12pt | Bold | #00066F | Justify |
|
|
104
|
+
| Bullet item | 12pt | Regular | #000000 | Left |
|
|
105
|
+
| Bullet marker | — | — | #00066F | — |
|
|
106
|
+
|
|
107
|
+
### Other Elements
|
|
108
|
+
|
|
109
|
+
| Element | Size | Weight | Color |
|
|
110
|
+
|---------|------|--------|-------|
|
|
111
|
+
| Footer | 10pt | Regular | #666666 |
|
|
112
|
+
| Reference entry | 8pt | Regular | #333333 |
|
|
113
|
+
| Reference heading | 13pt | Bold | #00066F |
|
|
114
|
+
| Q&A question | 12pt | Bold | #00066F |
|
|
115
|
+
| Q&A answer | 12pt | Regular | #000000 |
|
|
116
|
+
| Warning box | 12pt | Bold | #FFFFFF on #00066F bg |
|
|
117
|
+
|
|
118
|
+
### Page Setup
|
|
119
|
+
|
|
120
|
+
| Property | Value |
|
|
121
|
+
|----------|-------|
|
|
122
|
+
| Page size | US Letter (8.5 × 11 in) |
|
|
123
|
+
| Margins | 0.5 in all sides |
|
|
124
|
+
| Body width | 7.5 in |
|
|
125
|
+
| Footer height | 0.4 in from bottom |
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Content Extraction
|
|
130
|
+
|
|
131
|
+
The `extract_docx.py` script classifies each Word paragraph into typed blocks:
|
|
132
|
+
|
|
133
|
+
| Type | Description |
|
|
134
|
+
|------|-------------|
|
|
135
|
+
| `h1` | Top-level heading (title) |
|
|
136
|
+
| `h2` | Section heading |
|
|
137
|
+
| `h3` | Sub-heading |
|
|
138
|
+
| `heading_bold` | All-bold paragraph (inline heading) |
|
|
139
|
+
| `body` | Regular paragraph |
|
|
140
|
+
| `bullet` | List item |
|
|
141
|
+
| `author` | Author name |
|
|
142
|
+
| `date` | Date string |
|
|
143
|
+
| `disclaimer` | Disclaimer text |
|
|
144
|
+
| `warning` | Warning box content |
|
|
145
|
+
| `question` | Q&A question |
|
|
146
|
+
| `answer_start` | Q&A answer (YES/NO prefix) |
|
|
147
|
+
| `reference` | Numbered citation |
|
|
148
|
+
| `ref_heading` | "References" heading |
|
|
149
|
+
| `figure_caption` | Figure/table caption |
|
|
150
|
+
| `page_break` | Hard page break |
|
|
151
|
+
|
|
152
|
+
Each block includes `runs` with per-run bold/italic flags for inline formatting.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Image Handling
|
|
157
|
+
|
|
158
|
+
The script extracts embedded DOCX images via `python-docx`:
|
|
159
|
+
|
|
160
|
+
1. Reads all image relationships from the DOCX package
|
|
161
|
+
2. Maps paragraph indices to embedded image positions
|
|
162
|
+
3. Writes images to temp files
|
|
163
|
+
4. Inserts ReportLab `Image` flowables at the correct positions
|
|
164
|
+
5. Scales to fit within `page_width - 2 × margin`
|
|
165
|
+
|
|
166
|
+
Images that appear between text paragraphs (image-only paragraphs) are also caught
|
|
167
|
+
and appended after all text content.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Cover Page Behavior
|
|
172
|
+
|
|
173
|
+
The ReportLab script generates a **placeholder cover** (navy background with title
|
|
174
|
+
text) as pages 1-2 of its output. This exists so the script works standalone, but
|
|
175
|
+
when pairing with `ima-cover-creator`, **skip the first 2 pages** during merge.
|
|
176
|
+
|
|
177
|
+
To check which pages to skip:
|
|
178
|
+
```python
|
|
179
|
+
from pypdf import PdfReader
|
|
180
|
+
r = PdfReader("content.pdf")
|
|
181
|
+
for i in range(min(3, len(r.pages))):
|
|
182
|
+
text = r.pages[i].extract_text()[:100]
|
|
183
|
+
print(f"Page {i}: {text}")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The first content page typically starts with "Introduction" or a section heading.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Scripts
|
|
191
|
+
|
|
192
|
+
| Script | Purpose |
|
|
193
|
+
|--------|---------|
|
|
194
|
+
| `generate_pdf.py` | Main: DOCX → branded PDF via ReportLab |
|
|
195
|
+
| `extract_docx.py` | Extracts structured content from Word documents |
|
|
196
|
+
| `docx_utils.py` | Shared utilities for DOCX parsing |
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Fonts
|
|
201
|
+
|
|
202
|
+
Lato TTF files are auto-downloaded from Google Fonts on first run into the `fonts/`
|
|
203
|
+
directory (which is git-ignored). The font family (Regular, Bold, Italic, BoldItalic)
|
|
204
|
+
is registered with ReportLab so that `<b>` and `<i>` markup works in Paragraph objects.
|
|
205
|
+
|
|
206
|
+
If the fonts are already present, the download is skipped.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Customization
|
|
211
|
+
|
|
212
|
+
### Adjusting the footer
|
|
213
|
+
|
|
214
|
+
The footer shows the document title and date. To customize:
|
|
215
|
+
```python
|
|
216
|
+
# In generate_pdf.py, the footer text is built from:
|
|
217
|
+
title_short = (title[:60] + "...") if len(title) > 60 else title
|
|
218
|
+
footer_text = f"{title_short} ({clean_date})"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Adding new paragraph types
|
|
222
|
+
|
|
223
|
+
1. Add a classifier rule in `extract_docx.py`
|
|
224
|
+
2. Add a handler in `block_to_flowables()` in `generate_pdf.py`
|
|
225
|
+
3. Create a ReportLab `ParagraphStyle` in `build_styles()`
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Relationship to Other Skills
|
|
230
|
+
|
|
231
|
+
| Skill | Role |
|
|
232
|
+
|-------|------|
|
|
233
|
+
| **ima-cover-creator** | Generates branded cover page (PPTX → PDF) |
|
|
234
|
+
| **ima-cancer-care-guides** | Full pipeline including Markdown source and Canva API mapping |
|
|
235
|
+
| **ima-brand** | Source of truth for colors, typography, voice |
|
|
236
|
+
|
|
237
|
+
**Typical workflow:**
|
|
238
|
+
```
|
|
239
|
+
ima-cover-creator → cover.pdf (1 page)
|
|
240
|
+
ima-doc2pdf → content.pdf (skip first 2 pages)
|
|
241
|
+
pypdf merge → final.pdf → Canva import
|
|
242
|
+
```
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Content Page Formatting Spec
|
|
2
|
+
|
|
3
|
+
Values confirmed from Canva design data (Cancer Drug Resistance Guide, March 2026).
|
|
4
|
+
Canva internal units × 0.75 = points.
|
|
5
|
+
|
|
6
|
+
## Page Setup
|
|
7
|
+
|
|
8
|
+
| Property | Value |
|
|
9
|
+
|----------|-------|
|
|
10
|
+
| Page size | US Letter (8.5 × 11 in / 612 × 792 pt) |
|
|
11
|
+
| Margins | 0.5 in (36 pt) all sides |
|
|
12
|
+
| Body text width | 7.5 in (540 pt) |
|
|
13
|
+
| Footer position | 0.4 in from bottom |
|
|
14
|
+
|
|
15
|
+
## Color Palette
|
|
16
|
+
|
|
17
|
+
| Name | Hex | Usage |
|
|
18
|
+
|------|-----|-------|
|
|
19
|
+
| Trustworthy Indigo | #00066F | Headings, bullet markers, warning bg |
|
|
20
|
+
| Body Black | #000000 | Body text (pure black, not #1A1A1A) |
|
|
21
|
+
| Dark Gray | #333333 | Reference entries |
|
|
22
|
+
| Gray Text | #666666 | Footer |
|
|
23
|
+
| Light Gray | #CCCCCC | Footer rule |
|
|
24
|
+
| White | #FFFFFF | Warning text |
|
|
25
|
+
| Vital Gold | #FFCC00 | Warning emphasis |
|
|
26
|
+
|
|
27
|
+
## Section Heading (h2)
|
|
28
|
+
|
|
29
|
+
- Font: Lato 15pt Bold
|
|
30
|
+
- Color: #00066F (navy)
|
|
31
|
+
- Alignment: Center
|
|
32
|
+
- Spacing: 16pt above, 5pt below
|
|
33
|
+
|
|
34
|
+
## Sub-heading (h3)
|
|
35
|
+
|
|
36
|
+
- Font: Lato 13pt Bold
|
|
37
|
+
- Color: #00066F (navy)
|
|
38
|
+
- Alignment: Left
|
|
39
|
+
- Spacing: 10pt above, 3pt below
|
|
40
|
+
|
|
41
|
+
## Body Paragraph
|
|
42
|
+
|
|
43
|
+
- Font: Lato 12pt Regular
|
|
44
|
+
- Color: #000000
|
|
45
|
+
- Alignment: Justify
|
|
46
|
+
- Line height: 1.4 (14.5pt leading)
|
|
47
|
+
- Spacing: 6pt below
|
|
48
|
+
|
|
49
|
+
## Bullet Lists
|
|
50
|
+
|
|
51
|
+
- Font: Lato 12pt Regular
|
|
52
|
+
- Color: #000000
|
|
53
|
+
- Bullet marker color: #00066F (navy)
|
|
54
|
+
- Indent: 18pt
|
|
55
|
+
- Spacing: 1pt above, 1pt below each item
|
|
56
|
+
|
|
57
|
+
## Bold Inline Variants
|
|
58
|
+
|
|
59
|
+
- **Body bold** (#000000): Drug names, emphasis
|
|
60
|
+
- **Body bold navy** (#00066F): Inline sub-headings
|
|
61
|
+
|
|
62
|
+
## Warning Box
|
|
63
|
+
|
|
64
|
+
- Background: #00066F (navy) — note: ReportLab uses solid, Canva uses gradient
|
|
65
|
+
- Text: Lato 12pt Bold, #FFFFFF
|
|
66
|
+
- Emphasis: Vital Gold #FFCC00
|
|
67
|
+
- Alignment: Center
|
|
68
|
+
- Padding: 10pt all sides
|
|
69
|
+
|
|
70
|
+
## Footer
|
|
71
|
+
|
|
72
|
+
- Font: Lato 10pt
|
|
73
|
+
- Color: #666666
|
|
74
|
+
- Alignment: Center
|
|
75
|
+
- Content: "{Document Title} ({Date})"
|
|
76
|
+
- Rule above: 0.5pt, #CCCCCC
|
|
77
|
+
|
|
78
|
+
## References
|
|
79
|
+
|
|
80
|
+
- Heading: Lato 13pt Bold, #00066F
|
|
81
|
+
- Entry: Lato 8pt Regular, #333333
|
|
82
|
+
- Hanging indent: 14pt
|
|
83
|
+
|
|
84
|
+
## Q&A
|
|
85
|
+
|
|
86
|
+
- Question: Lato 12pt Bold, #00066F
|
|
87
|
+
- Answer: Lato 12pt Regular, #000000
|
|
88
|
+
- YES/NO prefix: Bold
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared utilities for Word document processing across ima-cancer-care-guides scripts.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from docx.oxml.ns import qn
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def has_page_break(para):
|
|
9
|
+
"""Detect a hard page break in a Word paragraph.
|
|
10
|
+
|
|
11
|
+
Checks both explicit w:br type=page runs and section-level page breaks
|
|
12
|
+
(w:pPr/w:sectPr), which appear on the last paragraph of a section.
|
|
13
|
+
"""
|
|
14
|
+
for run in para.runs:
|
|
15
|
+
for br in run._element.findall(qn('w:br')):
|
|
16
|
+
if br.get(qn('w:type')) == 'page':
|
|
17
|
+
return True
|
|
18
|
+
pPr = para._element.find(qn('w:pPr'))
|
|
19
|
+
if pPr is not None and pPr.find(qn('w:sectPr')) is not None:
|
|
20
|
+
return True
|
|
21
|
+
return False
|