opencode-sa-assistant 0.2.5 β 0.2.6
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 +43 -12
- package/package.json +1 -1
- package/src/skills/docx/SKILL.md +214 -0
- package/src/skills/pptx/SKILL.md +76 -14
- package/src/skills/pptx/references/html2pptx.md +191 -0
package/README.md
CHANGED
|
@@ -17,6 +17,9 @@ This plugin activates when you use the `wadd` keyword in your messages, transfor
|
|
|
17
17
|
- `sa-researcher`: AWS service information via MCP tools
|
|
18
18
|
- `sa-reviewer`: Well-Architected Framework reviews (6 pillars)
|
|
19
19
|
|
|
20
|
+
**Orchestrator**:
|
|
21
|
+
- `sa-orchestrator`: Master agent that coordinates Gurus and Specialists based on Guru_Mandate rules
|
|
22
|
+
|
|
20
23
|
## Features
|
|
21
24
|
|
|
22
25
|
β¨ **WADD Mode Activation**: Type `wadd` to activate SA mode
|
|
@@ -26,6 +29,33 @@ This plugin activates when you use the `wadd` keyword in your messages, transfor
|
|
|
26
29
|
π **AWS MCP Tools**: Real-time AWS documentation access
|
|
27
30
|
π°π· **Bilingual**: Korean prose + English technical terms
|
|
28
31
|
|
|
32
|
+
## Agents
|
|
33
|
+
|
|
34
|
+
### Guru Agents (Philosophy & Strategy)
|
|
35
|
+
|
|
36
|
+
| Agent | Core Principles | Use When | Avoid When |
|
|
37
|
+
|-------|----------------|----------|------------|
|
|
38
|
+
| **sa-bezos** | Customer Obsession, Day 1, Working Backwards | Customer value definition, Long-term vs short-term decisions, PR/FAQ writing | Pure technical implementation, Cost calculations |
|
|
39
|
+
| **sa-vogels** | Everything Fails, Design for Failure, Loose Coupling | Distributed system design, Scalability/resilience patterns, Operational excellence | Business strategy, Cost ROI analysis |
|
|
40
|
+
| **sa-naval** | Leverage, Asymmetric Outcomes, Compounding | Cost analysis, ROI, Leverage points identification, Complexity removal | Technical architecture details, Customer experience |
|
|
41
|
+
| **sa-feynman** | Feynman Technique, First Principles, Simplicity | Complex concept simplification, Non-technical presentations, Learning materials | Detailed technical decisions, Cost analysis |
|
|
42
|
+
|
|
43
|
+
### Specialist Agents (Execution)
|
|
44
|
+
|
|
45
|
+
| Agent | Role | Output |
|
|
46
|
+
|-------|------|--------|
|
|
47
|
+
| **sa-explorer** | Architecture analysis | Component discovery, Dependency mapping, As-Is documentation |
|
|
48
|
+
| **sa-researcher** | AWS information gathering | Service info, Pricing, Best practices with evidence |
|
|
49
|
+
| **sa-reviewer** | Well-Architected review | 6 Pillars evaluation, Risk summary, Prioritized recommendations |
|
|
50
|
+
|
|
51
|
+
### Orchestrator
|
|
52
|
+
|
|
53
|
+
**sa-orchestrator** is the master agent that:
|
|
54
|
+
- Classifies user intent (Architecture Review, Service Selection, Cost Analysis, etc.)
|
|
55
|
+
- Invokes appropriate Gurus based on Guru_Mandate rules
|
|
56
|
+
- Delegates tasks to Specialists
|
|
57
|
+
- Enforces anti-patterns (no architecture decisions without Guru consultation)
|
|
58
|
+
|
|
29
59
|
## Installation
|
|
30
60
|
|
|
31
61
|
Add the plugin to your `opencode.json`:
|
|
@@ -107,17 +137,18 @@ packages/opencode-sa-assistant/
|
|
|
107
137
|
βββ src/
|
|
108
138
|
β βββ index.ts # Plugin entry point
|
|
109
139
|
β βββ hooks/wadd-mode.ts # WADD keyword detection
|
|
110
|
-
β βββ
|
|
111
|
-
β β βββ
|
|
112
|
-
β β βββ
|
|
113
|
-
β β
|
|
114
|
-
β
|
|
115
|
-
β β βββ
|
|
116
|
-
β β βββ
|
|
117
|
-
β β βββ
|
|
118
|
-
β β
|
|
119
|
-
β βββ
|
|
120
|
-
β
|
|
140
|
+
β βββ agents/index.ts # Agent/Skill installation
|
|
141
|
+
β β βββ prompts/
|
|
142
|
+
β β β βββ orchestrator.ts # SA Orchestrator
|
|
143
|
+
β β β βββ gurus.ts # 4 Guru agents
|
|
144
|
+
β β β βββ specialists.ts # 3 Specialist agents
|
|
145
|
+
β β βββ skills/
|
|
146
|
+
β β β βββ guru/SKILL.md
|
|
147
|
+
β β β βββ mcp/SKILL.md
|
|
148
|
+
β β β βββ docx/SKILL.md
|
|
149
|
+
β β β βββ pptx/SKILL.md
|
|
150
|
+
β β βββ __tests__/
|
|
151
|
+
β β βββ *.test.ts # 80 tests
|
|
121
152
|
βββ package.json
|
|
122
153
|
βββ tsconfig.json
|
|
123
154
|
βββ bunfig.toml
|
|
@@ -151,7 +182,7 @@ The plugin integrates with AWS Documentation MCP for real-time information:
|
|
|
151
182
|
|
|
152
183
|
## License
|
|
153
184
|
|
|
154
|
-
|
|
185
|
+
MIT
|
|
155
186
|
|
|
156
187
|
## Author
|
|
157
188
|
|
package/package.json
CHANGED
package/src/skills/docx/SKILL.md
CHANGED
|
@@ -272,6 +272,218 @@ new Paragraph({
|
|
|
272
272
|
})
|
|
273
273
|
```
|
|
274
274
|
|
|
275
|
+
### μ΄λ―Έμ§ λΉμ¨ μ μ§ μ½μ
|
|
276
|
+
|
|
277
|
+
μ΄λ―Έμ§ μλ³Έ λΉμ¨μ μ μ§νλ©΄μ μ½μ
νλ €λ©΄ `image-size` ν¨ν€μ§λ₯Ό μ¬μ©ν©λλ€.
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
const sizeOf = require('image-size');
|
|
281
|
+
|
|
282
|
+
function createImageWithAspectRatio(imagePath, maxWidth = 500) {
|
|
283
|
+
const dimensions = sizeOf(imagePath);
|
|
284
|
+
const ratio = dimensions.height / dimensions.width;
|
|
285
|
+
const displayWidth = Math.min(maxWidth, dimensions.width);
|
|
286
|
+
const displayHeight = Math.round(displayWidth * ratio);
|
|
287
|
+
|
|
288
|
+
return new Paragraph({
|
|
289
|
+
alignment: AlignmentType.CENTER,
|
|
290
|
+
children: [new ImageRun({
|
|
291
|
+
type: imagePath.endsWith('.png') ? 'png' : 'jpg',
|
|
292
|
+
data: fs.readFileSync(imagePath),
|
|
293
|
+
transformation: { width: displayWidth, height: displayHeight }
|
|
294
|
+
})]
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// μ¬μ© μμ
|
|
299
|
+
createImageWithAspectRatio('diagrams/architecture.png', 500)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## λ€μ΄μ΄κ·Έλ¨ μ½μ
|
|
303
|
+
|
|
304
|
+
### Mermaid λ€μ΄μ΄κ·Έλ¨μ μ΄λ―Έμ§λ‘ λ³ν
|
|
305
|
+
|
|
306
|
+
Word λ¬Έμμμ Mermaid λ€μ΄μ΄κ·Έλ¨μ νμνλ €λ©΄ λ¨Όμ μ΄λ―Έμ§λ‘ λ³νν΄μΌ ν©λλ€.
|
|
307
|
+
|
|
308
|
+
#### 1. Mermaid CLI μ€μΉ
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
npm install @mermaid-js/mermaid-cli
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### 2. .mmd νμΌ μμ±
|
|
315
|
+
|
|
316
|
+
```mermaid
|
|
317
|
+
graph TB
|
|
318
|
+
Client[Client] --> ALB[Application Load Balancer]
|
|
319
|
+
ALB --> Lambda[AWS Lambda]
|
|
320
|
+
Lambda --> DynamoDB[(DynamoDB)]
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### 3. μ΄λ―Έμ§λ‘ λ³ν
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
npx mmdc -i diagram.mmd -o diagram.png -w 800 -b white
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### 4. λΉμ¨ μ μ§νμ¬ μ½μ
|
|
330
|
+
|
|
331
|
+
```javascript
|
|
332
|
+
const sizeOf = require('image-size');
|
|
333
|
+
|
|
334
|
+
function insertMermaidDiagram(mmdPath, outputPngPath, maxWidth = 500) {
|
|
335
|
+
// 1. Mermaid β PNG λ³ν (μ¬μ μ€ν νμ)
|
|
336
|
+
// npx mmdc -i ${mmdPath} -o ${outputPngPath} -w 800 -b white
|
|
337
|
+
|
|
338
|
+
// 2. μ΄λ―Έμ§ ν¬κΈ° νμΈ λ° λΉμ¨ κ³μ°
|
|
339
|
+
const dimensions = sizeOf(outputPngPath);
|
|
340
|
+
const ratio = dimensions.height / dimensions.width;
|
|
341
|
+
const displayHeight = Math.round(maxWidth * ratio);
|
|
342
|
+
|
|
343
|
+
return new Paragraph({
|
|
344
|
+
alignment: AlignmentType.CENTER,
|
|
345
|
+
children: [new ImageRun({
|
|
346
|
+
type: "png",
|
|
347
|
+
data: fs.readFileSync(outputPngPath),
|
|
348
|
+
transformation: { width: maxWidth, height: displayHeight }
|
|
349
|
+
})]
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## μ½λ λΈλ‘ (λ©ν°λΌμΈ)
|
|
355
|
+
|
|
356
|
+
**μ€μ**: Wordμμλ `\n` λ¬Έμκ° μ λλ‘ λ λλ§λμ§ μμ΅λλ€. κ° μ€μ λ³λ Paragraphλ‘ λΆλ¦¬ν΄μΌ ν©λλ€.
|
|
357
|
+
|
|
358
|
+
### μλͺ»λ λ°©μ (λμ μν¨)
|
|
359
|
+
|
|
360
|
+
```javascript
|
|
361
|
+
// β μ΄ λ°©μμ μ€λ°κΏμ΄ 무μλ¨
|
|
362
|
+
new Paragraph({
|
|
363
|
+
children: [new TextRun({ text: "line1\nline2\nline3" })]
|
|
364
|
+
})
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### μ¬λ°λ₯Έ λ°©μ
|
|
368
|
+
|
|
369
|
+
```javascript
|
|
370
|
+
// β
κ° μ€μ λ³λ Paragraphλ‘ λΆλ¦¬
|
|
371
|
+
function createCodeBlock(codeLines) {
|
|
372
|
+
const lines = Array.isArray(codeLines) ? codeLines : codeLines.split('\n');
|
|
373
|
+
return lines.map((line, index) =>
|
|
374
|
+
new Paragraph({
|
|
375
|
+
shading: { fill: "F5F5F5", type: ShadingType.CLEAR },
|
|
376
|
+
spacing: {
|
|
377
|
+
before: index === 0 ? 120 : 0,
|
|
378
|
+
after: index === lines.length - 1 ? 120 : 0
|
|
379
|
+
},
|
|
380
|
+
indent: { left: 360 },
|
|
381
|
+
children: [new TextRun({
|
|
382
|
+
text: line || " ", // λΉ μ€μ 곡백μΌλ‘
|
|
383
|
+
font: "Courier New",
|
|
384
|
+
size: 18
|
|
385
|
+
})]
|
|
386
|
+
})
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// μ¬μ© μμ - λ°°μ΄λ‘ μ λ¬
|
|
391
|
+
...createCodeBlock([
|
|
392
|
+
"{",
|
|
393
|
+
" \"key\": \"value\",",
|
|
394
|
+
" \"nested\": {",
|
|
395
|
+
" \"item\": 123",
|
|
396
|
+
" }",
|
|
397
|
+
"}"
|
|
398
|
+
])
|
|
399
|
+
|
|
400
|
+
// λλ λ¬Έμμ΄λ‘ μ λ¬
|
|
401
|
+
...createCodeBlock(`function hello() {
|
|
402
|
+
console.log("Hello, World!");
|
|
403
|
+
}`)
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## Pretty JSON νν
|
|
407
|
+
|
|
408
|
+
JSON λ°μ΄ν°λ₯Ό κ°λ
μ± μ’κ² νννλ ν¬νΌ ν¨μ:
|
|
409
|
+
|
|
410
|
+
```javascript
|
|
411
|
+
function jsonToCodeBlock(jsonObject, indent = 4) {
|
|
412
|
+
const prettyJson = JSON.stringify(jsonObject, null, indent);
|
|
413
|
+
return createCodeBlock(prettyJson.split('\n'));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// μ¬μ© μμ
|
|
417
|
+
const apiResponse = {
|
|
418
|
+
userId: "user_abc123",
|
|
419
|
+
metadata: {
|
|
420
|
+
model: "claude-3-haiku",
|
|
421
|
+
tokens: 150
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// λ¬Έμμ μΆκ°
|
|
426
|
+
...jsonToCodeBlock(apiResponse)
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
## ν
μ΄λΈ μ΄ λλΉ μ‘°μ
|
|
430
|
+
|
|
431
|
+
### νΌμΌνΈ κΈ°λ° μ΄ λλΉ μ§μ
|
|
432
|
+
|
|
433
|
+
```javascript
|
|
434
|
+
function createTableWithWidths(headers, rows, widthPercents) {
|
|
435
|
+
const totalWidth = 9360; // DXA λ¨μ (μ½ 6.5μΈμΉ, μΌλ°μ μΈ λ³Έλ¬Έ λλΉ)
|
|
436
|
+
const colWidths = widthPercents.map(p => Math.floor(totalWidth * p / 100));
|
|
437
|
+
|
|
438
|
+
const tableBorder = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" };
|
|
439
|
+
const cellBorders = { top: tableBorder, bottom: tableBorder, left: tableBorder, right: tableBorder };
|
|
440
|
+
|
|
441
|
+
// ν€λ ν μμ±
|
|
442
|
+
const headerRow = new TableRow({
|
|
443
|
+
tableHeader: true,
|
|
444
|
+
children: headers.map((header, i) =>
|
|
445
|
+
new TableCell({
|
|
446
|
+
width: { size: colWidths[i], type: WidthType.DXA },
|
|
447
|
+
borders: cellBorders,
|
|
448
|
+
shading: { fill: "232F3E", type: ShadingType.CLEAR },
|
|
449
|
+
children: [new Paragraph({
|
|
450
|
+
children: [new TextRun({ text: header, bold: true, color: "FFFFFF" })]
|
|
451
|
+
})]
|
|
452
|
+
})
|
|
453
|
+
)
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// λ°μ΄ν° ν μμ±
|
|
457
|
+
const dataRows = rows.map(row =>
|
|
458
|
+
new TableRow({
|
|
459
|
+
children: row.map((cell, i) =>
|
|
460
|
+
new TableCell({
|
|
461
|
+
width: { size: colWidths[i], type: WidthType.DXA },
|
|
462
|
+
borders: cellBorders,
|
|
463
|
+
children: [new Paragraph({ children: [new TextRun(cell)] })]
|
|
464
|
+
})
|
|
465
|
+
)
|
|
466
|
+
})
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
return new Table({
|
|
470
|
+
columnWidths: colWidths,
|
|
471
|
+
rows: [headerRow, ...dataRows]
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// μ¬μ© μμ: ν©μ΄ 100%κ° λλλ‘ μ§μ
|
|
476
|
+
createTableWithWidths(
|
|
477
|
+
["λͺ¨λΈ", "Input λΉμ©", "Output λΉμ©"],
|
|
478
|
+
[
|
|
479
|
+
["Claude 3 Haiku", "$0.25/1M", "$1.25/1M"],
|
|
480
|
+
["Claude 3 Sonnet", "$3.00/1M", "$15.00/1M"],
|
|
481
|
+
["Claude 3 Opus", "$15.00/1M", "$75.00/1M"]
|
|
482
|
+
],
|
|
483
|
+
[40, 30, 30] // 40% + 30% + 30% = 100%
|
|
484
|
+
)
|
|
485
|
+
```
|
|
486
|
+
|
|
275
487
|
## AWS Blog μ€νμΌ κ°μ΄λ
|
|
276
488
|
|
|
277
489
|
### μμ± μμΉ
|
|
@@ -305,3 +517,5 @@ new Paragraph({
|
|
|
305
517
|
νμ μμ‘΄μ±:
|
|
306
518
|
- **docx**: `npm install docx` (λ¬Έμ μμ±)
|
|
307
519
|
- **sharp**: `npm install sharp` (μ΄λ―Έμ§ μ²λ¦¬)
|
|
520
|
+
- **image-size**: `npm install image-size` (μ΄λ―Έμ§ ν¬κΈ° νμΈ)
|
|
521
|
+
- **@mermaid-js/mermaid-cli**: `npm install @mermaid-js/mermaid-cli` (λ€μ΄μ΄κ·Έλ¨ λ λλ§)
|
package/src/skills/pptx/SKILL.md
CHANGED
|
@@ -16,7 +16,6 @@ A user may ask you to create, edit, or analyze the contents of a .pptx file. A .
|
|
|
16
16
|
If you just need to read the text contents of a presentation, you should convert the document to markdown:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
# Convert document to markdown
|
|
20
19
|
python -m markitdown path-to-file.pptx
|
|
21
20
|
```
|
|
22
21
|
|
|
@@ -24,7 +23,9 @@ python -m markitdown path-to-file.pptx
|
|
|
24
23
|
You need raw XML access for: comments, speaker notes, slide layouts, animations, design elements, and complex formatting. For any of these features, you'll need to unpack a presentation and read its raw XML contents.
|
|
25
24
|
|
|
26
25
|
#### Unpacking a file
|
|
27
|
-
|
|
26
|
+
```bash
|
|
27
|
+
python scripts/ooxml/unpack.py <office_file> <output_dir>
|
|
28
|
+
```
|
|
28
29
|
|
|
29
30
|
#### Key file structures
|
|
30
31
|
* `ppt/presentation.xml` - Main presentation metadata and slide references
|
|
@@ -40,6 +41,36 @@ You need raw XML access for: comments, speaker notes, slide layouts, animations,
|
|
|
40
41
|
|
|
41
42
|
When creating a new PowerPoint presentation from scratch, use the **html2pptx** workflow to convert HTML slides to PowerPoint with accurate positioning.
|
|
42
43
|
|
|
44
|
+
### Layout Initialization (MANDATORY)
|
|
45
|
+
|
|
46
|
+
**CRITICAL**: Always define layout constants BEFORE creating slides. Different layouts have different dimensions:
|
|
47
|
+
|
|
48
|
+
| Layout | Width | Height | Use Case |
|
|
49
|
+
|--------|-------|--------|----------|
|
|
50
|
+
| `LAYOUT_16x9` | 10" | 5.625" | Standard widescreen |
|
|
51
|
+
| `LAYOUT_WIDE` | 13.33" | 7.5" | Extra wide presentations |
|
|
52
|
+
| `LAYOUT_4x3` | 10" | 7.5" | Legacy/square displays |
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
const LAYOUT_NAME = 'LAYOUT_WIDE';
|
|
56
|
+
const SLIDE = { width: 13.33, height: 7.5 };
|
|
57
|
+
const MARGIN = 0.5;
|
|
58
|
+
const CONTENT = {
|
|
59
|
+
width: SLIDE.width - (2 * MARGIN),
|
|
60
|
+
height: SLIDE.height - (2 * MARGIN)
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
let pptx = new PptxGenJS();
|
|
64
|
+
pptx.layout = LAYOUT_NAME;
|
|
65
|
+
|
|
66
|
+
slide.addText('Title', {
|
|
67
|
+
x: MARGIN,
|
|
68
|
+
y: MARGIN,
|
|
69
|
+
w: CONTENT.width,
|
|
70
|
+
h: 0.8
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
43
74
|
### Design Principles
|
|
44
75
|
|
|
45
76
|
**CRITICAL**: Before creating any presentation, analyze the content and choose appropriate design elements:
|
|
@@ -73,31 +104,62 @@ AWS λΈλλ μμ νλ νΈ, νμ€ μ¬λΌμ΄λ ꡬμ±, HTML ν
νλ¦Ώ μ
|
|
|
73
104
|
When editing slides in an existing PowerPoint presentation, you need to work with the raw Office Open XML (OOXML) format.
|
|
74
105
|
|
|
75
106
|
### Workflow
|
|
76
|
-
1. Unpack the presentation: `python ooxml/
|
|
107
|
+
1. Unpack the presentation: `python scripts/ooxml/unpack.py <office_file> <output_dir>`
|
|
77
108
|
2. Edit the XML files (primarily `ppt/slides/slide{N}.xml` and related files)
|
|
78
109
|
3. **CRITICAL**: Validate immediately after each edit
|
|
79
|
-
4. Pack the final presentation: `python ooxml/
|
|
110
|
+
4. Pack the final presentation: `python scripts/ooxml/pack.py <input_directory> <office_file>`
|
|
80
111
|
|
|
81
112
|
## Creating a new PowerPoint presentation **using a template**
|
|
82
113
|
|
|
83
114
|
When you need to create a presentation that follows an existing template's design:
|
|
84
115
|
|
|
116
|
+
### Template Workflow Scripts
|
|
117
|
+
|
|
118
|
+
All scripts are located in the `scripts/` directory of this skill.
|
|
119
|
+
|
|
120
|
+
| Script | Command | Purpose |
|
|
121
|
+
|--------|---------|---------|
|
|
122
|
+
| `thumbnail.py` | `python scripts/thumbnail.py template.pptx` | Generate slide thumbnails for visual inspection |
|
|
123
|
+
| `inventory.py` | `python scripts/inventory.py template.pptx -o inventory.json` | Extract all text elements with shape IDs |
|
|
124
|
+
| `replace.py` | `python scripts/replace.py template.pptx mapping.json -o output.pptx` | Replace text using JSON mapping |
|
|
125
|
+
| `rearrange.py` | `python scripts/rearrange.py template.pptx operations.json -o output.pptx` | Duplicate/delete/reorder slides |
|
|
126
|
+
|
|
85
127
|
### Workflow
|
|
128
|
+
|
|
86
129
|
1. **Extract template text AND create visual thumbnail grid**:
|
|
87
|
-
|
|
88
|
-
|
|
130
|
+
```bash
|
|
131
|
+
python -m markitdown template.pptx > template-content.md
|
|
132
|
+
python scripts/thumbnail.py template.pptx --output-dir ./thumbs
|
|
133
|
+
```
|
|
89
134
|
|
|
90
|
-
2. **
|
|
135
|
+
2. **Extract text inventory** (get shape IDs and text content):
|
|
136
|
+
```bash
|
|
137
|
+
python scripts/inventory.py template.pptx -o inventory.json
|
|
138
|
+
```
|
|
91
139
|
|
|
92
140
|
3. **Create presentation outline based on template inventory**
|
|
93
141
|
|
|
94
|
-
4. **Duplicate, reorder, and delete slides
|
|
95
|
-
|
|
96
|
-
|
|
142
|
+
4. **Duplicate, reorder, and delete slides**:
|
|
143
|
+
```bash
|
|
144
|
+
# Create operations.json:
|
|
145
|
+
# {"operations": [{"action": "duplicate", "slide": 1, "count": 3}, {"action": "delete", "slides": [5, 6]}]}
|
|
146
|
+
python scripts/rearrange.py template.pptx operations.json -o structured.pptx
|
|
147
|
+
```
|
|
97
148
|
|
|
98
|
-
|
|
149
|
+
5. **Generate replacement mapping** and save to JSON:
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"replacements": [
|
|
153
|
+
{"from": "Template Title", "to": "My Presentation"},
|
|
154
|
+
{"from": "Placeholder Text", "to": "Actual Content"}
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
```
|
|
99
158
|
|
|
100
|
-
|
|
159
|
+
6. **Apply replacements**:
|
|
160
|
+
```bash
|
|
161
|
+
python scripts/replace.py structured.pptx mapping.json -o final.pptx
|
|
162
|
+
```
|
|
101
163
|
|
|
102
164
|
## Dependencies
|
|
103
165
|
|
|
@@ -106,5 +168,5 @@ Required dependencies:
|
|
|
106
168
|
- **pptxgenjs**: `npm install -g pptxgenjs` (for creating presentations)
|
|
107
169
|
- **playwright**: `npm install -g playwright` (for HTML rendering)
|
|
108
170
|
- **sharp**: `npm install -g sharp` (for SVG rasterization)
|
|
109
|
-
- **LibreOffice**: For PDF conversion
|
|
110
|
-
- **Poppler**: For pdftoppm
|
|
171
|
+
- **LibreOffice**: For PDF conversion (thumbnail generation)
|
|
172
|
+
- **Poppler**: For pdftoppm (thumbnail generation)
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# HTML to PowerPoint Guide
|
|
2
|
+
|
|
3
|
+
Convert HTML slides to PowerPoint presentations with accurate positioning using the `html2pptx.js` library.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Creating HTML Slides](#creating-html-slides)
|
|
8
|
+
2. [Using the html2pptx Library](#using-the-html2pptx-library)
|
|
9
|
+
3. [Using PptxGenJS](#using-pptxgenjs)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Creating HTML Slides
|
|
14
|
+
|
|
15
|
+
Every HTML slide must include proper body dimensions:
|
|
16
|
+
|
|
17
|
+
### Layout Dimensions
|
|
18
|
+
|
|
19
|
+
- **16:9** (default): `width: 720pt; height: 405pt`
|
|
20
|
+
- **4:3**: `width: 720pt; height: 540pt`
|
|
21
|
+
- **16:10**: `width: 720pt; height: 450pt`
|
|
22
|
+
|
|
23
|
+
### Supported Elements
|
|
24
|
+
|
|
25
|
+
- `<p>`, `<h1>`-`<h6>` - Text with styling
|
|
26
|
+
- `<ul>`, `<ol>` - Lists (never use manual bullets β’, -, *)
|
|
27
|
+
- `<b>`, `<strong>` - Bold text (inline formatting)
|
|
28
|
+
- `<i>`, `<em>` - Italic text (inline formatting)
|
|
29
|
+
- `<u>` - Underlined text (inline formatting)
|
|
30
|
+
- `<span>` - Inline formatting with CSS styles (bold, italic, underline, color)
|
|
31
|
+
- `<br>` - Line breaks
|
|
32
|
+
- `<div>` with bg/border - Becomes shape
|
|
33
|
+
- `<img>` - Images
|
|
34
|
+
- `class="placeholder"` - Reserved space for charts (returns `{ id, x, y, w, h }`)
|
|
35
|
+
|
|
36
|
+
### Critical Text Rules
|
|
37
|
+
|
|
38
|
+
**ALL text MUST be inside `<p>`, `<h1>`-`<h6>`, `<ul>`, or `<ol>` tags:**
|
|
39
|
+
- β
Correct: `<div><p>Text here</p></div>`
|
|
40
|
+
- β Wrong: `<div>Text here</div>` - **Text will NOT appear in PowerPoint**
|
|
41
|
+
- β Wrong: `<span>Text</span>` - **Text will NOT appear in PowerPoint**
|
|
42
|
+
|
|
43
|
+
**NEVER use manual bullet symbols (β’, -, *, etc.)** - Use `<ul>` or `<ol>` lists instead
|
|
44
|
+
|
|
45
|
+
**ONLY use web-safe fonts:**
|
|
46
|
+
- β
Web-safe: `Arial`, `Helvetica`, `Times New Roman`, `Georgia`, `Courier New`, `Verdana`, `Tahoma`, `Trebuchet MS`, `Impact`
|
|
47
|
+
- β Wrong: `'Segoe UI'`, `'SF Pro'`, `'Roboto'`, custom fonts
|
|
48
|
+
|
|
49
|
+
### Styling
|
|
50
|
+
|
|
51
|
+
- Use `display: flex` on body to prevent margin collapse
|
|
52
|
+
- Use `margin` for spacing (padding included in size)
|
|
53
|
+
- Flexbox works - positions calculated from rendered layout
|
|
54
|
+
- Use hex colors with `#` prefix in CSS
|
|
55
|
+
|
|
56
|
+
### Shape Styling (DIV elements only)
|
|
57
|
+
|
|
58
|
+
**IMPORTANT: Backgrounds, borders, and shadows only work on `<div>` elements, NOT on text elements**
|
|
59
|
+
|
|
60
|
+
- **Backgrounds**: CSS `background` or `background-color` on `<div>` elements only
|
|
61
|
+
- **Borders**: CSS `border` on `<div>` elements converts to PowerPoint shape borders
|
|
62
|
+
- **Border radius**: CSS `border-radius` on `<div>` elements for rounded corners
|
|
63
|
+
- **Box shadows**: CSS `box-shadow` on `<div>` elements (outer shadows only)
|
|
64
|
+
|
|
65
|
+
### Icons & Gradients
|
|
66
|
+
|
|
67
|
+
- **CRITICAL: Never use CSS gradients** - They don't convert to PowerPoint
|
|
68
|
+
- **ALWAYS create gradient/icon PNGs FIRST using Sharp, then reference in HTML**
|
|
69
|
+
|
|
70
|
+
**Rasterizing Icons with Sharp:**
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
const sharp = require('sharp');
|
|
74
|
+
const { FaHome } = require('react-icons/fa');
|
|
75
|
+
|
|
76
|
+
async function rasterizeIconPng(IconComponent, color, size, filename) {
|
|
77
|
+
const svgString = ReactDOMServer.renderToStaticMarkup(
|
|
78
|
+
React.createElement(IconComponent, { color: `#${color}`, size: size })
|
|
79
|
+
);
|
|
80
|
+
await sharp(Buffer.from(svgString)).png().toFile(filename);
|
|
81
|
+
return filename;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Example HTML Slide
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<!DOCTYPE html>
|
|
89
|
+
<html>
|
|
90
|
+
<head>
|
|
91
|
+
<style>
|
|
92
|
+
html { background: #ffffff; }
|
|
93
|
+
body {
|
|
94
|
+
width: 720pt; height: 405pt; margin: 0; padding: 0;
|
|
95
|
+
background: #f5f5f5; font-family: Arial, sans-serif;
|
|
96
|
+
display: flex;
|
|
97
|
+
}
|
|
98
|
+
.content { margin: 30pt; padding: 40pt; background: #ffffff; border-radius: 8pt; }
|
|
99
|
+
h1 { color: #2d3748; font-size: 32pt; }
|
|
100
|
+
</style>
|
|
101
|
+
</head>
|
|
102
|
+
<body>
|
|
103
|
+
<div class="content">
|
|
104
|
+
<h1>Title</h1>
|
|
105
|
+
<ul>
|
|
106
|
+
<li><b>Item:</b> Description</li>
|
|
107
|
+
</ul>
|
|
108
|
+
<div id="chart" class="placeholder" style="width: 350pt; height: 200pt;"></div>
|
|
109
|
+
</div>
|
|
110
|
+
</body>
|
|
111
|
+
</html>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Using the html2pptx Library
|
|
115
|
+
|
|
116
|
+
### Basic Usage
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
const pptxgen = require('pptxgenjs');
|
|
120
|
+
const html2pptx = require('./html2pptx');
|
|
121
|
+
|
|
122
|
+
const pptx = new pptxgen();
|
|
123
|
+
pptx.layout = 'LAYOUT_16x9';
|
|
124
|
+
|
|
125
|
+
const { slide, placeholders } = await html2pptx('slide1.html', pptx);
|
|
126
|
+
|
|
127
|
+
if (placeholders.length > 0) {
|
|
128
|
+
slide.addChart(pptx.charts.LINE, chartData, placeholders[0]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await pptx.writeFile('output.pptx');
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### API Reference
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
await html2pptx(htmlFile, pres, options)
|
|
138
|
+
// Returns: { slide, placeholders: [{ id, x, y, w, h }] }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Using PptxGenJS
|
|
142
|
+
|
|
143
|
+
### β οΈ Critical Rules
|
|
144
|
+
|
|
145
|
+
**Colors - NEVER use `#` prefix:**
|
|
146
|
+
- β
Correct: `color: "FF0000"`, `fill: { color: "0066CC" }`
|
|
147
|
+
- β Wrong: `color: "#FF0000"` (breaks document)
|
|
148
|
+
|
|
149
|
+
### Adding Charts
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
slide.addChart(pptx.charts.BAR, [{
|
|
153
|
+
name: "Sales 2024",
|
|
154
|
+
labels: ["Q1", "Q2", "Q3", "Q4"],
|
|
155
|
+
values: [4500, 5500, 6200, 7100]
|
|
156
|
+
}], {
|
|
157
|
+
...placeholders[0],
|
|
158
|
+
barDir: 'col',
|
|
159
|
+
showTitle: true,
|
|
160
|
+
title: 'Quarterly Sales',
|
|
161
|
+
showCatAxisTitle: true,
|
|
162
|
+
catAxisTitle: 'Quarter',
|
|
163
|
+
showValAxisTitle: true,
|
|
164
|
+
valAxisTitle: 'Sales ($000s)',
|
|
165
|
+
chartColors: ["4472C4"]
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Adding Tables
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
slide.addTable([
|
|
173
|
+
["Header 1", "Header 2", "Header 3"],
|
|
174
|
+
["Row 1, Col 1", "Row 1, Col 2", "Row 1, Col 3"]
|
|
175
|
+
], {
|
|
176
|
+
x: 0.5, y: 1, w: 9, h: 3,
|
|
177
|
+
border: { pt: 1, color: "999999" },
|
|
178
|
+
fill: { color: "F1F1F1" }
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Adding Images
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
const aspectRatio = imgWidth / imgHeight;
|
|
186
|
+
const h = 3;
|
|
187
|
+
const w = h * aspectRatio;
|
|
188
|
+
const x = (10 - w) / 2;
|
|
189
|
+
|
|
190
|
+
slide.addImage({ path: "chart.png", x, y: 1.5, w, h });
|
|
191
|
+
```
|