@mevdragon/vidfarm-devcli 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/vidfarm-media/SKILL.md +3 -3
- package/SKILL.director.md +20 -20
- package/SKILL.platform.md +4 -4
- package/dist/src/app.js +301 -45
- package/dist/src/cli.js +11 -10
- package/dist/src/devcli/clips.js +64 -64
- package/dist/src/reskin/agency-page.js +214 -170
- package/dist/src/reskin/calendar-page.js +502 -187
- package/dist/src/reskin/chat-page.js +470 -277
- package/dist/src/reskin/discover-page.js +988 -272
- package/dist/src/reskin/document.js +602 -13
- package/dist/src/reskin/help-page.js +138 -100
- package/dist/src/reskin/index-page.js +1 -1
- package/dist/src/reskin/inpaint-page.js +541 -0
- package/dist/src/reskin/job-runs-page.js +355 -127
- package/dist/src/reskin/library-page.js +435 -262
- package/dist/src/reskin/login-page.js +118 -81
- package/dist/src/reskin/pricing-page.js +195 -166
- package/dist/src/reskin/settings-page.js +520 -186
- package/dist/src/reskin/theme.js +106 -9
- package/package.json +1 -1
package/dist/src/devcli/clips.js
CHANGED
|
@@ -14,9 +14,9 @@ import { createInterface } from "node:readline/promises";
|
|
|
14
14
|
import { createIdV7 } from "../lib/ids.js";
|
|
15
15
|
import { buildEffectiveGuidance, ClipModelClient, detectLocalAgent, detectScenes, estimateScanCostFromScenes, fitScenesToDurationBand, formatCostEstimate, hasFfmpeg, LocalAgentClipClient, normalizeAspect, normalizeCropFocus, normalizeWindows, parseClipHuntPrompt, parseTimeRanges, probeVideo, resolveDurationBand, scanVideo, searchClips } from "../services/clip-curation/index.js";
|
|
16
16
|
import { ClipStore } from "./clip-store.js";
|
|
17
|
-
export const CLIPS_HELP = `vidfarm
|
|
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
20
|
--provider <p> agent | gemini | openai | openrouter
|
|
21
21
|
Default: agent when a local claude/codex CLI is installed
|
|
22
22
|
(your subscription, no API key), else gemini.
|
|
@@ -27,8 +27,8 @@ export const CLIPS_HELP = `vidfarm clips — build & search a local clip library
|
|
|
27
27
|
Inline hints are honored too: "between 12:30 and 15:45",
|
|
28
28
|
"30 sec clips", "vertical", "no captions".
|
|
29
29
|
--range "MM:SS-MM:SS" Only hunt inside this source range (repeatable / comma-separated)
|
|
30
|
-
--duration <sec> Target
|
|
31
|
-
--aspect <a> Crop
|
|
30
|
+
--duration <sec> Target raw length as a SOFT band (10→5-20s, 30→20-40s, …)
|
|
31
|
+
--aspect <a> Crop raws to 9:16 | 16:9 | 4:3 | 1:1 (or vertical/horizontal/square)
|
|
32
32
|
--crop-focus <f> center | top | bottom | left | right (default: center)
|
|
33
33
|
--no-text Prefer scenes WITHOUT burned-in captions/on-screen text
|
|
34
34
|
(scene selection — NEVER GhostCut on the long-form source)
|
|
@@ -52,25 +52,25 @@ export const CLIPS_HELP = `vidfarm clips — build & search a local clip library
|
|
|
52
52
|
--embed-provider gemini|openai --embed-key <key> Embeddings for agent/openrouter scans
|
|
53
53
|
--home <dir> Library dir (default: ~/.vidfarm or VIDFARM_HOME)
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
--source <id> Only
|
|
55
|
+
raws list List raws in the library
|
|
56
|
+
--source <id> Only raws from one source video
|
|
57
57
|
--limit <n> | --json
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
raws search "<natural language>" Hybrid structured + semantic search
|
|
60
60
|
--limit <n> | --json | --gemini-key <key>
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
raws match "<text>" Top library raws to REPLACE a scene (vector search)
|
|
63
63
|
--criteria '<json>' Match a structured SplicingCriteria instead of text
|
|
64
64
|
--scenes <annotations.json> Batch: match every scene of a decomposed video
|
|
65
65
|
(array of scene annotations, or {annotations|scenes:[…]})
|
|
66
66
|
--limit <n> | --json
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
raws preset list List built-in + saved presets
|
|
69
|
+
raws preset run <name> Run a preset query [--limit <n>] [--json]
|
|
70
|
+
raws preset save <name> --from-query '<json | natural language>'
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
raws sources List scanned source videos
|
|
73
|
+
raws export <raw-ids...> --to <dir> Copy raw mp4s (+ metadata.json) out
|
|
74
74
|
`;
|
|
75
75
|
export async function runClipsCommand(argv) {
|
|
76
76
|
const sub = argv[0];
|
|
@@ -96,7 +96,7 @@ export async function runClipsCommand(argv) {
|
|
|
96
96
|
case "-h":
|
|
97
97
|
return runOverview();
|
|
98
98
|
default:
|
|
99
|
-
console.error(`Unknown
|
|
99
|
+
console.error(`Unknown raws subcommand: ${sub}\n`);
|
|
100
100
|
console.log(CLIPS_HELP);
|
|
101
101
|
process.exitCode = 1;
|
|
102
102
|
}
|
|
@@ -106,8 +106,8 @@ function runOverview() {
|
|
|
106
106
|
const store = new ClipStore();
|
|
107
107
|
const stats = store.stats();
|
|
108
108
|
store.close();
|
|
109
|
-
console.log(`vidfarm
|
|
110
|
-
console.log(` ${stats.clips}
|
|
109
|
+
console.log(`vidfarm raws — library at ${resolveHome()}`);
|
|
110
|
+
console.log(` ${stats.clips} raws · ${stats.sources} source videos · ${stats.presets} presets\n`);
|
|
111
111
|
console.log(CLIPS_HELP);
|
|
112
112
|
}
|
|
113
113
|
// ── clips scan ──────────────────────────────────────────────────────────────
|
|
@@ -186,12 +186,12 @@ async function runScan(argv) {
|
|
|
186
186
|
}
|
|
187
187
|
const videoArg = positionals[0];
|
|
188
188
|
if (!videoArg)
|
|
189
|
-
throw new Error("Usage: vidfarm
|
|
189
|
+
throw new Error("Usage: vidfarm raws scan <video-path> [--range MM:SS-MM:SS] [--duration 30] [--aspect 9:16] [--dry-run]");
|
|
190
190
|
const videoPath = path.resolve(process.cwd(), videoArg);
|
|
191
191
|
if (!existsSync(videoPath))
|
|
192
192
|
throw new Error(`No such video: ${videoPath}`);
|
|
193
193
|
if (!(await hasFfmpeg())) {
|
|
194
|
-
throw new Error("ffmpeg is required for `
|
|
194
|
+
throw new Error("ffmpeg is required for `raws scan` but was not found (bundled ffmpeg-static missing and none on PATH).");
|
|
195
195
|
}
|
|
196
196
|
// ── Model resolution: LOCAL-FIRST ─────────────────────────────────────────
|
|
197
197
|
// 1. --provider agent (or no --provider + a local claude/codex CLI installed)
|
|
@@ -217,24 +217,24 @@ async function runScan(argv) {
|
|
|
217
217
|
const embedding = provider === "agent"
|
|
218
218
|
? resolveAgentEmbeddingConfig(values)
|
|
219
219
|
: apiKey ? resolveEmbeddingConfig(provider, apiKey, values) : null;
|
|
220
|
-
console.log(`[
|
|
220
|
+
console.log(`[raws] probing ${path.basename(videoPath)} …`);
|
|
221
221
|
const probe = await probeVideo(videoPath);
|
|
222
|
-
console.log(`[
|
|
222
|
+
console.log(`[raws] ${fmtDuration(probe.duration_sec)} · ${probe.width ?? "?"}x${probe.height ?? "?"} · ${probe.codec ?? "?"}`);
|
|
223
223
|
const windows = normalizeWindows(rawWindows, probe.duration_sec);
|
|
224
224
|
if (rawWindows.length && !windows.length) {
|
|
225
225
|
throw new Error("--range produced no usable window inside the video's duration.");
|
|
226
226
|
}
|
|
227
227
|
if (windows.length) {
|
|
228
|
-
console.log(`[
|
|
228
|
+
console.log(`[raws] hunting only ${windows.map((w) => `${fmtClock(w.start_sec)}–${fmtClock(w.end_sec)}`).join(", ")} (${fmtDuration(windows.reduce((a, w) => a + w.end_sec - w.start_sec, 0))} of ${fmtDuration(probe.duration_sec)}).`);
|
|
229
229
|
}
|
|
230
230
|
if (durationBand) {
|
|
231
|
-
console.log(`[
|
|
231
|
+
console.log(`[raws] target clip length ~${durationBand.target_sec}s (soft range ${durationBand.min_sec}–${durationBand.max_sec}s).`);
|
|
232
232
|
}
|
|
233
233
|
if (aspect && aspect !== "original")
|
|
234
|
-
console.log(`[
|
|
234
|
+
console.log(`[raws] cropping clips to ${aspect} (${cropFocus}).`);
|
|
235
235
|
if (avoidText)
|
|
236
|
-
console.log(`[
|
|
237
|
-
console.log(`[
|
|
236
|
+
console.log(`[raws] preferring scenes without on-screen text/captions (selection only — GhostCut is never run on the source).`);
|
|
237
|
+
console.log(`[raws] detecting scenes …`);
|
|
238
238
|
const detected = await detectScenes(videoPath, { durationSec: probe.duration_sec, windows, ...sceneOptions });
|
|
239
239
|
const scenes = fitScenesToDurationBand(detected, durationBand);
|
|
240
240
|
if (scenes.length === 0)
|
|
@@ -247,13 +247,13 @@ async function runScan(argv) {
|
|
|
247
247
|
embedding,
|
|
248
248
|
tier
|
|
249
249
|
});
|
|
250
|
-
console.log(`[
|
|
250
|
+
console.log(`[raws] evaluating scenes with your local ${client.tagModelId} subscription (no API cost).`);
|
|
251
251
|
}
|
|
252
252
|
else if (apiKey) {
|
|
253
253
|
client = new ClipModelClient({ provider, apiKey, tier, embedding });
|
|
254
254
|
}
|
|
255
255
|
if (client && !client.canEmbed) {
|
|
256
|
-
console.warn(`[
|
|
256
|
+
console.warn(`[raws] no embedding provider for "${provider}" — raws will be structured-searchable only.`);
|
|
257
257
|
console.warn(` Add --embed-provider gemini|openai --embed-key <key> for semantic search.`);
|
|
258
258
|
}
|
|
259
259
|
// Cost estimate: local-agent scans ride the user's subscription, so only the
|
|
@@ -279,7 +279,7 @@ async function runScan(argv) {
|
|
|
279
279
|
console.log(` Guidance: "${guidance.replace(/\n/g, " · ")}"`);
|
|
280
280
|
console.log("");
|
|
281
281
|
if (values["dry-run"]) {
|
|
282
|
-
console.log("[
|
|
282
|
+
console.log("[raws] --dry-run: not scanning. Re-run without --dry-run to build the library.");
|
|
283
283
|
return;
|
|
284
284
|
}
|
|
285
285
|
// Actually scanning now — an evaluator is required.
|
|
@@ -293,7 +293,7 @@ async function runScan(argv) {
|
|
|
293
293
|
if (estimate && estimate.usd > 1 && !values.yes) {
|
|
294
294
|
const ok = await confirm(`This scan is estimated at $${estimate.usd.toFixed(2)}. Proceed? [y/N] `);
|
|
295
295
|
if (!ok) {
|
|
296
|
-
console.log("[
|
|
296
|
+
console.log("[raws] aborted.");
|
|
297
297
|
return;
|
|
298
298
|
}
|
|
299
299
|
}
|
|
@@ -301,7 +301,7 @@ async function runScan(argv) {
|
|
|
301
301
|
if (activeClient.embeddingModelId) {
|
|
302
302
|
const existing = store.getLibraryEmbeddingModel();
|
|
303
303
|
if (existing && existing !== activeClient.embeddingModelId) {
|
|
304
|
-
console.warn(`[
|
|
304
|
+
console.warn(`[raws] WARNING: library was built with "${existing}" embeddings but this scan uses "${activeClient.embeddingModelId}". Semantic search across them will be unreliable.`);
|
|
305
305
|
}
|
|
306
306
|
else {
|
|
307
307
|
store.rememberEmbeddingModel(activeClient.embeddingModelId);
|
|
@@ -319,7 +319,7 @@ async function runScan(argv) {
|
|
|
319
319
|
reencodeClips: true,
|
|
320
320
|
crop: aspect && aspect !== "original" ? { aspect, focus: cropFocus } : undefined
|
|
321
321
|
};
|
|
322
|
-
console.log(`[
|
|
322
|
+
console.log(`[raws] scanning ${scenes.length} scenes (${provider}/${activeClient.tagModelId}, ${concurrency}x) …`);
|
|
323
323
|
let errors = 0;
|
|
324
324
|
const started = timestamp();
|
|
325
325
|
const result = await scanVideo({
|
|
@@ -333,10 +333,10 @@ async function runScan(argv) {
|
|
|
333
333
|
concurrency,
|
|
334
334
|
onRefine: (r) => {
|
|
335
335
|
if (r.refined) {
|
|
336
|
-
console.log(`[
|
|
336
|
+
console.log(`[raws] guided segmentation: ${scenes.length} → ${r.kept} raws (dropped ${r.dropped}, merged ${r.merged}).`);
|
|
337
337
|
}
|
|
338
338
|
else if (r.note) {
|
|
339
|
-
console.log(`[
|
|
339
|
+
console.log(`[raws] guided segmentation skipped: ${r.note}.`);
|
|
340
340
|
}
|
|
341
341
|
},
|
|
342
342
|
onClip: (clip, i, total) => {
|
|
@@ -365,10 +365,10 @@ async function runScan(argv) {
|
|
|
365
365
|
/* ignore */
|
|
366
366
|
}
|
|
367
367
|
store.close();
|
|
368
|
-
console.log(`\n[
|
|
369
|
-
console.log(`
|
|
370
|
-
console.log(` db
|
|
371
|
-
console.log(` try: vidfarm
|
|
368
|
+
console.log(`\n[raws] done in ${elapsed(started)}: ${result.clips.length} raws${errors ? `, ${errors} scene(s) failed` : ""}.`);
|
|
369
|
+
console.log(` raws → ${store.paths.clipsDir}`);
|
|
370
|
+
console.log(` db → ${store.paths.db}`);
|
|
371
|
+
console.log(` try: vidfarm raws search "…" | vidfarm raws preset run "funny reaction gifs"`);
|
|
372
372
|
}
|
|
373
373
|
// ── clips scan --cloud (explicit backup: the deployed pipeline) ─────────────
|
|
374
374
|
// Uploads the local file into the user's temp folder (30-day TTL, auto-removed)
|
|
@@ -384,13 +384,13 @@ async function runScanCloud(input) {
|
|
|
384
384
|
let fileName;
|
|
385
385
|
if (!input.sourceUrl) {
|
|
386
386
|
if (!input.videoArg)
|
|
387
|
-
throw new Error("Usage: vidfarm
|
|
387
|
+
throw new Error("Usage: vidfarm raws scan --cloud <video-path> | --cloud --url <video-url>");
|
|
388
388
|
const videoPath = path.resolve(process.cwd(), input.videoArg);
|
|
389
389
|
if (!existsSync(videoPath))
|
|
390
390
|
throw new Error(`No such video: ${videoPath}`);
|
|
391
391
|
fileName = path.basename(videoPath);
|
|
392
392
|
const sizeBytes = statSync(videoPath).size;
|
|
393
|
-
console.log(`[
|
|
393
|
+
console.log(`[raws] uploading ${fileName} (${(sizeBytes / (1024 * 1024)).toFixed(1)} MB) to your temp folder (auto-deletes in 30 days) …`);
|
|
394
394
|
const presignRes = await fetch(`${baseUrl}/api/v1/user/me/temporary-files/presign`, {
|
|
395
395
|
method: "POST",
|
|
396
396
|
headers,
|
|
@@ -428,8 +428,8 @@ async function runScanCloud(input) {
|
|
|
428
428
|
}
|
|
429
429
|
tempFileId = presign.file_id;
|
|
430
430
|
}
|
|
431
|
-
console.log(`[
|
|
432
|
-
const scanRes = await fetch(`${baseUrl}/
|
|
431
|
+
console.log(`[raws] starting cloud hunt …`);
|
|
432
|
+
const scanRes = await fetch(`${baseUrl}/raws/scan`, {
|
|
433
433
|
method: "POST",
|
|
434
434
|
headers,
|
|
435
435
|
body: JSON.stringify({
|
|
@@ -450,28 +450,28 @@ async function runScanCloud(input) {
|
|
|
450
450
|
if (!scanRes.ok || !scan.scan_id) {
|
|
451
451
|
throw new Error(`Cloud scan failed to start (${scanRes.status}): ${scan.error ?? "unexpected response"}`);
|
|
452
452
|
}
|
|
453
|
-
console.log(`[
|
|
453
|
+
console.log(`[raws] scan ${scan.scan_id} ${scan.status ?? "queued"}${scan.tracer ? ` (tracer ${scan.tracer})` : ""}.`);
|
|
454
454
|
if (scan.compute_estimate?.estimated_charge_usd != null) {
|
|
455
|
-
console.log(`[
|
|
455
|
+
console.log(`[raws] estimated AWS compute charge ~$${scan.compute_estimate.estimated_charge_usd.toFixed(4)} (AI runs on your saved provider keys).`);
|
|
456
456
|
}
|
|
457
457
|
// Async by design — poll until the pipeline finishes.
|
|
458
458
|
const started = timestamp();
|
|
459
459
|
for (;;) {
|
|
460
460
|
await new Promise((r) => setTimeout(r, 5000));
|
|
461
|
-
const pollRes = await fetch(`${baseUrl}/
|
|
461
|
+
const pollRes = await fetch(`${baseUrl}/raws/scan/${encodeURIComponent(scan.scan_id)}`, { headers });
|
|
462
462
|
if (!pollRes.ok)
|
|
463
463
|
continue;
|
|
464
464
|
const poll = await pollRes.json();
|
|
465
465
|
const status = poll.source?.status ?? poll.scan?.status ?? "running";
|
|
466
466
|
if (status === "complete") {
|
|
467
|
-
console.log(`\n[
|
|
468
|
-
console.log(` browse → ${baseUrl}/
|
|
467
|
+
console.log(`\n[raws] cloud hunt done in ${elapsed(started)}: ${poll.source?.clip_count ?? 0} raws.`);
|
|
468
|
+
console.log(` browse → ${baseUrl}/library/raws`);
|
|
469
469
|
return;
|
|
470
470
|
}
|
|
471
471
|
if (status === "failed") {
|
|
472
472
|
throw new Error(`Cloud hunt failed: ${poll.scan?.error ?? "unknown error"}`);
|
|
473
473
|
}
|
|
474
|
-
process.stdout.write(`\r[
|
|
474
|
+
process.stdout.write(`\r[raws] ${status} … ${elapsed(started)} `);
|
|
475
475
|
}
|
|
476
476
|
}
|
|
477
477
|
/**
|
|
@@ -523,13 +523,13 @@ async function runList(argv) {
|
|
|
523
523
|
return;
|
|
524
524
|
}
|
|
525
525
|
if (clips.length === 0) {
|
|
526
|
-
console.log("No
|
|
526
|
+
console.log("No raws yet. Run: vidfarm raws scan <video-path>");
|
|
527
527
|
return;
|
|
528
528
|
}
|
|
529
529
|
for (const clip of clips) {
|
|
530
530
|
console.log(`${clip.clip_id.slice(0, 14)} ${fmtRange(clip)} ${clip.source_filename} ${summarizeTags(clip)}`);
|
|
531
531
|
}
|
|
532
|
-
console.log(`\n${clips.length}
|
|
532
|
+
console.log(`\n${clips.length} raws.`);
|
|
533
533
|
}
|
|
534
534
|
// ── clips search ────────────────────────────────────────────────────────────
|
|
535
535
|
async function runSearch(argv) {
|
|
@@ -548,12 +548,12 @@ async function runSearch(argv) {
|
|
|
548
548
|
});
|
|
549
549
|
const query = positionals.join(" ").trim();
|
|
550
550
|
if (!query)
|
|
551
|
-
throw new Error('Usage: vidfarm
|
|
551
|
+
throw new Error('Usage: vidfarm raws search "<natural language>"');
|
|
552
552
|
const limit = clampInt(values.limit, 10, 1, 200);
|
|
553
553
|
const store = new ClipStore(values.home);
|
|
554
554
|
if (store.stats().clips === 0) {
|
|
555
555
|
store.close();
|
|
556
|
-
console.log("Library is empty — scan a video first: vidfarm
|
|
556
|
+
console.log("Library is empty — scan a video first: vidfarm raws scan <video-path>");
|
|
557
557
|
return;
|
|
558
558
|
}
|
|
559
559
|
const provider = normalizeProvider(values.provider);
|
|
@@ -566,7 +566,7 @@ async function runSearch(argv) {
|
|
|
566
566
|
if (client.canEmbed) {
|
|
567
567
|
const libModel = store.getLibraryEmbeddingModel();
|
|
568
568
|
if (libModel && client.embeddingModelId && libModel !== client.embeddingModelId) {
|
|
569
|
-
console.warn(`[
|
|
569
|
+
console.warn(`[raws] note: library uses "${libModel}" embeddings but you're searching with "${client.embeddingModelId}" — semantic re-rank may be off; pass --provider to match.`);
|
|
570
570
|
}
|
|
571
571
|
queryEmbedding = await client.embedQuery(criteria.semantic_text ?? query);
|
|
572
572
|
}
|
|
@@ -575,11 +575,11 @@ async function runSearch(argv) {
|
|
|
575
575
|
// Local-first: no key, but a local agent CLI can still turn the natural-
|
|
576
576
|
// language query into a structured filter (vector re-rank needs a key).
|
|
577
577
|
const agent = new LocalAgentClipClient({});
|
|
578
|
-
console.log(`[
|
|
578
|
+
console.log(`[raws] structuring query with your local ${agent.tagModelId} …`);
|
|
579
579
|
criteria = await agent.naturalLanguageToCriteria(query);
|
|
580
580
|
}
|
|
581
581
|
else {
|
|
582
|
-
console.warn(`[
|
|
582
|
+
console.warn(`[raws] no ${provider} key or local agent CLI — structured/keyword search only.`);
|
|
583
583
|
}
|
|
584
584
|
const t0 = timestamp();
|
|
585
585
|
const hits = store.search({ criteria, queryEmbedding, limit });
|
|
@@ -627,7 +627,7 @@ async function runMatch(argv) {
|
|
|
627
627
|
const store = new ClipStore(values.home);
|
|
628
628
|
if (store.stats().clips === 0) {
|
|
629
629
|
store.close();
|
|
630
|
-
console.log("Library is empty — scan a video first: vidfarm
|
|
630
|
+
console.log("Library is empty — scan a video first: vidfarm raws scan <video-path>");
|
|
631
631
|
return;
|
|
632
632
|
}
|
|
633
633
|
// Embed the match query in the SAME space the library was built with.
|
|
@@ -636,7 +636,7 @@ async function runMatch(argv) {
|
|
|
636
636
|
const embedKey = resolveProviderKey(embedProvider, values, { optional: true });
|
|
637
637
|
const client = embedKey ? new ClipModelClient({ provider: embedProvider, apiKey: embedKey }) : null;
|
|
638
638
|
if (!client)
|
|
639
|
-
console.warn(`[
|
|
639
|
+
console.warn(`[raws] no ${embedProvider} key — structured match only (set the key for vector re-rank).`);
|
|
640
640
|
if (values.scenes) {
|
|
641
641
|
const filePath = path.resolve(process.cwd(), values.scenes);
|
|
642
642
|
if (!existsSync(filePath))
|
|
@@ -670,7 +670,7 @@ async function runMatch(argv) {
|
|
|
670
670
|
console.log(JSON.stringify(output, null, 2));
|
|
671
671
|
return;
|
|
672
672
|
}
|
|
673
|
-
console.log(`Matched ${scenes.length} scenes against ${all.length}
|
|
673
|
+
console.log(`Matched ${scenes.length} scenes against ${all.length} raws:`);
|
|
674
674
|
for (const o of output) {
|
|
675
675
|
const rep = o.replaceability ? ` [replaceability: ${o.replaceability}]` : "";
|
|
676
676
|
const dur = typeof o.duration === "number" ? ` (${o.duration.toFixed(1)}s)` : "";
|
|
@@ -699,7 +699,7 @@ async function runMatch(argv) {
|
|
|
699
699
|
text = positionals.join(" ").trim();
|
|
700
700
|
if (!text) {
|
|
701
701
|
store.close();
|
|
702
|
-
throw new Error('Usage: vidfarm
|
|
702
|
+
throw new Error('Usage: vidfarm raws match "<text>" | --criteria \'<json>\' | --scenes <annotations.json>');
|
|
703
703
|
}
|
|
704
704
|
criteria = { semantic_text: text };
|
|
705
705
|
}
|
|
@@ -724,7 +724,7 @@ async function runPreset(argv) {
|
|
|
724
724
|
return runPresetRun(rest);
|
|
725
725
|
if (action === "save")
|
|
726
726
|
return runPresetSave(rest);
|
|
727
|
-
throw new Error("Usage: vidfarm
|
|
727
|
+
throw new Error("Usage: vidfarm raws preset <list|run <name>|save <name> --from-query '<...>'>");
|
|
728
728
|
}
|
|
729
729
|
async function runPresetList(argv) {
|
|
730
730
|
const { values } = parseArgs({ args: argv, options: { json: { type: "boolean" }, home: { type: "string" } } });
|
|
@@ -756,12 +756,12 @@ async function runPresetRun(argv) {
|
|
|
756
756
|
});
|
|
757
757
|
const name = positionals.join(" ").trim();
|
|
758
758
|
if (!name)
|
|
759
|
-
throw new Error("Usage: vidfarm
|
|
759
|
+
throw new Error("Usage: vidfarm raws preset run <name>");
|
|
760
760
|
const store = new ClipStore(values.home);
|
|
761
761
|
const preset = store.findPreset(name);
|
|
762
762
|
if (!preset) {
|
|
763
763
|
store.close();
|
|
764
|
-
throw new Error(`No preset "${name}". Try: vidfarm
|
|
764
|
+
throw new Error(`No preset "${name}". Try: vidfarm raws preset list`);
|
|
765
765
|
}
|
|
766
766
|
const limit = clampInt(values.limit, 10, 1, 200);
|
|
767
767
|
// Presets carry semantic_text; embed it if we have a key for the re-rank.
|
|
@@ -800,7 +800,7 @@ async function runPresetSave(argv) {
|
|
|
800
800
|
const name = positionals.join(" ").trim();
|
|
801
801
|
const fromQuery = values["from-query"];
|
|
802
802
|
if (!name || !fromQuery) {
|
|
803
|
-
throw new Error("Usage: vidfarm
|
|
803
|
+
throw new Error("Usage: vidfarm raws preset save <name> --from-query '<json | natural language>'");
|
|
804
804
|
}
|
|
805
805
|
let criteria;
|
|
806
806
|
const asJson = tryParseJson(fromQuery);
|
|
@@ -857,7 +857,7 @@ async function runExport(argv) {
|
|
|
857
857
|
const ids = positionals;
|
|
858
858
|
const to = values.to;
|
|
859
859
|
if (ids.length === 0 || !to) {
|
|
860
|
-
throw new Error("Usage: vidfarm
|
|
860
|
+
throw new Error("Usage: vidfarm raws export <raw-ids...> --to <dir>");
|
|
861
861
|
}
|
|
862
862
|
const outDir = path.resolve(process.cwd(), to);
|
|
863
863
|
mkdirSync(outDir, { recursive: true });
|
|
@@ -885,7 +885,7 @@ async function runExport(argv) {
|
|
|
885
885
|
store.close();
|
|
886
886
|
const { writeFileSync } = await import("node:fs");
|
|
887
887
|
writeFileSync(path.join(outDir, "clips.metadata.json"), JSON.stringify(exported, null, 2));
|
|
888
|
-
console.log(`\nExported ${exported.length}
|
|
888
|
+
console.log(`\nExported ${exported.length} raws + clips.metadata.json → ${outDir}`);
|
|
889
889
|
}
|
|
890
890
|
// ── helpers ─────────────────────────────────────────────────────────────────
|
|
891
891
|
function normalizeProvider(value) {
|