@mevdragon/vidfarm-devcli 0.20.6 → 0.20.8

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 (154) 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 +111 -0
  8. package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +243 -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 +265 -0
  13. package/SKILL.director.md +476 -260
  14. package/dist/src/cli.js +362 -96
  15. package/dist/src/devcli/clips.js +15 -10
  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 +10 -0
  20. package/package.json +30 -10
  21. package/SKILL.platform.md +0 -432
  22. package/demo/README.md +0 -28
  23. package/demo/dist/app.css +0 -1
  24. package/demo/dist/app.js +0 -1850
  25. package/demo/dist/chunks/chunk-DXB73IDG.js +0 -1
  26. package/demo/dist/chunks/chunk-S7OWAJDS.js +0 -36
  27. package/demo/dist/chunks/chunk-VTIBZ6AN.js +0 -1
  28. package/demo/dist/chunks/dist-ADSJKBVE.js +0 -332
  29. package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +0 -1
  30. package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +0 -425
  31. package/demo/dist/chunks/lib-XAQ37YOE.js +0 -1
  32. package/demo/dist/chunks/src-TJ2QYA4U.js +0 -207
  33. package/demo/dist/favicon.ico +0 -0
  34. package/demo/dist/icons/timeline/audio.svg +0 -7
  35. package/demo/dist/icons/timeline/captions.svg +0 -5
  36. package/demo/dist/icons/timeline/composition.svg +0 -12
  37. package/demo/dist/icons/timeline/image.svg +0 -18
  38. package/demo/dist/icons/timeline/music.svg +0 -10
  39. package/demo/dist/icons/timeline/text.svg +0 -3
  40. package/demo/dist/index.html +0 -15
  41. package/dist/src/account-pages-legacy.js +0 -9396
  42. package/dist/src/account-pages.js +0 -61
  43. package/dist/src/app.js +0 -21603
  44. package/dist/src/composition-runtime.js +0 -1053
  45. package/dist/src/config.js +0 -217
  46. package/dist/src/context.js +0 -447
  47. package/dist/src/dev-app-legacy.js +0 -739
  48. package/dist/src/dev-app.js +0 -6
  49. package/dist/src/devcli/migrate-local.js +0 -140
  50. package/dist/src/devcli/sync.js +0 -368
  51. package/dist/src/domain.js +0 -5
  52. package/dist/src/editor-chat-history.js +0 -82
  53. package/dist/src/editor-chat.js +0 -828
  54. package/dist/src/editor-dark-theme.js +0 -1128
  55. package/dist/src/frontend/debug.js +0 -71
  56. package/dist/src/frontend/discover-client.js +0 -130
  57. package/dist/src/frontend/discover-store.js +0 -23
  58. package/dist/src/frontend/file-directory.js +0 -1018
  59. package/dist/src/frontend/flockposter-cache-store.js +0 -124
  60. package/dist/src/frontend/homepage-client.js +0 -446
  61. package/dist/src/frontend/homepage-shared.js +0 -201
  62. package/dist/src/frontend/homepage-store.js +0 -66
  63. package/dist/src/frontend/homepage-view.js +0 -705
  64. package/dist/src/frontend/page-runtime-client.js +0 -132
  65. package/dist/src/frontend/page-runtime-store.js +0 -9
  66. package/dist/src/frontend/sentry.js +0 -42
  67. package/dist/src/frontend/template-editor-chat.js +0 -4181
  68. package/dist/src/help-page.js +0 -346
  69. package/dist/src/homepage.js +0 -1458
  70. package/dist/src/index.js +0 -16
  71. package/dist/src/instrument.js +0 -30
  72. package/dist/src/landing-page.js +0 -384
  73. package/dist/src/page-runtime.js +0 -2
  74. package/dist/src/page-shell.js +0 -1452
  75. package/dist/src/primitive-context.js +0 -416
  76. package/dist/src/primitive-registry.js +0 -3940
  77. package/dist/src/primitive-sdk.js +0 -4
  78. package/dist/src/primitives/hyperframes-media.js +0 -108
  79. package/dist/src/react-page-shell.js +0 -35
  80. package/dist/src/ready-post-schedule-component.js +0 -1540
  81. package/dist/src/registry.js +0 -296
  82. package/dist/src/reskin/agency-page.js +0 -299
  83. package/dist/src/reskin/calendar-page.js +0 -568
  84. package/dist/src/reskin/chat-page.js +0 -942
  85. package/dist/src/reskin/discover-page.js +0 -1788
  86. package/dist/src/reskin/document.js +0 -1587
  87. package/dist/src/reskin/help-page.js +0 -357
  88. package/dist/src/reskin/index-page.js +0 -62
  89. package/dist/src/reskin/inpaint-clipper-page.js +0 -890
  90. package/dist/src/reskin/inpaint-page.js +0 -2554
  91. package/dist/src/reskin/inpaint-video-page.js +0 -1339
  92. package/dist/src/reskin/job-runs-page.js +0 -477
  93. package/dist/src/reskin/library-page.js +0 -1634
  94. package/dist/src/reskin/login-page.js +0 -262
  95. package/dist/src/reskin/portfolio-page.js +0 -687
  96. package/dist/src/reskin/pricing-page.js +0 -390
  97. package/dist/src/reskin/settings-page.js +0 -732
  98. package/dist/src/reskin/theme.js +0 -711
  99. package/dist/src/runtime.js +0 -35
  100. package/dist/src/services/api-call-history.js +0 -249
  101. package/dist/src/services/auth.js +0 -152
  102. package/dist/src/services/billing-pricing.js +0 -39
  103. package/dist/src/services/billing.js +0 -241
  104. package/dist/src/services/cast.js +0 -127
  105. package/dist/src/services/chat-threads.js +0 -92
  106. package/dist/src/services/clip-records.js +0 -250
  107. package/dist/src/services/clip-search.js +0 -77
  108. package/dist/src/services/clip-vectors.js +0 -125
  109. package/dist/src/services/composition-sanitize.js +0 -124
  110. package/dist/src/services/composition-watch.js +0 -79
  111. package/dist/src/services/elevenlabs.js +0 -222
  112. package/dist/src/services/file-directory.js +0 -117
  113. package/dist/src/services/fork-access.js +0 -93
  114. package/dist/src/services/fork-manifest.js +0 -42
  115. package/dist/src/services/ghostcut.js +0 -179
  116. package/dist/src/services/hyperframes.js +0 -3654
  117. package/dist/src/services/job-capacity.js +0 -14
  118. package/dist/src/services/job-logs.js +0 -197
  119. package/dist/src/services/jobs.js +0 -136
  120. package/dist/src/services/local-dynamo.js +0 -0
  121. package/dist/src/services/media-processing.js +0 -766
  122. package/dist/src/services/primitive-media-lambda.js +0 -280
  123. package/dist/src/services/providers.js +0 -2748
  124. package/dist/src/services/rate-limits.js +0 -262
  125. package/dist/src/services/scene-annotations.js +0 -32
  126. package/dist/src/services/serverless-auth.js +0 -382
  127. package/dist/src/services/serverless-jobs.js +0 -1084
  128. package/dist/src/services/serverless-provider-keys.js +0 -409
  129. package/dist/src/services/serverless-records.js +0 -1515
  130. package/dist/src/services/serverless-template-configs.js +0 -75
  131. package/dist/src/services/storage.js +0 -461
  132. package/dist/src/services/swipe-customize.js +0 -437
  133. package/dist/src/services/template-certification.js +0 -413
  134. package/dist/src/services/template-loader.js +0 -99
  135. package/dist/src/services/template-runtime-bundles.js +0 -217
  136. package/dist/src/services/template-sources.js +0 -1017
  137. package/dist/src/services/upstream.js +0 -248
  138. package/dist/src/services/video-normalization.js +0 -2
  139. package/dist/src/services/webhooks.js +0 -62
  140. package/dist/src/template-editor-pages.js +0 -2576
  141. package/dist/src/template-editor-shell.js +0 -2893
  142. package/dist/src/template-sdk.js +0 -4
  143. package/dist/src/worker.js +0 -17
  144. package/public/assets/discover-client-app.js +0 -1
  145. package/public/assets/file-directory-app.js +0 -3
  146. package/public/assets/homepage-app.js +0 -54
  147. package/public/assets/homepage-client-app.js +0 -80
  148. package/public/assets/page-runtime-client-app.js +0 -94
  149. package/public/assets/placeholders/scene-placeholder.png +0 -0
  150. package/src/assets/SELLING_AWARENESS_STAGES.md +0 -579
  151. package/src/assets/SELLING_WITH_HOOKS.md +0 -377
  152. package/src/assets/SELLING_WITH_VSLS.md +0 -606
  153. package/src/assets/favicon.ico +0 -0
  154. package/src/assets/logo-vidfarm.png +0 -0
@@ -0,0 +1,273 @@
1
+ ## Mental model
2
+
3
+ A Vidfarm composition is an **HTML document + JSON metadata**. Each layer (video clip, audio, image, text, caption) is a timed DOM element with `data-start`, `data-duration`, `data-track-index` attributes. The Trackpad Editor is a timeline UI over this document, similar to Premiere or DaVinci.
4
+
5
+ Every director workflow goes through three concepts:
6
+
7
+ 1. **Template** — a published composition anyone can fork. Template ids start with `template_`; each template opens in the editor at `/editor/:templateId` and has a source video, a decomposed timeline, and metadata (viral DNA, captions, scenes).
8
+ 2. **Composition fork** — a private editable copy of a template. Every fork has a `forkId`, an owner, a working state, and a version history. The Trackpad Editor operates on a fork.
9
+ 3. **Publish** — render the current fork state to MP4 via a Lambda render job. Each publish snapshots the current working state as an immutable version.
10
+
11
+ The Trackpad Editor is the primary surface. Most director actions happen there. This skill's REST endpoints exist for automation, CI, and headless flows.
12
+
13
+ ## Base URL and auth
14
+
15
+ The current API base URLs are:
16
+
17
+ - staging: `https://staging.vidfarm.cc`
18
+ - production: `https://vidfarm.cc`
19
+
20
+ Every browser action is authenticated by the session cookie set at `/login`. Headless API-key auth uses the `vidfarm-api-key` header set to the caller's API key (from Settings → API Keys).
21
+
22
+ **Auth contract: API-key auth is the `vidfarm-api-key: <key>` request header and nothing else. `Authorization: Bearer <key>` is NOT supported — the server never reads the `authorization` header (see `requireAuth` in `src/app.ts`).**
23
+
24
+ ```
25
+ vidfarm-api-key: vf_key_<id>
26
+ ```
27
+
28
+ Scope note (verified against `src/app.ts`): the `vidfarm-api-key` header authenticates **every** authenticated surface — the `requireAuth` routes (`/api/v1/user/*`, `/api/v1/agency/*`, `/api/v1/templates/*`, `/api/v1/primitives/*`, `/api/v1/rate-limit-status`, editor-chat threads) **and** the `/api/v1/compositions/*` + `/api/v1/videos/*` + editor page routes, where an explicit API key takes precedence over any browser session cookie. Cookies remain how the web UI authenticates; headless callers never need one. Reads on composition routes also work without any credential because the `forkId` acts as an unguessable view bearer token, and share-link tokens (header `vidfarm-share-token` or `?share=` query) can grant `viewer`/`editor`/`publisher` roles.
29
+
30
+ ## Login flow
31
+
32
+ Directors log in with OTP:
33
+
34
+ 1. `POST /api/v1/user/request-otp { email }` — sends a code to email; returns `{ ok, delivery }` (plus `dev_code` when delivery is `console` outside production)
35
+ 2. `POST /api/v1/user/verify-otp { email, code, name? }` — returns `{ customer, apiKey }` and mints a fresh API key
36
+
37
+ Save the `apiKey` in the client and use it for automation (as the `vidfarm-api-key` header). The browser login form uses the session-cookie variants `POST /login/otp/request` and `POST /login/otp/verify` instead.
38
+
39
+ ## Provider keys
40
+
41
+ Vidfarm executes many features (smart auto-decompose, editor chat) with the **caller's own AI provider keys**. Directors save keys once via:
42
+
43
+ - UI: Settings → Provider Keys
44
+ - API: `POST /api/v1/user/me/provider-keys { provider, secret, label? }` — one key per call; `provider` is `"openai" | "gemini" | "openrouter" | "perplexity"`. The key is validated against the provider before it is saved. List saved keys with `GET /api/v1/user/me/provider-keys`.
45
+
46
+ Priority: Gemini → OpenAI → OpenRouter. If a director has no keys, smart decompose falls back to a deterministic time-based split.
47
+
48
+ ## Discover templates
49
+
50
+ Templates are listed on the Vidfarm homepage and `/discover`. Each has a `template_...` id, a preview MP4, source video metadata, and viral DNA.
51
+
52
+ - Browser: browse `/discover`
53
+ - API: `GET /discover/feed` — returns `{ templates: [{ templateId, slugId, title, previewUrl, viralDna, durationSeconds, sourceType, promotions, keywords, summary, ... }], next_cursor }`
54
+ - Search: `GET /discover/feed?q=<offer>&limit=20` keyword-searches the catalog so you can answer "which templates suit my `<offer>`?" (e.g. `?q=weight%20loss%20app`). Ranking is a coarse keyword match over each template's title, viral DNA, and the decompose-derived `promotions` (offer categories the format can sell), `keywords`, and `summary` — read those fields to judge fit and rank the best 3-6 semantically. `GET /api/v1/videos?q=<offer>&limit=20[&mine=true]` runs the same search over source **inspirations**. `promotions`/`keywords`/`summary` are populated by the decompose pass, so a freshly ingested template that has not been decomposed yet won't match on them — decompose it (or fall back to broader/synonym terms) if a search comes up empty.
55
+
56
+ Each template exposes a public preview:
57
+
58
+ - `GET /editor/:templateId` — opens the Trackpad Editor for the template (redirects to your fork of it, or to `/login`)
59
+ - `GET /editor/:templateId/composition` — raw composition HTML built from the template's source video
60
+ - `GET /discover/skills/:templateId` — the template's SKILL.md content as JSON (registry templates only)
61
+
62
+ Directors don't edit templates directly. They fork.
63
+
64
+ ### Add your own source (inspiration)
65
+
66
+ To bring a new viral video into the catalog as a **private** template you own, ingest its social URL — or upload a video file directly:
67
+
68
+ - `POST /discover/templates { source_url, tagline?, notes? }` — accepts TikTok / YouTube / Instagram / Twitter-X URLs only. The video download runs async; the response is the inspiration record (`202`, `status: "processing"`). The card finalizes into a private `template_...` the next time `GET /discover/feed` is polled (the feed poll doubles as the completion check).
69
+ - **Upload a file** (three steps, same finalize behavior as the URL flow):
70
+ 1. `POST /discover/templates/upload/presign { file_name, content_type?, size_bytes? }` — video files only (MP4/MOV/WebM), 200 MB cap. Returns `transport: "presigned"` with a `{ upload: { method, url, headers } }` S3 PUT target, or `transport: "server"` (local-storage boxes) pointing at the multipart fallback below. Both include the `storage_key` to finalize with.
71
+ 2. Send the bytes: `PUT` them to the presigned URL, or `POST /discover/templates/upload` (multipart, field `file`) when transport is `server`.
72
+ 3. `POST /discover/templates { upload: { storage_key, file_name }, title?, tagline?, notes? }` — queues an async `video_ingest` job (durable mirror + duration probe + thumbnail) and returns the inspiration record (`202`). Finalizes into a private template exactly like the URL flow. `title` names the /discover card; untitled templates display as their `template_...` id (the file name is only shown while processing, never persisted as the title).
73
+ - `DELETE /discover/templates/:entryId` — remove a private inspiration/template you own (accepts either the `inspiration_...` or minted `template_...` id).
74
+ - `POST /api/v1/inspirations/:inspirationId/decompose { user_prompt? }` — AI-decompose an inspiration's downloaded video into scenes (requires a saved provider key; same 120s source cap as auto-decompose).
75
+
76
+ devcli: `vidfarm inspiration-add <url|file.mp4>` (a local file path runs the presign→PUT→finalize flow for you), `vidfarm inspiration-rm <id>`, `vidfarm inspiration-decompose <id>`, `vidfarm discover [query]` to browse/search templates, `vidfarm public-raws [query] [--category <key>] [--type <raw_type>] [--bookmark <raw_id>]` to browse/save the public raws catalog, and `vidfarm videos [query] [--mine]` to browse/search source inspirations. On the web, the Discover page's **Add Template** modal accepts either a URL or a file upload.
77
+
78
+ ## Fork a template
79
+
80
+ ```
81
+ POST /api/v1/compositions
82
+ Content-Type: application/json
83
+
84
+ { "template_id": "template_<...>", "title": "Optional title" }
85
+ ```
86
+
87
+ `template_id` is required and must start with `template_` (the handler also accepts the same value under the key `source`). Returns the new fork's metadata (201):
88
+
89
+ ```
90
+ { "fork_id": "<forkId>", "template_id": "template_...", "parent_fork_id": "...", "parent_version": 1, "title": "...", "visibility": "private", "latest_version": 0, "composition_url": "...", "composition_data_url": "...", "versions_url": "...", "role": "owner", "capabilities": { ... } }
91
+ ```
92
+
93
+ Take the returned `fork_id` and open the Trackpad Editor at:
94
+
95
+ ```
96
+ /editor/<templateId>/fork/<forkId>
97
+ ```
98
+
99
+ The editor loads `composition.html` and `composition.json` from the fork's working state.
100
+
101
+ ### New fork from an existing fork (branch this copy, or start fresh)
102
+
103
+ Once you already hold a fork, spawn ANOTHER editable copy — a branch — with one unified endpoint. This is what the editor's ⋯ menu **New fork** modal and the devcli `clone --from` command both call:
104
+
105
+ ```
106
+ POST /api/v1/compositions/:forkId/fork
107
+ Content-Type: application/json
108
+
109
+ { "from": "current" | "default", "parent_version": <optional N>, "title": "Optional" }
110
+ ```
111
+
112
+ - `from: "current"` (default) — branch a new private fork from **this** fork's working copy; all current edits carry over. Optionally pin `parent_version` to clone a saved version instead of the live working state. (The legacy `POST /:forkId/clone` route is an alias for exactly this.)
113
+ - `from: "default"` — start a **fresh** fork from the template's canonical default composition, discarding the current fork's edits (they stay in the fork you branched from).
114
+
115
+ Returns the same fork metadata shape as `POST /api/v1/compositions` (201). devcli: `vidfarm clone <forkId> [--from current|default] [--version N] [--title …]`. In the in-editor AI chat this is the `editor_action` `fork_composition` action (`fork_from: current|default`).
116
+
117
+ ## Render (publish to MP4)
118
+
119
+ Rendering publishes the fork's current working state to an MP4 using HyperFrames (cloud Lambda fan-out on the deployed host, or the free in-process renderer on a local `vidfarm serve` box). In the Trackpad Editor this is the **Render** button. The REST route is `/render`; the older `/export` path is kept as a deprecated alias for already-published devcli clients (treat "Render", "publish", `/render`, and `/export` as the same operation). The devcli exposes it as `vidfarm render <forkId> [--wait]`; scripted local dirs can use `vidfarm render <forkId> --dir ./work --wait`.
120
+
121
+ ```
122
+ POST /api/v1/compositions/:forkId/render
123
+ Content-Type: application/json
124
+
125
+ { "title": "optional", "version": null, "tracer": "optional-trace-id", "html": "optional HTML to save to working state before rendering" }
126
+ ```
127
+
128
+ Returns 202 with `{ ok, renderId, status, progress, expectedOutputPublicUrl, outputUrl, cost, title, version, ... }` where `expectedOutputPublicUrl` is the deterministic public MP4 URL and `version` is the snapshot version created for this publish. Failure modes: `402` if the fork owner is not on a paid plan, `409` while GhostCut subtitle removal is still `pending` (retry after `/remove-video-captions-poll` reports done/failed), `412` if the provider-key preflight fails. Poll:
129
+
130
+ ```
131
+ GET /api/v1/compositions/:forkId/renders/:renderId
132
+ ```
133
+
134
+ Response includes `{ ok, renderId, status: "RUNNING" | "SUCCEEDED" | "FAILED", progress, framesRendered, totalFrames, cost, expectedOutputPublicUrl, outputUrl, outputS3Uri, errors }` (`progress` is 0..1). On success, `expectedOutputPublicUrl` is the durable public MP4 URL and `outputUrl` remains the completion-time field.
135
+
136
+ Every publish creates an immutable version snapshot at `versions/<N>/composition.html` and `versions/<N>/composition.json`.
137
+
138
+ The Web UI **Render** button and devcli render both use this same endpoint. The fast `202` response includes the deterministic `expectedOutputPublicUrl` so a caller can store or pass along the final public S3 URL before the render has completed, then poll by `renderId` until `status` settles.
139
+
140
+ ## Approve a finished post
141
+
142
+ A render produces a bare MP4 URL. **Approving** wraps that MP4 (plus caption, title, pinned comment, and any carousel slides) into a shareable preview page — the phone-mockup page a human opens to review and copy the post.
143
+
144
+ ```
145
+ POST /api/v1/approved/posts
146
+ Content-Type: application/json
147
+
148
+ { "caption": "required", "title": "optional", "pinned_comment": "optional", "tracer": "optional",
149
+ "media": [ { "url": "https://.../output.mp4", "kind": "video", "role": "primary" } ] }
150
+ ```
151
+
152
+ `caption` is required. `media[]` items take `{ url, kind?: "image"|"video"|..., role?: "primary"|"slide"|... }`. Response (`201`) is the approved post including **`share_url`** — the prod frontend page for previewing/sharing. Surface that URL to the user; it is the headline output of this step.
153
+
154
+ - `GET /api/v1/approved/posts` — list your approved posts
155
+ - `GET /api/v1/approved/posts/:postId` — read one (returns `share_url`, `download_zip_url`)
156
+
157
+ devcli: `vidfarm approve --video <mp4-url> --caption "..."` prints the `share_url` as a first-class openable link; `vidfarm posts` lists, `vidfarm post <id>` reads one.
158
+
159
+ ## Schedule a post
160
+
161
+ Schedule an approved post to a connected destination channel (FlockPoster social account or email) at one ISO timestamp:
162
+
163
+ ```
164
+ POST /api/v1/approved/posts/:postId/schedules
165
+ Content-Type: application/json
166
+
167
+ { "destination_type": "flockposter" | "email", "destination_id": "<channel or email>",
168
+ "scheduled_at": "2026-07-10T14:00:00Z", "timezone": "America/New_York", "additional_notes": "optional" }
169
+ ```
170
+
171
+ Minimum 10-minute lead time. Response (`201`) is the schedule record. Browse existing schedules with `GET /api/v1/approved/posts/:postId/schedules`.
172
+
173
+ devcli: `vidfarm schedule <postId> --at <iso> --to <destinationId> [--type flockposter|email]`, and `vidfarm schedules <postId>` to browse.
174
+
175
+ ## Version history
176
+
177
+ ```
178
+ GET /api/v1/compositions/:forkId/versions?limit=100
179
+ ```
180
+
181
+ Returns `{ versions: [{ version, reason, message, created_by, created_at, render_job_id, composition_url, composition_data_url }], next_cursor }`. Reason is `publish`, `clone`, `manual`, or `migration`. To snapshot the working state manually, `POST /api/v1/compositions/:forkId/versions { "message": "optional" }`.
182
+
183
+ To view a past cut without reverting:
184
+
185
+ ```
186
+ GET /api/v1/compositions/:forkId/versions/:version/composition.html
187
+ ```
188
+
189
+ To revert the working state to a version, `PUT` the historical HTML/JSON back to the working keys.
190
+
191
+ ## Sharing and visibility
192
+
193
+ Every fork has a visibility: `private` (default), `unlisted` (legacy), or `public` (view-only).
194
+
195
+ ```
196
+ PATCH /api/v1/compositions/:forkId/visibility { "visibility": "public" }
197
+ ```
198
+
199
+ Public forks are accessible at `/editor/<templateId>?fork=<forkId>` (view-only for non-owners). Public visibility is the simplest sharing surface.
200
+
201
+ Fine-grained sharing uses per-user permissions and share links (all body fields are snake_case):
202
+
203
+ - `POST /api/v1/compositions/:forkId/permissions { grantee_email | grantee_customer_id, role, expires_at? }` — grant a specific registered user access. Exactly one of `grantee_email` or `grantee_customer_id` is required. Roles: `viewer`, `editor`, `publisher`. Returns `{ permission_id, fork_id, grantee_type, grantee_id, role, granted_by, expires_at, created_at }`.
204
+ - `POST /api/v1/compositions/:forkId/share-links { role, expires_at? }` — create a token URL. Anyone with the token gets the role. Returns `{ token, share_url, role, expires_at, ... }`.
205
+ - `GET /api/v1/compositions/shared/:token` — access via share link
206
+ - `DELETE /api/v1/compositions/:forkId/permissions/:permissionId` — revoke a grant
207
+ - `DELETE /api/v1/compositions/:forkId/share-links/:token` — revoke a link
208
+
209
+ ## Cloning a fork
210
+
211
+ Directors can clone another fork (their own or a shared one) as a new starting point:
212
+
213
+ ```
214
+ POST /api/v1/compositions/:forkId/clone
215
+ Content-Type: application/json
216
+
217
+ { "parent_version": "<N or omit for latest>", "title": "optional" }
218
+ ```
219
+
220
+ The `:forkId` in the path is the fork being cloned. The new fork inherits the parent's HTML/JSON at the specified version and creates a version-1 snapshot with `reason: "clone"`.
221
+
222
+ ## Delete a fork
223
+
224
+ ```
225
+ DELETE /api/v1/compositions/:forkId
226
+ ```
227
+
228
+ Soft-delete only. The fork's `deletedAt` is set. Versions and share links are preserved for audit.
229
+
230
+ ## Cost spectrum (free → $10+/video) — default to saving the director money
231
+
232
+ There is no single price for a video. The **approach** the director picks sets the cost, across a wide spectrum. Always steer toward the cheapest approach that still meets their goal, and make the tradeoff explicit rather than silently choosing an expensive path.
233
+
234
+ | Approach | Typical cost | How |
235
+ |---|---|---|
236
+ | **Reuse + render locally** | **free** | Fork an already-decomposed template, swap captions / images / video with existing MP4s (from **My Files**, the director's **local computer** — reference files straight off disk with `place --src ./file`, no upload — or a **web search**), and render **locally** via `vidfarm serve` (native in-process HyperFrames render — free and unguarded, no cloud). |
237
+ | **Reuse + cloud render** | **~$0.001 – $0.03** | Same reuse, but render on the cloud renderer (`POST /compositions/:forkId/render`, ~$0.01–$0.10 depending on length/res). Cheap **image** generation/edits fit in this band too. |
238
+ | **AI-generate some scenes** | **~$1** | Replace a few scenes with AI-generated video clips for high specificity/customization (see the "Generate AI media" section). |
239
+ | **Heavy AI generation** | **$10+** | Many/long AI video clips, custom characters, fully bespoke scenes. |
240
+
241
+ Rules of thumb:
242
+
243
+ - **Reusing existing footage — or footage the director films themselves — is the cheapest.** AI-generating characters/scenes is the most expensive. Editing captions/images on a reused template is nearly free.
244
+ - **Local render is free; cloud render costs pennies.** If the director is iterating with a coding agent on a `vidfarm serve` box, they can render locally at no charge until they want the durable cloud MP4.
245
+ - **Decompose is a one-time ~$0.10** (smart decompose ≈ provider passthrough + GhostCut ~$0.10/30s) per **new** source video — but the director can **skip it entirely** by forking a template that's **already decomposed** in the catalog.
246
+ - **Image generation is cheap** — use it freely, no need to ask permission.
247
+ - **AI video generation is expensive — ask the director's permission before using it.** Default to reuse/local/image approaches unless they've okayed video gen or told you the budget covers it.
248
+ - **Ask about budget.** During Getting Started (and whenever it's relevant per editor project, or when the director asks about cost), ask roughly what they want to spend per video, and pick the approach band that fits. If they haven't said, assume the cheapest approach that works.
249
+
250
+ The director always chooses the method that works for them — the point is to surface the tradeoff and default to thrift, not to force the cheapest path.
251
+
252
+ **How the approach is painted — the three paintbrushes.** The cost of a video is set by *which brush* recreates each scene: (1) **raw clips** remixed from existing footage, (2) **HTML/JS hyperframes** (animated text/images/graphics), (3) **pure AI generation** (image/video/voice/music, most expensive). A technical replication decomposition names, per beat, when/where/what each brush should be, and offers two harnesses — **(A) cheap & efficient** (recaption, bg+fg video memes, hyperframe animation, reuse/greenscreen the asset library, AI only if needed) and **(B) best quality** (AI video by default, AI-image storyboarding first, adversarial grading with a coding agent). Default to (A); reserve (B) for premium/budgeted work. Full methodology in `references/editor-workflows.md` (“The three paintbrushes & two replication harnesses”).
253
+
254
+ ## Billing
255
+
256
+ Vidfarm charges directly in USD from the caller's wallet. There are no credits.
257
+
258
+ - **Wallet top-up** — Stripe checkout via the Settings UI, backed by `POST /settings/wallet/funding-link` (browser session; returns `{ checkout_url, client_reference_id }`)
259
+ - **Balance & history** — shown on the Settings page; billing events via `GET /u/:customerId/settings/wallet/events` (browser session). There is no `/api/v1/user/me/wallet` REST endpoint.
260
+ - **Cost anchors** (see the Cost spectrum section above for how these combine per approach):
261
+ - Local render on a `vidfarm serve` box (in-process HyperFrames): **free**
262
+ - Cloud render (HyperFrames Lambda fan-out): typically $0.01 – $0.10 per MP4 depending on length/resolution
263
+ - Image generation / edit: cheap (provider passthrough) — use freely
264
+ - AI **video** generation: expensive ($1–$10+/video territory) — ask permission first
265
+ - Auto-decompose smart mode: pass-through of caller's AI provider spend (~$0.10 one-time with GhostCut), or skip it by forking an already-decomposed template
266
+ - Auto-decompose time-slice: free
267
+ - GhostCut subtitle removal: ~$0.10 per 30 seconds of source video
268
+
269
+ Framing:
270
+
271
+ - Vidfarm is optimized for cost efficiency, not markup
272
+ - Total director cost per video is typically far below alternatives
273
+ - Platform retains a small safety buffer for operational correctness