@mevdragon/vidfarm-devcli 0.20.9 → 0.20.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.
@@ -121,7 +121,7 @@ If a local AI script rewrites text or scenes without consuming those files first
121
121
  | `vidfarm generate <image\|video> --prompt "…"` | `POST /api/v1/primitives/{images,videos}/generate` (polls job) | generate AI media → finished URL |
122
122
  | `vidfarm inpaint <image> --mask <png> --prompt "…" [--region "label=…"] [--ref …] [--out <f>]` | `POST /api/v1/primitives/images/inpaint` (polls job) | masked image EDIT — replace ONLY the transparent-mask region, keep everything else (devcli twin of the /inpaint page) |
123
123
  | `vidfarm create-overlay "<subject>" [--key-color #00FF00] [--aspect-ratio 1:1] [--place <dir>] [--out <f>]` | `POST /api/v1/primitives/images/create-overlay` (polls job) | **Vox-style** transparent OVERLAY — AI image on a forced key-color background, chroma-keyed out in one job → ready-to-composite transparent PNG |
124
- | `vidfarm remove-background-greenscreen <image> [--key-color #00FF00] [--tolerance 0.3] [--out <f>]` | `POST /api/v1/primitives/images/remove-background-greenscreen` (polls job) | cheap chroma-key removal of a FLAT solid background → transparent PNG (cloud primitive) |
124
+ | `vidfarm remove-greenscreen <image\|video> [--preset green\|blue\|white\|black\|digital-green\|magenta] [--key-color #00FF00] [--tolerance 0.3] [--local] [--out <f>]` | `POST /api/v1/primitives/remove-greenscreen` (polls job) | chroma-key a FLAT solid background → transparent PNG/WebP (image) or WebM/VP9-alpha (video); auto-detects media kind. `--local` runs it FREE in-process (sharp/ffmpeg, no wallet); default cloud is billed at real compute × 1.2. Aliases: `greenscreen`, `remove-background-greenscreen`. |
125
125
  | `vidfarm tts "…" [--style "…"] [--voice <v>] [--out <file>]` | (LOCAL-FIRST: your own OPENAI/GEMINI/OPENROUTER_API_KEY → audio file on disk; `--cloud` = `POST /api/v1/primitives/audio/speech` + poll, ElevenLabs on the platform key by default, `--own-key` for yours) | text → narration audio; `--cloud --voice <voice_id>` picks an ElevenLabs voice |
126
126
  | `vidfarm music "<prompt>" [--length <sec>] [--out <f>] [--own-key]` | `POST /api/v1/primitives/music/generate` (polls job) | prompt → music track (ElevenLabs; platform key + wallet by default, `--own-key` for yours) |
127
127
  | `vidfarm voices [--own-key] [--limit N]` | `GET /api/v1/primitives/audio/voices` | list ElevenLabs voices (voice_id/name/labels) for `tts --voice`; default a voice + tell the user they can choose |
@@ -17,18 +17,17 @@ curl -X POST "$VIDFARM_BASE/api/v1/primitives/images/remove-background" \
17
17
  -d '{"source_image_url": "https://cdn.example.com/photo.jpg"}'
18
18
  ```
19
19
 
20
- ## Primitive: image_remove_background_greenscreen
21
-
22
- Cheap **local chroma-key** background removal no third-party API, just `sharp` doing per-pixel color-distance keying, so it costs a fraction of `image_remove_background`. Use it whenever the source sits on a **flat, solid, evenly-lit background**: a green screen, or any single fill color you pass via `key_color` (e.g. `#FFFFFF` for a white background, `#0047BB` for blue). The result is a transparent PNG/WebP at a durable Vidfarm URL.
23
-
24
- - `POST /api/v1/primitives/images/remove-background-greenscreen` (flat alias: `POST /api/v1/primitives/remove-background-greenscreen`)
25
- - Body: `{ "tracer": "...", "payload": { "source_image_url": "https://...", "key_color"?: "#00FF00", "tolerance"?: 0.3, "softness"?: 0.1, "despill"?: true, "output_format"?: "png" | "webp" }, "webhook_url"?: "..." }`
26
- - `key_color` the background color to key out (hex / `rgb(...)` / named `green`/`white`/`blue`/`black`). Default `#00FF00`.
27
- - `tolerance` (0–1, default `0.3`) how close a pixel must be to `key_color` to be removed. Raise it if fringe survives, lower it if the subject is being eaten.
28
- - `softness` (0–1, default `0.1`) feather band beyond `tolerance` for anti-aliased edges.
29
- - `despill` (default `true`) suppresses the residual key-color rim on the subject's edges (only applied for chromatic keys like green/blue).
30
- - Response: standard primitive job; poll for completion, then read `image.file_url` / `primary_file_url`. `keyed_fraction` in the output tells you what share of pixels were removed (near-0 usually means the wrong `key_color` or too-low `tolerance`).
31
- - Billing: small compute-only wallet fee (`greenscreen_chroma_key` cost center) — cheaper than the RapidAPI route.
20
+ ## Primitive: remove-greenscreen (image **or** video chroma-key)
21
+
22
+ **Local chroma-key** background removal for an IMAGE **or** a VIDEO no third-party API, so it costs a fraction of `image_remove_background`. Use it whenever the source sits on a **flat, solid, evenly-lit background**: a green screen, or any single fill color. An IMAGE returns a transparent PNG/WebP; a VIDEO returns a transparent **WebM (VP9 alpha)** that stays transparent in the `/editor` preview AND the final render (it composites over whatever layers sit behind it).
23
+
24
+ - **Unified (auto-detect):** `POST /api/v1/primitives/remove-greenscreen` detects image vs video from the source and dispatches. Preferred entry point.
25
+ - Image-only: `POST /api/v1/primitives/images/remove-background-greenscreen` (flat alias: `POST /api/v1/primitives/remove-background-greenscreen`). Video-only: `POST /api/v1/primitives/videos/remove-greenscreen`.
26
+ - Body: `{ "tracer": "...", "payload": { "source_url": "https://..." (image OR video), "media_type"?: "image" | "video" (skip auto-detect), "preset"?: "green" | "digital-green" | "blue" | "white" | "black" | "magenta", "key_color"?: "#00FF00", "tolerance"?: 0.3, "softness"?: 0.1, "despill"?: true, "output_format"?: "png" | "webp" (image only) }, "webhook_url"?: "..." }`
27
+ - `preset` pick a common background color by name; fills `key_color` + tuned defaults. `key_color` (hex / `rgb(...)` / named) overrides the preset for a custom color/range.
28
+ - `tolerance` (0–1) — how close a pixel must be to key it out. Raise it if fringe survives, lower it if the subject is eaten. `softness` (01) feathers the edge; `despill` (default `true`) trims the residual key-color rim (green/blue only).
29
+ - Response: standard primitive job; poll, then read `primary_file_url` (a `.png/.webp` for image, `.webm` for video). After keying, `set_layer_media` the URL back onto the source layer to swap the transparent cut-out in place.
30
+ - Billing: the **real measured AWS compute cost × 1.2** (`greenscreen_chroma_key` cost center) no flat fee. Far cheaper than the RapidAPI matting route.
32
31
 
33
32
  ## Primitive: media_overlay ("create media overlay" — Vox-style)
34
33
 
package/SKILL.director.md CHANGED
@@ -986,7 +986,7 @@ If a local AI script rewrites text or scenes without consuming those files first
986
986
  | `vidfarm generate <image\|video> --prompt "…"` | `POST /api/v1/primitives/{images,videos}/generate` (polls job) | generate AI media → finished URL |
987
987
  | `vidfarm inpaint <image> --mask <png> --prompt "…" [--region "label=…"] [--ref …] [--out <f>]` | `POST /api/v1/primitives/images/inpaint` (polls job) | masked image EDIT — replace ONLY the transparent-mask region, keep everything else (devcli twin of the /inpaint page) |
988
988
  | `vidfarm create-overlay "<subject>" [--key-color #00FF00] [--aspect-ratio 1:1] [--place <dir>] [--out <f>]` | `POST /api/v1/primitives/images/create-overlay` (polls job) | **Vox-style** transparent OVERLAY — AI image on a forced key-color background, chroma-keyed out in one job → ready-to-composite transparent PNG |
989
- | `vidfarm remove-background-greenscreen <image> [--key-color #00FF00] [--tolerance 0.3] [--out <f>]` | `POST /api/v1/primitives/images/remove-background-greenscreen` (polls job) | cheap chroma-key removal of a FLAT solid background → transparent PNG (cloud primitive) |
989
+ | `vidfarm remove-greenscreen <image\|video> [--preset green\|blue\|white\|black\|digital-green\|magenta] [--key-color #00FF00] [--tolerance 0.3] [--local] [--out <f>]` | `POST /api/v1/primitives/remove-greenscreen` (polls job) | chroma-key a FLAT solid background → transparent PNG/WebP (image) or WebM/VP9-alpha (video); auto-detects media kind. `--local` runs it FREE in-process (sharp/ffmpeg, no wallet); default cloud is billed at real compute × 1.2. Aliases: `greenscreen`, `remove-background-greenscreen`. |
990
990
  | `vidfarm tts "…" [--style "…"] [--voice <v>] [--out <file>]` | (LOCAL-FIRST: your own OPENAI/GEMINI/OPENROUTER_API_KEY → audio file on disk; `--cloud` = `POST /api/v1/primitives/audio/speech` + poll, ElevenLabs on the platform key by default, `--own-key` for yours) | text → narration audio; `--cloud --voice <voice_id>` picks an ElevenLabs voice |
991
991
  | `vidfarm music "<prompt>" [--length <sec>] [--out <f>] [--own-key]` | `POST /api/v1/primitives/music/generate` (polls job) | prompt → music track (ElevenLabs; platform key + wallet by default, `--own-key` for yours) |
992
992
  | `vidfarm voices [--own-key] [--limit N]` | `GET /api/v1/primitives/audio/voices` | list ElevenLabs voices (voice_id/name/labels) for `tts --voice`; default a voice + tell the user they can choose |
@@ -1157,18 +1157,17 @@ curl -X POST "$VIDFARM_BASE/api/v1/primitives/images/remove-background" \
1157
1157
  -d '{"source_image_url": "https://cdn.example.com/photo.jpg"}'
1158
1158
  ```
1159
1159
 
1160
- ## Primitive: image_remove_background_greenscreen
1160
+ ## Primitive: remove-greenscreen (image **or** video chroma-key)
1161
1161
 
1162
- Cheap **local chroma-key** background removal no third-party API, just `sharp` doing per-pixel color-distance keying, so it costs a fraction of `image_remove_background`. Use it whenever the source sits on a **flat, solid, evenly-lit background**: a green screen, or any single fill color you pass via `key_color` (e.g. `#FFFFFF` for a white background, `#0047BB` for blue). The result is a transparent PNG/WebP at a durable Vidfarm URL.
1162
+ **Local chroma-key** background removal for an IMAGE **or** a VIDEO no third-party API, so it costs a fraction of `image_remove_background`. Use it whenever the source sits on a **flat, solid, evenly-lit background**: a green screen, or any single fill color. An IMAGE returns a transparent PNG/WebP; a VIDEO returns a transparent **WebM (VP9 alpha)** that stays transparent in the `/editor` preview AND the final render (it composites over whatever layers sit behind it).
1163
1163
 
1164
- - `POST /api/v1/primitives/images/remove-background-greenscreen` (flat alias: `POST /api/v1/primitives/remove-background-greenscreen`)
1165
- - Body: `{ "tracer": "...", "payload": { "source_image_url": "https://...", "key_color"?: "#00FF00", "tolerance"?: 0.3, "softness"?: 0.1, "despill"?: true, "output_format"?: "png" | "webp" }, "webhook_url"?: "..." }`
1166
- - `key_color` the background color to key out (hex / `rgb(...)` / named `green`/`white`/`blue`/`black`). Default `#00FF00`.
1167
- - `tolerance` (0–1, default `0.3`) how close a pixel must be to `key_color` to be removed. Raise it if fringe survives, lower it if the subject is being eaten.
1168
- - `softness` (0–1, default `0.1`) feather band beyond `tolerance` for anti-aliased edges.
1169
- - `despill` (default `true`) suppresses the residual key-color rim on the subject's edges (only applied for chromatic keys like green/blue).
1170
- - Response: standard primitive job; poll for completion, then read `image.file_url` / `primary_file_url`. `keyed_fraction` in the output tells you what share of pixels were removed (near-0 usually means the wrong `key_color` or too-low `tolerance`).
1171
- - Billing: small compute-only wallet fee (`greenscreen_chroma_key` cost center) — cheaper than the RapidAPI route.
1164
+ - **Unified (auto-detect):** `POST /api/v1/primitives/remove-greenscreen` detects image vs video from the source and dispatches. Preferred entry point.
1165
+ - Image-only: `POST /api/v1/primitives/images/remove-background-greenscreen` (flat alias: `POST /api/v1/primitives/remove-background-greenscreen`). Video-only: `POST /api/v1/primitives/videos/remove-greenscreen`.
1166
+ - Body: `{ "tracer": "...", "payload": { "source_url": "https://..." (image OR video), "media_type"?: "image" | "video" (skip auto-detect), "preset"?: "green" | "digital-green" | "blue" | "white" | "black" | "magenta", "key_color"?: "#00FF00", "tolerance"?: 0.3, "softness"?: 0.1, "despill"?: true, "output_format"?: "png" | "webp" (image only) }, "webhook_url"?: "..." }`
1167
+ - `preset` pick a common background color by name; fills `key_color` + tuned defaults. `key_color` (hex / `rgb(...)` / named) overrides the preset for a custom color/range.
1168
+ - `tolerance` (0–1) — how close a pixel must be to key it out. Raise it if fringe survives, lower it if the subject is eaten. `softness` (01) feathers the edge; `despill` (default `true`) trims the residual key-color rim (green/blue only).
1169
+ - Response: standard primitive job; poll, then read `primary_file_url` (a `.png/.webp` for image, `.webm` for video). After keying, `set_layer_media` the URL back onto the source layer to swap the transparent cut-out in place.
1170
+ - Billing: the **real measured AWS compute cost × 1.2** (`greenscreen_chroma_key` cost center) no flat fee. Far cheaper than the RapidAPI matting route.
1172
1171
 
1173
1172
  ## Primitive: media_overlay ("create media overlay" — Vox-style)
1174
1173
 
package/dist/src/cli.js CHANGED
@@ -22,7 +22,7 @@ import { renderCompositionStills } from "./devcli/stills.js";
22
22
  import { runDoctorCommand } from "./devcli/doctor.js";
23
23
  import { runSkillsCommand } from "./devcli/skills.js";
24
24
  import { initTelemetry, reportCliCrash } from "./devcli/telemetry.js";
25
- import { resolveLocalDataDir, localBackendAvailable, LocalModeUnavailableError } from "./devcli/local-backend.js";
25
+ import { resolveLocalDataDir, localBackendAvailable, LocalModeUnavailableError, localApiRequest } from "./devcli/local-backend.js";
26
26
  // vidfarm-devcli — command-line bridge for the Vidfarm video studio. The
27
27
  // `serve` command boots the FULL editor locally (single origin, disk-backed
28
28
  // records + storage) so power users edit compositions on disk while a browser
@@ -172,14 +172,20 @@ Generate AI media and drop it on the timeline (for local coding agents):
172
172
  --out <file> Download the transparent PNG to this path
173
173
  --no-wait Return the job id instead of polling to the URL
174
174
  --place <dir> --at <time>|--replace <layer_key> Fuse onto a composition
175
- remove-background-greenscreen <image|url> Cheap chroma-key removal of a FLAT
176
- solid background → transparent PNG (cloud primitive;
177
- differs from remove-background = local ONNX matting)
178
- POST /api/v1/primitives/images/remove-background-greenscreen
179
- --key-color <c> Background color to key out (default #00FF00; e.g. #FFFFFF)
175
+ remove-greenscreen <image|video|url> Key out a FLAT solid background from an
176
+ IMAGE (→ transparent PNG/WebP) or VIDEO ( transparent
177
+ WebM/VP9 alpha), auto-detecting the media kind.
178
+ (aliases: greenscreen, remove-background-greenscreen)
179
+ POST /api/v1/primitives/remove-greenscreen
180
+ --preset <name> green | digital-green | blue | white | black | magenta
181
+ --key-color <c> Custom background color (hex/rgb/named; overrides --preset)
182
+ --media-type <k> Force image|video instead of auto-detecting
180
183
  --tolerance <0..1> Key radius (default 0.3); --softness <0..1> edge feather (0.1)
181
184
  --no-despill Skip edge color-fringe suppression
182
- --out <file> Download the transparent PNG to this path
185
+ --output-format <f> Image only: png|webp
186
+ --local Run the keyer in-process for FREE (sharp/ffmpeg, no wallet)
187
+ --cloud Force the billed cloud primitive (default when a key is set)
188
+ --out <file> Download the transparent result to this path
183
189
  place <dir|composition.html> Insert media into a local composition
184
190
  (fill a gap, replace a scene, or overlay an AUDIO
185
191
  track) — same clip markup the browser editor makes;
@@ -667,10 +673,11 @@ async function main() {
667
673
  case "overlay":
668
674
  await runCreateOverlayCommand(rest);
669
675
  return;
676
+ case "remove-greenscreen":
670
677
  case "remove-background-greenscreen":
671
678
  case "rmbg-green":
672
679
  case "greenscreen":
673
- await runRemoveBackgroundGreenscreenCommand(rest);
680
+ await runRemoveGreenscreenCommand(rest);
674
681
  return;
675
682
  case "tts":
676
683
  await runTtsCommand(rest);
@@ -3585,33 +3592,67 @@ async function runCreateOverlayCommand(argv) {
3585
3592
  }
3586
3593
  }
3587
3594
  // ── Green-screen (chroma-key) background removal ──────────────────────────────
3588
- // The devcli twin of the image_remove_background_greenscreen primitive: cheap
3589
- // local chroma key on a flat, solid background. A cloud primitive job (distinct
3590
- // from `vidfarm remove-background`, which is the free LOCAL ONNX matting engine).
3591
- async function runRemoveBackgroundGreenscreenCommand(argv) {
3595
+ // The devcli twin of the remove-greenscreen primitive: key out a flat, solid
3596
+ // background from an IMAGE (→ transparent PNG/WebP) OR a VIDEO (→ transparent
3597
+ // WebM/VP9 alpha), auto-detecting the media kind. LOCAL vs CLOUD: --local runs
3598
+ // the keyer in-process for FREE (sharp/ffmpeg, no wallet); --cloud runs the
3599
+ // billed primitive. Default = cloud (paid), unless there's no cloud key AND the
3600
+ // local backend is present (free / offline), in which case it runs local.
3601
+ const GREENSCREEN_PRESET_NAMES = new Set(["green", "digital-green", "blue", "white", "black", "magenta"]);
3602
+ function resolveGreenscreenTarget(values) {
3603
+ if (values.local)
3604
+ return "local";
3605
+ if (values.cloud)
3606
+ return "cloud";
3607
+ const env = (process.env.VIDFARM_TARGET ?? "").trim().toLowerCase();
3608
+ if (env === "local" || env === "cloud")
3609
+ return env;
3610
+ const hasCloudKey = Boolean(values["api-key"] ?? process.env.VIDFARM_API_KEY);
3611
+ if (!hasCloudKey && localBackendAvailable())
3612
+ return "local";
3613
+ return "cloud";
3614
+ }
3615
+ async function runRemoveGreenscreenCommand(argv) {
3592
3616
  const parsed = parseArgs({
3593
3617
  args: argv,
3594
3618
  allowPositionals: true,
3595
3619
  options: {
3596
3620
  ...commonOptions(),
3597
3621
  source: { type: "string" },
3622
+ preset: { type: "string" },
3623
+ "media-type": { type: "string" },
3598
3624
  "key-color": { type: "string" },
3599
3625
  tolerance: { type: "string" },
3600
3626
  softness: { type: "string" },
3601
3627
  "no-despill": { type: "boolean", default: false },
3602
3628
  "output-format": { type: "string" },
3629
+ local: { type: "boolean", default: false },
3630
+ cloud: { type: "boolean", default: false },
3603
3631
  out: { type: "string" },
3604
3632
  "no-wait": { type: "boolean", default: false },
3605
3633
  tracer: { type: "string" }
3606
3634
  }
3607
3635
  });
3608
3636
  const ctx = commonContext(parsed.values);
3637
+ const target = resolveGreenscreenTarget(parsed.values);
3638
+ if (target === "local" && !localBackendAvailable())
3639
+ throw new LocalModeUnavailableError();
3609
3640
  const sourceArg = parsed.values.source ?? parsed.positionals[0];
3610
3641
  if (!sourceArg) {
3611
- throw new Error("remove-background-greenscreen requires a source image: `vidfarm remove-background-greenscreen <image|url> [--key-color #00FF00] [--out cutout.png]`.");
3642
+ throw new Error("remove-greenscreen requires a source image or video: `vidfarm remove-greenscreen <image|video|url> [--preset green|blue|white|black|digital-green|magenta] [--key-color #00FF00] [--local] [--out cutout.png|.webm]`.");
3612
3643
  }
3613
3644
  const sourceUrl = await resolveSingleMediaUrl(ctx, sourceArg);
3614
- const payload = { source_image_url: sourceUrl };
3645
+ const presetRaw = parsed.values.preset?.trim().toLowerCase();
3646
+ if (presetRaw && !GREENSCREEN_PRESET_NAMES.has(presetRaw)) {
3647
+ throw new Error(`Unknown --preset "${presetRaw}". Choose one of: ${[...GREENSCREEN_PRESET_NAMES].join(", ")}, or pass --key-color <hex>.`);
3648
+ }
3649
+ const mediaTypeArg = parsed.values["media-type"]?.trim().toLowerCase();
3650
+ const mediaType = mediaTypeArg === "image" || mediaTypeArg === "video"
3651
+ ? mediaTypeArg
3652
+ : (/\.(mp4|mov|webm|m4v|mkv)(\?|#|$)/i.test(sourceUrl) ? "video" : "image");
3653
+ const payload = { source_url: sourceUrl, media_type: mediaType };
3654
+ if (presetRaw)
3655
+ payload.preset = presetRaw;
3615
3656
  if (parsed.values["key-color"])
3616
3657
  payload.key_color = parsed.values["key-color"];
3617
3658
  if (parsed.values.tolerance)
@@ -3620,12 +3661,17 @@ async function runRemoveBackgroundGreenscreenCommand(argv) {
3620
3661
  payload.softness = Number(parsed.values.softness);
3621
3662
  if (parsed.values["no-despill"])
3622
3663
  payload.despill = false;
3623
- if (parsed.values["output-format"])
3664
+ if (mediaType === "image" && parsed.values["output-format"])
3624
3665
  payload.output_format = parsed.values["output-format"];
3625
- const tracer = parsed.values.tracer ?? `devcli-gsbg-${Date.now().toString(36)}`;
3626
- const submit = await apiRequest({ method: "POST", host: ctx.host, path: "/api/v1/primitives/images/remove-background-greenscreen", auth: ctx.auth, body: { tracer, payload } });
3627
- assertApiOk(submit, "remove-background-greenscreen");
3666
+ const tracer = parsed.values.tracer ?? `devcli-greenscreen-${Date.now().toString(36)}`;
3667
+ const route = "/api/v1/primitives/remove-greenscreen";
3668
+ const submit = target === "local"
3669
+ ? await localApiRequest({ method: "POST", path: route, auth: ctx.auth, body: { tracer, payload }, home: ctx.home })
3670
+ : await apiRequest({ method: "POST", host: ctx.host, path: route, auth: ctx.auth, body: { tracer, payload } });
3671
+ assertApiOk(submit, "remove-greenscreen");
3628
3672
  const jobId = submit.json?.job_id;
3673
+ if (!ctx.json)
3674
+ console.log(`${DIM}${target === "local" ? "Local (free)" : "Cloud"} ${mediaType} greenscreen removal…${RESET}`);
3629
3675
  const wait = !parsed.values["no-wait"];
3630
3676
  if (!wait || !jobId) {
3631
3677
  if (!ctx.json && jobId)
@@ -3635,7 +3681,7 @@ async function runRemoveBackgroundGreenscreenCommand(argv) {
3635
3681
  }
3636
3682
  if (!ctx.json)
3637
3683
  console.log(`${DIM}Keying out background (${jobId})… polling every 5s.${RESET}`);
3638
- const job = await pollPrimitiveJob(ctx, jobId);
3684
+ const job = await pollGreenscreenJob(ctx, target, jobId);
3639
3685
  const mediaUrl = resolveJobMediaUrl(job);
3640
3686
  const status = String(job?.status ?? "");
3641
3687
  if (!mediaUrl) {
@@ -3655,12 +3701,38 @@ async function runRemoveBackgroundGreenscreenCommand(argv) {
3655
3701
  await downloadUrlToFile(mediaUrl, outPath);
3656
3702
  }
3657
3703
  if (ctx.json) {
3658
- printJson({ ok: true, job_id: jobId, media_url: mediaUrl, out: outPath, keyed_fraction: job?.result?.keyed_fraction ?? null });
3704
+ printJson({ ok: true, job_id: jobId, media_type: mediaType, target, media_url: mediaUrl, out: outPath });
3659
3705
  }
3660
3706
  else {
3661
- console.log(`${GREEN}Transparent cut-out ready:${RESET} ${mediaUrl}`);
3707
+ console.log(`${GREEN}Transparent ${mediaType === "video" ? "WebM" : "cut-out"} ready:${RESET} ${mediaUrl}`);
3662
3708
  if (outPath)
3663
3709
  console.log(`${DIM}Saved to ${outPath}${RESET}`);
3710
+ console.log(`${DIM}Place it with: vidfarm set-media <dir> --src "${mediaUrl}" --replace <layer_key> (keeps its transparency).${RESET}`);
3711
+ }
3712
+ }
3713
+ // Poll a greenscreen job through the same backend it was submitted to (local
3714
+ // in-process app or cloud). Mirrors pollPrimitiveJob's terminal conditions.
3715
+ async function pollGreenscreenJob(ctx, target, jobId) {
3716
+ if (target !== "local")
3717
+ return pollPrimitiveJob(ctx, jobId);
3718
+ const MAX_WAIT_MS = 8 * 60 * 1000;
3719
+ const startedAt = Date.now();
3720
+ let last = null;
3721
+ for (;;) {
3722
+ await sleep(5000);
3723
+ const res = await localApiRequest({ method: "GET", path: `/api/v1/user/me/jobs/${encodeURIComponent(jobId)}`, auth: ctx.auth, home: ctx.home });
3724
+ if (res.ok && res.json) {
3725
+ last = res.json;
3726
+ const status = String(last.status ?? "");
3727
+ if (!ctx.json)
3728
+ console.log(`${DIM} ${status}${typeof last.progress === "number" ? ` ${Math.round(last.progress * 100)}%` : ""}${RESET}`);
3729
+ if (status === "succeeded" || status === "failed" || status === "cancelled")
3730
+ return last;
3731
+ if (resolveJobMediaUrl(last))
3732
+ return last;
3733
+ }
3734
+ if (Date.now() - startedAt > MAX_WAIT_MS)
3735
+ return last ?? { status: "timeout" };
3664
3736
  }
3665
3737
  }
3666
3738
  // ── Speech: tts / stt ────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mevdragon/vidfarm-devcli",
3
- "version": "0.20.9",
3
+ "version": "0.20.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": {