volito 0.1.0 → 0.3.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 (78) hide show
  1. package/README.md +200 -9
  2. package/dist/browser.d.ts +50 -1
  3. package/dist/browser.js +352 -43
  4. package/dist/chunks/{chunk-CFDFWZAC.js → chunk-CUQY6YI3.js} +1032 -404
  5. package/dist/chunks/chunk-PM3PLI3X.js +71 -0
  6. package/dist/chunks/chunk-QA4SEJCZ.js +601 -0
  7. package/dist/chunks/chunk-ZFG3X3DU.js +67 -0
  8. package/dist/chunks/chunk-ZHMWEZ4C.js +186 -0
  9. package/dist/chunks/init-JX2BYRKT.js +189 -0
  10. package/dist/chunks/review-RBH5EUZN.js +18 -0
  11. package/dist/cli.js +232 -32
  12. package/dist/index.d.ts +314 -2
  13. package/dist/index.js +75 -21
  14. package/dist/mcp.js +129 -22
  15. package/dist/player.d.ts +643 -0
  16. package/dist/player.js +6320 -0
  17. package/dist/registry.json +1 -1
  18. package/examples/player.html +93 -0
  19. package/examples/states.html +73 -0
  20. package/examples/volito-intro/DESIGN.md +23 -0
  21. package/examples/volito-intro/frame.js +187 -0
  22. package/examples/volito-intro/item.json +43 -0
  23. package/package.json +15 -7
  24. package/packs/3d/pack.json +1 -1
  25. package/packs/annotate/emphasis/frame.js +8 -0
  26. package/packs/annotate/pack.json +1 -1
  27. package/packs/annotate/reactions/frame.js +12 -0
  28. package/packs/backdrops/gradient/frame.js +5 -0
  29. package/packs/backdrops/pack.json +1 -1
  30. package/packs/broadcast/pack.json +1 -1
  31. package/packs/data/pack.json +1 -1
  32. package/packs/effects/chromatic/frame.js +17 -0
  33. package/packs/effects/chromatic/item.json +15 -0
  34. package/packs/effects/glass/frame.js +44 -0
  35. package/packs/effects/glass/item.json +16 -0
  36. package/packs/effects/grain/frame.js +20 -0
  37. package/packs/effects/grain/item.json +16 -0
  38. package/packs/effects/light-leak/frame.js +24 -0
  39. package/packs/effects/light-leak/item.json +15 -0
  40. package/packs/effects/neon/frame.js +28 -0
  41. package/packs/effects/neon/item.json +15 -0
  42. package/packs/effects/pack.json +13 -0
  43. package/packs/effects/shared.js +23 -0
  44. package/packs/effects/warp/frame.js +13 -0
  45. package/packs/effects/warp/item.json +15 -0
  46. package/packs/essentials/pack.json +1 -1
  47. package/packs/essentials/wipe/frame.js +13 -0
  48. package/packs/promo/pack.json +1 -1
  49. package/packs/social/pack.json +1 -1
  50. package/packs/transitions/glitch/frame.js +14 -0
  51. package/packs/transitions/pack.json +1 -1
  52. package/packs/transitions/shared.js +12 -0
  53. package/packs/type/pack.json +1 -1
  54. package/packs/type/word-by-word/frame.js +10 -0
  55. package/packs/ui/bell/frame.js +24 -0
  56. package/packs/ui/bell/item.json +21 -0
  57. package/packs/ui/button/frame.js +55 -0
  58. package/packs/ui/button/item.json +26 -0
  59. package/packs/ui/like/frame.js +34 -0
  60. package/packs/ui/like/item.json +21 -0
  61. package/packs/ui/loader/frame.js +34 -0
  62. package/packs/ui/loader/item.json +23 -0
  63. package/packs/ui/pack.json +13 -0
  64. package/packs/ui/shared.js +54 -0
  65. package/packs/ui/toggle/frame.js +30 -0
  66. package/packs/ui/toggle/item.json +22 -0
  67. package/{skill → skills/volito}/SKILL.md +43 -3
  68. package/skills/volito/references/art-direction.md +58 -0
  69. package/skills/volito/references/brand.md +59 -0
  70. package/skills/volito/references/delivery.md +40 -0
  71. package/skills/volito/references/matching-a-reference.md +39 -0
  72. package/skills/volito/references/motion-craft.md +56 -0
  73. package/skills/volito/references/self-review.md +44 -0
  74. package/skills/volito/references/type-and-layout.md +44 -0
  75. package/skills/volito/references/voice-and-timing.md +37 -0
  76. package/skills/volito/references/writing-items.md +44 -0
  77. /package/{skill/template → templates/item}/frame.js +0 -0
  78. /package/{skill/template → templates/item}/item.json +0 -0
@@ -0,0 +1,186 @@
1
+ import {
2
+ ASPECT_NAMES,
3
+ checkItem,
4
+ framePixels,
5
+ openSession,
6
+ renderSheet
7
+ } from "./chunk-CUQY6YI3.js";
8
+
9
+ // src/node/review.ts
10
+ import { mkdirSync, writeFileSync } from "node:fs";
11
+ import { join, resolve } from "node:path";
12
+ var PLATFORM_COVERS = {
13
+ reels: {
14
+ name: "Instagram Reels",
15
+ covers: [
16
+ { area: "header", rect: [0, 0, 1, 0.1] },
17
+ { area: "like, comment and share buttons", rect: [0.86, 0.46, 0.14, 0.24] },
18
+ { area: "username, caption and audio", rect: [0, 0.7, 1, 0.3] }
19
+ ]
20
+ },
21
+ shorts: {
22
+ name: "YouTube Shorts",
23
+ covers: [
24
+ { area: "search and menu", rect: [0, 0, 1, 0.07] },
25
+ { area: "like, comment and share buttons", rect: [0.86, 0.38, 0.14, 0.4] },
26
+ { area: "title, channel and Subscribe", rect: [0, 0.78, 1, 0.22] }
27
+ ]
28
+ },
29
+ tiktok: {
30
+ name: "TikTok",
31
+ covers: [
32
+ { area: "Following and For You", rect: [0, 0, 1, 0.075] },
33
+ { area: "profile, like, comment and share buttons", rect: [0.87, 0.4, 0.13, 0.38] },
34
+ { area: "username, caption and sound", rect: [0, 0.78, 1, 0.22] }
35
+ ]
36
+ }
37
+ };
38
+ var FPS = 20;
39
+ var STILL = 4e-3;
40
+ var ANALYSIS_WIDTH = 270;
41
+ function frameChange(a, b) {
42
+ let changed = 0;
43
+ const n = Math.min(a.length, b.length);
44
+ for (let i = 0; i < n; i += 4) if (Math.abs(a[i] - b[i]) > 24 || Math.abs(a[i + 1] - b[i + 1]) > 24 || Math.abs(a[i + 2] - b[i + 2]) > 24 || Math.abs(a[i + 3] - b[i + 3]) > 24) changed++;
45
+ return changed / (n / 4);
46
+ }
47
+ function readTiming(changes, visible, fps, duration, still = STILL) {
48
+ const n = visible.length;
49
+ const firstVisible = visible.findIndex(Boolean);
50
+ const visibleAt = firstVisible < 0 ? duration : firstVisible / fps;
51
+ const min = Math.max(2, Math.round(0.3 * fps));
52
+ let best = { from: -1, to: -1 };
53
+ for (let i = Math.max(1, firstVisible + 1); i < n; ) {
54
+ if ((changes[i] ?? 1) > still) {
55
+ i++;
56
+ continue;
57
+ }
58
+ let j = i;
59
+ while (j + 1 < n && (changes[j + 1] ?? 1) <= still) j++;
60
+ if (j - i + 1 >= min && j - i > best.to - best.from) best = { from: i - 1, to: j };
61
+ i = j + 1;
62
+ }
63
+ if (best.from < 0) return { visibleAt, settledAt: null, exitAt: null, hold: 0, duration };
64
+ const settledAt = best.from / fps;
65
+ const exitAt = best.to >= n - 1 ? null : best.to / fps;
66
+ return { visibleAt, settledAt, exitAt, hold: (exitAt ?? duration) - settledAt, duration };
67
+ }
68
+ function zoneShares(px, width, height) {
69
+ let total = 0;
70
+ const opaque = new Uint8Array(width * height);
71
+ for (let i = 0, p = 0; i < px.length; i += 4, p++) if (px[i + 3] > 128) {
72
+ opaque[p] = 1;
73
+ total++;
74
+ }
75
+ const out = [];
76
+ if (!total) return out;
77
+ for (const z of Object.values(PLATFORM_COVERS)) {
78
+ for (const c of z.covers) {
79
+ const [fx, fy, fw, fh] = c.rect;
80
+ const x0 = Math.floor(fx * width), x1 = Math.ceil((fx + fw) * width), y0 = Math.floor(fy * height), y1 = Math.ceil((fy + fh) * height);
81
+ let inside = 0;
82
+ for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) inside += opaque[y * width + x];
83
+ out.push({ platform: z.name, area: c.area, share: inside / total });
84
+ }
85
+ }
86
+ return out;
87
+ }
88
+ var aspectName = (a) => typeof a === "string" ? a : `${a.width}x${a.height}`;
89
+ var s2 = (x) => `${x.toFixed(2)} s`;
90
+ async function reviewItem(item, opts = {}) {
91
+ const aspects = opts.aspects?.length ? opts.aspects : opts.aspect ? [opts.aspect] : item.aspects ?? ASPECT_NAMES;
92
+ const dir = opts.outDir ? resolve(opts.outDir) : void 0;
93
+ if (dir) mkdirSync(dir, { recursive: true });
94
+ const out = [];
95
+ for (const aspect of aspects) {
96
+ const name = aspectName(aspect);
97
+ const issues = [];
98
+ const check = await checkItem(item, { ...opts, aspects: [aspect] });
99
+ const cr = check.aspects[0];
100
+ for (const e of cr.errors) issues.push({ level: "error", message: e });
101
+ for (const w of cr.warnings) issues.push({ level: "warning", message: w });
102
+ let timing = { visibleAt: 0, settledAt: null, exitAt: null, hold: 0, duration: 0 };
103
+ let zones = [];
104
+ if (cr.ok) {
105
+ const s = await openSession(item, { ...opts, aspect, width: ANALYSIS_WIDTH });
106
+ const duration = s.built.duration;
107
+ const n = Math.max(2, Math.floor(duration * FPS) + 1);
108
+ const frames = [];
109
+ const visible = [];
110
+ const changes = [0];
111
+ let size = { width: 0, height: 0 };
112
+ for (let i = 0; i < n; i++) {
113
+ const f = framePixels(s, Math.min(duration, i / FPS));
114
+ size = { width: f.width, height: f.height };
115
+ let any = false;
116
+ for (let k = 3; k < f.data.length; k += 16) if (f.data[k] > 8) {
117
+ any = true;
118
+ break;
119
+ }
120
+ visible.push(any);
121
+ if (i > 0) changes.push(frameChange(frames[i - 1], f.data));
122
+ frames.push(f.data);
123
+ }
124
+ timing = readTiming(changes, visible, FPS, duration);
125
+ const words = Object.entries(item.props).filter(([, p]) => p.type === "text" || p.type === "longtext").map(([k]) => String(s.built.props[k] ?? "")).join(" ").trim().split(/\s+/).filter(Boolean).length;
126
+ const interactive = !!item.states;
127
+ if (interactive) issues.push({ level: "note", message: "it has interactive states: on a page each state plays on its own, so its holds wait on the viewer \u2014 check each state with <volito-player>" });
128
+ else if (timing.settledAt === null) issues.push({ level: "note", message: "it never holds still (an idle, a moving ground or a long entrance) \u2014 fine for grounds; check the sheet that text is readable while it moves" });
129
+ else {
130
+ const entrance = timing.settledAt - timing.visibleAt;
131
+ if (entrance > 1.5) issues.push({ level: "warning", message: `its entrance takes ${s2(entrance)} to settle \u2014 viewers wait for it`, fix: "--speed 1.25, a faster mood, or a shorter entrance move" });
132
+ const needed = Math.max(1.5, words * 0.25);
133
+ if (words && timing.hold < needed) issues.push({ level: "warning", message: `it holds still for ${s2(timing.hold)}; ${words} words need about ${s2(needed)} to read`, fix: `a longer --duration, or --beat out to start the exit later` });
134
+ if (timing.visibleAt > 1) issues.push({ level: "note", message: `nothing shows until ${s2(timing.visibleAt)}` });
135
+ }
136
+ if (item.placement !== "full" && item.category !== "transition" && size.width && size.height > size.width * 1.5) {
137
+ const mid = timing.settledAt !== null ? Math.min(duration, timing.settledAt + Math.min(0.5, timing.hold / 2)) : duration / 2;
138
+ const f = framePixels(s, mid);
139
+ zones = zoneShares(f.data, f.width, f.height);
140
+ const worst = /* @__PURE__ */ new Map();
141
+ for (const z of zones) if (z.share > (worst.get(z.platform)?.share ?? 0)) worst.set(z.platform, z);
142
+ for (const [platform, z] of worst) if (z.share >= 0.05) issues.push({ level: "warning", message: `${Math.round(z.share * 100)}% of the graphic sits under ${platform}${platform.endsWith("s") ? "'" : "'s"} ${z.area}`, fix: "a top or centre placement, or keep it for 16:9 and 1:1" });
143
+ }
144
+ }
145
+ let sheet;
146
+ if (dir && cr.ok) {
147
+ const r = await renderSheet(item, { ...opts, aspect, tileWidth: 360 });
148
+ sheet = join(dir, `${item.id}-${name.replace(":", "x")}.png`);
149
+ writeFileSync(sheet, r.png);
150
+ }
151
+ out.push({ aspect: name, ok: !issues.some((i) => i.level === "error"), timing, zones, issues, ...sheet ? { sheet } : {} });
152
+ }
153
+ const report = { ref: item.ref, placement: item.placement, ok: out.every((a) => a.ok), aspects: out, ...dir ? { dir } : {} };
154
+ if (dir) {
155
+ writeFileSync(join(dir, "review.json"), `${JSON.stringify(report, null, 2)}
156
+ `);
157
+ writeFileSync(join(dir, "review.md"), `${describeReview(report, { markdown: true })}
158
+ `);
159
+ }
160
+ return report;
161
+ }
162
+ function describeReview(rep, opts = {}) {
163
+ const md = !!opts.markdown;
164
+ const lines = [md ? `# Review: ${rep.ref}` : `${rep.ref}: ${rep.ok ? "ready" : "problems"}`];
165
+ for (const a of rep.aspects) {
166
+ const t = a.timing;
167
+ const timing = t.settledAt === null ? "keeps moving" : `in by ${s2(t.settledAt)} \xB7 holds ${s2(t.hold)}${t.exitAt !== null ? ` \xB7 leaves at ${s2(t.exitAt)}` : " to the end"}`;
168
+ lines.push(md ? `
169
+ ## ${a.aspect} \u2014 ${a.ok ? "ok" : "problems"}
170
+
171
+ ${timing}` : ` ${a.aspect}: ${a.ok ? "ok" : "FAILED"} \xB7 ${timing}`);
172
+ for (const i of a.issues) lines.push(`${md ? "- " : " "}${i.level}: ${i.message}${i.fix ? ` \u2192 ${i.fix}` : ""}`);
173
+ if (a.sheet) lines.push(md ? `
174
+ ![${a.aspect}](${a.sheet.split("/").pop()})` : ` sheet: ${a.sheet}`);
175
+ }
176
+ return lines.join("\n");
177
+ }
178
+
179
+ export {
180
+ PLATFORM_COVERS,
181
+ frameChange,
182
+ readTiming,
183
+ zoneShares,
184
+ reviewItem,
185
+ describeReview
186
+ };
@@ -0,0 +1,189 @@
1
+ import {
2
+ ffmpegPath,
3
+ packageRoot,
4
+ packageVersion,
5
+ probeFfmpeg
6
+ } from "./chunk-ZFG3X3DU.js";
7
+
8
+ // src/node/init.ts
9
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
10
+ import { delimiter, dirname, join } from "node:path";
11
+ var AGENTS = ["claude", "cursor", "vscode", "codex"];
12
+ var AGENT_NAMES = { claude: "Claude Code", cursor: "Cursor", vscode: "VS Code", codex: "Codex" };
13
+ var MCP_SERVER = { command: "npx", args: ["-y", "volito", "mcp"] };
14
+ var CODEX_COMMAND = "codex mcp add volito -- npx -y volito mcp";
15
+ var MCP_FILES = {
16
+ claude: { path: ".mcp.json", key: "mcpServers", entry: { ...MCP_SERVER } },
17
+ cursor: { path: join(".cursor", "mcp.json"), key: "mcpServers", entry: { ...MCP_SERVER } },
18
+ vscode: { path: join(".vscode", "mcp.json"), key: "servers", entry: { type: "stdio", ...MCP_SERVER } }
19
+ };
20
+ var SKILL_DIR = join(".claude", "skills", "volito");
21
+ function detectAgents(cwd, onPath = binOnPath) {
22
+ const has = (p) => existsSync(join(cwd, p));
23
+ const found = /* @__PURE__ */ new Set();
24
+ if (has(".claude") || has("CLAUDE.md") || has(".mcp.json") || onPath("claude")) found.add("claude");
25
+ if (has(".cursor") || has(".cursorrules")) found.add("cursor");
26
+ if (has(".vscode")) found.add("vscode");
27
+ if (has("AGENTS.md") || has(".codex") || onPath("codex")) found.add("codex");
28
+ return AGENTS.filter((a) => found.has(a));
29
+ }
30
+ function parseAgents(arg) {
31
+ const words = arg.split(/[\s,]+/).map((w) => w.trim().toLowerCase()).filter(Boolean);
32
+ if (words.includes("all")) return [...AGENTS];
33
+ const bad = words.filter((w) => !AGENTS.includes(w));
34
+ if (bad.length || !words.length) throw new Error(`--agent takes all or any of ${AGENTS.join(", ")} (got "${arg}")`);
35
+ return AGENTS.filter((a) => words.includes(a));
36
+ }
37
+ function mergeMcpConfig(text, key, entry, force = false) {
38
+ let doc = {};
39
+ if (text && text.trim()) {
40
+ try {
41
+ const parsed = JSON.parse(text);
42
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
43
+ doc = parsed;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+ const servers = doc[key] ?? {};
49
+ if (typeof servers !== "object" || servers === null || Array.isArray(servers)) return null;
50
+ const map = servers;
51
+ const had = "volito" in map;
52
+ if (had && !force) return { text: text ?? "", changed: false, had };
53
+ const next = { ...doc, [key]: { ...map, volito: entry } };
54
+ return { text: `${JSON.stringify(next, null, 2)}
55
+ `, changed: true, had };
56
+ }
57
+ function planInit(cwd, opts = {}, onPath = binOnPath) {
58
+ const detected = detectAgents(cwd, onPath);
59
+ const agents = opts.agents?.length ? opts.agents : detected.length ? detected : ["claude"];
60
+ const actions = [];
61
+ for (const agent of agents) {
62
+ const file = MCP_FILES[agent];
63
+ if (!file) {
64
+ actions.push({ kind: "note", agent, detail: `${AGENT_NAMES[agent]} keeps MCP servers in its global config. Add Volito with:
65
+ ${CODEX_COMMAND}` });
66
+ continue;
67
+ }
68
+ const path = join(cwd, file.path);
69
+ const merged = mergeMcpConfig(existsSync(path) ? readFileSync(path, "utf8") : null, file.key, file.entry, opts.force);
70
+ if (!merged) actions.push({ kind: "skip", agent, path, detail: `${file.path} is not a JSON object with a "${file.key}" map \u2014 left alone. Add "volito": ${JSON.stringify(file.entry)} under "${file.key}" yourself.` });
71
+ else if (!merged.changed) actions.push({ kind: "skip", agent, path, detail: `${file.path} already has a volito server (--force replaces it)` });
72
+ else actions.push({ kind: "write", agent, path, content: merged.text, detail: `${merged.had ? "replaced" : "added"} the volito MCP server in ${file.path}` });
73
+ }
74
+ if (opts.skill !== false && agents.includes("claude")) {
75
+ const from = skillSource();
76
+ const path = join(cwd, SKILL_DIR);
77
+ if (existsSync(join(path, "SKILL.md")) && !opts.force) actions.push({ kind: "skip", agent: "claude", path, detail: `${SKILL_DIR} already exists (--force replaces it)` });
78
+ else actions.push({ kind: "copy", agent: "claude", from, path, detail: `put the Volito skill in ${SKILL_DIR}` });
79
+ }
80
+ if (agents.some((a) => a !== "claude")) actions.push({ kind: "note", detail: "For the skill in other agents: npx skills add monarcpro/volito" });
81
+ return { agents, detected: detected.length > 0, actions };
82
+ }
83
+ function applyInit(actions) {
84
+ for (const a of actions) {
85
+ if (a.kind === "write") {
86
+ mkdirSync(dirname(a.path), { recursive: true });
87
+ writeFileSync(a.path, a.content);
88
+ } else if (a.kind === "copy") {
89
+ mkdirSync(a.path, { recursive: true });
90
+ cpSync(a.from, a.path, { recursive: true, force: true });
91
+ }
92
+ }
93
+ }
94
+ function skillSource() {
95
+ return join(packageRoot(), "skills", "volito");
96
+ }
97
+ function binOnPath(bin) {
98
+ const exts = process.platform === "win32" ? ["", ".cmd", ".exe", ".bat"] : [""];
99
+ for (const dir of (process.env.PATH ?? "").split(delimiter)) {
100
+ if (!dir) continue;
101
+ for (const ext of exts) if (existsSync(join(dir, bin + ext))) return true;
102
+ }
103
+ return false;
104
+ }
105
+ function describeInit(plan, dryRun) {
106
+ const lines = [];
107
+ lines.push(`${dryRun ? "Would set up" : "Set up"} Volito ${packageVersion()} for ${plan.agents.map((a) => AGENT_NAMES[a]).join(", ")}${plan.detected ? "" : " (no agent found in this folder, so Claude Code; pick with --agent)"}`);
108
+ for (const a of plan.actions) {
109
+ const doing = a.kind === "write" || a.kind === "copy";
110
+ const mark = doing ? dryRun ? "\xB7" : "\u2713" : a.kind === "skip" ? "\u2013" : "\u2192";
111
+ const detail = doing && dryRun ? a.detail.replace(/^(added|replaced|put)\b/, (w) => `would ${{ added: "add", replaced: "replace", put: "put" }[w]}`) : a.detail;
112
+ lines.push(` ${mark} ${detail}`);
113
+ }
114
+ lines.push("");
115
+ lines.push(dryRun ? "Nothing was written (--dry-run)." : 'Restart your agent, then ask it: "Make a lower third for Priya Sharma, product designer, 9:16 for Reels, in our brand."');
116
+ return lines.join("\n");
117
+ }
118
+ function runDoctor(cwd, onPath = binOnPath) {
119
+ const checks = [];
120
+ const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
121
+ const nodeOk = major > 20 || major === 20 && minor >= 10;
122
+ checks.push({ name: "Node", ok: nodeOk, detail: `${process.versions.node}${nodeOk ? "" : " \u2014 Volito needs 20.10 or newer"}`, fix: nodeOk ? void 0 : "install Node.js 20.10 or newer" });
123
+ const probe = probeFfmpeg();
124
+ if (!probe) {
125
+ checks.push({ name: "FFmpeg", ok: false, detail: `not found (${ffmpegPath()}) \u2014 stills, sheets and PNG frames still work`, fix: "macOS: brew install ffmpeg \xB7 Debian/Ubuntu: apt install ffmpeg \xB7 Windows: winget install ffmpeg \xB7 or set VOLITO_FFMPEG" });
126
+ } else {
127
+ const want = [
128
+ ["prores_ks", "MOV ProRes 4444"],
129
+ ["libx264", "MP4"],
130
+ ["libvpx-vp9", "WebM"],
131
+ ["libopus", "WebM sound"],
132
+ ["aac", "MP4 sound"]
133
+ ];
134
+ const missing = want.filter(([enc]) => !probe.encoders.has(enc)).map(([, what]) => what);
135
+ const hevc = probe.encoders.has("hevc_videotoolbox") || probe.encoders.has("libx265");
136
+ checks.push({ name: "FFmpeg", ok: true, detail: probe.path });
137
+ checks.push({ name: "Formats", ok: missing.length === 0, detail: missing.length ? `missing encoders for ${missing.join(", ")}` : "MOV, MP4, WebM and their sound", fix: missing.length ? "install an FFmpeg built with libvpx, libopus and libx264" : void 0 });
138
+ checks.push({ name: "HEVC with alpha", ok: hevc, detail: hevc ? "available" : "needs VideoToolbox (macOS) or libx265", fix: hevc ? void 0 : "optional: render ProRes 4444 or WebM for alpha instead" });
139
+ }
140
+ const fonts = join(packageRoot(), "fonts");
141
+ const faces = existsSync(fonts) ? readdirSync(fonts).filter((f) => f.endsWith(".ttf")).length : 0;
142
+ checks.push({ name: "Fonts", ok: faces > 0, detail: `${faces} bundled faces` });
143
+ const agents = detectAgents(cwd, onPath);
144
+ for (const agent of AGENTS) {
145
+ const file = MCP_FILES[agent];
146
+ if (!file) {
147
+ if (agents.includes(agent)) checks.push({ name: AGENT_NAMES[agent], ok: true, detail: "found; its MCP config is global", fix: CODEX_COMMAND });
148
+ continue;
149
+ }
150
+ const path = join(cwd, file.path);
151
+ if (!existsSync(path)) {
152
+ if (agents.includes(agent)) checks.push({ name: AGENT_NAMES[agent], ok: false, detail: `no ${file.path}`, fix: `volito init --agent ${agent}` });
153
+ continue;
154
+ }
155
+ const merged = mergeMcpConfig(readFileSync(path, "utf8"), file.key, file.entry);
156
+ checks.push({ name: AGENT_NAMES[agent], ok: !!merged?.had, detail: merged?.had ? `volito server in ${file.path}` : `${file.path} has no volito server`, fix: merged?.had ? void 0 : `volito init --agent ${agent}` });
157
+ }
158
+ if (agents.includes("claude")) {
159
+ const has = existsSync(join(cwd, SKILL_DIR, "SKILL.md"));
160
+ checks.push({ name: "Skill", ok: has, detail: has ? SKILL_DIR : `no ${SKILL_DIR}`, fix: has ? void 0 : "volito init" });
161
+ }
162
+ return checks;
163
+ }
164
+ function describeDoctor(checks) {
165
+ const width = Math.max(...checks.map((c) => c.name.length));
166
+ const lines = [`Volito ${packageVersion()}`];
167
+ for (const c of checks) {
168
+ lines.push(` ${c.ok ? "\u2713" : "\u2717"} ${c.name.padEnd(width)} ${c.detail}`);
169
+ if (!c.ok && c.fix) lines.push(` ${" ".repeat(width)} \u2192 ${c.fix}`);
170
+ }
171
+ return lines.join("\n");
172
+ }
173
+ export {
174
+ AGENTS,
175
+ AGENT_NAMES,
176
+ CODEX_COMMAND,
177
+ MCP_SERVER,
178
+ SKILL_DIR,
179
+ applyInit,
180
+ binOnPath,
181
+ describeDoctor,
182
+ describeInit,
183
+ detectAgents,
184
+ mergeMcpConfig,
185
+ parseAgents,
186
+ planInit,
187
+ runDoctor,
188
+ skillSource
189
+ };
@@ -0,0 +1,18 @@
1
+ import {
2
+ PLATFORM_COVERS,
3
+ describeReview,
4
+ frameChange,
5
+ readTiming,
6
+ reviewItem,
7
+ zoneShares
8
+ } from "./chunk-ZHMWEZ4C.js";
9
+ import "./chunk-CUQY6YI3.js";
10
+ import "./chunk-ZFG3X3DU.js";
11
+ export {
12
+ PLATFORM_COVERS,
13
+ describeReview,
14
+ frameChange,
15
+ readTiming,
16
+ reviewItem,
17
+ zoneShares
18
+ };