Repository navigation
feat(stream): video libraries, directory linking, uploads, and video management - #203
Conversation
🦋 Changeset detectedLatest commit: a8b0e56 The changes in this PR will be included in the next version bump. This PR includes changesets to release 18 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…face Stream error messages Verbose debug logging read every request body as JSON, which would buffer an entire video into memory on octet-stream uploads; non-JSON bodies are now described from their headers instead. Error extraction also learns the Stream API's StatusModel shape (lowercase message), so 400/422/429 responses carry the API's explanation instead of a generic HTTP error.
…management New experimental stream namespace: - stream library: list, create, show, credentials, delete (aliases: libraries, lib) - stream link / unlink: bind a directory to a library via .bunny/stream.json - stream upload: local file via binary PUT, or a URL fetched server side (--header for authenticated origins); library from --lib <id>, the linked directory, or an interactive picker - stream videos: list, show, update, delete by GUID Library CRUD runs on the core API with the account key; uploads and video commands use the library's own Stream API key, resolved automatically. No output format prints API keys (credentials --show-secret is the deliberate reveal path); JSON output strips ApiKey, ReadOnlyApiKey, and the deprecated ApiAccessKey.
…ctions, and captions Restructure: the video group is canonical (videos stays an alias), upload splits into video upload (local files) and video fetch (server-side URL fetch with --header/--collection/--thumbnail-time), and link/unlink move under library. library update: new command for library settings, sharing the encoding and transcribing flags with create (--encoding-tier, --jit, --codecs, --resolutions, --transcribing and its language/generation options); sparse bodies send only the flagged fields, and a no-flag TTY run opens an interactive editor. New video verbs: thumbnail (--url or --file), resolutions, cleanup (full selector surface, confirmed, --dry-run), stats (--heatmap/--play-data), and update gains --collection/--chapters/--moments. New namespaces: collection (create/list/show/rename/delete) and caption (add with the API's validation surfaced, delete). Paid operations: encode enable/reencode, transcribe, and smart, each printing its billing note; smart confirms before implicitly transcribing a caption-less video. Uploads over 2 GiB switch to TUS resumable uploads automatically: 64 MiB chunks, every request signed, offsets driven by the server, and resume via HEAD after transient failures. openapi-client: verbose debug logging redacts secret-shaped fields (keys, passwords, tokens, authorization headers) from request and response body dumps. Hardening from review: pagination drains tolerate absent or misreported totalItems, --force disables pickers on paid commands too, collection show requests the preview URLs it documents, and video show reports captions.
1d2704d to
65772e2
Compare
Live testing showed the Stream API cascade-deletes a collection's videos; the confirmation, output, JSON shape (videosDeleted), and docs said the opposite. The prompt now names the video count, and the docs explain that clearing a video's collection first is the way to keep it.
…ing a phantom charge Live testing showed the smart endpoint refuses captionless videos even when the library has transcribing enabled; it never auto-transcribes. The confirmation gate promised a transcription charge that cannot happen and then failed anyway. A captionless video now errors up front with a pointer to stream transcribe / caption add, and smart's --force is picker-only.
# Conflicts: # packages/cli/src/cli.ts # packages/openapi-client/src/middleware.test.ts # packages/openapi-client/src/middleware.ts
* feat(tools): add stream tools Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(stream): CLI spec v1.1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(stream): PR #242 review fixes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(stream): smart maps --source-language to the video's caption tracks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(stream): cleanup --dry-run lists what would be deleted Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(openapi-client): verbose logging skips binary request bodies; surface Stream error messages Verbose debug logging read every request body as JSON, which would buffer an entire video into memory on octet-stream uploads; non-JSON bodies are now described from their headers instead. Error extraction also learns the Stream API's StatusModel shape (lowercase message), so 400/422/429 responses carry the API's explanation instead of a generic HTTP error. * test(openapi-client): redaction test sends an explicit JSON content-type * fix(cli): help no longer prints a flag twice when it has a long alias * feat(stream): import a video library from Vimeo, S3, Wistia, Mux, Cloudflare Stream, JW Player, or Brightcove * fix(stream-import): ANSI stripping and trailing-slash trimming run in linear time * fix(openapi-client): verbose logs redact Authorization headers and URL query strings * fix(stream): resume retries failed videos and keeps its scope; timeouts cancel their work; 429 replays get a fresh deadline * fix(stream-import): .ts is no longer treated as a video extension, so HLS segments are not imported * fix(stream-import-mux): only a public playback ID backs a stream.mux.com URL; signed-only assets fall through to the master * fix(stream-import-cloudflare): list videos with limit and an RFC 3339 after cursor instead of per_page and a uid * fix(stream-import-brightcove): only HTTPS renditions and masters are candidates * fix(stream-import-s3): static credentials are all or nothing; a custom endpoint is configurable and its host is validated * fix(stream-import): vimeo, wistia, and jwplayer fetch each source record once per video * feat(stream-import): queueing is the import; wait is opt-in, dedup ranks tagged videos by Bunny status, and a saved run can be refreshed against Bunny * feat(stream): import returns once Bunny has the videos, --wait stays with live progress, and import status shows Bunny's progress * fix(stream-import): a fresh run re-tags videos Bunny holds from a run that died before tagging; refresh flags videos stalled at zero bytes * feat(stream): import status marks videos stalled at zero bytes for over 30 minutes * fix(stream-import): recover orphans before the journal is overwritten, write state atomically under a run lock, and stop hiding ordinary errors as stack traces * fix(stream-import): adapters pick real Wistia and Mux renditions, surface auth errors, and skip signed, archived, or unready sources * fix(stream): import moves onto the tools layer, dry runs write nothing, and flags, prompts, help, and progress are tidied * chore(release): publish stream-import after openapi-client and include the openapi-client fixes in the changeset * docs(stream-import): add a trademark disclaimer to each source adapter README * fix(stream-import): a fetch with no video ID is recovered on the next run, stale locks are claimed safely, same-title lost fetches are never cross-matched, and the stack-frame check runs in linear time * fix(stream): import status locks before reading, engine logs no longer tear the spinner, --processing-timeout is not capped by --video-timeout, and a cancelled release publishes nothing * fix(stream-import): wistia pages through project media, and a folder-scoped resume imports only that folder * test(stream-import): give the folder fixture its videoCount * fix(stream-import): a folder-scoped --wait only waits on that folder's videos * fix(stream-import): a folder-scoped --wait also waits on saved entries recorded under that folder * refactor(stream): import runs on the tools layer as stream.import.sources, plan, run, and status, with source credentials read from the host env * fix(stream-import): failed runs keep the journal, pauses keep Bunny's video, scoped resumes never narrow, cancellation and 5xx retries work under Bun, and redaction covers userinfo and Basic auth * fix(stream-import): adapters list past provider caps, return null for deleted videos, pin download hosts, name missing permissions, and use ambient AWS credentials only when the host allows it * fix(stream): import tools share no state between calls, read only the env they are given, report paused and outcome in every result, and keep host-neutral errors * chore: release jobs wait for a published dependency, CI builds the stream-import packages, and docs match the tools and adapter behaviour * fix(stream-import): a pause lets an in-flight hand-off to Bunny settle before releasing the journal, and videos the source no longer lists are failed visibly * fix(stream-import): a pause during the source lookup starts no Bunny fetch; only a fetch already sent settles * docs(skills): ship an experimental stream import reference in the bunny-cli agent skill * feat(stream): offer to save import source credentials to the profile * feat(packages): framework detector (#241) * feat(sites): publish framework detection as @bunny.net/framework-detector The framework presets, framework and package manager detection, and the GitHub Actions workflow move out of the CLI into a dependency-free package, so the bunny.net dashboard and the Sites control plane run the same code as `bunny sites` instead of porting it. Detection becomes pure: detectFrameworkFrom() takes a snapshot of a project root (entry names, package.json, Gemfile, and each .csproj for Blazor WebAssembly), and the CLI's detectFramework()/detectPackageManager() build that snapshot from disk. The workflow gains an optional branch for repositories whose default branch is not main. The package follows database-client: dist/ for npm consumers, the root tsconfig paths for in-repo tooling (so the CLI needs no prebuild), npm publish from its own version-gated release job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sites): sort the content hash by code unit, not by locale contentHashId sorted paths with localeCompare, which follows the machine's locale: some locales order letters differently, and every locale ignores punctuation and case at first, so the same files could hash differently on two machines, and differently in the dashboard, which ports this function. Sort by UTF-16 code unit instead. Existing deploys keep their ids. A site's next content deploy gets a new content hash once, so an unchanged folder is published again instead of being skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cli): take @bunny.net/framework-detector as a devDependency The CLI is published with npm publish, which ships workspace:* as is, so a runtime dependency on it would fail npm i -g @bunny.net/cli. The binary inlines the package at build time, like every other workspace package the CLI uses, so it belongs with them in devDependencies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(framework-detector): quote the branch in the generated workflow A git ref may contain YAML flow syntax: `release,prod` rendered as `branches: [release,prod]` becomes two branches, so a push to `prod` would deploy, and `release]prod` breaks the YAML. The branch is now a quoted scalar; the default `main` is unchanged. The test parses the workflow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: one changeset for the branch, and bun add in the README AGENTS.md asks for one changeset per branch, so the content hash note joins the framework-detector one. The README shows bun add first, npm install for everyone else. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(framework-detector): read the workflow's on key on every Bun version Bun 1.3.1 parses YAML 1.1 and reads on as the boolean true; the pinned 1.3.11 keeps on, so the branch test failed in CI. Look the key up both ways. The workflow's doc comment also still said pushes to main, which the branch option changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(stream): saved import credentials live in an owner-only file and are dropped only when the source rejects them * removes outdated tests (#240) * test: exercise identifier quote escaping, assert comment-heavy SQL is sent, use documentation IPs * test: drop test-only exports and dead parseTableFromPath, test through real callers * test: delete duplicate, probe, and copied-source tests * test: use Bun.write for env fixtures * fix(stream): import review feedback: 5xx read retries, JIT statuses, private S3 dedup tags, metaTag merge, retry prompt, and import list * feat(stream): import skips failures it cannot fix, --clean-failed, --retry-stalled, --include-ts, 2160p warnings, and 1000-item library pages * style(stream-import-s3): wrap the video-key filter line for biome * style(stream): format the merged interactive test for biome --------- Co-authored-by: Amir Keshavarz <amir@bunny.net> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
# Conflicts: # bun.lock # packages/cli/src/core/format.test.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 25ef5757c9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (retryStalled && isStalled(existing, now)) { | ||
| await this.bunny.deleteVideo(existing.guid, signal); |
There was a problem hiding this comment.
Restrict stalled-video deletion to the selected folder
When --resume --folder <id> --retry-stalled uses a journal containing multiple folders, this loop examines every saved migration rather than the selected scope and can delete a stalled Bunny video from another folder. Because the later queue predicate excludes that now-ID-less out-of-scope entry, the command does not re-import it, so a folder-scoped run can permanently delete unrelated content.
Useful? React with 👍 / 👎.
| if (output === "json") { | ||
| logger.log(JSON.stringify(result, null, 2)); |
There was a problem hiding this comment.
Emit only one JSON document on partial updates
When any referrer operation fails, the tool returns complete: false; this branch prints that result, and the subsequent UserError makes defineCommand() print a second error object to stdout before exiting. Thus stream library security update --output json produces multiple JSON documents (or can lose the buffered first write) precisely on partial failures, breaking JSON consumers; preserve the nonzero exit without throwing after the result has been emitted, or emit one combined payload.
AGENTS.md reference: AGENTS.md:L364-L369
Useful? React with 👍 / 👎.
| JSON.stringify( | ||
| { | ||
| queued: true, | ||
| url, |
There was a problem hiding this comment.
Redact credentials from echoed fetch URLs
When the input is a presigned URL or otherwise carries credentials in its query string or userinfo, this field writes the complete secret-bearing URL to stdout; the text success message and spinner also echo it. This exposes download tokens in CI logs and captured JSON even though the user asked to submit the URL, not reveal its credentials; retain the full URL only for the API request and redact its query/userinfo in every displayed result.
AGENTS.md reference: AGENTS.md:L254-L256
Useful? React with 👍 / 👎.
| const content = await this.adapter.listContent( | ||
| folderId ? { folderId } : undefined, | ||
| ); |
There was a problem hiding this comment.
Propagate cancellation through import discovery
During the actual interruptible import run, a first Ctrl-C aborts ctx.signal, but source discovery receives no signal and runMigration() does not inspect the abort until after both source discovery and the Bunny video index finish. On large paginated sources, the command can therefore continue making requests for minutes instead of returning a paused result, forcing users to send the second Ctrl-C and lose the clean resume path; thread the run signal through listContent and the destination listing or check it during pagination.
AGENTS.md reference: AGENTS.md:L191-L191
Useful? React with 👍 / 👎.
| const SECRET_KEY_RE = | ||
| /^(.*key|.*password|.*secret|.*token|.*credentials?|.*authorization.*|.*cookie)$/i; |
There was a problem hiding this comment.
Redact password fragments in verbose responses
With --verbose, Magic Containers registry responses can contain the string fields firstPasswordSymbols and lastPasswordSymbols. The previous substring matcher redacted these, but this new suffix-anchored expression does not match them because their names end in Symbols, so password fragments are now written to debug logs; match password-bearing names anywhere or explicitly cover these fields.
AGENTS.md reference: AGENTS.md:L254-L256
Useful? React with 👍 / 👎.
A new experimental
streamnamespace for managing bunny.net Stream, plus a smallopenapi-clientfix (verbose logging skips binary request bodies, redacts secret-shaped fields, and surfaces Stream API error messages).Commands
bunny stream library(aliaseslibraries,lib)list,create,show,update,deletecredentials: retrieve a library's Stream API keys (masked unless--show-secret)link/unlink: bind the current directory to a library via.bunny/stream.jsonso other stream commands can omit--libcreate/updatecarry the encoding and transcribing settings:--encoding-tier,--jit,--codecs,--resolutions,--transcribing,--transcribing-languages, and the generation togglesbunny stream video(aliasvideos)upload <file>: create-and-upload in one command; files over 2 GiB upload resumably via TUSfetch <url>: server-side fetch of a remote video, with--headerfor authenticated originslist(--search,--collection),show,update(--title,--collection,--chapters,--moments),deletethumbnail(--urlor--file),resolutions,cleanup,stats(--heatmap,--play-data)uploadandfetchaccept--collectionand--thumbnail-timebunny stream collection(aliascollections)create,list,show,rename,deletebunny stream caption(aliascaptions)add <video> <lang> --file(.vtt/.srt),delete <video> <lang>Paid operations
bunny stream encode enable/encode reencodebunny stream transcribe <video>(--languages,--source-language,--generate-*,--force)bunny stream smart <video>(--title,--description,--chapters,--moments)All commands support
--output json|csv|markdown, resolve the library from--lib <id>, the linked directory, or an interactive picker, and take--forceto skip confirmations non-interactively.