> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirage.strukto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Dropbox

> Mount Dropbox as a read/write MIRAGE resource from Node or the browser via OAuth2.

Mirage ships `DropboxResource` in **two runtimes**:

* `@struktoai/mirage-node`, uses `client_id` + `client_secret` + long-lived refresh token to fetch
  short-lived access tokens server-side.
* `@struktoai/mirage-browser`, supports the same refresh token via PKCE (no client secret in the bundle).

Both runtimes hit the same [Dropbox v2 HTTP API](https://www.dropbox.com/developers/documentation/http/documentation):
`/2/files/list_folder` for directories, `/2/files/download` for content, and
`/2/files/upload` / `create_folder_v2` / `delete_v2` / `move_v2` / `copy_v2` for writes
(single-call uploads cap at \~150 MB). Credentials are obtained the same way in both runtimes, see
[Dropbox Credentials](/home/setup/dropbox). The Python package ships the same
backend, see [Dropbox (Python)](/python/resource/dropbox).

## Node (server-side)

```bash theme={null}
pnpm add @struktoai/mirage-node
```

```ts theme={null}
import { DropboxResource, MountMode, Workspace } from '@struktoai/mirage-node'

const dropbox = new DropboxResource({
  clientId: process.env.DROPBOX_APP_KEY!,
  clientSecret: process.env.DROPBOX_APP_SECRET!,
  refreshToken: process.env.DROPBOX_REFRESH_TOKEN!,
})

const ws = new Workspace({ '/dropbox': dropbox }, { mode: MountMode.READ })
const res = await ws.execute('ls /dropbox/')
console.log(res.stdoutText)
```

The `DropboxTokenManager` caches the access token in memory and refreshes it \~5 minutes before
expiry, so cold-start API calls don't pay the refresh round-trip.

## Mount a subfolder

Pass `rootPath` to expose a single Dropbox folder as the mount root instead of the whole
account. Every command and FUSE/VFS op is scoped to that folder; paths outside it are
unreachable.

```ts theme={null}
const dropbox = new DropboxResource({
  clientId: process.env.DROPBOX_APP_KEY!,
  clientSecret: process.env.DROPBOX_APP_SECRET!,
  refreshToken: process.env.DROPBOX_REFRESH_TOKEN!,
  rootPath: '/Team/data',
})

const ws = new Workspace({ '/dropbox': dropbox }, { mode: MountMode.READ })
await ws.execute('ls /dropbox/') // lists the contents of /Team/data
```

`rootPath` accepts `Team/data`, `/Team/data`, or `/Team/data/` (all normalized the same way);
`..` segments are rejected. Config dictionaries (e.g. via the resource registry) may spell it
`root_path`. Omitting it (or passing `/`) mounts the account root as before.

## Search push-down

By default a recursive `grep`/`rg` walks the tree and downloads every file. With
`contentSearch: true` (config dictionaries may spell it `content_search`), both commands
first ask [`/2/files/search_v2`](https://www.dropbox.com/developers/documentation/http/documentation#files-search)
which files contain the pattern's literal, then download and scan only those candidates —
the output stays exactly GNU/ripgrep because the local scan still decides every match.
Regex patterns narrow on an extracted required literal; flags whose output must see every
file in scope (`grep -v`, `grep -c`, `rg -v`, `rg --type/--glob`) always take the full
walk, as do file operands and multi-pattern (`-e`/`-f`) runs. An empty or failed search
also falls back to the full walk.

```ts theme={null}
const dropbox = new DropboxResource({
  clientId: process.env.DROPBOX_APP_KEY!,
  clientSecret: process.env.DROPBOX_APP_SECRET!,
  refreshToken: process.env.DROPBOX_REFRESH_TOKEN!,
  contentSearch: true,
})
```

The knob is off by default for two reasons: full-text content search is plan-gated
(Dropbox Professional/Essentials/Business and up — on other plans `search_v2` silently
matches file names only, so a narrowed scan would miss content matches), and Dropbox's
search index lags recent writes by a short delay, so a push-down may miss files written
moments earlier. Only enable it when the account's plan includes full-text search and
slightly stale results are acceptable. See
[Dropbox (Python) — Search push-down](/python/resource/dropbox#search-push-down) for the
mirrored Python knob.

## Browser (PKCE, no client secret)

```bash theme={null}
pnpm add @struktoai/mirage-browser
```

```ts theme={null}
import { DropboxResource, MountMode, Workspace } from '@struktoai/mirage-browser'

// Refresh token obtained via the PKCE flow, see examples/typescript/browser/src/dropbox_pkce.ts.
const dropbox = new DropboxResource({
  clientId: import.meta.env.VITE_DROPBOX_APP_KEY,
  refreshToken: refreshTokenFromLocalStorage,
})

const ws = new Workspace({ '/dropbox': dropbox }, { mode: MountMode.READ })
await ws.execute('ls /dropbox/')
```

Set the redirect URI on your Dropbox app's **Settings** tab to your dev/prod origin
(e.g. `http://localhost:5173/dropbox_pkce.html`). The bundled
[`examples/typescript/browser/src/dropbox_pkce.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/browser/src/dropbox_pkce.ts)
runs the full PKCE dance end-to-end and persists the refresh token to `localStorage`.

## VFS mode (`patchNodeFs`)

Mirage exposes a `patchNodeFs(workspace)` shim that routes `fs.promises.*` calls under a mount
through the workspace, so any library that uses Node's `fs` API can read directly from Dropbox
without code changes.

```ts theme={null}
import { createRequire } from 'node:module'
import { DropboxResource, MountMode, patchNodeFs, Workspace } from '@struktoai/mirage-node'

const require = createRequire(import.meta.url)
const fs = require('fs') as typeof import('fs')

const ws = new Workspace({ '/dropbox': dropbox }, { mode: MountMode.READ })
const restore = patchNodeFs(ws)

const entries = await fs.promises.readdir('/dropbox/')
const stat = await fs.promises.stat('/dropbox/data')
const bytes = await fs.promises.readFile('/dropbox/data/example.parquet')

restore()
```

Only `fs.promises.*` (the async API) is patched. Sync forms like `fs.statSync` and
`fs.readFileSync` aren't supported because remote reads can't block the event loop.

## FUSE mode

For tools that need a real filesystem path (CLI tools, editors, system utilities), mount the
workspace under FUSE:

```ts theme={null}
import { DropboxResource, Mount, MountMode, Workspace } from '@struktoai/mirage-node'

const ws = new Workspace({ '/dropbox': new Mount(dropbox, { mode: MountMode.READ, backend: MountBackend.FUSE }) })
await ws.fuseReady()
const mp = ws.fuseMountpoint

// The dropbox subtree is exposed at the mountpoint root, so `${mp}/` is a real path:
//   ls ${mp}/
//   find ${mp} -name '*.parquet'
//   cat ${mp}/data/example.json | jq .

await ws.close()
```

Requires [macFUSE](https://osxfuse.github.io/) on macOS or libfuse on Linux. See
[FUSE setup](/home/setup/macos).

## Available commands

`DropboxResource` ships the same shell command set as the GDrive resource:

* **Filesystem**: `ls`, `cat`, `head`, `tail`, `nl`, `wc`, `stat`, `find`, `tree`, `du`, `file`,
  `realpath`, `basename`, `dirname`
* **Write** (on a `WRITE` mount): `tee`, `touch`, `mkdir`, `rm`, `rmdir`, `mv`, `cp`, `truncate`,
  shell redirection (`>` / `>>`)
* **Search/text**: `grep`, `rg`, `awk`, `sed`, `sort`, `uniq`, `cut`, `diff`, `cmp`, `jq`
* **Encoding**: `base64`
* **Format-aware**: `cat_parquet`, `cat_feather`, `cat_hdf5`, `head_parquet`, `head_feather`,
  `head_hdf5`, `cut_*`, `grep_*`, `ls_*`, `stat_*`, `tail_*`, `wc_*`, `file_*` for `.parquet`,
  `.feather`, `.hdf5`/`.h5`, `.orc` files

The format-aware commands transparently decode binary tabular files, so
`cat /dropbox/data/example.parquet` returns a column preview instead of binary garbage.

## Examples

End-to-end runnable scripts are in
[`examples/typescript/dropbox/`](https://github.com/strukto-ai/mirage/tree/main/examples/typescript/dropbox):

* [`dropbox.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/dropbox/dropbox.ts), `Workspace.execute('ls/stat/cat/tree …')` shell demo
* [`dropbox_parquet.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/dropbox/dropbox_parquet.ts), parquet preview through `cat`
* [`dropbox_vfs.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/dropbox/dropbox_vfs.ts), `patchNodeFs` + native `fs.promises.*` calls
* [`dropbox_fuse.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/dropbox/dropbox_fuse.ts), FUSE mount with shell access in another terminal
