From ed85d0eb20510a6bfc65cfa371c6ae03abe32c39 Mon Sep 17 00:00:00 2001 From: jamie-at-bunny Date: Mon, 12 Oct 2026 13:35:24 +0100 Subject: [PATCH 1/2] docs(stream): add stream commands to the agent skill and update the import reference --- .changeset/stream-skills.md | 5 + packages/cli/src/commands/skills/content.ts | 6 +- skills/bunny-cli/SKILL.md | 12 +- skills/bunny-cli/references/stream-import.md | 139 ++++++++++++++ skills/bunny-cli/references/stream.md | 191 ++++++++++++------- 5 files changed, 283 insertions(+), 70 deletions(-) create mode 100644 .changeset/stream-skills.md create mode 100644 skills/bunny-cli/references/stream-import.md diff --git a/.changeset/stream-skills.md b/.changeset/stream-skills.md new file mode 100644 index 00000000..5b8ea5ba --- /dev/null +++ b/.changeset/stream-skills.md @@ -0,0 +1,5 @@ +--- +"@bunny.net/cli": patch +--- + +Add the `bunny stream` library, video, collection, caption, and paid commands to the agent skill, and bring the import reference up to date diff --git a/packages/cli/src/commands/skills/content.ts b/packages/cli/src/commands/skills/content.ts index 5179fd23..9e868f4f 100644 --- a/packages/cli/src/commands/skills/content.ts +++ b/packages/cli/src/commands/skills/content.ts @@ -22,6 +22,9 @@ import storageMd from "@skills/bunny-cli/references/storage.md" with { import streamMd from "@skills/bunny-cli/references/stream.md" with { type: "text", }; +import streamImportMd from "@skills/bunny-cli/references/stream-import.md" with { + type: "text", +}; import skillMd from "@skills/bunny-cli/SKILL.md" with { type: "text" }; import type { ProjectSkill } from "@/core/agent-skill.ts"; @@ -37,7 +40,7 @@ This project uses bunny.net. Manage its resources with the \`bunny\` CLI: databa - When the CLI has no command for something, fall back to \`bunny api \` against api.bunny.net.`; /** The shipped bunny-cli skill, embedded at bundle time from skills/bunny-cli/. */ -// Experimental namespaces (apps, registries) stay out of the skill until they stabilize; stream import ships early, labelled experimental. +// Experimental namespaces (apps, registries) stay out of the skill until they stabilize; stream ships early, labelled experimental. export const BUNNY_CLI_SKILL: ProjectSkill = { name: "bunny-cli", agentsSection: AGENTS_SECTION, @@ -53,5 +56,6 @@ export const BUNNY_CLI_SKILL: ProjectSkill = { "references/sites.md": sitesMd, "references/storage.md": storageMd, "references/stream.md": streamMd, + "references/stream-import.md": streamImportMd, }, }; diff --git a/skills/bunny-cli/SKILL.md b/skills/bunny-cli/SKILL.md index cb1d827d..e69a3d93 100644 --- a/skills/bunny-cli/SKILL.md +++ b/skills/bunny-cli/SKILL.md @@ -1,6 +1,6 @@ --- name: bunny-cli -description: Manage bunny.net resources from the command line (databases, DNS, Edge Storage, Edge Scripts, static sites, sandboxes, Stream video imports, authentication, and raw API requests) and query a Bunny Database from application code with `@bunny.net/database-client`. Use when working with bunny.net (pullzones, DNS zones/records, databases, Edge Storage zones and files, Edge Scripts, Magic Containers, static-site hosting/deploys, cloud sandboxes, moving a video library into Bunny Stream from Vimeo, S3, Wistia, Mux, Cloudflare Stream, JW Player, or Brightcove), invoking the `bunny` CLI, or making authenticated API calls to api.bunny.net. +description: Manage bunny.net resources from the command line (databases, DNS, Edge Storage, Edge Scripts, static sites, sandboxes, Stream video libraries and imports, authentication, and raw API requests) and query a Bunny Database from application code with `@bunny.net/database-client`. Use when working with bunny.net (pullzones, DNS zones/records, databases, Edge Storage zones and files, Edge Scripts, Magic Containers, static-site hosting/deploys, cloud sandboxes, Stream video libraries, uploads, collections, captions, and transcription, moving a video library into Bunny Stream from Vimeo, S3, Wistia, Mux, Cloudflare Stream, JW Player, or Brightcove), invoking the `bunny` CLI, or making authenticated API calls to api.bunny.net. --- # bunny.net CLI Skill @@ -68,7 +68,14 @@ bunny sites deploy ./dist # deploy a directory and p bunny sites domains add example.com --wait # custom production domain bunny sites deployments publish --previous --force # instant rollback +# manage Bunny Stream video (experimental) +bunny stream library create my-library +bunny stream library link my-library +bunny stream video upload ./video.mp4 --title "Launch demo" +bunny stream video list + # import a video library into Bunny Stream (experimental) +bunny stream import list --source vimeo # browse folders first bunny stream import --library 12345 --source vimeo --dry-run # plan first bunny stream import --library 12345 --source vimeo --force # hand every video to Bunny bunny stream import status --library 12345 # encoding progress @@ -86,7 +93,8 @@ Use this to route to the correct reference file: - **Static sites (create, deploy, rollback, custom domains, GitHub Actions)** -> `references/sites.md` - **Edge Storage (zones, files, connection credentials, S3/FTP/HTTP, custom domains)** -> `references/storage.md` - **Sandboxes (create, exec, ssh, files list/cp, public URLs, persistent env vars, Claude Code auth)** -> `references/sandbox.md` -- **Import a video library into Bunny Stream (experimental: Vimeo, S3, Wistia, Mux, Cloudflare Stream, JW Player, Brightcove; dry run, resume, status)** -> `references/stream.md` +- **Bunny Stream (experimental: libraries, player/security settings, video upload/fetch/embed/cleanup, collections, captions, encoding, transcription, smart titles/chapters)** -> `references/stream.md` +- **Import a video library into Bunny Stream (experimental: Vimeo, S3, Wistia, Mux, Cloudflare Stream, JW Player, Brightcove; browse, dry run, resume, status)** -> `references/stream-import.md` - **Make raw API requests** -> `references/api.md` - **CLI doesn't have a command for it** -> use `bunny api` as a fallback (see `references/api.md`) diff --git a/skills/bunny-cli/references/stream-import.md b/skills/bunny-cli/references/stream-import.md new file mode 100644 index 00000000..0d6bce7f --- /dev/null +++ b/skills/bunny-cli/references/stream-import.md @@ -0,0 +1,139 @@ +# Stream Import Commands + +> **Experimental.** `bunny stream` is hidden from `--help` while it stabilizes. Flags and JSON shapes may still change. + +`bunny stream import` moves an existing video library into a Bunny Stream library from **Vimeo**, **AWS S3** (or an S3-compatible provider), **Wistia**, **Mux**, **Cloudflare Stream**, **JW Player**, or **Brightcove**. Bunny fetches every file straight from the source by URL, so nothing is downloaded to the local machine. The command hands each video to Bunny, tags it, and returns once they are all queued; encoding then happens on Bunny's side. + +## Resolving the library + +`--library` (alias `--lib`) takes a library name or numeric ID. When omitted, it resolves in this order: + +1. `--library` +2. `.bunny/stream.json` (the directory's linked library, see `stream.md`) +3. Interactive picker, which offers to link the directory (never on `--dry-run`) + +The library's own Stream API key is read from the account automatically and never printed. + +## Source credentials + +Credentials come from environment variables, never from flags: + +| Source | Variables | +| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `vimeo` | `VIMEO_ACCESS_TOKEN` (scopes `public`, `private`, `video_files`; downloads need a Standard plan or above) | +| `s3` | `S3_BUCKET`, `AWS_REGION` (or `AWS_DEFAULT_REGION`; default `us-east-1`); optional `S3_PREFIX`, `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `S3_PRESIGNED_URL_TTL`, `S3_ENDPOINT` (or `AWS_ENDPOINT_URL_S3`) | +| `wistia` | `WISTIA_ACCESS_TOKEN` | +| `mux` | `MUX_TOKEN_ID`, `MUX_TOKEN_SECRET` | +| `cloudflare` | `CLOUDFLARE_API_TOKEN` (Stream:Edit), `CLOUDFLARE_ACCOUNT_ID` | +| `jwplayer` | `JWPLAYER_API_KEY` (v2), `JWPLAYER_SITE_ID` | +| `brightcove` | `BRIGHTCOVE_CLIENT_ID`, `BRIGHTCOVE_CLIENT_SECRET` (CMS video read), `BRIGHTCOVE_ACCOUNT_ID` | + +`bunny stream import --help` lists the same variables. Interactively, the command prompts only for missing required values, then offers to save them for the active profile in `$XDG_CONFIG_HOME/bunnynet/stream-import-credentials.json` (default `~/.config/...`). Environment variables always win over saved values, and saved values are removed only when the source rejects them. Unattended runs (`--output json`, no TTY) fail naming the missing variables. + +With `--source` omitted, the command picks the one source whose credentials are all set, otherwise it prompts (or fails when unattended). + +S3 notes: leaving both AWS keys unset uses the default AWS credential chain; setting only one is an error. The IAM policy needs `s3:ListBucket` and `s3:GetObject`. Only video extensions are imported; `.ts` files only with `--include-ts` (or `S3_INCLUDE_TS=true`), since HLS buckets hold thousands of segments. + +## Typical workflows + +```bash +# 1. Browse the source: folders and their IDs, or every video +bunny stream import list --source vimeo +bunny stream import list --source vimeo --videos + +# 2. Look before importing: folder and video counts, what is already in Bunny +bunny stream import --library 12345 --source vimeo --dry-run + +# 3. Import (confirms first; --force skips the prompt and is required without a TTY) +bunny stream import --library 12345 --source vimeo --force + +# 4. Check how far Bunny has got with encoding +bunny stream import status --library 12345 --source vimeo + +# One folder only (Vimeo/Wistia projects, Brightcove folders, S3 prefixes) +bunny stream import --library 12345 --source vimeo --folder 987654 --force + +# S3, unattended, overriding the bucket and prefix for this run +bunny stream import --library 12345 --source s3 --bucket my-videos --prefix 2024/ --force + +# Continue an interrupted or partly failed run +bunny stream import --library 12345 --source vimeo --resume --force + +# Re-fetch videos stuck at zero bytes, or retry ones Bunny rejected +bunny stream import --library 12345 --source vimeo --retry-stalled --force +bunny stream import --library 12345 --source vimeo --clean-failed --force +``` + +--- + +## `bunny stream import`: Import a video library + +| Flag | Description | +| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| `--library`, `--lib` | Destination library name or ID (defaults to the linked library) | +| `--source`, `-s` | `vimeo`, `s3`, `wistia`, `mux`, `cloudflare`, `jwplayer`, `brightcove` | +| `--folder` | One source folder only (Vimeo, S3, Wistia, Brightcove; rejected for Mux, Cloudflare Stream, JW Player) | +| `--dry-run` | Print the plan; writes nothing (no state file, no link, nothing in the library) | +| `--resume` | Continue the saved import for this source and library | +| `--wait` | Stay until Bunny has encoded every video (default: return once they are queued) | +| `--force`, `-f` | Skip the confirmation (which defaults to No) and the post-run retry offer | +| `--concurrency`, `-c` | Videos handed to Bunny in parallel, 1 to 20 (default 3) | +| `--bucket`, `--prefix`, `--url-ttl` | S3 overrides for one run (`--url-ttl` 3600 to 604800 seconds) | +| `--include-ts` | S3: import `.ts` files too | +| `--clean-failed` | Delete a video's failed Bunny copies before importing it again; retries rejected videos and ones that failed 3 times | +| `--retry-stalled` | Delete and import again videos stuck at zero bytes past the 30 minute stall threshold | +| `--request-timeout` | Seconds per HTTP request, 1 to 3600 (default 30) | +| `--video-timeout` | Seconds per video hand-off, or per encode wait with `--wait`, 60 to 86400 (default 5400) | +| `--processing-timeout` | With `--wait`, seconds per encode, 60 to 86400 (default 3600); raises `--video-timeout` when larger | + +**Re-running is safe.** Every imported video carries a per-source metaTag (`vimeoId`, `s3Source`, `wistiaId`, `muxAssetId`, `cfStreamId`, `jwPlayerId`, `brightcoveId`). A re-run skips tagged videos Bunny has finished, leaves ones still encoding alone, and imports again only the ones Bunny failed on. Source folders become Stream collections of the same name. Videos the source reports as beyond Bunny's limits (over 72 hours, or above 2160p) are flagged before you confirm. + +**Progress** is saved to `$XDG_STATE_HOME/bunnynet/stream-import//-.json` (default `~/.local/state/...`). `--resume` retries failed videos, works outstanding ones, and keeps the `--folder` scope the run started with. It does **not** retry videos Bunny rejected for a reason the same file cannot get past (over limits, invalid framerate, corrupt original), ones no longer at the source, or ones that failed 3 times; use `--clean-failed` for those. Two imports cannot share one state file. + +**Ctrl-C** stops queuing new videos, saves progress, prints the `--resume` command, and exits 130. A second Ctrl-C exits at once. + +**Exit codes:** 0 when everything was queued (or finished with `--wait`), 1 when any video failed (they are listed with Bunny's reason; in a terminal it offers to retry them), 130 when paused. + +### JSON output + +Every `--output json` document carries an `outcome`: + +- `no_videos`, `nothing_new`, `dry_run`: stopped before importing. Shape `{ outcome, library, source, folder, dryRun, truncated, summary }`; `summary` holds the counts (including `processingOnBunny`) and name lists (capped at 10000; `truncated` says when). +- `imported`, `paused`: shape `{ outcome, library, source, folder, dryRun, status, waited, queued, completed, processing, failed, collections, warnings, statePath, elapsedMs }`, each `failed` entry `{ name, sourceId, error }`. On `paused`, `library` and `statePath` may be null if it stopped before reaching them. + +Declining the confirmation prints nothing. + +--- + +## `bunny stream import list`: Browse a source + +```bash +bunny stream import list --source vimeo # folders: ID, name, video count +bunny stream import list --source vimeo --videos # every video, with folder, duration, size +bunny stream import list --source vimeo --folder 987654 # videos in one folder +bunny stream import list --source s3 --bucket my-videos --prefix 2024/ +``` + +Resolves the source and credentials the same way an import does and needs no library. The folder IDs it prints are what `--folder` takes. JSON: `{ source, supportsFolders, folders, uncategorized, videos, truncated }`. + +--- + +## `bunny stream import status`: Check a queued import + +```bash +bunny stream import status --library 12345 +bunny stream import status --library 12345 --source s3 --output json +``` + +Reads the saved run (pass `--source` when more than one platform was imported into the library), asks Bunny for the current state of every video it queued, and prints Bunny's status, encode percentage, size, and when each was queued. A video still processing with no data after 30 minutes is marked `(stalled?)`: re-run the import with `--retry-stalled` to delete and fetch it again. Exits 1 when any video failed, so it works as a poll in scripts. JSON: `{ library, source, status, completed, processing, failed, stalled, stalledAfterMinutes, saved, videos }`. + +--- + +## Anti-patterns + +- **Passing credentials as flags or pasting them into commands.** They are read from environment variables only; export them (or use a `.env` the shell loads) instead. +- **Importing without a dry run.** Run `import list` and `--dry-run` first to see the folders, video counts, and what is already in Bunny. +- **Holding the command open with `--wait` in automation.** Queue with `--force`, then poll `bunny stream import status` until it exits 0; encodes can take hours. +- **Deleting the state file to "start fresh".** It is how a resume recovers videos Bunny created but never tagged; deleting it can create duplicates. +- **Treating `(stalled?)` as success.** Bunny gives no signal for a fetch that died silently; re-run with `--retry-stalled`. +- **Looping `--resume` on rejected videos.** It skips them on purpose; use `--clean-failed` once, or fix the source file. diff --git a/skills/bunny-cli/references/stream.md b/skills/bunny-cli/references/stream.md index 6745aa84..a0900e83 100644 --- a/skills/bunny-cli/references/stream.md +++ b/skills/bunny-cli/references/stream.md @@ -1,110 +1,167 @@ -# Stream Import Commands +# Stream Commands > **Experimental.** `bunny stream` is hidden from `--help` while it stabilizes. Flags and JSON shapes may still change. -`bunny stream import` moves an existing video library into a Bunny Stream library from **Vimeo**, **AWS S3** (or an S3-compatible provider), **Wistia**, **Mux**, **Cloudflare Stream**, **JW Player**, or **Brightcove**. Bunny fetches every file straight from the source by URL, so nothing is downloaded to the local machine. The command hands each video to Bunny, tags it, and returns once they are all queued; encoding then happens on Bunny's side. +All Stream commands live under `bunny stream`, split into resource groups: -## Resolving the library +- **`bunny stream library`** (aliases `libraries`, `lib`) manages the video library itself, plus its `player` and `security` settings, with the account API key. +- **`bunny stream video`** (alias `videos`) manages the videos inside one library. +- **`bunny stream collection`** (alias `collections`) manages groups of videos within a library. +- **`bunny stream caption`** (alias `captions`) manages caption files on one video. +- **`bunny stream encode`**, **`bunny stream transcribe`**, and **`bunny stream smart`** are paid operations. +- **`bunny stream import`** moves a library in from another platform; see `stream-import.md`. -`--library` (alias `--lib`) takes a library name or numeric ID. When omitted, it resolves in this order: +Each library carries its own Stream API key for the video-level API. Video commands resolve it from the library automatically, so there is nothing extra to configure; the key is never printed unless you ask for it with `library credentials`. -1. `--library` -2. `.bunny/stream.json` (the directory's linked library) -3. Interactive picker, which offers to link the directory (never on `--dry-run`) +## Resolving the library -The library's own Stream API key is read from the account automatically; nothing extra to configure, and it is never printed. +`library` commands take the library as an optional positional (name or numeric ID). Every other stream command takes it as `--lib` (alias `--library`), because its positional is a video GUID, a collection ID, or a local file path. When omitted, the library resolves in this order: -## Source credentials +1. Explicit name or ID +2. `.bunny/stream.json` (written by `bunny stream library link`) +3. Interactive picker, which offers to link the directory (never on destructive commands) -Credentials come only from environment variables (or the interactive prompt), never from flags. Set them before running unattended: +Non-interactive runs (`--output json`, no TTY, or `--force`) error instead of prompting: pass a library or link the directory first. A `video` or `collection` command with no ID behaves the same way. `caption` commands always need the video GUID as a positional. -| Source | Variables | -| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `vimeo` | `VIMEO_ACCESS_TOKEN` (scopes `public`, `private`, `video_files`; downloads need a paid plan) | -| `s3` | `S3_BUCKET`, `AWS_REGION` (default `us-east-1`); optional `S3_PREFIX`, `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `S3_PRESIGNED_URL_TTL`, `S3_ENDPOINT` | -| `wistia` | `WISTIA_ACCESS_TOKEN` | -| `mux` | `MUX_TOKEN_ID`, `MUX_TOKEN_SECRET` | -| `cloudflare` | `CLOUDFLARE_API_TOKEN` (Stream:Edit), `CLOUDFLARE_ACCOUNT_ID` | -| `jwplayer` | `JWPLAYER_API_KEY` (v2), `JWPLAYER_SITE_ID` | -| `brightcove` | `BRIGHTCOVE_CLIENT_ID`, `BRIGHTCOVE_CLIENT_SECRET` (CMS video read), `BRIGHTCOVE_ACCOUNT_ID` | +## Typical workflows -`bunny stream import --help` lists the same variables. With `--source` omitted, the command picks the one source whose variables are all set, otherwise it prompts (or fails when unattended). For S3, leaving both AWS keys unset uses the default AWS credential chain (profile, SSO, instance role); setting only one is an error. +```bash +# New library, linked to this directory +bunny stream library create my-library +bunny stream library link my-library + +# Upload a local file, or have bunny.net fetch a URL server-side +bunny stream video upload ./video.mp4 --title "Launch demo" +bunny stream video fetch https://example.com/video.mp4 --header "Authorization: Bearer abc" + +# Find it, check it, embed it +bunny stream video list --search launch +bunny stream video show 1a2b3c4d-... +bunny stream video embed 1a2b3c4d-... --expires 1h + +# Organise with collections +bunny stream collection create --name Tutorials +bunny stream video update 1a2b3c4d-... --collection 8a7b6c5d-... +``` -## Typical workflows +--- + +## `bunny stream library`: Libraries ```bash -# 1. Look before importing: folders, video counts, what is already in Bunny -bunny stream import --library 12345 --source vimeo --dry-run +bunny stream library list # ID, name, videos, storage, traffic, replication regions +bunny stream library create my-library # stored in DE, replicated to SG, LA, NY by default +bunny stream library create my-library --replication-regions NY,SG # DE alone means no replication +bunny stream library create my-library --encoding-tier premium --codecs x264,vp9 +bunny stream library show my-library # API keys are never printed here +bunny stream library update my-library --resolutions 720p,1080p +bunny stream library update my-library --transcribing --transcribing-languages en,de +bunny stream library update my-library --add-replication-regions SYD # add-only +bunny stream library credentials my-library # library ID + API key, masked +bunny stream library credentials my-library --show-secret --read-only +bunny stream library credentials my-library --rotate # reset the key (confirms first) +bunny stream library link my-library +bunny stream library unlink +bunny stream library delete my-library # deletes every video in it; confirms with the count +``` -# 2. Import (confirms first; --force skips the prompt and is required without a TTY) -bunny stream import --library 12345 --source vimeo --force +`update` sends only the flags you pass. With no flags it opens an interactive editor (name, resolutions, transcribing); unattended runs need at least one flag. -# 3. Check how far Bunny has got with encoding -bunny stream import status --library 12345 --source vimeo +Run `bunny stream library --help` for every flag. The ones with consequences: -# One folder only (Vimeo/Wistia projects, Brightcove folders, S3 prefixes) -bunny stream import --library 12345 --source vimeo --folder 987654 --force +- `--codecs` other than `x264`, and `--jit`, need `--encoding-tier premium` (billed per codec per minute). +- `--early-play` needs `--keep-original` and exposes the originals. +- `--transcribing` bills per use on every new upload and re-encode. +- `--add-replication-regions` is add-only: regions can never be removed. +- `--content-tagging` is accepted on `update` only. +- `--force` is required for `update`, `unlink`, `delete`, and `credentials --rotate` without a TTY. -# S3, unattended, overriding the bucket and prefix for this run -bunny stream import --library 12345 --source s3 --bucket my-videos --prefix 2024/ --force +### Player and security -# Continue an interrupted or partly failed run -bunny stream import --library 12345 --source vimeo --resume --force +```bash +bunny stream library player show my-library +bunny stream library player update my-library --color "#FF7755" --speeds 0.5,1.0,1.15,2.0 --heatmap +bunny stream library player update my-library --no-controls +bunny stream library player reset my-library # dashboard defaults; confirms first +bunny stream library security show my-library # token key masked +bunny stream library security update my-library --embed-token --block-direct-access --allow-referrer example.com +bunny stream library security update my-library --drm-basic +bunny stream library security reset-token my-library # signed URLs stop working ``` ---- +Run `player update --help` and `security update --help` for the full flag lists; every boolean takes a `--no-` form. `security update` applies domain changes one at a time and skips ones already in place, so re-running a partly failed update is safe. -## `bunny stream import`: Import a video library +--- -| Flag | Description | -| ----------------------------------- | -------------------------------------------------------------------------------------------- | -| `--library`, `--lib` | Destination library name or ID (defaults to the linked library) | -| `--source`, `-s` | `vimeo`, `s3`, `wistia`, `mux`, `cloudflare`, `jwplayer`, `brightcove` | -| `--folder` | One source folder only (not for Mux, Cloudflare Stream, or JW Player, which have no folders) | -| `--dry-run` | Print the plan; writes nothing (no state file, no link, nothing in the library) | -| `--resume` | Continue the saved import for this source and library | -| `--wait` | Stay until Bunny has encoded every video (default: return once they are queued) | -| `--force`, `-f` | Skip the confirmation (which defaults to No) | -| `--concurrency`, `-c` | Videos handed to Bunny in parallel, 1 to 20 (default 3) | -| `--bucket`, `--prefix`, `--url-ttl` | S3 overrides for one run | -| `--request-timeout` | Seconds per HTTP request (default 30) | -| `--video-timeout` | Seconds per video hand-off, or per encode wait with `--wait` (default 5400) | -| `--processing-timeout` | With `--wait`, seconds per encode (default 3600); raises `--video-timeout` when larger | +## `bunny stream video`: Videos -**Re-running is safe.** Every imported video carries a per-source metaTag (`vimeoId`, `s3Source`, `wistiaId`, `muxAssetId`, `cfStreamId`, `jwPlayerId`, `brightcoveId`). A re-run skips tagged videos Bunny has finished, leaves ones still encoding alone, and imports again only the ones Bunny failed on. Source folders become Stream collections of the same name. +```bash +bunny stream video upload ./video.mp4 --lib 12345 --title "Launch demo" +bunny stream video upload ./video.mp4 --collection 8a7b6c5d-... +bunny stream video fetch https://example.com/video.mp4 --lib 12345 +bunny stream video list --collection 8a7b6c5d-... +bunny stream video show 1a2b3c4d-... # includes caption languages and the Direct Play URL +bunny stream video update 1a2b3c4d-... --title "Launch demo" +bunny stream video update 1a2b3c4d-... --chapters '[{"title":"Intro","start":0,"end":30}]' +bunny stream video update 1a2b3c4d-... --collection "" # remove from its collection +bunny stream video thumbnail 1a2b3c4d-... --file ./thumb.jpg # or --url +bunny stream video resolutions 1a2b3c4d-... # configured, encoded, and stored +bunny stream video cleanup 1a2b3c4d-... --non-configured --dry-run +bunny stream video stats 1a2b3c4d-... --from 2026-01-01 --to 2026-01-31 +bunny stream video embed 1a2b3c4d-... --expires 30m # signed when embed tokens are on +bunny stream video delete 1a2b3c4d-... +``` -**Progress** is saved to `$XDG_STATE_HOME/bunnynet/stream-import//-.json` (default `~/.local/state/...`). `--resume` retries failed videos, works outstanding ones, and keeps the `--folder` scope the run started with. Two imports cannot share one state file; the second fails while the first is running. +There is no `video create`: add a video with `upload` (local file) or `fetch` (bunny.net downloads a URL). -**Ctrl-C** stops queuing new videos, lets a hand-off already sent to Bunny finish so it is recorded, prints the `--resume` command, and exits 130. A second Ctrl-C exits at once. +- **`upload`** creates the entry, then sends the bytes. Files over 2 GB switch to resumable TUS uploads automatically. If the byte upload fails, an entry that never received bytes is deleted; one that may hold the upload is kept with a warning naming its GUID. Encoding continues after the command returns, so the status is usually `Uploaded` or `Processing`. +- **`fetch`** returns once the fetch is queued and reports no video ID; the video shows up in `video list` once fetched. +- **`cleanup`** deletes renditions, MP4 fallbacks, or the original, and requires at least one selector (`--resolutions`, `--non-configured`, `--all`, `--original`, `--mp4`, `--outputs hls|mp4|all`). `--dry-run` lists what would go and skips the confirmation. -**Exit codes:** 0 when everything was queued (or finished with `--wait`), 1 when any video failed (they are listed), 130 when paused. +Run `bunny stream video --help` for every flag. `--force` is required for `delete` and `cleanup` without a TTY. -### JSON output +--- -Every `--output json` document carries an `outcome`: +## `bunny stream collection` and `bunny stream caption` -- `no_videos`, `nothing_new`, `dry_run`: stopped before importing. Shape `{ outcome, library, source, folder, dryRun, truncated, summary }`; `summary` holds the counts and name lists (capped at 10000; `truncated` says when). -- `imported`, `paused`: shape `{ outcome, library, source, folder, dryRun, status, waited, queued, completed, processing, failed, collections, warnings, statePath, elapsedMs }`, each `failed` entry `{ name, sourceId, error }`. On `paused`, `library` and `statePath` may be null if it stopped before reaching them. +```bash +bunny stream collection list --search tut +bunny stream collection create --name Tutorials +bunny stream collection show 8a7b6c5d-... +bunny stream collection rename 8a7b6c5d-... Guides +bunny stream collection delete 8a7b6c5d-... # deletes the videos inside it too + +bunny stream caption add 1a2b3c4d-... en --file ./captions.vtt --label "English" +bunny stream caption delete 1a2b3c4d-... en +``` -Declining the confirmation prints nothing. +Deleting a collection deletes every video in it. Move videos out first with `video update --collection ""`. `caption add` takes a local `.vtt` or `.srt` and reports the API validator's errors and warnings. --- -## `bunny stream import status`: Check a queued import +## Paid commands: `encode`, `transcribe`, `smart` ```bash -bunny stream import status --library 12345 -bunny stream import status --library 12345 --source s3 --output json +bunny stream encode enable --codecs x264,vp9 # switch the library to premium encoding +bunny stream encode reencode 1a2b3c4d-... # re-encode one video with current settings +bunny stream transcribe 1a2b3c4d-... --languages en,de --source-language en +bunny stream smart 1a2b3c4d-... --title --chapters # generate fields from the transcript ``` -Reads the saved run (pass `--source` when more than one platform was imported into the library), asks Bunny for the current state of every video it queued, and prints Bunny's status, encode percentage, size, and when each was queued. A video still processing with no data after 30 minutes is marked `(stalled?)`: delete it in the dashboard and re-run the import. Exits 1 when any video failed, so it works as a poll in scripts. JSON: `{ library, source, status, completed, processing, failed, stalled, stalledAfterMinutes, saved, videos }`. +All three state the cost before running. + +- **`encode enable`** bills per output codec per minute; `--jit`, `--codecs`, `--resolutions` apply in the same request. `encode reencode` bills at the same rate. +- **`transcribe`** costs $0.10 per language-minute (source transcript plus each target). The estimate is exact with `--source-language`, a range without. `--generate-title`, `--generate-description`, `--generate-chapters`, `--generate-moments` also generate fields. **`--force` here is the API's force flag** (re-run and override library defaults), not a confirmation skip. +- **`smart`** needs at least one of `--title`, `--description`, `--chapters`, `--moments`. `--source-language` picks the caption track (auto-generated tracks are `-auto`; `en` falls back to `en-auto`). On a video with no captions it shows the transcription cost and asks first; unattended runs stop unless `--transcribe` approves it. `--force` only disables pickers. --- ## Anti-patterns -- **Passing credentials as flags or pasting them into commands.** They are read from environment variables only; export them (or use a `.env` the shell loads) instead. -- **Importing without a dry run.** Run `--dry-run` first to see the folder and video counts and what is already in Bunny. -- **Holding the command open with `--wait` in automation.** Queue with `--force`, then poll `bunny stream import status` until it exits 0; encodes can take hours. -- **Deleting the state file to "start fresh".** It is how a resume recovers videos Bunny created but never tagged; deleting it can create duplicates. -- **Treating `(stalled?)` as success.** Bunny gives no signal for a fetch that died silently; the stalled video needs deleting and re-importing. +- **Deleting a collection to "ungroup" videos.** It deletes the videos too; clear `--collection` on each video first. +- **Running `stream transcribe` on a library that transcribes automatically.** A fresh upload or `encode reencode` already triggers transcription; transcribing again bills twice. +- **Using `--force` on `transcribe` to skip a prompt.** It re-runs the transcription and overrides library defaults. +- **Expecting `video fetch` to return a video ID.** Poll `video list` instead. +- **Running `video cleanup` without `--dry-run` first.** A deleted original is gone, and re-creating renditions means a paid re-encode. +- **Expecting to remove replication regions.** `--add-replication-regions` is add-only, permanently. +- **Rotating keys casually.** `credentials --rotate` and `security reset-token` break existing integrations and signed URLs. From e2afcb3c0e2dd26bdf5ae6094f9ba269f1c022c3 Mon Sep 17 00:00:00 2001 From: Amir Keshavarz Date: Thu, 15 Oct 2026 16:08:50 +0200 Subject: [PATCH 2/2] docs(stream): update the agent skill for the restructured stream commands Plural libraries, videos, and collections namespaces; captions, encode, transcribe, and smart under videos; stream link, unlink, and regions; the new create, credentials, and rotate flags; and drop the preview label. --- .changeset/stream-skills.md | 2 +- packages/cli/src/commands/skills/content.ts | 2 +- skills/bunny-cli/SKILL.md | 15 +- skills/bunny-cli/references/stream-import.md | 4 +- skills/bunny-cli/references/stream.md | 191 ++++++++++--------- 5 files changed, 110 insertions(+), 104 deletions(-) diff --git a/.changeset/stream-skills.md b/.changeset/stream-skills.md index 5b8ea5ba..bf2faceb 100644 --- a/.changeset/stream-skills.md +++ b/.changeset/stream-skills.md @@ -2,4 +2,4 @@ "@bunny.net/cli": patch --- -Add the `bunny stream` library, video, collection, caption, and paid commands to the agent skill, and bring the import reference up to date +Add the `bunny stream` libraries, videos, captions, collections, and paid commands to the agent skill, and bring the import reference up to date diff --git a/packages/cli/src/commands/skills/content.ts b/packages/cli/src/commands/skills/content.ts index 9e868f4f..c20b1e03 100644 --- a/packages/cli/src/commands/skills/content.ts +++ b/packages/cli/src/commands/skills/content.ts @@ -40,7 +40,7 @@ This project uses bunny.net. Manage its resources with the \`bunny\` CLI: databa - When the CLI has no command for something, fall back to \`bunny api \` against api.bunny.net.`; /** The shipped bunny-cli skill, embedded at bundle time from skills/bunny-cli/. */ -// Experimental namespaces (apps, registries) stay out of the skill until they stabilize; stream ships early, labelled experimental. +// Experimental namespaces (apps, registries) stay out of the skill until they stabilize. export const BUNNY_CLI_SKILL: ProjectSkill = { name: "bunny-cli", agentsSection: AGENTS_SECTION, diff --git a/skills/bunny-cli/SKILL.md b/skills/bunny-cli/SKILL.md index e69a3d93..ea41e01f 100644 --- a/skills/bunny-cli/SKILL.md +++ b/skills/bunny-cli/SKILL.md @@ -68,13 +68,12 @@ bunny sites deploy ./dist # deploy a directory and p bunny sites domains add example.com --wait # custom production domain bunny sites deployments publish --previous --force # instant rollback -# manage Bunny Stream video (experimental) -bunny stream library create my-library -bunny stream library link my-library -bunny stream video upload ./video.mp4 --title "Launch demo" -bunny stream video list +# manage Bunny Stream video +bunny stream libraries create my-library --link --save-env --no-add-videos # link this directory, write .env +bunny stream videos upload ./video.mp4 --title "Launch demo" +bunny stream videos list -# import a video library into Bunny Stream (experimental) +# import a video library into Bunny Stream bunny stream import list --source vimeo # browse folders first bunny stream import --library 12345 --source vimeo --dry-run # plan first bunny stream import --library 12345 --source vimeo --force # hand every video to Bunny @@ -93,8 +92,8 @@ Use this to route to the correct reference file: - **Static sites (create, deploy, rollback, custom domains, GitHub Actions)** -> `references/sites.md` - **Edge Storage (zones, files, connection credentials, S3/FTP/HTTP, custom domains)** -> `references/storage.md` - **Sandboxes (create, exec, ssh, files list/cp, public URLs, persistent env vars, Claude Code auth)** -> `references/sandbox.md` -- **Bunny Stream (experimental: libraries, player/security settings, video upload/fetch/embed/cleanup, collections, captions, encoding, transcription, smart titles/chapters)** -> `references/stream.md` -- **Import a video library into Bunny Stream (experimental: Vimeo, S3, Wistia, Mux, Cloudflare Stream, JW Player, Brightcove; browse, dry run, resume, status)** -> `references/stream-import.md` +- **Bunny Stream (libraries, player/security settings, video upload/fetch/embed/cleanup, captions, collections, encoding, transcription, smart titles/chapters)** -> `references/stream.md` +- **Import a video library into Bunny Stream (Vimeo, S3, Wistia, Mux, Cloudflare Stream, JW Player, Brightcove; browse, dry run, resume, status)** -> `references/stream-import.md` - **Make raw API requests** -> `references/api.md` - **CLI doesn't have a command for it** -> use `bunny api` as a fallback (see `references/api.md`) diff --git a/skills/bunny-cli/references/stream-import.md b/skills/bunny-cli/references/stream-import.md index 0d6bce7f..79a1a19d 100644 --- a/skills/bunny-cli/references/stream-import.md +++ b/skills/bunny-cli/references/stream-import.md @@ -1,7 +1,5 @@ # Stream Import Commands -> **Experimental.** `bunny stream` is hidden from `--help` while it stabilizes. Flags and JSON shapes may still change. - `bunny stream import` moves an existing video library into a Bunny Stream library from **Vimeo**, **AWS S3** (or an S3-compatible provider), **Wistia**, **Mux**, **Cloudflare Stream**, **JW Player**, or **Brightcove**. Bunny fetches every file straight from the source by URL, so nothing is downloaded to the local machine. The command hands each video to Bunny, tags it, and returns once they are all queued; encoding then happens on Bunny's side. ## Resolving the library @@ -9,7 +7,7 @@ `--library` (alias `--lib`) takes a library name or numeric ID. When omitted, it resolves in this order: 1. `--library` -2. `.bunny/stream.json` (the directory's linked library, see `stream.md`) +2. `.bunny/stream.json` (the directory's linked library, written by `bunny stream link`; see `stream.md`) 3. Interactive picker, which offers to link the directory (never on `--dry-run`) The library's own Stream API key is read from the account automatically and never printed. diff --git a/skills/bunny-cli/references/stream.md b/skills/bunny-cli/references/stream.md index a0900e83..5565ae16 100644 --- a/skills/bunny-cli/references/stream.md +++ b/skills/bunny-cli/references/stream.md @@ -1,167 +1,176 @@ # Stream Commands -> **Experimental.** `bunny stream` is hidden from `--help` while it stabilizes. Flags and JSON shapes may still change. - All Stream commands live under `bunny stream`, split into resource groups: -- **`bunny stream library`** (aliases `libraries`, `lib`) manages the video library itself, plus its `player` and `security` settings, with the account API key. -- **`bunny stream video`** (alias `videos`) manages the videos inside one library. -- **`bunny stream collection`** (alias `collections`) manages groups of videos within a library. -- **`bunny stream caption`** (alias `captions`) manages caption files on one video. -- **`bunny stream encode`**, **`bunny stream transcribe`**, and **`bunny stream smart`** are paid operations. +- **`bunny stream libraries`** (aliases `library`, `lib`) manages the video library itself, plus its `player` and `security` settings, with the account API key. +- **`bunny stream videos`** (alias `video`) manages the videos inside one library, their `captions`, and the paid `encode`, `transcribe`, and `smart`. +- **`bunny stream collections`** (alias `collection`) manages groups of videos within a library. +- **`bunny stream link`** and **`bunny stream unlink`** mirror `libraries link` and `libraries unlink`. +- **`bunny stream regions`** lists the region codes a library can replicate to. - **`bunny stream import`** moves a library in from another platform; see `stream-import.md`. -Each library carries its own Stream API key for the video-level API. Video commands resolve it from the library automatically, so there is nothing extra to configure; the key is never printed unless you ask for it with `library credentials`. +Each library carries its own Stream API key for the video-level API. Video commands resolve it from the library automatically, so there is nothing extra to configure; the key is never printed unless you ask for it with `libraries credentials`. ## Resolving the library -`library` commands take the library as an optional positional (name or numeric ID). Every other stream command takes it as `--lib` (alias `--library`), because its positional is a video GUID, a collection ID, or a local file path. When omitted, the library resolves in this order: +`libraries` commands take the library as an optional positional (name or numeric ID). Every other stream command takes it as `--lib` (alias `--library`), because its positional is a video GUID, a collection ID, or a local file path. When omitted, the library resolves in this order: 1. Explicit name or ID -2. `.bunny/stream.json` (written by `bunny stream library link`) +2. `.bunny/stream.json` (written by `bunny stream link`) 3. Interactive picker, which offers to link the directory (never on destructive commands) -Non-interactive runs (`--output json`, no TTY, or `--force`) error instead of prompting: pass a library or link the directory first. A `video` or `collection` command with no ID behaves the same way. `caption` commands always need the video GUID as a positional. +Non-interactive runs (`--output json`, no TTY, or `--force`) error instead of prompting: pass a library or link the directory first. A `videos`, `collections`, or `videos captions` command with no ID behaves the same way. On destructive commands and `videos smart`, `--force` also disables the pickers, so it never acts on something you did not name; on `videos transcribe` that is `--overwrite`, and `videos encode` has neither. ## Typical workflows ```bash -# New library, linked to this directory -bunny stream library create my-library -bunny stream library link my-library +# New library: link this directory and write BUNNY_STREAM_LIBRARY_ID and BUNNY_STREAM_API_KEY to .env +bunny stream libraries create my-library --link --save-env --no-add-videos # Upload a local file, or have bunny.net fetch a URL server-side -bunny stream video upload ./video.mp4 --title "Launch demo" -bunny stream video fetch https://example.com/video.mp4 --header "Authorization: Bearer abc" +bunny stream videos upload ./video.mp4 --title "Launch demo" +bunny stream videos fetch https://example.com/video.mp4 --header "Authorization: Bearer abc" # Find it, check it, embed it -bunny stream video list --search launch -bunny stream video show 1a2b3c4d-... -bunny stream video embed 1a2b3c4d-... --expires 1h +bunny stream videos list --search launch +bunny stream videos show 1a2b3c4d-... +bunny stream videos embed 1a2b3c4d-... --expires 1h # Organise with collections -bunny stream collection create --name Tutorials -bunny stream video update 1a2b3c4d-... --collection 8a7b6c5d-... +bunny stream collections create Tutorials +bunny stream videos update 1a2b3c4d-... --collection 8a7b6c5d-... ``` --- -## `bunny stream library`: Libraries +## `bunny stream libraries`: Libraries ```bash -bunny stream library list # ID, name, videos, storage, traffic, replication regions -bunny stream library create my-library # stored in DE, replicated to SG, LA, NY by default -bunny stream library create my-library --replication-regions NY,SG # DE alone means no replication -bunny stream library create my-library --encoding-tier premium --codecs x264,vp9 -bunny stream library show my-library # API keys are never printed here -bunny stream library update my-library --resolutions 720p,1080p -bunny stream library update my-library --transcribing --transcribing-languages en,de -bunny stream library update my-library --add-replication-regions SYD # add-only -bunny stream library credentials my-library # library ID + API key, masked -bunny stream library credentials my-library --show-secret --read-only -bunny stream library credentials my-library --rotate # reset the key (confirms first) -bunny stream library link my-library -bunny stream library unlink -bunny stream library delete my-library # deletes every video in it; confirms with the count +bunny stream libraries list # ID, name, videos, storage, traffic, replication regions +bunny stream libraries create my-library # stored in DE, replicated to SG, LA, NY by default +bunny stream libraries create my-library --replication-regions NY,SG # DE alone means no replication +bunny stream libraries create my-library --encoding-tier premium --codecs x264,vp9 +bunny stream libraries show my-library # API keys are never printed here +bunny stream libraries update my-library --resolutions 720p,1080p +bunny stream libraries update my-library --transcribing --transcribing-languages en,de +bunny stream libraries update my-library --encoding-tier premium --codecs x264,vp9 # paid; warns about billing +bunny stream libraries update my-library --add-replication-regions SYD # add-only +bunny stream libraries credentials my-library # library ID + API key, masked +bunny stream libraries credentials my-library --show-secret --read-only +bunny stream libraries credentials my-library --save-env # write BUNNY_STREAM_LIBRARY_ID and BUNNY_STREAM_API_KEY to .env +bunny stream libraries credentials my-library --rotate # reset the key (confirms first) +bunny stream libraries delete my-library # deletes every video in it; confirms with the count +bunny stream regions # DE is the fixed main region; SG, LA, NY the default replicas +bunny stream link my-library # same as `libraries link` +bunny stream unlink ``` -`update` sends only the flags you pass. With no flags it opens an interactive editor (name, resolutions, transcribing); unattended runs need at least one flag. +`create` takes the name as its positional. After creating, it offers to link the directory, save `BUNNY_STREAM_LIBRARY_ID` and `BUNNY_STREAM_API_KEY` to `.env` (the names the Stream examples read), and add videos by importing from another platform or uploading a file. `--link`/`--no-link` and `--save-env`/`--no-save-env` answer the first two, `--no-add-videos` skips the third; non-interactive runs skip anything a flag does not set. + +`update` sends only the flags you pass. With no flags it opens an interactive editor (name, resolutions, transcribing, keep original, MP4 fallback, content tagging); unattended runs need at least one flag. Rename with `update --name`. -Run `bunny stream library --help` for every flag. The ones with consequences: +Run `bunny stream libraries --help` for every flag. The ones with consequences: -- `--codecs` other than `x264`, and `--jit`, need `--encoding-tier premium` (billed per codec per minute). +- `--encoding-tier premium` bills per output codec per minute. `--codecs` other than `x264`, and `--jit`, need it. - `--early-play` needs `--keep-original` and exposes the originals. - `--transcribing` bills per use on every new upload and re-encode. - `--add-replication-regions` is add-only: regions can never be removed. - `--content-tagging` is accepted on `update` only. -- `--force` is required for `update`, `unlink`, `delete`, and `credentials --rotate` without a TTY. +- `--force` is required for `unlink`, `delete`, `credentials --rotate`, `player reset`, and `security show --rotate` without a TTY. ### Player and security ```bash -bunny stream library player show my-library -bunny stream library player update my-library --color "#FF7755" --speeds 0.5,1.0,1.15,2.0 --heatmap -bunny stream library player update my-library --no-controls -bunny stream library player reset my-library # dashboard defaults; confirms first -bunny stream library security show my-library # token key masked -bunny stream library security update my-library --embed-token --block-direct-access --allow-referrer example.com -bunny stream library security update my-library --drm-basic -bunny stream library security reset-token my-library # signed URLs stop working +bunny stream libraries player show my-library +bunny stream libraries player update my-library --color "#FF7755" --speeds 0.5,1.0,1.15,2.0 --heatmap +bunny stream libraries player update my-library --no-controls +bunny stream libraries player reset my-library # dashboard defaults; confirms first +bunny stream libraries security show my-library # token key masked +bunny stream libraries security update my-library --embed-token --block-direct-access --allow-referrer example.com +bunny stream libraries security update my-library --drm-basic +bunny stream libraries security show my-library --rotate # reset the token key (confirms first); signed URLs stop working ``` Run `player update --help` and `security update --help` for the full flag lists; every boolean takes a `--no-` form. `security update` applies domain changes one at a time and skips ones already in place, so re-running a partly failed update is safe. --- -## `bunny stream video`: Videos +## `bunny stream videos`: Videos ```bash -bunny stream video upload ./video.mp4 --lib 12345 --title "Launch demo" -bunny stream video upload ./video.mp4 --collection 8a7b6c5d-... -bunny stream video fetch https://example.com/video.mp4 --lib 12345 -bunny stream video list --collection 8a7b6c5d-... -bunny stream video show 1a2b3c4d-... # includes caption languages and the Direct Play URL -bunny stream video update 1a2b3c4d-... --title "Launch demo" -bunny stream video update 1a2b3c4d-... --chapters '[{"title":"Intro","start":0,"end":30}]' -bunny stream video update 1a2b3c4d-... --collection "" # remove from its collection -bunny stream video thumbnail 1a2b3c4d-... --file ./thumb.jpg # or --url -bunny stream video resolutions 1a2b3c4d-... # configured, encoded, and stored -bunny stream video cleanup 1a2b3c4d-... --non-configured --dry-run -bunny stream video stats 1a2b3c4d-... --from 2026-01-01 --to 2026-01-31 -bunny stream video embed 1a2b3c4d-... --expires 30m # signed when embed tokens are on -bunny stream video delete 1a2b3c4d-... +bunny stream videos upload ./video.mp4 --lib 12345 --title "Launch demo" +bunny stream videos upload ./video.mp4 --collection 8a7b6c5d-... +bunny stream videos fetch https://example.com/video.mp4 --lib 12345 +bunny stream videos list --collection 8a7b6c5d-... +bunny stream videos show 1a2b3c4d-... # includes caption languages and the Direct Play URL +bunny stream videos update 1a2b3c4d-... --title "Launch demo" +bunny stream videos update 1a2b3c4d-... --chapters '[{"title":"Intro","start":0,"end":30}]' +bunny stream videos update 1a2b3c4d-... --collection "" # remove from its collection +bunny stream videos thumbnail 1a2b3c4d-... --file ./thumb.jpg # or --url +bunny stream videos resolutions 1a2b3c4d-... # configured, encoded, and stored +bunny stream videos cleanup 1a2b3c4d-... --non-configured --dry-run +bunny stream videos stats 1a2b3c4d-... --from 2026-01-01 --to 2026-01-31 +bunny stream videos embed 1a2b3c4d-... --expires 30m # signed when embed tokens are on +bunny stream videos delete 1a2b3c4d-... ``` -There is no `video create`: add a video with `upload` (local file) or `fetch` (bunny.net downloads a URL). +There is no `videos create`: add a video with `upload` (local file) or `fetch` (bunny.net downloads a URL). - **`upload`** creates the entry, then sends the bytes. Files over 2 GB switch to resumable TUS uploads automatically. If the byte upload fails, an entry that never received bytes is deleted; one that may hold the upload is kept with a warning naming its GUID. Encoding continues after the command returns, so the status is usually `Uploaded` or `Processing`. -- **`fetch`** returns once the fetch is queued and reports no video ID; the video shows up in `video list` once fetched. -- **`cleanup`** deletes renditions, MP4 fallbacks, or the original, and requires at least one selector (`--resolutions`, `--non-configured`, `--all`, `--original`, `--mp4`, `--outputs hls|mp4|all`). `--dry-run` lists what would go and skips the confirmation. +- **`fetch`** returns once the fetch is queued and reports no video ID; the video shows up in `videos list` once fetched. +- **`cleanup`** deletes renditions, MP4 fallbacks, or the original, and requires at least one selector (`--resolutions`, `--non-configured`, `--all`, `--original`, `--mp4`); `--outputs hls|mp4|all` only narrows which outputs those selectors touch. `--dry-run` lists what would go and skips the confirmation. + +Run `bunny stream videos --help` for every flag. `--force` is required for `delete` and `cleanup` without a TTY. + +### Captions + +```bash +bunny stream videos captions add ./captions.vtt 1a2b3c4d-... en --label "English" +bunny stream videos captions remove 1a2b3c4d-... en +``` -Run `bunny stream video --help` for every flag. `--force` is required for `delete` and `cleanup` without a TTY. +`captions add [video] [lang]` takes a local `.vtt` or `.srt` and reports the API validator's errors and warnings; `--label` defaults to the language code. `captions remove [video] [lang]` offers the video's caption languages to pick from interactively and needs `--force` without a TTY. Unattended runs need the video and the language. These are captions you wrote yourself, unrelated to the paid transcription. --- -## `bunny stream collection` and `bunny stream caption` +## `bunny stream collections`: Collections ```bash -bunny stream collection list --search tut -bunny stream collection create --name Tutorials -bunny stream collection show 8a7b6c5d-... -bunny stream collection rename 8a7b6c5d-... Guides -bunny stream collection delete 8a7b6c5d-... # deletes the videos inside it too - -bunny stream caption add 1a2b3c4d-... en --file ./captions.vtt --label "English" -bunny stream caption delete 1a2b3c4d-... en +bunny stream collections list --search tut +bunny stream collections create Tutorials +bunny stream collections show 8a7b6c5d-... +bunny stream collections update 8a7b6c5d-... --name Guides +bunny stream collections delete 8a7b6c5d-... # deletes the videos inside it too ``` -Deleting a collection deletes every video in it. Move videos out first with `video update --collection ""`. `caption add` takes a local `.vtt` or `.srt` and reports the API validator's errors and warnings. +Deleting a collection deletes every video in it. Move videos out first with `videos update --collection ""`. --- -## Paid commands: `encode`, `transcribe`, `smart` +## Paid commands: premium encoding, `videos encode`, `videos transcribe`, `videos smart` ```bash -bunny stream encode enable --codecs x264,vp9 # switch the library to premium encoding -bunny stream encode reencode 1a2b3c4d-... # re-encode one video with current settings -bunny stream transcribe 1a2b3c4d-... --languages en,de --source-language en -bunny stream smart 1a2b3c4d-... --title --chapters # generate fields from the transcript +bunny stream libraries update my-library --encoding-tier premium --codecs x264,vp9 # switch the library to premium encoding +bunny stream videos encode 1a2b3c4d-... # re-encode one video with current settings (alias reencode) +bunny stream videos transcribe 1a2b3c4d-... --target-languages en,de --source-language en +bunny stream videos smart 1a2b3c4d-... --title --chapters # generate fields from the transcript ``` -All three state the cost before running. +All of them state the cost before running. -- **`encode enable`** bills per output codec per minute; `--jit`, `--codecs`, `--resolutions` apply in the same request. `encode reencode` bills at the same rate. -- **`transcribe`** costs $0.10 per language-minute (source transcript plus each target). The estimate is exact with `--source-language`, a range without. `--generate-title`, `--generate-description`, `--generate-chapters`, `--generate-moments` also generate fields. **`--force` here is the API's force flag** (re-run and override library defaults), not a confirmation skip. -- **`smart`** needs at least one of `--title`, `--description`, `--chapters`, `--moments`. `--source-language` picks the caption track (auto-generated tracks are `-auto`; `en` falls back to `en-auto`). On a video with no captions it shows the transcription cost and asks first; unattended runs stop unless `--transcribe` approves it. `--force` only disables pickers. +- **Premium encoding** bills per output codec per minute; `--jit`, `--codecs`, `--resolutions` apply in the same request. `videos encode` regenerates every output at the library's encoding rate. +- **`videos transcribe`** costs $0.10 per language-minute (source transcript plus each target). The estimate is exact with `--source-language`, a range without. `--generate-title`, `--generate-description`, `--generate-chapters`, `--generate-moments` also generate fields. `--overwrite` replaces the existing transcription and overrides the library defaults (it is sent as the API's force flag), and disables the pickers. +- **`videos smart`** needs at least one of `--title`, `--description`, `--chapters`, `--moments`. `--source-language` picks the caption track (auto-generated tracks are `-auto`; `en` falls back to `en-auto`). On a video with no captions it shows the transcription cost and asks first; unattended runs stop unless `--transcribe` approves it. `--force` only disables pickers. --- ## Anti-patterns - **Deleting a collection to "ungroup" videos.** It deletes the videos too; clear `--collection` on each video first. -- **Running `stream transcribe` on a library that transcribes automatically.** A fresh upload or `encode reencode` already triggers transcription; transcribing again bills twice. -- **Using `--force` on `transcribe` to skip a prompt.** It re-runs the transcription and overrides library defaults. -- **Expecting `video fetch` to return a video ID.** Poll `video list` instead. -- **Running `video cleanup` without `--dry-run` first.** A deleted original is gone, and re-creating renditions means a paid re-encode. +- **Running `videos transcribe` on a library that transcribes automatically.** A fresh upload or `videos encode` already triggers transcription; transcribing again bills twice. +- **Using `--overwrite` on `videos transcribe` to skip a prompt.** It re-runs the transcription and overrides library defaults. +- **Expecting `videos fetch` to return a video ID.** Poll `videos list` instead. +- **Running `videos cleanup` without `--dry-run` first.** A deleted original is gone, and re-creating renditions means a paid re-encode. - **Expecting to remove replication regions.** `--add-replication-regions` is add-only, permanently. -- **Rotating keys casually.** `credentials --rotate` and `security reset-token` break existing integrations and signed URLs. +- **Rotating keys casually.** `credentials --rotate` and `security show --rotate` break existing integrations and signed URLs. +- **Using the old spellings.** `encode enable`, `encode reencode`, top-level `transcribe`, `smart`, and `captions`, `collections rename`, and `security reset-token` still work but are hidden; use the commands above.