---
changelog:
  category: Release
  version: files-sdk@1.4.0
date: '2026-05-18T05:00:05Z'
seo:
  description: >-
    Add Alibaba Cloud Object Storage Service (OSS) adapter (files-sdk/alibaba).
    Thin wrapper around the S3 adapter — endpoint derived from the region code…
title: files-sdk@1.4.0
type: changelog
---
### Minor Changes

-   ef0d6af: Add Alibaba Cloud Object Storage Service (OSS) adapter (`files-sdk/alibaba`). Thin wrapper around the S3 adapter — endpoint derived from the region code (`oss-<region>.aliyuncs.com`), virtual-hosted-style addressing, errors relabelled as "Alibaba Cloud error". Auto-loads from `ALIBABA_ACCESS_KEY_ID` and `ALIBABA_ACCESS_KEY_SECRET`.
-   d619709: Add `files` CLI for agents and scripts. One binary covers every adapter via `--provider <name>` with lazy imports — cold-start cost matches whichever single provider you select. Each `Adapter` method maps to a subcommand (`upload`, `download`, `head`, `exists`, `delete`, `copy`, `list`, `url`, `sign-upload`), with JSON-by-default output, `stdin`/`stdout` streaming for binary bodies, `--dry-run` and `--verbose` modes, and a stable exit-code mapping (`NotFound` → 1, `Provider` → 2, `Unauthorized` → 3, `Conflict` → 4). Provider credentials come from each adapter's existing env-var conventions, and `--config-json` is an escape hatch for the long tail of adapter options. `files ... mcp` boots a stdio MCP server exposing every command as a tool — provider and credentials bind at startup, so the agent only passes operation arguments.
-   d0aec82: Add Cloudinary adapter (`files-sdk/cloudinary`). Defaults to `resource_type: "raw"` for arbitrary-bytes storage; switch to `image`/`video` for transforms. Reads `CLOUDINARY_URL` or individual `CLOUDINARY_*` env vars. Full Adapter surface including signed delivery URLs for `private`/`authenticated` types and form-POST signed upload URLs.
-   8b62142: Add Firebase Storage adapter (`files-sdk/firebase-storage`). Wraps the official `firebase-admin` SDK; the underlying `getStorage().bucket()` returns a `@google-cloud/storage` `Bucket`, so V4 signed read URLs, POST policy uploads with `maxSize`, server-side copy, and the full metadata round-trip all work out of the box. Auto-loads credentials from `FIREBASE_PROJECT_ID` / `FIREBASE_CLIENT_EMAIL` / `FIREBASE_PRIVATE_KEY` / `FIREBASE_STORAGE_BUCKET`, falling back to a service-account JSON path (`GOOGLE_APPLICATION_CREDENTIALS`) and then to Application Default Credentials. Accepts an existing `App` or `Bucket` via `app` to share initialization with Firestore/Auth. The bucket name defaults to `<projectId>.firebasestorage.app` when neither `bucket` nor `FIREBASE_STORAGE_BUCKET` is set. Firebase's `?alt=media&token=…` download-token URL form is out of scope for v1 — reach for `adapter.raw` if you need it.
-   8b62142: Add PocketBase adapter (`files-sdk/pocketbase`). Wraps the official `pocketbase` JS SDK and maps the unified key/blob API onto a dedicated collection: each upload becomes (or updates) a record whose configurable `keyField` (unique-indexed text, default `"key"`) holds the user-facing key and whose configurable `fileField` (single-value file, default `"file"`) holds the body. Auto-loads from `POCKETBASE_URL` plus either `POCKETBASE_ADMIN_EMAIL` + `POCKETBASE_ADMIN_PASSWORD` (admin login on first call) or `POCKETBASE_AUTH_TOKEN` (pre-issued token); accepts an existing `PocketBase` client via `client`. `url()` returns `pb.files.getURL()`, threading a short-lived file token from `pb.files.getToken()` for authenticated clients; set `publicBaseUrl` for a CDN override. `signedUploadUrl()` throws — PocketBase has no presigned upload primitive. `copy()` is read-then-write (no server-side copy). `list()` paginates via page number encoded as a numeric cursor string. `UploadOptions` `cacheControl` and `metadata` throw — PocketBase has no per-file HTTP cache headers and no arbitrary-metadata field on the file; add extra typed columns to the collection and write via `raw` if you need them. `responseContentDisposition` on `url()` throws — use `raw` and the `?download=true` query string instead.
-   d0aec82: Add SharePoint adapter (`files-sdk/sharepoint`). Resolves `siteUrl` and named `documentLibrary` to a drive via Microsoft Graph, then delegates to the OneDrive adapter for file operations. Falls back to `SHAREPOINT_*` env vars then to `ONEDRIVE_*`. Resolution is lazy and cached after the first call.
-   ef0d6af: Add Tencent Cloud Object Storage (COS) adapter (`files-sdk/tencent`). Thin wrapper around the S3 adapter — endpoint derived from the region code (`cos.<region>.myqcloud.com`), virtual-hosted-style addressing, errors relabelled as "Tencent Cloud error". Auto-loads from `TENCENT_SECRET_ID` and `TENCENT_SECRET_KEY`. Bucket name must include the `-<appid>` suffix per COS's namespacing.
-   ef0d6af: Add Yandex Object Storage adapter (`files-sdk/yandex`). Thin wrapper around the S3 adapter — fixed global endpoint (`storage.yandexcloud.net`), region defaults to `ru-central1` for signing, virtual-hosted-style addressing, errors relabelled as "Yandex Cloud error". Auto-loads from `YANDEX_ACCESS_KEY_ID` and `YANDEX_SECRET_ACCESS_KEY`.
-   de63748: Add Bun S3 adapter at `files-sdk/bun-s3`, backed by Bun's native `Bun.S3Client` instead of `@aws-sdk/client-s3`. Use this when you're already on Bun and want to skip the AWS SDK dependency. Implements the full adapter surface (upload, download, head, exists, delete, copy, list, url, signedUploadUrl) with three deliberate limitations vs `files-sdk/s3`: `copy()` is client-side (Bun has no server-side `CopyObject` primitive), and `upload(metadata|cacheControl)` plus `signedUploadUrl(maxSize)` throw because `Bun.S3Client` doesn't expose equivalent options. Pass `client: Bun.s3` to reuse the global singleton, or hand in any custom `Bun.S3Client`-shaped instance.
-   28e3243: Add Bunny Storage adapter (`files-sdk/bunny-storage`). Wraps the official `@bunny.net/storage-sdk` and connects to a Storage Zone via zone name + access key + region. Auto-loads from `BUNNY_STORAGE_ZONE` / `BUNNY_STORAGE_ACCESS_KEY` / `BUNNY_STORAGE_REGION`, with `STORAGE_*` accepted as aliases (the names used in the Bunny SDK's README). `url()` requires `publicBaseUrl` (typically a Bunny Pull Zone) and returns a permanent CDN URL — Bunny has no signed-read primitive, so `expiresIn` is ignored and `responseContentDisposition` throws. `signedUploadUrl()` throws because Bunny writes require the Storage API `AccessKey` header. `copy()` is a read-then-write (no server-side copy primitive in the SDK). Custom `metadata` and `cacheControl` on upload throw — configure cache behavior on the Pull Zone instead.
-   78bcf37: Move provider SDKs to optional peer dependencies. Installing `files-sdk` no longer pulls in every provider SDK by default — the package fully installs at a fraction of the previous size, and unused providers can't drag in transitive CVEs. Install only what you use:

    ```sh
    # S3 (and any S3-compatible: R2, MinIO, DigitalOcean Spaces, …)
    npm install files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner

    # GCS
    npm install files-sdk @google-cloud/storage google-auth-library

    # Azure
    npm install files-sdk @azure/storage-blob @azure/identity
    ```

    **Breaking (install-time only):** if you upgrade and your project doesn't list the relevant provider SDK in its own `package.json`, the next adapter import will throw `ERR_MODULE_NOT_FOUND`. Fix is one `npm install`. The published JS for each adapter subpath (`files-sdk/s3`, `files-sdk/gcs`, …) is byte-identical to the previous release — provider SDKs were already externalized, so runtime behavior, tree-shaking, and bundle sizes don't change. The `files` CLI keeps `commander` as a regular dep, so `npx files` works out of the box. Fixes #34.

### Patch Changes

-   a53be2d: Expand adapter test coverage for error-recovery branches that were previously unexercised: `exists()` swallowing a thrown `NotFound` (azure, gcs, netlify-blobs, r2) versus rethrowing other mapped errors; the supabase stream-download error envelope; and dropbox's `exists()` returning false for `folder`/`deleted` `.tag`s plus the `shared_link_already_exists` recovery falling through when no usable URL is embedded. No runtime behavior changes.
