@mevdragon/vidfarm-devcli 0.18.0 → 0.18.1

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 (53) hide show
  1. package/.agents/skills/vidfarm-media/SKILL.md +41 -4
  2. package/SKILL.director.md +17 -3
  3. package/SKILL.platform.md +3 -3
  4. package/demo/dist/app.js +69 -69
  5. package/demo/dist/favicon.ico +0 -0
  6. package/dist/src/app.js +1550 -187
  7. package/dist/src/cli.js +227 -7
  8. package/dist/src/devcli/clip-store.js +29 -2
  9. package/dist/src/devcli/clips.js +52 -9
  10. package/dist/src/devcli/composition-edit.js +262 -0
  11. package/dist/src/devcli/timeline-edit.js +283 -0
  12. package/dist/src/editor-chat.js +29 -6
  13. package/dist/src/frontend/discover-client.js +130 -0
  14. package/dist/src/frontend/discover-store.js +23 -0
  15. package/dist/src/frontend/file-directory.js +744 -0
  16. package/dist/src/frontend/template-editor-chat.js +22 -19
  17. package/dist/src/landing-page.js +24 -7
  18. package/dist/src/page-shell.js +25 -1
  19. package/dist/src/reskin/agency-page.js +1 -1
  20. package/dist/src/reskin/calendar-page.js +2 -1
  21. package/dist/src/reskin/chat-page.js +319 -72
  22. package/dist/src/reskin/discover-page.js +486 -31
  23. package/dist/src/reskin/document.js +1171 -384
  24. package/dist/src/reskin/help-page.js +1 -0
  25. package/dist/src/reskin/inpaint-page.js +10 -4
  26. package/dist/src/reskin/library-page.js +918 -220
  27. package/dist/src/reskin/login-page.js +6 -6
  28. package/dist/src/reskin/pricing-page.js +2 -0
  29. package/dist/src/reskin/settings-page.js +55 -10
  30. package/dist/src/reskin/theme.js +337 -17
  31. package/dist/src/services/clip-curation/gemini.js +5 -0
  32. package/dist/src/services/clip-curation/hunt.js +79 -1
  33. package/dist/src/services/clip-curation/index.js +2 -1
  34. package/dist/src/services/clip-curation/local-agent.js +4 -3
  35. package/dist/src/services/clip-curation/media-select.js +85 -0
  36. package/dist/src/services/clip-curation/query.js +5 -1
  37. package/dist/src/services/clip-curation/refine.js +50 -20
  38. package/dist/src/services/clip-curation/scan.js +10 -3
  39. package/dist/src/services/clip-curation/taxonomy.js +3 -1
  40. package/dist/src/services/clip-curation/taxonomy.v1.json +13 -1
  41. package/dist/src/services/clip-records.js +14 -1
  42. package/dist/src/services/clip-search.js +43 -13
  43. package/dist/src/services/file-directory.js +114 -0
  44. package/dist/src/services/storage.js +24 -1
  45. package/dist/src/services/upstream.js +5 -5
  46. package/dist/src/template-editor-shell.js +16 -2
  47. package/package.json +1 -1
  48. package/public/assets/discover-client-app.js +1 -0
  49. package/public/assets/file-directory-app.js +2 -0
  50. package/public/assets/homepage-client-app.js +12 -12
  51. package/public/assets/page-runtime-client-app.js +24 -24
  52. package/src/assets/favicon.ico +0 -0
  53. package/src/assets/logo-vidfarm.png +0 -0
@@ -72,15 +72,52 @@ Voices, styles, formats, and the exact word-timestamp paths: `references/tts.md`
72
72
  ## BGM and SFX resolution order
73
73
 
74
74
  1. **User file / URL** — `bgm.file` / `bgm.url` in the request; copied or downloaded into `assets/bgm/`.
75
- 2. **My Files** — `vidfarm files --search "<query> music"` (needs `$VIDFARM_API_KEY`); the user's own uploaded library, searchable by meaning when notes are annotated.
75
+ 2. **File directory** — `vidfarm directory search "<query> music"` or the legacy `vidfarm files --search` (needs `$VIDFARM_API_KEY`); the user's own library, searchable by meaning when notes are annotated.
76
76
  3. **Local dir** — `$MUSIC_DIR` / `$SFX_DIR` (or request `bgm.dir` / `sfx_dir`), picked by filename keyword match against the query.
77
77
  4. **Skip, with a note** — a silent bed or a missing whoosh is fine; a stock-audio account is not. The meta's `notes[]` says exactly what was skipped and why.
78
78
 
79
+ ## The file directory (My Files + raws, unified)
80
+
81
+ Everything the user owns is one navigable virtual directory (source of truth: `src/services/file-directory.ts`). Three canonical roots, each with nested subfolders; paths are canonical, copyable, and the display/navigation layer — a file's real handle is still its backend id.
82
+
83
+ | Root | What it holds | Subfolder convention | Lifecycle |
84
+ | --- | --- | --- | --- |
85
+ | `/files` | durable personal files (My Files attachments) | free-form | permanent; vector-searchable via `notes` |
86
+ | `/temp` | scratch space | auto-organized into date folders `YYYY-MM-DD` | auto-deleted after 30 days |
87
+ | `/raws` | reusable clip / source-footage library | by source, `/raws/<source-slug>/` | permanent |
88
+
89
+ Example canonical path: `/raws/product-demos/clip.mp4`.
90
+
91
+ **devcli** (needs `$VIDFARM_API_KEY`):
92
+
93
+ ```bash
94
+ vidfarm directory ls [path] # browse a folder; path defaults to "/" (lists the three roots)
95
+ vidfarm directory search "<query>" [--path <scope>] [--mode auto|semantic|substring|path] [--limit N]
96
+ ```
97
+
98
+ **REST** (cookie/api-key authed, on the vidfarm server):
99
+
100
+ - `GET /api/v1/user/me/directory?path=/raws/demos` → `{ path, root, folders[], files[], next_offset }` (path `/` lists the three roots).
101
+ - `POST /api/v1/user/me/directory/search` `{ query, path?, mode?, limit? }` → `{ results[], query, mode, scope, semantic }`. `mode`: `auto` (default = semantic vector + substring + absolute-path, combined), `semantic`, `substring`, `path`.
102
+ - `PATCH /raws/:clipId` `{ folder_path }` → move a raw between folders (`""` = raws root).
103
+
104
+ Every list/search item is the canonical **DirectoryItem**:
105
+
106
+ ```jsonc
107
+ { "path": "/raws/demos/clip.mp4", "root": "raws", "folderPath": "/raws/demos",
108
+ "name": "clip.mp4", "kind": "file", // or "folder"
109
+ "id": "clip_…", "contentType": "video/mp4", // id/contentType present for files
110
+ "viewUrl": "https://…", "thumbUrl": "https://…",
111
+ "sizeBytes": 0, "durationSec": 0, "meta": {} }
112
+ ```
113
+
114
+ Legacy commands still work and map onto the same directory: `vidfarm files --search` / `vidfarm put-file` / `vidfarm annotate-file <id> --notes "…"` (annotate so semantic search finds an asset later) live under `/files`; `vidfarm raws search` / `vidfarm raws scan <video>` populate and query `/raws`.
115
+
79
116
  ## Sourcing images and footage
80
117
 
81
- Same ownership rule, different surfaces:
118
+ Same ownership rule, resolved through the file directory:
82
119
 
83
- - **My Files** — `vidfarm files --search "<meaning>"`; annotate assets with `vidfarm annotate-file <id> --notes "…"` so search finds them later; write with `vidfarm put-file`.
84
- - **Raws library** — `vidfarm raws search "<text>"` over raws hunted from the user's long-form footage (`vidfarm raws scan <video>`).
120
+ - **Search what the user has** — `vidfarm directory search "<meaning>"` (scope with `--path /raws` or `--path /files`); or the legacy `vidfarm files --search` / `vidfarm raws search`.
121
+ - **Populate raws** — `vidfarm raws scan <video>` hunts reusable raws out of the user's long-form footage into `/raws/<source>/`.
85
122
  - **Generate on the user's keys** — `vidfarm generate image|video --prompt "…"` (BYOK primitives; `--place <dir>` drops the result straight into a composition).
86
123
  - Never hotlink or scrape third-party stock; a capture workflow's own screenshots and the user's brand assets are always fair game.
package/SKILL.director.md CHANGED
@@ -127,7 +127,7 @@ The editor is a full timeline surface:
127
127
  - **Timeline** — multi-track, drag/trim/split clips, group/ungroup layers
128
128
  - **Inspector panel** — per-layer property editor (text, color, font, position, size, media source, playback start, volume). Image layers also expose an **Animate (Ken Burns)** dropdown — a slow pan/zoom over the clip's full duration (see below). Text/caption layers expose an **Animated captions** preset picker — word-by-word TikTok/CapCut caption styles (see below) — plus active-word color, highlight color, and uppercase controls.
129
129
  - **Canvas frame overlay** — 8-handle resize + WYSIWYG positioning
130
- - **Chat panel** — AI assistant that can programmatically edit the composition (`add_layer`, `remove_layer`, `set_layer_timing/visual/style/media/text`, `set_captions`, `group_layers`, `generate_layer`, `replace_composition_html`, ...). The assistant uses the caller's saved provider keys. **`generate_layer`** is the one-shot "generate AI media and place it": it submits the `/videos/generate` or `/images/generate` primitive job, drops a placeholder clip into the target slot (fill a blank gap, replace a scene, or overlay), and auto-swaps in the finished media when the job settles — no manual polling. In-flight generations surface in `editor_context.pending_generations`.
130
+ - **Chat panel** — AI assistant that can programmatically edit the composition (`add_layer`, `remove_layer`, `set_layer_timing/visual/style/media/text`, `set_layer_keyframes` — script-free CSS keyframe motion on one layer — `set_captions`, `group_layers`, `nudge_layers`, `ripple_edit`, `trim_layer`, `set_layer_zindex`, `generate_layer`, `replace_composition_html`, ...). The assistant uses the caller's saved provider keys, and reads an enriched `editor_context` (per-layer transitions / Ken Burns / custom-animation state; `recent_action_results` now reports successes as well as failures). See "Script-free keyframe motion & timeline verbs" below for the newer motion/timeline actions. **`generate_layer`** is the one-shot "generate AI media and place it": it submits the `/videos/generate` or `/images/generate` primitive job, drops a placeholder clip into the target slot (fill a blank gap, replace a scene, or overlay), and auto-swaps in the finished media when the job settles — no manual polling. In-flight generations surface in `editor_context.pending_generations`.
131
131
  - **Auto-save** — every commit writes composition.html + composition.json to the working state. No explicit save button.
132
132
 
133
133
  The editor persists to:
@@ -319,6 +319,20 @@ Four surfaces:
319
319
 
320
320
  The plain STT primitive grew the same hook: `POST /api/v1/primitives/audio/transcribe` accepts `word_timestamps: true` to attach word-level timings to each transcript segment (real on an OpenAI key via whisper-1; other providers stay segment-level).
321
321
 
322
+ ### Script-free keyframe motion & timeline verbs
323
+
324
+ Beyond the Ken Burns / transition / animated-caption presets, the copilot can hand-author motion and re-time layers directly (all `editor_action` verbs; all preview AND render identically):
325
+
326
+ - **`set_layer_keyframes`** — author a script-free CSS `@keyframes` animation on ONE layer: `opacity`, `translate_x` / `translate_y` (% of the layer's own box), `scale`, and `rotate` (deg), keyed across the clip. Params: `layer_key`, `keyframes: [{ offset 0..1, opacity?, translate_x?, translate_y?, scale?, rotate? }]`, optional `keyframe_easing`, optional `keyframe_duration` (defaults to the clip's duration). This is the durable way to hand-craft motion beyond the Ken Burns / transition / caption preset vocabulary.
327
+ - **`nudge_layers`** — relative timeline move: `layer_key` (or `layer_keys`) + `delta_start` (seconds) and/or `delta_track` (lanes). Group-aware.
328
+ - **`ripple_edit`** — insert (`delta_start > 0`) or close (`delta_start < 0`) time at `at_time`, shifting every downstream clip.
329
+ - **`trim_layer`** — move one edge: `edge=start|end` + `to_time` (seconds). A left trim also advances the media in-point for video/audio.
330
+ - **`set_layer_zindex`** — restack: `z_order=front|back|forward|backward` (stacking == track index) or an explicit `track`.
331
+
332
+ **Web editor = declarative/CSS motion only.** In the web editor, motion is CSS-only. The JS runtime adapters (anime.js, GSAP, Lottie, Three.js, TypeGPU) are a **local-devcli-only** capability — they are stripped from the composition on save, and `replace_composition_html` now **rejects `<script>`-bearing HTML** in the web editor. On the web, author motion with CSS `@keyframes` (via `set_layer_keyframes` or a `<style>` block) plus the preset vocabulary (Ken Burns, transitions, animated captions). Scripted / adapter-driven compositions belong to the desktop `vidfarm serve` (or pulled-fork) flow, which renders them correctly through `vidfarm render`.
333
+
334
+ **Enriched editor context.** The per-layer snapshot the copilot reads now includes each layer's `transition` / `transition_out` / `transition_duration`, its `ken_burns` preset, and `animation` (the custom CSS keyframe name it authored), and `recent_action_results` now reports successes (`ok: true` + a summary) as well as failures — so the model can see the effect of what it just did instead of only what broke. For exact markup it can also `GET /api/v1/compositions/:forkId/composition.html`.
335
+
322
336
  ### Local file paths as media (skip the S3 upload)
323
337
 
324
338
  `vidfarm place --src` (and `approve --video/--media`) accept a **local file path**, not just a URL — so a power user bulk-building compositions from a folder of clips on their own machine never has to upload every asset to Vidfarm storage:
@@ -591,7 +605,7 @@ When a director has a **recurring character** that must look the same across vid
591
605
 
592
606
  **Assume My Files is multi-offer.** A user often runs more than one product, offer, brand, or region, and namescopes assets into folders accordingly — by product (`acme-skincare/`, `zensleep/`), by offer/campaign (`summer-sale/`), by region (`us/`, `eu/`), by asset type (`logos/`, `ugc-clips/`), or any arbitrary scheme. There is **no fixed layout** — read the `folders` tree first and reason about how this user organized things. Before pulling assets for a task, infer which folder(s) match the product/offer/region the current composition is about (match folder names to the composition title, video context, and what the user said), then scope reads to that folder so you never mix one brand's logo/product-shot/music into another brand's video. If the target offer is ambiguous, ask which product/offer/region (or which folder) this work is for rather than guessing across offers.
593
607
 
594
- In the editor web copilot the same filesystem is exposed via the **`browse_files` tool** (`action=list` / `action=search` / `action=read` / `action=write` / `action=annotate`), so the copilot follows the identical reasoning: search or list to find the right offer's folder, then read an asset — or `write` a text doc (About.md, awareness-levels.md, …) or import a media URL (`source_url`) into that folder, annotating anything worth finding again. The devcli equivalents are `vidfarm files [--search]` / `get-file` / `put-file [--notes]` / `annotate-file`.
608
+ In the editor web copilot the same filesystem is exposed via the **`browse_files` tool** (`action=list` / `action=search` / `action=read` / `action=write` / `action=annotate`), so the copilot follows the identical reasoning: search or list to find the right offer's folder, then read an asset — or `write` a text doc (About.md, awareness-levels.md, …) or import a media URL (`source_url`) into that folder, annotating anything worth finding again. `browse_files list` defaults to `path='/'` when `path` is omitted, so the `/raws` (hunted raws) and `/temp` (scratch) roots surface alongside the My Files folders instead of being hidden; a `/raws` listing also accepts a `content_type` filter (exact shot-kind — `talking_head`, `b_roll`, `product_shot`, `screen_recording`, …), and every listing paginates via `offset` / `limit`. The devcli equivalents are `vidfarm files [--search]` / `get-file` / `put-file [--notes]` / `annotate-file`.
595
609
 
596
610
  ## Local dev loop (`vidfarm serve`)
597
611
 
@@ -645,7 +659,7 @@ Import by task:
645
659
  Ground rules:
646
660
 
647
661
  - **Local coding agents** (Claude Code / Codex on a `vidfarm serve` box or a pulled fork) get the FULL packs — install, read, and follow them. Scripted/GSAP compositions authored this way render correctly through `vidfarm render` (local and cloud); note that the **web editor strips `<script>` on save**, so keep compositions that must round-trip through the browser editor declarative (the built-in Ken Burns / transitions / animated-captions vocabulary).
648
- - **The web copilot** never installs packs — it has a `load_skill` tool that reads the same content on demand from the vidfarm mirror. Nothing to do there; it is already wired.
662
+ - **The web copilot** never installs packs — it has a `load_skill` tool that reads the same content on demand from the vidfarm mirror (served from `.agents/skills/`; the `.claude/skills/*` entries are just Claude Code discovery symlinks to the same dirs). It can `load_skill` any hyperframes pack (`hyperframes`, `hyperframes-core`, `hyperframes-animation`, `hyperframes-keyframes`, `hyperframes-creative`, `hyperframes-cli`) plus the workflow packs (`embedded-captions`, `product-launch-video`, `faceless-explainer`, `website-to-video`, `general-video`, `motion-graphics`, `slideshow`, `talking-head-recut`, `vidfarm-media`). Nothing to install; it is already wired. **In the web editor, apply only the CSS / `@keyframes` + declarative-preset half of `hyperframes-animation` / `hyperframes-keyframes`** — their JS-adapter techniques are devcli-only (script is stripped on save).
649
663
  - Vidfarm-managed environments set `HYPERFRAMES_SKIP_SKILLS=1` and `HYPERFRAMES_NO_TELEMETRY=1`, so `npx hyperframes init` never overwrites the vidfarm-scrubbed packs and the bundled CLI never phones home. Do not run `hyperframes auth`, `hyperframes cloud`, `hyperframes publish`, `hyperframes play`, or `hyperframes feedback` — vidfarm's own render/share/telemetry surfaces cover all of them.
650
664
 
651
665
  ## What NOT to do
package/SKILL.platform.md CHANGED
@@ -191,7 +191,7 @@ The same runtime is used inside the Trackpad Editor iframe AND when a published
191
191
  - Renders the composition into a preview iframe using a `blob:` URL
192
192
  - Bidirectionally syncs timeline drag → HTML `data-start`/`data-duration` mutations, and inspector edits → text/style changes
193
193
  - Persists every commit back to the API as PUT `composition.html` and PATCH `composition.json`
194
- - Exposes an **Editor Action API** so the Chat panel can invoke typed actions (`add_layer`, `remove_layer`, `set_layer_start`, `set_layer_duration`, `set_layer_style`, `set_captions` — animated word-by-word caption runs — `group_layers`, `ungroup_layers`, `replace_composition_html`, etc.)
194
+ - Exposes an **Editor Action API** so the Chat panel can invoke typed actions (`add_layer`, `remove_layer`, `set_layer_start`, `set_layer_duration`, `set_layer_style`, `set_layer_keyframes` — script-free CSS `@keyframes` motion on one layer (opacity/translate/scale/rotate over the clip) — `set_captions` — animated word-by-word caption runs — `group_layers`, `ungroup_layers`, `nudge_layers` (relative timeline move), `ripple_edit` (insert/close time at a point, shifting downstream clips), `trim_layer` (move one edge; a left-trim advances the media in-point), `set_layer_zindex` (restack == track index), `replace_composition_html`, etc.)
195
195
  - Runs the composition-runtime script inside the preview iframe to keep timeline scrub in sync with the current time. That runtime also scrubs **Ken Burns** image animations (CSS pan/zoom on `[data-kenburns]` `<img>` layers) and **animated captions** (word-by-word `vf-cap-*` animations on `[data-caption-animation]` caption layers — Ken Burns normalizes to clip progress, captions seek to the absolute offset `time − data-start` because each word's inline delay/duration is its spoken window) to the playhead, so the preview matches the render frame-for-frame. Presets and keyframes live in `src/hyperframes/composition.ts` (`KEN_BURNS_CSS`, `CAPTION_ANIM_CSS` + `CAPTION_STYLE_PRESETS`), mirrored into the editor bundle and the serve-time HTML rewrite; `normalizePublishHtml` re-injects both stylesheets (marker-guarded) into legacy compositions at render time. Caption gotcha: the layer's inline `text-transform`/`overflow` must be set on the element (the stylesheet's `[data-caption-uppercase]` rule loses to inline styles), and cue text edits must rebuild the word spans (`refreshCaptionWordSpans`) rather than setting `textContent`. Speech→captions REST surfaces: `primitive:captions` (`POST /api/v1/primitives/audio/captions`, src/primitive-registry.ts) is the composition-agnostic job — STT with `wordTimestamps` (openai/whisper-1 real, others estimated via `estimateCaptionWords`) paged by `src/services/captions.ts` into cues + a ready `set_captions_action`; `POST /api/v1/compositions/:forkId/captions` (src/app.ts, next to the composition.html PUT) is the one-step variant that resolves the fork's narration, transcribes on raw customer keys (`loadCustomerVisionKeys` + `transcribeSpeechWithKey`), applies via `insertCaptionLayers` (shared with the devcli), and persists through the same sanitize path — intentionally NOT watch-suppressed so serve tabs live-morph; not for open cloud editor sessions (auto-save clobber). Scrub-stability guard (both `syncCaptionClip` copies — `src/composition-runtime.ts` + the editor's `runtimeScript()`): Chrome permanently drops a CSS-paused `fill:none` animation once its `currentTime` is pushed past the effect end, so the scrub parks `fill:none` word animations at 0 when the playhead is past their window (visually identical resting state, keeps them alive for scrub-back) while `fill:forwards` presets overshoot to hold their final keyframe; missing word animations are restarted by cycling `animation-name` through `none`. Live element patches that rebuild word spans must re-scrub via the `__player.syncCaptions()` runtime API (the editor calls it after `patchLiveElement`/`previewLiveElement`) or fresh spans sit paused-at-0 (invisible for word-pop/stack-up) until the next seek.
196
196
 
197
197
  The editor is a single monolithic React component (~9k lines). It builds cleanly to a static bundle deployed to CloudFront under `/editor/`.
@@ -266,7 +266,7 @@ Configuration: `GHOSTCUT_KEY` and `GHOSTCUT_SECRET` env vars per stage. Pass-thr
266
266
 
267
267
  The chat model uses the **caller's provider keys**. No Vidfarm-owned model calls happen in this path.
268
268
 
269
- Beyond the Editor Action API, the chat harness registers shared tools defined in **two synced copies** (`src/app.ts` for the in-process dev server and `infra/lambda/editor-chat.ts` for the deployed Lambda — keep them in lockstep): `http_request` (allowlisted REST/primitive calls), `frontend_action` (tracer/thread state), `video_context`, `load_skill`, and `browse_files`. `load_skill` is the copilot's knowledge-on-demand seam: it fetches vendored skill-pack content (SKILL.md or a reference file) from the host's public `/skill/:name` + `/skill-pack/:name/files/*` mirror routes (backed by `.agents/skills/`, shipped in the api Docker image), size-capped, with the pack catalog + one-liners defined once in `EDITOR_CHAT_SKILL_PACKS` (`src/editor-chat.ts`) and shared by both copies. The stream-loop guardrails (`redactUnverifiedJobIds`, `buildEditorChatGuardrailNotice`, async-job handoff text) also live in `src/editor-chat.ts` and are imported by both copies — never re-implement them locally. `editor_action`'s `replace_composition_html` is lint-gated in the tool `execute` via `src/services/composition-lint.ts` (a rejected result carries `rejected: true` + `lint_errors` and omits `action_type`, so the browser never applies it). `browse_files` reads **and writes** the caller's My Files library — `action=list`/`search`/`read`/`write`/`annotate`. `write` saves a text file (md/txt/csv/json/srt/vtt) via `POST /me/attachments/upload`, or **imports a durable media URL** into the library when `source_url` is passed (how a generated `character_sprite_card.png` gets persisted instead of stranding in a job result). `annotate` sets free-form metadata `notes` on any entry (`PATCH /me/attachments/:id`); notes are **vector-embedded server-side** (BYOK, same gemini→openai auto-pick seam as clip search; no key fail-softs to keyword-only) and `search` runs the hybrid keyword+semantic lookup over every file's name/folder/notes via `POST /me/attachments/search` — the My Files mirror of `/raws/search`. Write+annotate are what let the copilot persist Getting Started context (About.md, awareness-levels.md, persuasive-angles.md, ad-hooks.md) and character-consistency pairs (`about_character.md` + `character_sprite_card.png`) namescoped under a product/offer folder; the system prompt in `src/editor-chat.ts` drives when to save and annotate. The devcli mirrors are `vidfarm put-file [--notes]`, `annotate-file`, and `files --search`.
269
+ Beyond the Editor Action API, the chat harness registers shared tools defined in **two synced copies** (`src/app.ts` for the in-process dev server and `infra/lambda/editor-chat.ts` for the deployed Lambda — keep them in lockstep): `http_request` (allowlisted REST/primitive calls), `frontend_action` (tracer/thread state), `video_context`, `load_skill`, and `browse_files`. `load_skill` is the copilot's knowledge-on-demand seam: it fetches vendored skill-pack content (SKILL.md or a reference file) from the host's public `/skill/:name` + `/skill-pack/:name/files/*` mirror routes (backed by `.agents/skills/`, shipped in the api Docker image), size-capped, with the pack catalog + one-liners defined once in `EDITOR_CHAT_SKILL_PACKS` (`src/editor-chat.ts`) and shared by both copies. Loadable packs are the hyperframes suite (`hyperframes`, `hyperframes-core`, `hyperframes-animation`, `hyperframes-keyframes`, `hyperframes-creative`, `hyperframes-cli`) plus the workflow packs (`embedded-captions`, `product-launch-video`, `faceless-explainer`, `website-to-video`, `general-video`, `motion-graphics`, `slideshow`, `talking-head-recut`, `vidfarm-media`); the `.claude/skills/*` entries are just Claude Code discovery symlinks to the same `.agents/skills/` dirs. In the web editor the copilot applies only the CSS / `@keyframes` + declarative-preset half of the animation/keyframes packs — their JS-adapter techniques are devcli-only. The stream-loop guardrails (`redactUnverifiedJobIds`, `buildEditorChatGuardrailNotice`, async-job handoff text) also live in `src/editor-chat.ts` and are imported by both copies — never re-implement them locally. `editor_action`'s `replace_composition_html` is lint-gated in the tool `execute` via `src/services/composition-lint.ts` (a rejected result carries `rejected: true` + `lint_errors` and omits `action_type`, so the browser never applies it). Web-editor motion is **declarative/CSS only**: the JS runtime adapters (anime.js / GSAP / Lottie / Three.js / TypeGPU) are stripped on save and `replace_composition_html` additionally rejects any `<script>`-bearing HTML, so the copilot authors motion with `set_layer_keyframes` (a CSS `@keyframes` animation on one layer) / `<style>` `@keyframes` + the preset vocabulary; scripted / adapter-driven compositions are a devcli-only (`vidfarm render`) capability. The `editor_context` snapshot the model receives now carries each layer's `transition` / `transition_out` / `transition_duration`, `ken_burns` preset, and `animation` (custom CSS keyframe name), and `recent_action_results` reports successes (`ok: true` + summary) as well as failures; the model can also GET `…/compositions/:forkId/composition.html` for exact markup. `browse_files` reads **and writes** the caller's My Files library — `action=list`/`search`/`read`/`write`/`annotate`. `action=list` defaults to `path='/'` when `path` is omitted, so the `/raws` (hunted raws) and `/temp` (scratch) roots surface alongside the My Files folders instead of being hidden; a `/raws` listing accepts a `content_type` shot-kind filter (`talking_head`/`b_roll`/`product_shot`/`screen_recording`/…) and paginates via `offset`/`limit`. `write` saves a text file (md/txt/csv/json/srt/vtt) via `POST /me/attachments/upload`, or **imports a durable media URL** into the library when `source_url` is passed (how a generated `character_sprite_card.png` gets persisted instead of stranding in a job result). `annotate` sets free-form metadata `notes` on any entry (`PATCH /me/attachments/:id`); notes are **vector-embedded server-side** (BYOK, same gemini→openai auto-pick seam as clip search; no key fail-softs to keyword-only) and `search` runs the hybrid keyword+semantic lookup over every file's name/folder/notes via `POST /me/attachments/search` — the My Files mirror of `/raws/search`. Write+annotate are what let the copilot persist Getting Started context (About.md, awareness-levels.md, persuasive-angles.md, ad-hooks.md) and character-consistency pairs (`about_character.md` + `character_sprite_card.png`) namescoped under a product/offer folder; the system prompt in `src/editor-chat.ts` drives when to save and annotate. The devcli mirrors are `vidfarm put-file [--notes]`, `annotate-file`, and `files --search`.
270
270
 
271
271
  ## Primitives
272
272
 
@@ -280,7 +280,7 @@ Primitives are hosted operations that are NOT templates — they're reusable bui
280
280
  - `brainstorm` — LLM brainstorm route
281
281
  - `tts` / `stt` — speech, BYOK on the caller's saved provider keys. `POST /api/v1/primitives/audio/speech` (`primitive:tts`) turns text into narration audio with a promptable voice style (`instructions`); `POST /api/v1/primitives/audio/transcribe` (`primitive:stt`) transcribes any video or audio URL (video demuxes through the primitive-media ffmpeg seam first, 64kbps mono, ~40min/20MB cap) and returns **two formats**: a simple subtitle transcript (plain text + SRT cues) and an advanced multi-speaker version (speaker-attributed timed segments; diarization is Gemini-only — JSON-mode audio transcription in `src/services/speech.ts`). The raw provider speech calls live in `src/services/speech.ts`, shared by `ProviderService` (leased BYOK keys, usage metering) and the devcli `vidfarm tts`/`vidfarm stt` commands, which run LOCAL-FIRST on the user's own env key (local ffmpeg demux, no job, no wallet) with `--cloud` as the explicit backup route.
282
282
 
283
- Directors call primitives when they want a one-off asset before feeding it into a template. Templates call primitives internally as workflow steps. All primitives are metered against the caller's wallet (BYOK AI calls excepted e.g. tts/stt model spend rides the caller's own provider key; only compute like the stt demux step wallet-bills).
283
+ Directors call primitives when they want a one-off asset before feeding it into a template. Templates call primitives internally as workflow steps. Each primitive declares one of three billing modes: **WALLET** a metered platform charge (e.g. `remove_background` / `image_remove_background` RapidAPI pass-through, `video_remove_captions` GhostCut, cloud `hyperframes_render`); **BYOK** — the AI spend rides the caller's own provider key and is never wallet-billed (tts/stt, smart decompose, clip tagging/embedding); or **COMPUTE-ONLY** — only the AWS Lambda / Step-Functions compute bills (clip hunts, the stt audio-demux step). So "all primitives are metered against the caller's wallet" holds only for the compute/platform legs — every BYOK AI call is excepted.
284
284
 
285
285
  ## Billing
286
286