egaki 0.9.0 → 0.11.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/dist/cli/chatgpt-auth.d.ts +30 -5
  3. package/dist/cli/chatgpt-auth.d.ts.map +1 -1
  4. package/dist/cli/chatgpt-auth.js +257 -79
  5. package/dist/cli/cli.d.ts.map +1 -1
  6. package/dist/cli/cli.js +132 -28
  7. package/dist/cli/dev-server.d.ts +30 -0
  8. package/dist/cli/dev-server.d.ts.map +1 -0
  9. package/dist/cli/dev-server.js +266 -0
  10. package/dist/cli/dev-server.test.d.ts +2 -0
  11. package/dist/cli/dev-server.test.d.ts.map +1 -0
  12. package/dist/cli/dev-server.test.js +95 -0
  13. package/dist/cli/generate.d.ts +8 -0
  14. package/dist/cli/generate.d.ts.map +1 -1
  15. package/dist/cli/generate.js +170 -58
  16. package/dist/cli/generate.test.js +50 -1
  17. package/dist/cli/login.d.ts +38 -3
  18. package/dist/cli/login.d.ts.map +1 -1
  19. package/dist/cli/login.js +44 -17
  20. package/dist/cli/login.test.d.ts +2 -0
  21. package/dist/cli/login.test.d.ts.map +1 -0
  22. package/dist/cli/login.test.js +158 -0
  23. package/dist/cli/open-browser.d.ts +3 -1
  24. package/dist/cli/open-browser.d.ts.map +1 -1
  25. package/dist/cli/open-browser.js +2 -2
  26. package/dist/cli/xai-auth.d.ts +3 -1
  27. package/dist/cli/xai-auth.d.ts.map +1 -1
  28. package/dist/cli/xai-auth.js +13 -3
  29. package/dist/vite/angled-screen-shader.d.ts +5 -0
  30. package/dist/vite/angled-screen-shader.d.ts.map +1 -1
  31. package/dist/vite/angled-screen-shader.js +77 -34
  32. package/dist/vite/generated-media-client.d.ts.map +1 -1
  33. package/dist/vite/generated-media-client.js +2 -2
  34. package/dist/vite/vite-plugin.d.ts.map +1 -1
  35. package/dist/vite/vite-plugin.js +128 -10
  36. package/package.json +6 -10
  37. package/skills/egaki/SKILL.md +14 -0
  38. package/src/cli/chatgpt-auth.ts +379 -89
  39. package/src/cli/cli.ts +161 -27
  40. package/src/cli/dev-server.test.ts +109 -0
  41. package/src/cli/dev-server.ts +303 -0
  42. package/src/cli/generate.test.ts +57 -0
  43. package/src/cli/generate.ts +191 -61
  44. package/src/cli/login.test.ts +185 -0
  45. package/src/cli/login.ts +68 -16
  46. package/src/cli/open-browser.ts +7 -6
  47. package/src/cli/xai-auth.ts +15 -3
  48. package/src/vite/angled-screen-shader.tsx +81 -34
  49. package/src/vite/generated-media-client.tsx +6 -1
  50. package/src/vite/vite-plugin.ts +137 -10
@@ -18,6 +18,7 @@ import { fileURLToPath } from 'node:url';
18
18
  import { spiceflowPlugin } from 'spiceflow/vite';
19
19
  import react from '@vitejs/plugin-react';
20
20
  import tailwindcss from '@tailwindcss/vite';
21
+ import pc from 'picocolors';
21
22
  import { mdxParse } from 'safe-mdx/parse';
22
23
  import { collectServerImportSources } from "./server-mdx.js";
23
24
  import { parseFrontmatter } from "./mdx-parse.js";
@@ -34,6 +35,23 @@ const RESOLVED_MDX = '\0' + VIRTUAL_MDX;
34
35
  const VIRTUAL_MODULES = 'virtual:egaki-modules';
35
36
  const RESOLVED_MODULES = '\0' + VIRTUAL_MODULES;
36
37
  const PKG_NAME = 'egaki';
38
+ // Packages that must resolve to a single physical copy (same pattern as
39
+ // spiceflow:dedupe-singleton). Remotion Player + useVideoConfig share React
40
+ // context; two copies → "No video config found". Motion patches need one
41
+ // JSAnimation class. resolve.dedupe alone fails under pnpm when the package
42
+ // is only a transitive dep of egaki (not hoisted to the consumer root).
43
+ const dedupePackages = new Set([
44
+ 'remotion',
45
+ '@remotion/player',
46
+ '@remotion/media',
47
+ '@remotion/web-renderer',
48
+ 'motion',
49
+ 'motion-dom',
50
+ 'motion-utils',
51
+ ]);
52
+ // Fake importer anchored inside egaki's package so this.resolve() finds the
53
+ // same remotion/motion copy egaki itself depends on.
54
+ const dedupeImporter = path.join(__pkgRoot, '_dedupe_importer_.js');
37
55
  /** Resolve a relative MDX import source against the project root,
38
56
  * probing common extensions. Returns the absolute path or undefined. */
39
57
  function resolveSourceToFile(root, source) {
@@ -237,7 +255,7 @@ export function video(options) {
237
255
  if (entry.isDirectory()) {
238
256
  walkDir(fullPath);
239
257
  }
240
- else if (/\.(tsx?|jsx?|mdx?)$/.test(entry.name) && !/\.(test|spec|config)\./.test(entry.name)) {
258
+ else if (/\.(tsx?|jsx?|mdx?)$/.test(entry.name) && !/\.(test|spec|config)\./.test(entry.name) && !/\.d\.ts$/.test(entry.name)) {
241
259
  // Entry MDX files are NOT skipped: they may be imported as
242
260
  // partials by other MDX files (e.g. `import Intro from './intro.mdx'`).
243
261
  // Self-import is a user error and harmless (safe-mdx handles it).
@@ -291,6 +309,50 @@ export function video(options) {
291
309
  ].join('\n');
292
310
  }
293
311
  },
312
+ // Print SDK usage instructions after Vite's startup banner.
313
+ // Agents read terminal output to learn how to interact with the
314
+ // running dev server via Playwriter + window.egakiSDK.
315
+ configureServer(server) {
316
+ server.httpServer?.on('listening', () => {
317
+ const log = server.config.logger;
318
+ const p = (s) => log.info(s);
319
+ // Small delay so our block prints after Vite's "ready in" + URL lines
320
+ setTimeout(() => {
321
+ const title = ` ${pc.cyan(pc.bold('egaki SDK'))} ${pc.dim('—')} ${pc.white('programmatic control via')} ${pc.yellow('window.egakiSDK')}`;
322
+ const sep = pc.dim(' ─'.padEnd(72, '─'));
323
+ p('');
324
+ p(title);
325
+ p(sep);
326
+ p('');
327
+ p(` ${pc.green('●')} ${pc.bold('Get info')} ${pc.dim('composition metadata, sections, fps')}`);
328
+ p(` ${pc.cyan(`playwriter -s 1 -e 'console.log(await state.page.evaluate(() => window.egakiSDK.getInfo()))'`)}`);
329
+ p('');
330
+ p(` ${pc.green('●')} ${pc.bold('Screenshot')} ${pc.dim('capture a single frame as PNG')}`);
331
+ p(` ${pc.cyan("playwriter -s 1 -e \"$(cat <<'JS'")}`);
332
+ p(` ${pc.cyan('const dataUrl = await state.page.evaluate(() => window.egakiSDK.screenshot({ frame: 60 }))')}`);
333
+ p(` ${pc.cyan('const buf = Buffer.from(await (await fetch(dataUrl)).arrayBuffer())')}`);
334
+ p(` ${pc.cyan('require("node:fs").writeFileSync("/tmp/frame-60.png", buf)')}`);
335
+ p(` ${pc.cyan('JS')}`);
336
+ p(` ${pc.cyan(')"')}`);
337
+ p('');
338
+ p(` ${pc.green('●')} ${pc.bold('Filmstrip')} ${pc.dim('grid of frames across scenes')}`);
339
+ p(` ${pc.cyan("playwriter -s 1 -e \"$(cat <<'JS'")}`);
340
+ p(` ${pc.cyan('const dataUrl = await state.page.evaluate(() =>')}`);
341
+ p(` ${pc.cyan(' window.egakiSDK.filmstrip({ scenes: [0, 1, 2], framesPerScene: 3 }))')}`);
342
+ p(` ${pc.cyan('const buf = Buffer.from(await (await fetch(dataUrl)).arrayBuffer())')}`);
343
+ p(` ${pc.cyan('require("node:fs").writeFileSync("/tmp/filmstrip.png", buf)')}`);
344
+ p(` ${pc.cyan('JS')}`);
345
+ p(` ${pc.cyan(')"')}`);
346
+ p('');
347
+ p(` ${pc.green('●')} ${pc.bold('Export video')} ${pc.dim('render MP4 via WebCodecs')}`);
348
+ p(` ${pc.cyan(`playwriter -s 1 -e 'await state.page.evaluate(() => window.egakiSDK.export({ path: "output.mp4" }))'`)}`);
349
+ p('');
350
+ p(` ${pc.green('●')} ${pc.bold('Seek')} ${pc.dim('jump to a specific frame')}`);
351
+ p(` ${pc.cyan(`playwriter -s 1 -e 'await state.page.evaluate(() => window.egakiSDK.seekTo(120))'`)}`);
352
+ p('');
353
+ }, 50);
354
+ });
355
+ },
294
356
  // HMR for file changes in the project.
295
357
  //
296
358
  // Entry MDX: the source string flows server → client through the RSC
@@ -352,13 +414,32 @@ export function video(options) {
352
414
  return [];
353
415
  }
354
416
  if (isEntryMdx) {
355
- invalidateVirtual([RESOLVED_APP, RESOLVED_MDX]);
417
+ // Every root .mdx is both a route entry (virtual:egaki-mdx / RSC
418
+ // flight) and a ?raw partial in virtual:egaki-modules (importable
419
+ // via `import X from './foo.mdx'`). Must refresh both paths:
420
+ // rsc:update for the entry source, and modules dep-accept for
421
+ // partials. Invalidating only RESOLVED_MODULES is not enough —
422
+ // Vite keeps the cached ?raw binding, so also invalidate every
423
+ // module graph node for this file (including `?raw`).
424
+ invalidateVirtual([RESOLVED_APP, RESOLVED_MDX, RESOLVED_MODULES]);
356
425
  if (this.environment.name === 'client') {
426
+ const fileMods = this.environment.moduleGraph.getModulesByFile(ctx.file);
427
+ if (fileMods) {
428
+ for (const mod of fileMods) {
429
+ this.environment.moduleGraph.invalidateModule(mod);
430
+ }
431
+ }
357
432
  ctx.server.environments.client?.hot.send({
358
433
  type: 'custom',
359
434
  event: 'rsc:update',
360
435
  data: { file: ctx.file },
361
436
  });
437
+ const modulesMod = this.environment.moduleGraph.getModuleById(RESOLVED_MODULES);
438
+ const updates = [
439
+ ...(fileMods ? [...fileMods] : []),
440
+ ...(modulesMod ? [modulesMod] : []),
441
+ ];
442
+ return updates;
362
443
  }
363
444
  return [];
364
445
  }
@@ -408,22 +489,29 @@ export function video(options) {
408
489
  arr.push(new RegExp(`^${PKG_NAME}`));
409
490
  arr.push(/^tweakpane/);
410
491
  config.resolve.noExternal = arr;
411
- // Deduplicate motion packages so egaki's motion-timing virtual module
412
- // patches the same JSAnimation class that motion/react uses. Without
413
- // this, Vite can resolve motion-dom to different instances and
414
- // prototype patches won't affect the user's animations.
415
- // Always applied (not gated by hasMotion) because configEnvironment
416
- // runs before configResolved where hasMotion is set. Harmless when
417
- // motion isn't installed — dedupe on missing packages is a no-op.
418
- config.resolve.dedupe = mergeUnique(config.resolve.dedupe, ['motion', 'motion-dom', 'motion-utils']);
492
+ // Baseline hint for Vite's built-in resolver (same list as the
493
+ // resolveId singleton plugin below). Real enforcement is the
494
+ // egaki:dedupe-singleton plugin — resolve.dedupe alone is not enough
495
+ // under pnpm when remotion is only a transitive dep of egaki.
496
+ config.resolve.dedupe = mergeUnique(config.resolve.dedupe, [...dedupePackages]);
419
497
  if (name === 'client') {
420
498
  config.optimizeDeps ??= {};
421
499
  config.optimizeDeps.exclude = mergeUnique(config.optimizeDeps.exclude, [PKG_NAME]);
500
+ // Prebundle safe-mdx as a single entry so its pure-CJS leaves
501
+ // (format via fault/micromark) stay internalized with CJS interop.
502
+ // Do NOT list format/fault as separate optimizeDeps entries — that
503
+ // splits them into named-only ESM chunks and the browser crashes with
504
+ // "does not provide an export named 'default'" (same bug Holocron hit
505
+ // when safe-mdx leaked to the client; see holocron vite CHANGELOG).
506
+ // Holocron's clean fix is keep safe-mdx server-only; egaki needs it on
507
+ // the client for MDX-in-browser, so correct prebundling is the fix.
422
508
  config.optimizeDeps.include = mergeUnique(config.optimizeDeps.include, [
423
509
  `${PKG_NAME} > spiceflow > @vitejs/plugin-rsc/vendor/react-server-dom/client.browser`,
424
510
  `${PKG_NAME} > remotion`,
425
511
  `${PKG_NAME} > @remotion/player`,
426
512
  `${PKG_NAME} > safe-mdx`,
513
+ 'safe-mdx',
514
+ 'safe-mdx/parse',
427
515
  ]);
428
516
  }
429
517
  if (name === 'rsc' || name === 'ssr') {
@@ -432,9 +520,39 @@ export function video(options) {
432
520
  }
433
521
  },
434
522
  };
523
+ // Force remotion / motion to a single copy across all importers, even when
524
+ // egaki is a transitive or file:-linked dep under pnpm. Mirrors
525
+ // spiceflow:dedupe-singleton (spiceflow/src/vite.tsx).
526
+ //
527
+ // Vite's resolve.dedupe sets basedir=root then walks node_modules/, but
528
+ // pnpm only symlinks direct deps at the root. Transitive deps stay buried
529
+ // in .pnpm/, so dedupe silently falls back to importer-based resolution
530
+ // and Player vs useVideoConfig load different remotion instances.
531
+ const dedupeSingletonPlugin = {
532
+ name: 'egaki:dedupe-singleton',
533
+ enforce: 'pre',
534
+ async resolveId(id, _importer, options) {
535
+ if (id.startsWith('.') ||
536
+ id.startsWith('/') ||
537
+ id.startsWith('\0') ||
538
+ id.includes('?')) {
539
+ return null;
540
+ }
541
+ const pkgName = id.startsWith('@')
542
+ ? id.split('/').slice(0, 2).join('/')
543
+ : (id.split('/')[0] ?? id);
544
+ if (!dedupePackages.has(pkgName))
545
+ return null;
546
+ return this.resolve(id, dedupeImporter, {
547
+ ...options,
548
+ skipSelf: true,
549
+ });
550
+ },
551
+ };
435
552
  return [
436
553
  videoPlugin,
437
554
  rscPackagePlugin,
555
+ dedupeSingletonPlugin,
438
556
  tailwindcss(),
439
557
  spiceflowPlugin({ entry: VIRTUAL_APP }),
440
558
  react(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "egaki",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "description": "AI image, video, and speech generation CLI with MDX-to-video framework",
6
6
  "main": "./dist/cli/main.js",
@@ -94,9 +94,10 @@
94
94
  "@tailwindcss/vite": "^4.2.2",
95
95
  "@tweakpane/core": "^2.0.5",
96
96
  "@tweakpane/plugin-essentials": "^0.2.1",
97
+ "@vitejs/plugin-react": "^6.0.2",
97
98
  "ai": "6.0.103",
98
99
  "eventsource-parser": "^3.0.6",
99
- "goke": "^6.12.1",
100
+ "goke": "^6.14.1",
100
101
  "js-yaml": "^4.1.1",
101
102
  "mediabunny": "^1.50.8",
102
103
  "moveable": "^0.53.0",
@@ -104,27 +105,23 @@
104
105
  "react": "19.2.7",
105
106
  "react-dom": "19.2.7",
106
107
  "remotion": "4.0.494",
107
- "safe-mdx": "^1.11.3",
108
+ "safe-mdx": "^1.12.0",
108
109
  "shiki": "^4.2.0",
110
+ "spiceflow": "1.26.0-rsc.7",
109
111
  "string-dedent": "^3.0.2",
110
112
  "tailwindcss": "^4.3.0",
111
113
  "tweakpane": "^4.0.5",
114
+ "vite": "^8.0.10",
112
115
  "yaml": "^2.9.0",
113
116
  "zod": "^4.3.6",
114
- "@vitejs/plugin-react": "^6.0.2",
115
- "spiceflow": "1.26.0-rsc.7",
116
117
  "zustand": "^5.0.14"
117
118
  },
118
119
  "peerDependencies": {
119
- "vite": ">=6",
120
120
  "motion": ">=12",
121
121
  "motion-dom": ">=12",
122
122
  "motion-utils": ">=12"
123
123
  },
124
124
  "peerDependenciesMeta": {
125
- "vite": {
126
- "optional": true
127
- },
128
125
  "motion": {
129
126
  "optional": true
130
127
  },
@@ -141,7 +138,6 @@
141
138
  "devDependencies": {
142
139
  "@types/js-yaml": "^4.0.9",
143
140
  "@types/react": "^19.2.17",
144
- "vite": "^8.0.10",
145
141
  "vitest": "^4.1.8"
146
142
  },
147
143
  "scripts": {
@@ -66,6 +66,20 @@ Agents should always pass `-m` explicitly to avoid the interactive picker.
66
66
 
67
67
  1. **`grok-imagine-video-1.5`** (xAI Grok)
68
68
 
69
+ ## Extracting small assets from a reference
70
+
71
+ **Crop to the target object first** with an image tool, leaving a small margin,
72
+ then pass that crop to `egaki image --input`. This makes small details easier for
73
+ the model to see and reduces ambiguity about which object to preserve. In a
74
+ switch-extraction comparison, a tight crop preserved the design much better than
75
+ the full instrument image with the same prompt.
76
+
77
+ Keep the prompt short: "Extract this switch exactly as it is, with a transparent
78
+ background. Do not redesign it or change its shape, materials, or details."
79
+
80
+ **Inspect the result:** generation can still redraw details. Use a direct crop
81
+ and background masking when exact original pixels are required.
82
+
69
83
  ## Video generation note
70
84
 
71
85
  Video generation can be very slow (1-3 minutes per request). Always use a