@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
@@ -1,6 +0,0 @@
1
- import { renderDevApp as renderDevAppLegacy } from "./dev-app-legacy.js";
2
- import { renderLegacyPageAsReact } from "./react-page-shell.js";
3
- export function renderDevApp(...args) {
4
- return renderLegacyPageAsReact(renderDevAppLegacy(...args));
5
- }
6
- //# sourceMappingURL=dev-app.js.map
@@ -1,140 +0,0 @@
1
- // `vidfarm migrate-local` — bridge the legacy standalone SQLite clip library
2
- // (~/.vidfarm/clips.db, sqlite-vec) into the UNIFIED local backend that
3
- // `vidfarm serve` + the file directory read (disk-backed clipRecords + storage
4
- // under ~/.vidfarm/data). After migrating, every locally-hunted clip is
5
- // browsable at /raws and semantically searchable with the SAME 768-dim vectors
6
- // (identical model + text builder), so nothing is re-embedded.
7
- //
8
- // Idempotent: putClip is an upsert keyed by clip_id, and a `migrated.json` marker
9
- // stops the lazy auto-offer from nagging. The SQLite db is left untouched.
10
- import path from "node:path";
11
- import { existsSync, writeFileSync } from "node:fs";
12
- import { parseArgs } from "node:util";
13
- const GREEN = "\x1b[32m";
14
- const DIM = "\x1b[2m";
15
- const BOLD = "\x1b[1m";
16
- const RESET = "\x1b[0m";
17
- /** Slugify a source filename into the folder clips group under (mirrors the
18
- * serve-box source-slug so migrated clips land in the same /raws/<slug> folder). */
19
- function sourceSlug(filename) {
20
- return String(filename || "")
21
- .toLowerCase()
22
- .replace(/\.[a-z0-9]+$/i, "")
23
- .replace(/[^a-z0-9]+/g, "-")
24
- .replace(/^-+|-+$/g, "")
25
- .slice(0, 60);
26
- }
27
- export async function runMigrateLocalCommand(argv) {
28
- const { values } = parseArgs({
29
- args: argv,
30
- allowPositionals: true,
31
- options: {
32
- home: { type: "string" },
33
- "dry-run": { type: "boolean", default: false },
34
- force: { type: "boolean", default: false }
35
- }
36
- });
37
- const { withLocalBackend, resolveLocalHome, resolveLocalDataDir } = await import("./local-backend.js");
38
- const home = resolveLocalHome(values.home);
39
- const dbPath = path.join(home, "clips.db");
40
- if (!existsSync(dbPath)) {
41
- console.log(`No local SQLite clip library at ${DIM}${dbPath}${RESET} — nothing to migrate.`);
42
- return;
43
- }
44
- const markerPath = path.join(resolveLocalDataDir(values.home), "migrated.json");
45
- if (existsSync(markerPath) && !values.force) {
46
- console.log(`Already migrated (${DIM}${markerPath}${RESET}). Re-run with ${BOLD}--force${RESET} to migrate again.`);
47
- return;
48
- }
49
- // Read the legacy SQLite store (read-only) — the ClipStore class is retained
50
- // solely for this bridge.
51
- const { ClipStore } = await import("./clip-store.js");
52
- const store = new ClipStore(home);
53
- const clips = store.listClips();
54
- store.attachEmbeddings(clips);
55
- const sources = store.listSourceVideos();
56
- const presets = store.listPresets().filter((p) => !p.builtin);
57
- const libModel = store.getLibraryEmbeddingModel();
58
- if (clips.length === 0 && sources.length === 0) {
59
- store.close();
60
- console.log("The local SQLite library is empty — nothing to migrate.");
61
- return;
62
- }
63
- if (values["dry-run"]) {
64
- store.close();
65
- console.log(`${BOLD}migrate-local (dry run)${RESET} ${DIM}${home}${RESET}`);
66
- console.log(` ${clips.length} clip${clips.length === 1 ? "" : "s"} · ${sources.length} source${sources.length === 1 ? "" : "s"} · ${presets.length} custom preset${presets.length === 1 ? "" : "s"}`);
67
- console.log(` embedding model: ${libModel ?? DIM + "(none — clips keyword-only)" + RESET}`);
68
- console.log(` → would write into the unified backend at ${DIM}${resolveLocalDataDir(values.home)}${RESET}`);
69
- return;
70
- }
71
- // Boot the local backend (sets drivers + resolves the stable owner id), then
72
- // reach the disk-backed services directly.
73
- const backend = await withLocalBackend({ home: values.home });
74
- const owner = backend.ownerId;
75
- const { clipRecords } = await import("../services/clip-records.js");
76
- const { storage } = await import("../services/storage.js");
77
- const now = new Date().toISOString();
78
- // Sources first (so /raws folder grouping + source lookups resolve).
79
- let sourceCount = 0;
80
- for (const s of sources) {
81
- await clipRecords.putSource({
82
- source_video_id: s.source_video_id,
83
- owner_id: owner,
84
- filename: s.filename,
85
- raw_s3_uri: s.source_path ?? undefined,
86
- duration_sec: s.duration_sec ?? undefined,
87
- clip_count: s.clip_count ?? 0,
88
- tier: s.tier ?? undefined,
89
- status: "complete",
90
- created_at: s.scanned_at ?? now,
91
- updated_at: s.scanned_at ?? now
92
- });
93
- sourceCount += 1;
94
- }
95
- // Clips: copy the mp4/thumb bytes into the storage driver at the canonical
96
- // unified keys, then upsert the record carrying its embedding verbatim.
97
- let clipCount = 0;
98
- let withVideo = 0;
99
- for (const clip of clips) {
100
- const clipId = clip.clip_id;
101
- const srcMp4 = path.join(store.paths.clipsDir, `${clipId}.mp4`);
102
- const clipKey = `clips/${owner}/${clipId}.mp4`;
103
- if (existsSync(srcMp4)) {
104
- await storage.putFile(clipKey, srcMp4, "video/mp4");
105
- withVideo += 1;
106
- }
107
- const srcThumb = path.join(store.paths.thumbsDir, `${clipId}.jpg`);
108
- let thumbKey = "";
109
- if (existsSync(srcThumb)) {
110
- thumbKey = `thumbs/${owner}/${clipId}.jpg`;
111
- await storage.putFile(thumbKey, srcThumb, "image/jpeg");
112
- }
113
- const unified = {
114
- ...clip,
115
- owner_id: owner,
116
- file_path: clipKey,
117
- thumbnail_path: thumbKey,
118
- folder_path: clip.folder_path && clip.folder_path.trim() ? clip.folder_path : sourceSlug(clip.source_filename),
119
- // Vectors are the same 768-dim space; stamp the library model so cloud/
120
- // local search stays cosine-comparable.
121
- ...(clip.embedding && clip.embedding.length && !clip.embedding_model && libModel
122
- ? { embedding_model: libModel }
123
- : {})
124
- };
125
- await clipRecords.putClip(unified);
126
- clipCount += 1;
127
- }
128
- // Custom presets (built-ins are seeded on both sides already).
129
- let presetCount = 0;
130
- for (const p of presets) {
131
- await clipRecords.putPreset(owner, p);
132
- presetCount += 1;
133
- }
134
- store.close();
135
- writeFileSync(markerPath, JSON.stringify({ migrated_at: now, clips: clipCount, sources: sourceCount, presets: presetCount, embedding_model: libModel }, null, 2));
136
- console.log(`${GREEN}✓ Migrated the local clip library into the unified backend.${RESET}`);
137
- console.log(` ${clipCount} clip${clipCount === 1 ? "" : "s"} (${withVideo} with video) · ${sourceCount} source${sourceCount === 1 ? "" : "s"} · ${presetCount} preset${presetCount === 1 ? "" : "s"}`);
138
- console.log(` ${DIM}Browse them:${RESET} vidfarm directory ls /raws ${DIM}or${RESET} vidfarm serve → /library/raws`);
139
- }
140
- //# sourceMappingURL=migrate-local.js.map
@@ -1,368 +0,0 @@
1
- // `vidfarm sync` — move files between the LOCAL disk backend (what `vidfarm
2
- // serve` + the file directory read) and the CLOUD (vidfarm.cc), with filepath
3
- // conflict resolution. This is how an AI agent hands local work up to the cloud
4
- // after a long local-first editing / clip-hunting session:
5
- //
6
- // vidfarm sync push local My Files → cloud (upload local work)
7
- // vidfarm sync pull cloud My Files → local (bring cloud assets down)
8
- // vidfarm sync push /files/brand scope to a subtree
9
- // --dry-run list the deltas + conflicts, transfer nothing
10
- // --on-conflict newest|skip|keep-both|local|cloud (default: prompt; agents = newest)
11
- //
12
- // Scope: the /files (My Files) root — the durable, vector-searchable per-user
13
- // asset store. Both sides share the canonical /files path, so "same path" is the
14
- // collision key. Raws (/raws) sync is a documented follow-up (it needs the
15
- // source-ingest pipeline, not a plain byte copy).
16
- import { parseArgs } from "node:util";
17
- import { homedir } from "node:os";
18
- import path from "node:path";
19
- import { createInterface } from "node:readline";
20
- const GREEN = "\x1b[32m";
21
- const YELLOW = "\x1b[33m";
22
- const RED = "\x1b[31m";
23
- const DIM = "\x1b[2m";
24
- const BOLD = "\x1b[1m";
25
- const RESET = "\x1b[0m";
26
- const DEFAULT_HOST = "https://vidfarm.cc";
27
- function authHeaders(apiKey) {
28
- const h = { accept: "application/json" };
29
- if (apiKey)
30
- h["vidfarm-api-key"] = apiKey;
31
- return h;
32
- }
33
- // ── transport: one request/upload/delete per space ──────────────────────────
34
- async function req(ctx, space, input) {
35
- if (space === "local") {
36
- const { localApiRequest } = await import("./local-backend.js");
37
- const r = await localApiRequest({ ...input, auth: { apiKey: ctx.apiKey }, home: ctx.home });
38
- return { status: r.status, ok: r.ok, json: r.json };
39
- }
40
- const url = new URL(input.path, ctx.host);
41
- for (const [k, v] of Object.entries(input.query ?? {}))
42
- if (v)
43
- url.searchParams.set(k, v);
44
- const headers = authHeaders(ctx.apiKey);
45
- let body;
46
- if (input.body !== undefined) {
47
- headers["content-type"] = "application/json";
48
- body = JSON.stringify(input.body);
49
- }
50
- const res = await fetch(url, { method: input.method.toUpperCase(), headers, body });
51
- const text = await res.text();
52
- let json = null;
53
- try {
54
- json = text ? JSON.parse(text) : null;
55
- }
56
- catch {
57
- json = null;
58
- }
59
- return { status: res.status, ok: res.ok, json };
60
- }
61
- /** Fetch the raw bytes of a file item from its own space. */
62
- async function readBytes(ctx, space, item) {
63
- if (!item.viewUrl)
64
- throw new Error(`No view URL for ${item.path}`);
65
- if (space === "local") {
66
- const { withLocalBackend } = await import("./local-backend.js");
67
- const backend = await withLocalBackend({ home: ctx.home, apiKey: ctx.apiKey });
68
- const u = new URL(item.viewUrl, "http://vidfarm.local");
69
- if (!isLocalFileViewPath(u.pathname)) {
70
- throw new Error(`Refusing local sync read outside Vidfarm file storage: ${item.path}`);
71
- }
72
- const res = await backend.app.request(u.pathname + u.search, { headers: authHeaders(ctx.apiKey) });
73
- if (!res.ok)
74
- throw new Error(`local read ${item.path} → HTTP ${res.status}`);
75
- return Buffer.from(await res.arrayBuffer());
76
- }
77
- const url = validateCloudFileViewUrl(ctx, item);
78
- const res = await fetch(url, { headers: authHeaders(ctx.apiKey) });
79
- if (!res.ok)
80
- throw new Error(`cloud read ${item.path} → HTTP ${res.status}`);
81
- return Buffer.from(await res.arrayBuffer());
82
- }
83
- function isLocalFileViewPath(pathname) {
84
- return pathname === "/api/v1/user/me/files/view" || pathname === "/template-media" || pathname.startsWith("/storage/");
85
- }
86
- function validateCloudFileViewUrl(ctx, item) {
87
- let url;
88
- try {
89
- url = new URL(item.viewUrl || "", ctx.host);
90
- }
91
- catch {
92
- throw new Error(`Invalid cloud view URL for ${item.path}`);
93
- }
94
- const host = new URL(ctx.host);
95
- const isSameOrigin = url.origin === host.origin;
96
- const isS3 = /^https:\/\/[^/]+\.s3[.-][a-z0-9-]+\.amazonaws\.com$/i.test(url.origin)
97
- || /^https:\/\/s3[.-][a-z0-9-]+\.amazonaws\.com$/i.test(url.origin)
98
- || /\.s3[.-][a-z0-9-]+\.amazonaws\.com$/i.test(url.hostname);
99
- if (isSameOrigin && isLocalFileViewPath(url.pathname))
100
- return url;
101
- if (isS3)
102
- return url;
103
- throw new Error(`Refusing cloud sync read from non-Vidfarm URL for ${item.path}`);
104
- }
105
- /** Upload bytes into a space's My Files at folderPath/fileName (multipart — the
106
- * one transport that works on both S3 and local storage). */
107
- async function uploadBytes(ctx, space, input) {
108
- const form = new FormData();
109
- const blob = new Blob([new Uint8Array(input.bytes)], { type: input.contentType || "application/octet-stream" });
110
- form.set("file", blob, input.fileName);
111
- if (input.folderPath)
112
- form.set("folder_path", input.folderPath);
113
- if (input.notes)
114
- form.set("notes", input.notes);
115
- const p = "/api/v1/user/me/attachments/upload";
116
- if (space === "local") {
117
- const { withLocalBackend } = await import("./local-backend.js");
118
- const backend = await withLocalBackend({ home: ctx.home, apiKey: ctx.apiKey });
119
- const res = await backend.app.request(p, { method: "POST", headers: authHeaders(ctx.apiKey), body: form });
120
- if (!res.ok)
121
- throw new Error(`local upload ${input.folderPath}/${input.fileName} → HTTP ${res.status}`);
122
- return;
123
- }
124
- const res = await fetch(new URL(p, ctx.host), { method: "POST", headers: authHeaders(ctx.apiKey), body: form });
125
- if (!res.ok)
126
- throw new Error(`cloud upload ${input.folderPath}/${input.fileName} → HTTP ${res.status} ${(await res.text()).slice(0, 200)}`);
127
- }
128
- async function deleteAttachment(ctx, space, id) {
129
- await req(ctx, space, { method: "DELETE", path: `/api/v1/user/me/attachments/${encodeURIComponent(id)}` }).catch(() => undefined);
130
- }
131
- // ── enumerate a root recursively as a flat file list ────────────────────────
132
- function toFileItem(raw) {
133
- const meta = raw?.meta ?? {};
134
- const updatedAtRaw = meta.updatedAt ?? meta.updated_at ?? raw.updatedAt ?? raw.createdAt ?? meta.createdAt;
135
- const updatedAt = updatedAtRaw ? Date.parse(String(updatedAtRaw)) : undefined;
136
- return {
137
- path: String(raw?.path ?? ""),
138
- name: String(raw?.name ?? ""),
139
- folderPath: String(raw?.folderPath ?? ""),
140
- id: raw?.id,
141
- contentType: raw?.contentType,
142
- sizeBytes: typeof raw?.sizeBytes === "number" ? raw.sizeBytes : undefined,
143
- viewUrl: raw?.viewUrl,
144
- updatedAt: Number.isFinite(updatedAt) ? updatedAt : undefined,
145
- notes: typeof meta.notes === "string" ? meta.notes : undefined
146
- };
147
- }
148
- async function enumerateFiles(ctx, space, rootPath) {
149
- const out = [];
150
- const queue = [rootPath];
151
- const seen = new Set();
152
- while (queue.length) {
153
- const dir = queue.shift();
154
- if (seen.has(dir))
155
- continue;
156
- seen.add(dir);
157
- const r = await req(ctx, space, { method: "GET", path: "/api/v1/user/me/directory", query: { path: dir, limit: "500" } });
158
- if (!r.ok)
159
- throw new Error(`${space} directory ls ${dir} → HTTP ${r.status}${r.json?.error ? ` (${r.json.error})` : ""}`);
160
- for (const f of Array.isArray(r.json?.folders) ? r.json.folders : []) {
161
- if (f?.path)
162
- queue.push(String(f.path));
163
- }
164
- for (const f of Array.isArray(r.json?.files) ? r.json.files : []) {
165
- out.push(toFileItem(f));
166
- }
167
- }
168
- return out;
169
- }
170
- async function resolveConflict(policy, src, dst, direction) {
171
- const srcIsLocal = direction === "push";
172
- switch (policy) {
173
- case "skip":
174
- return "skip";
175
- case "keep-both":
176
- return "keep-both";
177
- case "local":
178
- return srcIsLocal ? "transfer" : "skip";
179
- case "cloud":
180
- return srcIsLocal ? "skip" : "transfer";
181
- case "newest": {
182
- const s = src.updatedAt ?? 0;
183
- const d = dst.updatedAt ?? 0;
184
- if (s === d)
185
- return "skip";
186
- return s > d ? "transfer" : "skip";
187
- }
188
- case "prompt":
189
- default: {
190
- if (!process.stdin.isTTY) {
191
- // Non-interactive (AI agent): newest-wins.
192
- return resolveConflict("newest", src, dst, direction);
193
- }
194
- const answer = await prompt(` ${YELLOW}conflict${RESET} ${src.path}\n keep [${BOLD}s${RESET}ource=${srcIsLocal ? "local" : "cloud"}] · [${BOLD}k${RESET}eep target] · [${BOLD}b${RESET}oth] · [s${BOLD}K${RESET}ip]? `);
195
- const a = answer.trim().toLowerCase();
196
- if (a === "s" || a === "source")
197
- return "transfer";
198
- if (a === "b" || a === "both")
199
- return "keep-both";
200
- return "skip";
201
- }
202
- }
203
- }
204
- function prompt(q) {
205
- const rl = createInterface({ input: process.stdin, output: process.stdout });
206
- return new Promise((resolve) => rl.question(q, (a) => { rl.close(); resolve(a); }));
207
- }
208
- // Suffix a file name before its extension: "logo.png" → "logo (cloud).png".
209
- function suffixName(name, suffix) {
210
- const dot = name.lastIndexOf(".");
211
- if (dot <= 0)
212
- return `${name} (${suffix})`;
213
- return `${name.slice(0, dot)} (${suffix})${name.slice(dot)}`;
214
- }
215
- // ── command ─────────────────────────────────────────────────────────────────
216
- export async function runSyncCommand(argv) {
217
- const direction = argv[0];
218
- if (direction !== "push" && direction !== "pull") {
219
- console.log(SYNC_HELP);
220
- if (direction && direction !== "help" && direction !== "--help" && direction !== "-h")
221
- process.exitCode = 1;
222
- return;
223
- }
224
- const { values, positionals } = parseArgs({
225
- args: argv.slice(1),
226
- allowPositionals: true,
227
- options: {
228
- host: { type: "string", default: DEFAULT_HOST },
229
- "api-key": { type: "string" },
230
- home: { type: "string" },
231
- "dry-run": { type: "boolean", default: false },
232
- "on-conflict": { type: "string" }
233
- }
234
- });
235
- const policy = normalizePolicy(values["on-conflict"]);
236
- const ctx = {
237
- host: String(values.host ?? DEFAULT_HOST).replace(/\/+$/, ""),
238
- apiKey: values["api-key"] ?? process.env.VIDFARM_API_KEY,
239
- home: values.home
240
- };
241
- // Scope: a /files subtree (default the whole root).
242
- const rawRootPath = positionals[0] ?? "/files";
243
- const rootPath = normalizeSyncRoot(rawRootPath);
244
- if (!rootPath) {
245
- throw new Error(`sync currently covers the /files (My Files) root — got "${rawRootPath}". Raws sync is a follow-up.`);
246
- }
247
- const srcSpace = direction === "push" ? "local" : "cloud";
248
- const dstSpace = direction === "push" ? "cloud" : "local";
249
- const homeLabel = ctx.home ?? process.env.VIDFARM_HOME ?? path.join(homedir(), ".vidfarm");
250
- console.log(`${BOLD}sync ${direction}${RESET} ${DIM}${srcSpace} → ${dstSpace}${RESET} ${DIM}scope=${rootPath} · local=${homeLabel} · cloud=${ctx.host} · on-conflict=${policy}${values["dry-run"] ? " · DRY RUN" : ""}${RESET}\n`);
251
- const [srcFiles, dstFiles] = await Promise.all([enumerateFiles(ctx, srcSpace, rootPath), enumerateFiles(ctx, dstSpace, rootPath)]);
252
- const dstByPath = new Map(dstFiles.map((f) => [f.path, f]));
253
- let transferred = 0;
254
- let skipped = 0;
255
- let conflicts = 0;
256
- let inSync = 0;
257
- for (const src of srcFiles) {
258
- const dst = dstByPath.get(src.path);
259
- if (!dst) {
260
- // New file on the target → transfer.
261
- if (values["dry-run"]) {
262
- console.log(` ${GREEN}+ new${RESET} ${src.path}`);
263
- }
264
- else {
265
- await transfer(ctx, srcSpace, dstSpace, src, fileNameFromSyncPath(src.path));
266
- console.log(` ${GREEN}✓ sent${RESET} ${src.path}`);
267
- }
268
- transferred += 1;
269
- continue;
270
- }
271
- // Same path both sides. Equal size → treat as already in sync (cheap, no
272
- // byte compare); differing size → a real conflict.
273
- if (src.sizeBytes != null && dst.sizeBytes != null && src.sizeBytes === dst.sizeBytes) {
274
- inSync += 1;
275
- continue;
276
- }
277
- conflicts += 1;
278
- const resolution = await resolveConflict(policy, src, dst, direction);
279
- if (resolution === "skip") {
280
- console.log(` ${DIM}– skip${RESET} ${src.path} ${DIM}(conflict)${RESET}`);
281
- skipped += 1;
282
- continue;
283
- }
284
- if (values["dry-run"]) {
285
- console.log(` ${YELLOW}~ ${resolution === "keep-both" ? "both" : "over"}${RESET} ${src.path}`);
286
- transferred += 1;
287
- continue;
288
- }
289
- if (resolution === "keep-both") {
290
- const keepBothName = suffixName(fileNameFromSyncPath(src.path), srcSpace);
291
- await transfer(ctx, srcSpace, dstSpace, src, keepBothName);
292
- console.log(` ${YELLOW}✓ both${RESET} ${src.path} ${DIM}→ ${keepBothName}${RESET}`);
293
- }
294
- else {
295
- // Overwrite: drop the target copy first so we replace rather than dup.
296
- if (dst.id)
297
- await deleteAttachment(ctx, dstSpace, dst.id);
298
- await transfer(ctx, srcSpace, dstSpace, src, fileNameFromSyncPath(src.path));
299
- console.log(` ${GREEN}✓ over${RESET} ${src.path}`);
300
- }
301
- transferred += 1;
302
- }
303
- console.log(`\n ${transferred} transferred · ${inSync} already in sync · ${conflicts} conflict${conflicts === 1 ? "" : "s"} · ${skipped} skipped`);
304
- if (values["dry-run"])
305
- console.log(` ${DIM}(dry run — nothing was written)${RESET}`);
306
- }
307
- async function transfer(ctx, from, to, item, fileName) {
308
- if (!item.path.startsWith("/files/")) {
309
- throw new Error(`Refusing to sync item outside /files: ${item.path}`);
310
- }
311
- const folderPath = folderPathFromSyncPath(item.path);
312
- const bytes = await readBytes(ctx, from, item);
313
- await uploadBytes(ctx, to, {
314
- fileName,
315
- folderPath,
316
- contentType: item.contentType,
317
- notes: item.notes,
318
- bytes
319
- });
320
- }
321
- function normalizeSyncRoot(value) {
322
- const raw = value.trim();
323
- const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
324
- const normalized = path.posix.normalize(withSlash).replace(/\/+$/, "") || "/files";
325
- if (normalized === "/files" || normalized.startsWith("/files/"))
326
- return normalized;
327
- return null;
328
- }
329
- function fileNameFromSyncPath(value) {
330
- const name = path.posix.basename(value);
331
- if (!name || name === "." || name === ".." || name.includes("/")) {
332
- throw new Error(`Invalid sync file path: ${value}`);
333
- }
334
- return name;
335
- }
336
- function folderPathFromSyncPath(value) {
337
- if (!value.startsWith("/files/"))
338
- throw new Error(`Invalid sync file path: ${value}`);
339
- const dir = path.posix.dirname(value);
340
- if (dir === "/files")
341
- return "";
342
- if (!dir.startsWith("/files/"))
343
- throw new Error(`Invalid sync file path: ${value}`);
344
- return dir.slice("/files/".length);
345
- }
346
- function normalizePolicy(v) {
347
- const s = String(v ?? "").trim().toLowerCase();
348
- if (["newest", "skip", "keep-both", "local", "cloud"].includes(s))
349
- return s;
350
- if (s === "keepboth" || s === "both")
351
- return "keep-both";
352
- return "prompt";
353
- }
354
- const SYNC_HELP = `vidfarm sync — move My Files between the local disk backend and the cloud
355
-
356
- sync push [path] local → cloud (upload local work; default scope /files)
357
- sync pull [path] cloud → local (bring cloud assets down)
358
-
359
- --dry-run list deltas + conflicts, transfer nothing
360
- --on-conflict <policy> newest | skip | keep-both | local | cloud
361
- (default: prompt; non-interactive/agent = newest)
362
- --host <url> cloud host (default https://vidfarm.cc)
363
- --api-key <key> or VIDFARM_API_KEY
364
- --home <dir> local backend home (or VIDFARM_HOME; default ~/.vidfarm)
365
-
366
- Collision key = the canonical /files path. Equal size ⇒ already in sync.
367
- Raws (/raws) sync is a follow-up — it needs the source-ingest pipeline.`;
368
- //# sourceMappingURL=sync.js.map
@@ -1,5 +0,0 @@
1
- // Providers that count as a qualified AI key for text/image/video generation
2
- // (everything except the audio-only elevenlabs key).
3
- export const QUALIFIED_AI_PROVIDER_TYPES = ["openai", "gemini", "openrouter", "perplexity"];
4
- export const CUSTOMER_PLAN_TIERS = ["client", "standalone", "agency"];
5
- //# sourceMappingURL=domain.js.map
@@ -1,82 +0,0 @@
1
- const MAX_CHAT_PAYLOAD_STRING_LENGTH = 1200;
2
- const MAX_CHAT_PAYLOAD_ARRAY_ITEMS = 24;
3
- const MAX_CHAT_PAYLOAD_OBJECT_KEYS = 80;
4
- const DATA_URL_PREFIX_REGEX = /^data:([^;,]+)[^,]*,/i;
5
- const SECRET_HEADER_REGEX = /authorization|api[-_]?key|secret|token|vidfarm-api-key/i;
6
- function summarizeLargeString(value) {
7
- const dataUrlMatch = DATA_URL_PREFIX_REGEX.exec(value);
8
- if (dataUrlMatch) {
9
- return `[${dataUrlMatch[1]} data URL omitted, ${value.length.toLocaleString()} chars]`;
10
- }
11
- if (value.length > MAX_CHAT_PAYLOAD_STRING_LENGTH) {
12
- return `${value.slice(0, MAX_CHAT_PAYLOAD_STRING_LENGTH)}... [truncated ${value.length.toLocaleString()} chars]`;
13
- }
14
- return value;
15
- }
16
- export function sanitizeChatPayload(value, depth = 0) {
17
- if (value === null || value === undefined) {
18
- return value;
19
- }
20
- if (typeof value === "string") {
21
- return summarizeLargeString(value);
22
- }
23
- if (typeof value === "number" || typeof value === "boolean") {
24
- return value;
25
- }
26
- if (typeof value === "bigint") {
27
- return value.toString();
28
- }
29
- if (typeof value === "function" || typeof value === "symbol") {
30
- return undefined;
31
- }
32
- if (depth >= 8) {
33
- return "[nested payload omitted]";
34
- }
35
- if (Array.isArray(value)) {
36
- const items = value
37
- .slice(0, MAX_CHAT_PAYLOAD_ARRAY_ITEMS)
38
- .map((entry) => sanitizeChatPayload(entry, depth + 1));
39
- if (value.length > MAX_CHAT_PAYLOAD_ARRAY_ITEMS) {
40
- items.push(`[${value.length - MAX_CHAT_PAYLOAD_ARRAY_ITEMS} more items omitted]`);
41
- }
42
- return items;
43
- }
44
- if (typeof value === "object") {
45
- const output = {};
46
- const entries = Object.entries(value);
47
- for (const [key, entry] of entries.slice(0, MAX_CHAT_PAYLOAD_OBJECT_KEYS)) {
48
- const sanitized = sanitizeChatPayload(entry, depth + 1);
49
- if (sanitized !== undefined) {
50
- output[key] = sanitized;
51
- }
52
- }
53
- if (entries.length > MAX_CHAT_PAYLOAD_OBJECT_KEYS) {
54
- output.__omitted_keys = entries.length - MAX_CHAT_PAYLOAD_OBJECT_KEYS;
55
- }
56
- return output;
57
- }
58
- return String(value);
59
- }
60
- export function sanitizeHttpExchange(exchange) {
61
- return {
62
- ...exchange,
63
- request: {
64
- ...exchange.request,
65
- headers: Object.fromEntries(Object.entries(exchange.request.headers).map(([key, value]) => [
66
- key,
67
- SECRET_HEADER_REGEX.test(key) ? "[redacted]" : summarizeLargeString(value)
68
- ])),
69
- body: sanitizeChatPayload(exchange.request.body)
70
- },
71
- result: {
72
- ...exchange.result,
73
- headers: Object.fromEntries(Object.entries(exchange.result.headers).map(([key, value]) => [
74
- key,
75
- SECRET_HEADER_REGEX.test(key) ? "[redacted]" : summarizeLargeString(value)
76
- ])),
77
- body: sanitizeChatPayload(exchange.result.body),
78
- error: exchange.result.error ? summarizeLargeString(exchange.result.error) : exchange.result.error
79
- }
80
- };
81
- }
82
- //# sourceMappingURL=editor-chat-history.js.map