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.
@@ -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
- const CODEX_MODELS = ['gpt-5.4', 'gpt-5.3-codex', 'gpt-5.3-codex-spark'];
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
- stdio: 'pipe',
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 success = result.code === 0;
739
- const message = success
740
- ? `${engineLabel} edit completed.`
741
- : (result.timeoutMessage || `${engineLabel} exited with code ${result.code}.`);
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: success ? 'success' : 'failed',
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 Nano Banana Pro saves a local asset under `<slides-dir>/assets/`.
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. If `GOOGLE_API_KEY` (or `GEMINI_API_KEY`) is unavailable or Nano Banana is down, ask the user for a Google API key or fall back to web search/download into `<slides-dir>/assets/`.
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 Nano Banana Pro via `slides-grab image` and keep the saved asset local under `<slides-dir>/assets/`.
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 Nano Banana Pro saves a local asset under `<slides-dir>/assets/`.
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. If `GOOGLE_API_KEY` or `GEMINI_API_KEY` is unavailable, or the Nano Banana API fails, ask the user for a Google API key or fall back to web search + download into `<slides-dir>/assets/`.
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 Nano Banana Pro 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/`.
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 from `src/design-styles-data.js` — colors, fonts, layout, signature elements, and things to avoid. If the meta specifies a custom direction instead of a bundled ID, use that custom direction as the design basis.
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 Nano Banana Pro and save it under `<slides-dir>/assets/`.
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 Nano Banana Pro for bespoke slide imagery before reaching for remote URLs.
50
- - If `GOOGLE_API_KEY` (or `GEMINI_API_KEY`) is unavailable or the Nano Banana API fails, ask the user for a Google API key or fall back to web search + download into `<slides-dir>/assets/`.
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 Nano Banana Pro for bespoke generated images when helpful
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
- - If `GOOGLE_API_KEY` / `GEMINI_API_KEY` is unavailable, ask the user for a Google API key or fall back to web search + download
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 Nano Banana Pro when a slide needs bespoke generated imagery.
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
- - If `GOOGLE_API_KEY` or `GEMINI_API_KEY` is unavailable, or the Nano Banana API fails, ask the user for a Google API key or fall back to web search + download into `<slides-dir>/assets/`.
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):** Run `slides-grab list-styles`, shortlist 2–3 styles that match the topic/tone, present the shortlist with reasons, and get explicit user approval. Optionally offer `slides-grab preview-styles` for visual preview. If no bundled style fits, propose a custom direction and get approval.
27
- 3. Create or revise `slide-outline.md` with ordered slides and key messages. Record the approved style ID in the meta section (`style: <id>`).
28
- 4. Present a concise summary to user.
29
- 5. Repeat revisions until explicit approval.
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