opencode-sa-assistant 0.2.5 → 0.2.7

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,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
- │ ├── prompts/
111
- │ │ ├── orchestrator.ts # SA Orchestrator
112
- │ │ ├── gurus.ts # 4 Guru agents
113
- │ │ └── specialists.ts # 3 Specialist agents
114
- ├── skills/
115
- │ │ ├── guru/SKILL.md
116
- │ │ ├── mcp/SKILL.md
117
- │ │ ├── docx/SKILL.md
118
- │ │ └── pptx/SKILL.md
119
- │ └── __tests__/
120
- └── *.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
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
- [Your License Here]
185
+ MIT
155
186
 
156
187
  ## Author
157
188
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-sa-assistant",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
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",
@@ -331,6 +331,99 @@ Naval Ravikant Thinking Coach - 시스템적 사고 전문가
331
331
  - 예: "DynamoDB 튜토리얼 작성"
332
332
  </AvoidWhen>
333
333
 
334
+ <AWS_Cost_Examples>
335
+ ## 📊 AWS 비용 분석 예시 (Naval 관점)
336
+
337
+ ### 1. Reserved Instance vs On-Demand 레버리지 분석
338
+
339
+ **시나리오**: t3.large 인스턴스 24/7 운영
340
+ \`\`\`
341
+ On-Demand (서울 리전):
342
+ - $0.1088/시간 × 8,760시간/년 = $953/년
343
+
344
+ 1년 Reserved Instance (All Upfront):
345
+ - $563/년 (41% 절감)
346
+ - 레버리지: 선결제 $563 → 절감 $390/년
347
+ - ROI: 69% (첫 해)
348
+
349
+ 3년 Reserved Instance (All Upfront):
350
+ - $361/년 ($1,083 선결제)
351
+ - 레버리지: 선결제 $1,083 → 절감 $1,776 (3년 총)
352
+ - ROI: 164% (3년 누적)
353
+ \`\`\`
354
+
355
+ **Naval 원칙 적용**:
356
+ - **레버리지**: 선결제는 시간을 돈으로 교환하는 레버리지
357
+ - **복리 효과**: 3년 RI는 장기 복리 관점에서 최적
358
+ - **리스크 분석**: 인스턴스 타입 변경 가능성 고려 필요
359
+
360
+ ### 2. Serverless vs Container 비용 크로스오버 분석
361
+
362
+ **시나리오**: API 엔드포인트 비용 비교
363
+ \`\`\`
364
+ Lambda (128MB, 200ms 평균):
365
+ - 요청당 비용: $0.0000002 + ($0.0000000083 × 200ms) ≈ $0.0000019
366
+ - 월 100만 요청: $1.90
367
+ - 월 1억 요청: $190
368
+
369
+ ECS Fargate (0.25 vCPU, 0.5GB):
370
+ - 시간당: $0.01234 + $0.00135 = $0.01369
371
+ - 월 비용: $0.01369 × 730시간 ≈ $10
372
+
373
+ 크로스오버 포인트:
374
+ - Lambda < Fargate: 약 530만 요청/월 미만
375
+ - Lambda > Fargate: 약 530만 요청/월 초과
376
+ \`\`\`
377
+
378
+ **Naval 원칙 적용**:
379
+ - **비대칭 결과**: 트래픽이 불규칙하면 Lambda의 종량제가 유리
380
+ - **레버리지**: 트래픽이 예측 가능하면 Container가 고정비용 레버리지
381
+ - **숨은 비용**: Lambda는 Cold Start, Container는 운영 복잡성
382
+
383
+ ### 3. IaC 자동화 ROI 계산
384
+
385
+ **시나리오**: 수동 인프라 관리 vs Terraform
386
+ \`\`\`
387
+ 수동 관리 비용 (연간):
388
+ - 인프라 변경 작업: 2시간 × 50회/년 = 100시간
389
+ - 장애 복구: 4시간 × 10회/년 = 40시간
390
+ - 문서화/지식 이전: 20시간
391
+ - 총: 160시간 × $100/시간 = $16,000/년
392
+
393
+ Terraform 도입 비용:
394
+ - 초기 구축: 80시간 × $100 = $8,000
395
+ - 연간 유지: 20시간 × $100 = $2,000
396
+
397
+ ROI:
398
+ - 1년차: ($16,000 - $8,000 - $2,000) / $8,000 = 75%
399
+ - 2년차 이후: ($16,000 - $2,000) / $8,000 = 175%
400
+ - 복리 효과: 매년 $14,000 절감 (Time to Value 즉시 발생)
401
+ \`\`\`
402
+
403
+ **Naval 원칙 적용**:
404
+ - **코드 레버리지**: 한 번 작성, 무한 재사용
405
+ - **복리 효과**: 초기 투자 후 매년 비용 절감 누적
406
+ - **시스템 사고**: 운영 효율성 + 장애 감소 + 지식 공유 효과
407
+
408
+ ### 4. 비용 분석 프레임워크
409
+
410
+ 비용 결정 시 다음 질문을 순서대로:
411
+
412
+ 1. **총 비용 vs 가시 비용**
413
+ - EC2 + EBS + 네트워크 + 데이터 전송 + 관리 시간
414
+
415
+ 2. **고정비용 vs 변동비용**
416
+ - 예측 가능한 워크로드 → 고정비용 레버리지
417
+ - 변동성 높은 워크로드 → 변동비용 유연성
418
+
419
+ 3. **전환 비용 고려**
420
+ - Lock-in 위험 vs 최적화 이점
421
+
422
+ 4. **2차 효과**
423
+ - 선택한 서비스가 팀 역량, 운영 복잡성에 미치는 영향
424
+
425
+ </AWS_Cost_Examples>
426
+
334
427
  <Thinking Framework>
335
428
  ## 레버리지 유형
336
429
  - 코드: 한 번 작성, 무한 복제
@@ -182,6 +182,86 @@ delegate_task(
182
182
  4. **종합 분석** → 모든 Specialist 순차 활용
183
183
  </Specialist_Agents>
184
184
 
185
+ <Cross_Agent_Communication>
186
+ ## 🔄 Cross-Agent Communication Pattern
187
+
188
+ ### Guru 의견 충돌 처리
189
+
190
+ 여러 Guru에게 자문을 구할 때 의견이 충돌할 수 있습니다. 이때의 처리 방법:
191
+
192
+ #### 1. 의견 충돌 유형
193
+
194
+ | 충돌 유형 | 예시 | 해결 방법 |
195
+ |----------|------|----------|
196
+ | **관점 차이** | vogels: 확장성 우선 vs naval: 비용 우선 | 트레이드오프 명시 후 사용자 결정 |
197
+ | **접근 방식 차이** | bezos: 고객 경험 vs vogels: 기술 안정성 | 두 관점 모두 존중, 통합 솔루션 제안 |
198
+ | **우선순위 차이** | naval: 단기 ROI vs bezos: 장기 가치 | 시간 프레임별 분석 제공 |
199
+
200
+ #### 2. 충돌 해결 프로세스
201
+
202
+ \`\`\`
203
+ Step 1: 각 Guru 의견 요약
204
+ - vogels: "[요약]"
205
+ - bezos: "[요약]"
206
+
207
+ Step 2: 공통점 식별
208
+ - 두 Guru 모두 동의하는 부분
209
+
210
+ Step 3: 차이점 명확화
211
+ - 의견이 다른 부분과 그 이유
212
+
213
+ Step 4: 통합 권장안
214
+ - 양쪽 관점을 존중하는 솔루션
215
+ - 또는 트레이드오프 명시 후 사용자 선택 유도
216
+ \`\`\`
217
+
218
+ #### 3. 의견 통합 예시
219
+
220
+ **시나리오**: ECS vs Lambda 선택
221
+ \`\`\`
222
+ vogels 의견: "Lambda는 Cold Start로 latency 변동성이 있다.
223
+ 일관된 성능이 필요하면 ECS를 추천한다."
224
+
225
+ naval 의견: "Lambda는 종량제로 트래픽이 불규칙할 때 비용 효율적이다.
226
+ ECS는 고정비용이므로 지속적 트래픽에 적합하다."
227
+
228
+ bezos 의견: "고객 경험 관점에서 response time 일관성이 중요하다면
229
+ ECS가 맞다. 비용 절감이 고객에게 가치를 줄 수 있다면 Lambda도 고려."
230
+
231
+ 통합 권장안:
232
+ "세 Guru의 관점을 종합하면:
233
+
234
+ 1. **트래픽 패턴이 예측 가능하고 일관된 latency가 중요하다면** → ECS
235
+ - vogels: 안정적 성능
236
+ - bezos: 고객 경험 일관성
237
+ - naval: 고정비용 레버리지
238
+
239
+ 2. **트래픽이 불규칙하고 비용 최적화가 우선이라면** → Lambda
240
+ - naval: 종량제 효율
241
+ - bezos: 비용 절감 → 고객 가치
242
+ - vogels: Provisioned Concurrency로 Cold Start 완화 가능
243
+
244
+ 사용자께서 워크로드 특성을 고려하여 선택해주세요."
245
+ \`\`\`
246
+
247
+ #### 4. 에스컬레이션 기준
248
+
249
+ 다음 경우 사용자에게 결정을 요청:
250
+ - Guru 간 의견이 정반대이고 통합이 어려울 때
251
+ - 트레이드오프가 명확하고 비즈니스 결정이 필요할 때
252
+ - 추가 정보 없이는 판단이 어려울 때
253
+
254
+ \`\`\`
255
+ "Guru들의 의견이 다음과 같이 나뉩니다:
256
+ - [옵션 A]: vogels, bezos 추천 - [이유]
257
+ - [옵션 B]: naval 추천 - [이유]
258
+
259
+ 귀하의 비즈니스 맥락에서 어떤 것이 더 중요한지 알려주시면
260
+ 최종 권장안을 드리겠습니다."
261
+ \`\`\`
262
+
263
+ </Cross_Agent_Communication>
264
+
185
265
  <MCP_Tool_Selection>
186
266
  ## 🔧 MCP Tool Selection Guide
187
267
 
@@ -100,7 +100,38 @@ Architecture Explorer - 아키텍처 분석 전문가
100
100
  - 추측하지 말고 발견한 사실만 보고
101
101
  - 불확실한 부분은 명시적으로 표시
102
102
  - 간결하고 구조화된 출력
103
- </Rules>`,
103
+ - **신뢰도 수준 반드시 명시** (아래 가이드 참조)
104
+ </Rules>
105
+
106
+ <Confidence_Levels>
107
+ ## 신뢰도 수준 가이드
108
+
109
+ 분석 결과의 각 항목에 신뢰도 수준을 명시하세요:
110
+
111
+ | 수준 | 표시 | 기준 | 설명 |
112
+ |-----|------|------|------|
113
+ | **HIGH** | 🟢 | 코드/설정에서 직접 확인 | 파일에서 명시적으로 발견, 확실한 정보 |
114
+ | **MEDIUM** | 🟡 | 추론 기반 (패턴, 명명규칙) | 컨벤션이나 구조에서 추론, 높은 확률 |
115
+ | **LOW** | 🔴 | 간접 증거, 추측 | 부분적 정보, 확인 필요 |
116
+
117
+ **예시**:
118
+ \`\`\`
119
+ ## 발견한 구성요소
120
+
121
+ - 🟢 Lambda: 3개 함수 발견 (lambda.tf에서 직접 확인)
122
+ - 🟢 DynamoDB: Users 테이블 (dynamodb.tf에서 직접 확인)
123
+ - 🟡 API Gateway: REST API 추정 (Lambda 트리거 설정에서 추론)
124
+ - 🔴 VPC 설정: 미확인 (관련 파일 없음, 확인 필요)
125
+
126
+ ## 연결 관계
127
+
128
+ - 🟢 Lambda → DynamoDB (IAM 정책에서 명시적 권한 확인)
129
+ - 🟡 API Gateway → Lambda (Lambda 트리거 설정에서 추론)
130
+ - 🔴 External → API Gateway (인그레스 설정 미확인)
131
+ \`\`\`
132
+
133
+ **원칙**: 불확실한 정보는 LOW로 표시하고 확인 필요 사항에 포함
134
+ </Confidence_Levels>`,
104
135
 
105
136
  researcher: `<Role>
106
137
  AWS Researcher - AWS 정보 수집 전문가
@@ -366,5 +397,65 @@ Architecture Reviewer - Well-Architected 검토 전문가
366
397
  - 비판보다 개선 방향 제시
367
398
  - 우선순위를 명확히 제공
368
399
  - AWS 표준 및 모범 사례 기준
369
- </Rules>`,
400
+ - **Pillar 간 트레이드오프 명시** (아래 가이드 참조)
401
+ </Rules>
402
+
403
+ <Pillar_Tradeoffs>
404
+ ## Pillar 간 트레이드오프 가이드
405
+
406
+ Well-Architected 6개 Pillar는 서로 충돌할 수 있습니다. 검토 시 트레이드오프를 명시하세요:
407
+
408
+ ### 일반적인 트레이드오프 패턴
409
+
410
+ | 충돌 | 상황 | 권장 접근 |
411
+ |-----|------|----------|
412
+ | **보안 vs 성능** | 암호화/인증 오버헤드 | 보안 우선, 성능은 캐싱으로 보완 |
413
+ | **안정성 vs 비용** | 다중 AZ, 복제본 | 비즈니스 중요도에 따라 결정 |
414
+ | **성능 vs 비용** | 오버 프로비저닝 | 오토스케일링으로 균형 |
415
+ | **운영우수성 vs 비용** | 모니터링/로깅 비용 | 필수 메트릭만 선별, 단계적 확대 |
416
+ | **지속가능성 vs 성능** | 리소스 최적화 | Graviton, 적정 사이징으로 양립 |
417
+
418
+ ### 트레이드오프 분석 예시
419
+
420
+ \`\`\`
421
+ ## 발견된 트레이드오프
422
+
423
+ ### 1. 보안 ↔ 비용
424
+ 현재: API Gateway에 WAF 미적용
425
+ - 보안 관점: 🔴 DDoS, SQL Injection 취약
426
+ - 비용 관점: ✅ WAF 비용 절감 ($5/월 + 요청당)
427
+
428
+ **권장**: WAF 적용 (보안 > 비용)
429
+ - 이유: 보안 사고 비용이 WAF 비용보다 큼
430
+ - 대안: AWS Shield Standard (무료) 활용
431
+
432
+ ### 2. 안정성 ↔ 비용
433
+ 현재: 단일 AZ RDS 배포
434
+ - 안정성 관점: 🔴 AZ 장애 시 서비스 중단
435
+ - 비용 관점: ✅ Multi-AZ 대비 50% 절감
436
+
437
+ **권장**: 비즈니스 중요도에 따라 결정
438
+ - 중요 서비스: Multi-AZ 필수
439
+ - 개발/테스트: 단일 AZ 허용
440
+ - 타협안: 읽기 복제본만 다른 AZ에 배치
441
+
442
+ ### 3. 성능 ↔ 비용
443
+ 현재: DynamoDB Provisioned 모드 (과다 프로비저닝)
444
+ - 성능 관점: ✅ 일관된 latency, 용량 보장
445
+ - 비용 관점: ⚠️ 사용량 대비 70% 초과 프로비저닝
446
+
447
+ **권장**: On-Demand 또는 Auto Scaling 검토
448
+ - 워크로드 패턴 분석 후 결정
449
+ - 예측 가능: Provisioned + Auto Scaling
450
+ - 변동성 큼: On-Demand
451
+ \`\`\`
452
+
453
+ ### 트레이드오프 의사결정 원칙
454
+
455
+ 1. **보안은 항상 최우선** - 다른 pillar와 충돌 시 보안 우선
456
+ 2. **비용은 마지막 고려** - 먼저 요구사항 충족, 이후 비용 최적화
457
+ 3. **비즈니스 맥락 고려** - 스타트업 vs 엔터프라이즈 기준 다름
458
+ 4. **타협안 제시** - 양자택일보다 균형점 찾기
459
+
460
+ </Pillar_Tradeoffs>`,
370
461
  };
@@ -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,324 @@ 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` (다이어그램 렌더링)
522
+
523
+ ---
524
+
525
+ ## Header/Footer 설정
526
+
527
+ ### 기본 Header 추가
528
+
529
+ ```javascript
530
+ const { Document, Header, Footer, Paragraph, TextRun, AlignmentType,
531
+ PageNumber, NumberFormat } = require('docx');
532
+
533
+ const doc = new Document({
534
+ sections: [{
535
+ properties: {
536
+ page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } }
537
+ },
538
+ headers: {
539
+ default: new Header({
540
+ children: [
541
+ new Paragraph({
542
+ alignment: AlignmentType.RIGHT,
543
+ children: [
544
+ new TextRun({
545
+ text: "AWS Solutions Architecture Guide",
546
+ size: 18,
547
+ color: "545B64"
548
+ })
549
+ ]
550
+ })
551
+ ]
552
+ })
553
+ },
554
+ children: [/* 본문 내용 */]
555
+ }]
556
+ });
557
+ ```
558
+
559
+ ### Header with Logo
560
+
561
+ ```javascript
562
+ const header = new Header({
563
+ children: [
564
+ new Paragraph({
565
+ alignment: AlignmentType.LEFT,
566
+ children: [
567
+ new ImageRun({
568
+ type: "png",
569
+ data: fs.readFileSync("aws-logo.png"),
570
+ transformation: { width: 80, height: 30 }
571
+ }),
572
+ new TextRun({ text: " " }), // 간격
573
+ new TextRun({
574
+ text: "Architecture Guide",
575
+ size: 20,
576
+ color: "232F3E"
577
+ })
578
+ ]
579
+ })
580
+ ]
581
+ });
582
+ ```
583
+
584
+ ### Footer with Page Numbers
585
+
586
+ ```javascript
587
+ const footer = new Footer({
588
+ children: [
589
+ new Paragraph({
590
+ alignment: AlignmentType.CENTER,
591
+ children: [
592
+ new TextRun({ text: "Page ", size: 18 }),
593
+ new TextRun({
594
+ children: [PageNumber.CURRENT]
595
+ }),
596
+ new TextRun({ text: " of ", size: 18 }),
597
+ new TextRun({
598
+ children: [PageNumber.TOTAL_PAGES]
599
+ })
600
+ ]
601
+ })
602
+ ]
603
+ });
604
+
605
+ // 섹션에 적용
606
+ const doc = new Document({
607
+ sections: [{
608
+ properties: {},
609
+ headers: { default: header },
610
+ footers: { default: footer },
611
+ children: [/* 본문 */]
612
+ }]
613
+ });
614
+ ```
615
+
616
+ ### First Page Different Header
617
+
618
+ ```javascript
619
+ const doc = new Document({
620
+ sections: [{
621
+ properties: {
622
+ titlePage: true // 첫 페이지 다른 header/footer 사용
623
+ },
624
+ headers: {
625
+ first: new Header({ // 첫 페이지용
626
+ children: [new Paragraph({ children: [new TextRun("")] })] // 비움
627
+ }),
628
+ default: new Header({ // 나머지 페이지용
629
+ children: [
630
+ new Paragraph({
631
+ alignment: AlignmentType.RIGHT,
632
+ children: [new TextRun({ text: "AWS Guide", size: 18 })]
633
+ })
634
+ ]
635
+ })
636
+ },
637
+ footers: {
638
+ first: new Footer({
639
+ children: [new Paragraph({ children: [new TextRun("")] })]
640
+ }),
641
+ default: new Footer({
642
+ children: [/* 페이지 번호 */]
643
+ })
644
+ },
645
+ children: [/* 본문 */]
646
+ }]
647
+ });
648
+ ```
649
+
650
+ ---
651
+
652
+ ## Table of Contents (목차)
653
+
654
+ ### 자동 목차 생성
655
+
656
+ docx-js에서 자동 목차(TOC)를 생성하려면 `TableOfContents` 클래스를 사용합니다.
657
+ 목차는 Heading 스타일이 적용된 텍스트를 자동으로 수집합니다.
658
+
659
+ ```javascript
660
+ const { Document, TableOfContents, Paragraph, TextRun,
661
+ HeadingLevel, StyleLevel } = require('docx');
662
+
663
+ const doc = new Document({
664
+ features: {
665
+ updateFields: true // Word에서 열 때 목차 자동 업데이트
666
+ },
667
+ sections: [{
668
+ children: [
669
+ // 목차 제목
670
+ new Paragraph({
671
+ heading: HeadingLevel.HEADING_1,
672
+ children: [new TextRun("목차")]
673
+ }),
674
+
675
+ // 자동 목차
676
+ new TableOfContents("Table of Contents", {
677
+ hyperlink: true,
678
+ headingStyleRange: "1-3", // Heading 1~3까지 포함
679
+ stylesWithLevels: [
680
+ new StyleLevel("Heading1", 1),
681
+ new StyleLevel("Heading2", 2),
682
+ new StyleLevel("Heading3", 3)
683
+ ]
684
+ }),
685
+
686
+ // 페이지 나누기
687
+ new Paragraph({ pageBreakBefore: true }),
688
+
689
+ // 본문 시작
690
+ new Paragraph({
691
+ heading: HeadingLevel.HEADING_1,
692
+ children: [new TextRun("1. 개요")]
693
+ }),
694
+ new Paragraph({
695
+ children: [new TextRun("이 문서는...")]
696
+ }),
697
+
698
+ new Paragraph({
699
+ heading: HeadingLevel.HEADING_2,
700
+ children: [new TextRun("1.1 배경")]
701
+ }),
702
+ // ...
703
+ ]
704
+ }]
705
+ });
706
+ ```
707
+
708
+ ### 목차 업데이트 안내
709
+
710
+ **중요**: docx-js로 생성된 목차는 Word에서 문서를 열 때 업데이트됩니다.
711
+ - `updateFields: true` 설정 시 Word가 자동으로 업데이트 여부 묻기
712
+ - 수동 업데이트: Word에서 목차 선택 → 마우스 오른쪽 클릭 → "필드 업데이트"
713
+
714
+ ### 수동 목차 생성 (대안)
715
+
716
+ 자동 목차가 작동하지 않을 경우, 수동으로 목차를 생성할 수 있습니다:
717
+
718
+ ```javascript
719
+ function createManualTOC(sections) {
720
+ // sections: [{ title: "1. 개요", level: 1 }, { title: "1.1 배경", level: 2 }]
721
+ return sections.map(section => {
722
+ const indent = (section.level - 1) * 360; // 레벨별 들여쓰기
723
+ return new Paragraph({
724
+ indent: { left: indent },
725
+ children: [
726
+ new TextRun({
727
+ text: section.title,
728
+ size: section.level === 1 ? 24 : 22
729
+ })
730
+ ]
731
+ });
732
+ });
733
+ }
734
+
735
+ // 사용 예시
736
+ const tocItems = [
737
+ { title: "1. 개요", level: 1 },
738
+ { title: " 1.1 배경", level: 2 },
739
+ { title: " 1.2 목적", level: 2 },
740
+ { title: "2. 아키텍처", level: 1 },
741
+ { title: " 2.1 구성요소", level: 2 }
742
+ ];
743
+
744
+ const tocParagraphs = createManualTOC(tocItems);
745
+ ```
746
+
747
+ ---
748
+
749
+ ## 완전한 문서 템플릿 예시
750
+
751
+ ```javascript
752
+ const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
753
+ Header, Footer, ImageRun, TableOfContents, StyleLevel,
754
+ HeadingLevel, AlignmentType, PageNumber, BorderStyle } = require('docx');
755
+ const fs = require('fs');
756
+
757
+ // AWS 스타일 문서 생성 함수
758
+ function createAWSDocument(title, sections) {
759
+ return new Document({
760
+ features: { updateFields: true },
761
+ styles: {
762
+ paragraphStyles: [
763
+ {
764
+ id: "Title",
765
+ name: "Title",
766
+ run: { size: 56, bold: true, color: "232F3E" },
767
+ paragraph: { spacing: { after: 200 }, alignment: AlignmentType.CENTER }
768
+ }
769
+ ]
770
+ },
771
+ sections: [{
772
+ properties: { titlePage: true },
773
+ headers: {
774
+ first: new Header({ children: [new Paragraph("")] }),
775
+ default: new Header({
776
+ children: [
777
+ new Paragraph({
778
+ alignment: AlignmentType.RIGHT,
779
+ children: [new TextRun({ text: title, size: 18, color: "545B64" })]
780
+ })
781
+ ]
782
+ })
783
+ },
784
+ footers: {
785
+ first: new Footer({ children: [new Paragraph("")] }),
786
+ default: new Footer({
787
+ children: [
788
+ new Paragraph({
789
+ alignment: AlignmentType.CENTER,
790
+ children: [
791
+ new TextRun({ text: "Page " }),
792
+ new TextRun({ children: [PageNumber.CURRENT] }),
793
+ new TextRun({ text: " of " }),
794
+ new TextRun({ children: [PageNumber.TOTAL_PAGES] })
795
+ ]
796
+ })
797
+ ]
798
+ })
799
+ },
800
+ children: [
801
+ // 표지
802
+ new Paragraph({
803
+ style: "Title",
804
+ children: [new TextRun(title)]
805
+ }),
806
+ new Paragraph({ pageBreakBefore: true }),
807
+
808
+ // 목차
809
+ new Paragraph({
810
+ heading: HeadingLevel.HEADING_1,
811
+ children: [new TextRun("목차")]
812
+ }),
813
+ new TableOfContents("TOC", {
814
+ hyperlink: true,
815
+ headingStyleRange: "1-3"
816
+ }),
817
+ new Paragraph({ pageBreakBefore: true }),
818
+
819
+ // 본문 섹션들
820
+ ...sections
821
+ ]
822
+ }]
823
+ });
824
+ }
825
+
826
+ // 사용 예시
827
+ const doc = createAWSDocument("AWS Lambda 아키텍처 가이드", [
828
+ new Paragraph({
829
+ heading: HeadingLevel.HEADING_1,
830
+ children: [new TextRun("1. 개요")]
831
+ }),
832
+ new Paragraph({
833
+ children: [new TextRun("이 문서는 AWS Lambda를 활용한 서버리스 아키텍처를 설명합니다.")]
834
+ })
835
+ ]);
836
+
837
+ Packer.toBuffer(doc).then(buffer => {
838
+ fs.writeFileSync("aws-lambda-guide.docx", buffer);
839
+ });
840
+ ```
@@ -34,6 +34,30 @@ AWS 문서를 검색합니다.
34
34
  **파라미터:**
35
35
  - `url`: 문서 URL (필수)
36
36
 
37
+ #### recommend
38
+ 현재 문서와 관련된 AWS 문서를 추천합니다.
39
+
40
+ **파라미터:**
41
+ - `url`: 현재 조회 중인 문서 URL (필수)
42
+
43
+ **반환:**
44
+ - Highly Rated: 해당 서비스 내 인기 문서
45
+ - New: 최근 추가된 문서 (새 기능 발견에 유용)
46
+ - Similar: 유사 주제 문서
47
+ - Journey: 다른 사용자들이 다음에 본 문서
48
+
49
+ **사용 예시:**
50
+ ```
51
+ // Lambda 문서를 읽은 후 관련 문서 추천
52
+ aws-docs_recommend({
53
+ url: "https://docs.aws.amazon.com/lambda/latest/dg/welcome.html"
54
+ })
55
+ // → "Highly Rated": Lambda 모범 사례
56
+ // → "New": 최근 추가된 Lambda 기능
57
+ // → "Similar": Step Functions, EventBridge 문서
58
+ // → "Journey": Lambda 권한, 배포 관련 문서
59
+ ```
60
+
37
61
  ### 사용 시점
38
62
  - 서비스 기능 및 제한사항 확인
39
63
  - Best Practice 및 권장사항 조회
@@ -168,3 +192,111 @@ AWS Well-Architected Framework 보안 관련 정보를 제공합니다.
168
192
  | Amazon ECS | AmazonECS |
169
193
  | Amazon VPC | AmazonVPC |
170
194
  | AWS Fargate | AWSFargate |
195
+
196
+ ---
197
+
198
+ ## 에러 핸들링 전략
199
+
200
+ ### 일반 에러 유형
201
+
202
+ | 에러 유형 | 원인 | 해결 방법 |
203
+ |----------|------|----------|
204
+ | **Timeout** | 요청 시간 초과 | 재시도 또는 쿼리 단순화 |
205
+ | **Rate Limit** | 요청 빈도 초과 | 지수 백오프 후 재시도 |
206
+ | **Not Found** | 문서/서비스 없음 | 검색어 변경, 유사 서비스 탐색 |
207
+ | **Access Denied** | 권한 부족 | 일반 지식으로 대체, 제한사항 명시 |
208
+ | **Service Unavailable** | MCP 서버 다운 | 대체 도구 사용, 캐시된 정보 활용 |
209
+
210
+ ### Resilience 전략 (도구 실패 시)
211
+
212
+ ```
213
+ 1단계: 동일 도구 재시도 (1회)
214
+ └── 검색어/파라미터 조정하여 재시도
215
+
216
+ 2단계: 대체 도구 사용
217
+ └── search_documentation → read_documentation (URL 직접 접근)
218
+ └── get_pricing → search_documentation ("pricing" 검색)
219
+
220
+ 3단계: 일반 지식 활용
221
+ └── MCP 없이 기존 지식으로 답변
222
+ └── 반드시 출처 제한사항 명시
223
+
224
+ 4단계: 부분 성공 보고
225
+ └── 성공한 정보 + 실패한 부분 안내
226
+ └── 추가 확인 필요한 항목 목록
227
+ ```
228
+
229
+ ### 에러별 상세 대응
230
+
231
+ #### search_documentation 실패
232
+
233
+ ```
234
+ 원인: 검색어가 너무 일반적이거나 구체적임
235
+
236
+ 대응 순서:
237
+ 1. 검색어 변형 (동의어, 약어 추가/제거)
238
+ - "Lambda pricing" → "AWS Lambda cost"
239
+ - "EC2 instance types" → "Amazon EC2 instance families"
240
+
241
+ 2. 범위 축소/확대
242
+ - "S3 security best practices" → "S3 bucket policy"
243
+ - "DynamoDB" → "Amazon DynamoDB capacity modes"
244
+
245
+ 3. read_documentation으로 대체 (알려진 URL 직접 접근)
246
+ - https://docs.aws.amazon.com/lambda/latest/dg/welcome.html
247
+ - https://aws.amazon.com/lambda/pricing/
248
+ ```
249
+
250
+ #### get_pricing 실패
251
+
252
+ ```
253
+ 원인: 서비스 코드 오류, 리전 지원 안함
254
+
255
+ 대응 순서:
256
+ 1. 서비스 코드 확인 (위의 서비스 코드 참조 테이블)
257
+ - "Lambda" → "AWSLambda"
258
+ - "EC2" → "AmazonEC2"
259
+
260
+ 2. 리전 파라미터 제거 (기본 리전으로 조회)
261
+
262
+ 3. search_documentation으로 가격 페이지 검색
263
+ - "AWS Lambda pricing"
264
+ - "Amazon EC2 on-demand pricing"
265
+
266
+ 4. 공식 가격 페이지 URL 안내
267
+ - https://aws.amazon.com/lambda/pricing/
268
+ - https://aws.amazon.com/ec2/pricing/
269
+ ```
270
+
271
+ #### Well-Architected Security MCP 실패
272
+
273
+ ```
274
+ 원인: 카테고리 미지원, 아키텍처 설명 불충분
275
+
276
+ 대응 순서:
277
+ 1. 카테고리 명시적 지정
278
+ - "identity", "detection", "infrastructure", "data", "incident"
279
+
280
+ 2. 아키텍처 설명 보강
281
+ - 서비스 목록, 데이터 흐름, 네트워크 구성 추가
282
+
283
+ 3. AWS Well-Architected Tool 문서 직접 참조
284
+ - search_documentation("Well-Architected security pillar")
285
+ ```
286
+
287
+ ### 사용자 커뮤니케이션
288
+
289
+ MCP 도구 실패 시 사용자에게 알리는 방법:
290
+
291
+ ```
292
+ ✅ 좋은 예:
293
+ "AWS Lambda 가격 정보를 MCP로 조회했으나 현재 서비스가 응답하지 않습니다.
294
+ 일반적인 가격 정보를 안내드리며, 정확한 최신 가격은
295
+ https://aws.amazon.com/lambda/pricing/ 에서 확인해주세요."
296
+
297
+ ❌ 나쁜 예:
298
+ "MCP 도구 에러가 발생했습니다."
299
+ "가격 정보를 가져올 수 없습니다."
300
+ ```
301
+
302
+ **원칙**: 에러 메시지보다 해결책과 대안을 제시하세요.
@@ -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
+ ```