@vanillaskyai/video 0.7.1 → 0.8.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 (84) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/PUBLIC-API.md +83 -79
  3. package/README.md +16 -15
  4. package/dist/{builtin-server-KB7FKF6A.js → builtin-server-CUYPIDE7.js} +2 -2
  5. package/dist/check-runtime.js +1 -1
  6. package/dist/{chunk-2PYO6VAC.js → chunk-2DOLF4QN.js} +1 -43
  7. package/dist/{chunk-GAIOHGNR.js → chunk-2OKB27TG.js} +42 -45
  8. package/dist/{chunk-K746NUIP.js → chunk-2USVB3CY.js} +80 -29
  9. package/dist/{chunk-RE4IMWJR.js → chunk-3VRMKFM7.js} +87 -14
  10. package/dist/{chunk-JNN3EYVP.js → chunk-4UNKZEAT.js} +46 -46
  11. package/dist/{chunk-2XT4MZ76.js → chunk-APQKY6P3.js} +36 -3
  12. package/dist/{chunk-LLPMVDJZ.js → chunk-L7QGGKJ2.js} +69 -38
  13. package/dist/{chunk-RGF452LL.js → chunk-OWBT45EN.js} +1 -1
  14. package/dist/{chunk-LKBZX7GV.js → chunk-PP3MCPN4.js} +4 -4
  15. package/dist/chunk-ZD2VTUYR.js +28 -0
  16. package/dist/cli.js +131 -24
  17. package/dist/{compose-video-PD6LKKRK.js → compose-video-2NFH4DZY.js} +3 -3
  18. package/dist/{events-B6qS1Lsb.d.ts → events-BQ6z264n.d.ts} +1 -1
  19. package/dist/index.d.ts +5 -67
  20. package/dist/index.js +2 -4
  21. package/dist/{kit-8g46H2RZ.d.ts → kit-tQnCPMOM.d.ts} +1 -1
  22. package/dist/react.d.ts +33 -53
  23. package/dist/react.js +642 -536
  24. package/dist/server.d.ts +11 -13
  25. package/dist/server.js +170 -81
  26. package/dist/template-catalog.js +1 -1
  27. package/dist/templates.d.ts +3 -3
  28. package/dist/test.d.ts +2 -2
  29. package/dist/test.js +3 -3
  30. package/dist/{text-stream-ROTNGSD4.js → text-stream-54LXABJW.js} +1 -1
  31. package/dist/{types-2wHBqtg8.d.ts → types-CEh9eUFu.d.ts} +1 -1
  32. package/dist/{types-DrABlRa7.d.ts → types-DWFO6qca.d.ts} +1 -1
  33. package/docs/agent-integration.md +9 -3
  34. package/docs/architecture.md +8 -10
  35. package/docs/concepts.md +16 -40
  36. package/docs/custom-templates.md +27 -23
  37. package/docs/customization.md +12 -49
  38. package/docs/errors.md +58 -68
  39. package/docs/getting-started.md +21 -14
  40. package/docs/media-and-audio.md +2 -2
  41. package/docs/performance.md +69 -0
  42. package/docs/persistence.md +12 -27
  43. package/docs/production.md +2 -2
  44. package/docs/provider-integration.md +11 -5
  45. package/docs/reference/provider-adapters.md +37 -89
  46. package/docs/security.md +1 -1
  47. package/docs/streaming-protocol.md +2 -2
  48. package/examples/custom-template/README.md +6 -6
  49. package/package.json +6 -7
  50. package/registry/items/barChart.json +2 -2
  51. package/registry/items/bigNumber.json +2 -2
  52. package/registry/items/brandMessage.json +2 -2
  53. package/registry/items/cardList.json +2 -2
  54. package/registry/items/codeEditor.json +2 -2
  55. package/registry/items/ctaLogo.json +2 -2
  56. package/registry/items/ctaMedia.json +2 -2
  57. package/registry/items/incomingCall.json +2 -2
  58. package/registry/items/media.json +2 -2
  59. package/registry/items/milestone.json +2 -2
  60. package/registry/items/notification.json +2 -2
  61. package/registry/items/phoneMockup.json +2 -2
  62. package/registry/items/problemSolution.json +2 -2
  63. package/registry/items/progressRing.json +2 -2
  64. package/registry/items/promptInput.json +2 -2
  65. package/registry/items/reaction.json +2 -2
  66. package/registry/items/reviewStack.json +2 -2
  67. package/registry/items/steps.json +2 -2
  68. package/registry/items/terminal.json +2 -2
  69. package/registry/items/testimonial.json +2 -2
  70. package/registry/items/tripleStats.json +2 -2
  71. package/registry/items/tweet.json +2 -2
  72. package/registry/items/webMockup.json +2 -2
  73. package/starters/video-chat/.env.example +4 -2
  74. package/starters/video-chat/README.md +17 -5
  75. package/starters/video-chat/package.json +2 -4
  76. package/starters/video-chat/providers/speech.ts +20 -0
  77. package/starters/video-chat/providers/video.ts +66 -0
  78. package/starters/video-chat/providers.ts +3 -0
  79. package/starters/video-chat/server.ts +4 -80
  80. package/starters/video-chat/stock.ts +114 -99
  81. package/dist/chunk-SKRGRKHY.js +0 -142
  82. package/dist/state-DZcKuS32.d.ts +0 -3
  83. package/docs/branding-and-personalization.md +0 -87
  84. package/examples/custom-template/supplied-media.tsx +0 -92
@@ -3,7 +3,7 @@ function createBackgroundMediaSchemaProperties(options = {}) {
3
3
  const mediaKeyword = {
4
4
  type: "string",
5
5
  title: "Background search keyword",
6
- description: "2-4 word English term for Pexels stock-footage search (auto-fills mediaUrl).",
6
+ description: "2-4 word English term for stock-media search (auto-fills mediaUrl).",
7
7
  ...options.stockMediaKeyword === false ? {} : { format: "stock-media-keyword" },
8
8
  default: ""
9
9
  };
@@ -26,7 +26,7 @@ function createBackgroundMediaSchemaProperties(options = {}) {
26
26
  mediaPoster: {
27
27
  type: "string",
28
28
  title: "Background poster image",
29
- description: "Still image URL shown while a video backdrop is decoding its first frame. Auto-filled from Pexels' thumbnail when fillPexelsUrls sets a video mediaUrl. Hides the gradient flash that would otherwise appear in the ~50\u2013400ms gap between a <video> mounting and decoding its first frame.",
29
+ description: "Still image URL shown while a video backdrop is decoding its first frame. A media resolver can supply a poster alongside a video mediaUrl. Hides the gradient flash that would otherwise appear in the ~50\u2013400ms gap between a <video> mounting and decoding its first frame.",
30
30
  format: "uri",
31
31
  default: ""
32
32
  },
@@ -84,7 +84,7 @@ var BUILTIN_TEMPLATE_SCHEMAS = {
84
84
  "reactionTag": {
85
85
  "type": "string",
86
86
  "title": "Reaction tag",
87
- "description": "Pexels meme search intent. Pick from the punchline/problem copy first, then the overall video topic.",
87
+ "description": "Stock-media search intent. Pick from the punchline/problem copy first, then the overall video topic.",
88
88
  "enum": [
89
89
  "launch",
90
90
  "productLaunch",
@@ -125,7 +125,7 @@ var BUILTIN_TEMPLATE_SCHEMAS = {
125
125
  "mediaUrl": {
126
126
  "type": "string",
127
127
  "title": "Reaction clip",
128
- "description": "Resolved Pexels MP4 URL.",
128
+ "description": "Resolved stock-video URL.",
129
129
  "format": "uri",
130
130
  "default": ""
131
131
  },
@@ -0,0 +1,28 @@
1
+ import {
2
+ getReadableSceneDuration
3
+ } from "./chunk-3O7OMMMF.js";
4
+
5
+ // src/protocol/scene-duration.ts
6
+ var NARRATION_WORDS_PER_SECOND = 2.6;
7
+ var NARRATION_TAIL_SECONDS = 0.6;
8
+ function getSceneDurationBounds(scene, metadata) {
9
+ return {
10
+ minimum: Math.max(1e-3, metadata?.minDuration ?? 1),
11
+ readable: getReadableSceneDuration(scene, metadata)
12
+ };
13
+ }
14
+ function getSpokenDuration(narration) {
15
+ const words = narration.trim().split(/\s+/u).filter(Boolean).length;
16
+ if (words === 0) return 0;
17
+ return words / NARRATION_WORDS_PER_SECOND + NARRATION_TAIL_SECONDS;
18
+ }
19
+ function getSceneDuration(scene, metadata) {
20
+ const { readable } = getSceneDurationBounds(scene, metadata);
21
+ return Math.max(readable, scene.narration ? getSpokenDuration(scene.narration) : 0);
22
+ }
23
+
24
+ export {
25
+ getSceneDurationBounds,
26
+ getSpokenDuration,
27
+ getSceneDuration
28
+ };
package/dist/cli.js CHANGED
@@ -8,14 +8,14 @@ import {
8
8
  import {
9
9
  getBuiltinTemplateMetadata,
10
10
  listBuiltinTemplateMetadata
11
- } from "./chunk-JNN3EYVP.js";
11
+ } from "./chunk-4UNKZEAT.js";
12
12
  import "./chunk-3O7OMMMF.js";
13
13
  import {
14
14
  assertTemplateTransitionMetadata
15
15
  } from "./chunk-2E6T633S.js";
16
16
  import {
17
17
  BUILTIN_TEMPLATE_MANIFEST
18
- } from "./chunk-LKBZX7GV.js";
18
+ } from "./chunk-PP3MCPN4.js";
19
19
  import {
20
20
  externalVideoBackdropCapabilitySource
21
21
  } from "./chunk-WJ6FLKNC.js";
@@ -1629,6 +1629,10 @@ async function initVideoChatApp(options) {
1629
1629
  for (const path of SCAFFOLD_FILES) {
1630
1630
  planned.set(path, readFileSync2(join8(starterRoot, path), "utf8"));
1631
1631
  }
1632
+ const providersPath = safeProjectPath(cwd, "providers.ts");
1633
+ if (!existsSync3(providersPath)) {
1634
+ planned.set("providers.ts", readFileSync2(join8(starterRoot, "providers.ts"), "utf8"));
1635
+ }
1632
1636
  planned.set("package.json", mergedManifest(cwd, starterRoot, options.sdkSpec));
1633
1637
  planned.set(".gitignore", mergeGitignore(
1634
1638
  existsSync3(join8(cwd, ".gitignore")) ? readFileSync2(join8(cwd, ".gitignore"), "utf8") : void 0
@@ -1651,7 +1655,6 @@ async function initVideoChatApp(options) {
1651
1655
  }
1652
1656
  throw new Error(`${path} already exists with different content. Run init in a new npm project.`);
1653
1657
  }
1654
- if (!changed) return { initialized: false, installed: false };
1655
1658
  for (const [path, contents] of planned) {
1656
1659
  const destination = safeProjectPath(cwd, path);
1657
1660
  if (!existsSync3(destination) || readFileSync2(destination, "utf8") !== contents) atomicWrite(destination, contents);
@@ -1662,16 +1665,89 @@ async function initVideoChatApp(options) {
1662
1665
  const detail = cause instanceof Error ? cause.message : String(cause);
1663
1666
  throw new Error(`${detail}. The app files are ready; run npm install to finish setup.`);
1664
1667
  }
1665
- return { initialized: true, installed: true };
1668
+ return { initialized: changed, installed: true };
1669
+ }
1670
+
1671
+ // src/cli/providers.ts
1672
+ import { randomUUID as randomUUID3 } from "crypto";
1673
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
1674
+ import { dirname as dirname4, join as join9 } from "path";
1675
+ var PROVIDERS = {
1676
+ speech: { dependency: "@ai-sdk/xai", version: "^4.0.52", key: "XAI_API_KEY", export: "speechProvider" },
1677
+ video: { dependency: "@fal-ai/client", version: "1.10.1", key: "FAL_KEY", export: "videoProvider" }
1678
+ };
1679
+ function registry(enabled) {
1680
+ const imports = enabled.map((name) => `import { ${PROVIDERS[name].export} } from "./providers/${name}";`).join("\n");
1681
+ const value = enabled.length ? `{
1682
+ ${enabled.map((name) => ` ...${PROVIDERS[name].export},`).join("\n")}
1683
+ }` : "{}";
1684
+ return `import type { VideoChatHandlerOptions } from "@vanillaskyai/video/server";
1685
+ ${imports ? `${imports}
1686
+ ` : ""}
1687
+ export const providers: Pick<VideoChatHandlerOptions, "generateSpeech" | "generateVideo" | "transcribe"> = ${value};
1688
+ `;
1689
+ }
1690
+ async function addVideoChatProvider(name, options) {
1691
+ if (name !== "speech" && name !== "video") throw new Error("Choose a provider capability: speech or video.");
1692
+ const manifestPath = safeProjectPath(options.cwd, "package.json");
1693
+ const registryPath = safeProjectPath(options.cwd, "providers.ts");
1694
+ if (!existsSync4(manifestPath) || !existsSync4(registryPath)) throw new Error("Run vanillasky init before adding providers.");
1695
+ const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
1696
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) throw new Error("Expected an application package.json object.");
1697
+ const config = manifest.vanillasky ?? {};
1698
+ if (!config || typeof config !== "object" || Array.isArray(config)) throw new Error("Expected vanillasky configuration to be an object.");
1699
+ const previous = config.providers ?? [];
1700
+ if (!Array.isArray(previous) || previous.some((value) => value !== "speech" && value !== "video") || new Set(previous).size !== previous.length) {
1701
+ throw new Error("Invalid vanillasky.providers configuration.");
1702
+ }
1703
+ const enabled = [...previous].sort();
1704
+ if (readFileSync3(registryPath, "utf8") !== registry(enabled)) {
1705
+ throw new Error("providers.ts has custom wiring. Preserve it and add the provider manually, or restore the generated wiring before using this command.");
1706
+ }
1707
+ const adapterPath = safeProjectPath(options.cwd, `providers/${name}.ts`);
1708
+ const adapter = readFileSync3(join9(options.starterRoot ?? locateStarterRoot(), "providers", `${name}.ts`), "utf8");
1709
+ if (!enabled.includes(name) && existsSync4(adapterPath) && readFileSync3(adapterPath, "utf8") !== adapter) {
1710
+ throw new Error(`providers/${name}.ts already contains customer source; no files were changed.`);
1711
+ }
1712
+ const dependencies = manifest.dependencies ?? {};
1713
+ if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) throw new Error("Expected dependencies to be an object.");
1714
+ if (!enabled.includes(name)) enabled.push(name);
1715
+ enabled.sort();
1716
+ const provider = PROVIDERS[name];
1717
+ manifest.dependencies = { ...dependencies, [provider.dependency]: dependencies[provider.dependency] ?? provider.version };
1718
+ manifest.vanillasky = { ...config, providers: enabled };
1719
+ const writes = /* @__PURE__ */ new Map([
1720
+ [manifestPath, `${JSON.stringify(manifest, null, 2)}
1721
+ `],
1722
+ [registryPath, registry(enabled)]
1723
+ ]);
1724
+ if (!existsSync4(adapterPath)) writes.set(adapterPath, adapter);
1725
+ const staged = [...writes].map(([path, contents]) => ({ path, contents, temporary: safeProjectPath(options.cwd, `${path.slice(options.cwd.length + 1)}.${randomUUID3()}.tmp`) }));
1726
+ for (const { path, contents, temporary } of staged) {
1727
+ mkdirSync3(dirname4(path), { recursive: true });
1728
+ writeFileSync3(temporary, contents, { flag: "wx" });
1729
+ try {
1730
+ renameSync3(temporary, path);
1731
+ } finally {
1732
+ if (existsSync4(temporary)) unlinkSync3(temporary);
1733
+ }
1734
+ }
1735
+ try {
1736
+ await (options.installDependencies ?? defaultInstall)(options.cwd);
1737
+ } catch {
1738
+ throw new Error(`Provider files are ready. Rerun npx vanillasky providers add ${name} to finish installation.`);
1739
+ }
1740
+ return `Enabled ${name}. Add ${provider.key} to .env.local, then restart npm run dev.`;
1666
1741
  }
1667
1742
 
1668
1743
  // src/cli/doctor.ts
1669
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1670
- import { join as join9, resolve as resolve8 } from "path";
1744
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
1745
+ import { join as join10, resolve as resolve8 } from "path";
1671
1746
  var REQUIRED_FILES = [
1672
1747
  ".env.local",
1673
1748
  "index.html",
1674
1749
  "server.ts",
1750
+ "providers.ts",
1675
1751
  "src/main.tsx",
1676
1752
  "stock.ts",
1677
1753
  "tsconfig.json",
@@ -1680,11 +1756,15 @@ var REQUIRED_FILES = [
1680
1756
  var REQUIRED_DEPENDENCIES = [
1681
1757
  "@vanillaskyai/video",
1682
1758
  "@ai-sdk/anthropic",
1683
- "@ai-sdk/xai",
1684
- "@fal-ai/client",
1685
1759
  "ai",
1686
1760
  "react",
1687
- "react-dom"
1761
+ "react-dom",
1762
+ "@types/node",
1763
+ "@types/react",
1764
+ "@types/react-dom",
1765
+ "@vitejs/plugin-react",
1766
+ "typescript",
1767
+ "vite"
1688
1768
  ];
1689
1769
  var PROVIDER_KEYS = /* @__PURE__ */ new Set(["ANTHROPIC_API_KEY", "XAI_API_KEY", "FAL_KEY", "PEXELS_API_KEY"]);
1690
1770
  var CLIENT_ENV_PREFIXES = ["VITE", "NEXT_PUBLIC"].map((prefix) => `${prefix}_`);
@@ -1717,14 +1797,14 @@ function doctorVideoChatApp(cwdInput) {
1717
1797
  const lines = [];
1718
1798
  let ok = true;
1719
1799
  for (const path of REQUIRED_FILES) {
1720
- if (!existsSync4(join9(cwd, path))) {
1800
+ if (!existsSync5(join10(cwd, path))) {
1721
1801
  ok = false;
1722
1802
  lines.push(`MISSING ${path}`);
1723
1803
  }
1724
1804
  }
1725
1805
  let manifest = {};
1726
1806
  try {
1727
- const parsed = JSON.parse(readFileSync3(join9(cwd, "package.json"), "utf8"));
1807
+ const parsed = JSON.parse(readFileSync4(join10(cwd, "package.json"), "utf8"));
1728
1808
  if (!isJsonObject(parsed)) throw new Error("expected an object");
1729
1809
  manifest = parsed;
1730
1810
  } catch {
@@ -1737,17 +1817,22 @@ function doctorVideoChatApp(cwdInput) {
1737
1817
  ok = false;
1738
1818
  lines.push(`MISSING dependency ${name}`);
1739
1819
  }
1820
+ const installed = (name) => existsSync5(join10(cwd, "node_modules", name, "package.json"));
1821
+ for (const name of REQUIRED_DEPENDENCIES.filter((name2) => dependencies[name2] && !installed(name2))) {
1822
+ ok = false;
1823
+ lines.push(`NOT INSTALLED ${name} \u2014 run npm install`);
1824
+ }
1740
1825
  const scripts = stringMap2(manifest.scripts);
1741
1826
  if (scripts.dev !== "vite" || scripts.build !== "tsc && vite build") {
1742
1827
  ok = false;
1743
1828
  lines.push("MISSING video-chat dev/build scripts");
1744
1829
  }
1745
- const ignore = existsSync4(join9(cwd, ".gitignore")) ? readFileSync3(join9(cwd, ".gitignore"), "utf8") : "";
1830
+ const ignore = existsSync5(join10(cwd, ".gitignore")) ? readFileSync4(join10(cwd, ".gitignore"), "utf8") : "";
1746
1831
  if (!effectivelyIgnoresLocalEnvironment(ignore)) {
1747
1832
  ok = false;
1748
1833
  lines.push("UNSAFE .env.local is not ignored");
1749
1834
  }
1750
- const environment = existsSync4(join9(cwd, ".env.local")) ? parseEnvironment(readFileSync3(join9(cwd, ".env.local"), "utf8")) : /* @__PURE__ */ new Map();
1835
+ const environment = existsSync5(join10(cwd, ".env.local")) ? parseEnvironment(readFileSync4(join10(cwd, ".env.local"), "utf8")) : /* @__PURE__ */ new Map();
1751
1836
  for (const name of environment.keys()) {
1752
1837
  if (CLIENT_ENV_PREFIXES.some((prefix) => name.startsWith(prefix) && PROVIDER_KEYS.has(name.slice(prefix.length)))) {
1753
1838
  ok = false;
@@ -1760,10 +1845,23 @@ function doctorVideoChatApp(cwdInput) {
1760
1845
  ok = false;
1761
1846
  lines.push("MISSING ANTHROPIC_API_KEY in .env.local");
1762
1847
  }
1763
- lines.push(environment.get("XAI_API_KEY") ? "READY generated speech" : "OPTIONAL generated speech \u2014 add XAI_API_KEY");
1764
- lines.push(environment.get("FAL_KEY") ? "READY generated video + transcription" : "OPTIONAL generated video + transcription \u2014 add FAL_KEY");
1848
+ const configuration = isJsonObject(manifest.vanillasky) ? manifest.vanillasky : {};
1849
+ const enabled = Array.isArray(configuration.providers) ? configuration.providers : [];
1850
+ for (const provider of [
1851
+ { id: "speech", label: "generated speech", dependency: "@ai-sdk/xai", key: "XAI_API_KEY" },
1852
+ { id: "video", label: "generated video + transcription", dependency: "@fal-ai/client", key: "FAL_KEY" }
1853
+ ]) {
1854
+ const configured = enabled.includes(provider.id) && existsSync5(join10(cwd, "providers.ts")) && existsSync5(join10(cwd, "providers", `${provider.id}.ts`)) && dependencies[provider.dependency] && installed(provider.dependency);
1855
+ if (!configured) {
1856
+ lines.push(`OPTIONAL ${provider.label} \u2014 run npx vanillasky providers add ${provider.id}`);
1857
+ } else if (!environment.get(provider.key)) {
1858
+ lines.push(`OPTIONAL ${provider.label} \u2014 add ${provider.key} to .env.local`);
1859
+ } else {
1860
+ lines.push(`READY ${provider.label}`);
1861
+ }
1862
+ }
1765
1863
  lines.push(environment.get("PEXELS_API_KEY") ? "READY stock media" : "OPTIONAL stock media \u2014 add PEXELS_API_KEY");
1766
- if (existsSync4(join9(cwd, "vanillasky", "templates"))) {
1864
+ if (existsSync5(join10(cwd, "vanillasky", "templates"))) {
1767
1865
  lines.push("CHECK custom templates with npx vanillasky templates check");
1768
1866
  }
1769
1867
  return { ok, lines };
@@ -1777,6 +1875,7 @@ function help() {
1777
1875
  "Usage:",
1778
1876
  " vanillasky init",
1779
1877
  " vanillasky doctor",
1878
+ " vanillasky providers add <speech|video>",
1780
1879
  " vanillasky templates <command>",
1781
1880
  "",
1782
1881
  "Template commands:",
@@ -1848,14 +1947,22 @@ function runVanillaSkyCli(argv, environment = {}) {
1848
1947
  sdkSpec: environment.sdkSpec ?? process.env.npm_config_package,
1849
1948
  installDependencies: environment.installDependencies
1850
1949
  });
1851
- if (!result.initialized) {
1852
- write("Video chat is already initialized.");
1853
- return 0;
1854
- }
1855
- write("Video chat initialized with packaged templates and browser voice.");
1856
- write("Add ANTHROPIC_API_KEY to .env.local.");
1857
- write("Then run: npm run dev");
1858
- write("Check setup: npx vanillasky doctor");
1950
+ write(result.initialized ? "Video chat initialized with packaged templates and browser voice." : "Video chat is already initialized; dependencies checked.");
1951
+ const health = doctorVideoChatApp(cwd);
1952
+ health.lines.forEach(write);
1953
+ write(health.ok ? "Ready. Run: npm run dev" : health.lines.some((line) => line === "MISSING ANTHROPIC_API_KEY in .env.local") ? "Add ANTHROPIC_API_KEY to .env.local. Then run: npm run dev" : "Next: fix the missing setup items above, then run npm run dev.");
1954
+ return 0;
1955
+ } catch (error) {
1956
+ write(error instanceof Error ? error.message : String(error));
1957
+ return 1;
1958
+ }
1959
+ })();
1960
+ }
1961
+ if (rootCommand === "providers") {
1962
+ return (async () => {
1963
+ try {
1964
+ if (rootArgs.length !== 2 || rootArgs[0] !== "add") throw new Error("Usage: vanillasky providers add <speech|video>");
1965
+ write(await addVideoChatProvider(rootArgs[1], { cwd, starterRoot: environment.starterRoot, installDependencies: environment.installDependencies }));
1859
1966
  return 0;
1860
1967
  } catch (error) {
1861
1968
  write(error instanceof Error ? error.message : String(error));
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createVideo
3
- } from "./chunk-RE4IMWJR.js";
3
+ } from "./chunk-3VRMKFM7.js";
4
4
  import "./chunk-E7CL7UPB.js";
5
- import "./chunk-GAIOHGNR.js";
5
+ import "./chunk-2OKB27TG.js";
6
6
  import "./chunk-MMUXVA47.js";
7
- import "./chunk-2PYO6VAC.js";
7
+ import "./chunk-2DOLF4QN.js";
8
8
  import "./chunk-AOWSXU2K.js";
9
9
  import "./chunk-3O7OMMMF.js";
10
10
  import "./chunk-XGRA2MUV.js";
@@ -1,4 +1,4 @@
1
- import { b as VideoOrientation, V as VideoStyle, c as Video, a as VideoCapabilities, d as VideoAudio, e as VideoScene, f as VIDEO_PROTOCOL_VERSION } from './types-2wHBqtg8.js';
1
+ import { b as VideoOrientation, V as VideoStyle, c as Video, a as VideoCapabilities, d as VideoAudio, e as VideoScene, f as VIDEO_PROTOCOL_VERSION } from './types-CEh9eUFu.js';
2
2
 
3
3
  type VideoWarningCategory = "validation" | "readability" | "grounding" | "provider" | "media" | "protocol";
4
4
  type VideoWarningCode = "scene_duration_adjusted" | "scene_omitted_unreadable" | "scene_omitted_for_closer" | "scene_variable_clipped" | "chart_scale_imbalance" | "plan_incomplete" | "plan_missing_closer" | "provider_warning" | "provider_diagnostics_unavailable" | "media_budget_reached";
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { c as Video, h as VideoBrandInput, i as VideoBrand, e as VideoScene, d as VideoAudio, V as VideoStyle, b as VideoOrientation, j as VideoTemplatePacing } from './types-2wHBqtg8.js';
2
- export { k as VideoBackground, g as VideoInput, l as VideoKnowledgeMode, m as VideoStyleOptions, n as VideoSuppliedMedia } from './types-2wHBqtg8.js';
3
- export { V as VideoStatus } from './state-DZcKuS32.js';
4
- import { b as VideoEvent } from './events-B6qS1Lsb.js';
1
+ import { c as Video, h as VideoBrandInput, i as VideoBrand, e as VideoScene, j as VideoTemplatePacing } from './types-CEh9eUFu.js';
2
+ export { d as VideoAudio, k as VideoBackground, b as VideoOrientation, V as VideoStyle, l as VideoStyleOptions } from './types-CEh9eUFu.js';
3
+
4
+ type VideoStatus = "idle" | "streaming" | "complete" | "error" | "aborted";
5
5
 
6
6
  type VideoValidationErrorCode = "invalid_video" | "unsupported_video_version";
7
7
  declare class VideoValidationError extends Error {
@@ -15,68 +15,6 @@ declare function getVideoDuration(config: Video): number;
15
15
 
16
16
  declare function resolveVideoBrand(input?: VideoBrandInput): VideoBrand;
17
17
 
18
- /**
19
- * Compose a playable video from scenes the host builds itself.
20
- *
21
- * `VideoInput.opening` is a single line of copy, so an application that wants
22
- * several scenes of its own before the generated ones has nowhere to put them.
23
- * Handing the player a fresh `video` prop is not an answer either: that resets
24
- * playback to zero on every change, so a video that grows replays its opening
25
- * each time something is appended. The player's `stream` prop appends, and this
26
- * builds a stream.
27
- *
28
- * The reason it belongs here rather than in each application is that the
29
- * envelope rules are exact and their failure is silent. `response.start` must
30
- * be sequence zero, every event exactly one higher, `eventId` exactly
31
- * `${runId}:${sequence}`, `scene.add.position` the index the scene lands at,
32
- * and the completion snapshot must reproduce what the reducer built. Miss any
33
- * one and the whole stream is rejected: the player renders nothing, and the
34
- * console stays clean.
35
- */
36
- interface SceneTimeline {
37
- /**
38
- * Hand this to the player's `stream` prop once. Never swap it: a new stream
39
- * is a new video, and playback restarts.
40
- */
41
- stream: AsyncIterable<VideoEvent>;
42
- /** Append a scene. Ignored once the timeline has completed. */
43
- add(scene: VideoScene): void;
44
- /**
45
- * Say what the soundtrack is, or that there is none.
46
- *
47
- * Only meaningful with `awaitAudio`, where it releases the scenes held back
48
- * while the track was unknown. Calling it twice, or after the first scene has
49
- * been emitted, does nothing.
50
- */
51
- setAudio(audio: VideoAudio | undefined): void;
52
- /** Finish the video. Safe to call more than once. */
53
- complete(): void;
54
- }
55
- interface SceneTimelineOptions {
56
- style: VideoStyle;
57
- orientation?: VideoOrientation;
58
- /**
59
- * The soundtrack, when it is already known.
60
- *
61
- * Use `awaitAudio` instead when it is resolved by a request that has not
62
- * answered yet.
63
- */
64
- audio?: VideoAudio;
65
- /**
66
- * Hold scenes until `setAudio` is called.
67
- *
68
- * `audio.set` is only valid before the first scene, so a timeline that opens
69
- * with host-built scenes cannot add a soundtrack afterwards - the event
70
- * arrives too late and the video plays silently, with nothing to show for it.
71
- * Holding the openings costs the wait for the track to resolve, not the wait
72
- * for the video to be planned.
73
- */
74
- awaitAudio?: boolean;
75
- requestId?: string;
76
- runId?: string;
77
- }
78
- declare function createSceneTimeline(options: SceneTimelineOptions): SceneTimeline;
79
-
80
18
  interface SceneDurationBounds {
81
19
  /**
82
20
  * The least this scene survives being shown for.
@@ -100,4 +38,4 @@ declare function getSceneDurationBounds(scene: Pick<VideoScene, "variables">, me
100
38
  declare function getSpokenDuration(narration: string): number;
101
39
  declare function getSceneDuration(scene: Pick<VideoScene, "variables" | "narration">, metadata: VideoTemplatePacing | undefined): number;
102
40
 
103
- export { type SceneDurationBounds, type SceneTimeline, type SceneTimelineOptions, Video, VideoAudio, VideoBrand, VideoBrandInput, VideoOrientation, VideoScene, VideoStyle, VideoValidationError, type VideoValidationErrorCode, createSceneTimeline, getSceneDuration, getSceneDurationBounds, getSpokenDuration, getVideoDuration, parseVideo, resolveVideoBrand };
41
+ export { type SceneDurationBounds, Video, VideoBrand, VideoBrandInput, VideoScene, type VideoStatus, VideoValidationError, type VideoValidationErrorCode, getSceneDuration, getSceneDurationBounds, getSpokenDuration, getVideoDuration, parseVideo, resolveVideoBrand };
package/dist/index.js CHANGED
@@ -1,13 +1,12 @@
1
1
  import {
2
- createSceneTimeline,
3
2
  getSceneDuration,
4
3
  getSceneDurationBounds,
5
4
  getSpokenDuration
6
- } from "./chunk-SKRGRKHY.js";
5
+ } from "./chunk-ZD2VTUYR.js";
7
6
  import {
8
7
  VideoValidationError,
9
8
  parseVideo
10
- } from "./chunk-2PYO6VAC.js";
9
+ } from "./chunk-2DOLF4QN.js";
11
10
  import "./chunk-AOWSXU2K.js";
12
11
  import "./chunk-3O7OMMMF.js";
13
12
  import {
@@ -18,7 +17,6 @@ import {
18
17
  } from "./chunk-XGRA2MUV.js";
19
18
  export {
20
19
  VideoValidationError,
21
- createSceneTimeline,
22
20
  getSceneDuration,
23
21
  getSceneDurationBounds,
24
22
  getSpokenDuration,
@@ -1,4 +1,4 @@
1
- import { V as VideoStyle, a as VideoCapabilities } from './types-2wHBqtg8.js';
1
+ import { V as VideoStyle, a as VideoCapabilities } from './types-CEh9eUFu.js';
2
2
  import { ComponentType } from 'react';
3
3
  import { S as SceneTemplateMetadata, d as TemplateJsonSchema, f as TemplateJsonSchemaProperty } from './catalog-types-BIhSpOWK.js';
4
4
 
package/dist/react.d.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  import * as react from 'react';
2
2
  import { CSSProperties, ReactElement, ReactNode } from 'react';
3
- import { b as VideoEvent, V as VideoWarning } from './events-B6qS1Lsb.js';
4
- import { b as VideoOrientation, c as Video, e as VideoScene, g as VideoInput, h as VideoBrandInput, m as VideoStyleOptions } from './types-2wHBqtg8.js';
5
- import { T as TemplateRegistry } from './kit-8g46H2RZ.js';
6
- import { V as VideoStatus } from './state-DZcKuS32.js';
7
- import { c as VideoChatMode, e as VideoChatAskOptions, f as VideoChatMedia, g as VideoChatSuggestion, a as VideoChatCapabilities, h as VideoChatWelcome } from './types-DrABlRa7.js';
3
+ import { b as VideoEvent } from './events-BQ6z264n.js';
4
+ import { b as VideoOrientation, c as Video, e as VideoScene, h as VideoBrandInput, l as VideoStyleOptions } from './types-CEh9eUFu.js';
5
+ import { T as TemplateRegistry } from './kit-tQnCPMOM.js';
6
+ import { V as VideoChatMode, a as VideoChatAskOptions, b as VideoChatMedia, c as VideoChatSuggestion, d as VideoChatCapabilities, e as VideoChatWelcome } from './types-DWFO6qca.js';
8
7
  import './catalog-types-BIhSpOWK.js';
9
8
 
10
9
  type VideoPlaybackMode = "manual" | "muted-autoplay" | "autoplay-with-sound" | "autoplay-after-interaction";
@@ -62,6 +61,10 @@ interface VideoPlayerSharedProps {
62
61
  /** Fires when a streamed response has finished composing. */
63
62
  onComplete?: (video: Video) => void;
64
63
  onError?: (error: Error) => void;
64
+ /** First active scene committed, observed at the next animation frame; excludes idle posters. */
65
+ onFramePresented?: () => unknown;
66
+ /** Stream playback has reached its available scenes; excludes initial waiting and deliberate pauses. */
67
+ onStallChange?: (stalled: boolean) => unknown;
65
68
  /** Fires when the scene under the playhead changes, including on a loop wrap. */
66
69
  onSceneChange?: (scene: VideoScene, index: number) => void;
67
70
  }
@@ -94,33 +97,6 @@ declare class VideoError extends Error {
94
97
  constructor(message: string, options: VideoErrorOptions);
95
98
  }
96
99
 
97
- interface UseVideoOptions {
98
- endpoint?: string | URL;
99
- /** Customer-owned templates that replace matching built-ins and add new IDs. */
100
- templates?: TemplateRegistry;
101
- /** Templates the planner may select for this client experience. Renderers remain available for replay. */
102
- templateIds?: readonly string[];
103
- headers?: HeadersInit;
104
- credentials?: RequestCredentials;
105
- fetcher?: typeof fetch;
106
- /** Override ID generation for tracing or deterministic tests. */
107
- createRequestId?: () => string;
108
- }
109
- interface UseVideoResult {
110
- /** Resolves with a completed video; rejects terminal errors and aborts. */
111
- generate(input: VideoInput): Promise<Video>;
112
- abort(reason?: string): void;
113
- video?: Video;
114
- status: VideoStatus;
115
- error?: VideoError;
116
- warnings: readonly VideoWarning[];
117
- /** Spread into an explicitly rendered VideoPlayer. */
118
- playerProps: VideoPlayerProps & {
119
- video?: never;
120
- };
121
- }
122
- declare function useVideo(options?: UseVideoOptions): UseVideoResult;
123
-
124
100
  /**
125
101
  * Say a video's narration as it plays.
126
102
  *
@@ -143,31 +119,14 @@ interface NarrationVoice {
143
119
  *
144
120
  * The signal aborts when the scene changes, when the viewer interrupts, or
145
121
  * when the player goes away. Stop speaking promptly: talking over the next
146
- * scene is worse than being cut off.
122
+ * scene is worse than being cut off. Report onStart only when speech actually
123
+ * begins playing; leave it uncalled when onset cannot be observed.
147
124
  */
148
125
  speak(text: string, options: {
149
126
  signal: AbortSignal;
127
+ onStart?: (source?: "browser" | "generated") => void;
150
128
  }): void | Promise<void>;
151
129
  }
152
- interface NarrationOptions {
153
- voice: NarrationVoice;
154
- /** Say nothing at all while false. Defaults to true. */
155
- enabled?: boolean;
156
- }
157
- interface Narration {
158
- /**
159
- * Hand this to the player's `onSceneChange`.
160
- *
161
- * The player already reports which scene is showing, which is the only cue a
162
- * narrator needs, so nothing here has to run its own clock.
163
- */
164
- onSceneChange: (scene: VideoScene, index: number) => void;
165
- /** Stop the current line. Nothing is said again until the next scene. */
166
- interrupt: () => void;
167
- /** Whether a line is being spoken right now. */
168
- speaking: boolean;
169
- }
170
- declare function useNarration(options: NarrationOptions): Narration;
171
130
 
172
131
  interface VideoChatPreparedSpeech {
173
132
  /** Measured or conservatively estimated spoken duration, in seconds. */
@@ -188,6 +147,8 @@ interface CreateVideoChatVoiceOptions {
188
147
  credentials?: RequestCredentials;
189
148
  fetcher?: typeof fetch;
190
149
  maxCachedLines?: number;
150
+ /** Called when optional generated speech falls back to browser speech. */
151
+ onFallback?: () => unknown;
191
152
  }
192
153
  /**
193
154
  * Create the SDK's generated-speech client with a browser-voice fallback.
@@ -204,6 +165,8 @@ interface VideoChatTurn {
204
165
  /** True only after the complete response has been received. */
205
166
  completed: boolean;
206
167
  opening?: string;
168
+ /** Concise notices for recovered optional failures. */
169
+ warnings?: readonly string[];
207
170
  /** Optional stock footage held behind the opening hook until playback starts. */
208
171
  openingMedia?: VideoChatMedia;
209
172
  orientation: VideoOrientation;
@@ -231,7 +194,23 @@ interface UseVideoChatOptions {
231
194
  createTurnId?: () => string;
232
195
  /** Observe how long a fresh response takes to display its first actual scene. */
233
196
  onFirstFrame?: (metric: VideoChatFirstFrameMetric) => unknown;
197
+ /** Local observations only; no automatic telemetry or prompt/provider data. */
198
+ onPlaybackMetric?: (metric: VideoChatPlaybackMetric) => unknown;
234
199
  }
200
+ type VideoChatPlaybackMetric = {
201
+ turnId: string;
202
+ mode: VideoChatMode;
203
+ elapsedMs: number;
204
+ } & ({
205
+ type: "first-frame";
206
+ } | {
207
+ type: "first-speech";
208
+ source: "browser" | "generated" | "custom";
209
+ } | {
210
+ type: "stall";
211
+ durationMs: number;
212
+ reason: "scene-generation";
213
+ });
235
214
  interface VideoChatFirstFrameMetric {
236
215
  turnId: string;
237
216
  mode: VideoChatMode;
@@ -252,6 +231,7 @@ interface UseVideoChatResult {
252
231
  welcome?: VideoChatWelcome;
253
232
  availableModes: readonly VideoChatMode[];
254
233
  status: VideoChatStatus;
234
+ warnings: readonly string[];
255
235
  error?: VideoError;
256
236
  suggestions: readonly VideoChatSuggestion[];
257
237
  caption?: string;
@@ -280,4 +260,4 @@ interface VideoChatProps {
280
260
  /** A complete voice-and-video chat interface backed by createVideoChatHandler. */
281
261
  declare function VideoChat({ options, className, welcomeTitle }: VideoChatProps): react.JSX.Element;
282
262
 
283
- export { type CreateVideoChatVoiceOptions, type Narration, type NarrationOptions, type NarrationVoice, type UseVideoChatOptions, type UseVideoChatResult, type UseVideoOptions, type UseVideoResult, VideoChat, VideoChatAskOptions, VideoChatCapabilities, type VideoChatFirstFrameMetric, VideoChatMedia, VideoChatMode, type VideoChatPreparedSpeech, type VideoChatProps, type VideoChatStatus, VideoChatSuggestion, type VideoChatTurn, type VideoChatVoice, VideoChatWelcome, VideoError, type VideoErrorOptions, type VideoPlaybackMode, VideoPlayer, type VideoPlayerProps, createVideoChatVoice, useNarration, useVideo, useVideoChat };
263
+ export { type CreateVideoChatVoiceOptions, type UseVideoChatOptions, type UseVideoChatResult, VideoChat, VideoChatAskOptions, VideoChatCapabilities, type VideoChatFirstFrameMetric, VideoChatMedia, VideoChatMode, type VideoChatPlaybackMetric, type VideoChatPreparedSpeech, type VideoChatProps, type VideoChatStatus, VideoChatSuggestion, type VideoChatTurn, type VideoChatVoice, VideoChatWelcome, VideoError, type VideoErrorOptions, type VideoPlaybackMode, VideoPlayer, type VideoPlayerProps, createVideoChatVoice, useVideoChat };