storyforge 0.6.0 → 0.7.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.
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  log
4
4
  } from "./chunk-GJQ45C5W.js";
5
+ import "./chunk-NSPRIPOP.js";
5
6
 
6
7
  // src/commands/dev.ts
7
8
  import * as fs2 from "fs";
@@ -840,7 +841,7 @@ async function devCommand(options) {
840
841
  }
841
842
  void (async () => {
842
843
  try {
843
- const { gatherCliUsage, CLI_USAGE_PARSER_VERSION } = await import("./cli-usage-FG3YUG3W.js");
844
+ const { gatherCliUsage, CLI_USAGE_PARSER_VERSION } = await import("./cli-usage-G762TREV.js");
844
845
  const report = await gatherCliUsage();
845
846
  const g = global;
846
847
  g.__forgeCliUsageCache = { at: Date.now(), ver: CLI_USAGE_PARSER_VERSION, data: report };
@@ -876,7 +877,7 @@ async function devCommand(options) {
876
877
  const pathname = url.pathname;
877
878
  if (pathname === "/api/cli-usage") {
878
879
  try {
879
- const { gatherCliUsage, CLI_USAGE_PARSER_VERSION } = await import("./cli-usage-FG3YUG3W.js");
880
+ const { gatherCliUsage, CLI_USAGE_PARSER_VERSION } = await import("./cli-usage-G762TREV.js");
880
881
  const g = global;
881
882
  const now = Date.now();
882
883
  if (g.__forgeCliUsageCache && g.__forgeCliUsageCache.ver === CLI_USAGE_PARSER_VERSION && now - g.__forgeCliUsageCache.at < 3e4) {
@@ -1614,7 +1615,7 @@ Return ONLY the complete updated TSX. No markdown fences, no explanation.`;
1614
1615
  return "0.0.0";
1615
1616
  })();
1616
1617
  void (async () => {
1617
- const { BridgePoller } = await import("./bridge-poller-MM6K2GKM.js");
1618
+ const { BridgePoller } = await import("./bridge-poller-JIP6RGMN.js");
1618
1619
  const poller = new BridgePoller({ baseUrl: bridgeUrl, token: bridgeToken, clientVersion: `storyforge ${pkgVersion}` });
1619
1620
  poller.start();
1620
1621
  })();
@@ -1678,15 +1679,329 @@ async function loginCommand() {
1678
1679
  }
1679
1680
  }
1680
1681
 
1682
+ // src/commands/doctor.ts
1683
+ import { spawn as spawn2 } from "child_process";
1684
+ import { promisify as promisify2 } from "util";
1685
+ import { execFile as execFileCb } from "child_process";
1686
+ import os3 from "os";
1687
+ var execFile2 = promisify2(execFileCb);
1688
+ async function runDoctor() {
1689
+ const platform = os3.platform();
1690
+ const arch = os3.arch();
1691
+ const cores = os3.cpus().length;
1692
+ const ramGb = Math.round(os3.totalmem() / 1024 ** 3);
1693
+ const renderers = await Promise.all([
1694
+ checkFfmpeg(platform),
1695
+ checkPython(),
1696
+ checkManim(),
1697
+ checkHyperframes(),
1698
+ checkChromeHeadlessShell(platform),
1699
+ checkNode(),
1700
+ checkNpm()
1701
+ ]);
1702
+ const ready = renderers.filter((r) => r.required).every((r) => r.installed);
1703
+ return { platform, arch, cores, ramGb, renderers, ready };
1704
+ }
1705
+ function formatDoctorReport(r) {
1706
+ const lines = [];
1707
+ lines.push(`storyforge doctor`);
1708
+ lines.push(` platform: ${r.platform} \xB7 ${r.arch} \xB7 ${r.cores} cores \xB7 ${r.ramGb} GB RAM`);
1709
+ lines.push("");
1710
+ for (const dep of r.renderers) {
1711
+ const tick = dep.installed ? "\x1B[32m\u2713\x1B[0m" : dep.required ? "\x1B[31m\u2717\x1B[0m" : "\x1B[33m\u25CB\x1B[0m";
1712
+ const ver = dep.version ? ` (${dep.version})` : "";
1713
+ const need = dep.required ? "" : " [optional]";
1714
+ lines.push(` ${tick} ${dep.name.padEnd(28)}${ver}${need}`);
1715
+ if (!dep.installed && dep.installCommand) {
1716
+ lines.push(` install: ${dep.installCommand}`);
1717
+ }
1718
+ }
1719
+ lines.push("");
1720
+ if (r.ready) {
1721
+ lines.push(`\x1B[32m\u2713 ready to render.\x1B[0m next: \`storyforge\` to start the bridge.`);
1722
+ } else {
1723
+ lines.push(`\x1B[33m! missing required renderers.\x1B[0m run: \`storyforge install-renderers\` to install them.`);
1724
+ }
1725
+ return lines.join("\n");
1726
+ }
1727
+ async function probeVersion(cmd, args = ["--version"], parser = firstLine) {
1728
+ try {
1729
+ const { stdout, stderr } = await execFile2(cmd, args, { timeout: 5e3 });
1730
+ const out = (stdout || stderr || "").trim();
1731
+ return { installed: true, version: parser(out) };
1732
+ } catch {
1733
+ return { installed: false, version: null };
1734
+ }
1735
+ }
1736
+ function firstLine(s) {
1737
+ return s.split("\n")[0]?.trim().slice(0, 80) ?? null;
1738
+ }
1739
+ async function checkFfmpeg(platform) {
1740
+ const probe = await probeVersion("ffmpeg", ["-version"], (s) => {
1741
+ const m = s.match(/ffmpeg version (\S+)/);
1742
+ return m?.[1] ?? null;
1743
+ });
1744
+ return {
1745
+ name: "ffmpeg",
1746
+ installed: probe.installed,
1747
+ version: probe.version,
1748
+ rationale: "concat + audio mux + watermark \u2014 required by every engine",
1749
+ installCommand: platform === "darwin" ? "brew install ffmpeg" : platform === "linux" ? "sudo apt install -y ffmpeg" : "https://ffmpeg.org/download.html",
1750
+ required: true
1751
+ };
1752
+ }
1753
+ async function checkPython() {
1754
+ const probe = await probeVersion("python3", ["--version"], (s) => {
1755
+ const m = s.match(/Python (\S+)/);
1756
+ return m?.[1] ?? null;
1757
+ });
1758
+ return {
1759
+ name: "python3",
1760
+ installed: probe.installed,
1761
+ version: probe.version,
1762
+ rationale: "manim engine runtime",
1763
+ installCommand: os3.platform() === "darwin" ? "brew install python@3.12" : "sudo apt install -y python3",
1764
+ required: false
1765
+ };
1766
+ }
1767
+ async function checkManim() {
1768
+ const probe = await new Promise((resolve3) => {
1769
+ const p = spawn2("python3", ["-m", "manim", "--version"], { stdio: ["ignore", "pipe", "pipe"] });
1770
+ let stdout = "";
1771
+ let stderr = "";
1772
+ p.stdout.on("data", (d) => {
1773
+ stdout += d.toString("utf8");
1774
+ });
1775
+ p.stderr.on("data", (d) => {
1776
+ stderr += d.toString("utf8");
1777
+ });
1778
+ p.on("error", () => resolve3({ installed: false, version: null }));
1779
+ p.on("close", (code) => {
1780
+ if (code === 0) {
1781
+ const m = (stdout + stderr).match(/Manim Community v(\S+)/);
1782
+ resolve3({ installed: true, version: m?.[1] ?? null });
1783
+ } else {
1784
+ resolve3({ installed: false, version: null });
1785
+ }
1786
+ });
1787
+ setTimeout(() => {
1788
+ p.kill();
1789
+ resolve3({ installed: false, version: null });
1790
+ }, 5e3);
1791
+ });
1792
+ return {
1793
+ name: "manim (python pkg)",
1794
+ installed: probe.installed,
1795
+ version: probe.version,
1796
+ rationale: "3Blue1Brown-style mathematical animations",
1797
+ installCommand: "python3 -m pip install --user manim",
1798
+ required: false
1799
+ };
1800
+ }
1801
+ async function checkHyperframes() {
1802
+ const probe = await probeVersion("hyperframes", ["--version"]);
1803
+ return {
1804
+ name: "hyperframes",
1805
+ installed: probe.installed,
1806
+ version: probe.version,
1807
+ rationale: "GSAP / Three.js / Lottie cascades (Stage 2 engine)",
1808
+ installCommand: "npm install -g hyperframes",
1809
+ required: false
1810
+ };
1811
+ }
1812
+ async function checkChromeHeadlessShell(platform) {
1813
+ if (platform !== "linux") {
1814
+ return {
1815
+ name: "chrome-headless-shell",
1816
+ installed: false,
1817
+ version: null,
1818
+ rationale: "hyperframes deterministic mode (Linux only \u2014 macOS uses screenshot fallback)",
1819
+ installCommand: null,
1820
+ required: false
1821
+ };
1822
+ }
1823
+ const probe = await probeVersion("chrome-headless-shell", ["--version"]);
1824
+ return {
1825
+ name: "chrome-headless-shell",
1826
+ installed: probe.installed,
1827
+ version: probe.version,
1828
+ rationale: "hyperframes deterministic BeginFrame mode",
1829
+ installCommand: "npx puppeteer browsers install chrome-headless-shell",
1830
+ required: false
1831
+ };
1832
+ }
1833
+ async function checkNode() {
1834
+ const probe = await probeVersion("node", ["--version"]);
1835
+ return {
1836
+ name: "node",
1837
+ installed: probe.installed,
1838
+ version: probe.version,
1839
+ rationale: "Remotion + JS pipeline runtime",
1840
+ installCommand: "https://nodejs.org/",
1841
+ required: true
1842
+ };
1843
+ }
1844
+ async function checkNpm() {
1845
+ const probe = await probeVersion("npm", ["--version"]);
1846
+ return {
1847
+ name: "npm",
1848
+ installed: probe.installed,
1849
+ version: probe.version,
1850
+ rationale: "Remotion + hyperframes installer",
1851
+ installCommand: "comes with node",
1852
+ required: true
1853
+ };
1854
+ }
1855
+ async function doctorCommand() {
1856
+ const report = await runDoctor();
1857
+ console.log(formatDoctorReport(report));
1858
+ process.exit(report.ready ? 0 : 1);
1859
+ }
1860
+
1861
+ // src/commands/install-renderers.ts
1862
+ import { spawn as spawn3 } from "child_process";
1863
+ import readline2 from "readline/promises";
1864
+ import { stdin as input, stdout as output } from "process";
1865
+ import os4 from "os";
1866
+ function planInstall(missing, platform) {
1867
+ const steps = [];
1868
+ for (const r of missing) {
1869
+ if (r.installed) continue;
1870
+ switch (r.name) {
1871
+ case "ffmpeg": {
1872
+ if (platform === "darwin") {
1873
+ steps.push({ name: "ffmpeg", argv: ["brew", "install", "ffmpeg"], display: "brew install ffmpeg" });
1874
+ } else if (platform === "linux") {
1875
+ steps.push({ name: "ffmpeg", argv: ["sudo", "apt", "install", "-y", "ffmpeg"], display: "sudo apt install -y ffmpeg" });
1876
+ } else {
1877
+ steps.push({ name: "ffmpeg", argv: null, display: r.installCommand ?? "see ffmpeg.org", manual: true });
1878
+ }
1879
+ break;
1880
+ }
1881
+ case "python3": {
1882
+ if (platform === "darwin") {
1883
+ steps.push({ name: "python3", argv: ["brew", "install", "python@3.12"], display: "brew install python@3.12" });
1884
+ } else if (platform === "linux") {
1885
+ steps.push({ name: "python3", argv: ["sudo", "apt", "install", "-y", "python3", "python3-pip"], display: "sudo apt install -y python3 python3-pip" });
1886
+ } else {
1887
+ steps.push({ name: "python3", argv: null, display: "install Python 3 from python.org", manual: true });
1888
+ }
1889
+ break;
1890
+ }
1891
+ case "manim (python pkg)": {
1892
+ steps.push({ name: "manim (python pkg)", argv: ["python3", "-m", "pip", "install", "--user", "manim"], display: "python3 -m pip install --user manim" });
1893
+ break;
1894
+ }
1895
+ case "hyperframes": {
1896
+ steps.push({ name: "hyperframes", argv: ["npm", "install", "-g", "hyperframes"], display: "npm install -g hyperframes" });
1897
+ break;
1898
+ }
1899
+ case "chrome-headless-shell": {
1900
+ if (platform === "linux") {
1901
+ steps.push({ name: "chrome-headless-shell", argv: ["npx", "puppeteer", "browsers", "install", "chrome-headless-shell"], display: "npx puppeteer browsers install chrome-headless-shell" });
1902
+ }
1903
+ break;
1904
+ }
1905
+ case "node":
1906
+ case "npm":
1907
+ steps.push({ name: r.name, argv: null, display: r.installCommand ?? "install Node.js from nodejs.org", manual: true });
1908
+ break;
1909
+ }
1910
+ }
1911
+ return steps;
1912
+ }
1913
+ async function ask(rl, question) {
1914
+ const ans = (await rl.question(question)).trim().toLowerCase();
1915
+ return ans === "" || ans === "y" || ans === "yes";
1916
+ }
1917
+ function runStep(argv) {
1918
+ return new Promise((resolve3) => {
1919
+ const [cmd, ...args] = argv;
1920
+ const proc = spawn3(cmd, args, { stdio: ["inherit", "inherit", "inherit"] });
1921
+ proc.on("error", (err) => resolve3({ ok: false, tail: err.message }));
1922
+ proc.on("close", (code) => resolve3({ ok: code === 0 }));
1923
+ });
1924
+ }
1925
+ async function installRenderersCommand(opts = {}) {
1926
+ console.log("\x1B[1mstoryforge install-renderers\x1B[0m\n");
1927
+ const report = await runDoctor();
1928
+ const missing = report.renderers.filter((r) => !r.installed && r.installCommand);
1929
+ if (missing.length === 0) {
1930
+ console.log("\x1B[32m\u2713 all renderers already installed.\x1B[0m");
1931
+ process.exit(0);
1932
+ }
1933
+ const steps = planInstall(missing, report.platform);
1934
+ if (steps.length === 0) {
1935
+ console.log("\x1B[33m! nothing to install on this platform \u2014 run `storyforge doctor` for the manual install hints.\x1B[0m");
1936
+ process.exit(1);
1937
+ }
1938
+ console.log(`Detected ${steps.length} missing renderer${steps.length === 1 ? "" : "s"}:`);
1939
+ for (const s of steps) {
1940
+ console.log(` \u2022 ${s.name.padEnd(28)} \u2192 ${s.display}${s.manual ? " [manual]" : ""}`);
1941
+ }
1942
+ console.log("");
1943
+ const rl = readline2.createInterface({ input, output });
1944
+ let installed = 0;
1945
+ let skipped = 0;
1946
+ let failed = [];
1947
+ try {
1948
+ for (const step of steps) {
1949
+ if (step.manual || !step.argv) {
1950
+ console.log(`\x1B[33m\u25B8 ${step.name}\x1B[0m \u2014 manual install: ${step.display}`);
1951
+ skipped++;
1952
+ continue;
1953
+ }
1954
+ const proceed = opts.yes ? true : await ask(rl, `Install \x1B[1m${step.name}\x1B[0m via \x1B[36m${step.display}\x1B[0m? [Y/n] `);
1955
+ if (!proceed) {
1956
+ console.log(` skipped ${step.name}.`);
1957
+ skipped++;
1958
+ continue;
1959
+ }
1960
+ console.log(`
1961
+ \x1B[36m\u25B8 ${step.display}\x1B[0m`);
1962
+ const result = await runStep(step.argv);
1963
+ if (result.ok) {
1964
+ console.log(`\x1B[32m \u2713 ${step.name} installed.\x1B[0m
1965
+ `);
1966
+ installed++;
1967
+ } else {
1968
+ console.log(`\x1B[31m \u2717 ${step.name} install failed.\x1B[0m
1969
+ `);
1970
+ failed.push(step.name);
1971
+ }
1972
+ }
1973
+ } finally {
1974
+ rl.close();
1975
+ }
1976
+ console.log("");
1977
+ console.log(`Installed: ${installed} \xB7 Skipped: ${skipped} \xB7 Failed: ${failed.length}`);
1978
+ if (failed.length > 0) {
1979
+ console.log(`\x1B[31m failed: ${failed.join(", ")}\x1B[0m`);
1980
+ }
1981
+ console.log("\nRe-probing\u2026\n");
1982
+ const after = await runDoctor();
1983
+ const stillMissing = after.renderers.filter((r) => !r.installed && r.required);
1984
+ if (stillMissing.length === 0) {
1985
+ console.log("\x1B[32m\u2713 ready to render.\x1B[0m next: `storyforge` to start the bridge.");
1986
+ process.exit(0);
1987
+ } else {
1988
+ console.log(`\x1B[33m! still missing required: ${stillMissing.map((r) => r.name).join(", ")}\x1B[0m`);
1989
+ console.log(" run `storyforge doctor` to see install hints.");
1990
+ process.exit(1);
1991
+ }
1992
+ }
1993
+
1681
1994
  // src/index.ts
1682
- var VERSION = "0.1.0";
1995
+ var VERSION = "0.7.0";
1683
1996
  var HELP = `
1684
1997
  storyforge \u2014 local bridge for the Forge video production web app
1685
1998
 
1686
1999
  Usage:
1687
- storyforge Auto-link current folder, start server, open browser (default)
1688
- storyforge [options]
1689
- storyforge login Log in
2000
+ storyforge Auto-link current folder, start bridge, open browser (default)
2001
+ storyforge login Log in to forge.algo-thinker.com
2002
+ storyforge doctor Probe local renderers (ffmpeg / python / manim / hyperframes)
2003
+ storyforge install-renderers Install missing renderers (interactive, asks per-tool)
2004
+ storyforge install-renderers -y Same, but auto-confirm every prompt
1690
2005
  storyforge --help Show this help
1691
2006
  storyforge --version Show version
1692
2007
 
@@ -1695,6 +2010,14 @@ Options:
1695
2010
  --dir <dir> Project directory (default: current)
1696
2011
  --no-open Do not open browser automatically
1697
2012
 
2013
+ First-time setup:
2014
+ npm install -g storyforge
2015
+ storyforge login
2016
+ storyforge doctor # see what's missing
2017
+ storyforge install-renderers # ffmpeg + python + manim + hyperframes
2018
+ cd /path/to/your/project
2019
+ storyforge # links project, starts bridge, opens browser
2020
+
1698
2021
  In any folder with assets (audio/, images-horizontal/, images-vertical/, scripts/),
1699
2022
  just run: storyforge
1700
2023
  `.trim();
@@ -1733,6 +2056,15 @@ async function main() {
1733
2056
  await loginCommand();
1734
2057
  return;
1735
2058
  }
2059
+ if (firstArg === "doctor") {
2060
+ await doctorCommand();
2061
+ return;
2062
+ }
2063
+ if (firstArg === "install-renderers" || firstArg === "install") {
2064
+ const yes = args.includes("-y") || args.includes("--yes");
2065
+ await installRenderersCommand({ yes });
2066
+ return;
2067
+ }
1736
2068
  const opts = parseArgs(args);
1737
2069
  await devCommand({
1738
2070
  port: typeof opts.port === "string" ? opts.port : void 0,