Skip to content

Stream CLI spec v1.1 (iterates on #203) - #242

Merged
amir-at-bunny merged 5 commits into
BunnyWay:feat/streamfrom
NathanZBunny:feat/stream-v1.1
Oct 7, 2026
Merged

amir-at-bunny merged 5 commits into
BunnyWay:feat/streamfrom
NathanZBunny:feat/stream-v1.1

Conversation

@NathanZBunny

Copy link
Copy Markdown
Contributor

Iterates on #203 with the Stream CLI spec v1.1. Targets feat/stream so it can be reviewed and merged into #203.

Commits

  1. feat(tools): add stream tools: adds stream tools to packages/tools
  2. feat(stream): CLI spec v1.1: the CLI changes

Important

The tools commit must be merged together with the CLI commit. Don't merge or cherry-pick one without the other.

What's new

  • Library player commands: stream library player show | update | reset
  • Library security commands: stream library security show | update | reset-token
  • Video embed: stream video embed
  • Default replication regions when you create a library
  • Encoding flags on library create/update and encode reencode
  • credentials --rotate for library credentials
  • Smart transcription offer, with a transcription cost estimate

Checks

  • bun run typecheck ✅
  • bunx biome ci . ✅
  • bun test in packages/tools (38 pass) and packages/cli (696 pass) ✅

🤖 Generated with Claude Code

NathanZBunny and others added 2 commits October 5, 2026 21:14
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bunnynet-devops

Copy link
Copy Markdown

@codex review

@changeset-bot

changeset-bot Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 6b9898a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NathanZBunny
NathanZBunny marked this pull request as draft October 5, 2026 20:46
@NathanZBunny
NathanZBunny marked this pull request as ready for review October 5, 2026 20:46
@bunnynet-devops

Copy link
Copy Markdown

@codex review

NathanZBunny and others added 2 commits October 5, 2026 21:54
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jamie-at-bunny
jamie-at-bunny requested review from amir-at-bunny and jamie-at-bunny and removed request for amir-at-bunny October 6, 2026 07:56
@amir-at-bunny

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@amir-at-bunny
amir-at-bunny merged commit 50c69f9 into BunnyWay:feat/stream Oct 7, 2026
1 check passed
amir-at-bunny added a commit that referenced this pull request Oct 7, 2026
…management (#203)

* 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.

* feat(stream): video libraries, directory linking, uploads, and video 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.

* feat(stream): full command surface for video libraries, videos, collections, 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.

* fix(stream): collection delete warns that its videos are deleted too

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.

* fix(stream): smart requires an existing transcript instead of confirming 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.

* refactor testing and upload error message fixes

* Stream CLI spec v1.1 (iterates on #203) (#242)

* 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>

* feat(stream): import (#224)

* 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>

---------

Co-authored-by: jamie-at-bunny <jamie@bunny.net>
Co-authored-by: NathanZBunny <nathan@bunny.net>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants