opencode-sa-assistant 0.2.4 β†’ 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 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,12 +29,43 @@ 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
- ```bash
32
- # From the repository root
33
- cd packages/opencode-sa-assistant
34
- bun install
61
+ Add the plugin to your `opencode.json`:
62
+
63
+ ```json
64
+ {
65
+ "plugin": [
66
+ "opencode-sa-assistant"
67
+ ]
68
+ }
35
69
  ```
36
70
 
37
71
  ## Usage
@@ -73,14 +107,14 @@ wadd bezosμ—κ²Œ 고객 κ²½ν—˜ κ΄€μ μ—μ„œ 이 κΈ°λŠ₯을 평가해달라고 ν•΄
73
107
 
74
108
  ## Skills
75
109
 
76
- The plugin includes 4 skill modules:
110
+ The plugin includes 4 skill modules that are automatically installed to `~/.config/opencode/skills/`:
77
111
 
78
112
  - **guru**: Guru consultation patterns and philosophies
79
113
  - **mcp**: AWS MCP tools reference (search_documentation, read_documentation)
80
114
  - **docx**: AWS Blog-style document generation guidelines
81
115
  - **pptx**: SA presentation templates
82
116
 
83
- Skills are automatically loaded when needed.
117
+ Skills are installed globally to ensure they are available before any plugin caching occurs. Use `/skill guru` or `/skill mcp` to load them.
84
118
 
85
119
  ## Testing
86
120
 
@@ -92,7 +126,7 @@ bun test
92
126
  bun test src/__tests__/integration.test.ts
93
127
  ```
94
128
 
95
- **Test Coverage**: 65 tests, 212 assertions, 100% pass rate
129
+ **Test Coverage**: 80 tests, 234 assertions, 100% pass rate
96
130
 
97
131
  ## Development
98
132
 
@@ -103,17 +137,18 @@ packages/opencode-sa-assistant/
103
137
  β”œβ”€β”€ src/
104
138
  β”‚ β”œβ”€β”€ index.ts # Plugin entry point
105
139
  β”‚ β”œβ”€β”€ hooks/wadd-mode.ts # WADD keyword detection
106
- β”‚ β”œβ”€β”€ prompts/
107
- β”‚ β”‚ β”œβ”€β”€ orchestrator.ts # SA Orchestrator
108
- β”‚ β”‚ β”œβ”€β”€ gurus.ts # 4 Guru agents
109
- β”‚ β”‚ └── specialists.ts # 3 Specialist agents
110
- β”‚ β”œβ”€β”€ skills/
111
- β”‚ β”‚ β”œβ”€β”€ guru/SKILL.md
112
- β”‚ β”‚ β”œβ”€β”€ mcp/SKILL.md
113
- β”‚ β”‚ β”œβ”€β”€ docx/SKILL.md
114
- β”‚ β”‚ └── pptx/SKILL.md
115
- β”‚ └── __tests__/
116
- β”‚ └── *.test.ts # 65 tests
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
117
152
  β”œβ”€β”€ package.json
118
153
  β”œβ”€β”€ tsconfig.json
119
154
  └── bunfig.toml
@@ -147,7 +182,7 @@ The plugin integrates with AWS Documentation MCP for real-time information:
147
182
 
148
183
  ## License
149
184
 
150
- [Your License Here]
185
+ MIT
151
186
 
152
187
  ## Author
153
188
 
@@ -163,6 +198,23 @@ Contributions are welcome! Please ensure:
163
198
 
164
199
  ## Changelog
165
200
 
201
+ ### v0.2.5 (2026-02-05)
202
+
203
+ - Fix: Skills now installed to global location (`~/.config/opencode/skills/`)
204
+ - Resolves oh-my-opencode cache timing issue
205
+ - Skills are now available via `/skill` command immediately
206
+ - Test coverage: 80 tests, 234 assertions
207
+
208
+ ### v0.2.4 (2026-02-05)
209
+
210
+ - Added skill installation to `.opencode/skills/`
211
+ - Skills: guru, mcp, docx, pptx
212
+
213
+ ### v0.2.3 (2026-02-05)
214
+
215
+ - Aligned wadd hook with oh-my-opencode pattern
216
+ - Added `removeCodeBlocks()` for keyword detection
217
+
166
218
  ### v0.1.0 (2026-02-05)
167
219
 
168
220
  - Initial release
@@ -171,4 +223,3 @@ Contributions are welcome! Please ensure:
171
223
  - Guru_Mandate consultation system
172
224
  - Well-Architected Framework integration
173
225
  - AWS MCP tools support
174
- - 65 tests, 100% pass rate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-sa-assistant",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "description": "OpenCode plugin for AWS Solutions Architect assistant with multi-agent Guru system",
@@ -172,11 +172,12 @@ export function uninstallSAAgents(): string[] {
172
172
  export const SA_SKILL_NAMES = ["docx", "pptx", "mcp", "guru"] as const;
173
173
 
174
174
  /**
175
- * Get the skills directory path for project-level installation
176
- * Uses process.cwd() to find the project root
175
+ * Get the skills directory path for global installation
176
+ * Uses ~/.config/opencode/skills/ to ensure skills are available
177
+ * before any plugin loads (solving the oh-my-opencode cache timing issue)
177
178
  */
178
179
  function getSkillsDir(): string {
179
- return join(process.cwd(), ".opencode", "skills");
180
+ return join(homedir(), ".config", "opencode", "skills");
180
181
  }
181
182
 
182
183
  /**
@@ -195,9 +196,7 @@ function getSourceSkillsDir(): string {
195
196
  }
196
197
 
197
198
  /**
198
- * Install SA skills to .opencode/skills/
199
- * Creates the directory structure if it doesn't exist.
200
- * Only writes files if they don't exist (preserves user modifications).
199
+ * Install SA skills to ~/.config/opencode/skills/ (global location)
201
200
  */
202
201
  export function installSASkills(): { installed: string[]; skipped: string[]; errors: string[] } {
203
202
  const skillsDir = getSkillsDir();
@@ -243,7 +242,7 @@ export function installSASkills(): { installed: string[]; skipped: string[]; err
243
242
  }
244
243
 
245
244
  /**
246
- * Uninstall SA skills (remove SKILL.md files and directories)
245
+ * Uninstall SA skills from ~/.config/opencode/skills/
247
246
  */
248
247
  export function uninstallSASkills(): string[] {
249
248
  const skillsDir = getSkillsDir();
@@ -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` (λ‹€μ΄μ–΄κ·Έλž¨ λ Œλ”λ§)
@@ -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
- `python ooxml/scripts/unpack.py <office_file> <output_dir>`
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/scripts/unpack.py <office_file> <output_dir>`
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/scripts/pack.py <input_directory> <office_file>`
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
- * Extract text: `python -m markitdown template.pptx > template-content.md`
88
- * Create thumbnail grids: `python scripts/thumbnail.py template.pptx`
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. **Analyze template and save inventory to a file**
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 using `rearrange.py`**
95
-
96
- 5. **Extract ALL text using the `inventory.py` script**
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
- 6. **Generate replacement text and save the data to a JSON file**
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
- 7. **Apply replacements using the `replace.py` script**
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
+ ```