slides-grab 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,6 +22,12 @@ The editor is pure javascript file. You can easily add up new features like addi
22
22
  </a>
23
23
  </p>
24
24
 
25
+ <p align="center">
26
+ <strong>👉 See what slides-grab can do:</strong>
27
+ <a href="https://vkehfdl1.github.io/slides-grab/"><strong>Live Showcase Gallery</strong></a><br>
28
+ Real presentations built with slides-grab — open any deck and flip through the slides.
29
+ </p>
30
+
25
31
  ---
26
32
 
27
33
  ## Quick Start
@@ -49,7 +55,7 @@ git clone https://github.com/vkehfdl1/slides-grab.git && cd slides-grab
49
55
  npm ci && npx playwright install chromium
50
56
  ```
51
57
 
52
- > Requires **Node.js >= 18**.
58
+ > Requires **Node.js >= 20**.
53
59
 
54
60
  ### No-clone install
55
61
 
@@ -86,7 +92,7 @@ slides-grab pdf --resolution 2160p # Higher-resolution image-backed PDF export
86
92
  slides-grab pdf --mode print # Export searchable/selectable text PDF
87
93
  slides-grab png # Render one PNG per slide (default 2160p)
88
94
  slides-grab png --slide-mode card-news # Render square 1:1 PNGs for Instagram
89
- slides-grab image --prompt "..." # Generate a local slide image with Nano Banana Pro
95
+ slides-grab image --prompt "..." # Generate a local slide image with god-tibo-imagen by default (uses your local Codex ChatGPT login — no API key required)
90
96
  slides-grab fetch-video --url <youtube-url> --slides-dir decks/my-deck # Download a local video asset with yt-dlp
91
97
  slides-grab tldraw # Render a .tldr diagram into a slide-sized local SVG asset
92
98
  slides-grab list-templates # Show available slide templates
@@ -116,14 +122,23 @@ Slides should store local image and video files in `<slides-dir>/assets/` and re
116
122
  - Unsupported: absolute filesystem paths such as `/Users/...` or `C:\\...`
117
123
  - Unsupported for saved slides: remote video URLs; download them into `<slides-dir>/assets/` first
118
124
 
119
- For bespoke generated imagery, prefer Nano Banana Pro:
125
+ For bespoke generated imagery, slides-grab bundles **god-tibo-imagen** as the default provider. It reuses your local Codex ChatGPT login (`~/.codex/auth.json`), so **no separate OpenAI/Google API key is required** — you only need a Codex CLI ChatGPT login on an account that is entitled to image generation:
120
126
 
121
127
  ```bash
122
- export GOOGLE_API_KEY=...
128
+ codex login # one-time setup if not already logged in
123
129
  slides-grab image --slides-dir decks/my-deck --prompt "Editorial hero image of a robotics warehouse at dawn"
124
130
  ```
125
131
 
126
- The command saves the result into `<slides-dir>/assets/` and prints the portable `./assets/<file>` reference to use from slide HTML. If `GOOGLE_API_KEY` (or `GEMINI_API_KEY`) is unavailable, ask for a Google API key or fall back to web search + local download into `assets/`.
132
+ The command saves the result into `<slides-dir>/assets/` and prints the portable `./assets/<file>` reference to use from slide HTML.
133
+
134
+ > ⚠️ **WARNING**: god-tibo-imagen calls an unsupported private Codex backend that may break without notice. It also requires a Codex/ChatGPT account that is entitled to image generation; not all ChatGPT accounts have this entitlement.
135
+
136
+ Optional alternative providers via `--provider`:
137
+
138
+ - `--provider codex` (alias `openai`): Codex/OpenAI `gpt-image-2`. Requires `OPENAI_API_KEY`. Maps `--aspect-ratio` to the nearest supported OpenAI image size (`16:9` defaults to a landscape `1536x1024` request).
139
+ - `--provider nano-banana` (alias `gemini`): Google `gemini-3-pro-image-preview`. Requires `GOOGLE_API_KEY` (or `GEMINI_API_KEY`). Supports `--image-size 2K|4K`.
140
+
141
+ If the default god-tibo-imagen call fails, slides-grab automatically falls back to whichever optional provider has credentials available; otherwise it asks you to fall back to web search + local download into `assets/`.
127
142
 
128
143
  Run `slides-grab validate --slides-dir <path>` before export to catch missing local assets and discouraged path forms.
129
144
 
@@ -235,6 +250,7 @@ templates/ Slide HTML templates (cover, content, chart, ...)
235
250
  src/ Design styles data, style config, path resolution
236
251
  skills/ Shared Vercel-installable agent skills + references
237
252
  docs/ Installation & usage guides
253
+ showcase/ Static gallery deployed to GitHub Pages (https://vkehfdl1.github.io/slides-grab/)
238
254
  ```
239
255
 
240
256
  ## License
package/bin/ppt-agent.js CHANGED
@@ -227,19 +227,31 @@ program
227
227
 
228
228
  program
229
229
  .command('image')
230
- .description('Generate a local slide image asset with Nano Banana Pro')
230
+ .description('Generate a local slide image asset (default: god-tibo-imagen via your Codex ChatGPT login — no OpenAI/Google API key required)')
231
231
  .option('--prompt <text>', 'Prompt for image generation')
232
232
  .option('--slides-dir <path>', 'Slide directory', 'slides')
233
233
  .option('--output <path>', 'Optional output path inside <slides-dir>/assets/')
234
234
  .option('--name <slug>', 'Optional asset basename without extension')
235
- .option('--model <id>', 'Model id (default: gemini-3-pro-image-preview)')
236
- .option('--aspect-ratio <ratio>', 'Aspect ratio (default: 16:9)')
237
- .option('--image-size <size>', 'Image size preset: 2K or 4K (default: 4K)')
235
+ .option('--provider <name>', 'Image provider: god-tibo (default), codex (OpenAI), or nano-banana. Aliases: codex-cli → god-tibo, openai → codex, gemini → nano-banana')
236
+ .option('--model <id>', 'Model id (default: gpt-5.4 for god-tibo, gpt-image-2 for codex, gemini-3-pro-image-preview for nano-banana)')
237
+ .option('--aspect-ratio <ratio>', 'Aspect ratio; for god-tibo it is injected as a prompt hint, for codex it maps to the nearest supported OpenAI size (default: 16:9)')
238
+ .option('--image-size <size>', 'Nano Banana image size preset: 2K or 4K (default: 4K)')
239
+ .addHelpText('after', [
240
+ '',
241
+ 'Auth:',
242
+ ' Default (god-tibo): run `codex login` once to populate ~/.codex/auth.json. No OpenAI/Google API key required;',
243
+ ' requires a Codex/ChatGPT account entitled to image generation.',
244
+ ' Codex/OpenAI provider: set OPENAI_API_KEY.',
245
+ ' Nano Banana provider: set GOOGLE_API_KEY or GEMINI_API_KEY.',
246
+ '',
247
+ 'WARNING: god-tibo-imagen calls an unsupported private Codex backend that may break without notice.',
248
+ ].join('\n'))
238
249
  .action(async (options = {}) => {
239
250
  const args = ['--slides-dir', options.slidesDir];
240
251
  if (options.prompt) args.push('--prompt', String(options.prompt));
241
252
  if (options.output) args.push('--output', String(options.output));
242
253
  if (options.name) args.push('--name', String(options.name));
254
+ if (options.provider) args.push('--provider', String(options.provider));
243
255
  if (options.model) args.push('--model', String(options.model));
244
256
  if (options.aspectRatio) args.push('--aspect-ratio', String(options.aspectRatio));
245
257
  if (options.imageSize) args.push('--image-size', String(options.imageSize));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slides-grab",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "Agent-first presentation framework — plan, design, and visually edit HTML slides with Claude Code or Codex, then export to PDF or experimental/unstable PPTX/Figma formats",
5
5
  "license": "MIT",
6
6
  "author": "vkehfdl1",
@@ -24,7 +24,7 @@
24
24
  "ppt"
25
25
  ],
26
26
  "engines": {
27
- "node": ">=18.0.0"
27
+ "node": ">=20.0.0"
28
28
  },
29
29
  "type": "module",
30
30
  "bin": {
@@ -37,6 +37,7 @@
37
37
  "scripts/download-video.js",
38
38
  "scripts/editor-server.js",
39
39
  "scripts/generate-image.js",
40
+ "src/god-tibo-imagen.js",
40
41
  "scripts/figma-export.js",
41
42
  "scripts/html2pdf.js",
42
43
  "scripts/html2pptx.js",
@@ -51,14 +52,16 @@
51
52
  "scripts": {
52
53
  "html2pptx": "node scripts/html2pptx.js",
53
54
  "build-viewer": "node scripts/build-viewer.js",
55
+ "build:showcase": "node showcase/scripts/build-manifest.js",
54
56
  "validate": "node scripts/validate-slides.js",
55
57
  "convert": "node convert.cjs",
56
- "test": "node --test --test-concurrency=1 tests/design/design-styles.test.js tests/editor/editor-codex-edit.test.js tests/editor/editor-server.test.js tests/nano-banana/nano-banana.test.js tests/pdf/html2pdf.test.js tests/pdf/html2pdf.e2e.test.js tests/figma/figma-export.test.js tests/image-contract/image-contract.test.js tests/tldraw/render-tldraw.test.js tests/validation/validate-slides.test.js tests/viewer/build-viewer.test.js tests/skills/installable-skills.test.js tests/video/download-video.test.js",
58
+ "test": "node --test --test-concurrency=1 tests/design/design-styles.test.js tests/editor/editor-codex-edit.test.js tests/editor/edit-subprocess-abort.test.js tests/editor/editor-server.test.js tests/editor/editor-server-orphan-prevention.test.js tests/editor/editor-model-dispatch.test.js tests/god-tibo/god-tibo.test.js tests/nano-banana/nano-banana.test.js tests/pdf/html2pdf.test.js tests/pdf/html2pdf.e2e.test.js tests/figma/figma-export.test.js tests/image-contract/image-contract.test.js tests/tldraw/render-tldraw.test.js tests/validation/validate-slides.test.js tests/viewer/build-viewer.test.js tests/skills/installable-skills.test.js tests/video/download-video.test.js",
57
59
  "test:e2e": "node --test tests/editor/editor-ui.e2e.test.js tests/editor/editor-concurrency.e2e.test.js"
58
60
  },
59
61
  "dependencies": {
60
62
  "commander": "^12.1.0",
61
63
  "express": "^5.2.1",
64
+ "god-tibo-imagen": "0.2.0",
62
65
  "lucide-react": "^1.7.0",
63
66
  "pdf-lib": "^1.17.1",
64
67
  "playwright": "^1.40.0",
@@ -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(
@@ -732,16 +829,26 @@ async function startServer(opts) {
732
829
  runStore.appendLog(runId, chunk);
733
830
  broadcastSSE('applyLog', { runId, slide, stream, chunk });
734
831
  },
832
+ onChild: (child) => trackChild(runId, child),
833
+ signal: abortController.signal,
735
834
  });
736
835
 
737
836
  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}.`);
837
+ const aborted = Boolean(result.aborted);
838
+ const success = !aborted && result.code === 0;
839
+ let message;
840
+ if (aborted) {
841
+ message = result.abortMessage || `${engineLabel} edit was aborted.`;
842
+ } else if (success) {
843
+ message = `${engineLabel} edit completed.`;
844
+ } else {
845
+ message = result.timeoutMessage || `${engineLabel} exited with code ${result.code}.`;
846
+ }
847
+
848
+ const status = aborted ? 'aborted' : success ? 'success' : 'failed';
742
849
 
743
850
  runStore.finishRun(runId, {
744
- status: success ? 'success' : 'failed',
851
+ status,
745
852
  code: result.code,
746
853
  message,
747
854
  });
@@ -751,14 +858,20 @@ async function startServer(opts) {
751
858
  slide,
752
859
  model: selectedModel,
753
860
  success,
861
+ aborted,
754
862
  code: result.code,
755
863
  message,
756
864
  });
757
865
  broadcastRunsSnapshot();
758
866
 
867
+ if (clientDisconnected || res.writableEnded) {
868
+ return;
869
+ }
870
+
759
871
  res.json({
760
872
  ...runSummary,
761
873
  success,
874
+ aborted,
762
875
  runId,
763
876
  model: selectedModel,
764
877
  code: result.code,
@@ -768,7 +881,7 @@ async function startServer(opts) {
768
881
  const message = error instanceof Error ? error.message : String(error);
769
882
 
770
883
  runStore.finishRun(runId, {
771
- status: 'failed',
884
+ status: clientDisconnected ? 'aborted' : 'failed',
772
885
  code: -1,
773
886
  message,
774
887
  });
@@ -778,22 +891,40 @@ async function startServer(opts) {
778
891
  slide,
779
892
  model: selectedModel,
780
893
  success: false,
894
+ aborted: clientDisconnected,
781
895
  code: -1,
782
896
  message,
783
897
  });
784
898
  broadcastRunsSnapshot();
785
899
 
900
+ if (clientDisconnected || res.writableEnded) {
901
+ return;
902
+ }
903
+
786
904
  res.status(500).json({
787
905
  success: false,
788
906
  runId,
789
907
  error: message,
790
908
  });
791
909
  } finally {
910
+ req.off?.('close', handleClientClose);
911
+ res.off?.('close', handleClientClose);
912
+ childProcessesByRunId.delete(runId);
913
+ abortControllersByRunId.delete(runId);
792
914
  runStore.clearActiveRun(slide, runId);
793
915
  await rm(tmpPath, { recursive: true, force: true }).catch(() => {});
794
916
  }
795
917
  });
796
918
 
919
+ app.post('/api/runs/:runId/cancel', (req, res) => {
920
+ const { runId } = req.params;
921
+ const killed = killTrackedChild(runId, { reason: 'cancelled via /api/runs/:runId/cancel' });
922
+ if (!killed) {
923
+ return res.status(404).json({ error: 'No active run for this runId.' });
924
+ }
925
+ res.json({ runId, cancelled: true });
926
+ });
927
+
797
928
  let debounceTimer = null;
798
929
  const watcher = fsWatch(slidesDirectory, { persistent: false }, (_eventType, filename) => {
799
930
  if (!filename || !SLIDE_FILE_PATTERN.test(filename)) return;
@@ -812,8 +943,24 @@ async function startServer(opts) {
812
943
  process.stdout.write(` Slides: ${slidesDirectory}\n`);
813
944
  process.stdout.write(' ─────────────────────────────────────\n\n');
814
945
 
946
+ let shuttingDown = false;
815
947
  async function shutdown() {
948
+ if (shuttingDown) return;
949
+ shuttingDown = true;
816
950
  process.stdout.write('\n[editor] Shutting down...\n');
951
+
952
+ const killedCount = killAllTrackedChildren({
953
+ reason: 'editor server is shutting down',
954
+ signal: 'SIGTERM',
955
+ });
956
+ if (killedCount > 0) {
957
+ process.stdout.write(`[editor] Sent SIGTERM to ${killedCount} active edit subprocess(es).\n`);
958
+ await new Promise((resolve) => {
959
+ const t = setTimeout(resolve, 1_000);
960
+ t.unref?.();
961
+ });
962
+ }
963
+
817
964
  watcher.close();
818
965
  for (const client of sseClients) {
819
966
  client.end();
@@ -826,6 +973,14 @@ async function startServer(opts) {
826
973
 
827
974
  process.on('SIGINT', shutdown);
828
975
  process.on('SIGTERM', shutdown);
976
+
977
+ return {
978
+ server,
979
+ shutdown,
980
+ childProcessesByRunId,
981
+ killTrackedChild,
982
+ killAllTrackedChildren,
983
+ };
829
984
  }
830
985
 
831
986
  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/`.
@@ -27,7 +27,7 @@ Generate high-quality `slide-XX.html` files in the selected slides workspace (`s
27
27
  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
28
  5. Generate slide HTML files with 2-digit numbering in selected `--slides-dir`.
29
29
  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/`.
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 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
31
  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
32
  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
33
  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 +46,8 @@ Generate high-quality `slide-XX.html` files in the selected slides workspace (`s
46
46
  - Allow `data:` URLs when the slide must be fully self-contained.
47
47
  - 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
48
  - 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/`.
49
+ - Prefer `slides-grab image` with the default god-tibo-imagen provider for bespoke slide imagery before reaching for remote URLs.
50
+ - 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
51
  - Prefer local videos with a `poster="./assets/<file>"` thumbnail so PDF export uses the still image.
52
52
  - Use `slides-grab fetch-video` or `yt-dlp` to pull supported web videos into `<slides-dir>/assets/` before saving slide HTML.
53
53
  - 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.