@mevdragon/vidfarm-devcli 0.20.6 → 0.20.9

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 (157) hide show
  1. package/.agents/skills/editor-capabilities/SKILL.md +15 -1
  2. package/.agents/skills/vidfarm-director/SKILL.md +106 -0
  3. package/.agents/skills/vidfarm-director/recipes/find-and-fork-template.md +16 -0
  4. package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +13 -0
  5. package/.agents/skills/vidfarm-director/recipes/onboard-a-new-director.md +13 -0
  6. package/.agents/skills/vidfarm-director/recipes/retheme-template.md +17 -0
  7. package/.agents/skills/vidfarm-director/references/assets-and-sourcing.md +117 -0
  8. package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +245 -0
  9. package/.agents/skills/vidfarm-director/references/core-workflows.md +273 -0
  10. package/.agents/skills/vidfarm-director/references/editor-workflows.md +365 -0
  11. package/.agents/skills/vidfarm-director/references/onboarding.md +28 -0
  12. package/.agents/skills/vidfarm-director/references/primitives.md +308 -0
  13. package/SKILL.director.md +517 -250
  14. package/dist/src/cli.js +563 -99
  15. package/dist/src/devcli/clips.js +29 -12
  16. package/dist/src/devcli/doctor.js +2 -2
  17. package/dist/src/devcli/local-backend.js +46 -2
  18. package/dist/src/services/clip-curation/cost.js +5 -1
  19. package/dist/src/services/clip-curation/gemini.js +14 -2
  20. package/dist/src/services/clip-curation/hunt.js +56 -0
  21. package/dist/src/services/clip-curation/index.js +1 -1
  22. package/dist/src/services/clip-curation/scan.js +5 -3
  23. package/package.json +30 -10
  24. package/SKILL.platform.md +0 -432
  25. package/demo/README.md +0 -28
  26. package/demo/dist/app.css +0 -1
  27. package/demo/dist/app.js +0 -1850
  28. package/demo/dist/chunks/chunk-DXB73IDG.js +0 -1
  29. package/demo/dist/chunks/chunk-S7OWAJDS.js +0 -36
  30. package/demo/dist/chunks/chunk-VTIBZ6AN.js +0 -1
  31. package/demo/dist/chunks/dist-ADSJKBVE.js +0 -332
  32. package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +0 -1
  33. package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +0 -425
  34. package/demo/dist/chunks/lib-XAQ37YOE.js +0 -1
  35. package/demo/dist/chunks/src-TJ2QYA4U.js +0 -207
  36. package/demo/dist/favicon.ico +0 -0
  37. package/demo/dist/icons/timeline/audio.svg +0 -7
  38. package/demo/dist/icons/timeline/captions.svg +0 -5
  39. package/demo/dist/icons/timeline/composition.svg +0 -12
  40. package/demo/dist/icons/timeline/image.svg +0 -18
  41. package/demo/dist/icons/timeline/music.svg +0 -10
  42. package/demo/dist/icons/timeline/text.svg +0 -3
  43. package/demo/dist/index.html +0 -15
  44. package/dist/src/account-pages-legacy.js +0 -9396
  45. package/dist/src/account-pages.js +0 -61
  46. package/dist/src/app.js +0 -21603
  47. package/dist/src/composition-runtime.js +0 -1053
  48. package/dist/src/config.js +0 -217
  49. package/dist/src/context.js +0 -447
  50. package/dist/src/dev-app-legacy.js +0 -739
  51. package/dist/src/dev-app.js +0 -6
  52. package/dist/src/devcli/migrate-local.js +0 -140
  53. package/dist/src/devcli/sync.js +0 -368
  54. package/dist/src/domain.js +0 -5
  55. package/dist/src/editor-chat-history.js +0 -82
  56. package/dist/src/editor-chat.js +0 -828
  57. package/dist/src/editor-dark-theme.js +0 -1128
  58. package/dist/src/frontend/debug.js +0 -71
  59. package/dist/src/frontend/discover-client.js +0 -130
  60. package/dist/src/frontend/discover-store.js +0 -23
  61. package/dist/src/frontend/file-directory.js +0 -1018
  62. package/dist/src/frontend/flockposter-cache-store.js +0 -124
  63. package/dist/src/frontend/homepage-client.js +0 -446
  64. package/dist/src/frontend/homepage-shared.js +0 -201
  65. package/dist/src/frontend/homepage-store.js +0 -66
  66. package/dist/src/frontend/homepage-view.js +0 -705
  67. package/dist/src/frontend/page-runtime-client.js +0 -132
  68. package/dist/src/frontend/page-runtime-store.js +0 -9
  69. package/dist/src/frontend/sentry.js +0 -42
  70. package/dist/src/frontend/template-editor-chat.js +0 -4181
  71. package/dist/src/help-page.js +0 -346
  72. package/dist/src/homepage.js +0 -1458
  73. package/dist/src/index.js +0 -16
  74. package/dist/src/instrument.js +0 -30
  75. package/dist/src/landing-page.js +0 -384
  76. package/dist/src/page-runtime.js +0 -2
  77. package/dist/src/page-shell.js +0 -1452
  78. package/dist/src/primitive-context.js +0 -416
  79. package/dist/src/primitive-registry.js +0 -3940
  80. package/dist/src/primitive-sdk.js +0 -4
  81. package/dist/src/primitives/hyperframes-media.js +0 -108
  82. package/dist/src/react-page-shell.js +0 -35
  83. package/dist/src/ready-post-schedule-component.js +0 -1540
  84. package/dist/src/registry.js +0 -296
  85. package/dist/src/reskin/agency-page.js +0 -299
  86. package/dist/src/reskin/calendar-page.js +0 -568
  87. package/dist/src/reskin/chat-page.js +0 -942
  88. package/dist/src/reskin/discover-page.js +0 -1788
  89. package/dist/src/reskin/document.js +0 -1587
  90. package/dist/src/reskin/help-page.js +0 -357
  91. package/dist/src/reskin/index-page.js +0 -62
  92. package/dist/src/reskin/inpaint-clipper-page.js +0 -890
  93. package/dist/src/reskin/inpaint-page.js +0 -2554
  94. package/dist/src/reskin/inpaint-video-page.js +0 -1339
  95. package/dist/src/reskin/job-runs-page.js +0 -477
  96. package/dist/src/reskin/library-page.js +0 -1634
  97. package/dist/src/reskin/login-page.js +0 -262
  98. package/dist/src/reskin/portfolio-page.js +0 -687
  99. package/dist/src/reskin/pricing-page.js +0 -390
  100. package/dist/src/reskin/settings-page.js +0 -732
  101. package/dist/src/reskin/theme.js +0 -711
  102. package/dist/src/runtime.js +0 -35
  103. package/dist/src/services/api-call-history.js +0 -249
  104. package/dist/src/services/auth.js +0 -152
  105. package/dist/src/services/billing-pricing.js +0 -39
  106. package/dist/src/services/billing.js +0 -241
  107. package/dist/src/services/cast.js +0 -127
  108. package/dist/src/services/chat-threads.js +0 -92
  109. package/dist/src/services/clip-records.js +0 -250
  110. package/dist/src/services/clip-search.js +0 -77
  111. package/dist/src/services/clip-vectors.js +0 -125
  112. package/dist/src/services/composition-sanitize.js +0 -124
  113. package/dist/src/services/composition-watch.js +0 -79
  114. package/dist/src/services/elevenlabs.js +0 -222
  115. package/dist/src/services/file-directory.js +0 -117
  116. package/dist/src/services/fork-access.js +0 -93
  117. package/dist/src/services/fork-manifest.js +0 -42
  118. package/dist/src/services/ghostcut.js +0 -179
  119. package/dist/src/services/hyperframes.js +0 -3654
  120. package/dist/src/services/job-capacity.js +0 -14
  121. package/dist/src/services/job-logs.js +0 -197
  122. package/dist/src/services/jobs.js +0 -136
  123. package/dist/src/services/local-dynamo.js +0 -0
  124. package/dist/src/services/media-processing.js +0 -766
  125. package/dist/src/services/primitive-media-lambda.js +0 -280
  126. package/dist/src/services/providers.js +0 -2748
  127. package/dist/src/services/rate-limits.js +0 -262
  128. package/dist/src/services/scene-annotations.js +0 -32
  129. package/dist/src/services/serverless-auth.js +0 -382
  130. package/dist/src/services/serverless-jobs.js +0 -1084
  131. package/dist/src/services/serverless-provider-keys.js +0 -409
  132. package/dist/src/services/serverless-records.js +0 -1515
  133. package/dist/src/services/serverless-template-configs.js +0 -75
  134. package/dist/src/services/storage.js +0 -461
  135. package/dist/src/services/swipe-customize.js +0 -437
  136. package/dist/src/services/template-certification.js +0 -413
  137. package/dist/src/services/template-loader.js +0 -99
  138. package/dist/src/services/template-runtime-bundles.js +0 -217
  139. package/dist/src/services/template-sources.js +0 -1017
  140. package/dist/src/services/upstream.js +0 -248
  141. package/dist/src/services/video-normalization.js +0 -2
  142. package/dist/src/services/webhooks.js +0 -62
  143. package/dist/src/template-editor-pages.js +0 -2576
  144. package/dist/src/template-editor-shell.js +0 -2893
  145. package/dist/src/template-sdk.js +0 -4
  146. package/dist/src/worker.js +0 -17
  147. package/public/assets/discover-client-app.js +0 -1
  148. package/public/assets/file-directory-app.js +0 -3
  149. package/public/assets/homepage-app.js +0 -54
  150. package/public/assets/homepage-client-app.js +0 -80
  151. package/public/assets/page-runtime-client-app.js +0 -94
  152. package/public/assets/placeholders/scene-placeholder.png +0 -0
  153. package/src/assets/SELLING_AWARENESS_STAGES.md +0 -579
  154. package/src/assets/SELLING_WITH_HOOKS.md +0 -377
  155. package/src/assets/SELLING_WITH_VSLS.md +0 -606
  156. package/src/assets/favicon.ico +0 -0
  157. package/src/assets/logo-vidfarm.png +0 -0
@@ -12,20 +12,25 @@ import { homedir } from "node:os";
12
12
  import { parseArgs } from "node:util";
13
13
  import { createInterface } from "node:readline/promises";
14
14
  import { createIdV7 } from "../lib/ids.js";
15
- import { buildEffectiveGuidance, ClipModelClient, DEFAULT_CLIP_ASPECT, DEFAULT_CLIP_DURATION_BAND, detectLocalAgent, detectScenes, effectiveHuntDurationSec, estimateScanCostFromScenes, fitScenesToDurationBand, formatCostEstimate, hasFfmpeg, LocalAgentClipClient, normalizeAspect, normalizeCropFocus, normalizeWindows, parseClipHuntPrompt, parseTimeRanges, probeVideo, resolveDurationBand, resolveTargetClipCount, scanVideo, searchClips } from "../services/clip-curation/index.js";
15
+ import { buildEffectiveGuidance, buildPrivateUserNotesGuidance, ClipModelClient, DEFAULT_CLIP_ASPECT, DEFAULT_CLIP_DURATION_BAND, detectLocalAgent, detectScenes, effectiveHuntDurationSec, estimateScanCostFromScenes, fitScenesToDurationBand, formatCostEstimate, hasFfmpeg, LocalAgentClipClient, normalizeAspect, normalizeCropFocus, normalizeWindows, parseClipHuntPrompt, parseTimeRanges, probeVideo, resolveDurationBand, resolveTargetClipCount, scanVideo, searchClips } from "../services/clip-curation/index.js";
16
16
  import { ClipStore } from "./clip-store.js";
17
17
  export const CLIPS_HELP = `vidfarm raws — build & search a local raws library (local ffmpeg + local agent by default)
18
18
 
19
19
  raws scan <video-path> Mine a long-form video into tagged, searchable raws
20
- --provider <p> agent | gemini | openai | openrouter
20
+ --provider <p> agent | gemini | openai | openrouter | nvidia
21
21
  Default: agent when a local claude/codex CLI is installed
22
22
  (your subscription, no API key), else gemini.
23
+ nvidia = lite, clipping-first key (minimax-m3); no audio/embeddings.
23
24
  --agent claude|codex Which local agent CLI to use for --provider agent (default: auto)
24
25
  --tier flash|flash-lite Gemini tier (default: flash-lite; ignored for openai/openrouter/agent)
25
26
  --prompt "<guidance>" Reshape clipping toward your goal: an LLM pass
26
27
  keeps/drops/merges scenes, and tags are biased to it.
27
28
  Inline hints are honored too: "between 12:30 and 15:45",
28
29
  "30 sec clips", "vertical", "no captions".
30
+ --notes "<private notes>" Store private owner notes as user_notes on derived raws
31
+ and feed them into decomposition/categorization. Explicit
32
+ private shot-kind hints like "content_type: demo" or
33
+ "category: talking_head" are honored and stay owner-only.
29
34
  --range "MM:SS-MM:SS" Only hunt inside this source range (repeatable / comma-separated)
30
35
  --duration <sec> Target raw length as a SOFT band (10→5-20s, 30→20-40s, …)
31
36
  --aspect <a> Crop raws to 9:16 | 16:9 | 4:3 | 1:1 (or vertical/horizontal/square)
@@ -49,8 +54,8 @@ export const CLIPS_HELP = `vidfarm raws — build & search a local raws library
49
54
  --threshold <0..1> Scene-cut sensitivity (default 0.3; lower = more cuts)
50
55
  --min-scene <sec> Merge scenes shorter than this (default 0.6)
51
56
  --max-scene <sec> Split scenes longer than this (default 8; --duration overrides)
52
- --gemini-key / --openai-key / --openrouter-key <key> Provider key (or *_API_KEY env)
53
- --embed-provider gemini|openai --embed-key <key> Embeddings for agent/openrouter scans
57
+ --gemini-key / --openai-key / --openrouter-key / --nvidia-key <key> Provider key (or *_API_KEY env)
58
+ --embed-provider gemini|openai --embed-key <key> Embeddings for agent/openrouter/nvidia scans
54
59
  --home <dir> Library dir (default: ~/.vidfarm or VIDFARM_HOME)
55
60
 
56
61
  raws list List raws in the library
@@ -152,12 +157,15 @@ async function runScan(argv) {
152
157
  "gemini-key": { type: "string" },
153
158
  "openai-key": { type: "string" },
154
159
  "openrouter-key": { type: "string" },
160
+ "nvidia-key": { type: "string" },
155
161
  "embed-provider": { type: "string" },
156
162
  "embed-key": { type: "string" },
163
+ notes: { type: "string" },
157
164
  home: { type: "string" }
158
165
  }
159
166
  });
160
167
  // ── Hunt spec: explicit flags win; inline prompt hints fill the gaps ──────
168
+ const userNotes = values.notes?.trim() || undefined;
161
169
  const guidancePromptRaw = values.prompt?.trim() || undefined;
162
170
  const parsedPrompt = parseClipHuntPrompt(guidancePromptRaw);
163
171
  const rawWindows = values.range?.length
@@ -179,7 +187,10 @@ async function runScan(argv) {
179
187
  const cropFocus = normalizeCropFocus(values["crop-focus"]);
180
188
  const avoidText = values["allow-text"] ? false : (Boolean(values["no-text"]) || parsedPrompt.avoid_text || true);
181
189
  const maxClips = values["max-clips"] ? Math.max(1, Math.round(Number(values["max-clips"]))) : parsedPrompt.max_clips ?? undefined;
182
- const guidance = buildEffectiveGuidance({ contentPrompt: guidancePromptRaw, avoidText }) || undefined;
190
+ const guidance = buildEffectiveGuidance({
191
+ contentPrompt: [guidancePromptRaw, buildPrivateUserNotesGuidance(userNotes)].filter(Boolean).join("\n"),
192
+ avoidText
193
+ }) || undefined;
183
194
  // ── BACKUP path: run the hunt on the deployed pipeline (--cloud) ──────────
184
195
  if (values.cloud) {
185
196
  const apiProvider = values.provider && !["agent", "local", "local-agent"].includes(values.provider)
@@ -193,6 +204,7 @@ async function runScan(argv) {
193
204
  tracer: values.tracer,
194
205
  folder: values.folder,
195
206
  prompt: guidancePromptRaw,
207
+ userNotes,
196
208
  provider: apiProvider,
197
209
  windows: rawWindows,
198
210
  durationBand,
@@ -337,6 +349,7 @@ async function runScan(argv) {
337
349
  framesPerScene,
338
350
  includeAudio,
339
351
  guidancePrompt: guidance,
352
+ userNotes,
340
353
  refineSegmentation: !values["no-refine"],
341
354
  reencodeClips: true,
342
355
  audioOnly: Boolean(values["audio-only"]),
@@ -459,6 +472,7 @@ async function runScanCloud(input) {
459
472
  body: JSON.stringify({
460
473
  ...(input.sourceUrl ? { source_url: input.sourceUrl } : { temp_file_id: tempFileId, filename: fileName }),
461
474
  prompt: input.prompt ?? "",
475
+ ...(input.userNotes ? { user_notes: input.userNotes } : {}),
462
476
  ...(input.provider ? { provider: input.provider } : {}),
463
477
  ...(input.tracer ? { tracer: input.tracer } : {}),
464
478
  ...(input.folder ? { folder_path: input.folder } : {}),
@@ -935,9 +949,9 @@ async function runExport(argv) {
935
949
  function normalizeProvider(value) {
936
950
  if (value === undefined || value === "gemini")
937
951
  return "gemini";
938
- if (value === "openai" || value === "openrouter")
952
+ if (value === "openai" || value === "openrouter" || value === "nvidia")
939
953
  return value;
940
- throw new Error(`--provider must be gemini | openai | openrouter (got "${value}")`);
954
+ throw new Error(`--provider must be gemini | openai | openrouter | nvidia (got "${value}")`);
941
955
  }
942
956
  // Parse a comma-separated --content-type flag into lowercase shot-KIND tags for
943
957
  // the tags.content_type filter (talking_head, b_roll, product_shot, …).
@@ -961,12 +975,14 @@ function defaultEmbedModelForEstimate(provider) {
961
975
  const ENV_KEYS = {
962
976
  gemini: ["GEMINI_API_KEY", "GOOGLE_GENERATIVE_AI_API_KEY"],
963
977
  openai: ["OPENAI_API_KEY"],
964
- openrouter: ["OPENROUTER_API_KEY"]
978
+ openrouter: ["OPENROUTER_API_KEY"],
979
+ nvidia: ["NVIDIA_API_KEY"]
965
980
  };
966
981
  const CONFIG_KEYS = {
967
982
  gemini: "gemini_api_key",
968
983
  openai: "openai_api_key",
969
- openrouter: "openrouter_api_key"
984
+ openrouter: "openrouter_api_key",
985
+ nvidia: "nvidia_api_key"
970
986
  };
971
987
  /** Resolve a provider key: --<provider>-key flag → *_API_KEY env → ~/.vidfarm/config.json. */
972
988
  function resolveProviderKey(provider, flags, opts = {}) {
@@ -997,15 +1013,16 @@ function resolveProviderKey(provider, flags, opts = {}) {
997
1013
  }
998
1014
  /**
999
1015
  * Embedding config for a scan. gemini/openai embed with their own key; openrouter
1000
- * can't embed, so pair it with a gemini/openai key via --embed-provider/--embed-key
1001
- * (or GEMINI_API_KEY/OPENAI_API_KEY env). Returns null → semantic search disabled.
1016
+ * and nvidia can't embed, so pair them with a gemini/openai key via
1017
+ * --embed-provider/--embed-key (or GEMINI_API_KEY/OPENAI_API_KEY env). Returns
1018
+ * null → semantic search disabled (structured-only library).
1002
1019
  */
1003
1020
  function resolveEmbeddingConfig(provider, apiKey, flags) {
1004
1021
  if (provider === "gemini")
1005
1022
  return { provider: "gemini", apiKey };
1006
1023
  if (provider === "openai")
1007
1024
  return { provider: "openai", apiKey };
1008
- // openrouter → look for an explicit embed provider, else any gemini/openai key.
1025
+ // openrouter / nvidia → look for an explicit embed provider, else any gemini/openai key.
1009
1026
  const explicit = flags["embed-provider"]?.trim();
1010
1027
  const tryProvider = (p) => {
1011
1028
  const key = flags["embed-key"]?.trim() || resolveProviderKey(p, flags, { optional: true });
@@ -133,10 +133,10 @@ export async function runDoctorCommand(argv) {
133
133
  const apiKey = parsed.values["api-key"] ?? process.env.VIDFARM_API_KEY;
134
134
  if (apiKey) {
135
135
  const who = await fetchWhoami(String(parsed.values.host).replace(/\/+$/, ""), apiKey);
136
- add("vidfarm auth", who.ok ? "ok" : "warn", who.detail);
136
+ add("vidfarm auth", who.ok ? "ok" : "warn", who.ok ? who.detail : `${who.detail} — serve will fall back to local browse mode`);
137
137
  }
138
138
  else {
139
- add("vidfarm auth", "warn", "VIDFARM_API_KEY not set — cloud commands need it (local serve/lint/stills/speech engines work without it)");
139
+ add("vidfarm auth", "warn", "VIDFARM_API_KEY not set — serve will fall back to local browse mode; cloud catalog/bulk routes need it");
140
140
  }
141
141
  // 6. Provider keys in env (BYOK local-first engines).
142
142
  const presentKeys = PROVIDER_KEY_ENV_VARS.filter((name) => process.env[name]?.trim());
@@ -17,6 +17,47 @@
17
17
  // drivers correctly — exactly the discipline `runServeCommand` follows.
18
18
  import path from "node:path";
19
19
  import { homedir } from "node:os";
20
+ import { existsSync } from "node:fs";
21
+ import { fileURLToPath } from "node:url";
22
+ /**
23
+ * Thrown when a local / `serve` command is invoked in the PUBLISHED CLI, which
24
+ * ships as a cloud client only — the in-process backend (`app.js` et al.) is
25
+ * deliberately excluded from the npm tarball so the server is never open-sourced.
26
+ * A full source checkout (where `app.js` sits next to the CLI on disk) still runs
27
+ * local mode; the published package degrades to this friendly error instead.
28
+ */
29
+ export class LocalModeUnavailableError extends Error {
30
+ constructor() {
31
+ super("Local / serve mode isn't available in the published Vidfarm CLI — it would require the full backend. " +
32
+ "Use cloud mode (the default): drop --local/--both, or pass --cloud. " +
33
+ "(Local mode is available when running the CLI from a full source checkout.)");
34
+ this.name = "LocalModeUnavailableError";
35
+ }
36
+ }
37
+ /** True only when the in-process backend (`app.js`) is present next to the CLI
38
+ * on disk — i.e. a full source/dist checkout, never the published npm package. */
39
+ export function localBackendAvailable() {
40
+ try {
41
+ const here = path.dirname(fileURLToPath(import.meta.url)); // dist/src/devcli
42
+ return existsSync(path.join(here, "..", "app.js"));
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
48
+ /** Wrap a dynamic import of a backend module so an absent file (published CLI)
49
+ * surfaces as a clean LocalModeUnavailableError, not a raw ERR_MODULE_NOT_FOUND. */
50
+ export async function importBackendModule(loader) {
51
+ try {
52
+ return await loader();
53
+ }
54
+ catch (error) {
55
+ if (error && typeof error === "object" && error.code === "ERR_MODULE_NOT_FOUND") {
56
+ throw new LocalModeUnavailableError();
57
+ }
58
+ throw error;
59
+ }
60
+ }
20
61
  /** The durable per-user local home. Everything the local backend persists lives
21
62
  * under here: records + storage at `<home>/data`, legacy clips at
22
63
  * `<home>/clips.db`, provider config at `<home>/config.json`. */
@@ -75,9 +116,12 @@ export async function withLocalBackend(opts = {}) {
75
116
  "vidfarm-local-dev-key";
76
117
  process.env.VIDFARM_API_KEY = apiKey;
77
118
  // Dynamic imports AFTER env is set, so config.ts binds the local drivers.
78
- const { ServerlessAuthService } = await import("../services/serverless-auth.js");
119
+ // Wrapped so the published (cloud-only) CLI — which ships without these backend
120
+ // modules — fails with a friendly LocalModeUnavailableError instead of a raw
121
+ // module-not-found stack trace.
122
+ const { ServerlessAuthService } = await importBackendModule(() => import("../services/serverless-auth.js"));
79
123
  const customer = await new ServerlessAuthService().authenticate(apiKey);
80
- const appMod = await import("../app.js");
124
+ const appMod = await importBackendModule(() => import("../app.js"));
81
125
  const app = appMod.default;
82
126
  cached = { app, apiKey, ownerId: customer.id, home };
83
127
  return cached;
@@ -13,7 +13,9 @@ const MODEL_PRICING = {
13
13
  "gemini-2.5-flash": { input: envPrice("VIDFARM_PRICE_FLASH_INPUT", 0.3), output: envPrice("VIDFARM_PRICE_FLASH_OUTPUT", 2.5) },
14
14
  // OpenRouter adds a small markup over Google's list price.
15
15
  "google/gemini-2.5-flash": { input: envPrice("VIDFARM_PRICE_OR_FLASH_INPUT", 0.315), output: envPrice("VIDFARM_PRICE_OR_FLASH_OUTPUT", 2.625) },
16
- "gpt-5.4-nano": { input: envPrice("VIDFARM_PRICE_GPT5_NANO_INPUT", 0.05), output: envPrice("VIDFARM_PRICE_GPT5_NANO_OUTPUT", 0.4) }
16
+ "gpt-5.4-nano": { input: envPrice("VIDFARM_PRICE_GPT5_NANO_INPUT", 0.05), output: envPrice("VIDFARM_PRICE_GPT5_NANO_OUTPUT", 0.4) },
17
+ // Nvidia lite clipping model (minimaxai/minimax-m3) — approximate, env-overridable.
18
+ "minimaxai/minimax-m3": { input: envPrice("VIDFARM_PRICE_NVIDIA_MINIMAX_INPUT", 0.15), output: envPrice("VIDFARM_PRICE_NVIDIA_MINIMAX_OUTPUT", 0.6) }
17
19
  };
18
20
  const DEFAULT_TAG_PRICING = { input: 0.15, output: 0.6 };
19
21
  const EMBED_PRICING = {
@@ -61,6 +63,8 @@ function defaultTagModel(provider, tier) {
61
63
  return "gpt-5.4-nano";
62
64
  if (provider === "openrouter")
63
65
  return "google/gemini-2.5-flash";
66
+ if (provider === "nvidia")
67
+ return "minimaxai/minimax-m3";
64
68
  return tier === "flash" ? "gemini-2.5-flash" : "gemini-2.5-flash-lite";
65
69
  }
66
70
  function compute(input) {
@@ -21,6 +21,8 @@ function resolveTagModel(provider, tier) {
21
21
  return process.env.VIDFARM_CLIP_TAG_MODEL_OPENAI ?? "gpt-5.4-nano";
22
22
  case "openrouter":
23
23
  return process.env.VIDFARM_CLIP_TAG_MODEL_OPENROUTER ?? "google/gemini-2.5-flash";
24
+ case "nvidia":
25
+ return process.env.VIDFARM_CLIP_TAG_MODEL_NVIDIA ?? "minimaxai/minimax-m3";
24
26
  case "gemini":
25
27
  default:
26
28
  return tier === "flash"
@@ -281,6 +283,14 @@ function buildLanguageModel(provider, apiKey, modelId, publicBaseUrl) {
281
283
  baseURL: "https://openrouter.ai/api/v1",
282
284
  headers: { "HTTP-Referer": publicBaseUrl ?? "https://vidfarm.cc", "X-Title": "Vidfarm Clips" }
283
285
  }).chatModel(modelId);
286
+ case "nvidia":
287
+ // Lite, clipping-first key — OpenAI-compatible multimodal chat (frames in,
288
+ // no audio). No embeddings API (handled by resolveEmbeddingConfig).
289
+ return createOpenAICompatible({
290
+ name: "nvidia",
291
+ apiKey,
292
+ baseURL: "https://integrate.api.nvidia.com/v1"
293
+ }).chatModel(modelId);
284
294
  case "gemini":
285
295
  default:
286
296
  return createGoogleGenerativeAI({ apiKey })(modelId);
@@ -302,8 +312,8 @@ function resolveEmbeddingConfig(provider, apiKey, explicit) {
302
312
  return { provider: "openai", apiKey };
303
313
  return null; // openrouter: no embeddings unless an explicit config is given
304
314
  }
305
- /** The document text embedded for a clip: description + transcript + salient tags. */
306
- export function buildClipEmbeddingText(tags, description) {
315
+ /** The document text embedded for a clip: description + transcript + salient tags + private owner notes. */
316
+ export function buildClipEmbeddingText(tags, description, userNotes) {
307
317
  const parts = [];
308
318
  if (description)
309
319
  parts.push(description);
@@ -322,6 +332,8 @@ export function buildClipEmbeddingText(tags, description) {
322
332
  ];
323
333
  if (tagBits.length)
324
334
  parts.push(tagBits.join(", "));
335
+ if (userNotes?.trim())
336
+ parts.push(`Owner notes: ${userNotes.trim()}`);
325
337
  return parts.join("\n").trim() || "video clip";
326
338
  }
327
339
  export function buildTaggingPrompt(transcript, version, hasAudio = false, guidancePrompt) {
@@ -6,6 +6,7 @@
6
6
  // NEVER GhostCut on the long-form source). Shared by devcli (local), the Hono
7
7
  // API, and the cloud scan Lambda so every surface parses and normalizes
8
8
  // identically. All helpers are pure/deterministic — no AI calls here.
9
+ import { ACTIVE_TAXONOMY_VERSION, getTaxonomy } from "./taxonomy.js";
9
10
  // ── Unspecified-prompt defaults ─────────────────────────────────────────────
10
11
  // When a user imports a source video without saying what they want, we bias
11
12
  // toward the short-form product default: a handful of DISTINCT, vertical,
@@ -293,6 +294,61 @@ export function buildEffectiveGuidance(input) {
293
294
  parts.push(AVOID_TEXT_GUIDANCE);
294
295
  return parts.join("\n");
295
296
  }
297
+ function canonicalizeContentTypeHint(value) {
298
+ const normalized = String(value ?? "")
299
+ .trim()
300
+ .toLowerCase()
301
+ .replace(/^#/, "")
302
+ .replace(/[\s-]+/g, "_");
303
+ if (!normalized)
304
+ return null;
305
+ if (normalized === "broll")
306
+ return "b_roll";
307
+ return normalized;
308
+ }
309
+ export function extractUserContentTypeHints(notes, version = ACTIVE_TAXONOMY_VERSION) {
310
+ const text = String(notes ?? "").trim();
311
+ if (!text)
312
+ return [];
313
+ const allowed = new Set(getTaxonomy(version).categories.content_type.map((value) => canonicalizeContentTypeHint(value)));
314
+ const found = new Set();
315
+ const capture = (value) => {
316
+ const normalized = canonicalizeContentTypeHint(value);
317
+ if (normalized && allowed.has(normalized))
318
+ found.add(normalized);
319
+ };
320
+ for (const match of text.matchAll(/\b(?:content[_\s-]*type|category|tag)s?\s*[:=]\s*([^\n.;]+)/gi)) {
321
+ const raw = String(match[1] ?? "");
322
+ for (const piece of raw.split(/[,/|]+|\band\b/gi))
323
+ capture(piece);
324
+ }
325
+ for (const match of text.matchAll(/\b#?(talking[\s_-]*head|b[\s_-]*roll|product[\s_-]*shot|screen[\s_-]*recording|demo|reaction|interview|establishing|lifestyle|text[\s_-]*graphic)\b/gi)) {
326
+ capture(String(match[1] ?? ""));
327
+ }
328
+ return [...found];
329
+ }
330
+ export function applyUserContentTypeHints(tags, notes, version = ACTIVE_TAXONOMY_VERSION) {
331
+ const hinted = extractUserContentTypeHints(notes, version);
332
+ if (!hinted.length)
333
+ return tags;
334
+ return {
335
+ ...tags,
336
+ content_type: [...new Set([...(tags.content_type ?? []), ...hinted])]
337
+ };
338
+ }
339
+ export function buildPrivateUserNotesGuidance(notes, version = ACTIVE_TAXONOMY_VERSION) {
340
+ const text = String(notes ?? "").trim();
341
+ if (!text)
342
+ return "";
343
+ const hinted = extractUserContentTypeHints(text, version);
344
+ const parts = [
345
+ `Private owner notes for this raw: "${text}". Use them to bias categorization and description, but stay truthful to the footage.`
346
+ ];
347
+ if (hinted.length) {
348
+ parts.push(`If the footage supports it, include these owner-requested private content_type tags: ${hinted.join(", ")}.`);
349
+ }
350
+ return parts.join("\n");
351
+ }
296
352
  /**
297
353
  * Deterministically extract structured hints from a free-form hunt prompt:
298
354
  * "please only hunt for clips between 12:30 and 15:45", "short 10 sec clips",
@@ -6,7 +6,7 @@ export { ACTIVE_TAXONOMY_VERSION, MULTI_VALUE_CATEGORIES, getTaxonomy, sanitizeT
6
6
  export { BUILTIN_PRESETS, findBuiltinPreset } from "./presets.js";
7
7
  export { ClipModelClient, ClipGeminiClient, ClipEmbeddingClient, EMBEDDING_DIM, EMBEDDING_MODEL, EMBEDDING_MODELS, TAG_MODEL_BY_TIER, buildClipEmbeddingText, buildQueryPrompt, buildTaggingPrompt, normalize } from "./gemini.js";
8
8
  export { LocalAgentClipClient, detectLocalAgent, extractJsonObject } from "./local-agent.js";
9
- export { AVOID_TEXT_GUIDANCE, DEFAULT_CLIP_ASPECT, DEFAULT_CLIP_DURATION_BAND, DEFAULT_CLIPS_PER_10_MIN, applyHuntSpecDefaults, buildEffectiveGuidance, capScenesEvenlyToCount, cropFilterFor, effectiveHuntDurationSec, fitScenesToDurationBand, normalizeAspect, normalizeCropFocus, normalizeHuntSpec, normalizeWindows, parseClipHuntPrompt, parseTimecode, parseTimeRanges, resolveDurationBand, resolveTargetClipCount } from "./hunt.js";
9
+ export { AVOID_TEXT_GUIDANCE, DEFAULT_CLIP_ASPECT, DEFAULT_CLIP_DURATION_BAND, DEFAULT_CLIPS_PER_10_MIN, applyHuntSpecDefaults, applyUserContentTypeHints, buildEffectiveGuidance, buildPrivateUserNotesGuidance, capScenesEvenlyToCount, cropFilterFor, effectiveHuntDurationSec, extractUserContentTypeHints, fitScenesToDurationBand, normalizeAspect, normalizeCropFocus, normalizeHuntSpec, normalizeWindows, parseClipHuntPrompt, parseTimecode, parseTimeRanges, resolveDurationBand, resolveTargetClipCount } from "./hunt.js";
10
10
  export { detectScenes, extractAudioClip, extractClip, extractKeyframes, extractSceneAudio, extractThumbnail, extractWaveformThumbnail, hasFfmpeg, probeVideo, resolveFfmpeg, resolveFfprobe } from "./ffmpeg.js";
11
11
  export { cosineSimilarity, matchesCriteria, rankHits, scoreClip, searchClips, structuredMatchRatio } from "./query.js";
12
12
  export { estimateScanCostFromDuration, estimateScanCostFromScenes, formatCostEstimate } from "./cost.js";
@@ -9,7 +9,7 @@ import { createIdV7 } from "../../lib/ids.js";
9
9
  import { ACTIVE_TAXONOMY_VERSION } from "./taxonomy.js";
10
10
  import { buildClipEmbeddingText } from "./gemini.js";
11
11
  import { detectScenes, extractAudioClip, extractClip, extractKeyframes, extractSceneAudio, extractThumbnail, extractWaveformThumbnail, probeVideo } from "./ffmpeg.js";
12
- import { capScenesEvenlyToCount, cropFilterFor, fitScenesToDurationBand, normalizeWindows } from "./hunt.js";
12
+ import { applyUserContentTypeHints, capScenesEvenlyToCount, cropFilterFor, fitScenesToDurationBand, normalizeWindows } from "./hunt.js";
13
13
  import { refineScenesWithGuidance } from "./refine.js";
14
14
  /** Turn one scene into a Clip. Throws only on unrecoverable extraction failure. */
15
15
  export async function processSceneToClip(input) {
@@ -46,6 +46,7 @@ export async function processSceneToClip(input) {
46
46
  scene,
47
47
  guidancePrompt: ctx.guidancePrompt
48
48
  });
49
+ const tags = applyUserContentTypeHints(tagResult.tags, ctx.userNotes, ctx.client.taxonomyVersion ?? ACTIVE_TAXONOMY_VERSION);
49
50
  // 4. Cut the clip + thumbnail. Audio-only hunts emit an .m4a (no video) with a
50
51
  // waveform PNG thumbnail; ordinary hunts emit an .mp4 cropped to the target
51
52
  // aspect when requested.
@@ -77,7 +78,7 @@ export async function processSceneToClip(input) {
77
78
  // 5. Embed the document text for semantic search (skipped when the client can't embed).
78
79
  let embedding;
79
80
  if (ctx.client.canEmbed) {
80
- const embedText = buildClipEmbeddingText(tagResult.tags, tagResult.description);
81
+ const embedText = buildClipEmbeddingText(tags, tagResult.description, ctx.userNotes);
81
82
  [embedding] = await ctx.client.embedDocuments([embedText]);
82
83
  }
83
84
  const clip = {
@@ -89,7 +90,7 @@ export async function processSceneToClip(input) {
89
90
  duration_sec: scene.duration_sec,
90
91
  file_path: ctx.clipStoredPath ? ctx.clipStoredPath(clipId, clipLocal) : clipLocal,
91
92
  thumbnail_path: ctx.thumbStoredPath ? ctx.thumbStoredPath(clipId, thumbLocal) : thumbLocal,
92
- tags: tagResult.tags,
93
+ tags,
93
94
  description: tagResult.description,
94
95
  embedding,
95
96
  taxonomy_version: ctx.client.taxonomyVersion ?? ACTIVE_TAXONOMY_VERSION,
@@ -98,6 +99,7 @@ export async function processSceneToClip(input) {
98
99
  embedding_model: ctx.client.embeddingModelId ?? undefined,
99
100
  aspect: !audioOnly && cropFilter && ctx.crop ? ctx.crop.aspect : undefined,
100
101
  media_kind: audioOnly ? "audio" : undefined,
102
+ user_notes: ctx.userNotes?.trim() || undefined,
101
103
  owner_id: input.ownerId,
102
104
  created_at: new Date().toISOString()
103
105
  };
package/package.json CHANGED
@@ -1,26 +1,44 @@
1
1
  {
2
2
  "name": "@mevdragon/vidfarm-devcli",
3
- "version": "0.20.6",
3
+ "version": "0.20.9",
4
4
  "description": "Local bridge for the Vidfarm Trackpad Editor. `vidfarm serve <template_id>` boots the FULL editor on localhost (disk-backed records/storage, free in-process render); edit composition.html on disk (Claude Code, Codex, etc.) and the browser live-morphs it.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "vidfarm": "dist/src/cli.js",
8
8
  "vidfarm-devcli": "dist/src/cli.js"
9
9
  },
10
+ "//files": "PUBLISHED = CLOUD-ONLY CLI. Ships ONLY the CLI's static import closure (backend-free) + shared client utils + skills/docs. The full backend (dist/src/app.js, editor-chat, serverless-*, reskin/, landing/homepage, services/hyperframes|providers|storage|ghostcut|upstream, infra) is INTENTIONALLY EXCLUDED so the server is never open-sourced. `prepack` runs scripts/check-devcli-pack-safe.mjs which HARD-FAILS if any backend file leaks in or a needed CLI file is missing. Do not broaden these globs without re-running that guard.",
10
11
  "files": [
11
- "dist/src/**/*.js",
12
- "dist/src/**/*.json",
13
- "demo/dist/**",
14
- "public/assets/**",
15
- "src/assets/**",
12
+ "dist/src/cli.js",
13
+ "dist/src/devcli/captions.js",
14
+ "dist/src/devcli/clip-store.js",
15
+ "dist/src/devcli/clips.js",
16
+ "dist/src/devcli/composition-edit.js",
17
+ "dist/src/devcli/doctor.js",
18
+ "dist/src/devcli/hyperframes-cli.js",
19
+ "dist/src/devcli/local-backend.js",
20
+ "dist/src/devcli/skills.js",
21
+ "dist/src/devcli/speech.js",
22
+ "dist/src/devcli/stills.js",
23
+ "dist/src/devcli/telemetry.js",
24
+ "dist/src/devcli/timeline-edit.js",
25
+ "dist/src/devcli/transitions.js",
26
+ "dist/src/hyperframes/composition.js",
27
+ "dist/src/lib/**/*.js",
28
+ "dist/src/services/captions.js",
29
+ "dist/src/services/composition-lint.js",
30
+ "dist/src/services/provider-errors.js",
31
+ "dist/src/services/speech.js",
32
+ "dist/src/services/clip-curation/**/*.js",
33
+ "dist/src/services/clip-curation/**/*.json",
16
34
  ".agents/skills/**",
17
35
  "README.md",
18
36
  "SKILL.director.md",
19
- "SKILL.platform.md",
20
- "!readme.secret.md"
37
+ "!readme.secret.md",
38
+ "!**/*.secret.*"
21
39
  ],
22
40
  "publishConfig": {
23
- "access": "restricted"
41
+ "access": "public"
24
42
  },
25
43
  "repository": {
26
44
  "type": "git",
@@ -48,7 +66,8 @@
48
66
  "cdk:deploy:staging-serverless": "npm run build && dotenv -e .env.staging -- npx aws-cdk deploy --app 'node dist/infra/cdk/bin/vidfarm-serverless-staging.js'",
49
67
  "cdk:deploy:staging-serverless:hotswap": "npm run build && dotenv -e .env.staging -- npx aws-cdk deploy --app 'node dist/infra/cdk/bin/vidfarm-serverless-staging.js' --hotswap-fallback --require-approval never",
50
68
  "cdk:deploy:prod-serverless:hotswap": "npm run build && dotenv -e .env.production -- npx aws-cdk deploy --app 'node dist/infra/cdk/bin/vidfarm-prod.js' --hotswap-fallback --require-approval never",
51
- "prepack": "npm run build"
69
+ "prepack": "npm run build && node scripts/check-devcli-pack-safe.mjs",
70
+ "check:pack-safe": "node scripts/check-devcli-pack-safe.mjs"
52
71
  },
53
72
  "dependencies": {
54
73
  "@ai-sdk/google": "^3.0.79",
@@ -96,6 +115,7 @@
96
115
  "aws-cdk-lib": "^2.196.0",
97
116
  "constructs": "^10.4.2",
98
117
  "dotenv-cli": "^11.0.0",
118
+ "react-grab": "^0.1.48",
99
119
  "tsx": "^4.19.4",
100
120
  "typescript": "^5.8.3",
101
121
  "zustand": "^5.0.13"