@officexapp/vidfarm-devcli 0.21.10 → 0.21.11
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-director/SKILL.md +1 -1
- package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +3 -1
- package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +7 -5
- package/.agents/skills/vidfarm-director/references/rest-api.md +1 -1
- package/.agents/skills/vidfarm-media/SKILL.md +15 -10
- package/SKILL.director.md +11 -7
- package/dist/src/cli.js +64 -5
- package/package.json +1 -1
|
@@ -50,7 +50,7 @@ Do not use this skill to author new templates from scratch, deploy platform infr
|
|
|
50
50
|
|
|
51
51
|
Vidfarm work can burn real AI credits on the user's wallet / provider keys. **Save them money by default.** Before the first billed step of a session, ask the user which spend posture they want, and explain it in one plain line each:
|
|
52
52
|
|
|
53
|
-
- **minimize** — cheapest. Stay on FREE local compute wherever possible (local render, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, reused raw clips + HTML hyperframes). No surprise AI spend.
|
|
53
|
+
- **minimize** — cheapest. Stay on FREE local compute wherever possible (local render, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, reused raw clips + HTML hyperframes). For assets, reach for the **free stock catalog** before paying to generate anything — `vidfarm media search "<meaning>" --type <bgm|sfx|image|vector|icon|video>` pulls royalty-free, commercial-safe music/sound-effects/images/icons/stock-video from pixabay/openverse/iconify at $0 instead of billing AI music/image generation (see the `vidfarm-media` skill). No surprise AI spend.
|
|
54
54
|
- **hybrid** *(recommend this)* — free where it's free; pay for AI only where it clearly wins (a hero shot, a voice you can't fake locally).
|
|
55
55
|
- **pure-ai** — best quality; use AI image/video/voice/music freely.
|
|
56
56
|
|
|
@@ -8,6 +8,8 @@ Use this when a coding agent is doing the work locally or the user wants a repro
|
|
|
8
8
|
4. Make deterministic edits to `composition.html` and optionally `composition.json`.
|
|
9
9
|
5. Validate with `vidfarm lint` or `vidfarm stills` when useful.
|
|
10
10
|
6. Render with `vidfarm render <forkId> --dir ./work --wait`.
|
|
11
|
-
7. Approve the finished MP4 with `vidfarm approve --video <url> --caption "..."`.
|
|
11
|
+
7. Approve the finished MP4 with `vidfarm approve --video <url|./final.mp4> --caption "..."`. This prints the shareable `share_url`.
|
|
12
|
+
|
|
13
|
+
**Approving a locally rendered file → cloud preview link.** Approve takes media by **URL**, not bytes. A local MP4 must become a URL first: the devcli presigns, PUTs the bytes **direct to S3**, finalizes, then approves with that URL — so `vidfarm approve --video ./final.mp4` handles files up to **200 MB** and bypasses the ~6 MB Lambda request-body limit. By raw REST: `POST /api/v1/user/me/temporary-files/presign` → PUT to the returned S3 URL → `POST /api/v1/user/me/temporary-files` (finalize) → pass the durable URL in the approve `media` array. Do not multipart-POST a big file to `.../temporary-files/upload` against the cloud host (Lambda-bound, ~6 MB cap).
|
|
12
14
|
|
|
13
15
|
Prefer this path for batch work, CI-like edits, or when the user wants free local rendering through `vidfarm serve`.
|
|
@@ -148,7 +148,7 @@ If a local AI script rewrites text or scenes without consuming those files first
|
|
|
148
148
|
| `vidfarm visibility <forkId> <private\|public>` | `PATCH .../compositions/:forkId/visibility` | set visibility |
|
|
149
149
|
| `vidfarm clone <forkId>` | `POST .../compositions/:forkId/clone` | clone a fork |
|
|
150
150
|
| `vidfarm share-link <forkId>` | `POST .../compositions/:forkId/share-links` | mint a share URL |
|
|
151
|
-
| `vidfarm approve --video <url\|file> --caption "…"` | `POST /api/v1/approved/posts` | approve post
|
|
151
|
+
| `vidfarm approve --video <url\|file> --caption "…"` | `POST /api/v1/approved/posts` | approve post — takes media by **`url`**. Local `--video`/`--media` auto-upload via presign→S3→finalize (up to **200 MB**, bypasses the ~6 MB Lambda limit) then approve with that URL; prints `share_url` |
|
|
152
152
|
| `vidfarm posts` / `vidfarm post <id>` | `GET /api/v1/approved/posts[/:id]` | browse approved posts |
|
|
153
153
|
| `vidfarm schedule <postId> --at <iso> --to <dest>` | `POST /api/v1/approved/posts/:postId/schedules` | schedule a post |
|
|
154
154
|
| `vidfarm schedules <postId>` | `GET /api/v1/approved/posts/:postId/schedules` | browse scheduled posts |
|
|
@@ -157,7 +157,7 @@ If a local AI script rewrites text or scenes without consuming those files first
|
|
|
157
157
|
| `vidfarm cost-mode [minimize\|hybrid\|pure-ai]` | (local `~/.vidfarm/cost-mode.json`) | show/set the money-saving preference every billed command respects; no arg = show + explain the three; `--clear` forgets it |
|
|
158
158
|
| `vidfarm wallet [--job <id>\|--tracer <t>] [--limit <n>]` | `GET /api/v1/user/me/wallet` | cost log: balance + lifetime spend + recent charges. `--job <renderJobId>` prints **what that one video cost** (sums its charges); `--tracer <t>` sums a tracer. Cloud-only, paid plan. Aliases: `spend`, `costs` |
|
|
159
159
|
| `vidfarm provider-keys` / `vidfarm add-provider-key <p> <secret>` | `GET`·`POST /api/v1/user/me/provider-keys` | manage AI keys |
|
|
160
|
-
| `vidfarm upload <file> [--folder <path>]` |
|
|
160
|
+
| `vidfarm upload <file> [--folder <path>]` | presign → S3 PUT → finalize (`.../temporary-files/presign` + `.../temporary-files`) | upload → durable URL (ephemeral, 30-day TTL; prefer `--folder temp` for scratch). Goes direct to S3, so large files (up to **200 MB**) bypass the ~6 MB Lambda body limit |
|
|
161
161
|
| `vidfarm download <url> [dest]` | (streams any URL to disk) | download media |
|
|
162
162
|
| `vidfarm download-post <url> [--quality best\|hd\|full_hd]` | `POST /api/v1/primitives/videos/download` + poll | download a social/media post into a durable MP4 or slideshow |
|
|
163
163
|
| `vidfarm download-post-audio <url>` | `POST /api/v1/primitives/audio/download` + poll | download a social/media post's audio into a durable audio file |
|
|
@@ -182,13 +182,15 @@ If a local AI script rewrites text or scenes without consuming those files first
|
|
|
182
182
|
|
|
183
183
|
**Frontend URLs are first-class output.** Every command that has a human-openable page (editor, discover, approved-post preview, share link) prints that prod frontend URL as a highlighted line. `render --wait` polls to completion and prints the final MP4 URL; `approve` prints the approved-post `share_url`. Add `--json` to any command for pure JSON (agent-friendly, no banners).
|
|
184
184
|
|
|
185
|
-
**Uploads/downloads/My-Files writes live in the devcli** because they are genuine multi-step / streaming flows: `upload`
|
|
185
|
+
**Uploads/downloads/My-Files writes live in the devcli** because they are genuine multi-step / streaming flows: `upload` presigns the **ephemeral** temporary-files route, PUTs the bytes **direct to S3**, finalizes, and prints the durable URL to drop into a composition or approved post — going direct to S3 means large files (up to **200 MB**) never hit the ~6 MB Lambda request-body limit; `put-file` posts to the **persistent** My Files route (`/me/attachments/upload`) so context docs and brand assets live in the user's library (accepts a local file, `--content <text>`, or piped `--stdin` with `--as <name>`); `download` streams any Vidfarm/media URL to disk.
|
|
186
|
+
|
|
187
|
+
**Approving a locally rendered MP4 (the URL-first rule).** The approve route (`POST /api/v1/approved/posts`) and every media-taking route accept a `url`, never raw file bytes — so you never "upload to approve" in one shot. The correct sequence for a big local file is: (1) `POST /api/v1/user/me/temporary-files/presign` with `{ file_name, content_type, size_bytes }` → (2) PUT the raw bytes to the returned presigned S3 URL → (3) `POST /api/v1/user/me/temporary-files` (finalize) → use the returned durable URL as the approve media `url`. `vidfarm approve --video ./final.mp4` does all of this automatically. Never POST a large file as multipart to `.../temporary-files/upload` against the cloud host: that path proxies through Lambda and caps near 6 MB (it exists only as a fallback for local-storage `vidfarm serve` boxes).
|
|
186
188
|
|
|
187
189
|
## Cost mode — the devcli's money-saving guardrail
|
|
188
190
|
|
|
189
|
-
`vidfarm cost-mode <minimize|hybrid|pure-ai>` records a single spend preference (in `~/.vidfarm/cost-mode.json`) that every **billed** command honors: `generate`, `music`, `decompose`, `inspiration-decompose`, `create`, `replicate`, `inpaint`, `create-overlay`, and the cloud paths of `render --target cloud`, `tts --cloud`, `stt --cloud`, `remove-greenscreen` (non-`--local`). FREE local engines never gate (`render` local default, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, all the file-editing verbs).
|
|
191
|
+
`vidfarm cost-mode <minimize|hybrid|pure-ai>` records a single spend preference (in `~/.vidfarm/cost-mode.json`) that every **billed** command honors: `generate`, `music`, `decompose`, `inspiration-decompose`, `create`, `replicate`, `inpaint`, `create-overlay`, and the cloud paths of `render --target cloud`, `tts --cloud`, `stt --cloud`, `remove-greenscreen` (non-`--local`). FREE local engines never gate (`render` local default, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, all the file-editing verbs). `vidfarm media search` (the free stock catalog — pixabay/openverse/iconify music, SFX, images, icons, stock video) is also free and never gates.
|
|
190
192
|
|
|
191
|
-
- **minimize** — a billed op is **refused** unless you add `--yes`; the error names the free local alternative. Use this to guarantee no surprise AI spend.
|
|
193
|
+
- **minimize** — a billed op is **refused** unless you add `--yes`; the error names the free local alternative. Use this to guarantee no surprise AI spend. Before paying to generate music, sound effects, or images, try `vidfarm media search "<meaning>" --type <bgm|sfx|image|vector|icon|video>` first — free royalty-free assets instead of a billed `music`/`generate` call.
|
|
192
194
|
- **hybrid** *(default recommendation)* — billed ops run but print a one-line cost notice each.
|
|
193
195
|
- **pure-ai** — billed ops run without gating; cost is still printed.
|
|
194
196
|
|
|
@@ -72,7 +72,7 @@ Use environment variables or a secret manager for keys. Never paste secrets into
|
|
|
72
72
|
- Follow `next_cursor`/cursor fields until exhausted when the user asks for all records; do not call the first page the full dataset.
|
|
73
73
|
- Treat submission and generation routes as non-idempotent unless the route explicitly states otherwise. Check existing state before retrying.
|
|
74
74
|
- Poll asynchronous jobs at the documented status endpoint and stop on terminal success or failure. Do not fabricate completion from an accepted response.
|
|
75
|
-
- Upload local media through the documented presign/upload/finalize sequence. The devcli is recommended on desktop because it performs this sequence automatically.
|
|
75
|
+
- Upload local media through the documented presign/upload/finalize sequence. The devcli is recommended on desktop because it performs this sequence automatically. **Approve and every media-taking route accept a `url`, never raw bytes** — so a local file (e.g. a locally rendered MP4) must first become a URL: `POST /api/v1/user/me/temporary-files/presign` → PUT the bytes straight to the returned S3 URL → `POST /api/v1/user/me/temporary-files` to finalize → use the returned durable URL as the media `url`. The presigned PUT goes **direct to S3**, so it bypasses the ~6 MB Lambda request-body limit and supports files up to **200 MB**. Do **not** POST large files as multipart to `.../temporary-files/upload` against the cloud host — that path routes through Lambda and caps near 6 MB (it is only for local-storage `vidfarm serve` boxes). Temp files carry a 30-day TTL.
|
|
76
76
|
- Parse and mutate composition HTML as a DOM. Never edit it by string concatenation.
|
|
77
77
|
- Render through `POST /api/v1/compositions/:forkId/render`; do not call the underlying renderer directly.
|
|
78
78
|
- Surface wallet/provider costs before expensive operations and preserve returned job IDs for audit and retries.
|
|
@@ -141,18 +141,23 @@ Same ownership rule, resolved through the file directory:
|
|
|
141
141
|
|
|
142
142
|
### Free media catalog — sources & keys
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
Icons + CC images/audio are **keyless**. Pixabay (stock photos/vectors/video) is
|
|
145
|
+
**BYOK** — each user saves their own free Pixabay key, exactly like their OpenAI /
|
|
146
|
+
Gemini keys, through the same provider-key surface (Settings → Bring your own keys,
|
|
147
|
+
`POST /api/v1/user/me/provider-keys` with `provider: "pixabay"`, or
|
|
148
|
+
`vidfarm add-provider-key pixabay <key>`). It is a stock-media key, not an AI key —
|
|
149
|
+
it never counts as a qualified AI provider and never touches editor chat.
|
|
145
150
|
|
|
146
151
|
| Source | Serves | Key |
|
|
147
152
|
| --- | --- | --- |
|
|
148
153
|
| **iconify** | `icon` (SVG) | keyless — always on |
|
|
149
154
|
| **openverse** | `image` (CC), `bgm`, `sfx` (CC/CC0 audio) | keyless — always on |
|
|
150
|
-
| **pixabay** | `image`, `vector`, `video` (photos/illustrations/stock video) |
|
|
151
|
-
|
|
152
|
-
**Recommend
|
|
153
|
-
returns nothing (or `providers_used` omits `pixabay`), the
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
| **pixabay** | `image`, `vector`, `video` (photos/illustrations/stock video) | **BYOK** — the user's own free `pixabay` provider key |
|
|
156
|
+
|
|
157
|
+
**Recommend the key when a search comes up short.** If `image`/`vector`/`video`
|
|
158
|
+
returns nothing (or `providers_used` omits `pixabay`), the caller hasn't saved a
|
|
159
|
+
Pixabay key — tell them to grab a **free** one at <https://pixabay.com/api/docs/>
|
|
160
|
+
and save it (`vidfarm add-provider-key pixabay <key>`, or Settings in the web app).
|
|
161
|
+
Icons, CC images, and bgm/sfx keep working without any key. (An operator may still
|
|
162
|
+
set a platform `PIXABAY_API_KEY` server-side as a fallback for users who haven't
|
|
163
|
+
saved their own, but the default posture is bring-your-own.)
|
package/SKILL.director.md
CHANGED
|
@@ -50,7 +50,7 @@ Do not use this skill to author new templates from scratch, deploy platform infr
|
|
|
50
50
|
|
|
51
51
|
Vidfarm work can burn real AI credits on the user's wallet / provider keys. **Save them money by default.** Before the first billed step of a session, ask the user which spend posture they want, and explain it in one plain line each:
|
|
52
52
|
|
|
53
|
-
- **minimize** — cheapest. Stay on FREE local compute wherever possible (local render, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, reused raw clips + HTML hyperframes). No surprise AI spend.
|
|
53
|
+
- **minimize** — cheapest. Stay on FREE local compute wherever possible (local render, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, reused raw clips + HTML hyperframes). For assets, reach for the **free stock catalog** before paying to generate anything — `vidfarm media search "<meaning>" --type <bgm|sfx|image|vector|icon|video>` pulls royalty-free, commercial-safe music/sound-effects/images/icons/stock-video from pixabay/openverse/iconify at $0 instead of billing AI music/image generation (see the `vidfarm-media` skill). No surprise AI spend.
|
|
54
54
|
- **hybrid** *(recommend this)* — free where it's free; pay for AI only where it clearly wins (a hero shot, a voice you can't fake locally).
|
|
55
55
|
- **pure-ai** — best quality; use AI image/video/voice/music freely.
|
|
56
56
|
|
|
@@ -1116,7 +1116,7 @@ If a local AI script rewrites text or scenes without consuming those files first
|
|
|
1116
1116
|
| `vidfarm visibility <forkId> <private\|public>` | `PATCH .../compositions/:forkId/visibility` | set visibility |
|
|
1117
1117
|
| `vidfarm clone <forkId>` | `POST .../compositions/:forkId/clone` | clone a fork |
|
|
1118
1118
|
| `vidfarm share-link <forkId>` | `POST .../compositions/:forkId/share-links` | mint a share URL |
|
|
1119
|
-
| `vidfarm approve --video <url\|file> --caption "…"` | `POST /api/v1/approved/posts` | approve post
|
|
1119
|
+
| `vidfarm approve --video <url\|file> --caption "…"` | `POST /api/v1/approved/posts` | approve post — takes media by **`url`**. Local `--video`/`--media` auto-upload via presign→S3→finalize (up to **200 MB**, bypasses the ~6 MB Lambda limit) then approve with that URL; prints `share_url` |
|
|
1120
1120
|
| `vidfarm posts` / `vidfarm post <id>` | `GET /api/v1/approved/posts[/:id]` | browse approved posts |
|
|
1121
1121
|
| `vidfarm schedule <postId> --at <iso> --to <dest>` | `POST /api/v1/approved/posts/:postId/schedules` | schedule a post |
|
|
1122
1122
|
| `vidfarm schedules <postId>` | `GET /api/v1/approved/posts/:postId/schedules` | browse scheduled posts |
|
|
@@ -1125,7 +1125,7 @@ If a local AI script rewrites text or scenes without consuming those files first
|
|
|
1125
1125
|
| `vidfarm cost-mode [minimize\|hybrid\|pure-ai]` | (local `~/.vidfarm/cost-mode.json`) | show/set the money-saving preference every billed command respects; no arg = show + explain the three; `--clear` forgets it |
|
|
1126
1126
|
| `vidfarm wallet [--job <id>\|--tracer <t>] [--limit <n>]` | `GET /api/v1/user/me/wallet` | cost log: balance + lifetime spend + recent charges. `--job <renderJobId>` prints **what that one video cost** (sums its charges); `--tracer <t>` sums a tracer. Cloud-only, paid plan. Aliases: `spend`, `costs` |
|
|
1127
1127
|
| `vidfarm provider-keys` / `vidfarm add-provider-key <p> <secret>` | `GET`·`POST /api/v1/user/me/provider-keys` | manage AI keys |
|
|
1128
|
-
| `vidfarm upload <file> [--folder <path>]` |
|
|
1128
|
+
| `vidfarm upload <file> [--folder <path>]` | presign → S3 PUT → finalize (`.../temporary-files/presign` + `.../temporary-files`) | upload → durable URL (ephemeral, 30-day TTL; prefer `--folder temp` for scratch). Goes direct to S3, so large files (up to **200 MB**) bypass the ~6 MB Lambda body limit |
|
|
1129
1129
|
| `vidfarm download <url> [dest]` | (streams any URL to disk) | download media |
|
|
1130
1130
|
| `vidfarm download-post <url> [--quality best\|hd\|full_hd]` | `POST /api/v1/primitives/videos/download` + poll | download a social/media post into a durable MP4 or slideshow |
|
|
1131
1131
|
| `vidfarm download-post-audio <url>` | `POST /api/v1/primitives/audio/download` + poll | download a social/media post's audio into a durable audio file |
|
|
@@ -1150,13 +1150,15 @@ If a local AI script rewrites text or scenes without consuming those files first
|
|
|
1150
1150
|
|
|
1151
1151
|
**Frontend URLs are first-class output.** Every command that has a human-openable page (editor, discover, approved-post preview, share link) prints that prod frontend URL as a highlighted line. `render --wait` polls to completion and prints the final MP4 URL; `approve` prints the approved-post `share_url`. Add `--json` to any command for pure JSON (agent-friendly, no banners).
|
|
1152
1152
|
|
|
1153
|
-
**Uploads/downloads/My-Files writes live in the devcli** because they are genuine multi-step / streaming flows: `upload`
|
|
1153
|
+
**Uploads/downloads/My-Files writes live in the devcli** because they are genuine multi-step / streaming flows: `upload` presigns the **ephemeral** temporary-files route, PUTs the bytes **direct to S3**, finalizes, and prints the durable URL to drop into a composition or approved post — going direct to S3 means large files (up to **200 MB**) never hit the ~6 MB Lambda request-body limit; `put-file` posts to the **persistent** My Files route (`/me/attachments/upload`) so context docs and brand assets live in the user's library (accepts a local file, `--content <text>`, or piped `--stdin` with `--as <name>`); `download` streams any Vidfarm/media URL to disk.
|
|
1154
|
+
|
|
1155
|
+
**Approving a locally rendered MP4 (the URL-first rule).** The approve route (`POST /api/v1/approved/posts`) and every media-taking route accept a `url`, never raw file bytes — so you never "upload to approve" in one shot. The correct sequence for a big local file is: (1) `POST /api/v1/user/me/temporary-files/presign` with `{ file_name, content_type, size_bytes }` → (2) PUT the raw bytes to the returned presigned S3 URL → (3) `POST /api/v1/user/me/temporary-files` (finalize) → use the returned durable URL as the approve media `url`. `vidfarm approve --video ./final.mp4` does all of this automatically. Never POST a large file as multipart to `.../temporary-files/upload` against the cloud host: that path proxies through Lambda and caps near 6 MB (it exists only as a fallback for local-storage `vidfarm serve` boxes).
|
|
1154
1156
|
|
|
1155
1157
|
## Cost mode — the devcli's money-saving guardrail
|
|
1156
1158
|
|
|
1157
|
-
`vidfarm cost-mode <minimize|hybrid|pure-ai>` records a single spend preference (in `~/.vidfarm/cost-mode.json`) that every **billed** command honors: `generate`, `music`, `decompose`, `inspiration-decompose`, `create`, `replicate`, `inpaint`, `create-overlay`, and the cloud paths of `render --target cloud`, `tts --cloud`, `stt --cloud`, `remove-greenscreen` (non-`--local`). FREE local engines never gate (`render` local default, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, all the file-editing verbs).
|
|
1159
|
+
`vidfarm cost-mode <minimize|hybrid|pure-ai>` records a single spend preference (in `~/.vidfarm/cost-mode.json`) that every **billed** command honors: `generate`, `music`, `decompose`, `inspiration-decompose`, `create`, `replicate`, `inpaint`, `create-overlay`, and the cloud paths of `render --target cloud`, `tts --cloud`, `stt --cloud`, `remove-greenscreen` (non-`--local`). FREE local engines never gate (`render` local default, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, all the file-editing verbs). `vidfarm media search` (the free stock catalog — pixabay/openverse/iconify music, SFX, images, icons, stock video) is also free and never gates.
|
|
1158
1160
|
|
|
1159
|
-
- **minimize** — a billed op is **refused** unless you add `--yes`; the error names the free local alternative. Use this to guarantee no surprise AI spend.
|
|
1161
|
+
- **minimize** — a billed op is **refused** unless you add `--yes`; the error names the free local alternative. Use this to guarantee no surprise AI spend. Before paying to generate music, sound effects, or images, try `vidfarm media search "<meaning>" --type <bgm|sfx|image|vector|icon|video>` first — free royalty-free assets instead of a billed `music`/`generate` call.
|
|
1160
1162
|
- **hybrid** *(default recommendation)* — billed ops run but print a one-line cost notice each.
|
|
1161
1163
|
- **pure-ai** — billed ops run without gating; cost is still printed.
|
|
1162
1164
|
|
|
@@ -1625,7 +1627,9 @@ Use this when a coding agent is doing the work locally or the user wants a repro
|
|
|
1625
1627
|
4. Make deterministic edits to `composition.html` and optionally `composition.json`.
|
|
1626
1628
|
5. Validate with `vidfarm lint` or `vidfarm stills` when useful.
|
|
1627
1629
|
6. Render with `vidfarm render <forkId> --dir ./work --wait`.
|
|
1628
|
-
7. Approve the finished MP4 with `vidfarm approve --video <url> --caption "..."`.
|
|
1630
|
+
7. Approve the finished MP4 with `vidfarm approve --video <url|./final.mp4> --caption "..."`. This prints the shareable `share_url`.
|
|
1631
|
+
|
|
1632
|
+
**Approving a locally rendered file → cloud preview link.** Approve takes media by **URL**, not bytes. A local MP4 must become a URL first: the devcli presigns, PUTs the bytes **direct to S3**, finalizes, then approves with that URL — so `vidfarm approve --video ./final.mp4` handles files up to **200 MB** and bypasses the ~6 MB Lambda request-body limit. By raw REST: `POST /api/v1/user/me/temporary-files/presign` → PUT to the returned S3 URL → `POST /api/v1/user/me/temporary-files` (finalize) → pass the durable URL in the approve `media` array. Do not multipart-POST a big file to `.../temporary-files/upload` against the cloud host (Lambda-bound, ~6 MB cap).
|
|
1629
1633
|
|
|
1630
1634
|
Prefer this path for batch work, CI-like edits, or when the user wants free local rendering through `vidfarm serve`.
|
|
1631
1635
|
|
package/dist/src/cli.js
CHANGED
|
@@ -508,7 +508,10 @@ Approve & schedule (publish a finished MP4 as a shareable post):
|
|
|
508
508
|
--media <url|file> Extra media (repeatable; local files auto-upload to temp/)
|
|
509
509
|
--folder <path> Temp-store folder for uploaded local files (default: temp)
|
|
510
510
|
--caption <text> Post caption (required)
|
|
511
|
-
|
|
511
|
+
Approve takes media by URL; a local file is
|
|
512
|
+
presigned + PUT straight to S3 + finalized into a
|
|
513
|
+
durable URL first (up to 200 MB — bypasses the ~6 MB
|
|
514
|
+
Lambda body limit), so a locally rendered MP4 works:
|
|
512
515
|
'vidfarm approve --video ./final.mp4 --caption "..."'
|
|
513
516
|
posts List your approved posts → GET /api/v1/approved/posts
|
|
514
517
|
post <postId> Read one approved post (prints share URL) → GET /api/v1/approved/posts/:postId
|
|
@@ -532,10 +535,12 @@ Agent skill (install the latest director skill so your AI agent can act):
|
|
|
532
535
|
(copy/share artifact; not the canonical install)
|
|
533
536
|
|
|
534
537
|
Files (multi-step flows the devcli handles for you):
|
|
535
|
-
upload <file> Upload to the EPHEMERAL temp store, print URL →
|
|
538
|
+
upload <file> Upload to the EPHEMERAL temp store, print URL → presign + S3 PUT + finalize
|
|
536
539
|
--folder <path> Namescope the upload under a folder. Prefer a
|
|
537
540
|
throwaway 'temp' folder (--folder temp) for scratch
|
|
538
541
|
media so it never clutters your durable library.
|
|
542
|
+
Bytes go direct to S3, so large files (up to 200 MB)
|
|
543
|
+
skip the ~6 MB Lambda request-body limit.
|
|
539
544
|
put-file [file] Write a file INTO My Files (persistent lib) → POST /api/v1/user/me/attachments/upload
|
|
540
545
|
--folder <path> Namescope under a product/offer folder (e.g. acme-skincare)
|
|
541
546
|
--as <name> Name the saved file (required for --content/--stdin)
|
|
@@ -3412,11 +3417,65 @@ function serveStorageUrl(baseUrl, key) {
|
|
|
3412
3417
|
}
|
|
3413
3418
|
// Upload a local file to the EPHEMERAL temp store on ctx.host, optionally
|
|
3414
3419
|
// namescoped under a folder, and return its durable URL.
|
|
3420
|
+
//
|
|
3421
|
+
// Prefers the presign → PUT-to-S3 → finalize path so the bytes never traverse
|
|
3422
|
+
// the Lambda request body (which caps at ~6 MB). This is what lets a locally
|
|
3423
|
+
// rendered MP4 (up to 200 MB) be uploaded to the cloud for approval + a
|
|
3424
|
+
// shareable preview link. Falls back to the multipart POST when the box runs on
|
|
3425
|
+
// local storage (presign transport === "server"), where there is no S3.
|
|
3415
3426
|
async function uploadLocalToTempStore(ctx, absPath, folder) {
|
|
3416
3427
|
if (!existsSync(absPath))
|
|
3417
3428
|
throw new Error(`No such local file: ${absPath}`);
|
|
3429
|
+
const buffer = readFileSync(absPath);
|
|
3430
|
+
const fileName = path.basename(absPath);
|
|
3431
|
+
const contentType = guessContentType(fileName);
|
|
3432
|
+
// Step 1: presign. On an S3-backed box this returns a direct PUT URL.
|
|
3433
|
+
const presign = await fetch(new URL("/api/v1/user/me/temporary-files/presign", ctx.host), {
|
|
3434
|
+
method: "POST",
|
|
3435
|
+
headers: { ...buildAuthHeaders(ctx.auth), "content-type": "application/json" },
|
|
3436
|
+
body: JSON.stringify({ file_name: fileName, content_type: contentType, size_bytes: buffer.byteLength, folder_path: folder })
|
|
3437
|
+
});
|
|
3438
|
+
const presignJson = await presign.json().catch(() => null);
|
|
3439
|
+
if (presign.ok && presignJson?.transport === "presigned" && presignJson?.upload?.url) {
|
|
3440
|
+
// Step 2a: PUT the bytes straight to S3 (bypasses the Lambda body limit).
|
|
3441
|
+
const put = await fetch(presignJson.upload.url, {
|
|
3442
|
+
method: presignJson.upload.method || "PUT",
|
|
3443
|
+
headers: presignJson.upload.headers || {},
|
|
3444
|
+
body: new Uint8Array(buffer)
|
|
3445
|
+
});
|
|
3446
|
+
if (!put.ok)
|
|
3447
|
+
throw new Error(`local media upload failed with HTTP ${put.status}.`);
|
|
3448
|
+
// Step 3: finalize — record the temp file now that the bytes have landed.
|
|
3449
|
+
const finalize = await fetch(new URL("/api/v1/user/me/temporary-files", ctx.host), {
|
|
3450
|
+
method: "POST",
|
|
3451
|
+
headers: { ...buildAuthHeaders(ctx.auth), "content-type": "application/json" },
|
|
3452
|
+
body: JSON.stringify({
|
|
3453
|
+
file_id: presignJson.file_id,
|
|
3454
|
+
file_name: presignJson.file_name || fileName,
|
|
3455
|
+
content_type: presignJson.content_type || contentType,
|
|
3456
|
+
size_bytes: buffer.byteLength,
|
|
3457
|
+
storage_key: presignJson.storage_key,
|
|
3458
|
+
folder_path: presignJson.folder_path
|
|
3459
|
+
})
|
|
3460
|
+
});
|
|
3461
|
+
const finalizeText = await finalize.text();
|
|
3462
|
+
let finalizeJson = null;
|
|
3463
|
+
try {
|
|
3464
|
+
finalizeJson = finalizeText ? JSON.parse(finalizeText) : null;
|
|
3465
|
+
}
|
|
3466
|
+
catch {
|
|
3467
|
+
finalizeJson = null;
|
|
3468
|
+
}
|
|
3469
|
+
if (!finalize.ok)
|
|
3470
|
+
throw new Error(`local media upload finalize failed (${finalize.status}): ${finalizeText.slice(0, 200)}`);
|
|
3471
|
+
const url = readUploadedFileUrl(finalizeJson) ?? presignJson.view_url ?? presignJson.s3_url ?? null;
|
|
3472
|
+
if (!url)
|
|
3473
|
+
throw new Error("local media upload returned no durable URL.");
|
|
3474
|
+
return url;
|
|
3475
|
+
}
|
|
3476
|
+
// Step 2b (fallback): local-storage box — POST the bytes as multipart form.
|
|
3418
3477
|
const form = new FormData();
|
|
3419
|
-
form.append("file", new Blob([
|
|
3478
|
+
form.append("file", new Blob([buffer], { type: contentType }), fileName);
|
|
3420
3479
|
if (folder)
|
|
3421
3480
|
form.append("folder_path", folder);
|
|
3422
3481
|
const res = await fetch(new URL("/api/v1/user/me/temporary-files/upload", ctx.host), { method: "POST", headers: buildAuthHeaders(ctx.auth), body: form });
|
|
@@ -4599,7 +4658,7 @@ async function runMediaCommand(argv) {
|
|
|
4599
4658
|
if (!items.length) {
|
|
4600
4659
|
console.log(`${DIM}No results. Try a broader query, another --type, or a specific --provider (pixabay|openverse|iconify).${RESET}`);
|
|
4601
4660
|
if (type === "image" || type === "vector" || type === "video") {
|
|
4602
|
-
console.log(`${DIM}(photos/vectors/video need
|
|
4661
|
+
console.log(`${DIM}(photos/vectors/video need YOUR OWN free Pixabay key — get one at https://pixabay.com/api/docs/ then save it: 'vidfarm add-provider-key pixabay <key>'. openverse covers CC images keyless.)${RESET}`);
|
|
4603
4662
|
}
|
|
4604
4663
|
return;
|
|
4605
4664
|
}
|
|
@@ -5868,7 +5927,7 @@ async function runAddProviderKeyCommand(argv) {
|
|
|
5868
5927
|
const provider = parsed.positionals[0];
|
|
5869
5928
|
const secret = parsed.positionals[1];
|
|
5870
5929
|
if (!provider || !secret)
|
|
5871
|
-
throw new Error("add-provider-key requires <provider> <secret> (provider: openai|gemini|openrouter|perplexity|nvidia|elevenlabs).");
|
|
5930
|
+
throw new Error("add-provider-key requires <provider> <secret> (provider: openai|gemini|openrouter|perplexity|nvidia|elevenlabs|pixabay).");
|
|
5872
5931
|
const ctx = commonContext(parsed.values);
|
|
5873
5932
|
const result = await apiRequest({ method: "POST", host: ctx.host, path: "/api/v1/user/me/provider-keys", auth: ctx.auth, body: { provider, secret, label: parsed.values.label } });
|
|
5874
5933
|
assertApiOk(result, "add-provider-key");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@officexapp/vidfarm-devcli",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.11",
|
|
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": {
|