slides-grab 1.0.0 → 1.1.2

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.
@@ -1,11 +1,11 @@
1
1
  ---
2
- name: ppt-presentation-skill
3
- description: End-to-end presentation workflow for Codex. Use when making a full presentation from scratch — planning, designing slides, editing, and exporting.
2
+ name: slides-grab
3
+ description: End-to-end presentation workflow for Codex. Use when making a full presentation from scratch — planning, designing slides, editing, and exporting. PDF is preferred; PPTX/Figma export is experimental / unstable.
4
4
  metadata:
5
- short-description: Full pipeline from topic to PPTX/PDF
5
+ short-description: Full pipeline from topic to PDF + experimental / unstable PPTX/Figma export
6
6
  ---
7
7
 
8
- # Presentation Skill (Codex) - Full Workflow Orchestrator
8
+ # slides-grab Skill (Codex) - Full Workflow Orchestrator
9
9
 
10
10
  Guides you through the complete presentation pipeline from topic to exported file.
11
11
 
@@ -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 **ppt-plan-skill** (`skills/ppt-plan-skill/SKILL.md`).
18
+ Use **slides-grab-plan** (`skills/slides-grab-plan/SKILL.md`).
19
19
 
20
20
  1. Take user's topic, audience, and tone.
21
21
  2. Create `slide-outline.md`.
@@ -26,23 +26,25 @@ Use **ppt-plan-skill** (`skills/ppt-plan-skill/SKILL.md`).
26
26
 
27
27
  ### Stage 2 — Design
28
28
 
29
- Use **ppt-design-skill** (`skills/ppt-design-skill/SKILL.md`).
29
+ Use **slides-grab-design** (`skills/slides-grab-design/SKILL.md`).
30
30
 
31
31
  1. Read approved `slide-outline.md`.
32
32
  2. Generate `slide-*.html` files in the slides workspace (default: `slides/`).
33
- 3. Build the viewer: `node scripts/build-viewer.js --slides-dir <path>`
34
- 4. Present viewer to user for review.
35
- 5. Revise individual slides based on feedback.
36
- 6. Optionally launch the visual editor: `slides-grab edit --slides-dir <path>`
33
+ 3. Run validation: `slides-grab validate --slides-dir <path>`
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>`
36
+ 6. Present viewer to user for review.
37
+ 7. Revise individual slides based on feedback, then re-run validation and rebuild the viewer.
38
+ 8. Optionally launch the visual editor: `slides-grab edit --slides-dir <path>`
37
39
 
38
40
  **Do not proceed to Stage 3 without approval.**
39
41
 
40
42
  ### Stage 3 — Export
41
43
 
42
- Use **ppt-pptx-skill** (`skills/ppt-pptx-skill/SKILL.md`).
44
+ Use **slides-grab-export** (`skills/slides-grab-export/SKILL.md`).
43
45
 
44
46
  1. Confirm user wants conversion.
45
- 2. Export to PPTX: `slides-grab convert --slides-dir <path> --output <name>.pptx`
47
+ 2. Export to PPTX: `slides-grab convert --slides-dir <path> --output <name>.pptx` (**experimental / unstable**)
46
48
  3. Export to PDF (if requested): `slides-grab pdf --slides-dir <path> --output <name>.pdf`
47
49
  4. Report results.
48
50
 
@@ -54,4 +56,5 @@ Use **ppt-pptx-skill** (`skills/ppt-pptx-skill/SKILL.md`).
54
56
  2. **Get explicit user approval** before advancing to the next stage.
55
57
  3. **Read each stage's SKILL.md** for detailed rules — this skill only orchestrates.
56
58
  4. **Use `decks/<deck-name>/`** as the slides workspace for multi-deck projects.
57
- 5. For full design constraints, refer to `.claude/skills/design-skill/SKILL.md`.
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`.
@@ -1,11 +1,11 @@
1
1
  ---
2
- name: ppt-design-skill
2
+ name: slides-grab-design
3
3
  description: Stage 2 design skill for Codex. Generate and iterate slide-XX.html files in the selected slides workspace.
4
4
  metadata:
5
5
  short-description: Build HTML slides and viewer for review loop
6
6
  ---
7
7
 
8
- # PPT Design Skill (Codex)
8
+ # slides-grab Design Skill (Codex)
9
9
 
10
10
  Use this after `slide-outline.md` is approved.
11
11
 
@@ -24,13 +24,20 @@ Generate high-quality `slide-XX.html` files in the selected slides workspace (`s
24
24
  ## Workflow
25
25
  1. Read approved `slide-outline.md`.
26
26
  2. Generate slide HTML files with 2-digit numbering in selected `--slides-dir`.
27
- 3. Run `node scripts/build-viewer.js --slides-dir <path>` after generation or edits.
28
- 4. Iterate on user feedback by editing only requested slide files.
29
- 5. Keep revising until user approves conversion stage.
27
+ 3. Run `slides-grab validate --slides-dir <path>` after generation or edits.
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.
30
+ 6. Iterate on user feedback by editing only requested slide files, then re-run validation and rebuild the viewer.
31
+ 7. Keep revising until user approves conversion stage.
30
32
 
31
33
  ## Rules
32
34
  - Keep slide size 720pt x 405pt.
33
35
  - Keep semantic text tags (`p`, `h1-h6`, `ul`, `ol`, `li`).
36
+ - Put local images under `<slides-dir>/assets/` and reference them as `./assets/<file>`.
37
+ - Allow `data:` URLs when the slide must be fully self-contained.
38
+ - Treat remote `https://` images as best-effort only, and never use absolute filesystem paths.
39
+ - Prefer `<img>` for slide imagery and `data-image-placeholder` when no final asset exists.
40
+ - Do not present slides for review until `slides-grab validate --slides-dir <path>` passes.
34
41
  - Do not start conversion before approval.
35
42
 
36
43
  ## Reference
@@ -1,16 +1,16 @@
1
1
  ---
2
- name: ppt-pptx-skill
3
- description: Stage 3 conversion skill for Codex. Convert approved HTML slides to PPTX/PDF and validate artifacts.
2
+ name: slides-grab-export
3
+ description: Stage 3 conversion skill for Codex. Convert approved HTML slides to PDF and to experimental / unstable PPTX/Figma outputs, then validate artifacts.
4
4
  metadata:
5
5
  short-description: Convert slides and run conversion checks
6
6
  ---
7
7
 
8
- # PPTX Conversion Skill (Codex)
8
+ # slides-grab Export Skill (Codex)
9
9
 
10
10
  Use this only after the user approves design output.
11
11
 
12
12
  ## Goal
13
- Convert reviewed slide HTML into PPTX (and optional PDF) reliably.
13
+ Convert reviewed slide HTML into PDF reliably, and into experimental / unstable PPTX/Figma outputs on a best-effort basis.
14
14
 
15
15
  ## Inputs
16
16
  - Approved `<slides-dir>/slide-*.html`
@@ -22,8 +22,8 @@ Convert reviewed slide HTML into PPTX (and optional PDF) reliably.
22
22
  ## Workflow
23
23
  1. Confirm user approval for conversion.
24
24
  2. Run conversion command:
25
- - `node .claude/skills/pptx-skill/scripts/html2pptx.js`
26
- - or `slides-grab convert --slides-dir <path>`
25
+ - `node .claude/skills/pptx-skill/scripts/html2pptx.js` (**experimental / unstable**)
26
+ - or `slides-grab convert --slides-dir <path>` (**experimental / unstable**)
27
27
  3. If requested, run PDF conversion:
28
28
  - `slides-grab pdf --slides-dir <path>`
29
29
  4. Report success/failure with actionable errors.
@@ -31,6 +31,7 @@ Convert reviewed slide HTML into PPTX (and optional PDF) reliably.
31
31
  ## Rules
32
32
  - Do not modify slide content during conversion stage unless explicitly requested.
33
33
  - If conversion fails, diagnose and fix root causes in source HTML/CSS.
34
+ - Always tell the user that PPTX and Figma export are experimental / unstable and may require manual cleanup.
34
35
 
35
36
  ## Reference
36
37
  For detailed conversion behavior and tools, use:
@@ -1,11 +1,11 @@
1
1
  ---
2
- name: ppt-plan-skill
2
+ name: slides-grab-plan
3
3
  description: Stage 1 planning skill for Codex. Build and iterate slide-outline.md until explicit user approval.
4
4
  metadata:
5
5
  short-description: Create and revise slide outline before design stage
6
6
  ---
7
7
 
8
- # PPT Plan Skill (Codex)
8
+ # slides-grab Plan Skill (Codex)
9
9
 
10
10
  Use this when the user asks to start a new presentation from scratch.
11
11
 
@@ -1,9 +1,60 @@
1
+ import { readFileSync } from 'node:fs';
1
2
  import { mkdir } from 'node:fs/promises';
2
- import { dirname } from 'node:path';
3
+ import { dirname, join } from 'node:path';
3
4
  import sharp from 'sharp';
4
5
 
6
+ import { getPackageRoot } from '../resolve.js';
7
+
5
8
  export const SLIDE_SIZE = { width: 960, height: 540 };
6
9
 
10
+ const PPT_DESIGN_SKILL_PATH = join(getPackageRoot(), 'skills', 'slides-grab-design', 'SKILL.md');
11
+ const DETAILED_DESIGN_SKILL_PATH = join(getPackageRoot(), '.claude', 'skills', 'design-skill', 'SKILL.md');
12
+ const DETAILED_DESIGN_SECTION_HEADINGS = [
13
+ '## Base Settings',
14
+ '### 4. Image Usage Rules (Local Asset / Data URL / Remote URL / Placeholder)',
15
+ '## Text Usage Rules',
16
+ '## Workflow (Stage 2: Design + Human Review)',
17
+ '## Important Notes',
18
+ ];
19
+ const DETAILED_DESIGN_SKILL_FALLBACK = [
20
+ '## Base Settings',
21
+ '',
22
+ '### Slide Size (16:9 default)',
23
+ '- Keep slide body at 720pt x 405pt.',
24
+ '- Use Pretendard as the default font stack.',
25
+ '- Include the Pretendard webfont CDN link when needed.',
26
+ '',
27
+ '### 4. Image Usage Rules (Local Asset / Data URL / Remote URL / Placeholder)',
28
+ '- Always include alt on img tags.',
29
+ '- Use ./assets/<file> as the default image contract for slide HTML.',
30
+ '- Keep slide assets in <slides-dir>/assets/.',
31
+ '- data: URLs are allowed for fully self-contained slides.',
32
+ '- Remote https:// URLs are allowed but non-deterministic and fallback only.',
33
+ '- Do not use absolute filesystem paths in slide HTML.',
34
+ '- Do not use non-body background-image for content imagery; use <img> instead.',
35
+ '- Use data-image-placeholder to reserve space when no image is available yet.',
36
+ '',
37
+ '## Text Usage Rules',
38
+ '- All text must be inside <p>, <h1>-<h6>, <ul>, <ol>, or <li>.',
39
+ '- Never place text directly in <div> or <span>.',
40
+ '',
41
+ '## Workflow (Stage 2: Design + Human Review)',
42
+ '- After slide generation or edits, run node scripts/build-viewer.js --slides-dir <path>.',
43
+ '- Edit only the relevant HTML file during revision loops.',
44
+ '- Never start PPTX conversion without explicit approval.',
45
+ '- Never forget to rebuild the viewer after slide changes.',
46
+ '',
47
+ '## Important Notes',
48
+ '- CSS gradients are not supported in PowerPoint conversion; replace them with background images.',
49
+ '- Always include the Pretendard CDN link.',
50
+ '- Use ./assets/<file> from each slide-XX.html and avoid absolute filesystem paths.',
51
+ '- Always include # prefix in CSS colors.',
52
+ '- Never place text directly in div/span.',
53
+ ].join('\n');
54
+
55
+ let cachedPptDesignSkillPrompt = null;
56
+ let cachedDetailedDesignSkillPrompt = null;
57
+
7
58
  function clamp(value, min, max) {
8
59
  return Math.min(max, Math.max(min, value));
9
60
  }
@@ -77,6 +128,67 @@ function formatTargets(targets) {
77
128
  });
78
129
  }
79
130
 
131
+ export function getPptDesignSkillPrompt() {
132
+ if (cachedPptDesignSkillPrompt !== null) {
133
+ return cachedPptDesignSkillPrompt;
134
+ }
135
+
136
+ try {
137
+ cachedPptDesignSkillPrompt = readFileSync(PPT_DESIGN_SKILL_PATH, 'utf8').trim();
138
+ } catch {
139
+ cachedPptDesignSkillPrompt = '';
140
+ }
141
+
142
+ return cachedPptDesignSkillPrompt;
143
+ }
144
+
145
+ function extractMarkdownSection(markdown, heading) {
146
+ const lines = markdown.split('\n');
147
+ const startIndex = lines.findIndex((line) => line.trim() === heading.trim());
148
+ if (startIndex === -1) {
149
+ return '';
150
+ }
151
+
152
+ const levelMatch = heading.match(/^(#+)\s/);
153
+ const headingLevel = levelMatch ? levelMatch[1].length : null;
154
+ if (!headingLevel) {
155
+ return '';
156
+ }
157
+
158
+ const extracted = [lines[startIndex]];
159
+ for (let i = startIndex + 1; i < lines.length; i += 1) {
160
+ const line = lines[i];
161
+ const nextHeadingMatch = line.match(/^(#+)\s/);
162
+ if (nextHeadingMatch && nextHeadingMatch[1].length <= headingLevel) {
163
+ break;
164
+ }
165
+ extracted.push(line);
166
+ }
167
+
168
+ return extracted.join('\n').trim();
169
+ }
170
+
171
+ export function getDetailedDesignSkillPrompt() {
172
+ if (cachedDetailedDesignSkillPrompt !== null) {
173
+ return cachedDetailedDesignSkillPrompt;
174
+ }
175
+
176
+ try {
177
+ const markdown = readFileSync(DETAILED_DESIGN_SKILL_PATH, 'utf8');
178
+ const sections = DETAILED_DESIGN_SECTION_HEADINGS
179
+ .map((heading) => extractMarkdownSection(markdown, heading))
180
+ .filter(Boolean);
181
+
182
+ cachedDetailedDesignSkillPrompt = sections.length > 0
183
+ ? sections.join('\n\n')
184
+ : DETAILED_DESIGN_SKILL_FALLBACK;
185
+ } catch {
186
+ cachedDetailedDesignSkillPrompt = DETAILED_DESIGN_SKILL_FALLBACK;
187
+ }
188
+
189
+ return cachedDetailedDesignSkillPrompt;
190
+ }
191
+
80
192
  export function buildCodexEditPrompt({ slideFile, slidePath, userPrompt, selections = [] }) {
81
193
  const sanitizedPrompt = typeof userPrompt === 'string' ? userPrompt.trim() : '';
82
194
  if (!sanitizedPrompt) {
@@ -103,9 +215,30 @@ export function buildCodexEditPrompt({ slideFile, slidePath, userPrompt, selecti
103
215
  ];
104
216
  });
105
217
 
218
+ const pptDesignSkillPrompt = getPptDesignSkillPrompt();
219
+ const skillLines = pptDesignSkillPrompt
220
+ ? [
221
+ 'Project skill guidance (follow strictly):',
222
+ `Source: ${PPT_DESIGN_SKILL_PATH}`,
223
+ pptDesignSkillPrompt,
224
+ '',
225
+ ]
226
+ : [];
227
+ const detailedDesignSkillPrompt = getDetailedDesignSkillPrompt();
228
+ const detailedSkillLines = detailedDesignSkillPrompt
229
+ ? [
230
+ 'Detailed design/export guardrails (selected from the full design system):',
231
+ `Primary source: ${DETAILED_DESIGN_SKILL_PATH}`,
232
+ detailedDesignSkillPrompt,
233
+ '',
234
+ ]
235
+ : [];
236
+
106
237
  return [
107
238
  `Edit ${normalizedSlidePath} only.`,
108
239
  '',
240
+ ...skillLines,
241
+ ...detailedSkillLines,
109
242
  'User edit request:',
110
243
  sanitizedPrompt,
111
244
  '',
@@ -116,6 +249,8 @@ export function buildCodexEditPrompt({ slideFile, slidePath, userPrompt, selecti
116
249
  '- Keep existing structure/content unless the request requires a change.',
117
250
  '- Keep slide dimensions at 720pt x 405pt.',
118
251
  '- Keep text in semantic tags (<p>, <h1>-<h6>, <ul>, <ol>, <li>).',
252
+ '- Keep local assets under ./assets/ and preserve portable relative paths.',
253
+ '- Do not persist runtime-only editor/viewer injections such as <base>, debug scripts, or viewer wrapper markup into the slide file.',
119
254
  '- Return after applying the change.',
120
255
  ].join('\n');
121
256
  }
@@ -149,6 +149,13 @@ popoverBgColorInput.addEventListener('input', () => {
149
149
  }, 'Background color updated.', { delay: 300 });
150
150
  });
151
151
 
152
+ function hasEditableFocus() {
153
+ const activeElement = document.activeElement;
154
+ if (!(activeElement instanceof HTMLElement)) return false;
155
+ if (activeElement.matches('input, textarea, select')) return true;
156
+ return activeElement.isContentEditable;
157
+ }
158
+
152
159
  // Style toggles
153
160
  toggleBold.addEventListener('click', () => {
154
161
  mutateSelectedObject((el) => {
@@ -199,9 +206,9 @@ alignRight.addEventListener('click', () => {
199
206
 
200
207
  // Global keyboard
201
208
  document.addEventListener('keydown', (event) => {
202
- const inPromptField = document.activeElement === promptInput;
209
+ const inEditableField = hasEditableFocus();
203
210
 
204
- if (state.toolMode === TOOL_MODE_SELECT && (event.ctrlKey || event.metaKey) && !inPromptField) {
211
+ if (state.toolMode === TOOL_MODE_SELECT && (event.ctrlKey || event.metaKey) && !inEditableField) {
205
212
  const key = event.key.toLowerCase();
206
213
  if (key === 'b') { event.preventDefault(); if (!toggleBold.disabled) toggleBold.click(); return; }
207
214
  if (key === 'i') { event.preventDefault(); if (!toggleItalic.disabled) toggleItalic.click(); return; }
@@ -219,7 +226,7 @@ document.addEventListener('keydown', (event) => {
219
226
  return;
220
227
  }
221
228
 
222
- if (inPromptField) return;
229
+ if (inEditableField) return;
223
230
 
224
231
  if (event.key === 'ArrowLeft') {
225
232
  event.preventDefault();
package/src/figma.js ADDED
@@ -0,0 +1,63 @@
1
+ import { mkdir } from 'node:fs/promises';
2
+ import { basename, dirname, extname, join, resolve } from 'node:path';
3
+
4
+ export const DEFAULT_FIGMA_SUFFIX = '-figma.pptx';
5
+ export const SLIDE_FILE_PATTERN = /^slide-.*\.html$/i;
6
+ export const FIGMA_EXPORT_LAYOUT_NAME = 'SLIDES_GRAB_STANDARD';
7
+ export const SLIDE_WIDTH_INCHES = 10;
8
+ export const SLIDE_HEIGHT_INCHES = 5.625;
9
+
10
+ export function buildDefaultFigmaOutput(slidesDir) {
11
+ const absoluteSlidesDir = resolve(slidesDir);
12
+ const deckName = basename(absoluteSlidesDir);
13
+ const parentDir = dirname(absoluteSlidesDir);
14
+ return join(parentDir, `${deckName}${DEFAULT_FIGMA_SUFFIX}`);
15
+ }
16
+
17
+ export function normalizeFigmaOutput(slidesDir, output) {
18
+ if (typeof output === 'string' && output.trim() !== '') {
19
+ const trimmed = output.trim();
20
+ return extname(trimmed).toLowerCase() === '.pptx' ? trimmed : `${trimmed}.pptx`;
21
+ }
22
+
23
+ return buildDefaultFigmaOutput(slidesDir);
24
+ }
25
+
26
+ export function getFigmaImportCaveats() {
27
+ return [
28
+ 'Figma export is experimental / unstable. Figma imports PPTX best-effort, and complex layouts, shadows, and grouped elements can shift or flatten.',
29
+ 'Fonts are resolved inside Figma. If Pretendard is unavailable there, expect substitution and reflow.',
30
+ 'Import is one-way. Re-importing creates a new Figma Slides file instead of updating the existing one.',
31
+ 'Review every imported slide, especially chart-heavy slides and text near slide edges.',
32
+ ];
33
+ }
34
+
35
+ export function getFigmaManualImportInstructions() {
36
+ return 'Figma Slides -> Import -> select the generated .pptx file.';
37
+ }
38
+
39
+ export function configureFigmaExportPresentation(pres) {
40
+ pres.defineLayout({
41
+ name: FIGMA_EXPORT_LAYOUT_NAME,
42
+ width: SLIDE_WIDTH_INCHES,
43
+ height: SLIDE_HEIGHT_INCHES,
44
+ });
45
+ pres.layout = FIGMA_EXPORT_LAYOUT_NAME;
46
+ return pres;
47
+ }
48
+
49
+ export async function ensureOutputDirectory(outputFile) {
50
+ await mkdir(dirname(resolve(outputFile)), { recursive: true });
51
+ }
52
+
53
+ function toSlideOrder(fileName) {
54
+ const match = fileName.match(/\d+/);
55
+ return match ? Number.parseInt(match[0], 10) : Number.POSITIVE_INFINITY;
56
+ }
57
+
58
+ export function sortFigmaSlideFiles(a, b) {
59
+ const orderA = toSlideOrder(a);
60
+ const orderB = toSlideOrder(b);
61
+ if (orderA !== orderB) return orderA - orderB;
62
+ return a.localeCompare(b);
63
+ }