Skip to content

DX-2969: add Blob commands - #17

Open
ytkimirti wants to merge 1 commit into
mainfrom
DX-2969-blob
Open

DX-2969: add Blob commands#17
ytkimirti wants to merge 1 commit into
mainfrom
DX-2969-blob

Conversation

@ytkimirti

@ytkimirti ytkimirti commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Overview

Adds first-class Upstash Blob bucket management to @upstash/cli and gives agents a simple bridge into Blob's S3-compatible data plane.

The CLI manages buckets through the Upstash Developer API. For object operations, it returns short-lived, bucket-scoped S3 credentials rather than reimplementing upload, download, list, copy, multipart, and signing behavior. Agents can use those credentials with AWS CLI, rclone, or any S3 SDK they already understand.

Command reference

Command Purpose
upstash blob create Create a private or public Blob bucket
upstash blob list List buckets without exposing their tokens
upstash blob get Fetch one bucket, optionally hiding its tokens
upstash blob credentials Exchange a bucket token for temporary S3 credentials
upstash blob delete Delete an empty bucket, with dry-run support

Account-level commands use the CLI's existing authentication flow: upstash login, UPSTASH_EMAIL plus UPSTASH_API_KEY, or the global credential flags.

Create a bucket

Create a private bucket:

upstash blob create --name uploads --visibility private

Create a public bucket:

upstash blob create --name public-assets --visibility public

Allow specific browser origins at creation time:

upstash blob create \
  --name uploads \
  --visibility private \
  --cors https://example.com https://admin.example.com

The command prints the created bucket as JSON. Create responses include the initial token and token_next values, so an agent can immediately connect to the bucket.

Blob currently has no CLI region or plan option, because those are not part of the Blob bucket API.

List buckets

upstash blob list

This prints the account's buckets as a JSON array. The list endpoint intentionally does not return token or token_next, making it safe for inventory and discovery workflows.

A simple way to select a bucket ID by name:

BUCKET_ID=$(upstash blob list | jq -r '.[] | select(.name == "uploads") | .id')

Get a bucket

Get complete bucket details, including the current and next bucket tokens:

upstash blob get --bucket-id "$BUCKET_ID"

Hide both token fields when output will be logged, copied, or shared:

upstash blob get --bucket-id "$BUCKET_ID" --hide-credentials

--hide-credentials removes the fields locally before printing. It does not depend on an unsupported API query parameter.

Get temporary S3 credentials

When account credentials are available, the CLI fetches the current bucket token through the Developer API and exchanges it for temporary S3 credentials:

upstash blob credentials --bucket-id "$BUCKET_ID"

The JSON response contains:

{
  "accessKeyId": "...",
  "secretAccessKey": "...",
  "sessionToken": "...",
  "endpoint": "https://<account>.r2.cloudflarestorage.com",
  "bucket": "<bucket-id>",
  "region": "auto",
  "expiresAt": 1234567890
}

expiresAt is the authoritative Unix expiry. The credentials are short-lived and scoped to one bucket.

If an agent only has a bucket token, it can use the standard Blob environment variable without Developer API account credentials:

UPSTASH_BLOB_TOKEN="$TOKEN" upstash blob credentials

There is deliberately no --token option because secrets passed as command-line arguments can leak into shell history and process listings.

Use the credentials with AWS CLI

CREDS=$(upstash blob credentials --bucket-id "$BUCKET_ID")

export AWS_ACCESS_KEY_ID=$(echo "$CREDS" | jq -r .accessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo "$CREDS" | jq -r .secretAccessKey)
export AWS_SESSION_TOKEN=$(echo "$CREDS" | jq -r .sessionToken)
ENDPOINT=$(echo "$CREDS" | jq -r .endpoint)
BUCKET=$(echo "$CREDS" | jq -r .bucket)

# List objects
aws s3 ls "s3://$BUCKET" \
  --endpoint-url "$ENDPOINT" \
  --region auto

# Upload an object
aws s3 cp ./avatar.png "s3://$BUCKET/avatars/avatar.png" \
  --endpoint-url "$ENDPOINT" \
  --region auto

# Download an object
aws s3 cp "s3://$BUCKET/avatars/avatar.png" ./avatar.png \
  --endpoint-url "$ENDPOINT" \
  --region auto

The same response works with rclone or an S3 client library.

Delete an empty bucket

Preview the action without resolving authentication or sending a request:

upstash blob delete --bucket-id "$BUCKET_ID" --dry-run

Example preview:

{
  "action": "delete",
  "bucket_id": "...",
  "dry_run": true
}

Delete the bucket after all objects have been removed:

upstash blob delete --bucket-id "$BUCKET_ID"

The CLI intentionally does not recursively delete objects. The Developer API refuses deletion of a non-empty bucket, preventing a bucket-management command from silently becoming a destructive object purge.

Credential safety and failure handling

  • temporary S3 credentials and bucket tokens are never cached or written to the CLI config
  • credential requests use Bearer authentication directly against https://blob.upstash.io/v1/credentials
  • successful responses are validated before printing
  • the returned endpoint must use HTTPS and end in .r2.cloudflarestorage.com
  • 429 and 503 responses use bounded retries with Retry-After support
  • rejected tokens produce a clear authentication error
  • all normal CLI errors continue to use the existing JSON stderr format

Scope

This PR intentionally does not add Blob object commands or a new runtime dependency. There is no local SigV4 implementation and no AWS SDK dependency. Once credentials are returned, the existing S3 ecosystem handles object operations better and is already familiar to coding agents.

Advanced control-plane operations such as rename, token rotation, visibility updates, CORS updates, and usage stats can be added separately without complicating this initial workflow.

Testing

Unit coverage includes:

  • command registration and Developer API method/path/body wiring
  • private visibility defaults and CORS forwarding
  • list and get output behavior
  • credential hiding
  • delete dry-run behavior
  • bucket ID and UPSTASH_BLOB_TOKEN authentication flows
  • explicit bucket ID precedence over an ambient Blob token
  • credential payload and R2 endpoint validation
  • rejected-token behavior
  • bounded 429 and 503 retries

Verification performed:

npm run build
npm run typecheck
npm test
node dist/cli.js blob --help
node dist/cli.js blob credentials --help
RUN_BLOB_INTEGRATION=1 npm test -- tests/integration/blob.test.ts

The opt-in live lifecycle test passed against the real APIs. It creates a private bucket, verifies list/get, waits for provisioning, obtains working temporary S3 credentials, and deletes the empty bucket with bounded cleanup retries.

Linear: https://linear.app/upstash/issue/DX-2969/cli-blob

@linear-code

linear-code Bot commented Aug 31, 2026

Copy link
Copy Markdown

DX-2969

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.

1 participant