@mevdragon/vidfarm-devcli 0.17.0 → 0.18.1

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 (55) hide show
  1. package/.agents/skills/vidfarm-media/SKILL.md +43 -6
  2. package/SKILL.director.md +37 -23
  3. package/SKILL.platform.md +6 -6
  4. package/demo/dist/app.js +69 -69
  5. package/demo/dist/favicon.ico +0 -0
  6. package/dist/src/app.js +1832 -213
  7. package/dist/src/cli.js +238 -17
  8. package/dist/src/devcli/clip-store.js +29 -2
  9. package/dist/src/devcli/clips.js +116 -73
  10. package/dist/src/devcli/composition-edit.js +262 -0
  11. package/dist/src/devcli/timeline-edit.js +283 -0
  12. package/dist/src/editor-chat.js +29 -6
  13. package/dist/src/frontend/discover-client.js +130 -0
  14. package/dist/src/frontend/discover-store.js +23 -0
  15. package/dist/src/frontend/file-directory.js +744 -0
  16. package/dist/src/frontend/template-editor-chat.js +22 -19
  17. package/dist/src/landing-page.js +24 -7
  18. package/dist/src/page-shell.js +25 -1
  19. package/dist/src/reskin/agency-page.js +214 -170
  20. package/dist/src/reskin/calendar-page.js +503 -187
  21. package/dist/src/reskin/chat-page.js +739 -299
  22. package/dist/src/reskin/discover-page.js +1450 -279
  23. package/dist/src/reskin/document.js +1392 -16
  24. package/dist/src/reskin/help-page.js +139 -100
  25. package/dist/src/reskin/index-page.js +1 -1
  26. package/dist/src/reskin/inpaint-page.js +547 -0
  27. package/dist/src/reskin/job-runs-page.js +355 -127
  28. package/dist/src/reskin/library-page.js +1188 -317
  29. package/dist/src/reskin/login-page.js +124 -87
  30. package/dist/src/reskin/pricing-page.js +197 -166
  31. package/dist/src/reskin/settings-page.js +566 -187
  32. package/dist/src/reskin/theme.js +434 -17
  33. package/dist/src/services/clip-curation/gemini.js +5 -0
  34. package/dist/src/services/clip-curation/hunt.js +79 -1
  35. package/dist/src/services/clip-curation/index.js +2 -1
  36. package/dist/src/services/clip-curation/local-agent.js +4 -3
  37. package/dist/src/services/clip-curation/media-select.js +85 -0
  38. package/dist/src/services/clip-curation/query.js +5 -1
  39. package/dist/src/services/clip-curation/refine.js +50 -20
  40. package/dist/src/services/clip-curation/scan.js +10 -3
  41. package/dist/src/services/clip-curation/taxonomy.js +3 -1
  42. package/dist/src/services/clip-curation/taxonomy.v1.json +13 -1
  43. package/dist/src/services/clip-records.js +14 -1
  44. package/dist/src/services/clip-search.js +43 -13
  45. package/dist/src/services/file-directory.js +114 -0
  46. package/dist/src/services/storage.js +24 -1
  47. package/dist/src/services/upstream.js +5 -5
  48. package/dist/src/template-editor-shell.js +16 -2
  49. package/package.json +1 -1
  50. package/public/assets/discover-client-app.js +1 -0
  51. package/public/assets/file-directory-app.js +2 -0
  52. package/public/assets/homepage-client-app.js +12 -12
  53. package/public/assets/page-runtime-client-app.js +24 -24
  54. package/src/assets/favicon.ico +0 -0
  55. package/src/assets/logo-vidfarm.png +0 -0
@@ -2,6 +2,24 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { memo, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import { debugError, debugLog, debugWarn } from "./debug.js";
5
+ // Parse the fork id out of the canonical editor path
6
+ // /editor/<template_id>/fork/<fork_id> → "<fork_id>" (else null). The editor
7
+ // URL scheme moved from ?fork= to a path segment; this reads the new form.
8
+ function forkIdFromLocation() {
9
+ if (typeof window === "undefined")
10
+ return null;
11
+ try {
12
+ const match = new URL(window.location.href).pathname.match(/^\/editor\/[^/]+\/fork\/([^/]+)\/?$/);
13
+ if (!match)
14
+ return null;
15
+ const forkId = decodeURIComponent(match[1]);
16
+ // /editor/original/fork/new (blank new project) has no real fork yet.
17
+ return forkId === "new" ? null : forkId;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
5
23
  const MB = 1024 * 1024;
6
24
  const MY_FILES_UPLOAD_LIMIT_BYTES = 50 * MB;
7
25
  const CHAT_INLINE_IMAGE_TARGET_BYTES = 2 * MB;
@@ -2417,25 +2435,10 @@ export function TemplateEditorChat({ boot }) {
2417
2435
  useEffect(() => {
2418
2436
  threadsRef.current = threads;
2419
2437
  }, [threads]);
2420
- const [currentForkId, setCurrentForkId] = useState(() => {
2421
- if (typeof window === "undefined") {
2422
- return null;
2423
- }
2424
- try {
2425
- return new URL(window.location.href).searchParams.get("fork");
2426
- }
2427
- catch {
2428
- return null;
2429
- }
2430
- });
2438
+ const [currentForkId, setCurrentForkId] = useState(() => forkIdFromLocation());
2431
2439
  useEffect(() => {
2432
2440
  const syncForkFromLocation = () => {
2433
- try {
2434
- setCurrentForkId(new URL(window.location.href).searchParams.get("fork"));
2435
- }
2436
- catch {
2437
- setCurrentForkId(null);
2438
- }
2441
+ setCurrentForkId(forkIdFromLocation());
2439
2442
  };
2440
2443
  window.addEventListener("popstate", syncForkFromLocation);
2441
2444
  return () => {
@@ -3704,7 +3707,7 @@ export function TemplateEditorChat({ boot }) {
3704
3707
  if (!forkRes.ok)
3705
3708
  throw new Error(fork?.error || `couldn't create an editable copy (${forkRes.status})`);
3706
3709
  const forkId = fork?.fork_id;
3707
- const editorUrl = `${origin}/editor/${encodeURIComponent(templateId)}${forkId ? `?fork=${encodeURIComponent(forkId)}` : ""}`;
3710
+ const editorUrl = `${origin}/editor/${encodeURIComponent(templateId)}${forkId ? `/fork/${encodeURIComponent(forkId)}` : ""}`;
3708
3711
  patch("Your video is ready.", `✅ Your video is ready — [Open it in the editor](${editorUrl}).`);
3709
3712
  }
3710
3713
  catch (error) {
@@ -4142,7 +4145,7 @@ export function TemplateEditorChat({ boot }) {
4142
4145
  left: openThreadMenu.left
4143
4146
  }, onClick: (event) => event.stopPropagation(), children: [_jsx("button", { type: "button", onClick: () => copyThreadId(openThreadMenuThread.id), children: "Copy Chat ID" }), _jsx("button", { type: "button", onClick: () => editThreadTracers(openThreadMenuThread.id), children: "Edit tracers" }), _jsx("button", { type: "button", onClick: () => removeThreadFromView(openThreadMenuThread.id), children: "Remove from view" }), _jsx("button", { type: "button", onClick: () => archiveThread(openThreadMenuThread.id), children: "Archive thread" }), _jsx("button", { type: "button", "data-danger": "true", onClick: () => deleteThread(openThreadMenuThread.id), children: "Delete" })] }), document.body) : null] }), _jsx("div", { className: "vf-editor-chat-viewport", ref: viewportRef, children: showHistoryPanel ? (_jsx("div", { className: "vf-editor-chat-history-panel", children: threadListContent })) : showBranchesPanel ? (_jsxs("div", { className: "vf-editor-chat-history-panel vf-editor-chat-branches-panel", children: [_jsxs("div", { className: "vf-editor-chat-branches-header", children: [_jsxs("div", { className: "vf-editor-chat-branches-header-main", children: [_jsx("div", { className: "vf-editor-chat-branches-kicker", children: "Forks" }), _jsxs("div", { className: "vf-editor-chat-branches-title", children: [branches?.length ?? 0, " fork", (branches?.length ?? 0) === 1 ? "" : "s", " of this template"] })] }), _jsx("button", { type: "button", className: "vf-editor-chat-branches-refresh", onClick: () => { void loadBranches({ force: true }); }, disabled: branchesLoading, "aria-label": "Refresh forks", children: branchesLoading ? "Loading…" : "Refresh" })] }), branchesError ? (_jsx("div", { className: "vf-editor-chat-thread-empty", role: "status", children: branchesError })) : branchesLoading && !branches ? (_jsx("div", { className: "vf-editor-chat-thread-empty", children: "Loading forks\u2026" })) : branches && branches.length ? (_jsx("div", { className: "vf-editor-chat-thread-list", children: branches.map((fork) => {
4144
4147
  const isCurrent = currentForkId === fork.forkId;
4145
- const forkUrl = `/editor/${encodeURIComponent(boot.template.templateId)}?fork=${encodeURIComponent(fork.forkId)}`;
4148
+ const forkUrl = `/editor/${encodeURIComponent(boot.template.templateId)}/fork/${encodeURIComponent(fork.forkId)}`;
4146
4149
  const label = fork.title && fork.title.trim() ? fork.title : fork.forkId;
4147
4150
  const parentSuffix = fork.parentForkId ? ` · forked from ${fork.parentForkId.slice(0, 12)}${fork.parentVersion ? `#v${fork.parentVersion}` : ""}` : "";
4148
4151
  const versionSuffix = fork.latestVersion && fork.latestVersion > 0 ? ` · v${fork.latestVersion}` : "";
@@ -33,8 +33,24 @@ export function renderLandingPage(input) {
33
33
  <head>
34
34
  <meta charset="utf-8">
35
35
  <meta name="viewport" content="width=device-width, initial-scale=1">
36
- <title>Vidfarm — short-form video that makes itself</title>
37
- <meta name="description" content="Fork a proven short-form template, remix it on the timeline, and render a share-ready MP4 — all in your browser. Vidfarm is the serverless video studio that scales to zero.">
36
+ <title>VidFarm — short-form video that makes itself</title>
37
+ <meta name="description" content="Fork a proven short-form template, remix it on the timeline, and render a share-ready MP4 — all in your browser. VidFarm is the serverless video studio that scales to zero.">
38
+ <meta name="theme-color" content="#171717">
39
+ <meta name="robots" content="index, follow">
40
+ <meta name="application-name" content="VidFarm">
41
+ <link rel="canonical" href="https://vidfarm.cc/">
42
+ <meta property="og:type" content="website">
43
+ <meta property="og:site_name" content="VidFarm">
44
+ <meta property="og:title" content="VidFarm — short-form video that makes itself">
45
+ <meta property="og:description" content="Fork a proven short-form template, remix it on the timeline, and render a share-ready MP4 — all in your browser. VidFarm is the serverless video studio that scales to zero.">
46
+ <meta property="og:url" content="https://vidfarm.cc/">
47
+ <meta property="og:image" content="https://vidfarm.cc/assets/logo-vidfarm.png">
48
+ <meta name="twitter:card" content="summary_large_image">
49
+ <meta name="twitter:title" content="VidFarm — short-form video that makes itself">
50
+ <meta name="twitter:description" content="Fork a proven short-form template, remix it on the timeline, and render a share-ready MP4 — all in your browser. VidFarm is the serverless video studio that scales to zero.">
51
+ <meta name="twitter:image" content="https://vidfarm.cc/assets/logo-vidfarm.png">
52
+ <link rel="icon" href="/assets/favicon.ico" sizes="any">
53
+ <link rel="apple-touch-icon" href="/assets/logo-vidfarm.png">
38
54
  <link rel="preconnect" href="https://fonts.googleapis.com">
39
55
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
40
56
  <link href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700;800&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
@@ -80,7 +96,7 @@ h1,h2,h3,h4{font-family:var(--font-display);font-weight:800;letter-spacing:-.025
80
96
  .glass{background:rgba(255,255,255,.72);backdrop-filter:blur(var(--glass-blur));-webkit-backdrop-filter:blur(var(--glass-blur));border:1px solid var(--border);box-shadow:var(--shadow-nav)}
81
97
  .nav{width:min(var(--container),100%);display:flex;align-items:center;gap:24px;padding:10px 12px 10px 22px;border-radius:var(--r-full)}
82
98
  .brand{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-display);font-weight:800;font-size:22px;color:var(--ink);letter-spacing:-.02em}
83
- .brand-mark{width:28px;height:28px;border-radius:9px;background:var(--gold-500);display:grid;place-items:center;font-size:16px;box-shadow:var(--shadow-xs)}
99
+ .brand-mark{display:inline-block;vertical-align:middle;width:48px;height:48px;border-radius:12px;background:center/contain no-repeat url("/assets/logo-vidfarm.png");font-size:0;color:transparent;text-indent:-9999px;overflow:hidden}
84
100
  .nav-links{display:flex;gap:2px;margin:0 auto;list-style:none}
85
101
  .nav-links a{display:inline-flex;align-items:center;gap:4px;padding:8px 12px;border-radius:var(--r-lg);font-size:15px;font-weight:500;color:var(--n-700);transition:background var(--dur) var(--ease)}
86
102
  .nav-links a:hover{background:#0000000a}
@@ -186,7 +202,8 @@ h1,h2,h3,h4{font-family:var(--font-display);font-weight:800;letter-spacing:-.025
186
202
  .footer-inner{width:min(var(--container),100%);margin:0 auto}
187
203
  .footer-top{display:grid;grid-template-columns:1.4fr 3fr;gap:40px;margin-bottom:40px}
188
204
  @media(max-width:820px){.footer-top{grid-template-columns:1fr}}
189
- .footer-brand{font-family:var(--font-display);font-weight:800;font-size:28px;color:var(--gold-500);display:inline-flex;align-items:center;gap:8px}
205
+ .footer-brand{font-family:var(--font-display);font-weight:800;font-size:28px;color:var(--gold-500);display:inline-flex;align-items:center;gap:12px}
206
+ .footer-brand .brand-mark{width:60px;height:60px;border-radius:14px}
190
207
  .footer-tagline{color:var(--n-500);margin-top:10px;max-width:280px}
191
208
  .footer-cols{display:grid;grid-template-columns:repeat(3,1fr);gap:32px}
192
209
  @media(max-width:560px){.footer-cols{grid-template-columns:repeat(2,1fr)}}
@@ -204,7 +221,7 @@ h1,h2,h3,h4{font-family:var(--font-display);font-weight:800;letter-spacing:-.025
204
221
  <!-- NAV (body-level so it stays sticky across the whole page) -->
205
222
  <header class="nav-wrap">
206
223
  <nav class="nav glass">
207
- <a class="brand" href="/"><span class="brand-mark">🎬</span>vidfarm</a>
224
+ <a class="brand" href="/"><span class="brand-mark">🎬</span>VidFarm</a>
208
225
  <ul class="nav-links">
209
226
  <li><a href="${HREF.studio}">Templates</a></li>
210
227
  <li><a href="${HREF.clips}">Clips</a></li>
@@ -239,7 +256,7 @@ h1,h2,h3,h4{font-family:var(--font-display);font-weight:800;letter-spacing:-.025
239
256
  <div class="hero-shot">
240
257
  <div class="shot-head">
241
258
  <span class="dot dot-r"></span><span class="dot dot-y"></span><span class="dot dot-g"></span>
242
- <span class="shot-title">vidfarm · trackpad editor</span>
259
+ <span class="shot-title">VidFarm · trackpad editor</span>
243
260
  </div>
244
261
  <div class="pipeline">
245
262
  <div class="node"><span class="tile tile-sky">📼</span><div><div class="node-title">Fork template</div><div class="node-sub">Proven hook</div></div></div>
@@ -344,7 +361,7 @@ h1,h2,h3,h4{font-family:var(--font-display);font-weight:800;letter-spacing:-.025
344
361
  <div class="footer-inner">
345
362
  <div class="footer-top">
346
363
  <div>
347
- <div class="footer-brand"><span class="brand-mark">🎬</span>vidfarm</div>
364
+ <div class="footer-brand"><span class="brand-mark">🎬</span>VidFarm</div>
348
365
  <div class="footer-tagline">The serverless studio for short-form video.</div>
349
366
  </div>
350
367
  <div class="footer-cols">
@@ -168,14 +168,38 @@ export function renderBrandLockup(input) {
168
168
  </a>
169
169
  `;
170
170
  }
171
+ const BRAND_NAME = "VidFarm";
172
+ const BRAND_ORIGIN = "https://vidfarm.cc";
173
+ const BRAND_OG_IMAGE = `${BRAND_ORIGIN}/assets/logo-vidfarm.png`;
171
174
  export function renderPageShell(input) {
172
175
  const isEditorTheme = input.bodyClass?.split(/\s+/).includes("is-editor-theme") ?? false;
176
+ // Brand the tab title unless the page already names VidFarm.
177
+ const pageTitle = /vidfarm/i.test(input.title) ? input.title : `${input.title} · ${BRAND_NAME}`;
178
+ const description = input.description ?? `${BRAND_NAME} — serverless short-form video studio. Fork a proven template, remix it, and render a share-ready MP4 in your browser.`;
179
+ const ogImage = input.social?.image ?? BRAND_OG_IMAGE;
180
+ const ogType = input.social?.type ?? "website";
181
+ const ogUrl = input.social?.url ?? BRAND_ORIGIN;
182
+ const metaTags = `
183
+ <meta name="description" content="${escapeHtml(description)}" />
184
+ <meta name="theme-color" content="#f8f4eb" />
185
+ <meta name="robots" content="index, follow" />
186
+ <meta name="application-name" content="${BRAND_NAME}" />
187
+ <meta property="og:type" content="${escapeHtml(ogType)}" />
188
+ <meta property="og:site_name" content="${BRAND_NAME}" />
189
+ <meta property="og:title" content="${escapeHtml(pageTitle)}" />
190
+ <meta property="og:description" content="${escapeHtml(description)}" />
191
+ <meta property="og:url" content="${escapeHtml(ogUrl)}" />
192
+ <meta property="og:image" content="${escapeHtml(ogImage)}" />
193
+ <meta name="twitter:card" content="summary_large_image" />
194
+ <meta name="twitter:title" content="${escapeHtml(pageTitle)}" />
195
+ <meta name="twitter:description" content="${escapeHtml(description)}" />
196
+ <meta name="twitter:image" content="${escapeHtml(ogImage)}" />`;
173
197
  return `<!doctype html>
174
198
  <html lang="en">
175
199
  <head>
176
200
  <meta charset="utf-8" />
177
201
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
178
- <title>${escapeHtml(input.title)}</title>
202
+ <title>${escapeHtml(pageTitle)}</title>${metaTags}
179
203
  <link rel="icon" href="/assets/favicon.ico" sizes="any" />
180
204
  <link rel="preconnect" href="https://fonts.googleapis.com" />
181
205
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />