slides-grab 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/AGENTS.md +80 -0
  2. package/LICENSE +21 -0
  3. package/PROGRESS.md +39 -0
  4. package/README.md +120 -0
  5. package/SETUP.md +51 -0
  6. package/bin/ppt-agent.js +204 -0
  7. package/convert.cjs +184 -0
  8. package/package.json +51 -0
  9. package/prd.json +135 -0
  10. package/prd.md +104 -0
  11. package/scripts/editor-server.js +779 -0
  12. package/scripts/html2pdf.js +217 -0
  13. package/scripts/validate-slides.js +416 -0
  14. package/skills/ppt-design-skill/SKILL.md +38 -0
  15. package/skills/ppt-plan-skill/SKILL.md +37 -0
  16. package/skills/ppt-pptx-skill/SKILL.md +37 -0
  17. package/skills/ppt-presentation-skill/SKILL.md +57 -0
  18. package/src/editor/codex-edit.js +213 -0
  19. package/src/editor/editor.html +1733 -0
  20. package/src/editor/js/editor-bbox.js +332 -0
  21. package/src/editor/js/editor-chat.js +56 -0
  22. package/src/editor/js/editor-direct-edit.js +110 -0
  23. package/src/editor/js/editor-dom.js +55 -0
  24. package/src/editor/js/editor-init.js +284 -0
  25. package/src/editor/js/editor-navigation.js +54 -0
  26. package/src/editor/js/editor-select.js +264 -0
  27. package/src/editor/js/editor-send.js +157 -0
  28. package/src/editor/js/editor-sse.js +163 -0
  29. package/src/editor/js/editor-state.js +32 -0
  30. package/src/editor/js/editor-utils.js +167 -0
  31. package/src/editor/screenshot.js +73 -0
  32. package/src/resolve.js +159 -0
  33. package/templates/chart.html +121 -0
  34. package/templates/closing.html +54 -0
  35. package/templates/content.html +50 -0
  36. package/templates/contents.html +60 -0
  37. package/templates/cover.html +64 -0
  38. package/templates/custom/.gitkeep +0 -0
  39. package/templates/custom/README.md +7 -0
  40. package/templates/diagram.html +98 -0
  41. package/templates/quote.html +31 -0
  42. package/templates/section-divider.html +43 -0
  43. package/templates/split-layout.html +41 -0
  44. package/templates/statistics.html +55 -0
  45. package/templates/team.html +49 -0
  46. package/templates/timeline.html +59 -0
  47. package/themes/corporate.css +8 -0
  48. package/themes/executive.css +10 -0
  49. package/themes/modern-dark.css +9 -0
  50. package/themes/sage.css +9 -0
  51. package/themes/warm.css +8 -0
package/AGENTS.md ADDED
@@ -0,0 +1,80 @@
1
+ # AGENTS.md
2
+
3
+ ## Project Overview
4
+
5
+ slides-grab is an agent-first PPT framework. AI agents write HTML slides directly, and a 3-stage pipeline (Planning → Design → Conversion) produces high-quality PPTX/PDF output.
6
+
7
+ **Core philosophy: HTML is the design language.** Agents write HTML/CSS directly. The framework provides templates, validation, and conversion tools — not abstractions over HTML.
8
+
9
+ ## Key Paths
10
+
11
+ | Path | Purpose |
12
+ |------|---------|
13
+ | `.claude/skills/plan-skill/SKILL.md` | Planning skill (outline generation supervisor) |
14
+ | `.claude/skills/design-skill/SKILL.md` | Design skill (HTML slide generation rules, ~963 lines) |
15
+ | `.claude/skills/pptx-skill/SKILL.md` | Conversion skill (HTML→PPTX) |
16
+ | `skills/ppt-plan-skill/SKILL.md` | Codex plan skill (outline workflow) |
17
+ | `skills/ppt-design-skill/SKILL.md` | Codex design skill (HTML slides + viewer loop) |
18
+ | `skills/ppt-pptx-skill/SKILL.md` | Codex conversion skill (HTML→PPTX/PDF) |
19
+ | `.claude/skills/pptx-skill/scripts/html2pptx.js` | Main HTML→PPTX converter (Playwright + PptxGenJS) |
20
+ | `.claude/skills/pptx-skill/scripts/thumbnail.py` | Thumbnail grid generator |
21
+ | `.claude/skills/pptx-skill/ooxml/` | OOXML pack/unpack/validate scripts |
22
+ | `scripts/build-viewer.js` | Builds viewer.html from slide-*.html files in selected `--slides-dir` |
23
+ | `slides/` (default) or custom `--slides-dir` path | Generated HTML slide workspace |
24
+ | `slide-outline.md` | Presentation outline (generated by organizer-agent) |
25
+ | `package.json` | Node.js dependencies (playwright, sharp, pptxgenjs) |
26
+ | `convert.cjs` | Legacy HTML→PPTX converter |
27
+
28
+ ## Slide Specifications
29
+
30
+ - **Size**: 720pt × 405pt (16:9)
31
+ - **Font**: Pretendard (CDN: `https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css`)
32
+ - **Text tags**: Only `<p>`, `<h1>`-`<h6>`, `<ul>`, `<ol>`, `<li>` — never put text directly in `<div>` or `<span>`
33
+ - **Colors in CSS**: Use `#` prefix (`#FF0000`)
34
+ - **Colors in PptxGenJS**: No `#` prefix (`FF0000`)
35
+ - **No CSS gradients**: Rasterize to PNG with Sharp instead
36
+
37
+ ## Agent System
38
+
39
+ | Agent | Role | Model | Tools |
40
+ |-------|------|-------|-------|
41
+ | `organizer-agent` | Structure presentation outline | Claude Sonnet | Read, Write, Edit |
42
+ | `research-agent` | Web research and data gathering | Claude Sonnet | WebSearch, WebFetch, Read, Grep, Glob |
43
+
44
+ ## Skill System
45
+
46
+ | Skill | Role | Stage |
47
+ |-------|------|-------|
48
+ | `plan-skill` | Supervisor: delegates to agents, manages user feedback loop | Stage 1 |
49
+ | `design-skill` | Defines HTML generation rules, templates, palettes | Stage 2 |
50
+ | `pptx-skill` | HTML→PPTX conversion pipeline | Stage 3 |
51
+ | `ppt-plan-skill` | Codex Stage 1 outline planning | Stage 1 |
52
+ | `ppt-design-skill` | Codex Stage 2 slide design/review | Stage 2 |
53
+ | `ppt-pptx-skill` | Codex Stage 3 conversion | Stage 3 |
54
+
55
+ ## Dependencies
56
+
57
+ ### Node.js (package.json)
58
+ - `pptxgenjs` ^3.12.0 — PowerPoint generation
59
+ - `playwright` ^1.40.0 — Browser automation (Chromium)
60
+ - `sharp` ^0.33.0 — Image processing
61
+
62
+ ### Python
63
+ - `markitdown` — Markdown conversion
64
+ - `defusedxml` — XML parsing
65
+ - `pillow` — Image processing
66
+
67
+ ### System (optional)
68
+ - `LibreOffice` (soffice) — PPTX thumbnail generation
69
+ - `Poppler` (pdftoppm) — PDF to image conversion
70
+
71
+ ## Rules for Agents
72
+
73
+ 1. **Do not modify SKILL.md philosophy sections** — design principles, rules, and constraints must be preserved when editing SKILL.md files.
74
+ 2. **All new scripts go in `scripts/`** unless they are skill-specific (then `skills/<name>/scripts/`).
75
+ 3. **Use ES modules** — `import/export` syntax, not `require()`. Exception: `.cjs` files for CommonJS compat.
76
+ 4. **JavaScript, not TypeScript** — Use JSDoc for type hints if needed.
77
+ 5. **Test scripts by running them** — Use `node scripts/<name>.js` to verify they work.
78
+ 6. **Playwright browser reuse** — When processing multiple slides, keep the browser instance open and reuse it across slides for performance.
79
+ 7. **Environment variables for API keys** — `GEMINI_API_KEY`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`. Never hardcode keys.
80
+ 8. **CSS-safe HTML** — All text in semantic tags (`<p>`, `<h1>`-`<h6>`, `<ul>`, `<ol>`), backgrounds only on `<div>` elements.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jeffrey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PROGRESS.md ADDED
@@ -0,0 +1,39 @@
1
+ ## US-001 — PASS (2026-02-27T14:20:45Z)
2
+ - Title: 템플릿 시스템 외부화
3
+ - Attempts: 1
4
+
5
+ ## US-002 — PASS (2026-02-27T14:27:39Z)
6
+ - Title: Playwright 기반 구조화 검증 스크립트
7
+ - Attempts: 1
8
+
9
+ ## US-003 — PASS (2026-02-27T14:33:38Z)
10
+ - Title: VLM Provider 표준 인터페이스 (Vercel AI SDK)
11
+ - Attempts: 1
12
+
13
+ ## US-004 — PASS (2026-02-27T14:44:20Z)
14
+ - Title: 차트/다이어그램/이미지 HTML 라이브러리 지원
15
+ - Attempts: 1
16
+ ## US-004 — PASS (2026-02-27T14:46:17Z)
17
+ - Title: 차트/다이어그램/이미지 HTML 라이브러리 지원
18
+ - Attempts: 1
19
+
20
+ ## US-005 — PASS (2026-02-27T14:55:18Z)
21
+ - Title: VLM 기반 슬라이드 자동 포맷 검증
22
+ - Attempts: 1
23
+
24
+ ## US-006 — PASS (2026-02-27T15:01:33Z)
25
+ - Title: PDF 출력 지원
26
+ - Attempts: 1
27
+
28
+ ## US-007 — PASS (2026-02-27T15:04:54Z)
29
+ - Title: VLM 기반 사용자 스타일 추출
30
+ - Attempts: 1
31
+
32
+ ## US-007 — PASS (2026-02-27T15:10:48Z)
33
+ - Title: VLM 기반 사용자 스타일 추출
34
+ - Attempts: 1
35
+
36
+ ## US-008 — PASS (2026-02-27T15:17:36Z)
37
+ - Title: 프로젝트 패키징 기반 구조
38
+ - Attempts: 1
39
+
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ <h1 align="center">slides-grab</h1>
2
+
3
+ <p align="center">Select context for agents directly from AI-generated HTML slides</p>
4
+
5
+ <p align="center">
6
+ How? Just drag an area in the slides and ask the agent to edit it.<br>
7
+ Simple things like text, size, or bold can still be edited manually, just like in the 2024 era.
8
+ </p>
9
+
10
+ <p align="center">
11
+ The whole slides are HTML & CSS, the programming langauge (which is not) that outperformed by AI agents.<br>
12
+ So the slides are beautiful, easily editable by AI agents, and can be converted to pdf and pptx.
13
+ </p>
14
+
15
+ <p align="center">
16
+ The editor is pure javascript file. You can easily add up new features like adding new coding agents, changing designs, etc.
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="https://github.com/vkehfdl1/slides-grab/releases/download/v0.0.1-demo/demo.mp4">
21
+ <img src="docs/assets/demo.gif" alt="slides-grab demo" width="720">
22
+ </a>
23
+ </p>
24
+
25
+ ---
26
+
27
+ ## Quick Start
28
+
29
+ Paste one of these into your coding agent:
30
+
31
+ **Claude Code:**
32
+
33
+ ```
34
+ Read https://raw.githubusercontent.com/vkehfdl1/slides-grab/main/docs/prompts/setup-claude.md and follow every step.
35
+ ```
36
+
37
+ **Codex:**
38
+
39
+ ```
40
+ Read https://raw.githubusercontent.com/vkehfdl1/slides-grab/main/docs/prompts/setup-codex.md and follow every step.
41
+ ```
42
+
43
+ Or clone manually:
44
+
45
+ ```bash
46
+ git clone https://github.com/vkehfdl1/slides-grab.git && cd slides-grab
47
+ npm ci && npx playwright install chromium
48
+ ```
49
+
50
+ > Requires **Node.js >= 18**.
51
+
52
+ ## Why This Project?
53
+
54
+ There are many AI tools that generate slide HTML. Almost none let you **visually point at what you want changed** and iterate in-place. slides-grab fills that gap:
55
+
56
+ - **Plan** — Agent creates a structured slide outline from your topic/files
57
+ - **Design** — Agent generates each slide as a self-contained HTML file
58
+ - **Edit** — Browser-based editor with bbox selection, direct text editing, and agent-powered rewrites
59
+ - **Export** — One command to PPTX or PDF
60
+
61
+ ## CLI Commands
62
+
63
+ All commands support `--slides-dir <path>` (default: `slides`).
64
+
65
+ ```bash
66
+ slides-grab edit # Launch visual slide editor
67
+ slides-grab build-viewer # Build single-file viewer.html
68
+ slides-grab validate # Validate slide HTML (Playwright-based)
69
+ slides-grab convert # Export to PPTX
70
+ slides-grab pdf # Export to PDF
71
+ slides-grab list-templates # Show available slide templates
72
+ slides-grab list-themes # Show available color themes
73
+ ```
74
+
75
+ ### Multi-Deck Workflow
76
+
77
+ ```bash
78
+ slides-grab edit --slides-dir decks/my-deck
79
+ slides-grab validate --slides-dir decks/my-deck
80
+ slides-grab pdf --slides-dir decks/my-deck --output decks/my-deck.pdf
81
+ slides-grab convert --slides-dir decks/my-deck --output decks/my-deck.pptx
82
+ ```
83
+
84
+ ## Installation Guides
85
+
86
+ - [Claude Code setup](docs/prompts/setup-claude.md)
87
+ - [Codex setup](docs/prompts/setup-codex.md)
88
+ - [Claude detailed guide](docs/installation/claude.md)
89
+ - [Codex detailed guide](docs/installation/codex.md)
90
+
91
+ ## npm Package
92
+
93
+ Also available as an npm package for standalone CLI usage:
94
+
95
+ ```bash
96
+ npm install slides-grab
97
+ ```
98
+
99
+ ## Project Structure
100
+
101
+ ```
102
+ bin/ CLI entry point
103
+ src/editor/ Visual editor (HTML + JS client modules)
104
+ scripts/ Build, validate, convert, editor server
105
+ templates/ Slide HTML templates (cover, content, chart, ...)
106
+ themes/ Color themes (modern-dark, executive, sage, ...)
107
+ .claude/skills/ Claude Code skill definitions
108
+ skills/ Codex skill definitions
109
+ docs/ Installation & usage guides
110
+ ```
111
+
112
+ ## License
113
+
114
+ [MIT](LICENSE)
115
+
116
+
117
+ ## Acknowledgment
118
+
119
+ This project is built based on the [ppt_team_agent](https://github.com/uxjoseph/ppt_team_agent) by Builder Josh. Huge thanks to him!
120
+
package/SETUP.md ADDED
@@ -0,0 +1,51 @@
1
+ # Setup
2
+
3
+ Copy-paste installation guide for `slides-grab`.
4
+
5
+ ## 1) Clone the Repository
6
+
7
+ ```bash
8
+ git clone https://github.com/vkehfdl1/slides-grab.git && cd slides-grab
9
+ ```
10
+
11
+ ## 2) Install Dependencies
12
+
13
+ ```bash
14
+ npm ci && npx playwright install chromium
15
+ ```
16
+
17
+ ### Platform-specific: install Node.js first if needed
18
+
19
+ macOS (Homebrew):
20
+
21
+ ```bash
22
+ brew update && brew install node git
23
+ ```
24
+
25
+ Ubuntu (apt):
26
+
27
+ ```bash
28
+ sudo apt-get update && sudo apt-get install -y curl git && curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt-get install -y nodejs
29
+ ```
30
+
31
+ Windows (winget, PowerShell):
32
+
33
+ ```powershell
34
+ winget install -e --id OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements; winget install -e --id Git.Git --accept-package-agreements --accept-source-agreements
35
+ ```
36
+
37
+ ## 3) Verify CLI
38
+
39
+ ```bash
40
+ npm exec -- slides-grab --help
41
+ ```
42
+
43
+ ## 4) For Codex Users
44
+
45
+ Install Codex skills after cloning:
46
+
47
+ ```bash
48
+ npm exec -- slides-grab install-codex-skills --force
49
+ ```
50
+
51
+ Then restart Codex.
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from 'node:child_process';
4
+ import { readFileSync } from 'node:fs';
5
+ import { dirname, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { Command } from 'commander';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const packageRoot = resolve(__dirname, '..');
11
+ const packageJson = JSON.parse(readFileSync(resolve(packageRoot, 'package.json'), 'utf-8'));
12
+
13
+ /**
14
+ * Run a Node.js script from the package, with CWD set to the user's directory.
15
+ * Scripts resolve slide paths via --slides-dir and templates/themes via src/resolve.js.
16
+ */
17
+ function runNodeScript(relativePath, args = []) {
18
+ return new Promise((resolvePromise, rejectPromise) => {
19
+ const scriptPath = resolve(packageRoot, relativePath);
20
+ const child = spawn(process.execPath, [scriptPath, ...args], {
21
+ cwd: process.cwd(),
22
+ stdio: 'inherit',
23
+ env: {
24
+ ...process.env,
25
+ PPT_AGENT_PACKAGE_ROOT: packageRoot,
26
+ }
27
+ });
28
+
29
+ child.on('error', rejectPromise);
30
+ child.on('close', (code, signal) => {
31
+ if (signal) {
32
+ rejectPromise(new Error(`Command terminated by signal ${signal}`));
33
+ return;
34
+ }
35
+ resolvePromise(code ?? 0);
36
+ });
37
+ });
38
+ }
39
+
40
+ async function runCommand(relativePath, args = []) {
41
+ try {
42
+ const code = await runNodeScript(relativePath, args);
43
+ if (code !== 0) {
44
+ process.exitCode = code;
45
+ }
46
+ } catch (error) {
47
+ console.error(`[slides-grab] ${error.message}`);
48
+ process.exitCode = 1;
49
+ }
50
+ }
51
+
52
+ const program = new Command();
53
+
54
+ program
55
+ .name('slides-grab')
56
+ .description('Agent-first PPT framework CLI')
57
+ .version(packageJson.version);
58
+
59
+ // --- Core workflow commands ---
60
+
61
+ program
62
+ .command('build-viewer')
63
+ .description('Build viewer.html from slide HTML files')
64
+ .option('--slides-dir <path>', 'Slide directory', 'slides')
65
+ .action(async (options = {}) => {
66
+ const args = ['--slides-dir', options.slidesDir];
67
+ await runCommand('scripts/build-viewer.js', args);
68
+ });
69
+
70
+ program
71
+ .command('validate')
72
+ .description('Run structured validation on slide HTML files (Playwright-based)')
73
+ .option('--slides-dir <path>', 'Slide directory', 'slides')
74
+ .action(async (options = {}) => {
75
+ const args = ['--slides-dir', options.slidesDir];
76
+ await runCommand('scripts/validate-slides.js', args);
77
+ });
78
+
79
+ program
80
+ .command('convert')
81
+ .description('Convert slide HTML files to PPTX')
82
+ .option('--slides-dir <path>', 'Slide directory', 'slides')
83
+ .option('--output <path>', 'Output PPTX file')
84
+ .action(async (options = {}) => {
85
+ const args = ['--slides-dir', options.slidesDir];
86
+ if (options.output) {
87
+ args.push('--output', String(options.output));
88
+ }
89
+ await runCommand('convert.cjs', args);
90
+ });
91
+
92
+ program
93
+ .command('pdf')
94
+ .description('Convert slide HTML files to PDF')
95
+ .option('--slides-dir <path>', 'Slide directory', 'slides')
96
+ .option('--output <path>', 'Output PDF file')
97
+ .action(async (options = {}) => {
98
+ const args = ['--slides-dir', options.slidesDir];
99
+ if (options.output) {
100
+ args.push('--output', String(options.output));
101
+ }
102
+ await runCommand('scripts/html2pdf.js', args);
103
+ });
104
+
105
+ program
106
+ .command('edit')
107
+ .description('Start interactive slide editor with Codex image-based edit flow')
108
+ .option('--port <number>', 'Server port')
109
+ .option('--slides-dir <path>', 'Slide directory', 'slides')
110
+ .action(async (options = {}) => {
111
+ const args = ['--slides-dir', options.slidesDir];
112
+ if (options.port) {
113
+ args.push('--port', String(options.port));
114
+ }
115
+ await runCommand('scripts/editor-server.js', args);
116
+ });
117
+
118
+ program
119
+ .command('install-codex-skills')
120
+ .description('Install project Codex skills into $CODEX_HOME/skills (default: ~/.codex/skills)')
121
+ .option('--force', 'Overwrite existing skill directories')
122
+ .option('--dry-run', 'Preview what would be installed')
123
+ .action(async (options = {}) => {
124
+ const args = [];
125
+ if (options.force) args.push('--force');
126
+ if (options.dryRun) args.push('--dry-run');
127
+ await runCommand('scripts/install-codex-skills.js', args);
128
+ });
129
+
130
+ // --- Template/theme discovery commands ---
131
+
132
+ program
133
+ .command('list-templates')
134
+ .description('List all available slide templates (local overrides + package built-ins)')
135
+ .action(async () => {
136
+ const { listTemplates } = await import('../src/resolve.js');
137
+ const templates = listTemplates();
138
+ if (templates.length === 0) {
139
+ console.log('No templates found.');
140
+ return;
141
+ }
142
+ console.log('Available templates:\n');
143
+ for (const t of templates) {
144
+ const tag = t.source === 'local' ? '(local)' : '(built-in)';
145
+ console.log(` ${t.name.padEnd(20)} ${tag}`);
146
+ }
147
+ console.log(`\nTotal: ${templates.length} templates`);
148
+ });
149
+
150
+ program
151
+ .command('list-themes')
152
+ .description('List all available color themes (local overrides + package built-ins)')
153
+ .action(async () => {
154
+ const { listThemes } = await import('../src/resolve.js');
155
+ const themes = listThemes();
156
+ if (themes.length === 0) {
157
+ console.log('No themes found.');
158
+ return;
159
+ }
160
+ console.log('Available themes:\n');
161
+ for (const t of themes) {
162
+ const tag = t.source === 'local' ? '(local)' : '(built-in)';
163
+ console.log(` ${t.name.padEnd(20)} ${tag}`);
164
+ }
165
+ console.log(`\nTotal: ${themes.length} themes`);
166
+ });
167
+
168
+ program
169
+ .command('show-template')
170
+ .description('Print the contents of a template file')
171
+ .argument('<name>', 'Template name (e.g. "cover", "content", "chart")')
172
+ .action(async (name) => {
173
+ const { resolveTemplate } = await import('../src/resolve.js');
174
+ const result = resolveTemplate(name);
175
+ if (!result) {
176
+ console.error(`Template "${name}" not found.`);
177
+ process.exitCode = 1;
178
+ return;
179
+ }
180
+ const content = readFileSync(result.path, 'utf-8');
181
+ console.log(`# Template: ${name} (${result.source})`);
182
+ console.log(`# Path: ${result.path}\n`);
183
+ console.log(content);
184
+ });
185
+
186
+ program
187
+ .command('show-theme')
188
+ .description('Print the contents of a theme file')
189
+ .argument('<name>', 'Theme name (e.g. "modern-dark", "executive")')
190
+ .action(async (name) => {
191
+ const { resolveTheme } = await import('../src/resolve.js');
192
+ const result = resolveTheme(name);
193
+ if (!result) {
194
+ console.error(`Theme "${name}" not found.`);
195
+ process.exitCode = 1;
196
+ return;
197
+ }
198
+ const content = readFileSync(result.path, 'utf-8');
199
+ console.log(`/* Theme: ${name} (${result.source}) */`);
200
+ console.log(`/* Path: ${result.path} */\n`);
201
+ console.log(content);
202
+ });
203
+
204
+ await program.parseAsync(process.argv);
package/convert.cjs ADDED
@@ -0,0 +1,184 @@
1
+ const PptxGenJS = require('pptxgenjs');
2
+ const { chromium } = require('playwright');
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const sharp = require('sharp');
6
+
7
+ // Inline a simplified version that uses Playwright Chromium (not Chrome)
8
+ const PT_PER_PX = 0.75;
9
+ const PX_PER_IN = 96;
10
+ const EMU_PER_IN = 914400;
11
+ const DEFAULT_SLIDES_DIR = 'slides';
12
+ const DEFAULT_OUTPUT = 'output.pptx';
13
+
14
+ function printUsage() {
15
+ process.stdout.write(
16
+ [
17
+ 'Usage: node convert.cjs [options]',
18
+ '',
19
+ 'Options:',
20
+ ` --slides-dir <path> Slide directory (default: ${DEFAULT_SLIDES_DIR})`,
21
+ ` --output <path> Output pptx path (default: ${DEFAULT_OUTPUT})`,
22
+ ' -h, --help Show this help message',
23
+ ].join('\n'),
24
+ );
25
+ process.stdout.write('\n');
26
+ }
27
+
28
+ function readOptionValue(args, index, optionName) {
29
+ const next = args[index + 1];
30
+ if (!next || next.startsWith('-')) {
31
+ throw new Error(`Missing value for ${optionName}.`);
32
+ }
33
+ return next;
34
+ }
35
+
36
+ function parseArgs(args) {
37
+ const options = {
38
+ slidesDir: DEFAULT_SLIDES_DIR,
39
+ output: DEFAULT_OUTPUT,
40
+ help: false,
41
+ };
42
+
43
+ for (let i = 0; i < args.length; i += 1) {
44
+ const arg = args[i];
45
+ if (arg === '-h' || arg === '--help') {
46
+ options.help = true;
47
+ continue;
48
+ }
49
+
50
+ if (arg === '--slides-dir') {
51
+ options.slidesDir = readOptionValue(args, i, '--slides-dir');
52
+ i += 1;
53
+ continue;
54
+ }
55
+
56
+ if (arg.startsWith('--slides-dir=')) {
57
+ options.slidesDir = arg.slice('--slides-dir='.length);
58
+ continue;
59
+ }
60
+
61
+ if (arg === '--output') {
62
+ options.output = readOptionValue(args, i, '--output');
63
+ i += 1;
64
+ continue;
65
+ }
66
+
67
+ if (arg.startsWith('--output=')) {
68
+ options.output = arg.slice('--output='.length);
69
+ continue;
70
+ }
71
+
72
+ throw new Error(`Unknown option: ${arg}`);
73
+ }
74
+
75
+ if (typeof options.slidesDir !== 'string' || options.slidesDir.trim() === '') {
76
+ throw new Error('--slides-dir must be a non-empty string.');
77
+ }
78
+
79
+ if (typeof options.output !== 'string' || options.output.trim() === '') {
80
+ throw new Error('--output must be a non-empty string.');
81
+ }
82
+
83
+ options.slidesDir = options.slidesDir.trim();
84
+ options.output = options.output.trim();
85
+ return options;
86
+ }
87
+
88
+ async function convertSlide(htmlFile, pres, browser) {
89
+ const filePath = path.isAbsolute(htmlFile) ? htmlFile : path.join(process.cwd(), htmlFile);
90
+
91
+ const page = await browser.newPage();
92
+ await page.goto(`file://${filePath}`);
93
+
94
+ const bodyDimensions = await page.evaluate(() => {
95
+ const body = document.body;
96
+ const style = window.getComputedStyle(body);
97
+ return {
98
+ width: parseFloat(style.width),
99
+ height: parseFloat(style.height),
100
+ };
101
+ });
102
+
103
+ await page.setViewportSize({
104
+ width: Math.round(bodyDimensions.width),
105
+ height: Math.round(bodyDimensions.height)
106
+ });
107
+
108
+ // Take screenshot and add as full-slide image
109
+ const screenshot = await page.screenshot({ type: 'png' });
110
+ await page.close();
111
+
112
+ // Resize to exact slide dimensions (13.33" x 7.5" at 150 DPI)
113
+ const targetWidth = Math.round(13.33 * 150);
114
+ const targetHeight = Math.round(7.5 * 150);
115
+
116
+ const resized = await sharp(screenshot)
117
+ .resize(targetWidth, targetHeight, { fit: 'fill' })
118
+ .png()
119
+ .toBuffer();
120
+
121
+ const tmpPath = path.join(process.env.TMPDIR || '/tmp', `slide-${Date.now()}-${Math.random().toString(36).slice(2)}.png`);
122
+ fs.writeFileSync(tmpPath, resized);
123
+
124
+ const slide = pres.addSlide();
125
+ slide.addImage({
126
+ path: tmpPath,
127
+ x: 0,
128
+ y: 0,
129
+ w: '100%',
130
+ h: '100%'
131
+ });
132
+
133
+ return tmpPath;
134
+ }
135
+
136
+ async function main() {
137
+ const options = parseArgs(process.argv.slice(2));
138
+ if (options.help) {
139
+ printUsage();
140
+ return;
141
+ }
142
+
143
+ const pres = new PptxGenJS();
144
+ pres.layout = 'LAYOUT_WIDE'; // 16:9
145
+
146
+ const slidesDir = path.resolve(process.cwd(), options.slidesDir);
147
+ const files = fs.readdirSync(slidesDir)
148
+ .filter(f => f.endsWith('.html'))
149
+ .sort();
150
+
151
+ console.log(`Converting ${files.length} slides...`);
152
+
153
+ // Launch Chromium (not Chrome)
154
+ const browser = await chromium.launch();
155
+ const tmpFiles = [];
156
+
157
+ for (const file of files) {
158
+ const filePath = path.join(slidesDir, file);
159
+ console.log(` Processing: ${file}`);
160
+ try {
161
+ const tmpPath = await convertSlide(filePath, pres, browser);
162
+ tmpFiles.push(tmpPath);
163
+ console.log(` ✓ ${file} done`);
164
+ } catch (err) {
165
+ console.error(` ✗ ${file} error: ${err.message}`);
166
+ }
167
+ }
168
+
169
+ await browser.close();
170
+
171
+ const outputFile = path.resolve(process.cwd(), options.output);
172
+ await pres.writeFile({ fileName: outputFile });
173
+ console.log(`\nSaved: ${outputFile}`);
174
+
175
+ // Cleanup tmp files
176
+ for (const f of tmpFiles) {
177
+ try { fs.unlinkSync(f); } catch {}
178
+ }
179
+ }
180
+
181
+ main().catch(err => {
182
+ console.error('Fatal error:', err);
183
+ process.exit(1);
184
+ });