slides-grab 1.2.5 → 1.3.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.
- package/README-ko.md +256 -0
- package/README.md +27 -9
- package/bin/ppt-agent.js +125 -4
- package/package.json +11 -7
- package/scripts/editor-server.js +170 -14
- package/scripts/generate-image.js +44 -0
- package/skills/slides-grab/SKILL.md +3 -3
- package/skills/slides-grab/references/presentation-workflow-reference.md +3 -3
- package/skills/slides-grab-design/SKILL.md +8 -4
- package/skills/slides-grab-design/references/design-rules.md +3 -3
- package/skills/slides-grab-design/references/detailed-design-rules.md +2 -2
- package/skills/slides-grab-plan/SKILL.md +17 -4
- package/skills/slides-grab-plan/references/design-md-to-slides-conversion.md +135 -0
- package/src/design-import.js +164 -0
- package/src/design-md-parser.js +415 -0
- package/src/design-styles.js +67 -2
- package/src/editor/codex-edit.js +43 -13
- package/src/editor/edit-subprocess.js +78 -5
- package/src/editor/editor-codex-prompt.md +2 -2
- package/src/editor/editor.html +3 -0
- package/src/editor/js/editor-state.js +2 -2
- package/src/editor/js/model-registry.js +38 -0
- package/src/god-tibo-imagen.js +135 -0
- package/src/nano-banana.js +332 -27
package/scripts/editor-server.js
CHANGED
|
@@ -12,12 +12,17 @@ import {
|
|
|
12
12
|
buildCodexEditPrompt,
|
|
13
13
|
buildCodexExecArgs,
|
|
14
14
|
buildClaudeExecArgs,
|
|
15
|
-
CLAUDE_MODELS,
|
|
16
|
-
isClaudeModel,
|
|
17
15
|
normalizeSelection,
|
|
18
16
|
scaleSelectionToScreenshot,
|
|
19
17
|
writeAnnotatedScreenshot,
|
|
20
18
|
} from '../src/editor/codex-edit.js';
|
|
19
|
+
import {
|
|
20
|
+
ALL_MODELS,
|
|
21
|
+
CODEX_MODELS,
|
|
22
|
+
CLAUDE_MODELS,
|
|
23
|
+
DEFAULT_CODEX_MODEL,
|
|
24
|
+
isClaudeModel,
|
|
25
|
+
} from '../src/editor/js/model-registry.js';
|
|
21
26
|
import {
|
|
22
27
|
parseEditTimeoutMs,
|
|
23
28
|
runEditSubprocess,
|
|
@@ -50,9 +55,7 @@ async function loadDeps() {
|
|
|
50
55
|
|
|
51
56
|
const DEFAULT_PORT = 3456;
|
|
52
57
|
const DEFAULT_SLIDES_DIR = 'slides';
|
|
53
|
-
|
|
54
|
-
const ALL_MODELS = [...CODEX_MODELS, ...CLAUDE_MODELS];
|
|
55
|
-
const DEFAULT_CODEX_MODEL = CODEX_MODELS[0];
|
|
58
|
+
|
|
56
59
|
const SLIDE_FILE_PATTERN = /^slide-.*\.html$/i;
|
|
57
60
|
const PORT_PROBE_HOSTS = ['::', '127.0.0.1'];
|
|
58
61
|
const PORT_PROBE_IGNORED_CODES = new Set(['EAFNOSUPPORT', 'EADDRNOTAVAIL']);
|
|
@@ -325,21 +328,28 @@ function mirrorRunLog(onLog) {
|
|
|
325
328
|
};
|
|
326
329
|
}
|
|
327
330
|
|
|
328
|
-
function spawnCodexEdit({ prompt, imagePath, model, cwd, onLog }) {
|
|
331
|
+
function spawnCodexEdit({ prompt, imagePath, model, cwd, onLog, onChild, signal }) {
|
|
329
332
|
const codexBin = process.env.PPT_AGENT_CODEX_BIN || 'codex';
|
|
330
333
|
const args = buildCodexExecArgs({ prompt, imagePath, model });
|
|
331
334
|
return runEditSubprocess({
|
|
332
335
|
bin: codexBin,
|
|
333
336
|
args,
|
|
334
337
|
cwd,
|
|
335
|
-
|
|
338
|
+
// Close stdin (`'ignore'`) so the Codex CLI does not wait for additional
|
|
339
|
+
// piped instructions. Recent Codex versions (>=0.125) print
|
|
340
|
+
// "Reading additional input from stdin..." and block forever when stdin
|
|
341
|
+
// is left open as a pipe even though the prompt is already passed via
|
|
342
|
+
// the trailing argv. This mirrors `spawnClaudeEdit` below.
|
|
343
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
336
344
|
timeoutMs: EDIT_TIMEOUT_MS,
|
|
337
345
|
engineLabel: 'Codex',
|
|
338
346
|
onLog: mirrorRunLog(onLog),
|
|
347
|
+
onChild,
|
|
348
|
+
signal,
|
|
339
349
|
});
|
|
340
350
|
}
|
|
341
351
|
|
|
342
|
-
function spawnClaudeEdit({ prompt, imagePath, model, cwd, onLog }) {
|
|
352
|
+
function spawnClaudeEdit({ prompt, imagePath, model, cwd, onLog, onChild, signal }) {
|
|
343
353
|
const claudeBin = process.env.PPT_AGENT_CLAUDE_BIN || 'claude';
|
|
344
354
|
const args = buildClaudeExecArgs({ prompt, imagePath, model });
|
|
345
355
|
|
|
@@ -356,6 +366,8 @@ function spawnClaudeEdit({ prompt, imagePath, model, cwd, onLog }) {
|
|
|
356
366
|
timeoutMs: EDIT_TIMEOUT_MS,
|
|
357
367
|
engineLabel: 'Claude',
|
|
358
368
|
onLog: mirrorRunLog(onLog),
|
|
369
|
+
onChild,
|
|
370
|
+
signal,
|
|
359
371
|
});
|
|
360
372
|
}
|
|
361
373
|
|
|
@@ -480,6 +492,79 @@ async function startServer(opts) {
|
|
|
480
492
|
|
|
481
493
|
const runStore = createRunStore();
|
|
482
494
|
|
|
495
|
+
const childProcessesByRunId = new Map();
|
|
496
|
+
const abortControllersByRunId = new Map();
|
|
497
|
+
|
|
498
|
+
function registerAbortController(runId, abortController) {
|
|
499
|
+
if (!abortController) return;
|
|
500
|
+
abortControllersByRunId.set(runId, abortController);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function trackChild(runId, child) {
|
|
504
|
+
if (!child || typeof child.kill !== 'function') return;
|
|
505
|
+
childProcessesByRunId.set(runId, child);
|
|
506
|
+
child.once('close', () => {
|
|
507
|
+
if (childProcessesByRunId.get(runId) === child) {
|
|
508
|
+
childProcessesByRunId.delete(runId);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function killTrackedChild(runId, { reason } = {}) {
|
|
514
|
+
const ac = abortControllersByRunId.get(runId);
|
|
515
|
+
if (ac && !ac.signal.aborted) {
|
|
516
|
+
try {
|
|
517
|
+
ac.abort();
|
|
518
|
+
} catch {}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const child = childProcessesByRunId.get(runId);
|
|
522
|
+
if (!child) return Boolean(ac);
|
|
523
|
+
childProcessesByRunId.delete(runId);
|
|
524
|
+
if (child.killed || child.exitCode != null) return true;
|
|
525
|
+
if (reason) {
|
|
526
|
+
process.stderr.write(`[editor] killing run ${runId}: ${reason}\n`);
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
child.kill('SIGTERM');
|
|
530
|
+
} catch {}
|
|
531
|
+
const forceKill = setTimeout(() => {
|
|
532
|
+
if (!child.killed && child.exitCode == null) {
|
|
533
|
+
try {
|
|
534
|
+
child.kill('SIGKILL');
|
|
535
|
+
} catch {}
|
|
536
|
+
}
|
|
537
|
+
}, 5_000);
|
|
538
|
+
forceKill.unref?.();
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function killAllTrackedChildren({ reason, signal = 'SIGTERM' } = {}) {
|
|
543
|
+
for (const ac of abortControllersByRunId.values()) {
|
|
544
|
+
if (!ac.signal.aborted) {
|
|
545
|
+
try {
|
|
546
|
+
ac.abort();
|
|
547
|
+
} catch {}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
abortControllersByRunId.clear();
|
|
551
|
+
|
|
552
|
+
const ids = Array.from(childProcessesByRunId.keys());
|
|
553
|
+
for (const runId of ids) {
|
|
554
|
+
const child = childProcessesByRunId.get(runId);
|
|
555
|
+
if (!child) continue;
|
|
556
|
+
childProcessesByRunId.delete(runId);
|
|
557
|
+
if (child.killed || child.exitCode != null) continue;
|
|
558
|
+
if (reason) {
|
|
559
|
+
process.stderr.write(`[editor] killing run ${runId}: ${reason}\n`);
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
child.kill(signal);
|
|
563
|
+
} catch {}
|
|
564
|
+
}
|
|
565
|
+
return ids.length;
|
|
566
|
+
}
|
|
567
|
+
|
|
483
568
|
const app = express();
|
|
484
569
|
app.use(express.json({ limit: '5mb' }));
|
|
485
570
|
app.use('/js', express.static(join(PACKAGE_ROOT, 'src', 'editor', 'js')));
|
|
@@ -692,6 +777,18 @@ async function startServer(opts) {
|
|
|
692
777
|
const screenshotPath = join(tmpPath, 'slide.png');
|
|
693
778
|
const annotatedPath = join(tmpPath, 'slide-annotated.png');
|
|
694
779
|
|
|
780
|
+
const abortController = new AbortController();
|
|
781
|
+
registerAbortController(runId, abortController);
|
|
782
|
+
let clientDisconnected = false;
|
|
783
|
+
const handleClientClose = () => {
|
|
784
|
+
if (clientDisconnected) return;
|
|
785
|
+
if (res.writableEnded) return;
|
|
786
|
+
clientDisconnected = true;
|
|
787
|
+
killTrackedChild(runId, { reason: 'client disconnected before /api/apply finished' });
|
|
788
|
+
};
|
|
789
|
+
req.on('close', handleClientClose);
|
|
790
|
+
res.on('close', handleClientClose);
|
|
791
|
+
|
|
695
792
|
try {
|
|
696
793
|
await withScreenshotPage(async (page) => {
|
|
697
794
|
await screenshotMod.captureSlideScreenshot(
|
|
@@ -719,6 +816,7 @@ async function startServer(opts) {
|
|
|
719
816
|
userPrompt: prompt,
|
|
720
817
|
slideMode: opts.mode,
|
|
721
818
|
selections: normalizedSelections,
|
|
819
|
+
designBaseDir: slidesDirectory,
|
|
722
820
|
});
|
|
723
821
|
|
|
724
822
|
const usesClaude = isClaudeModel(selectedModel);
|
|
@@ -732,16 +830,26 @@ async function startServer(opts) {
|
|
|
732
830
|
runStore.appendLog(runId, chunk);
|
|
733
831
|
broadcastSSE('applyLog', { runId, slide, stream, chunk });
|
|
734
832
|
},
|
|
833
|
+
onChild: (child) => trackChild(runId, child),
|
|
834
|
+
signal: abortController.signal,
|
|
735
835
|
});
|
|
736
836
|
|
|
737
837
|
const engineLabel = isClaudeModel(selectedModel) ? 'Claude' : 'Codex';
|
|
738
|
-
const
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
838
|
+
const aborted = Boolean(result.aborted);
|
|
839
|
+
const success = !aborted && result.code === 0;
|
|
840
|
+
let message;
|
|
841
|
+
if (aborted) {
|
|
842
|
+
message = result.abortMessage || `${engineLabel} edit was aborted.`;
|
|
843
|
+
} else if (success) {
|
|
844
|
+
message = `${engineLabel} edit completed.`;
|
|
845
|
+
} else {
|
|
846
|
+
message = result.timeoutMessage || `${engineLabel} exited with code ${result.code}.`;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const status = aborted ? 'aborted' : success ? 'success' : 'failed';
|
|
742
850
|
|
|
743
851
|
runStore.finishRun(runId, {
|
|
744
|
-
status
|
|
852
|
+
status,
|
|
745
853
|
code: result.code,
|
|
746
854
|
message,
|
|
747
855
|
});
|
|
@@ -751,14 +859,20 @@ async function startServer(opts) {
|
|
|
751
859
|
slide,
|
|
752
860
|
model: selectedModel,
|
|
753
861
|
success,
|
|
862
|
+
aborted,
|
|
754
863
|
code: result.code,
|
|
755
864
|
message,
|
|
756
865
|
});
|
|
757
866
|
broadcastRunsSnapshot();
|
|
758
867
|
|
|
868
|
+
if (clientDisconnected || res.writableEnded) {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
|
|
759
872
|
res.json({
|
|
760
873
|
...runSummary,
|
|
761
874
|
success,
|
|
875
|
+
aborted,
|
|
762
876
|
runId,
|
|
763
877
|
model: selectedModel,
|
|
764
878
|
code: result.code,
|
|
@@ -768,7 +882,7 @@ async function startServer(opts) {
|
|
|
768
882
|
const message = error instanceof Error ? error.message : String(error);
|
|
769
883
|
|
|
770
884
|
runStore.finishRun(runId, {
|
|
771
|
-
status: 'failed',
|
|
885
|
+
status: clientDisconnected ? 'aborted' : 'failed',
|
|
772
886
|
code: -1,
|
|
773
887
|
message,
|
|
774
888
|
});
|
|
@@ -778,22 +892,40 @@ async function startServer(opts) {
|
|
|
778
892
|
slide,
|
|
779
893
|
model: selectedModel,
|
|
780
894
|
success: false,
|
|
895
|
+
aborted: clientDisconnected,
|
|
781
896
|
code: -1,
|
|
782
897
|
message,
|
|
783
898
|
});
|
|
784
899
|
broadcastRunsSnapshot();
|
|
785
900
|
|
|
901
|
+
if (clientDisconnected || res.writableEnded) {
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
|
|
786
905
|
res.status(500).json({
|
|
787
906
|
success: false,
|
|
788
907
|
runId,
|
|
789
908
|
error: message,
|
|
790
909
|
});
|
|
791
910
|
} finally {
|
|
911
|
+
req.off?.('close', handleClientClose);
|
|
912
|
+
res.off?.('close', handleClientClose);
|
|
913
|
+
childProcessesByRunId.delete(runId);
|
|
914
|
+
abortControllersByRunId.delete(runId);
|
|
792
915
|
runStore.clearActiveRun(slide, runId);
|
|
793
916
|
await rm(tmpPath, { recursive: true, force: true }).catch(() => {});
|
|
794
917
|
}
|
|
795
918
|
});
|
|
796
919
|
|
|
920
|
+
app.post('/api/runs/:runId/cancel', (req, res) => {
|
|
921
|
+
const { runId } = req.params;
|
|
922
|
+
const killed = killTrackedChild(runId, { reason: 'cancelled via /api/runs/:runId/cancel' });
|
|
923
|
+
if (!killed) {
|
|
924
|
+
return res.status(404).json({ error: 'No active run for this runId.' });
|
|
925
|
+
}
|
|
926
|
+
res.json({ runId, cancelled: true });
|
|
927
|
+
});
|
|
928
|
+
|
|
797
929
|
let debounceTimer = null;
|
|
798
930
|
const watcher = fsWatch(slidesDirectory, { persistent: false }, (_eventType, filename) => {
|
|
799
931
|
if (!filename || !SLIDE_FILE_PATTERN.test(filename)) return;
|
|
@@ -812,8 +944,24 @@ async function startServer(opts) {
|
|
|
812
944
|
process.stdout.write(` Slides: ${slidesDirectory}\n`);
|
|
813
945
|
process.stdout.write(' ─────────────────────────────────────\n\n');
|
|
814
946
|
|
|
947
|
+
let shuttingDown = false;
|
|
815
948
|
async function shutdown() {
|
|
949
|
+
if (shuttingDown) return;
|
|
950
|
+
shuttingDown = true;
|
|
816
951
|
process.stdout.write('\n[editor] Shutting down...\n');
|
|
952
|
+
|
|
953
|
+
const killedCount = killAllTrackedChildren({
|
|
954
|
+
reason: 'editor server is shutting down',
|
|
955
|
+
signal: 'SIGTERM',
|
|
956
|
+
});
|
|
957
|
+
if (killedCount > 0) {
|
|
958
|
+
process.stdout.write(`[editor] Sent SIGTERM to ${killedCount} active edit subprocess(es).\n`);
|
|
959
|
+
await new Promise((resolve) => {
|
|
960
|
+
const t = setTimeout(resolve, 1_000);
|
|
961
|
+
t.unref?.();
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
|
|
817
965
|
watcher.close();
|
|
818
966
|
for (const client of sseClients) {
|
|
819
967
|
client.end();
|
|
@@ -826,6 +974,14 @@ async function startServer(opts) {
|
|
|
826
974
|
|
|
827
975
|
process.on('SIGINT', shutdown);
|
|
828
976
|
process.on('SIGTERM', shutdown);
|
|
977
|
+
|
|
978
|
+
return {
|
|
979
|
+
server,
|
|
980
|
+
shutdown,
|
|
981
|
+
childProcessesByRunId,
|
|
982
|
+
killTrackedChild,
|
|
983
|
+
killAllTrackedChildren,
|
|
984
|
+
};
|
|
829
985
|
}
|
|
830
986
|
|
|
831
987
|
const args = process.argv.slice(2);
|
|
@@ -4,31 +4,75 @@ import { resolve } from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
DEFAULT_CODEX_IMAGE_MODEL,
|
|
8
|
+
DEFAULT_CODEX_IMAGE_SIZE,
|
|
9
|
+
DEFAULT_GOD_TIBO_MODEL,
|
|
10
|
+
DEFAULT_IMAGE_PROVIDER,
|
|
7
11
|
DEFAULT_NANO_BANANA_ASPECT_RATIO,
|
|
8
12
|
DEFAULT_NANO_BANANA_IMAGE_SIZE,
|
|
9
13
|
DEFAULT_NANO_BANANA_MODEL,
|
|
14
|
+
IMAGE_PROVIDER_CODEX,
|
|
15
|
+
IMAGE_PROVIDER_GOD_TIBO,
|
|
16
|
+
IMAGE_PROVIDER_NANO_BANANA,
|
|
17
|
+
buildCodexImageApiRequest,
|
|
10
18
|
buildNanoBananaApiRequest,
|
|
19
|
+
extractCodexGeneratedImage,
|
|
11
20
|
extractGeneratedImage,
|
|
21
|
+
generateCodexImage,
|
|
12
22
|
generateNanoBananaImage,
|
|
23
|
+
getCodexFallbackMessage,
|
|
13
24
|
getNanoBananaFallbackMessage,
|
|
14
25
|
getNanoBananaUsage,
|
|
26
|
+
normalizeImageProvider,
|
|
15
27
|
parseNanoBananaCliArgs,
|
|
28
|
+
resolveCodexApiKey,
|
|
16
29
|
resolveNanoBananaApiKey,
|
|
17
30
|
resolveNanoBananaOutputPath,
|
|
18
31
|
runNanoBananaCli,
|
|
19
32
|
saveNanoBananaImage,
|
|
20
33
|
} from '../src/nano-banana.js';
|
|
34
|
+
import {
|
|
35
|
+
GOD_TIBO_DEFAULT_MODEL,
|
|
36
|
+
GOD_TIBO_PROVIDER_AUTO,
|
|
37
|
+
GOD_TIBO_PROVIDER_CODEX_CLI,
|
|
38
|
+
GOD_TIBO_PROVIDER_PRIVATE_CODEX,
|
|
39
|
+
generateGodTiboImage,
|
|
40
|
+
getGodTiboFallbackMessage,
|
|
41
|
+
injectAspectRatioHint,
|
|
42
|
+
resolveGodTiboConfig,
|
|
43
|
+
} from '../src/god-tibo-imagen.js';
|
|
21
44
|
|
|
22
45
|
export {
|
|
46
|
+
DEFAULT_CODEX_IMAGE_MODEL,
|
|
47
|
+
DEFAULT_CODEX_IMAGE_SIZE,
|
|
48
|
+
DEFAULT_GOD_TIBO_MODEL,
|
|
49
|
+
DEFAULT_IMAGE_PROVIDER,
|
|
23
50
|
DEFAULT_NANO_BANANA_ASPECT_RATIO,
|
|
24
51
|
DEFAULT_NANO_BANANA_IMAGE_SIZE,
|
|
25
52
|
DEFAULT_NANO_BANANA_MODEL,
|
|
53
|
+
GOD_TIBO_DEFAULT_MODEL,
|
|
54
|
+
GOD_TIBO_PROVIDER_AUTO,
|
|
55
|
+
GOD_TIBO_PROVIDER_CODEX_CLI,
|
|
56
|
+
GOD_TIBO_PROVIDER_PRIVATE_CODEX,
|
|
57
|
+
IMAGE_PROVIDER_CODEX,
|
|
58
|
+
IMAGE_PROVIDER_GOD_TIBO,
|
|
59
|
+
IMAGE_PROVIDER_NANO_BANANA,
|
|
60
|
+
buildCodexImageApiRequest,
|
|
26
61
|
buildNanoBananaApiRequest,
|
|
62
|
+
extractCodexGeneratedImage,
|
|
27
63
|
extractGeneratedImage,
|
|
64
|
+
generateCodexImage,
|
|
65
|
+
generateGodTiboImage,
|
|
28
66
|
generateNanoBananaImage,
|
|
67
|
+
getCodexFallbackMessage,
|
|
68
|
+
getGodTiboFallbackMessage,
|
|
29
69
|
getNanoBananaFallbackMessage,
|
|
30
70
|
getNanoBananaUsage,
|
|
71
|
+
injectAspectRatioHint,
|
|
72
|
+
normalizeImageProvider,
|
|
31
73
|
parseNanoBananaCliArgs,
|
|
74
|
+
resolveCodexApiKey,
|
|
75
|
+
resolveGodTiboConfig,
|
|
32
76
|
resolveNanoBananaApiKey,
|
|
33
77
|
resolveNanoBananaOutputPath,
|
|
34
78
|
runNanoBananaCli,
|
|
@@ -33,10 +33,10 @@ Use the installed **slides-grab-design** skill.
|
|
|
33
33
|
3. Generate `slide-*.html` files in the slides workspace (default: `slides/`).
|
|
34
34
|
4. Run validation: `slides-grab validate --slides-dir <path>`
|
|
35
35
|
5. If validation fails, automatically fix the slide HTML/CSS until validation passes.
|
|
36
|
-
6. For bespoke slide imagery, use `slides-grab image --prompt "<prompt>" --slides-dir <path>` so
|
|
36
|
+
6. For bespoke slide imagery, use `slides-grab image --prompt "<prompt>" --slides-dir <path>` so the default god-tibo-imagen provider (reuses local Codex ChatGPT login — no API key required) saves a local asset under `<slides-dir>/assets/`.
|
|
37
37
|
7. For complex diagrams (architecture, workflows, relationship maps, multi-node concepts), prefer `tldraw` over hand-built HTML/CSS diagrams. Render the asset with `slides-grab tldraw`, store it under `<slides-dir>/assets/`, and place it in the slide with a normal `<img>`.
|
|
38
38
|
8. Keep local videos under `<slides-dir>/assets/`, prefer `poster="./assets/<file>"` thumbnails, and use `slides-grab fetch-video --url <youtube-url> --slides-dir <path>` (or `yt-dlp` directly) when the source starts on a supported web page.
|
|
39
|
-
9.
|
|
39
|
+
9. The default provider, god-tibo-imagen, reuses the local Codex ChatGPT login (`~/.codex/auth.json`) — run `codex login` once; no API key required. ⚠️ god-tibo-imagen uses an unsupported private Codex backend that may break without notice. Optional alternatives: `--provider codex` (Codex/OpenAI gpt-image-2 via `OPENAI_API_KEY`; maps `--aspect-ratio` to the nearest supported OpenAI image size; `--image-size 2K|4K` is Nano Banana-only) or `--provider nano-banana` (Google `gemini-3-pro-image-preview` via `GOOGLE_API_KEY` or `GEMINI_API_KEY`; supports `--image-size 2K|4K`). If credentials are unavailable, fall back to web search/download into `<slides-dir>/assets/`.
|
|
40
40
|
10. Launch the interactive editor for review: `slides-grab edit --slides-dir <path>`
|
|
41
41
|
11. Revise slides based on user feedback via the editor, then re-run validation after each edit round.
|
|
42
42
|
12. When the user confirms editing is complete, suggest next steps: build the viewer (`slides-grab build-viewer --slides-dir <path>`) for a final preview, or proceed directly to Stage 3 for PDF/PPTX export.
|
|
@@ -67,7 +67,7 @@ Use the installed **slides-grab-export** skill.
|
|
|
67
67
|
5. **Call out export risk clearly**: PPTX and Figma export are experimental / unstable and must be described as best-effort output.
|
|
68
68
|
6. Use the stage skills as the source of truth for plan, design, and export rules.
|
|
69
69
|
7. When a slide needs a complex diagram, default to a `tldraw`-generated asset unless the user explicitly asks for a different approach.
|
|
70
|
-
8. When a slide needs bespoke imagery, prefer
|
|
70
|
+
8. When a slide needs bespoke imagery, prefer the default god-tibo-imagen provider via `slides-grab image` (reuses local Codex ChatGPT login — no API key required) and keep the saved asset local under `<slides-dir>/assets/`.
|
|
71
71
|
|
|
72
72
|
## Reference
|
|
73
73
|
- `references/presentation-workflow-reference.md` — archived end-to-end workflow guidance from the legacy skill set
|
|
@@ -27,10 +27,10 @@ Use the installed **slides-grab-design** skill.
|
|
|
27
27
|
4. Run validation: `slides-grab validate --slides-dir <path>`
|
|
28
28
|
5. If validation fails, automatically fix the slide HTML/CSS until validation passes.
|
|
29
29
|
6. Build the viewer: `slides-grab build-viewer --slides-dir <path>`
|
|
30
|
-
7. When a slide calls for bespoke imagery, prefer `slides-grab image --prompt "<prompt>" --slides-dir <path>` so
|
|
30
|
+
7. When a slide calls for bespoke imagery, prefer `slides-grab image --prompt "<prompt>" --slides-dir <path>` so the default god-tibo-imagen provider (reuses local Codex ChatGPT login — no API key required) saves a local asset under `<slides-dir>/assets/`.
|
|
31
31
|
8. For complex diagrams (architecture, workflows, relationship maps, multi-node concepts), prefer `tldraw`. Render a local diagram asset with `slides-grab tldraw`, store it under `<slides-dir>/assets/`, and place it into the slide with a normal `<img>`.
|
|
32
32
|
9. Keep local videos under `<slides-dir>/assets/`, prefer `poster="./assets/<file>"` thumbnails, and use `slides-grab fetch-video --url <youtube-url> --slides-dir <path>` (or `yt-dlp` directly) when the source starts on a supported web page.
|
|
33
|
-
10.
|
|
33
|
+
10. The default provider, god-tibo-imagen, reuses the local Codex ChatGPT login (`~/.codex/auth.json`) — run `codex login` once; no API key required. ⚠️ god-tibo-imagen uses an unsupported private Codex backend that may break without notice. Optional alternatives: `--provider codex` (Codex/OpenAI gpt-image-2 via `OPENAI_API_KEY`; maps `--aspect-ratio` to the nearest supported OpenAI image size; `--image-size 2K|4K` is Nano Banana-only) or `--provider nano-banana` (Google `gemini-3-pro-image-preview` via `GOOGLE_API_KEY` or `GEMINI_API_KEY`; supports `--image-size 2K|4K`). If credentials are unavailable, fall back to web search + download into `<slides-dir>/assets/`.
|
|
34
34
|
11. Present viewer to user for review.
|
|
35
35
|
12. Revise individual slides based on feedback, then re-run validation and rebuild the viewer.
|
|
36
36
|
13. Optionally launch the visual editor: `slides-grab edit --slides-dir <path>`
|
|
@@ -56,4 +56,4 @@ Use the installed **slides-grab-export** skill.
|
|
|
56
56
|
4. **Use `decks/<deck-name>/`** as the slides workspace for multi-deck projects.
|
|
57
57
|
5. **Call out export risk clearly**: PPTX and Figma export are experimental / unstable and should be described as best-effort output.
|
|
58
58
|
6. **Prefer tldraw for complex diagrams**: Use `slides-grab tldraw` for diagram-heavy slides unless the user explicitly wants another rendering path.
|
|
59
|
-
7. **Prefer
|
|
59
|
+
7. **Prefer Codex/OpenAI for bespoke imagery**: Use `slides-grab image` when a slide benefits from generated imagery, and keep the result as a local asset under `<slides-dir>/assets/`.
|
|
@@ -22,12 +22,16 @@ Generate high-quality `slide-XX.html` files in the selected slides workspace (`s
|
|
|
22
22
|
|
|
23
23
|
## Workflow
|
|
24
24
|
1. Read approved `slide-outline.md` and extract the `style` field from its meta section.
|
|
25
|
-
2. Load the chosen style's full spec
|
|
25
|
+
2. Load the chosen style's full spec:
|
|
26
|
+
- If `style` is a bundled id (e.g. `glassmorphism`), load from `src/design-styles-data.js` — colors, fonts, layout, signature elements, and things to avoid.
|
|
27
|
+
- If `style` ends in `.md` (e.g. `./DESIGN.slides.md` or `./DESIGN.md`), or if a design markdown file exists at the project root, parse it with `slides-grab show-design <path>` and treat the parsed output as the authoritative design system (colors, typography, layout, components, signature, avoid).
|
|
28
|
+
- **Precedence when both files exist:** `DESIGN.slides.md` takes priority over `DESIGN.md`. The `.slides.md` version is the slide-flavored conversion produced by the plan stage and is the only file safe to apply to slide HTML. If only `DESIGN.md` exists, treat it as web-flavored and follow the slide layout/avoid rules in `references/design-rules.md` strictly to avoid leaking web-only components (top-nav, CTA buttons, footer-band columns, pricing grids) into slides — or, preferably, switch back to the plan stage and produce a `DESIGN.slides.md` first.
|
|
29
|
+
- If the meta specifies a written custom direction, use that as the design basis.
|
|
26
30
|
3. Before generating slides, write a quick **visual thesis** (mood/material/energy), a **content plan** (opener → support/proof → detail/story → close/CTA), a **system declaration** (reused layout patterns, max two background colors, max two typefaces, image-led vs text-led slides, where section dividers reset tempo), and the core design tokens (background, surface, text, muted, accent + display/headline/body/caption roles). Ground these tokens in the chosen style's spec. Follow `references/beautiful-slide-defaults.md` for the full working model, content discipline, color discipline, and AI slop tropes to avoid.
|
|
27
31
|
4. If you need to confirm or revisit the approved bundled style before designing, re-run `slides-grab list-styles` and open the gallery from `slides-grab preview-styles` so the Stage 2 deck stays aligned with the Stage 1 direction.
|
|
28
32
|
5. Generate slide HTML files with 2-digit numbering in selected `--slides-dir`.
|
|
29
33
|
6. When a slide needs iconography, prefer Lucide as the default icon library. Use clean Lucide icons before falling back to emoji, and only use emoji when the brief explicitly calls for them.
|
|
30
|
-
7. When a slide explicitly needs bespoke imagery, when the user asks for an image, or when stronger imagery would materially improve the slide, prefer `slides-grab image --prompt "<prompt>" --slides-dir <path>` to generate a local asset with
|
|
34
|
+
7. When a slide explicitly needs bespoke imagery, when the user asks for an image, or when stronger imagery would materially improve the slide, prefer `slides-grab image --prompt "<prompt>" --slides-dir <path>` to generate a local asset with the default god-tibo-imagen provider (which reuses the local Codex ChatGPT login — no API key required) and save it under `<slides-dir>/assets/`.
|
|
31
35
|
8. If the deck needs a complex diagram (architecture, workflows, relationship maps, multi-node concepts), create the diagram in `tldraw`, export it with `slides-grab tldraw`, and treat the result as a local slide asset under `<slides-dir>/assets/`.
|
|
32
36
|
9. If the slide needs a local video, store the video under `<slides-dir>/assets/`, reference it as `./assets/<file>`, and prefer a `poster="./assets/<file>"` thumbnail so PDF export uses a stable still image.
|
|
33
37
|
10. If the source video starts on YouTube or another supported page, use `slides-grab fetch-video --url <youtube-url> --slides-dir <path>` (or `yt-dlp` directly if needed) to download it into `<slides-dir>/assets/` before saving the slide HTML.
|
|
@@ -46,8 +50,8 @@ Generate high-quality `slide-XX.html` files in the selected slides workspace (`s
|
|
|
46
50
|
- Allow `data:` URLs when the slide must be fully self-contained.
|
|
47
51
|
- Do not leave remote `http(s)://` image URLs in saved slide HTML; download source images into `<slides-dir>/assets/` and reference them as `./assets/<file>`.
|
|
48
52
|
- Prefer Lucide for default slide iconography. Avoid emoji as the default icon treatment unless the brief explicitly asks for emoji.
|
|
49
|
-
- Prefer `slides-grab image` with
|
|
50
|
-
-
|
|
53
|
+
- Prefer `slides-grab image` with the default god-tibo-imagen provider for bespoke slide imagery before reaching for remote URLs.
|
|
54
|
+
- The default provider, god-tibo-imagen, reuses the local Codex ChatGPT login (`~/.codex/auth.json`) — run `codex login` once to enable it; **no API key required**. ⚠️ god-tibo-imagen calls an unsupported private Codex backend that may break without notice. Optional alternatives: `--provider codex` (Codex/OpenAI gpt-image-2 via `OPENAI_API_KEY`; maps `--aspect-ratio` to the nearest supported OpenAI image size), or `--provider nano-banana` (Google Nano Banana / `gemini-3-pro-image-preview` via `GOOGLE_API_KEY` or `GEMINI_API_KEY`; supports `--image-size 2K|4K`). If image generation credentials are unavailable, fall back to web search + download into `<slides-dir>/assets/`.
|
|
51
55
|
- Prefer local videos with a `poster="./assets/<file>"` thumbnail so PDF export uses the still image.
|
|
52
56
|
- Use `slides-grab fetch-video` or `yt-dlp` to pull supported web videos into `<slides-dir>/assets/` before saving slide HTML.
|
|
53
57
|
- Prefer `<img>` for slide imagery and `data-image-placeholder` when no final asset exists.
|
|
@@ -6,7 +6,7 @@ These are the packaged design rules for installable `slides-grab` skills.
|
|
|
6
6
|
- Validate slides: `slides-grab validate --slides-dir <path>`
|
|
7
7
|
- Build review viewer: `slides-grab build-viewer --slides-dir <path>`
|
|
8
8
|
- Launch editor: `slides-grab edit --slides-dir <path>`
|
|
9
|
-
- Generate a bespoke image asset: `slides-grab image --prompt "<prompt>" --slides-dir <path>`
|
|
9
|
+
- Generate a bespoke image asset: `slides-grab image --prompt "<prompt>" --slides-dir <path>` (default provider: god-tibo-imagen via `codex login` — no API key required)
|
|
10
10
|
- Download a web video into slide assets: `slides-grab fetch-video --url <youtube-url> --slides-dir <path>`
|
|
11
11
|
- Render `tldraw` diagrams: `slides-grab tldraw --input <path> --output <path>`
|
|
12
12
|
- List bundled design collections: `slides-grab list-styles`
|
|
@@ -27,11 +27,11 @@ These are the packaged design rules for installable `slides-grab` skills.
|
|
|
27
27
|
## Asset rules
|
|
28
28
|
- Store deck-local assets in `<slides-dir>/assets/`
|
|
29
29
|
- Reference deck-local assets as `./assets/<file>`
|
|
30
|
-
- Use `slides-grab image --prompt "<prompt>" --slides-dir <path>` with
|
|
30
|
+
- Use `slides-grab image --prompt "<prompt>" --slides-dir <path>` with the default god-tibo-imagen provider (Codex CLI ChatGPT login) for bespoke generated images when helpful
|
|
31
31
|
- If an image comes from the web, download it into `<slides-dir>/assets/` before referencing it
|
|
32
32
|
- If a video comes from YouTube or another supported page, use `slides-grab fetch-video` (or `yt-dlp` directly) to download it into `<slides-dir>/assets/` before referencing it
|
|
33
33
|
- Keep local videos and their poster thumbnails together under `<slides-dir>/assets/`
|
|
34
|
-
-
|
|
34
|
+
- Default provider god-tibo-imagen reuses the local Codex ChatGPT login (`~/.codex/auth.json`) — run `codex login` once; no API key required. ⚠️ god-tibo-imagen uses an unsupported private Codex backend that may break without notice. Optional fallbacks: `--provider codex` (Codex/OpenAI gpt-image-2 via `OPENAI_API_KEY`; maps `--aspect-ratio` to the nearest supported OpenAI image size; `--image-size 2K|4K` is Nano Banana-only) or `--provider nano-banana` (Google `gemini-3-pro-image-preview` via `GOOGLE_API_KEY` / `GEMINI_API_KEY`; supports `--image-size 2K|4K`). If credentials are unavailable, fall back to web search + download
|
|
35
35
|
- Use `tldraw`-generated local assets for complex diagrams when possible
|
|
36
36
|
- Allow `data:` URLs only when the slide must be fully self-contained
|
|
37
37
|
- Do not leave remote `http(s)://` image URLs in saved slide HTML
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
- Use `./assets/<file>` as the default image and video contract for slide HTML.
|
|
11
11
|
- Keep slide assets in `<slides-dir>/assets/`.
|
|
12
12
|
- Use `tldraw`-generated assets for complex diagrams whenever possible.
|
|
13
|
-
- Use `slides-grab image --prompt "<prompt>" --slides-dir <path>` with
|
|
13
|
+
- Use `slides-grab image --prompt "<prompt>" --slides-dir <path>` with the default god-tibo-imagen provider (reuses Codex CLI ChatGPT login; no API key required) when a slide needs bespoke generated imagery.
|
|
14
14
|
- `data:` URLs are allowed for fully self-contained slides.
|
|
15
15
|
- Do not leave remote `http(s)://` image URLs in saved slide HTML; download source images into `<slides-dir>/assets/` and reference them as `./assets/<file>`.
|
|
16
16
|
- Store local videos under `<slides-dir>/assets/`, reference them as `./assets/<file>`, and prefer `poster="./assets/<file>"` for export-friendly thumbnails.
|
|
17
17
|
- If a video starts on YouTube or another supported page, use `slides-grab fetch-video --url <youtube-url> --slides-dir <path>` (or `yt-dlp` directly if needed) before saving the slide HTML.
|
|
18
|
-
-
|
|
18
|
+
- Default provider god-tibo-imagen reuses the local Codex ChatGPT login (`~/.codex/auth.json`) — run `codex login` once; **no API key required**. ⚠️ god-tibo-imagen uses an unsupported private Codex backend that may break without notice. Optional alternatives: `--provider codex` (Codex/OpenAI gpt-image-2 via `OPENAI_API_KEY`; maps `--aspect-ratio` to the nearest supported OpenAI image size; `--image-size 2K|4K` is Nano Banana-only) or `--provider nano-banana` (Google `gemini-3-pro-image-preview` via `GOOGLE_API_KEY` or `GEMINI_API_KEY`; supports `--image-size 2K|4K`). If credentials are unavailable, fall back to web search + download into `<slides-dir>/assets/`.
|
|
19
19
|
- Do not use absolute filesystem paths in slide HTML.
|
|
20
20
|
- Do not use non-body `background-image` for content imagery; use `<img>` instead.
|
|
21
21
|
- Use `data-image-placeholder` to reserve space when no image is available yet.
|
|
@@ -23,10 +23,22 @@ Produce an approved `slide-outline.md` before any slide HTML generation.
|
|
|
23
23
|
|
|
24
24
|
## Workflow
|
|
25
25
|
1. Analyze user goal and audience.
|
|
26
|
-
2. **Style selection (mandatory, before outline):**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
2. **Style selection (mandatory, before outline):** Three paths are accepted, in priority order:
|
|
27
|
+
a. **Bundled style** — run `slides-grab list-styles`, shortlist 2–3 styles, and get explicit user approval. Optionally offer `slides-grab preview-styles` for visual preview. Record as `style: <id>`.
|
|
28
|
+
b. **Custom DESIGN.md path** — if a local `DESIGN.md` exists (e.g. provided directly or fetched via `slides-grab import-design <https-url>`), inspect it with `slides-grab show-design ./DESIGN.md` and confirm with the user.
|
|
29
|
+
c. **Free-form custom direction** — if neither bundled nor DESIGN.md fits, propose a written custom direction and get approval.
|
|
30
|
+
3. **DESIGN.md → DESIGN.slides.md conversion (mandatory when path 2b was chosen):**
|
|
31
|
+
- A `DESIGN.md` imported from `voltagent/awesome-design-md` or similar sources describes a **marketing website** (top-nav, hero-band, CTA buttons, pricing cards, footer-band). Slides are **single 720pt × 405pt frames** with no scroll, no nav, no clicks — copying web components into slides produces deck pages that look like landing pages.
|
|
32
|
+
- Read `references/design-md-to-slides-conversion.md` for the canonical conversion guide.
|
|
33
|
+
- Translate the imported `./DESIGN.md` into a sibling `./DESIGN.slides.md` next to it. Leave the original `DESIGN.md` untouched. The `DESIGN.slides.md` MUST follow the Output Contract in the reference and apply every row of the web → slide mapping table (top-nav → eyebrow strip, hero-band → cover layout, CTA buttons → kicker text, footer-band → thin footer strip, pricing grids → dropped, etc.).
|
|
34
|
+
- Present a 5–10 line summary of the conversion to the user (kept tokens + dropped web sections + new slide layouts inferred) and wait for explicit approval before continuing.
|
|
35
|
+
- After approval, run `slides-grab show-design ./DESIGN.slides.md` to confirm the parser reads it cleanly.
|
|
36
|
+
4. Create or revise `slide-outline.md` with ordered slides and key messages. Record the approved style reference in the meta section:
|
|
37
|
+
- bundled style → `style: <id>`
|
|
38
|
+
- converted DESIGN.slides.md → `style: ./DESIGN.slides.md`
|
|
39
|
+
- free-form custom direction → leave a one-paragraph `style:` block describing it
|
|
40
|
+
5. Present a concise summary to user.
|
|
41
|
+
6. Repeat revisions until explicit approval.
|
|
30
42
|
|
|
31
43
|
## Rules
|
|
32
44
|
- **Do not write the outline before the user approves a style.** Style selection comes first.
|
|
@@ -40,3 +52,4 @@ Produce an approved `slide-outline.md` before any slide HTML generation.
|
|
|
40
52
|
If needed, use the bundled outline reference:
|
|
41
53
|
- `references/outline-format.md`
|
|
42
54
|
- `references/plan-workflow-reference.md` — archived detailed planning workflow and organizer-agent guidance
|
|
55
|
+
- `references/design-md-to-slides-conversion.md` — DESIGN.md (web) → DESIGN.slides.md (slide) translation guide, including the structured output template and the web → slide mapping table
|