slides-grab 1.2.0 → 1.2.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.
- package/README.md +49 -7
- package/bin/ppt-agent.js +76 -31
- package/package.json +8 -3
- package/scripts/download-video.js +213 -0
- package/scripts/editor-server.js +68 -8
- package/scripts/generate-image.js +49 -0
- package/scripts/html2pdf.js +134 -0
- package/skills/slides-grab/SKILL.md +17 -13
- package/skills/slides-grab/references/presentation-workflow-reference.md +17 -12
- package/skills/slides-grab-design/SKILL.md +22 -13
- package/skills/slides-grab-design/references/design-rules.md +13 -7
- package/skills/slides-grab-design/references/design-system-full.md +7 -19
- package/skills/slides-grab-design/references/detailed-design-rules.md +9 -3
- package/skills/slides-grab-plan/SKILL.md +7 -5
- package/src/design-styles-data.js +1928 -0
- package/src/design-styles.js +55 -0
- package/src/editor/codex-edit.js +36 -48
- package/src/editor/editor-codex-prompt.md +48 -0
- package/src/image-contract.js +138 -31
- package/src/nano-banana.js +417 -0
- package/src/resolve.js +2 -51
- package/src/validation/core.js +151 -18
- package/templates/design-styles/README.md +19 -0
- package/templates/design-styles/preview.html +3356 -0
- package/themes/corporate.css +0 -8
- package/themes/executive.css +0 -10
- package/themes/modern-dark.css +0 -9
- package/themes/sage.css +0 -9
- package/themes/warm.css +0 -8
package/src/validation/core.js
CHANGED
|
@@ -4,6 +4,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
4
4
|
import { chromium } from 'playwright';
|
|
5
5
|
import {
|
|
6
6
|
buildImageContractReport,
|
|
7
|
+
buildVideoContractReport,
|
|
7
8
|
classifyImageSource,
|
|
8
9
|
resolveSlideSourcePath,
|
|
9
10
|
} from '../image-contract.js';
|
|
@@ -114,6 +115,18 @@ async function fileExists(filePath) {
|
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
function shouldSkipLocalAssetExistenceCheck(classification) {
|
|
119
|
+
return (
|
|
120
|
+
classification.kind === 'empty'
|
|
121
|
+
|| classification.kind === 'data-url'
|
|
122
|
+
|| classification.kind === 'remote-url'
|
|
123
|
+
|| classification.kind === 'remote-url-insecure'
|
|
124
|
+
|| classification.kind === 'absolute-filesystem-path'
|
|
125
|
+
|| classification.kind === 'root-relative-path'
|
|
126
|
+
|| classification.kind === 'other-scheme'
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
117
130
|
async function inspectImageContract(slidesDir, fileName, inspection) {
|
|
118
131
|
const critical = [];
|
|
119
132
|
const warning = [];
|
|
@@ -135,15 +148,7 @@ async function inspectImageContract(slidesDir, fileName, inspection) {
|
|
|
135
148
|
target.push(issue);
|
|
136
149
|
}
|
|
137
150
|
|
|
138
|
-
if (
|
|
139
|
-
classification.kind === 'empty'
|
|
140
|
-
|| classification.kind === 'data-url'
|
|
141
|
-
|| classification.kind === 'remote-url'
|
|
142
|
-
|| classification.kind === 'remote-url-insecure'
|
|
143
|
-
|| classification.kind === 'absolute-filesystem-path'
|
|
144
|
-
|| classification.kind === 'root-relative-path'
|
|
145
|
-
|| classification.kind === 'other-scheme'
|
|
146
|
-
) {
|
|
151
|
+
if (shouldSkipLocalAssetExistenceCheck(classification)) {
|
|
147
152
|
continue;
|
|
148
153
|
}
|
|
149
154
|
|
|
@@ -201,15 +206,7 @@ async function inspectImageContract(slidesDir, fileName, inspection) {
|
|
|
201
206
|
|
|
202
207
|
for (const source of background.urls) {
|
|
203
208
|
const classification = classifyImageSource(source);
|
|
204
|
-
if (
|
|
205
|
-
classification.kind === 'empty'
|
|
206
|
-
|| classification.kind === 'data-url'
|
|
207
|
-
|| classification.kind === 'remote-url'
|
|
208
|
-
|| classification.kind === 'remote-url-insecure'
|
|
209
|
-
|| classification.kind === 'absolute-filesystem-path'
|
|
210
|
-
|| classification.kind === 'root-relative-path'
|
|
211
|
-
|| classification.kind === 'other-scheme'
|
|
212
|
-
) {
|
|
209
|
+
if (shouldSkipLocalAssetExistenceCheck(classification)) {
|
|
213
210
|
continue;
|
|
214
211
|
}
|
|
215
212
|
|
|
@@ -233,6 +230,94 @@ async function inspectImageContract(slidesDir, fileName, inspection) {
|
|
|
233
230
|
return { critical, warning };
|
|
234
231
|
}
|
|
235
232
|
|
|
233
|
+
async function inspectVideoContract(slidesDir, fileName, inspection) {
|
|
234
|
+
const critical = [];
|
|
235
|
+
const warning = [];
|
|
236
|
+
const slidePath = join(slidesDir, fileName);
|
|
237
|
+
|
|
238
|
+
for (const video of inspection.videos) {
|
|
239
|
+
const sources = [...new Set([
|
|
240
|
+
typeof video.src === 'string' ? video.src : '',
|
|
241
|
+
...video.sources,
|
|
242
|
+
].map((source) => source.trim()).filter(Boolean))];
|
|
243
|
+
|
|
244
|
+
const issues = buildVideoContractReport({
|
|
245
|
+
slideFile: fileName,
|
|
246
|
+
sources: sources.map((source) => ({
|
|
247
|
+
element: buildElementPath(video.element),
|
|
248
|
+
source,
|
|
249
|
+
})),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
for (const issue of issues) {
|
|
253
|
+
const target = issue.severity === 'critical' ? critical : warning;
|
|
254
|
+
target.push(issue);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for (const source of sources) {
|
|
258
|
+
const classification = classifyImageSource(source);
|
|
259
|
+
if (shouldSkipLocalAssetExistenceCheck(classification)) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const assetPath = resolveSlideSourcePath(slidePath, source);
|
|
264
|
+
if (!(await fileExists(assetPath))) {
|
|
265
|
+
critical.push(buildImageIssue(
|
|
266
|
+
'critical',
|
|
267
|
+
'missing-local-video-asset',
|
|
268
|
+
'Local video asset is missing.',
|
|
269
|
+
{
|
|
270
|
+
slide: fileName,
|
|
271
|
+
element: buildElementPath(video.element),
|
|
272
|
+
source,
|
|
273
|
+
assetPath,
|
|
274
|
+
},
|
|
275
|
+
));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const poster = typeof video.poster === 'string' ? video.poster.trim() : '';
|
|
280
|
+
if (!poster) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const posterIssues = buildImageContractReport({
|
|
285
|
+
slideFile: fileName,
|
|
286
|
+
sources: [{
|
|
287
|
+
element: buildElementPath(video.element),
|
|
288
|
+
source: poster,
|
|
289
|
+
}],
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
for (const issue of posterIssues) {
|
|
293
|
+
const target = issue.severity === 'critical' ? critical : warning;
|
|
294
|
+
target.push(issue);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const posterClassification = classifyImageSource(poster);
|
|
298
|
+
if (shouldSkipLocalAssetExistenceCheck(posterClassification)) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const posterPath = resolveSlideSourcePath(slidePath, poster);
|
|
303
|
+
if (!(await fileExists(posterPath))) {
|
|
304
|
+
critical.push(buildImageIssue(
|
|
305
|
+
'critical',
|
|
306
|
+
'missing-local-video-poster-asset',
|
|
307
|
+
'Video poster image is missing.',
|
|
308
|
+
{
|
|
309
|
+
slide: fileName,
|
|
310
|
+
element: buildElementPath(video.element),
|
|
311
|
+
source: poster,
|
|
312
|
+
assetPath: posterPath,
|
|
313
|
+
},
|
|
314
|
+
));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return { critical, warning };
|
|
319
|
+
}
|
|
320
|
+
|
|
236
321
|
export async function findSlideFiles(slidesDir) {
|
|
237
322
|
const entries = await readdir(slidesDir, { withFileTypes: true });
|
|
238
323
|
return entries
|
|
@@ -417,6 +502,31 @@ export async function inspectSlide(page, fileName, slidesDir) {
|
|
|
417
502
|
return values;
|
|
418
503
|
};
|
|
419
504
|
|
|
505
|
+
// Detect persisted runtime-only editor/viewer injections.
|
|
506
|
+
const baseElements = Array.from(document.querySelectorAll('head base[href]'));
|
|
507
|
+
for (const base of baseElements) {
|
|
508
|
+
critical.push({
|
|
509
|
+
code: 'persisted-editor-base-tag',
|
|
510
|
+
message: 'Slide contains a <base> tag injected by the editor runtime. Remove it so asset paths resolve correctly outside the editor.',
|
|
511
|
+
element: 'head > base',
|
|
512
|
+
detail: base.getAttribute('href'),
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const editorScriptSignatures = ['[slides-grab:image]', '[slides-grab:'];
|
|
517
|
+
const scripts = Array.from(document.querySelectorAll('head script:not([src])'));
|
|
518
|
+
for (const script of scripts) {
|
|
519
|
+
const text = script.textContent || '';
|
|
520
|
+
const matched = editorScriptSignatures.some((sig) => text.includes(sig));
|
|
521
|
+
if (matched) {
|
|
522
|
+
critical.push({
|
|
523
|
+
code: 'persisted-editor-script',
|
|
524
|
+
message: 'Slide contains a runtime-only editor script that should not be persisted. Remove the injected <script> block.',
|
|
525
|
+
element: 'head > script',
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
420
530
|
const bodyRect = document.body.getBoundingClientRect();
|
|
421
531
|
const frameRect = {
|
|
422
532
|
left: bodyRect.left,
|
|
@@ -519,6 +629,15 @@ export async function inspectSlide(page, fileName, slidesDir) {
|
|
|
519
629
|
alt: (element.getAttribute('alt') || '').trim(),
|
|
520
630
|
}));
|
|
521
631
|
|
|
632
|
+
const videos = Array.from(document.querySelectorAll('video')).map((element) => ({
|
|
633
|
+
element: elementPath(element),
|
|
634
|
+
src: (element.getAttribute('src') || '').trim(),
|
|
635
|
+
sources: Array.from(element.querySelectorAll('source[src]'))
|
|
636
|
+
.map((source) => (source.getAttribute('src') || '').trim())
|
|
637
|
+
.filter(Boolean),
|
|
638
|
+
poster: (element.getAttribute('poster') || '').trim(),
|
|
639
|
+
}));
|
|
640
|
+
|
|
522
641
|
const backgrounds = [document.body, ...Array.from(document.body.querySelectorAll('*'))]
|
|
523
642
|
.map((element) => {
|
|
524
643
|
const computedBackgroundImage = window.getComputedStyle(element).backgroundImage;
|
|
@@ -539,6 +658,7 @@ export async function inspectSlide(page, fileName, slidesDir) {
|
|
|
539
658
|
critical,
|
|
540
659
|
warning,
|
|
541
660
|
images,
|
|
661
|
+
videos,
|
|
542
662
|
backgrounds,
|
|
543
663
|
};
|
|
544
664
|
},
|
|
@@ -553,9 +673,14 @@ export async function inspectSlide(page, fileName, slidesDir) {
|
|
|
553
673
|
images: inspection.images,
|
|
554
674
|
backgrounds: inspection.backgrounds,
|
|
555
675
|
});
|
|
676
|
+
const videoContractIssues = await inspectVideoContract(slidesDir, fileName, {
|
|
677
|
+
videos: inspection.videos,
|
|
678
|
+
});
|
|
556
679
|
|
|
557
680
|
inspection.critical.push(...imageContractIssues.critical);
|
|
558
681
|
inspection.warning.push(...imageContractIssues.warning);
|
|
682
|
+
inspection.critical.push(...videoContractIssues.critical);
|
|
683
|
+
inspection.warning.push(...videoContractIssues.warning);
|
|
559
684
|
|
|
560
685
|
const summary = {
|
|
561
686
|
criticalCount: inspection.critical.length,
|
|
@@ -620,13 +745,21 @@ export function formatValidationFailureForExport(result, exportLabel = 'Export')
|
|
|
620
745
|
|
|
621
746
|
const EXPORT_BLOCKING_IMAGE_CONTRACT_CODES = new Set([
|
|
622
747
|
'absolute-filesystem-image-path',
|
|
748
|
+
'absolute-filesystem-video-path',
|
|
623
749
|
'missing-local-asset',
|
|
624
750
|
'missing-local-background-asset',
|
|
751
|
+
'missing-local-video-asset',
|
|
752
|
+
'missing-local-video-poster-asset',
|
|
625
753
|
'remote-background-image-url',
|
|
626
754
|
'remote-background-image-url-insecure',
|
|
627
755
|
'remote-image-url',
|
|
628
756
|
'remote-image-url-insecure',
|
|
757
|
+
'remote-video-url',
|
|
758
|
+
'remote-video-url-insecure',
|
|
629
759
|
'root-relative-image-path',
|
|
760
|
+
'root-relative-video-path',
|
|
761
|
+
'unsupported-image-url-scheme',
|
|
762
|
+
'unsupported-video-url-scheme',
|
|
630
763
|
'unsupported-background-image',
|
|
631
764
|
]);
|
|
632
765
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Design Style Collections
|
|
2
|
+
|
|
3
|
+
slides-grab bundles 35 design styles: 30 derived from [corazzon/pptx-design-styles](https://github.com/corazzon/pptx-design-styles) (MIT) plus 5 slides-grab originals.
|
|
4
|
+
|
|
5
|
+
These styles are reference directions for slide generation, not drop-in HTML slide templates. Agents may also design fully custom visuals beyond the bundled collection.
|
|
6
|
+
|
|
7
|
+
## Recommended workflow
|
|
8
|
+
|
|
9
|
+
1. `slides-grab list-styles`
|
|
10
|
+
2. `slides-grab preview-styles` to open the visual gallery in browser
|
|
11
|
+
3. Tell the agent which style to use, or ask for something custom
|
|
12
|
+
|
|
13
|
+
The preview/select flow is intentionally simple: it keeps design approval inside the CLI and a local HTML preview page instead of adding a separate app.
|
|
14
|
+
|
|
15
|
+
## Citation
|
|
16
|
+
|
|
17
|
+
- Upstream collection: `corazzon/pptx-design-styles`
|
|
18
|
+
- URL: <https://github.com/corazzon/pptx-design-styles>
|
|
19
|
+
- Reference used in this repo: `references/styles.md`
|