mulmocast 2.16.0 → 2.18.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 (51) hide show
  1. package/README.md +110 -38
  2. package/lib/actions/captions.js +1 -1
  3. package/lib/actions/image_agents.d.ts +1 -0
  4. package/lib/actions/image_agents.js +2 -2
  5. package/lib/actions/image_references.d.ts +2 -0
  6. package/lib/actions/image_references.js +3 -1
  7. package/lib/actions/images.d.ts +2 -0
  8. package/lib/actions/images.js +1 -0
  9. package/lib/actions/pdf.js +23 -2
  10. package/lib/agents/image_openai_agent.js +4 -3
  11. package/lib/agents/movie_genai_agent.d.ts +24 -0
  12. package/lib/agents/movie_genai_agent.js +56 -43
  13. package/lib/agents/tts_gemini_agent.d.ts +56 -0
  14. package/lib/agents/tts_gemini_agent.js +43 -38
  15. package/lib/cli/commands/bundle/builder.d.ts +2 -0
  16. package/lib/cli/commands/html/builder.d.ts +2 -0
  17. package/lib/cli/commands/markdown/builder.d.ts +2 -0
  18. package/lib/cli/commands/viewer/builder.d.ts +2 -0
  19. package/lib/cli/common.d.ts +2 -0
  20. package/lib/cli/common.js +5 -0
  21. package/lib/cli/helpers.js +3 -1
  22. package/lib/methods/mulmo_studio_context.d.ts +4 -0
  23. package/lib/methods/mulmo_studio_context.js +7 -0
  24. package/lib/types/provider2agent.d.ts +3 -4
  25. package/lib/types/provider2agent.js +38 -26
  26. package/lib/types/schema.d.ts +1 -1
  27. package/lib/types/type.d.ts +2 -0
  28. package/lib/utils/estimate_usage.js +12 -11
  29. package/lib/utils/gemini_omni_video.d.ts +49 -0
  30. package/lib/utils/gemini_omni_video.js +36 -0
  31. package/lib/utils/gemini_tts.d.ts +45 -0
  32. package/lib/utils/gemini_tts.js +35 -0
  33. package/lib/utils/html_render.d.ts +6 -5
  34. package/lib/utils/html_render.js +37 -30
  35. package/lib/utils/image_plugins/chart.js +2 -1
  36. package/lib/utils/image_plugins/html_tailwind.js +5 -4
  37. package/lib/utils/image_plugins/markdown.js +2 -1
  38. package/lib/utils/image_plugins/mermaid.js +2 -1
  39. package/lib/utils/image_plugins/slide.js +2 -1
  40. package/lib/utils/image_plugins/text_slide.js +2 -1
  41. package/lib/utils/image_plugins/vision.js +7 -0
  42. package/lib/utils/render_network_guard.d.ts +2 -0
  43. package/lib/utils/render_network_guard.js +36 -0
  44. package/lib/utils/render_network_policy.d.ts +15 -0
  45. package/lib/utils/render_network_policy.js +85 -0
  46. package/package.json +2 -2
  47. package/scripts/test/test_genai.json +10 -9
  48. package/scripts/test/test_genai_movie.json +1 -1
  49. package/scripts/test/test_image_references.json +3 -2
  50. package/scripts/test/test_movie_references.json +2 -1
  51. package/scripts/test/test_veo31_lite.json +3 -3
@@ -0,0 +1,85 @@
1
+ import nodePath from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ // What an HTML renderer's page may reach in strict network mode: files under the allowed roots, in-memory
4
+ // URLs, and the CDNs our own templates load from. Kept free of I/O so the rules can be tested without a browser.
5
+ export const RENDER_CDN_HOSTS = ["cdn.tailwindcss.com", "cdn.jsdelivr.net", "fonts.googleapis.com", "fonts.gstatic.com"];
6
+ // jsDelivr serves any file of any package and publishes per-version and per-file hit counts, so a page free to
7
+ // choose the path could signal through them; only the exact files our templates load are allowed.
8
+ export const RENDER_JSDELIVR_PATHS = [
9
+ "/npm/mermaid/dist/mermaid.min.js",
10
+ "/npm/mermaid@10/dist/mermaid.min.js",
11
+ "/npm/chart.js",
12
+ "/npm/chart.js@4",
13
+ "/npm/chartjs-chart-sankey",
14
+ "/npm/chartjs-chart-treemap@3",
15
+ ];
16
+ const JSDELIVR_HOST = "cdn.jsdelivr.net";
17
+ const IN_MEMORY_SCHEMES = ["data:", "blob:", "about:"];
18
+ export const STRICT_NETWORK_ENV = "MULMO_STRICT_NETWORK";
19
+ export const strictNetworkFromEnv = (env) => ["1", "true"].includes(env[STRICT_NETWORK_ENV] ?? "");
20
+ const parseUrl = (url) => {
21
+ try {
22
+ return new URL(url);
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ };
28
+ const filePathOf = (parsed) => {
29
+ try {
30
+ return fileURLToPath(parsed);
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ };
36
+ const isWithin = (root, filePath) => {
37
+ const relative = nodePath.relative(root, filePath);
38
+ return relative === "" || (!relative.startsWith("..") && !nodePath.isAbsolute(relative));
39
+ };
40
+ // The URL parser has already collapsed dot segments (encoded or not); the roots must already be real paths.
41
+ const isInsideFileRoots = (parsed, realRoots, realPath) => {
42
+ const filePath = filePathOf(parsed);
43
+ const realFilePath = filePath === undefined ? undefined : realPath(filePath);
44
+ return realFilePath !== undefined && realRoots.some((root) => isWithin(root, realFilePath));
45
+ };
46
+ const identityRealPath = (filePath) => nodePath.resolve(filePath);
47
+ const isAllowedCdnUrl = (parsed) => {
48
+ if (parsed.protocol !== "https:" || parsed.port !== "" || !RENDER_CDN_HOSTS.includes(parsed.hostname))
49
+ return false;
50
+ return parsed.hostname !== JSDELIVR_HOST || (parsed.search === "" && RENDER_JSDELIVR_PATHS.includes(parsed.pathname));
51
+ };
52
+ export const isAllowedRenderRequest = (url, realRoots = [], realPath = identityRealPath) => {
53
+ const parsed = parseUrl(url);
54
+ if (!parsed)
55
+ return false;
56
+ if (IN_MEMORY_SCHEMES.includes(parsed.protocol))
57
+ return true;
58
+ if (parsed.protocol === "file:")
59
+ return isInsideFileRoots(parsed, realRoots, realPath);
60
+ return isAllowedCdnUrl(parsed);
61
+ };
62
+ // A root that does not exist yet (the temp page before it is written) is its real parent plus its name.
63
+ export const realRoot = (root, realPath) => {
64
+ const resolved = nodePath.resolve(root);
65
+ const parent = nodePath.dirname(resolved);
66
+ return realPath(resolved) ?? nodePath.join(realPath(parent) ?? parent, nodePath.basename(resolved));
67
+ };
68
+ // Popups and preconnect hints open connections outside the page's request interception, so the whole browser
69
+ // talks through a proxy that does not exist, except for the CDNs (loopback included, which Chromium bypasses by default).
70
+ const UNREACHABLE_PROXY = "http://127.0.0.1:1";
71
+ export const strictNetworkLaunchArgs = () => [`--proxy-server=${UNREACHABLE_PROXY}`, `--proxy-bypass-list=${RENDER_CDN_HOSTS.join(";")};<-loopback>`];
72
+ // A popup the page writes into gets no evaluateOnNewDocument script, so it would still have WebRTC.
73
+ export const POPUP_BLOCK_SCRIPT = 'Object.defineProperty(globalThis, "open", { value: () => null, writable: false, configurable: false });';
74
+ // Request interception does not see WebSocket or EventSource connections; this does.
75
+ export const RENDER_CONTENT_SECURITY_POLICY = "connect-src 'self' data: blob:";
76
+ const CSP_META = `<meta http-equiv="Content-Security-Policy" content="${RENDER_CONTENT_SECURITY_POLICY}">`;
77
+ // The meta must come before anything a script could hide in, so it is never placed by searching for <head>
78
+ // (a comment or script text can contain one): right after a leading doctype, else first. Before a doctype it
79
+ // would switch the page to quirks mode; first otherwise, the parser still puts it in the head.
80
+ const LEADING_DOCTYPE = /^(\uFEFF?)(\s*<!doctype[^>]*>)?/i;
81
+ export const withRenderContentSecurityPolicy = (html) => {
82
+ const [prefix = "", bom = "", doctype] = LEADING_DOCTYPE.exec(html) ?? [];
83
+ const insertAt = doctype ? prefix.length : bom.length;
84
+ return [html.slice(0, insertAt), CSP_META, html.slice(insertAt)].join("");
85
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mulmocast",
3
- "version": "2.16.0",
3
+ "version": "2.18.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "lib/index.node.js",
@@ -99,7 +99,7 @@
99
99
  "homepage": "https://github.com/receptron/mulmocast-cli#readme",
100
100
  "dependencies": {
101
101
  "@google-cloud/text-to-speech": "^6.4.1",
102
- "@google/genai": "^2.17.1",
102
+ "@google/genai": "^2.24.0",
103
103
  "@graphai/anthropic_agent": "^2.1.0",
104
104
  "@graphai/browserless_agent": "^2.0.4",
105
105
  "@graphai/gemini_agent": "^2.1.0",
@@ -55,24 +55,25 @@
55
55
  "moviePrompt": "a woman takes a selfie with her phone"
56
56
  },
57
57
  {
58
- "id": "genai_veo3",
59
- "text": "movie generated by veo3",
58
+ "id": "genai_omni",
59
+ "text": "movie generated by gemini omni",
60
60
  "moviePrompt": "a woman is walking through a busy Tokyo street at night, she is wearing dark sunglasses",
61
61
  "movieParams": {
62
- "model": "veo-3.1-lite-generate-preview"
62
+ "model": "gemini-omni-1.1-flash"
63
63
  }
64
64
  },
65
65
  {
66
- "id": "genai_veo3_1",
67
- "text": "movie generated by veo3_1",
66
+ "id": "genai_omni_long",
67
+ "text": "a twelve-second movie generated by gemini omni, extended once",
68
+ "duration": 12,
68
69
  "moviePrompt": "a woman is walking through a busy Tokyo street at night, she is wearing dark sunglasses",
69
70
  "movieParams": {
70
- "model": "veo-3.1-generate-preview"
71
+ "model": "gemini-omni-1.1-flash"
71
72
  }
72
73
  },
73
74
  {
74
- "id": "genai_veo3_image",
75
- "text": "movie generated by veo3",
75
+ "id": "genai_omni_image",
76
+ "text": "movie generated by gemini omni from a first frame",
76
77
  "duration": 5,
77
78
  "imagePrompt": "a woman is walking through a busy Tokyo street at night, she is wearing dark sunglasses",
78
79
  "imageParams": {
@@ -80,7 +81,7 @@
80
81
  },
81
82
  "moviePrompt": "a woman is walking through a busy Tokyo street at night, she is wearing dark sunglasses",
82
83
  "movieParams": {
83
- "model": "veo-3.1-lite-generate-preview"
84
+ "model": "gemini-omni-1.1-flash"
84
85
  }
85
86
  }
86
87
  ]
@@ -11,7 +11,7 @@
11
11
  "lang": "en",
12
12
  "movieParams": {
13
13
  "provider": "google",
14
- "model": "veo-3.1-generate-preview"
14
+ "model": "gemini-omni-1.1-flash"
15
15
  },
16
16
  "beats": [
17
17
  {
@@ -15,7 +15,8 @@
15
15
  },
16
16
  "movieParams": {
17
17
  "provider": "google",
18
- "model": "veo-3.1-generate-preview"
18
+ "model": "veo-3.1-generate-001",
19
+ "vertexai_project": "YOUR_PROJECT_ID"
19
20
  },
20
21
  "lang": "en",
21
22
  "beats": [
@@ -24,7 +25,7 @@
24
25
  "text": "veo-3.1 with both ASSET and STYLE reference images",
25
26
  "moviePrompt": "the woman runs through narrow alleyways, neon signs blurring in the rain",
26
27
  "movieParams": {
27
- "model": "veo-3.1-generate-preview",
28
+ "model": "veo-3.1-generate-001",
28
29
  "referenceImages": [
29
30
  { "imageName": "character", "referenceType": "ASSET" },
30
31
  { "imageName": "black_cat", "referenceType": "ASSET" }
@@ -5,7 +5,8 @@
5
5
  "canvasSize": { "width": 1280, "height": 720 },
6
6
  "movieParams": {
7
7
  "provider": "google",
8
- "model": "veo-3.1-generate-preview"
8
+ "model": "veo-3.1-generate-001",
9
+ "vertexai_project": "YOUR_PROJECT_ID"
9
10
  },
10
11
  "imageParams": {
11
12
  "images": {
@@ -5,13 +5,13 @@
5
5
  "canvasSize": { "width": 1280, "height": 720 },
6
6
  "beats": [
7
7
  {
8
- "id": "google_genai_lite",
9
- "text": "Google GenAI veo-3.1-lite-generate-preview text-to-video",
8
+ "id": "google_genai_omni",
9
+ "text": "Google GenAI gemini-omni-1.1-flash text-to-video",
10
10
  "duration": 4,
11
11
  "moviePrompt": "A calm ocean wave rolling onto a sandy beach at golden hour",
12
12
  "movieParams": {
13
13
  "provider": "google",
14
- "model": "veo-3.1-lite-generate-preview"
14
+ "model": "gemini-omni-1.1-flash"
15
15
  }
16
16
  },
17
17
  {