slides-grab 1.1.2 → 1.1.4

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
@@ -31,16 +31,16 @@ Paste one of these into your coding agent:
31
31
  **Claude Code:**
32
32
 
33
33
  ```
34
- Read https://raw.githubusercontent.com/vkehfdl1/slides-grab/main/docs/prompts/setup-claude.md and follow every step.
34
+ Read https://raw.githubusercontent.com/vkehfdl1/slides-grab/main/docs/installation/claude.md and follow every step.
35
35
  ```
36
36
 
37
37
  **Codex:**
38
38
 
39
39
  ```
40
- Read https://raw.githubusercontent.com/vkehfdl1/slides-grab/main/docs/prompts/setup-codex.md and follow every step.
40
+ Read https://raw.githubusercontent.com/vkehfdl1/slides-grab/main/docs/installation/codex.md and follow every step.
41
41
  ```
42
42
 
43
- Or clone manually:
43
+ Or use the repo directly if you want to develop on slides-grab itself:
44
44
 
45
45
  ```bash
46
46
  git clone https://github.com/vkehfdl1/slides-grab.git && cd slides-grab
@@ -49,6 +49,14 @@ npm ci && npx playwright install chromium
49
49
 
50
50
  > Requires **Node.js >= 18**.
51
51
 
52
+ ### No-clone install
53
+
54
+ ```bash
55
+ npm install slides-grab
56
+ npx playwright install chromium
57
+ npx skills add ./node_modules/slides-grab -g -a codex -a claude-code --yes --copy
58
+ ```
59
+
52
60
  ## Why This Project?
53
61
 
54
62
  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:
@@ -69,8 +77,10 @@ slides-grab edit # Launch visual slide editor
69
77
  slides-grab build-viewer # Build single-file viewer.html
70
78
  slides-grab validate # Validate slide HTML (Playwright-based)
71
79
  slides-grab convert # Export to experimental / unstable PPTX
80
+ slides-grab convert --resolution 2160p # Higher-resolution raster PPTX export
72
81
  slides-grab figma # Export an experimental / unstable Figma Slides importable PPTX
73
82
  slides-grab pdf # Export PDF in capture mode (default)
83
+ slides-grab pdf --resolution 2160p # Higher-resolution image-backed PDF export
74
84
  slides-grab pdf --mode print # Export searchable/selectable text PDF
75
85
  slides-grab list-templates # Show available slide templates
76
86
  slides-grab list-themes # Show available color themes
@@ -89,6 +99,8 @@ Run `slides-grab validate --slides-dir <path>` before export to catch missing lo
89
99
 
90
100
  `slides-grab pdf` now defaults to `--mode capture`, which rasterizes each rendered slide into the PDF for better visual fidelity. Use `--mode print` when searchable/selectable browser text matters more than pixel-perfect parity.
91
101
 
102
+ `slides-grab pdf` and `slides-grab convert` now default to `2160p` / `4k` raster output for sharper exports. You can still override with `--resolution <preset>` using `720p`, `1080p`, `1440p`, `2160p`, or `4k` when you want smaller or faster artifacts.
103
+
92
104
  ### Multi-Deck Workflow
93
105
 
94
106
  Prerequisite: create or generate a deck in `decks/my-deck/` first.
@@ -114,19 +126,25 @@ This command reuses the HTML to PPTX pipeline and emits a `.pptx` deck intended
114
126
 
115
127
  ## Installation Guides
116
128
 
117
- - [Claude Code setup](docs/prompts/setup-claude.md)
118
- - [Codex setup](docs/prompts/setup-codex.md)
119
129
  - [Claude detailed guide](docs/installation/claude.md)
120
130
  - [Codex detailed guide](docs/installation/codex.md)
121
131
 
122
132
  ## npm Package
123
133
 
124
- Also available as an npm package for standalone CLI usage:
134
+ Also available as an npm package for standalone CLI + skill usage:
125
135
 
126
136
  ```bash
127
137
  npm install slides-grab
128
138
  ```
129
139
 
140
+ Install shared agent skills with Vercel Agent Skills:
141
+
142
+ ```bash
143
+ npx skills add ./node_modules/slides-grab -g -a codex -a claude-code --yes --copy
144
+ ```
145
+
146
+ This npm-install path is enough for normal usage. Clone the repo only when you want to modify or contribute to `slides-grab` itself.
147
+
130
148
  ## Project Structure
131
149
 
132
150
  ```
@@ -135,8 +153,7 @@ src/editor/ Visual editor (HTML + JS client modules)
135
153
  scripts/ Build, validate, convert, editor server
136
154
  templates/ Slide HTML templates (cover, content, chart, ...)
137
155
  themes/ Color themes (modern-dark, executive, sage, ...)
138
- .claude/skills/ Claude Code skill definitions
139
- skills/ Codex skill definitions
156
+ skills/ Shared Vercel-installable agent skills + references
140
157
  docs/ Installation & usage guides
141
158
  ```
142
159
 
package/bin/ppt-agent.js CHANGED
@@ -106,11 +106,15 @@ program
106
106
  .description('Convert slide HTML files to experimental / unstable PPTX')
107
107
  .option('--slides-dir <path>', 'Slide directory', 'slides')
108
108
  .option('--output <path>', 'Output PPTX file')
109
+ .option('--resolution <preset>', 'Raster size preset: 720p, 1080p, 1440p, 2160p, or 4k (default: 2160p)')
109
110
  .action(async (options = {}) => {
110
111
  const args = ['--slides-dir', options.slidesDir];
111
112
  if (options.output) {
112
113
  args.push('--output', String(options.output));
113
114
  }
115
+ if (options.resolution) {
116
+ args.push('--resolution', String(options.resolution));
117
+ }
114
118
  await runCommand('convert.cjs', args);
115
119
  });
116
120
 
@@ -120,6 +124,7 @@ program
120
124
  .option('--slides-dir <path>', 'Slide directory', 'slides')
121
125
  .option('--output <path>', 'Output PDF file')
122
126
  .option('--mode <mode>', 'PDF export mode: capture for visual fidelity, print for searchable text', 'capture')
127
+ .option('--resolution <preset>', 'Capture raster size preset: 720p, 1080p, 1440p, 2160p, or 4k (default: 2160p in capture mode)')
123
128
  .action(async (options = {}) => {
124
129
  const args = ['--slides-dir', options.slidesDir];
125
130
  if (options.output) {
@@ -128,6 +133,9 @@ program
128
133
  if (options.mode) {
129
134
  args.push('--mode', String(options.mode));
130
135
  }
136
+ if (options.resolution) {
137
+ args.push('--resolution', String(options.resolution));
138
+ }
131
139
  await runCommand('scripts/html2pdf.js', args);
132
140
  });
133
141
 
@@ -159,18 +167,6 @@ program
159
167
  await runCommand('scripts/editor-server.js', args);
160
168
  });
161
169
 
162
- program
163
- .command('install-codex-skills')
164
- .description('Install project Codex skills into $CODEX_HOME/skills (default: ~/.codex/skills)')
165
- .option('--force', 'Overwrite existing skill directories')
166
- .option('--dry-run', 'Preview what would be installed')
167
- .action(async (options = {}) => {
168
- const args = [];
169
- if (options.force) args.push('--force');
170
- if (options.dryRun) args.push('--dry-run');
171
- await runCommand('scripts/install-codex-skills.js', args);
172
- });
173
-
174
170
  // --- Template/theme discovery commands ---
175
171
 
176
172
  program
package/convert.cjs CHANGED
@@ -1,211 +1,12 @@
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 DEFAULT_SLIDES_DIR = 'slides';
9
- const DEFAULT_OUTPUT = 'output.pptx';
10
- const DEFAULT_CAPTURE_VIEWPORT = { width: 960, height: 540 };
11
- const DEFAULT_CAPTURE_DEVICE_SCALE_FACTOR = 2;
12
- const TARGET_RASTER_DPI = 150;
13
- const TARGET_SLIDE_SIZE_IN = { width: 13.33, height: 7.5 };
14
-
15
- function normalizeDimension(value, fallback) {
16
- if (!Number.isFinite(value) || value <= 0) {
17
- return fallback;
18
- }
19
- return Math.max(1, Math.round(value));
20
- }
21
-
22
- function buildPageOptions() {
23
- return {
24
- viewport: {
25
- width: DEFAULT_CAPTURE_VIEWPORT.width,
26
- height: DEFAULT_CAPTURE_VIEWPORT.height,
27
- },
28
- deviceScaleFactor: DEFAULT_CAPTURE_DEVICE_SCALE_FACTOR,
29
- };
30
- }
31
-
32
- function getTargetRasterSize() {
33
- return {
34
- width: Math.round(TARGET_SLIDE_SIZE_IN.width * TARGET_RASTER_DPI),
35
- height: Math.round(TARGET_SLIDE_SIZE_IN.height * TARGET_RASTER_DPI),
36
- };
37
- }
38
-
39
- function printUsage() {
40
- process.stdout.write(
41
- [
42
- 'Usage: node convert.cjs [options]',
43
- '',
44
- 'Options:',
45
- ` --slides-dir <path> Slide directory (default: ${DEFAULT_SLIDES_DIR})`,
46
- ` --output <path> Output pptx path (default: ${DEFAULT_OUTPUT})`,
47
- ' -h, --help Show this help message',
48
- ].join('\n'),
49
- );
50
- process.stdout.write('\n');
51
- }
52
-
53
- function readOptionValue(args, index, optionName) {
54
- const next = args[index + 1];
55
- if (!next || next.startsWith('-')) {
56
- throw new Error(`Missing value for ${optionName}.`);
57
- }
58
- return next;
59
- }
60
-
61
- function parseArgs(args) {
62
- const options = {
63
- slidesDir: DEFAULT_SLIDES_DIR,
64
- output: DEFAULT_OUTPUT,
65
- help: false,
66
- };
67
-
68
- for (let i = 0; i < args.length; i += 1) {
69
- const arg = args[i];
70
- if (arg === '-h' || arg === '--help') {
71
- options.help = true;
72
- continue;
73
- }
74
-
75
- if (arg === '--slides-dir') {
76
- options.slidesDir = readOptionValue(args, i, '--slides-dir');
77
- i += 1;
78
- continue;
79
- }
80
-
81
- if (arg.startsWith('--slides-dir=')) {
82
- options.slidesDir = arg.slice('--slides-dir='.length);
83
- continue;
84
- }
85
-
86
- if (arg === '--output') {
87
- options.output = readOptionValue(args, i, '--output');
88
- i += 1;
89
- continue;
90
- }
91
-
92
- if (arg.startsWith('--output=')) {
93
- options.output = arg.slice('--output='.length);
94
- continue;
95
- }
96
-
97
- throw new Error(`Unknown option: ${arg}`);
98
- }
99
-
100
- if (typeof options.slidesDir !== 'string' || options.slidesDir.trim() === '') {
101
- throw new Error('--slides-dir must be a non-empty string.');
102
- }
103
-
104
- if (typeof options.output !== 'string' || options.output.trim() === '') {
105
- throw new Error('--output must be a non-empty string.');
106
- }
107
-
108
- options.slidesDir = options.slidesDir.trim();
109
- options.output = options.output.trim();
110
- return options;
111
- }
112
-
113
- async function convertSlide(htmlFile, pres, browser) {
114
- const filePath = path.isAbsolute(htmlFile) ? htmlFile : path.join(process.cwd(), htmlFile);
115
-
116
- const page = await browser.newPage(buildPageOptions());
117
- await page.goto(`file://${filePath}`);
118
-
119
- const bodyDimensions = await page.evaluate(() => {
120
- const body = document.body;
121
- const style = window.getComputedStyle(body);
122
- return {
123
- width: parseFloat(style.width),
124
- height: parseFloat(style.height),
125
- };
126
- });
127
-
128
- await page.setViewportSize({
129
- width: normalizeDimension(bodyDimensions.width, DEFAULT_CAPTURE_VIEWPORT.width),
130
- height: normalizeDimension(bodyDimensions.height, DEFAULT_CAPTURE_VIEWPORT.height)
131
- });
132
-
133
- // Take screenshot and add as full-slide image
134
- const screenshot = await page.screenshot({ type: 'png' });
135
- await page.close();
136
-
137
- // Resize to exact slide dimensions (13.33" x 7.5" at 150 DPI)
138
- const targetSize = getTargetRasterSize();
139
-
140
- const resized = await sharp(screenshot)
141
- .resize(targetSize.width, targetSize.height, { fit: 'fill' })
142
- .png()
143
- .toBuffer();
144
-
145
- const tmpPath = path.join(process.env.TMPDIR || '/tmp', `slide-${Date.now()}-${Math.random().toString(36).slice(2)}.png`);
146
- fs.writeFileSync(tmpPath, resized);
147
-
148
- const slide = pres.addSlide();
149
- slide.addImage({
150
- path: tmpPath,
151
- x: 0,
152
- y: 0,
153
- w: '100%',
154
- h: '100%'
155
- });
156
-
157
- return tmpPath;
158
- }
159
-
160
- async function main() {
161
- const options = parseArgs(process.argv.slice(2));
162
- if (options.help) {
163
- printUsage();
164
- return;
165
- }
166
-
167
- const pres = new PptxGenJS();
168
- pres.layout = 'LAYOUT_WIDE'; // 16:9
169
-
170
- const slidesDir = path.resolve(process.cwd(), options.slidesDir);
171
- const { ensureSlidesPassValidation } = await import('./scripts/validate-slides.js');
172
- await ensureSlidesPassValidation(slidesDir, { exportLabel: 'PPTX export' });
173
- const files = fs.readdirSync(slidesDir)
174
- .filter(f => f.endsWith('.html'))
175
- .sort();
176
-
177
- console.log(`Converting ${files.length} slides...`);
178
-
179
- // Launch Chromium (not Chrome)
180
- const browser = await chromium.launch();
181
- const tmpFiles = [];
182
-
183
- for (const file of files) {
184
- const filePath = path.join(slidesDir, file);
185
- console.log(` Processing: ${file}`);
186
- try {
187
- const tmpPath = await convertSlide(filePath, pres, browser);
188
- tmpFiles.push(tmpPath);
189
- console.log(` ✓ ${file} done`);
190
- } catch (err) {
191
- console.error(` ✗ ${file} error: ${err.message}`);
192
- }
193
- }
194
-
195
- await browser.close();
196
-
197
- const outputFile = path.resolve(process.cwd(), options.output);
198
- await pres.writeFile({ fileName: outputFile });
199
- console.log(`\nSaved: ${outputFile}`);
200
-
201
- // Cleanup tmp files
202
- for (const f of tmpFiles) {
203
- try { fs.unlinkSync(f); } catch {}
204
- }
205
- }
1
+ const {
2
+ buildPageOptions,
3
+ getTargetRasterSize,
4
+ main,
5
+ parseArgs,
6
+ } = require('./src/pptx-raster-export.cjs');
206
7
 
207
8
  if (require.main === module) {
208
- main().catch(err => {
9
+ main().catch((err) => {
209
10
  console.error('Fatal error:', err);
210
11
  process.exit(1);
211
12
  });
@@ -214,5 +15,6 @@ if (require.main === module) {
214
15
  module.exports = {
215
16
  buildPageOptions,
216
17
  getTargetRasterSize,
18
+ main,
217
19
  parseArgs,
218
20
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slides-grab",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "Agent-first presentation framework — plan, design, and visually edit HTML slides with Claude Code or Codex, then export to PDF or experimental/unstable PPTX/Figma formats",
5
5
  "license": "MIT",
6
6
  "author": "vkehfdl1",
@@ -38,7 +38,6 @@
38
38
  "scripts/figma-export.js",
39
39
  "scripts/html2pdf.js",
40
40
  "scripts/html2pptx.js",
41
- "scripts/install-codex-skills.js",
42
41
  "scripts/validate-slides.js",
43
42
  "skills/",
44
43
  "src/",
@@ -52,8 +51,7 @@
52
51
  "build-viewer": "node scripts/build-viewer.js",
53
52
  "validate": "node scripts/validate-slides.js",
54
53
  "convert": "node convert.cjs",
55
- "codex:install-skills": "node scripts/install-codex-skills.js --force",
56
- "test": "node --test tests/editor/editor-codex-edit.test.js tests/pdf/html2pdf.test.js tests/pdf/html2pdf.e2e.test.js tests/figma/figma-export.test.js tests/image-contract/image-contract.test.js tests/validation/validate-slides.test.js",
54
+ "test": "node --test tests/editor/editor-codex-edit.test.js tests/pdf/html2pdf.test.js tests/pdf/html2pdf.e2e.test.js tests/figma/figma-export.test.js tests/image-contract/image-contract.test.js tests/validation/validate-slides.test.js tests/skills/installable-skills.test.js",
57
55
  "test:e2e": "node --test tests/editor/editor-ui.e2e.test.js tests/editor/editor-concurrency.e2e.test.js"
58
56
  },
59
57
  "dependencies": {
@@ -1,16 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { readdir, writeFile } from 'node:fs/promises';
4
+ import { createRequire } from 'node:module';
4
5
  import { basename, join, resolve } from 'node:path';
5
6
  import { pathToFileURL } from 'node:url';
6
7
  import { chromium } from 'playwright';
7
8
  import { PDFDocument } from 'pdf-lib';
9
+ import sharp from 'sharp';
8
10
 
9
11
  import { ensureSlidesPassValidation } from './validate-slides.js';
10
12
 
13
+ const require = createRequire(import.meta.url);
14
+ const {
15
+ getResolutionChoices,
16
+ getResolutionSize,
17
+ normalizeResolutionPreset,
18
+ } = require('../src/export-resolution.cjs');
19
+
11
20
  const DEFAULT_OUTPUT = 'slides.pdf';
12
21
  const DEFAULT_SLIDES_DIR = 'slides';
13
22
  const DEFAULT_MODE = 'capture';
23
+ const DEFAULT_CAPTURE_RESOLUTION = '2160p';
14
24
  const PDF_MODES = new Set(['capture', 'print']);
15
25
  const SLIDE_FILE_PATTERN = /^slide-.*\.html$/i;
16
26
  const FALLBACK_SLIDE_SIZE = { width: 960, height: 540 };
@@ -29,12 +39,14 @@ function printUsage() {
29
39
  ` --output <path> Output PDF path (default: ${DEFAULT_OUTPUT})`,
30
40
  ` --slides-dir <path> Slide directory (default: ${DEFAULT_SLIDES_DIR})`,
31
41
  ` --mode <mode> PDF export mode: capture|print (default: ${DEFAULT_MODE})`,
42
+ ` --resolution <preset> Capture raster size preset: ${getResolutionChoices().join('|')}|4k (default: ${DEFAULT_CAPTURE_RESOLUTION}; ignored in print mode)`,
32
43
  ' -h, --help Show this help message',
33
44
  '',
34
45
  'Examples:',
35
46
  ' node scripts/html2pdf.js',
36
47
  ' node scripts/html2pdf.js --output dist/deck.pdf',
37
48
  ' node scripts/html2pdf.js --mode print --output dist/searchable.pdf',
49
+ ' node scripts/html2pdf.js --resolution 2160p --output dist/deck-4k.pdf',
38
50
  ].join('\n'),
39
51
  );
40
52
  process.stdout.write('\n');
@@ -76,6 +88,25 @@ function cssPixelsToPdfPoints(value) {
76
88
  return Math.round((normalizeDimension(value, 0) * PDF_POINTS_PER_INCH) / CSS_PIXELS_PER_INCH);
77
89
  }
78
90
 
91
+ async function normalizeCaptureRasterSize(pngBytes, resolution = '') {
92
+ const targetSize = getResolutionSize(resolution);
93
+ if (!targetSize) {
94
+ return pngBytes;
95
+ }
96
+
97
+ const metadata = await sharp(pngBytes).metadata();
98
+ const currentWidth = normalizeDimension(metadata.width, targetSize.width);
99
+ const currentHeight = normalizeDimension(metadata.height, targetSize.height);
100
+ if (currentWidth === targetSize.width && currentHeight === targetSize.height) {
101
+ return pngBytes;
102
+ }
103
+
104
+ return sharp(pngBytes)
105
+ .resize(targetSize.width, targetSize.height, { fit: 'fill' })
106
+ .png()
107
+ .toBuffer();
108
+ }
109
+
79
110
  function formatDiagnosticEntry(entry) {
80
111
  const prefix = entry.slideFile ? `${entry.slideFile}: ` : '';
81
112
  return `${prefix}${entry.message}`;
@@ -108,6 +139,7 @@ export function parseCliArgs(args) {
108
139
  output: DEFAULT_OUTPUT,
109
140
  slidesDir: DEFAULT_SLIDES_DIR,
110
141
  mode: DEFAULT_MODE,
142
+ resolution: DEFAULT_CAPTURE_RESOLUTION,
111
143
  help: false,
112
144
  };
113
145
 
@@ -152,6 +184,17 @@ export function parseCliArgs(args) {
152
184
  continue;
153
185
  }
154
186
 
187
+ if (arg === '--resolution') {
188
+ options.resolution = normalizeResolutionPreset(readOptionValue(args, i, '--resolution'));
189
+ i += 1;
190
+ continue;
191
+ }
192
+
193
+ if (arg.startsWith('--resolution=')) {
194
+ options.resolution = normalizeResolutionPreset(arg.slice('--resolution='.length));
195
+ continue;
196
+ }
197
+
155
198
  throw new Error(`Unknown option: ${arg}`);
156
199
  }
157
200
 
@@ -165,6 +208,10 @@ export function parseCliArgs(args) {
165
208
  options.output = options.output.trim();
166
209
  options.slidesDir = options.slidesDir.trim();
167
210
  options.mode = normalizeMode(options.mode);
211
+ options.resolution = normalizeResolutionPreset(options.resolution);
212
+ if (options.mode === 'print') {
213
+ options.resolution = '';
214
+ }
168
215
 
169
216
  return options;
170
217
  }
@@ -188,13 +235,18 @@ export function buildPdfOptions(widthPx, heightPx) {
188
235
  };
189
236
  }
190
237
 
191
- export function buildPageOptions(mode = DEFAULT_MODE) {
238
+ export function buildPageOptions(mode = DEFAULT_MODE, resolution = '') {
239
+ const targetResolution = normalizeMode(mode) === 'capture' ? getResolutionSize(resolution) : null;
192
240
  return {
193
241
  viewport: {
194
242
  width: FALLBACK_SLIDE_SIZE.width,
195
243
  height: FALLBACK_SLIDE_SIZE.height,
196
244
  },
197
- deviceScaleFactor: normalizeMode(mode) === 'capture' ? DEFAULT_CAPTURE_DEVICE_SCALE_FACTOR : 1,
245
+ deviceScaleFactor: normalizeMode(mode) === 'capture'
246
+ ? targetResolution
247
+ ? targetResolution.height / FALLBACK_SLIDE_SIZE.height
248
+ : DEFAULT_CAPTURE_DEVICE_SCALE_FACTOR
249
+ : 1,
198
250
  };
199
251
  }
200
252
 
@@ -470,6 +522,7 @@ export async function renderSlideToPdf(page, slideFile, slidesDir, options = {})
470
522
  const slidePath = join(slidesDir, slideFile);
471
523
  const slideUrl = pathToFileURL(slidePath).href;
472
524
  const mode = normalizeMode(options.mode ?? DEFAULT_MODE);
525
+ const captureResolution = mode === 'capture' ? normalizeResolutionPreset(options.resolution ?? '') : '';
473
526
 
474
527
  await page.goto(slideUrl, { waitUntil: 'load' });
475
528
  await waitForSlideRenderReady(page, options);
@@ -495,11 +548,12 @@ export async function renderSlideToPdf(page, slideFile, slidesDir, options = {})
495
548
  height: viewportSize.height,
496
549
  },
497
550
  });
551
+ const normalizedPngBytes = await normalizeCaptureRasterSize(pngBytes, captureResolution);
498
552
  return {
499
553
  mode,
500
554
  width: normalizedSlideFrame.width,
501
555
  height: normalizedSlideFrame.height,
502
- pngBytes,
556
+ pngBytes: normalizedPngBytes,
503
557
  };
504
558
  }
505
559
 
@@ -560,7 +614,7 @@ async function main() {
560
614
  }
561
615
 
562
616
  const browser = await chromium.launch({ headless: true });
563
- const page = await browser.newPage(buildPageOptions(options.mode));
617
+ const page = await browser.newPage(buildPageOptions(options.mode, options.resolution));
564
618
  const diagnostics = createSlideDiagnostics();
565
619
  diagnostics.attach(page);
566
620
  const renderedSlides = [];
@@ -569,7 +623,10 @@ async function main() {
569
623
  for (const slideFile of slideFiles) {
570
624
  diagnostics.beginSlide(slideFile);
571
625
  try {
572
- const slideResult = await renderSlideToPdf(page, slideFile, slidesDir, { mode: options.mode });
626
+ const slideResult = await renderSlideToPdf(page, slideFile, slidesDir, {
627
+ mode: options.mode,
628
+ resolution: options.resolution,
629
+ });
573
630
  renderedSlides.push(slideResult);
574
631
  } catch (error) {
575
632
  throw decorateError(error, slideFile, diagnostics.getSlideDiagnostics(slideFile));
@@ -15,7 +15,7 @@ Guides you through the complete presentation pipeline from topic to exported fil
15
15
 
16
16
  ### Stage 1 — Plan
17
17
 
18
- Use **slides-grab-plan** (`skills/slides-grab-plan/SKILL.md`).
18
+ Use the installed **slides-grab-plan** skill.
19
19
 
20
20
  1. Take user's topic, audience, and tone.
21
21
  2. Create `slide-outline.md`.
@@ -26,13 +26,13 @@ Use **slides-grab-plan** (`skills/slides-grab-plan/SKILL.md`).
26
26
 
27
27
  ### Stage 2 — Design
28
28
 
29
- Use **slides-grab-design** (`skills/slides-grab-design/SKILL.md`).
29
+ Use the installed **slides-grab-design** skill.
30
30
 
31
31
  1. Read approved `slide-outline.md`.
32
32
  2. Generate `slide-*.html` files in the slides workspace (default: `slides/`).
33
33
  3. Run validation: `slides-grab validate --slides-dir <path>`
34
34
  4. If validation fails, automatically fix the slide HTML/CSS until validation passes.
35
- 5. Build the viewer: `node scripts/build-viewer.js --slides-dir <path>`
35
+ 5. Build the viewer: `slides-grab build-viewer --slides-dir <path>`
36
36
  6. Present viewer to user for review.
37
37
  7. Revise individual slides based on feedback, then re-run validation and rebuild the viewer.
38
38
  8. Optionally launch the visual editor: `slides-grab edit --slides-dir <path>`
@@ -41,7 +41,7 @@ Use **slides-grab-design** (`skills/slides-grab-design/SKILL.md`).
41
41
 
42
42
  ### Stage 3 — Export
43
43
 
44
- Use **slides-grab-export** (`skills/slides-grab-export/SKILL.md`).
44
+ Use the installed **slides-grab-export** skill.
45
45
 
46
46
  1. Confirm user wants conversion.
47
47
  2. Export to PPTX: `slides-grab convert --slides-dir <path> --output <name>.pptx` (**experimental / unstable**)
@@ -57,4 +57,7 @@ Use **slides-grab-export** (`skills/slides-grab-export/SKILL.md`).
57
57
  3. **Read each stage's SKILL.md** for detailed rules — this skill only orchestrates.
58
58
  4. **Use `decks/<deck-name>/`** as the slides workspace for multi-deck projects.
59
59
  5. **Call out export risk clearly**: PPTX and Figma export are experimental / unstable and must be described as best-effort output.
60
- 6. For full design constraints, refer to `.claude/skills/design-skill/SKILL.md`.
60
+ 6. Use the stage skills as the source of truth for plan, design, and export rules.
61
+
62
+ ## Reference
63
+ - `references/presentation-workflow-reference.md` — archived end-to-end workflow guidance from the legacy skill set
@@ -0,0 +1,52 @@
1
+ # Presentation Skill - Full Workflow Orchestrator
2
+
3
+ Guides you through the complete presentation pipeline from topic to exported file.
4
+
5
+ ---
6
+
7
+ ## Workflow
8
+
9
+ ### Stage 1 — Plan
10
+
11
+ Use **plan-skill** (`.claude/skills/plan-skill/SKILL.md`).
12
+
13
+ 1. Take user's topic, audience, and tone.
14
+ 2. Delegate outline creation to `organizer-agent`.
15
+ 3. Present `slide-outline.md` to user.
16
+ 4. Revise until user explicitly approves.
17
+
18
+ **Do not proceed to Stage 2 without approval.**
19
+
20
+ ### Stage 2 — Design
21
+
22
+ Use **design-skill** (`.claude/skills/design-skill/SKILL.md`).
23
+
24
+ 1. Read approved `slide-outline.md`.
25
+ 2. Generate `slide-*.html` files in the slides workspace (default: `slides/`).
26
+ 3. Run validation: `slides-grab validate --slides-dir <path>`
27
+ 4. If validation fails, automatically fix the slide HTML/CSS until validation passes.
28
+ 5. Build the viewer: `node scripts/build-viewer.js --slides-dir <path>`
29
+ 6. Present viewer to user for review.
30
+ 7. Revise individual slides based on feedback, then re-run validation and rebuild the viewer.
31
+ 8. Optionally launch the visual editor: `slides-grab edit --slides-dir <path>`
32
+
33
+ **Do not proceed to Stage 3 without approval.**
34
+
35
+ ### Stage 3 — Export
36
+
37
+ Use **pptx-skill** (`.claude/skills/pptx-skill/SKILL.md`).
38
+
39
+ 1. Confirm user wants conversion.
40
+ 2. Export to PPTX: `slides-grab convert --slides-dir <path> --output <name>.pptx` (**experimental / unstable**)
41
+ 3. Export to PDF (if requested): `slides-grab pdf --slides-dir <path> --output <name>.pdf`
42
+ 4. Report results.
43
+
44
+ ---
45
+
46
+ ## Rules
47
+
48
+ 1. **Always follow the stage order**: Plan → Design → Export.
49
+ 2. **Get explicit user approval** before advancing to the next stage.
50
+ 3. **Read each stage's SKILL.md** for detailed rules — this skill only orchestrates.
51
+ 4. **Use `decks/<deck-name>/`** as the slides workspace for multi-deck projects.
52
+ 5. **Call out export risk clearly**: PPTX and Figma export are experimental / unstable and should be described as best-effort output.
@@ -26,7 +26,7 @@ Generate high-quality `slide-XX.html` files in the selected slides workspace (`s
26
26
  2. Generate slide HTML files with 2-digit numbering in selected `--slides-dir`.
27
27
  3. Run `slides-grab validate --slides-dir <path>` after generation or edits.
28
28
  4. If validation fails, automatically fix the source slide HTML/CSS and re-run validation until it passes.
29
- 5. Run `node scripts/build-viewer.js --slides-dir <path>` only after validation passes.
29
+ 5. Run `slides-grab build-viewer --slides-dir <path>` only after validation passes.
30
30
  6. Iterate on user feedback by editing only requested slide files, then re-run validation and rebuild the viewer.
31
31
  7. Keep revising until user approves conversion stage.
32
32
 
@@ -39,7 +39,10 @@ Generate high-quality `slide-XX.html` files in the selected slides workspace (`s
39
39
  - Prefer `<img>` for slide imagery and `data-image-placeholder` when no final asset exists.
40
40
  - Do not present slides for review until `slides-grab validate --slides-dir <path>` passes.
41
41
  - Do not start conversion before approval.
42
+ - Use the packaged CLI and bundled references only; do not depend on unpublished agent-specific files.
42
43
 
43
44
  ## Reference
44
45
  For full constraints and style system, follow:
45
- - `.claude/skills/design-skill/SKILL.md`
46
+ - `references/design-rules.md`
47
+ - `references/detailed-design-rules.md`
48
+ - `references/design-system-full.md` — archived full design system, templates, and advanced pattern guidance