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 +43 -12
- package/package.json +1 -1
- package/src/prompts/gurus.ts +93 -0
- package/src/prompts/orchestrator.ts +80 -0
- package/src/prompts/specialists.ts +93 -2
- package/src/skills/docx/SKILL.md +533 -0
- package/src/skills/mcp/SKILL.md +132 -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/prompts/gurus.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
};
|
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,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
|
+
```
|
package/src/skills/mcp/SKILL.md
CHANGED
|
@@ -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
|
+
**원칙**: 에러 메시지보다 해결책과 대안을 제시하세요.
|
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
|
+
```
|