@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.
- package/.agents/skills/editor-capabilities/SKILL.md +15 -1
- package/.agents/skills/vidfarm-director/SKILL.md +106 -0
- package/.agents/skills/vidfarm-director/recipes/find-and-fork-template.md +16 -0
- package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +13 -0
- package/.agents/skills/vidfarm-director/recipes/onboard-a-new-director.md +13 -0
- package/.agents/skills/vidfarm-director/recipes/retheme-template.md +17 -0
- package/.agents/skills/vidfarm-director/references/assets-and-sourcing.md +117 -0
- package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +245 -0
- package/.agents/skills/vidfarm-director/references/core-workflows.md +273 -0
- package/.agents/skills/vidfarm-director/references/editor-workflows.md +365 -0
- package/.agents/skills/vidfarm-director/references/onboarding.md +28 -0
- package/.agents/skills/vidfarm-director/references/primitives.md +308 -0
- package/SKILL.director.md +517 -250
- package/dist/src/cli.js +563 -99
- package/dist/src/devcli/clips.js +29 -12
- package/dist/src/devcli/doctor.js +2 -2
- package/dist/src/devcli/local-backend.js +46 -2
- package/dist/src/services/clip-curation/cost.js +5 -1
- package/dist/src/services/clip-curation/gemini.js +14 -2
- package/dist/src/services/clip-curation/hunt.js +56 -0
- package/dist/src/services/clip-curation/index.js +1 -1
- package/dist/src/services/clip-curation/scan.js +5 -3
- package/package.json +30 -10
- package/SKILL.platform.md +0 -432
- package/demo/README.md +0 -28
- package/demo/dist/app.css +0 -1
- package/demo/dist/app.js +0 -1850
- package/demo/dist/chunks/chunk-DXB73IDG.js +0 -1
- package/demo/dist/chunks/chunk-S7OWAJDS.js +0 -36
- package/demo/dist/chunks/chunk-VTIBZ6AN.js +0 -1
- package/demo/dist/chunks/dist-ADSJKBVE.js +0 -332
- package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +0 -1
- package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +0 -425
- package/demo/dist/chunks/lib-XAQ37YOE.js +0 -1
- package/demo/dist/chunks/src-TJ2QYA4U.js +0 -207
- package/demo/dist/favicon.ico +0 -0
- package/demo/dist/icons/timeline/audio.svg +0 -7
- package/demo/dist/icons/timeline/captions.svg +0 -5
- package/demo/dist/icons/timeline/composition.svg +0 -12
- package/demo/dist/icons/timeline/image.svg +0 -18
- package/demo/dist/icons/timeline/music.svg +0 -10
- package/demo/dist/icons/timeline/text.svg +0 -3
- package/demo/dist/index.html +0 -15
- package/dist/src/account-pages-legacy.js +0 -9396
- package/dist/src/account-pages.js +0 -61
- package/dist/src/app.js +0 -21603
- package/dist/src/composition-runtime.js +0 -1053
- package/dist/src/config.js +0 -217
- package/dist/src/context.js +0 -447
- package/dist/src/dev-app-legacy.js +0 -739
- package/dist/src/dev-app.js +0 -6
- package/dist/src/devcli/migrate-local.js +0 -140
- package/dist/src/devcli/sync.js +0 -368
- package/dist/src/domain.js +0 -5
- package/dist/src/editor-chat-history.js +0 -82
- package/dist/src/editor-chat.js +0 -828
- package/dist/src/editor-dark-theme.js +0 -1128
- package/dist/src/frontend/debug.js +0 -71
- package/dist/src/frontend/discover-client.js +0 -130
- package/dist/src/frontend/discover-store.js +0 -23
- package/dist/src/frontend/file-directory.js +0 -1018
- package/dist/src/frontend/flockposter-cache-store.js +0 -124
- package/dist/src/frontend/homepage-client.js +0 -446
- package/dist/src/frontend/homepage-shared.js +0 -201
- package/dist/src/frontend/homepage-store.js +0 -66
- package/dist/src/frontend/homepage-view.js +0 -705
- package/dist/src/frontend/page-runtime-client.js +0 -132
- package/dist/src/frontend/page-runtime-store.js +0 -9
- package/dist/src/frontend/sentry.js +0 -42
- package/dist/src/frontend/template-editor-chat.js +0 -4181
- package/dist/src/help-page.js +0 -346
- package/dist/src/homepage.js +0 -1458
- package/dist/src/index.js +0 -16
- package/dist/src/instrument.js +0 -30
- package/dist/src/landing-page.js +0 -384
- package/dist/src/page-runtime.js +0 -2
- package/dist/src/page-shell.js +0 -1452
- package/dist/src/primitive-context.js +0 -416
- package/dist/src/primitive-registry.js +0 -3940
- package/dist/src/primitive-sdk.js +0 -4
- package/dist/src/primitives/hyperframes-media.js +0 -108
- package/dist/src/react-page-shell.js +0 -35
- package/dist/src/ready-post-schedule-component.js +0 -1540
- package/dist/src/registry.js +0 -296
- package/dist/src/reskin/agency-page.js +0 -299
- package/dist/src/reskin/calendar-page.js +0 -568
- package/dist/src/reskin/chat-page.js +0 -942
- package/dist/src/reskin/discover-page.js +0 -1788
- package/dist/src/reskin/document.js +0 -1587
- package/dist/src/reskin/help-page.js +0 -357
- package/dist/src/reskin/index-page.js +0 -62
- package/dist/src/reskin/inpaint-clipper-page.js +0 -890
- package/dist/src/reskin/inpaint-page.js +0 -2554
- package/dist/src/reskin/inpaint-video-page.js +0 -1339
- package/dist/src/reskin/job-runs-page.js +0 -477
- package/dist/src/reskin/library-page.js +0 -1634
- package/dist/src/reskin/login-page.js +0 -262
- package/dist/src/reskin/portfolio-page.js +0 -687
- package/dist/src/reskin/pricing-page.js +0 -390
- package/dist/src/reskin/settings-page.js +0 -732
- package/dist/src/reskin/theme.js +0 -711
- package/dist/src/runtime.js +0 -35
- package/dist/src/services/api-call-history.js +0 -249
- package/dist/src/services/auth.js +0 -152
- package/dist/src/services/billing-pricing.js +0 -39
- package/dist/src/services/billing.js +0 -241
- package/dist/src/services/cast.js +0 -127
- package/dist/src/services/chat-threads.js +0 -92
- package/dist/src/services/clip-records.js +0 -250
- package/dist/src/services/clip-search.js +0 -77
- package/dist/src/services/clip-vectors.js +0 -125
- package/dist/src/services/composition-sanitize.js +0 -124
- package/dist/src/services/composition-watch.js +0 -79
- package/dist/src/services/elevenlabs.js +0 -222
- package/dist/src/services/file-directory.js +0 -117
- package/dist/src/services/fork-access.js +0 -93
- package/dist/src/services/fork-manifest.js +0 -42
- package/dist/src/services/ghostcut.js +0 -179
- package/dist/src/services/hyperframes.js +0 -3654
- package/dist/src/services/job-capacity.js +0 -14
- package/dist/src/services/job-logs.js +0 -197
- package/dist/src/services/jobs.js +0 -136
- package/dist/src/services/local-dynamo.js +0 -0
- package/dist/src/services/media-processing.js +0 -766
- package/dist/src/services/primitive-media-lambda.js +0 -280
- package/dist/src/services/providers.js +0 -2748
- package/dist/src/services/rate-limits.js +0 -262
- package/dist/src/services/scene-annotations.js +0 -32
- package/dist/src/services/serverless-auth.js +0 -382
- package/dist/src/services/serverless-jobs.js +0 -1084
- package/dist/src/services/serverless-provider-keys.js +0 -409
- package/dist/src/services/serverless-records.js +0 -1515
- package/dist/src/services/serverless-template-configs.js +0 -75
- package/dist/src/services/storage.js +0 -461
- package/dist/src/services/swipe-customize.js +0 -437
- package/dist/src/services/template-certification.js +0 -413
- package/dist/src/services/template-loader.js +0 -99
- package/dist/src/services/template-runtime-bundles.js +0 -217
- package/dist/src/services/template-sources.js +0 -1017
- package/dist/src/services/upstream.js +0 -248
- package/dist/src/services/video-normalization.js +0 -2
- package/dist/src/services/webhooks.js +0 -62
- package/dist/src/template-editor-pages.js +0 -2576
- package/dist/src/template-editor-shell.js +0 -2893
- package/dist/src/template-sdk.js +0 -4
- package/dist/src/worker.js +0 -17
- package/public/assets/discover-client-app.js +0 -1
- package/public/assets/file-directory-app.js +0 -3
- package/public/assets/homepage-app.js +0 -54
- package/public/assets/homepage-client-app.js +0 -80
- package/public/assets/page-runtime-client-app.js +0 -94
- package/public/assets/placeholders/scene-placeholder.png +0 -0
- package/src/assets/SELLING_AWARENESS_STAGES.md +0 -579
- package/src/assets/SELLING_WITH_HOOKS.md +0 -377
- package/src/assets/SELLING_WITH_VSLS.md +0 -606
- package/src/assets/favicon.ico +0 -0
- package/src/assets/logo-vidfarm.png +0 -0
package/dist/src/devcli/clips.js
CHANGED
|
@@ -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({
|
|
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
|
|
1001
|
-
* (or GEMINI_API_KEY/OPENAI_API_KEY env). Returns
|
|
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 —
|
|
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
|
-
|
|
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(
|
|
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
|
|
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.
|
|
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
|
|
12
|
-
"dist/src
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"src/
|
|
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
|
-
"
|
|
20
|
-
"
|
|
37
|
+
"!readme.secret.md",
|
|
38
|
+
"!**/*.secret.*"
|
|
21
39
|
],
|
|
22
40
|
"publishConfig": {
|
|
23
|
-
"access": "
|
|
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"
|