bcbd1bdb22
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
263 lines
9.6 KiB
Plaintext
263 lines
9.6 KiB
Plaintext
---
|
|
title: Box
|
|
icon: box
|
|
description: Mount Box as a MIRAGE resource from Node or the browser via OAuth2.
|
|
---
|
|
|
|
Mirage ships `BoxResource` in **two runtimes**:
|
|
|
|
- `@struktoai/mirage-node`, uses `client_id` + `client_secret` + refresh token (rotated on each use)
|
|
- `@struktoai/mirage-browser`, supports the same refresh token via PKCE (no client secret in the bundle)
|
|
|
|
Both runtimes hit the same [Box v2 API](https://developer.box.com/reference/) endpoints
|
|
(`/folders/{id}/items`, `/files/{id}/content`, `/search`). Credentials are obtained the same way
|
|
in both runtimes, see [Box Credentials](/home/setup/box).
|
|
|
|
## Quick start: developer token
|
|
|
|
For exploration, skip the OAuth flow and use a developer token (one-button-click in the Box
|
|
app console, 60-minute lifetime). The `BoxResource` accepts an `accessToken` field that
|
|
short-circuits the refresh logic:
|
|
|
|
```ts
|
|
import { BoxResource, MountMode, Workspace } from '@struktoai/mirage-node'
|
|
|
|
const box = new BoxResource({
|
|
accessToken: process.env.BOX_DEVELOPER_TOKEN!,
|
|
})
|
|
const ws = new Workspace({ '/box': box }, { mode: MountMode.READ })
|
|
await ws.execute('ls /box/')
|
|
```
|
|
|
|
When the token expires (Box 401s with `invalid_token`), regenerate it in the console and
|
|
re-run. See [Box Credentials -> Quick Start](/home/setup/box#quick-start-developer-token-60-minutes-no-oauth).
|
|
|
|
## Service account (client credentials, no refresh token)
|
|
|
|
For headless server auth, skip OAuth and refresh tokens entirely. Create a Box app with the
|
|
**Server Authentication (Client Credentials Grant)** method, authorize it once in the Box
|
|
admin console (**Apps -> Custom Apps Manager**), and pass the enterprise ID:
|
|
|
|
```ts
|
|
const box = new BoxResource({
|
|
clientId: process.env.BOX_CLIENT_ID!,
|
|
clientSecret: process.env.BOX_CLIENT_SECRET!,
|
|
enterpriseId: process.env.BOX_ENTERPRISE_ID!,
|
|
})
|
|
```
|
|
|
|
Tokens are minted for the app's **service account** and re-fetched automatically on expiry;
|
|
there is no refresh token to rotate or persist.
|
|
|
|
<Warning>
|
|
The service account is a separate Box user with its own (initially empty) root folder. To see
|
|
your content, share folders with the service account's email address, shown under the app's
|
|
**General Settings** in the developer console.
|
|
</Warning>
|
|
|
|
## Node (server-side, long-running)
|
|
|
|
```bash
|
|
pnpm add @struktoai/mirage-node
|
|
```
|
|
|
|
```ts
|
|
import { BoxResource, MountMode, Workspace } from '@struktoai/mirage-node'
|
|
|
|
const box = new BoxResource({
|
|
clientId: process.env.BOX_CLIENT_ID!,
|
|
clientSecret: process.env.BOX_CLIENT_SECRET!,
|
|
refreshToken: process.env.BOX_REFRESH_TOKEN!,
|
|
// Box rotates the refresh token; persist the new one if you want to survive restarts.
|
|
onRefreshTokenRotated: async (next) => {
|
|
await fs.writeFile('.box-refresh', next, 'utf-8')
|
|
},
|
|
})
|
|
|
|
const ws = new Workspace({ '/box': box }, { mode: MountMode.READ })
|
|
const res = await ws.execute('ls /box/')
|
|
console.log(res.stdoutText)
|
|
```
|
|
|
|
The `BoxTokenManager` caches the access token in memory (5-minute safety buffer before expiry)
|
|
and rotates the refresh token on every refresh. Without `onRefreshTokenRotated`, the rotation
|
|
is in-memory only and a process restart needs a fresh refresh token.
|
|
|
|
## Browser (PKCE, no client secret)
|
|
|
|
```bash
|
|
pnpm add @struktoai/mirage-browser
|
|
```
|
|
|
|
```ts
|
|
import { BoxResource, MountMode, Workspace } from '@struktoai/mirage-browser'
|
|
|
|
// Refresh token obtained via the PKCE flow, see examples/typescript/browser/src/box_pkce.ts.
|
|
const box = new BoxResource({
|
|
clientId: import.meta.env.VITE_BOX_CLIENT_ID,
|
|
refreshToken: localStorage.getItem('box-refresh')!,
|
|
onRefreshTokenRotated: (next) => localStorage.setItem('box-refresh', next),
|
|
})
|
|
|
|
const ws = new Workspace({ '/box': box }, { mode: MountMode.READ })
|
|
await ws.execute('ls /box/')
|
|
```
|
|
|
|
Box requires the origin to be allowlisted on the app's **Allowed Origins** config (see
|
|
[setup](/home/setup/box#cors-notes)). The bundled
|
|
[`examples/typescript/browser/src/box_pkce.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/browser/src/box_pkce.ts)
|
|
runs the full PKCE dance and persists the rotated refresh token to `localStorage`.
|
|
|
|
## VFS mode (`patchNodeFs`)
|
|
|
|
```ts
|
|
import { createRequire } from 'node:module'
|
|
import { BoxResource, 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({ '/box': box }, { mode: MountMode.READ })
|
|
const restore = patchNodeFs(ws)
|
|
|
|
const entries = await fs.promises.readdir('/box/')
|
|
const stat = await fs.promises.stat('/box/Documents')
|
|
const bytes = await fs.promises.readFile('/box/Documents/example.json')
|
|
|
|
restore()
|
|
```
|
|
|
|
Only `fs.promises.*` is patched, sync forms aren't supported.
|
|
|
|
## FUSE mode
|
|
|
|
```ts
|
|
import { BoxResource, Mount, MountMode, Workspace } from '@struktoai/mirage-node'
|
|
|
|
const ws = new Workspace({ '/box': new Mount(box, { mode: MountMode.READ, fuse: true }) })
|
|
await ws.fuseReady()
|
|
const mp = ws.fuseMountpoint
|
|
// The box subtree is exposed at the mountpoint root, so ${mp}/ is a real path:
|
|
// ls ${mp}/
|
|
// find ${mp} -type f
|
|
// cat ${mp}/example.json | jq .
|
|
await ws.close()
|
|
```
|
|
|
|
Requires [macFUSE](https://osxfuse.github.io/) on macOS or libfuse on Linux.
|
|
|
|
## Path → ID resolution
|
|
|
|
Box uses numeric folder/file IDs internally (root folder is id `0`). Mirage caches the path → ID
|
|
mapping in the `RAMIndexCacheStore` attached to the resource. The first time you `ls /box/foo/bar/`,
|
|
it walks the tree top-down (one API call per level) and caches each entry's ID. Subsequent reads
|
|
of `/box/foo/bar/anyfile.json` hit the cache instead of re-walking.
|
|
|
|
The cache TTL defaults to 24h. To force re-resolution, recreate the `BoxResource`.
|
|
|
|
## Special file types
|
|
|
|
Five Box-specific file types get a `.json` suffix appended in the vfs and return clean,
|
|
agent-friendly JSON when you `cat` them. The Box file ID is unchanged, the suffix is
|
|
purely a vfs hint that `cat` returns JSON, mirroring Google Drive's `.gdoc.json` style.
|
|
|
|
In `ls /box/`:
|
|
|
|
```
|
|
hihi.gdoc.json # Box's Google Doc (backing bytes are .docx)
|
|
gsheet.gsheet.json # Box's Google Sheet (.xlsx)
|
|
hihihi.gslides.json # Box's Google Slides (.pptx)
|
|
test.boxnote.json # Box Note
|
|
test canvas.boxcanvas.json
|
|
```
|
|
|
|
### `.boxnote.json`, Box Notes
|
|
|
|
```json
|
|
{
|
|
"id": "...",
|
|
"body_text": "paragraphs joined by \n",
|
|
"paragraphs": [{ "text": "...", "authors": ["..."] }],
|
|
"authors": { "<id>": "Author Name" },
|
|
"last_edit_at": "..."
|
|
}
|
|
```
|
|
|
|
```bash
|
|
cat /box/foo.boxnote.json | jq -r .body_text # plain text
|
|
cat /box/foo.boxnote.json | jq .authors # who wrote it
|
|
```
|
|
|
|
### `.boxcanvas.json`, Box Canvas (whiteboards)
|
|
|
|
```json
|
|
{
|
|
"id": "...",
|
|
"widget_count": 11,
|
|
"widgets_by_type": { "shape": 6, "link": 5 },
|
|
"body_text": "shape labels joined by \n",
|
|
"widgets": [{ "id": "...", "type": "shape", "text": "..." }],
|
|
"authors": ["..."]
|
|
}
|
|
```
|
|
|
|
```bash
|
|
cat "/box/foo.boxcanvas.json" | jq -r .body_text
|
|
cat "/box/foo.boxcanvas.json" | jq .widgets_by_type
|
|
```
|
|
|
|
### `.gdoc.json` / `.gsheet.json` / `.gslides.json`, Box's V2 Google files
|
|
|
|
Box's V2 G Suite integration stores Google-format files as Office Open XML zips (`.docx` /
|
|
`.xlsx` / `.pptx`). On `cat`, Mirage uses Box's
|
|
[`representations` API](https://developer.box.com/reference/get-files-id/#param-representations)
|
|
with `X-Rep-Hints: [extracted_text]` to fetch Box's auto-extracted plain-text version, then
|
|
returns a JSON envelope:
|
|
|
|
```json
|
|
{
|
|
"id": "...",
|
|
"name": "hihi.gdoc.json",
|
|
"format": "docx",
|
|
"size": 8921,
|
|
"modified_at": "...",
|
|
"body_text": "auto-extracted plain text"
|
|
}
|
|
```
|
|
|
|
```bash
|
|
cat /box/hihi.gdoc.json | jq -r .body_text
|
|
cat /box/gsheet.gsheet.json | jq .format # "xlsx"
|
|
```
|
|
|
|
This is **different from Google Drive's `.gdoc.json`**, Box uses its own API + extracted-text
|
|
representation; Google Drive uses Google's full Documents API and returns the rich Document
|
|
structure (paragraphs, named styles, lists, etc.). For real edits to the underlying Google
|
|
Doc, mount Google Drive separately with Google credentials and use the
|
|
[GDocs / GSheets / GSlides resources](/home/setup/google), `gws-docs-*` commands won't work
|
|
on Box because the file IDs are in different namespaces.
|
|
|
|
## Available commands
|
|
|
|
`BoxResource` ships the same shell command set as `GDriveResource` and `DropboxResource`:
|
|
|
|
- **Filesystem**: `ls`, `cat`, `head`, `tail`, `nl`, `wc`, `stat`, `find`, `tree`, `du`, `file`,
|
|
`realpath`, `basename`, `dirname`
|
|
- **Search/text**: `grep`, `rg`, `awk`, `sed`, `sort`, `uniq`, `cut`, `diff`, `cmp`, `jq`
|
|
- **Encoding**: `base64`
|
|
- **Format-aware**: `cat_parquet`, `cat_feather`, `cat_hdf5`, plus `head_*`/`grep_*`/`ls_*`/
|
|
`stat_*`/`tail_*`/`wc_*`/`file_*`/`cut_*` variants for `.parquet`, `.feather`, `.hdf5`/`.h5`,
|
|
`.orc` files
|
|
|
|
## Examples
|
|
|
|
End-to-end runnable scripts are in
|
|
[`examples/typescript/box/`](https://github.com/strukto-ai/mirage/tree/main/examples/typescript/box):
|
|
|
|
- [`box.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/box/box.ts), `Workspace.execute('ls/stat/cat/tree …')` shell demo
|
|
- [`box_parquet.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/box/box_parquet.ts), parquet preview through `cat`
|
|
- [`box_boxnote.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/box/box_boxnote.ts), `.boxnote.json` decoded into clean JSON
|
|
- [`box_office.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/box/box_office.ts), `.gdoc.json` / `.gsheet.json` / `.gslides.json` via Box's extracted_text representation
|
|
- [`box_vfs.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/box/box_vfs.ts), `patchNodeFs` + native `fs.promises.*` calls
|
|
- [`box_fuse.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/box/box_fuse.ts), FUSE mount with shell access in another terminal
|