chore: import upstream snapshot with attribution
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

This commit is contained in:
wehub-resource-sync
2026-07-13 12:30:44 +08:00
commit bcbd1bdb22
5748 changed files with 562488 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
---
title: Alibaba OSS
icon: /images/aliyun-logo.svg
description: Set up Alibaba Cloud OSS as a MIRAGE resource from Node or the browser.
---
Alibaba Cloud OSS exposes an S3-compatible API with AWS Signature V4 against `https://s3.oss-<region>.aliyuncs.com`. MIRAGE derives this endpoint from your region automatically.
Credentials (RAM `AccessKey ID` and `AccessKey Secret`) are created the same way in both runtimes, see [Alibaba OSS Credentials](/home/setup/aliyun).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { AliyunResource, MountMode, Workspace } from '@struktoai/mirage-node'
const oss = new AliyunResource({
bucket: process.env.OSS_BUCKET!,
region: process.env.OSS_REGION!,
accessKeyId: process.env.OSS_ACCESS_KEY_ID!,
secretAccessKey: process.env.OSS_ACCESS_KEY_SECRET!,
})
const ws = new Workspace({ '/bucket/': oss }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `AliyunResource` is secret-free, your backend signs each operation using your RAM keys and returns a URL. OSS's S3-compatible endpoint accepts AWS Signature V4, so `@aws-sdk/s3-request-presigner` works, pointed at the OSS endpoint.
### 1. Server: sign URLs with the OSS endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const REGION = process.env.OSS_REGION!
const client = new S3Client({
region: REGION,
endpoint: `https://s3.oss-${REGION}.aliyuncs.com`,
credentials: {
accessKeyId: process.env.OSS_ACCESS_KEY_ID!,
secretAccessKey: process.env.OSS_ACCESS_KEY_SECRET!,
},
})
const BUCKET = process.env.OSS_BUCKET!
app.post('/presign/oss', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
### 2. Browser: wire it up
```ts
import { AliyunResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const oss = new AliyunResource({
bucket: 'my-bucket',
region: 'cn-hangzhou',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/oss', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': oss }, { mode: MountMode.READ })
```
<Tip>
`region` on the browser config is only used for display/logging; the actual endpoint is baked into the presigned URLs your backend returns.
</Tip>
### 3. CORS
Configure CORS rules in the Alibaba Cloud console: OSS -> your bucket -> **Content Security** -> **CORS**. Allow your dev/production origins with methods `GET, PUT, HEAD, DELETE, POST`, allowed headers `*`, and expose headers `ETag, Content-Length, Content-Type, Last-Modified`.
See the [Alibaba OSS resource](/python/resource/aliyun) docs for the equivalent Python wiring.
+127
View File
@@ -0,0 +1,127 @@
---
title: Backblaze B2
icon: /images/backblaze-logo.svg
description: Set up Backblaze B2 as a MIRAGE resource from Node or the browser.
---
Backblaze B2 uses the S3 API with AWS Signature V4 against `https://s3.<region>.backblazeb2.com`. MIRAGE derives this endpoint from your region automatically.
Credentials (B2 application key, the `keyID` is the access key and the `applicationKey` is the secret) are created the same way in both runtimes, see [Backblaze B2 Credentials](/home/setup/backblaze).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { BackblazeResource, MountMode, Workspace } from '@struktoai/mirage-node'
const b2 = new BackblazeResource({
bucket: process.env.B2_BUCKET!,
region: process.env.B2_REGION!,
accessKeyId: process.env.B2_ACCESS_KEY_ID!,
secretAccessKey: process.env.B2_SECRET_ACCESS_KEY!,
})
const ws = new Workspace({ '/bucket/': b2 }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `BackblazeResource` is secret-free, your backend signs each operation using your B2 application key and returns a URL. B2 accepts AWS Signature V4, so `@aws-sdk/s3-request-presigner` works, pointed at the B2 endpoint.
### 1. Server: sign URLs with the B2 endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const REGION = process.env.B2_REGION!
const client = new S3Client({
region: REGION,
endpoint: `https://s3.${REGION}.backblazeb2.com`,
credentials: {
accessKeyId: process.env.B2_ACCESS_KEY_ID!,
secretAccessKey: process.env.B2_SECRET_ACCESS_KEY!,
},
})
const BUCKET = process.env.B2_BUCKET!
app.post('/presign/b2', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
### 2. Browser: wire it up
```ts
import { BackblazeResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const b2 = new BackblazeResource({
bucket: 'my-bucket',
region: 'us-west-004',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/b2', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': b2 }, { mode: MountMode.READ })
```
<Tip>
`region` on the browser config is only used for display/logging; the actual endpoint is baked into the presigned URLs your backend returns.
</Tip>
### 3. CORS
B2 manages CORS through its **native API**, not the S3 `PutBucketCors` call. Set CORS rules in the Backblaze web console (Bucket Settings -> CORS Rules) or with the `b2` CLI:
```bash
b2 bucket update my-bucket \
--cors-rules '[{"corsRuleName":"mirage","allowedOrigins":["http://localhost:5173","https://app.example.com"],"allowedOperations":["s3_get","s3_put","s3_head","s3_delete","s3_post"],"allowedHeaders":["*"],"exposeHeaders":["etag","content-length","content-type","last-modified"],"maxAgeSeconds":3000}]'
```
See the [Backblaze resource](/python/resource/backblaze) docs for the equivalent Python wiring.
+131
View File
@@ -0,0 +1,131 @@
---
title: Ceph
icon: /images/ceph-logo.svg
description: Set up Ceph Rados Gateway as a MIRAGE resource from Node or the browser.
---
Ceph Rados Gateway (RGW) speaks the S3 API with AWS Signature V4 against your own gateway endpoint (e.g. `https://ceph.example.com`). RGW is self-hosted, so the endpoint is required and path-style addressing is used by default.
Credentials (RGW user access key + secret key) are created the same way in both runtimes, see [Ceph Credentials](/home/setup/ceph).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { CephResource, MountMode, Workspace } from '@struktoai/mirage-node'
const ceph = new CephResource({
bucket: process.env.CEPH_BUCKET!,
endpoint: process.env.CEPH_ENDPOINT_URL!,
accessKeyId: process.env.CEPH_ACCESS_KEY_ID!,
secretAccessKey: process.env.CEPH_SECRET_ACCESS_KEY!,
})
const ws = new Workspace({ '/bucket/': ceph }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `CephResource` is secret-free, your backend signs each operation using your RGW keys and returns a URL. RGW accepts AWS Signature V4, so `@aws-sdk/s3-request-presigner` works, pointed at your gateway endpoint.
### 1. Server: sign URLs with the RGW endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const client = new S3Client({
region: 'us-east-1',
endpoint: process.env.CEPH_ENDPOINT_URL,
forcePathStyle: true,
credentials: {
accessKeyId: process.env.CEPH_ACCESS_KEY_ID!,
secretAccessKey: process.env.CEPH_SECRET_ACCESS_KEY!,
},
})
const BUCKET = process.env.CEPH_BUCKET!
app.post('/presign/ceph', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
<Warning>
RGW uses **path-style** URLs (`forcePathStyle: true`), virtual-hosted style requires wildcard DNS on the gateway.
</Warning>
### 2. Browser: wire it up
```ts
import { CephResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const ceph = new CephResource({
bucket: 'my-bucket',
endpoint: 'https://ceph.example.com',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/ceph', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': ceph }, { mode: MountMode.READ })
```
<Tip>
`endpoint` on the browser config is only used for display/logging; the actual endpoint is baked into the presigned URLs your backend returns.
</Tip>
### 3. CORS
RGW supports the S3 `PutBucketCors` call, so the AWS CLI works against your gateway:
```bash
aws s3api put-bucket-cors --bucket "$CEPH_BUCKET" \
--endpoint-url "$CEPH_ENDPOINT_URL" \
--cors-configuration '{"CORSRules":[{"AllowedOrigins":["http://localhost:5173","https://app.example.com"],"AllowedMethods":["GET","PUT","HEAD","DELETE","POST"],"AllowedHeaders":["*"],"ExposeHeaders":["ETag","Content-Length","Content-Type","Last-Modified"]}]}'
```
See the [Ceph resource](/python/resource/ceph) docs for the equivalent Python wiring.
+45
View File
@@ -0,0 +1,45 @@
---
title: Chroma
icon: database
description: Mount a ChromaDB collection as a Mirage filesystem in TypeScript, with shell commands and native vector retrieval.
---
`ChromaResource` exposes a ChromaDB collection as a read-only filesystem: the
directory tree comes from the collection's `__path_tree__` document, pages are
chunk sets joined in `chunk_index` order, and vector retrieval is the
`chroma-query` command. The TypeScript backend mirrors the
[Python one](/python/resource/chroma) and returns identical results.
## Node
The `chromadb` client is an optional peer dependency; install it alongside
Mirage:
```bash
pnpm add @struktoai/mirage-node chromadb
```
```ts
import { ChromaResource, MountMode, Workspace } from '@struktoai/mirage-node'
const knowledge = new ChromaResource({
config: {
host: 'localhost',
port: 8000,
collectionName: 'knowledge',
// slugField: 'page_slug', (default)
// chunkIndexField: 'chunk_index', (default)
},
})
const ws = new Workspace({ '/knowledge': knowledge }, { mode: MountMode.READ })
console.log(await (await ws.execute('tree /knowledge/')).stdoutStr())
console.log(await (await ws.execute('grep -r refund /knowledge/policies/')).stdoutStr())
console.log(
await (await ws.execute('chroma-query "how am I throttled" /knowledge/')).stdoutStr(),
)
```
`grep` pushes `$contains` / `$regex` filters down to ChromaDB before fine
filtering locally, so recursive searches avoid fetching the whole collection.
@@ -0,0 +1,98 @@
---
title: Databricks Volume
icon: folder-open
description: Set up a Databricks Unity Catalog volume as a MIRAGE resource from Node.
---
`DatabricksVolumeResource` exposes files from a Unity Catalog volume through
MIRAGE's standard filesystem interface. Agents can list, stat, read, stream,
glob, and write files under the configured volume root.
MIRAGE ships it in `@struktoai/mirage-node` only. The TypeScript client calls
the Databricks Files REST API directly with `fetch`; the Databricks API does
not allow cross-origin browser requests, so there is no browser runtime.
For auth and environment setup, see [Databricks Volume Setup](/home/setup/databricks).
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import {
DatabricksVolumeResource,
MountMode,
Workspace,
normalizeDatabricksVolumeConfig,
} from '@struktoai/mirage-node'
const resource = await DatabricksVolumeResource.create(
normalizeDatabricksVolumeConfig({
catalog: 'main',
schema: 'default',
volume: 'agent_files',
root_path: '/reports',
}),
)
const ws = new Workspace({ '/dbx/': resource }, { mode: MountMode.READ })
const res = await ws.execute('ls /dbx/')
console.log(res.stdoutText)
```
| Field | Default | Notes |
| --- | --- | --- |
| `catalog` | required | Unity Catalog catalog name. |
| `schema` | required | Unity Catalog schema name. |
| `volume` | required | Unity Catalog volume name. |
| `root_path` | `/` | Subdirectory inside the volume to expose. |
| `host` | none | Optional workspace host override. |
| `token` | none | Optional PAT override. Redacted in snapshots. |
| `profile` | none | Optional `~/.databrickscfg` profile name. |
| `timeout` | `30` | Request timeout in seconds. |
Credentials resolve in order: explicit `host`/`token` in the config, then the
`DATABRICKS_HOST`/`DATABRICKS_TOKEN` environment variables, then the
`profile` section of `~/.databrickscfg` (or `DATABRICKS_CONFIG_PROFILE`,
falling back to `DEFAULT`).
## Filesystem layout
MIRAGE maps the configured mount prefix onto the configured volume subtree.
With `root_path: '/reports/2026'` and mount prefix `/dbx/`, the volume path
```text
/Volumes/main/default/agent_files/reports/2026/q1/summary.md
```
appears in MIRAGE as
```text
/dbx/q1/summary.md
```
`root_path` is normalized before use, and MIRAGE rejects any virtual path that
would escape above that configured subtree.
## Supported operations
Reads: `readdir`, `stat`, `exists`, `read_bytes`, `read_stream`, `range_read`,
and glob resolution. Writes: `write`, `create`, `mkdir`, `rmdir`, `unlink`,
recursive `rm`, `cp`, and `mv`.
Standard shell commands like `ls`, `cat`, `head`, `tail`, `grep`, `rg`,
`find`, `tree`, `touch`, `mkdir`, `rm`, `cp`, and `mv` work through those
primitives. `mv`/`cp` are non-atomic download + upload — the Files API has no
server-side rename.
## Snapshot behavior
`token` is redacted in resource state. Loading a snapshot back requires an
override config that provides fresh credentials if the runtime auth chain does
not already supply them.
## Example
See `examples/typescript/databricks_volume/databricks_volume.ts`, which
mirrors `examples/python/databricks_volume/databricks_volume.py`.
+128
View File
@@ -0,0 +1,128 @@
---
title: DigitalOcean Spaces
icon: /images/digitalocean-logo.svg
description: Set up DigitalOcean Spaces as a MIRAGE resource from Node or the browser.
---
DigitalOcean Spaces uses the S3 API with AWS Signature V4 against `https://<region>.digitaloceanspaces.com`. MIRAGE derives this endpoint from your region automatically.
Credentials (Spaces access key pair) are created the same way in both runtimes, see [DigitalOcean Credentials](/home/setup/digitalocean).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { DigitalOceanResource, MountMode, Workspace } from '@struktoai/mirage-node'
const spaces = new DigitalOceanResource({
bucket: process.env.DO_SPACE!,
region: process.env.DO_REGION!,
accessKeyId: process.env.DO_ACCESS_KEY_ID!,
secretAccessKey: process.env.DO_SECRET_ACCESS_KEY!,
})
const ws = new Workspace({ '/bucket/': spaces }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `DigitalOceanResource` is secret-free, your backend signs each operation using your Spaces keys and returns a URL. Spaces accepts AWS Signature V4, so `@aws-sdk/s3-request-presigner` works, pointed at the Spaces endpoint.
### 1. Server: sign URLs with the Spaces endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const REGION = process.env.DO_REGION!
const client = new S3Client({
region: REGION,
endpoint: `https://${REGION}.digitaloceanspaces.com`,
credentials: {
accessKeyId: process.env.DO_ACCESS_KEY_ID!,
secretAccessKey: process.env.DO_SECRET_ACCESS_KEY!,
},
})
const BUCKET = process.env.DO_SPACE!
app.post('/presign/spaces', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
### 2. Browser: wire it up
```ts
import { DigitalOceanResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const spaces = new DigitalOceanResource({
bucket: 'my-space',
region: 'nyc3',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/spaces', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': spaces }, { mode: MountMode.READ })
```
<Tip>
`region` on the browser config is only used for display/logging; the actual endpoint is baked into the presigned URLs your backend returns.
</Tip>
### 3. CORS
Spaces supports the S3 `PutBucketCors` call, so the AWS CLI works against the Spaces endpoint (you can also use the DigitalOcean control panel, Space -> Settings -> CORS Configurations):
```bash
aws s3api put-bucket-cors --bucket "$DO_SPACE" \
--endpoint-url "https://$DO_REGION.digitaloceanspaces.com" \
--cors-configuration '{"CORSRules":[{"AllowedOrigins":["http://localhost:5173","https://app.example.com"],"AllowedMethods":["GET","PUT","HEAD","DELETE","POST"],"AllowedHeaders":["*"],"ExposeHeaders":["ETag","Content-Length","Content-Type","Last-Modified"]}]}'
```
See the [DigitalOcean resource](/python/resource/digitalocean) docs for the equivalent Python wiring.
+43
View File
@@ -0,0 +1,43 @@
---
title: Disk
icon: hard-drive
description: Mount a directory on the host filesystem as a Mirage resource (Node only).
---
`DiskResource` mounts a directory on the host filesystem. Reads and writes go through `node:fs` against the configured `root`.
<Note>
Node only. The browser SDK has no kernel filesystem; use [OPFS](/typescript/setup/opfs) instead.
</Note>
## Install
```bash
pnpm add @struktoai/mirage-node
```
## Config
```ts
import { DiskResource, MountMode, Workspace } from '@struktoai/mirage-node'
const disk = new DiskResource({ root: '/Users/me/data' })
const ws = new Workspace({ '/disk': disk }, { mode: MountMode.WRITE })
await ws.execute('echo hello | tee /disk/note.txt')
await ws.execute('cat /disk/note.txt')
```
| Field | Default | Notes |
| --- | --- | --- |
| `root` | required | Absolute path on the host. Reads and writes are scoped to this directory. |
## Mount mode
`read`, `write`, `exec`. Use `MountMode.EXEC` if you need commands to run binaries from the mount.
## Snapshot behavior
`DiskResource.toState()` captures the file tree as bytes. Loading a snapshot back doesn't re-create the directory; pass a config file with a fresh `root` if the original location changed.
For behavior shared with Python (filesystem layout, supported shell commands), see the [Python Disk docs](/python/resource/disk).
+50
View File
@@ -0,0 +1,50 @@
---
title: Email
icon: envelope
description: Mount an IMAP/SMTP mailbox as a Mirage filesystem (Node only).
---
`EmailResource` reads messages over IMAP and sends via SMTP, exposing the mailbox as a tree of folders and messages.
<Note>
Node only. Requires `imapflow`, `mailparser`, and `nodemailer` peers. The browser cannot speak raw TCP for IMAP/SMTP.
</Note>
## Install
```bash
pnpm add @struktoai/mirage-node imapflow mailparser nodemailer
```
## Config
```ts
import { EmailResource, MountMode, Workspace } from '@struktoai/mirage-node'
const email = new EmailResource({
imapHost: 'imap.example.com',
smtpHost: 'smtp.example.com',
username: process.env.EMAIL_USER!,
password: process.env.EMAIL_PASSWORD!,
})
const ws = new Workspace({ '/mail': email }, { mode: MountMode.WRITE })
await ws.execute('ls /mail/INBOX/')
```
| Field | Default | Notes |
| --- | --- | --- |
| `imapHost` | required | IMAP server hostname. |
| `imapPort` | `993` | |
| `smtpHost` | required | SMTP server hostname. |
| `smtpPort` | `465` | |
| `username` | required | Mailbox login. |
| `password` | required | App password. Redacted in snapshots. |
| `useSsl` | `true` | Implicit TLS for both IMAP and SMTP. |
| `maxMessages` | `1000` | Hard ceiling per folder listing. |
## Mount mode
`read`, `write` (sending mail via the synthesized `/sent/` path).
For mailbox layout, search behavior, and synthesized date directories see the [Python Email docs](/python/resource/email).
+118
View File
@@ -0,0 +1,118 @@
---
title: FUSE
icon: hard-drive
description: Set up FUSE support for MIRAGE with the TypeScript SDK.
---
## Prerequisites
- Node.js 20+
- [pnpm](https://pnpm.io/) (recommended, `npm` and `yarn` also work, but the FUSE binding's install script needs extra configuration with pnpm).
## System FUSE
Install the OS-level FUSE kernel support first:
- [macOS FUSE Setup](/home/setup/macos), macFUSE + kernel extension + Apple Silicon recovery mode steps.
- [Linux FUSE Setup](/home/setup/linux), `fuse3` install and `/etc/fuse.conf`.
## Install the Node Binding
The Node package (`@struktoai/mirage-node`) ships FUSE support via an **optional peer dependency** on [`@zkochan/fuse-native`](https://www.npmjs.com/package/@zkochan/fuse-native), the pnpm author's actively-maintained fork that works on Node 20+ and supports both macOS and Linux (thanks to [@zkochan](https://github.com/fuse-friends/fuse-native/issues/36#issuecomment-3089754579)).
```bash
pnpm add @struktoai/mirage-node @zkochan/fuse-native
```
<Tip>
Non-FUSE users don't need `@zkochan/fuse-native`, the base `@struktoai/mirage-node` package works without it. Only install the binding when you want a real mountpoint.
</Tip>
### Allow pnpm to run the install script
`@zkochan/fuse-native` compiles native code on install. pnpm blocks install scripts by default, allow this one explicitly in `pnpm-workspace.yaml`:
```yaml
onlyBuiltDependencies:
- '@zkochan/fuse-native'
```
Or, if you're not using a pnpm workspace, in `package.json`:
```json
{
"pnpm": {
"onlyBuiltDependencies": ["@zkochan/fuse-native"]
}
}
```
## macOS 4+ Symlink Workaround
<Tabs>
<Tab title="macOS">
macFUSE 4 ships `libfuse.2.dylib` instead of the legacy `libosxfuse.2.dylib` that `@zkochan/fuse-native` was built against. Create a one-time symlink:
```bash
sudo ln -sf /usr/local/lib/libfuse.2.dylib /usr/local/lib/libosxfuse.2.dylib
pnpm rebuild @zkochan/fuse-native
```
</Tab>
<Tab title="Linux">
No workaround needed on Linux, `@zkochan/fuse-native` links against `libfuse3` directly.
</Tab>
</Tabs>
## Verify
```ts
import { Mount, MountMode, RAMResource, Workspace } from '@struktoai/mirage-node'
const ws = new Workspace({
'/data': new Mount(new RAMResource(), { mode: MountMode.WRITE, fuse: true }),
})
await ws.fuseReady()
await ws.execute('echo hello | tee /data/x.txt')
console.log('mountpoints:', ws.fuseMountpoints)
// Any tool (ls, cat, external subprocess) can now see x.txt under the mountpoint.
await ws.close() // auto-unmounts
```
If `ws.fuseMountpoints` prints a `/tmp/mirage-fuse-XXXXXX` path and no error is thrown, the binding is wired up correctly.
Mounts are async, so `await ws.fuseReady()` once after constructing the workspace; it resolves when every `fuse` mount is live (and rejects if one fails to mount). Until then `ws.fuseMountpoints` may be empty. (In Python, where mounts are synchronous, the constructor blocks until ready instead.)
## Per-mount FUSE
FUSE is configured **per mount**. Each mount whose `fuse` is set is exposed at
its own mountpoint, showing only that mount's subtree. The value is either
`true` (mount at a fresh temp directory) or a path string (mount there,
creating the directory if missing):
```yaml
mode: WRITE
mounts:
/data:
resource: ram
fuse: /tmp/data-repo # explicit path
/s3:
resource: s3
fuse: true # temp directory
```
Programmatically, set `fuse` per mount: `{ '/data': new Mount(dataResource, { fuse: '/tmp/data-repo' }), '/s3': new Mount(s3Resource, { fuse: true }) }`.
`ws.fuseMountpoints` returns a `{ prefix: path }` map of the live mountpoints.
<Note>
FUSE on Node has a runtime constraint worth knowing, `fs-monkey` can't patch ESM `node:fs` imports. See [TypeScript Limitations](/typescript/limitations) for details and workarounds.
</Note>
<Warning>
**macOS allows only one in-process FUSE mount.** macFUSE registers a
process-global signal source, so a second simultaneous mount in the same
process fails. Multiple per-mount FUSE mounts work on Linux; on macOS, enable
`fuse` on a single mount per workspace (or run extra mounts in separate
processes).
</Warning>
+151
View File
@@ -0,0 +1,151 @@
---
title: Google Cloud Storage
icon: google
description: Set up Google Cloud Storage as a MIRAGE resource from Node or the browser.
---
GCS exposes an S3-compatible API when you use HMAC keys, so MIRAGE signs GCS requests with the same AWS Signature V4 used for S3, against the endpoint `https://storage.googleapis.com`.
Credentials (HMAC access key + secret for a service account) are created the same way in both runtimes, see [GCS Credentials](/home/setup/gcs).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { GCSResource, MountMode, Workspace } from '@struktoai/mirage-node'
const gcs = new GCSResource({
bucket: process.env.GCS_BUCKET!,
accessKeyId: process.env.GCS_ACCESS_KEY_ID!,
secretAccessKey: process.env.GCS_SECRET_ACCESS_KEY!,
})
const ws = new Workspace({ '/bucket/': gcs }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `GCSResource` is secret-free, your backend signs each operation using your HMAC keys and hands back a URL. Since GCS accepts AWS Signature V4, the same `@aws-sdk/s3-request-presigner` code used for S3 works, pointed at the GCS endpoint.
### 1. Server: sign URLs with the GCS endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const client = new S3Client({
region: 'auto',
endpoint: 'https://storage.googleapis.com',
credentials: {
accessKeyId: process.env.GCS_ACCESS_KEY_ID!,
secretAccessKey: process.env.GCS_SECRET_ACCESS_KEY!,
},
})
const BUCKET = process.env.GCS_BUCKET!
app.post('/presign/gcs', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
### 2. Browser: wire it up
```ts
import { GCSResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const gcs = new GCSResource({
bucket: 'my-gcs-bucket',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/gcs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': gcs }, { mode: MountMode.READ })
```
<Note>
If you prefer Google's native signer (Google-provided IAM-signed URLs via `@google-cloud/storage`'s `getSignedUrl`), you can use it here too, the presigner contract only cares that the returned URL works with `fetch()`. Native signing avoids shipping HMAC keys to your backend.
</Note>
### 3. Configure CORS on the bucket
GCS rejects S3-flavored `PutBucketCors` calls, its CORS schema differs. Use **either** the native XML API (via HMAC keys, same creds your presigner uses) **or** `gsutil cors set`.
**Option A, helper script (no gcloud install):**
The browser example ships a signer that crafts the GCS-shaped CORS XML and submits it via AWS SigV4 to `https://storage.googleapis.com/<bucket>?cors`:
```bash
cd examples/typescript/browser
npx tsx scripts/configure-gcs-cors.ts http://localhost:5173 https://app.example.com
```
**Option B, `gsutil`:**
```bash
brew install google-cloud-sdk
gcloud auth login
cat > cors.json <<'EOF'
[
{
"origin": ["http://localhost:5173", "https://app.example.com"],
"method": ["GET", "PUT", "HEAD", "DELETE", "POST"],
"responseHeader": ["Content-Type", "ETag", "Last-Modified"],
"maxAgeSeconds": 3000
}
]
EOF
gsutil cors set cors.json gs://$GCS_BUCKET
```
<Warning>
GCS's XML API accepts `<CorsConfig><Cors><Origins>…</Origins><Methods>…</Methods><ResponseHeaders>…</ResponseHeaders><MaxAgeSec>…</MaxAgeSec></Cors></CorsConfig>`. There is no `<Headers>` / `<AllowedHeaders>` element, GCS allows all request headers through automatically.
</Warning>
See the [GCS resource](/python/resource/gcs) docs for the equivalent Python wiring.
+63
View File
@@ -0,0 +1,63 @@
---
title: Google Docs
icon: google
description: Mount Google Docs as JSON-backed files from Node or the browser.
---
`GDocsResource` exposes Google Docs as `.gdoc.json` files. Reads return the document's structured content; writes update the doc in place via the Google Docs API.
Setup steps for OAuth credentials live at [Google Workspace Credentials](/home/setup/google).
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { GDocsResource, MountMode, Workspace } from '@struktoai/mirage-node'
const docs = new GDocsResource({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
refreshToken: process.env.GOOGLE_REFRESH_TOKEN!,
})
const ws = new Workspace({ '/docs': docs }, { mode: MountMode.WRITE })
await ws.execute('cat /docs/owned/MyDoc.gdoc.json')
```
## Browser
```bash
pnpm add @struktoai/mirage-browser
```
```ts
import { GDocsResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const docs = new GDocsResource({
clientId: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
refreshToken: localStorage.getItem('gdocs.refresh_token')!,
})
const ws = new Workspace({ '/docs': docs }, { mode: MountMode.WRITE })
```
A PKCE example lives at `examples/typescript/browser/src/gdocs_pkce.ts`.
## Config
| Field | Default | Notes |
| --- | --- | --- |
| `clientId` | required | Google OAuth client ID. |
| `clientSecret` | required | Google OAuth client secret. Redacted in snapshots. |
| `refreshToken` | required | User's refresh token. Redacted in snapshots. |
| `refreshFn` | built-in | Optional override for the access-token refresh callback. |
## Mount mode
`read`, partial write (text replacements via the Docs API).
For the JSON shape and supported edits see the [Python Google Docs docs](/python/resource/gdocs).
+63
View File
@@ -0,0 +1,63 @@
---
title: Google Drive
icon: google
description: Mount Google Drive as a Mirage filesystem from Node or the browser.
---
`GDriveResource` exposes a user's Drive (My Drive plus shared drives the user can see) as a tree.
Setup steps for OAuth credentials live at [Google Workspace Credentials](/home/setup/google).
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { GDriveResource, MountMode, Workspace } from '@struktoai/mirage-node'
const drive = new GDriveResource({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
refreshToken: process.env.GOOGLE_REFRESH_TOKEN!,
})
const ws = new Workspace({ '/drive': drive }, { mode: MountMode.READ })
await ws.execute('ls /drive/owned/')
```
## Browser
```bash
pnpm add @struktoai/mirage-browser
```
```ts
import { GDriveResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const drive = new GDriveResource({
clientId: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
refreshToken: localStorage.getItem('gdrive.refresh_token')!,
})
const ws = new Workspace({ '/drive': drive }, { mode: MountMode.READ })
```
The browser examples in `examples/typescript/browser/src/gdrive_pkce.ts` show a full PKCE flow that keeps the secret out of the page.
## Config
| Field | Default | Notes |
| --- | --- | --- |
| `clientId` | required | Google OAuth client ID. |
| `clientSecret` | required | Google OAuth client secret. Redacted in snapshots. |
| `refreshToken` | required | User's refresh token. Redacted in snapshots. |
| `refreshFn` | built-in | Optional override for the access-token refresh callback. |
## Mount mode
`read`, partial write (uploads under `/drive/owned/`).
For the mounted layout (`owned/`, `shared/`, mime-aware listings) see the [Python Drive docs](/python/resource/gdrive).
+63
View File
@@ -0,0 +1,63 @@
---
title: GitHub
icon: github
description: Mount a GitHub repository as a Mirage filesystem from Node or the browser.
---
`GitHubResource` exposes a single repository at a ref as a read-only filesystem. Reads stream blobs through the GitHub API.
Setup steps for the personal access token live at [GitHub Credentials](/home/setup/github).
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { GitHubResource, MountMode, Workspace } from '@struktoai/mirage-node'
const gh = new GitHubResource({
token: process.env.GITHUB_TOKEN!,
owner: 'strukto-ai',
repo: 'mirage',
ref: 'main',
})
const ws = new Workspace({ '/repo': gh }, { mode: MountMode.READ })
await ws.execute('cat /repo/README.md')
```
## Browser
```bash
pnpm add @struktoai/mirage-browser
```
```ts
import { GitHubResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const gh = new GitHubResource({
token: GITHUB_TOKEN,
owner: 'strukto-ai',
repo: 'mirage',
})
```
The token reaches the browser, so use a fine-grained PAT scoped to read-only access on a single repo.
## Config
| Field | Default | Notes |
| --- | --- | --- |
| `token` | required | Personal access token. Redacted in snapshots. |
| `owner` | required | Repo owner (user or org). |
| `repo` | required | Repo name. |
| `ref` | `HEAD` | Branch, tag, or commit SHA. |
| `baseUrl` | `https://api.github.com` | Override for GitHub Enterprise. |
## Mount mode
`read`. Writes are not yet exposed.
For the mounted layout (refs, paths, blob caching) see the [Python GitHub docs](/python/resource/github).
+62
View File
@@ -0,0 +1,62 @@
---
title: GitHub CI
icon: github
description: Mount GitHub Actions runs, jobs, logs, and artifacts as a Mirage filesystem.
---
`GitHubCIResource` mounts the Actions surface of a single repository: runs, jobs, step logs, and artifact metadata.
Setup steps for the personal access token live at [GitHub CI Credentials](/home/setup/github_ci).
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { GitHubCIResource, MountMode, Workspace } from '@struktoai/mirage-node'
const ci = new GitHubCIResource({
token: process.env.GITHUB_TOKEN!,
owner: 'strukto-ai',
repo: 'mirage',
days: 14,
})
const ws = new Workspace({ '/ci': ci }, { mode: MountMode.READ })
await ws.execute('ls /ci/runs/')
```
## Browser
```bash
pnpm add @struktoai/mirage-browser
```
```ts
import { GitHubCIResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const ci = new GitHubCIResource({
token: GITHUB_TOKEN,
owner: 'strukto-ai',
repo: 'mirage',
})
```
## Config
| Field | Default | Notes |
| --- | --- | --- |
| `token` | required | Personal access token with `actions:read`. Redacted in snapshots. |
| `owner` | required | Repo owner. |
| `repo` | required | Repo name. |
| `days` | `30` | Window of recent runs to expose. |
| `maxRuns` | `1000` | Hard ceiling on listed runs. |
| `baseUrl` | `https://api.github.com` | Override for GitHub Enterprise. |
## Mount mode
`read`.
For the run/job/log layout see the [Python GitHub CI docs](/python/resource/github_ci).
+63
View File
@@ -0,0 +1,63 @@
---
title: Gmail
icon: google
description: Mount a Gmail mailbox as a Mirage filesystem from Node or the browser.
---
`GmailResource` browses messages, threads, and labels via the Gmail API.
Both runtimes share the same OAuth config: a Google Cloud `clientId` / `clientSecret` plus a long-lived `refreshToken` for the user. Setup steps live at [Google Workspace Credentials](/home/setup/google).
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { GmailResource, MountMode, Workspace } from '@struktoai/mirage-node'
const gmail = new GmailResource({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
refreshToken: process.env.GOOGLE_REFRESH_TOKEN!,
})
const ws = new Workspace({ '/mail': gmail }, { mode: MountMode.READ })
await ws.execute('ls /mail/INBOX/')
```
## Browser
```bash
pnpm add @struktoai/mirage-browser
```
```ts
import { GmailResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const gmail = new GmailResource({
clientId: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
refreshToken: localStorage.getItem('gmail.refresh_token')!,
})
const ws = new Workspace({ '/mail': gmail }, { mode: MountMode.READ })
```
For OAuth in a browser, prefer PKCE so secrets stay off the page; the [Google setup guide](/home/setup/google) shows the flow.
## Config
| Field | Default | Notes |
| --- | --- | --- |
| `clientId` | required | Google OAuth client ID. |
| `clientSecret` | required | Google OAuth client secret. Redacted in snapshots. |
| `refreshToken` | required | User's refresh token. Redacted in snapshots. |
| `refreshFn` | built-in | Optional override for the access-token refresh callback. |
## Mount mode
`read`. Partial write (drafts, labels) is exposed via Python and may land in TS in a future release.
For the mounted layout (labels, dates, attachments) see the [Python Gmail docs](/python/resource/gmail).
+59
View File
@@ -0,0 +1,59 @@
---
title: Google Sheets
icon: google
description: Mount Google Sheets spreadsheets as Mirage files from Node or the browser.
---
`GSheetsResource` exposes Google Sheets as `.gsheet.json` and CSV-style files. Reads return cell values; writes update ranges via the Sheets API.
Setup steps for OAuth credentials live at [Google Workspace Credentials](/home/setup/google).
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { GSheetsResource, MountMode, Workspace } from '@struktoai/mirage-node'
const sheets = new GSheetsResource({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
refreshToken: process.env.GOOGLE_REFRESH_TOKEN!,
})
const ws = new Workspace({ '/sheets': sheets }, { mode: MountMode.READ })
await ws.execute('ls /sheets/owned/')
```
## Browser
```bash
pnpm add @struktoai/mirage-browser
```
```ts
import { GSheetsResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const sheets = new GSheetsResource({
clientId: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
refreshToken: localStorage.getItem('gsheets.refresh_token')!,
})
```
## Config
| Field | Default | Notes |
| --- | --- | --- |
| `clientId` | required | Google OAuth client ID. |
| `clientSecret` | required | Google OAuth client secret. Redacted in snapshots. |
| `refreshToken` | required | User's refresh token. Redacted in snapshots. |
| `refreshFn` | built-in | Optional override for the access-token refresh callback. |
## Mount mode
`read`, partial write (range updates).
For the layout, sheet/tab structure, and write semantics see the [Python Sheets docs](/python/resource/gsheets).
+59
View File
@@ -0,0 +1,59 @@
---
title: Google Slides
icon: google
description: Mount Google Slides decks as Mirage files from Node or the browser.
---
`GSlidesResource` exposes Slides decks as `.gslides.json` files containing the deck structure, slides, and text content.
Setup steps for OAuth credentials live at [Google Workspace Credentials](/home/setup/google).
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { GSlidesResource, MountMode, Workspace } from '@struktoai/mirage-node'
const slides = new GSlidesResource({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
refreshToken: process.env.GOOGLE_REFRESH_TOKEN!,
})
const ws = new Workspace({ '/slides': slides }, { mode: MountMode.READ })
await ws.execute('ls /slides/owned/')
```
## Browser
```bash
pnpm add @struktoai/mirage-browser
```
```ts
import { GSlidesResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const slides = new GSlidesResource({
clientId: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
refreshToken: localStorage.getItem('gslides.refresh_token')!,
})
```
## Config
| Field | Default | Notes |
| --- | --- | --- |
| `clientId` | required | Google OAuth client ID. |
| `clientSecret` | required | Google OAuth client secret. Redacted in snapshots. |
| `refreshToken` | required | User's refresh token. Redacted in snapshots. |
| `refreshFn` | built-in | Optional override for the access-token refresh callback. |
## Mount mode
`read`, partial write (text element replacements).
For the JSON structure (slides, layouts, text frames) see the [Python Slides docs](/python/resource/gslides).
+100
View File
@@ -0,0 +1,100 @@
---
title: HF Buckets
icon: /images/huggingface-logo.svg
description: Mount Hugging Face Buckets as a Mirage filesystem in TypeScript with lazy reads and writes.
---
`HfBucketsResource` mounts a [Hugging Face Bucket](https://huggingface.co/docs/buckets)
at a prefix such as `/hf/`. The TypeScript backend mirrors the
[Python one](/python/resource/hf_buckets) and returns identical results.
For credential setup, see [HF Buckets Setup](/home/setup/hf_buckets).
<Note>
Node only. The backend uses the native [opendal](https://www.npmjs.com/package/opendal)
binding, which does not run in the browser.
</Note>
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { HfBucketsResource, MountMode, Workspace } from '@struktoai/mirage-node'
const bucket = new HfBucketsResource({
bucket: process.env.HF_BUCKET_NAME!, // "namespace/bucket-name"
token: process.env.HF_TOKEN,
// Optional:
// endpoint: 'https://huggingface.co',
// keyPrefix: 'data/',
})
const ws = new Workspace({ '/hf/': bucket }, { mode: MountMode.READ })
console.log((await ws.execute('ls /hf/')).stdoutText)
console.log((await ws.execute('tree /hf/')).stdoutText)
console.log((await ws.execute("find /hf/ -name '*.json'")).stdoutText)
console.log((await ws.execute('stat /hf/data/file.txt')).stdoutText)
```
`HfBucketsResource` takes the bucket in `namespace/bucket-name` form plus an
optional access token. Both `READ` and `WRITE` modes are supported out of the box.
## Filesystem Layout
Bucket object keys map to virtual paths under the mount prefix: virtual
`/hf/data/file.txt` maps to bucket key `data/file.txt`.
## Cache
Directory listings are cached with `indexTtl = 600` (10 minutes) and populate
file-size/type entries that `stat` reads via a fast path, so a `readdir`
followed by per-entry `stat` calls (what `ls` and most shell commands trigger)
costs one HTTP request instead of N.
## Shell Commands
The same command set as the Python backend, operating on real file content.
Large files benefit from range reads to avoid downloading entire objects.
### Read Commands
| Command | Notes |
| --------------- | ------------------------------------------ |
| `cat` | Read file content |
| `head` / `tail` | First/last N lines |
| `grep` / `rg` | Pattern search (file or directory level) |
| `jq` | Query JSON fields |
| `wc` | Line/word/byte counts |
| `stat` | File metadata (name, size, type, modified) |
| `find` | Recursive search with `-name`, `-maxdepth` |
| `tree` | Directory tree view |
| `nl` | Number lines |
| `du` | Disk usage summary |
| `file` | Detect file type |
| `strings` | Extract printable strings from binary |
| `xxd` | Hex dump |
| `md5` | MD5 checksum |
| `sha256sum` | SHA-256 checksum |
### Text Processing
`awk`, `sed`, `tr`, `sort`, `uniq`, `cut`, `join`, `paste`, `column`, `fold`,
`expand`, `unexpand`, `fmt`, `rev`, `tac`, `look`, `shuf`, `tsort`, `comm`,
`cmp`, `diff`, `iconv`, `base64`.
### File Operations
`rm`, `touch`, `mktemp`, `split`, `csplit`. Object stores have no real
directories, so `rm` is file-only (no `-r`).
### Path Utilities
`ls`, `basename`, `dirname`, `realpath`, `readlink`.
### Compression
`gzip`, `gunzip`, `zcat`, `zgrep`, `tar`, `zip`, `unzip`.
+54
View File
@@ -0,0 +1,54 @@
---
title: HF Datasets
icon: /images/huggingface-logo.svg
description: Mount a Hugging Face Dataset repo as a Mirage filesystem in TypeScript.
---
`HfDatasetsResource` mounts a [Hugging Face Dataset](https://huggingface.co/datasets)
repo at a prefix such as `/ds/`. All reads are lazy: only the bytes you actually
`cat`/`head` get transferred. The TypeScript backend mirrors the
[Python one](/python/resource/hf_datasets) and returns identical results.
For credential setup, see [HF Datasets Setup](/home/setup/hf_datasets).
<Note>
Node only. The backend uses the native [opendal](https://www.npmjs.com/package/opendal)
binding, which does not run in the browser.
</Note>
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { HfDatasetsResource, MountMode, Workspace } from '@struktoai/mirage-node'
const dataset = new HfDatasetsResource({
repoId: 'AlienKevin/SWE-ZERO-12M-trajectories', // "namespace/dataset-name"
token: process.env.HF_TOKEN, // optional for public datasets
// Optional:
// endpoint: 'https://huggingface.co',
// revision: 'main',
// keyPrefix: 'train/',
})
const ws = new Workspace({ '/ds/': dataset }, { mode: MountMode.READ })
console.log((await ws.execute('ls -lh /ds/')).stdoutText)
console.log((await ws.execute("find /ds/ -name '*.parquet' | wc -l")).stdoutText)
console.log((await ws.execute('cat /ds/README.md | head -n 20')).stdoutText)
```
`HfDatasetsResource` takes `repoId` in `namespace/dataset-name` form plus an
optional access token. Public datasets need no token.
## Filesystem Layout
Maps dataset repo files (README, parquet shards, etc.) to virtual paths under
the mount prefix. Parquet shards stream lazily via byte-range reads.
## Shell Commands
Same set as [HF Buckets](/typescript/setup/hf_buckets#shell-commands).
+66
View File
@@ -0,0 +1,66 @@
---
title: HF Models
icon: /images/huggingface-logo.svg
description: Mount a Hugging Face Model repo as a Mirage filesystem in TypeScript.
---
`HfModelsResource` mounts a [Hugging Face Model](https://huggingface.co/models)
repo at a prefix such as `/m/`. You can inspect configs, tokenizers, and READMEs
without downloading the weights, weights stream only when you actually `cat` them.
The TypeScript backend mirrors the [Python one](/python/resource/hf_models) and
returns identical results.
For credential setup, see [HF Models Setup](/home/setup/hf_models).
<Note>
Node only. The backend uses the native [opendal](https://www.npmjs.com/package/opendal)
binding, which does not run in the browser.
</Note>
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { HfModelsResource, MountMode, Workspace } from '@struktoai/mirage-node'
const model = new HfModelsResource({
repoId: 'sapientinc/HRM-Text-1B', // "namespace/model-name"
token: process.env.HF_TOKEN, // optional for public repos
// Optional:
// endpoint: 'https://huggingface.co',
// revision: 'main',
})
const ws = new Workspace({ '/m/': model }, { mode: MountMode.READ })
// ls is cheap: one HTTP list call
console.log((await ws.execute('ls -lh /m/')).stdoutText)
// Read the config (small, fast)
console.log((await ws.execute('cat /m/config.json')).stdoutText)
// Stat the weights without downloading them
console.log((await ws.execute('stat /m/model.safetensors')).stdoutText)
```
## Filesystem Layout
Maps model repo files (config, tokenizer, weights, etc.) to virtual paths.
For example, `sapientinc/HRM-Text-1B` mounted at `/m/` exposes:
```text
/m/
README.md
config.json
tokenizer.json
tokenizer_config.json
model.safetensors ← never downloaded unless you cat it
```
## Shell Commands
Same set as [HF Buckets](/typescript/setup/hf_buckets#shell-commands).
+45
View File
@@ -0,0 +1,45 @@
---
title: HF Spaces
icon: /images/huggingface-logo.svg
description: Mount a Hugging Face Space repo as a Mirage filesystem in TypeScript.
---
`HfSpacesResource` mounts a [Hugging Face Space](https://huggingface.co/spaces)
repo (app code, README, config, requirements) at a prefix such as `/s/`.
The TypeScript backend mirrors the [Python one](/python/resource/hf_spaces) and
returns identical results.
For credential setup, see [HF Spaces Setup](/home/setup/hf_spaces).
<Note>
Node only. The backend uses the native [opendal](https://www.npmjs.com/package/opendal)
binding, which does not run in the browser.
</Note>
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { HfSpacesResource, MountMode, Workspace } from '@struktoai/mirage-node'
const space = new HfSpacesResource({
repoId: 'HuggingFaceBio/carbon-demo', // "namespace/space-name"
token: process.env.HF_TOKEN, // optional for public spaces
// Optional:
// endpoint: 'https://huggingface.co',
// revision: 'main',
})
const ws = new Workspace({ '/s/': space }, { mode: MountMode.READ })
console.log((await ws.execute('ls /s/')).stdoutText)
console.log((await ws.execute("find /s/ -name '*.py'")).stdoutText)
console.log((await ws.execute('cat /s/README.md | head -n 15')).stdoutText)
```
## Shell Commands
Same set as [HF Buckets](/typescript/setup/hf_buckets#shell-commands).
+81
View File
@@ -0,0 +1,81 @@
---
title: LanceDB
icon: database
description: Mount a LanceDB table as a Mirage filesystem in TypeScript, with label folders and a semantic search command.
---
`LanceDBResource` exposes a LanceDB table as a read-only filesystem: group-by
columns become nested folders, each row is a card plus an optional blob file, and
semantic search is the `search` command. The TypeScript backend mirrors the
[Python one](/python/resource/lancedb) and returns identical results.
## Node
```bash
pnpm add @struktoai/mirage-node @lancedb/lancedb
```
```ts
import { LanceDBResource, MountMode, Workspace } from '@struktoai/mirage-node'
const fashion = new LanceDBResource({
config: {
uri: '/data/fashion.lancedb', // or s3://, gs://, db:// (LanceDB Cloud)
table: 'fashion',
groupBy: ['gender', 'articleType', 'baseColour'],
idColumn: 'id',
titleColumn: 'productDisplayName',
blobColumn: 'image_bytes',
blobExt: 'jpg',
vectorColumn: 'vector', // presence enables the search command
searchLimit: 5,
},
})
const ws = new Workspace({ '/fashion/': fashion }, { mode: MountMode.READ })
await ws.execute('ls /fashion/Men/Shoes/White')
await ws.execute('search "red running shoes" /fashion') // ranked paths + score + card
```
## Filesystem layout
```text
/fashion/<gender>/<articleType>/<baseColour>/<id>.md # row card
/fashion/<gender>/<articleType>/<baseColour>/<id>.jpg # raw blob bytes
```
Semantic search is the `search` command, not a path: it returns ranked rows as
the canonical `<id>.md` paths above, annotated with the vector distance.
## Cloud and Enterprise
`db://` URIs connect to LanceDB Cloud (`apiKey` + `region`) or Enterprise
(`apiKey` + `hostOverride`):
```ts
const fashion = new LanceDBResource({
config: {
uri: 'db://my-database',
apiKey: process.env.LANCEDB_API_KEY,
region: 'us-east-1',
hostOverride: 'https://my-database.us-east-1.api.lancedb.com', // Enterprise only
table: 'fashion',
groupBy: ['gender'],
idColumn: 'id',
vectorColumn: 'vector',
},
})
```
## Supported commands
`ls`, `cd`, `tree`, `cat`, `stat`, `find`, `wc`, and `search`. `grep`/`rg` stay
lexical; `search "<query>" <path>` is the semantic command, returning ranked
rows as canonical `<id>.md` paths plus a score, so results compose with `cat`,
`wc`, and pipes. Flags: `--top-k`, `--threshold`, `--method semantic`.
Search requires a table built with an embedding function on a source field, so
`tbl.search(text)` auto-embeds the query. The Node example under
`examples/typescript/lancedb/` builds such a table and produces the same output
as the Python example, including identical similarity scores (0.2679).
+59
View File
@@ -0,0 +1,59 @@
---
title: Langfuse
icon: chart-line
description: Mount Langfuse traces and observations as a Mirage filesystem.
---
`LangfuseResource` exposes Langfuse projects and traces as a read-only tree.
Setup steps for the public/secret keys live at [Langfuse Credentials](/home/setup/langfuse).
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { LangfuseResource, MountMode, Workspace } from '@struktoai/mirage-node'
const lf = new LangfuseResource({
publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
secretKey: process.env.LANGFUSE_SECRET_KEY!,
host: process.env.LANGFUSE_HOST,
})
const ws = new Workspace({ '/lf': lf }, { mode: MountMode.READ })
await ws.execute('ls /lf/traces/')
```
## Browser
```bash
pnpm add @struktoai/mirage-browser
```
```ts
import { LangfuseResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const lf = new LangfuseResource({
publicKey: LANGFUSE_PUBLIC_KEY,
secretKey: LANGFUSE_SECRET_KEY,
})
```
## Config
| Field | Default | Notes |
| --- | --- | --- |
| `publicKey` | required | Langfuse public key. |
| `secretKey` | required | Langfuse secret key. Redacted in snapshots. |
| `host` | `https://cloud.langfuse.com` | Override for self-hosted Langfuse. |
| `defaultTraceLimit` | `100` | Default page size for trace listings. |
| `defaultSearchLimit` | `100` | Default page size for search. |
## Mount mode
`read`.
For the layout and search behavior see the [Python Langfuse docs](/python/resource/langfuse).
+56
View File
@@ -0,0 +1,56 @@
---
title: Linear
icon: triangle
description: Mount Linear teams, projects, and issues as a Mirage filesystem.
---
`LinearResource` exposes a Linear workspace as a tree of teams, projects, and issues. Reads route through the Linear GraphQL API.
Setup steps for the API key live at [Linear Credentials](/home/setup/linear).
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { LinearResource, MountMode, Workspace } from '@struktoai/mirage-node'
const linear = new LinearResource({
apiKey: process.env.LINEAR_API_KEY!,
workspace: 'strukto',
})
const ws = new Workspace({ '/linear': linear }, { mode: MountMode.READ })
await ws.execute('ls /linear/teams/')
```
## Browser
```bash
pnpm add @struktoai/mirage-browser
```
```ts
import { LinearResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const linear = new LinearResource({
apiKey: LINEAR_API_KEY,
})
```
## Config
| Field | Default | Notes |
| --- | --- | --- |
| `apiKey` | required | Personal API key. Redacted in snapshots. |
| `workspace` | (auto) | Workspace identifier; auto-detected from the key. |
| `teamIds` | none | Optional allowlist of team IDs to expose. |
| `baseUrl` | `https://api.linear.app/graphql` | Override for self-hosted instances. |
## Mount mode
`read`.
For the mounted layout (`teams/`, `projects/`, `issues/`) see the [Python Linear docs](/python/resource/linear).
+129
View File
@@ -0,0 +1,129 @@
---
title: MinIO
icon: /images/minio-logo.svg
description: Set up MinIO as a MIRAGE resource from Node or the browser.
---
MinIO speaks the S3 API with AWS Signature V4 against your own server endpoint (e.g. `http://localhost:9000`). MinIO is self-hosted, so the endpoint is required and path-style addressing is used by default.
Credentials (access key + secret key from the MinIO Console) are created the same way in both runtimes, see [MinIO Credentials](/home/setup/minio).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { MinIOResource, MountMode, Workspace } from '@struktoai/mirage-node'
const minio = new MinIOResource({
bucket: process.env.MINIO_BUCKET!,
endpoint: process.env.MINIO_ENDPOINT!,
accessKeyId: process.env.MINIO_ACCESS_KEY!,
secretAccessKey: process.env.MINIO_SECRET_KEY!,
})
const ws = new Workspace({ '/bucket/': minio }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `MinIOResource` is secret-free, your backend signs each operation using your MinIO keys and returns a URL. MinIO accepts AWS Signature V4, so `@aws-sdk/s3-request-presigner` works, pointed at your MinIO endpoint.
### 1. Server: sign URLs with the MinIO endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const client = new S3Client({
region: 'us-east-1',
endpoint: process.env.MINIO_ENDPOINT,
forcePathStyle: true,
credentials: {
accessKeyId: process.env.MINIO_ACCESS_KEY!,
secretAccessKey: process.env.MINIO_SECRET_KEY!,
},
})
const BUCKET = process.env.MINIO_BUCKET!
app.post('/presign/minio', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
<Warning>
MinIO uses **path-style** URLs (`forcePathStyle: true`), virtual-hosted style requires extra DNS setup on a self-hosted server.
</Warning>
### 2. Browser: wire it up
```ts
import { MinIOResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const minio = new MinIOResource({
bucket: 'my-bucket',
endpoint: 'http://localhost:9000',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/minio', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': minio }, { mode: MountMode.READ })
```
<Tip>
`endpoint` on the browser config is only used for display/logging; the actual endpoint is baked into the presigned URLs your backend returns.
</Tip>
### 3. CORS
MinIO answers CORS preflights for **all origins by default**, so local dev typically works without configuration. To restrict origins, set the server environment variable and restart MinIO:
```bash
MINIO_API_CORS_ALLOW_ORIGIN=http://localhost:5173,https://app.example.com
```
See the [MinIO resource](/python/resource/minio) docs for the equivalent Python wiring.
+60
View File
@@ -0,0 +1,60 @@
---
title: MongoDB
icon: database
description: Mount a MongoDB cluster as a Mirage filesystem of databases, collections, and documents.
---
`MongoDBResource` exposes a MongoDB connection as a read-only filesystem: databases at the top level, collections inside, individual documents at the leaves.
Setup steps for the connection URI live at [MongoDB Credentials](/home/setup/mongodb).
## Node
```bash
pnpm add @struktoai/mirage-node mongodb
```
```ts
import { MongoDBResource, MountMode, Workspace } from '@struktoai/mirage-node'
const mongo = new MongoDBResource({
uri: process.env.MONGO_URI!,
databases: ['app', 'analytics'],
})
const ws = new Workspace({ '/mongo': mongo }, { mode: MountMode.READ })
await ws.execute('ls /mongo/app/users/')
```
## Browser
```bash
pnpm add @struktoai/mirage-browser
```
The browser variant routes Mongo operations through an HTTP driver (no direct DB connection from the page). The browser examples ship a `mongo_proxy` Vite plugin that forwards to a server-side `mongodb` driver:
```ts
import { MongoDBResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const mongo = new MongoDBResource({
uri: 'mongodb-proxy://app',
httpDriver: { fetch: window.fetch },
})
```
## Config
| Field | Default | Notes |
| --- | --- | --- |
| `uri` | required | Mongo connection URI. Redacted in snapshots. |
| `databases` | none | Optional allowlist of database names. |
| `defaultDocLimit` | `100` | Default page size for ad-hoc reads. |
| `defaultSearchLimit` | `100` | Default page size for search-style queries. |
| `maxDocLimit` | `10_000` | Hard ceiling per read. |
## Mount mode
`read`.
For the database/collection/document layout see the [Python MongoDB docs](/python/resource/mongodb).
+108
View File
@@ -0,0 +1,108 @@
---
title: Notion
icon: book
description: Mount Notion pages and databases as a Mirage filesystem.
---
`NotionResource` exposes a Notion workspace as a filesystem. The Node SDK talks to the Notion REST API with an integration token; the Browser SDK talks to Notion's MCP server with an OAuth client provider so secrets stay off the page.
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { MountMode, NotionResource, Workspace } from '@struktoai/mirage-node'
const notion = new NotionResource({ apiKey: process.env.NOTION_API_KEY! })
const ws = new Workspace({ '/notion': notion }, { mode: MountMode.READ })
await ws.execute('ls /notion/pages/')
```
| Field | Default | Notes |
| --- | --- | --- |
| `apiKey` | required | Notion internal integration token. Redacted in snapshots. |
| `baseUrl` | `https://api.notion.com/v1` | Override for testing against a mock server. |
## Browser
```bash
pnpm add @struktoai/mirage-browser @modelcontextprotocol/sdk
```
```ts
import { MountMode, NotionResource, Workspace } from '@struktoai/mirage-browser'
const notion = new NotionResource({
authProvider, // OAuthClientProvider from @modelcontextprotocol/sdk
})
const ws = new Workspace({ '/notion': notion }, { mode: MountMode.READ })
await ws.execute('ls /notion/')
```
| Field | Default | Notes |
| --- | --- | --- |
| `authProvider` | required | `OAuthClientProvider` from `@modelcontextprotocol/sdk`. Redacted in snapshots. |
| `serverUrl` | Notion's hosted MCP endpoint | Override if you proxy MCP through your own URL. |
## Mount mode
`read`, `write` (page edits via the MCP server in the browser SDK).
## Resource commands
`notion-search` works on read mounts; the rest require a write mount:
```text
notion-search --query "Roadmap" [--limit 20]
notion-page-create --parent <parent-path> --title "title"
notion-block-append --params '{"block_id":"..."}' --json '{"children":[...]}'
notion-comment-add --json '{"parent":{"page_id":"..."},"rich_text":[{"text":{"content":"Comment"}}]}'
```
## Layout
```text
/notion/
pages/
<page-title>__<page-id>/
page.json
<child-page-title>__<child-id>/
page.json
...
databases/
<database-title>__<database-id>/
database.json
<row-page-title>__<page-id>/
page.json
...
```
`database.json` is the database's identity plus its typed column schema,
with no rows inline; `ls` the database directory to enumerate row pages.
The shape is identical to the Python connector:
```json
{
"database_id": "2c4d6a7f-5bf3-8036-bed2-d1c95826f76b",
"title": "Item",
"url": "https://www.notion.so/2c4d6a7f5bf38036bed2d1c95826f76b",
"created_time": "2025-12-09T23:36:00.000Z",
"last_edited_time": "2026-04-15T12:30:00.000Z",
"parent": { "type": "workspace", "workspace": true },
"archived": false,
"is_inline": false,
"properties": {
"Name": { "id": "title", "type": "title", "title": {} },
"Number": { "id": "e%5BNQ", "type": "number", "number": { "format": "number" } },
"Date": { "id": "%3BgQq", "type": "date", "date": {} }
}
}
```
A database row is itself a page: its `page.json` reports `parent_type` of
`database_id`. For full `page.json` field details and supported edits see
the [Python Notion docs](/python/resource/notion).
+138
View File
@@ -0,0 +1,138 @@
---
title: OCI Object Storage
icon: server
description: Set up Oracle Cloud Object Storage as a MIRAGE resource from Node or the browser.
---
OCI's S3 Compatibility API accepts AWS Signature V4 against `https://<namespace>.compat.objectstorage.<region>.oci.customer-oci.com`. MIRAGE derives this endpoint from your namespace + region automatically.
Credentials (bucket, namespace, region, customer secret key pair) are created the same way in both runtimes, see [OCI Credentials](/home/setup/oci).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { MountMode, OCIResource, Workspace } from '@struktoai/mirage-node'
const oci = new OCIResource({
bucket: process.env.OCI_BUCKET!,
namespace: process.env.OCI_NAMESPACE!,
region: process.env.OCI_REGION!,
accessKeyId: process.env.OCI_ACCESS_KEY_ID!,
secretAccessKey: process.env.OCI_SECRET_ACCESS_KEY!,
})
const ws = new Workspace({ '/bucket/': oci }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `OCIResource` is secret-free, your backend signs each operation using your OCI customer secret keys and returns a URL. OCI's S3-compat endpoint accepts AWS Signature V4, so `@aws-sdk/s3-request-presigner` works, pointed at the OCI endpoint.
### 1. Server: sign URLs with the OCI endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const NAMESPACE = process.env.OCI_NAMESPACE!
const REGION = process.env.OCI_REGION!
const client = new S3Client({
region: REGION,
endpoint: `https://${NAMESPACE}.compat.objectstorage.${REGION}.oci.customer-oci.com`,
forcePathStyle: true,
credentials: {
accessKeyId: process.env.OCI_ACCESS_KEY_ID!,
secretAccessKey: process.env.OCI_SECRET_ACCESS_KEY!,
},
})
const BUCKET = process.env.OCI_BUCKET!
app.post('/presign/oci', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
<Warning>
OCI requires **path-style** URLs (`forcePathStyle: true`), virtual-hosted style is not supported on the S3 Compatibility API.
</Warning>
### 2. Browser: wire it up
```ts
import { MountMode, OCIResource, Workspace } from '@struktoai/mirage-browser'
const oci = new OCIResource({
bucket: 'my-oci-bucket',
namespace: 'idsiqbdbcr4i',
region: 'us-ashburn-1',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/oci', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': oci }, { mode: MountMode.READ })
```
### 3. CORS
OCI's S3-compatibility endpoint ships with **permissive default CORS rules** that echo back whatever `Origin` the browser sends, so local dev typically "just works" without any bucket configuration.
For production origins you can tighten the policy through the OCI Console (Bucket → Pre-Authenticated Requests / CORS) or via the native OCI CLI:
```bash
oci os bucket update --bucket-name $OCI_BUCKET \
--namespace $OCI_NAMESPACE \
--cors-rules '[{"allowedOrigins":["https://app.example.com"],"allowedMethods":["GET","PUT","HEAD","DELETE","POST"],"allowedHeaders":["*"],"exposedHeaders":["ETag","Content-Length","Content-Type","Last-Modified"],"maxAgeInSeconds":3000}]'
```
<Note>
OCI's CORS is **not** configurable via the S3-compat `PutBucketCors` call, the schema differs. Use the native OCI Console or CLI.
</Note>
See the [OCI resource](/python/resource/oci) docs for the equivalent Python wiring.
+53
View File
@@ -0,0 +1,53 @@
---
title: OPFS
icon: hard-drive
description: Persistent in-browser filesystem backed by the Origin Private File System.
---
`OPFSResource` mounts the [Origin Private File System](https://web.dev/articles/origin-private-file-system) as a Mirage resource. Files persist across page reloads but stay scoped to the current origin.
<Note>
Browser only. There is no OPFS in Node; for local persistence in Node, use [Disk](/typescript/setup/disk).
</Note>
## Install
```bash
pnpm add @struktoai/mirage-browser
```
## Config
```ts
import { MountMode, OPFSResource, Workspace } from '@struktoai/mirage-browser'
const opfs = new OPFSResource({ root: 'mirage' })
const ws = new Workspace({ '/data': opfs }, { mode: MountMode.WRITE })
await ws.execute('echo persistent | tee /data/note.txt')
```
| Field | Default | Notes |
| --- | --- | --- |
| `root` | `''` | Subdirectory under the OPFS root. Useful to namespace multiple workspaces. |
## Mount mode
`read`, `write`, `exec`.
## Storage and quotas
The browser sets per-origin quotas (typically a fraction of free disk, single-digit GB on most setups). Hitting it raises `QuotaExceededError`.
```ts
const { quota, usage } = await navigator.storage.estimate()
console.log(`${usage} / ${quota} bytes`)
```
For long-lived workspaces, request persistent storage early so the browser doesn't evict your data under storage pressure:
```ts
await navigator.storage.persist()
```
See [TypeScript Limitations: Browser](/typescript/limitations#browser) for the full set of browser constraints.
+62
View File
@@ -0,0 +1,62 @@
---
title: Postgres
icon: database
description: Mount a Postgres database as a read-only filesystem of schemas, tables, and rows.
---
`PostgresResource` connects to Postgres via a DSN and exposes its schemas and tables as a tree.
Setup steps for the DSN and required permissions live at [Postgres Credentials](/home/setup/postgres).
## Node
```bash
pnpm add @struktoai/mirage-node pg
```
```ts
import { MountMode, PostgresResource, Workspace } from '@struktoai/mirage-node'
const pg = new PostgresResource({
dsn: process.env.PG_DSN!,
schemas: ['public', 'analytics'],
})
const ws = new Workspace({ '/pg': pg }, { mode: MountMode.READ })
await ws.execute('ls /pg/public/')
```
## Browser
```bash
pnpm add @struktoai/mirage-browser
```
For browser apps, point the resource at a same-origin proxy or use a serverless Postgres driver such as `@neondatabase/serverless`. The browser examples include a Vite plugin that wires the resource to a server-side `pg` client:
```ts
import { MountMode, PostgresResource, Workspace } from '@struktoai/mirage-browser'
const pg = new PostgresResource({
dsn: 'postgres-proxy://app',
})
```
## Config
| Field | Default | Notes |
| --- | --- | --- |
| `dsn` | required | Postgres connection string. Redacted in snapshots. |
| `schemas` | none | Optional list to limit which schemas appear in the tree. |
| `defaultRowLimit` | `1000` | Default LIMIT applied to ad-hoc reads. |
| `maxReadRows` | `10_000` | Hard ceiling per read. |
| `maxReadBytes` | `10 MiB` | Hard ceiling per read. |
| `defaultSearchLimit` | `100` | Default LIMIT for search-style queries. |
## Mount mode
`read` only. Postgres mounts are read-only at the resource level.
## Snapshot behavior
The DSN is redacted on snapshot. Loading a snapshot back requires a config file that supplies a fresh DSN.
+74
View File
@@ -0,0 +1,74 @@
---
title: Qdrant
icon: database
description: Mount a Qdrant collection as a Mirage filesystem in TypeScript, with payload folders and a semantic search command.
---
`QdrantResource` exposes a [Qdrant](https://qdrant.tech/) collection as a read-only filesystem: group-by
payload fields become nested folders, each point is a `.json` payload file (plus
a `.txt` text file and an optional blob), and semantic search is the `search`
command. The TypeScript backend mirrors the [Python one](/python/resource/qdrant)
and returns identical results.
## Install
The client is browser-safe, so the resource ships in `core` and is available
from both the Node and browser packages:
```bash
pnpm add @struktoai/mirage-node @qdrant/js-client-rest
```
```ts
import { QdrantResource, MountMode, Workspace } from '@struktoai/mirage-node'
const fashion = new QdrantResource({
config: {
url: 'https://xyz.cloud.qdrant.io',
apiKey: process.env.QDRANT_API_KEY,
collection: 'fashion',
groupBy: ['gender', 'articleType', 'baseColour'],
idField: 'id',
textField: 'productDisplayName',
blobField: 'image_b64',
blobExt: 'jpg',
searchLimit: 5,
},
})
const ws = new Workspace({ '/fashion/': fashion }, { mode: MountMode.READ })
await ws.execute('ls /fashion/Men/Shoes/White')
await ws.execute('search "red running shoes" /fashion') // ranked paths + score + content
```
## Filesystem layout
```text
/fashion/<gender>/<articleType>/<baseColour>/<id>.json # full payload (metadata)
/fashion/<gender>/<articleType>/<baseColour>/<id>.txt # embedded source text
/fashion/<gender>/<articleType>/<baseColour>/<id>.jpg # raw blob bytes
```
`<id>` is the Qdrant point id. Semantic search is the `search` command, not a
path: it returns ranked points as the canonical `<id>.txt` (or `<id>.json`) paths
above, annotated with the similarity score.
## Search embedding
The query is embedded **server-side**: the TypeScript client sends the query
text to Qdrant, so the cluster must have inference enabled (Qdrant Cloud) and
store vectors from the same `embeddingModel`
(default `sentence-transformers/all-MiniLM-L6-v2`). Browsing
(`ls`/`cat`/`find`/`grep`) needs no embedding.
## Supported commands
`ls`, `cd`, `tree`, `cat`, `stat`, `find`, `wc`, `head`, `tail`, and `search`. `grep`/`rg` stay
lexical; `search "<query>" <path>` is the semantic command, returning ranked
points as canonical `<id>.txt` (or `<id>.json`) paths plus a score, so results
compose with `cat`, `wc`, and pipes. Flags: `--top-k`, `--threshold`, `--method semantic`.
Folder listings filter on payload fields. A filtered listing scrolls first and
only creates keyword payload indexes for the `groupBy` fields if Qdrant reports
one is required. `maxRows` caps how many points are scanned per folder.
+122
View File
@@ -0,0 +1,122 @@
---
title: QingStor
icon: cloud
description: Set up QingStor as a MIRAGE resource from Node or the browser.
---
QingStor (QingCloud Object Storage) exposes an S3-compatible API with AWS Signature V4 against `https://s3.<zone>.qingstor.com`. MIRAGE derives this endpoint from your zone automatically (the zone is passed as `region`).
Credentials (QingCloud API access key pair) are created the same way in both runtimes, see [QingStor Credentials](/home/setup/qingstor).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { MountMode, QingStorResource, Workspace } from '@struktoai/mirage-node'
const qs = new QingStorResource({
bucket: process.env.QINGSTOR_BUCKET!,
region: process.env.QINGSTOR_ZONE!,
accessKeyId: process.env.QINGSTOR_ACCESS_KEY_ID!,
secretAccessKey: process.env.QINGSTOR_SECRET_ACCESS_KEY!,
})
const ws = new Workspace({ '/bucket/': qs }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `QingStorResource` is secret-free, your backend signs each operation using your QingStor keys and returns a URL. QingStor accepts AWS Signature V4, so `@aws-sdk/s3-request-presigner` works, pointed at the QingStor endpoint.
### 1. Server: sign URLs with the QingStor endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const ZONE = process.env.QINGSTOR_ZONE!
const client = new S3Client({
region: ZONE,
endpoint: `https://s3.${ZONE}.qingstor.com`,
credentials: {
accessKeyId: process.env.QINGSTOR_ACCESS_KEY_ID!,
secretAccessKey: process.env.QINGSTOR_SECRET_ACCESS_KEY!,
},
})
const BUCKET = process.env.QINGSTOR_BUCKET!
app.post('/presign/qingstor', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
### 2. Browser: wire it up
```ts
import { MountMode, QingStorResource, Workspace } from '@struktoai/mirage-browser'
const qs = new QingStorResource({
bucket: 'my-bucket',
region: 'pek3a',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/qingstor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': qs }, { mode: MountMode.READ })
```
<Tip>
`region` on the browser config is only used for display/logging; the actual endpoint is baked into the presigned URLs your backend returns.
</Tip>
### 3. CORS
Configure CORS rules in the QingCloud console: QingStor -> your bucket -> **Basic Settings** -> **CORS**. Allow your dev/production origins with methods `GET, PUT, HEAD, DELETE, POST`, allowed headers `*`, and expose headers `ETag, Content-Length, Content-Type, Last-Modified`.
See the [QingStor resource](/python/resource/qingstor) docs for the equivalent Python wiring.
+142
View File
@@ -0,0 +1,142 @@
---
title: Cloudflare R2
icon: cloud
description: Set up Cloudflare R2 as a MIRAGE resource from Node or the browser.
---
R2 uses the S3 API with AWS Signature V4 against `https://<account-id>.r2.cloudflarestorage.com`. MIRAGE derives this endpoint from your account ID automatically.
Credentials (R2 API token with object read/write scopes) are created the same way in both runtimes, see [R2 Credentials](/home/setup/r2).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { MountMode, R2Resource, Workspace } from '@struktoai/mirage-node'
const r2 = new R2Resource({
bucket: process.env.R2_BUCKET!,
accountId: process.env.R2_ACCOUNT_ID!,
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
})
const ws = new Workspace({ '/bucket/': r2 }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `R2Resource` is secret-free, your backend signs each operation using your R2 keys and returns a URL. R2 accepts AWS Signature V4, so `@aws-sdk/s3-request-presigner` works with `region: 'auto'` pointed at R2's endpoint.
### 1. Server: sign URLs with the R2 endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const client = new S3Client({
region: 'auto',
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
})
const BUCKET = process.env.R2_BUCKET!
app.post('/presign/r2', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
### 2. Browser: wire it up
```ts
import { MountMode, R2Resource, Workspace } from '@struktoai/mirage-browser'
const r2 = new R2Resource({
bucket: 'my-r2-bucket',
accountId: 'abc123...',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/r2', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': r2 }, { mode: MountMode.READ })
```
<Tip>
`accountId` on the browser config is only used for display/logging; the actual endpoint is baked into the presigned URLs your backend returns.
</Tip>
### 3. Configure CORS on the bucket
R2 does accept S3-flavored `PutBucketCors`, but your token needs **"Admin Read & Write"** permissions on the bucket, object-scoped tokens will get `Access Denied`. Two paths:
**Option A, Cloudflare Dashboard (fastest, no new token):**
1. Open https://dash.cloudflare.com → **R2** → your bucket
2. **Settings** tab → **CORS Policy** → **Add CORS policy**
3. Origin: `http://localhost:5173` (and any production origins), methods `GET,PUT,HEAD,DELETE,POST`, allowed headers `*`
**Option B, admin-scoped R2 API token + the helper script:**
1. Cloudflare Dashboard → **R2** → **Manage R2 API Tokens** → **Create API token**
2. Permissions: **Admin Read & Write** on your bucket
3. Update `R2_ACCESS_KEY_ID` / `R2_SECRET_ACCESS_KEY` in `.env.development`
4. Run the same helper used for S3:
```bash
cd examples/typescript/browser
npx tsx scripts/configure-cors.ts http://localhost:5173 https://app.example.com
```
<Warning>
Object-scoped tokens (`Object Read & Write`) cannot edit CORS. If the script returns `Access Denied` on R2, you're almost certainly using an object token.
</Warning>
See the [R2 resource](/python/resource/r2) docs for the equivalent Python wiring.
+43
View File
@@ -0,0 +1,43 @@
---
title: Redis
icon: database
description: Mount Redis keys as a Mirage filesystem (Node only).
---
`RedisResource` exposes Redis keys as files under a single mount. The base of every key is the configured `keyPrefix`.
<Note>
Node only. Requires the `redis@^5` peer dependency. The browser SDK doesn't ship a Redis client.
</Note>
## Install
```bash
pnpm add @struktoai/mirage-node redis
```
## Config
```ts
import { MountMode, RedisResource, Workspace } from '@struktoai/mirage-node'
const redis = new RedisResource({
url: process.env.REDIS_URL ?? 'redis://localhost:6379',
keyPrefix: 'mirage/',
})
const ws = new Workspace({ '/r': redis }, { mode: MountMode.WRITE })
await ws.execute('echo "hello" | tee /r/greet')
await ws.execute('cat /r/greet')
```
| Field | Default | Notes |
| --- | --- | --- |
| `url` | `redis://localhost:6379` | Standard Redis connection URL. Redacted in snapshots. |
| `keyPrefix` | `''` | Prefix prepended to every key. Useful for multi-tenant Redis instances. |
## Mount mode
`read`, `write`, `exec`.
For the broader Redis semantics (binary keys, key patterns, snapshot caveats) see the [Python Redis docs](/python/resource/redis).
+178
View File
@@ -0,0 +1,178 @@
---
title: S3
icon: aws
description: Set up AWS S3 as a MIRAGE resource from Node or the browser.
---
MIRAGE ships `S3Resource` in **two runtimes**:
- `@struktoai/mirage-node`, signs requests server-side using [@aws-sdk/client-s3](https://www.npmjs.com/package/@aws-sdk/client-s3).
- `@struktoai/mirage-browser`, stays secret-free: your backend hands out **presigned URLs** and the browser fetches them with `fetch()`.
Credentials (IAM user, access keys) are created the same way in both runtimes, see [AWS S3 Credentials](/home/setup/s3).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { MountMode, Workspace } from '@struktoai/mirage-node'
import { S3Resource } from '@struktoai/mirage-node'
const s3 = new S3Resource({
bucket: process.env.AWS_S3_BUCKET!,
region: process.env.AWS_DEFAULT_REGION ?? 'us-east-1',
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
})
const ws = new Workspace({ '/bucket/': s3 }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
If `~/.aws/credentials` is configured, you can omit `accessKeyId`/`secretAccessKey` and the AWS SDK picks up the default profile.
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `S3Resource` never sees AWS credentials. You implement one function, `presignedUrlProvider`, that your frontend calls to get a signed URL for each S3 operation. The actual signing happens on **your server**, using any S3-signing SDK you prefer.
### 1. Server: sign URLs on demand
Using [@aws-sdk/s3-request-presigner](https://www.npmjs.com/package/@aws-sdk/s3-request-presigner):
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const client = new S3Client({ region: process.env.AWS_DEFAULT_REGION })
const BUCKET = process.env.AWS_S3_BUCKET!
app.post('/presign', async (req, res) => {
const { path, op, opts } = req.body as {
path: string
op: 'GET' | 'PUT' | 'HEAD' | 'DELETE' | 'LIST' | 'COPY'
opts?: Record<string, unknown>
}
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType as string | undefined }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix as string | undefined,
Delimiter: opts?.listDelimiter as string | undefined,
ContinuationToken: opts?.listContinuationToken as string | undefined,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource as string}`,
}); break
}
const url = await getSignedUrl(client, cmd, { expiresIn: ttl })
res.json({ url })
})
```
<Tip>
Scope the presigner to the **minimal operations your app needs**. Read-only viewers only need `GET`/`HEAD`/`LIST`; a file manager needs the full set.
</Tip>
### 2. Browser: wire it up
```ts
import { MountMode, S3Resource, Workspace } from '@struktoai/mirage-browser'
const s3 = new S3Resource({
bucket: 'my-bucket',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = (await r.json()) as { url: string }
return url
},
})
const ws = new Workspace({ '/bucket/': s3 }, { mode: MountMode.READ })
const res = await ws.execute('cat /bucket/hello.txt')
console.log(res.stdoutText)
```
### 3. Configure CORS on the bucket
Presigned URLs reach the S3 host directly from the browser. Your bucket must allow cross-origin requests from your app's origin, otherwise every `fetch()` fails with `TypeError: Failed to fetch` (the browser drops the response at the CORS preflight).
```json
[
{
"AllowedOrigins": ["http://localhost:5173", "https://app.example.com"],
"AllowedMethods": ["GET", "PUT", "HEAD", "DELETE", "POST"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag", "Content-Length", "Content-Type", "Last-Modified"],
"MaxAgeSeconds": 3000
}
]
```
```bash
aws s3api put-bucket-cors --bucket $AWS_S3_BUCKET --cors-configuration file://cors.json
```
**Or** use the helper in the browser example, it signs the `PutBucketCors` call with your existing env credentials and applies the same policy to all configured buckets in one shot:
```bash
cd examples/typescript/browser
npx tsx scripts/configure-cors.ts http://localhost:5173 https://app.example.com
```
<Warning>
CORS is origin-exact. `http://localhost:5173` ≠ `http://localhost:5174` ≠ `https://app.example.com`. Include every origin you'll serve the page from.
</Warning>
See [S3 Setup](/home/setup/s3) for credential setup.
## Scoping a resource to a key prefix
Pass `keyPrefix` to scope every operation to a subpath of the bucket:
```ts
const s3 = new S3Resource({
bucket: 'app-data',
region: 'eu-west-1',
keyPrefix: `users/${userId}/`,
})
```
When set, every read/write/list/stat/copy/rename/delete operation is transparently scoped to that bucket subpath. Agents see clean paths like `/data/notes.md`; the underlying bucket key is `users/${userId}/data/notes.md`. Useful for multi-tenant systems. Pairs naturally with STS AssumeRole session policies for AWS-side enforcement. Unset behavior is unchanged.
**Normalization:** leading slashes are stripped and a trailing slash is added automatically. Both `undefined` and an empty string are treated as "no prefix."
In YAML-based configs the snake-case spelling `key_prefix` is also accepted and is automatically mapped to `keyPrefix` at construction.
<Warning>
**Browser variant — security note**
For `mirage-browser`'s `S3Resource` (which uses `presignedUrlProvider`), `keyPrefix` flows through the same core code path, but a client-controlled prefix is not a security boundary — a malicious client can ignore or alter it before signing. For real isolation, enforce the prefix inside your server-side `presignedUrlProvider` implementation (and in the IAM/STS session policy backing those credentials). Treat the client-side `keyPrefix` as ergonomics only.
</Warning>
+128
View File
@@ -0,0 +1,128 @@
---
title: Scaleway
icon: /images/scaleway-logo.svg
description: Set up Scaleway Object Storage as a MIRAGE resource from Node or the browser.
---
Scaleway Object Storage uses the S3 API with AWS Signature V4 against `https://s3.<region>.scw.cloud`. MIRAGE derives this endpoint from your region automatically.
Credentials (IAM API key, the access key + secret key pair) are created the same way in both runtimes, see [Scaleway Credentials](/home/setup/scaleway).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { MountMode, ScalewayResource, Workspace } from '@struktoai/mirage-node'
const scw = new ScalewayResource({
bucket: process.env.SCW_BUCKET!,
region: process.env.SCW_REGION!,
accessKeyId: process.env.SCW_ACCESS_KEY!,
secretAccessKey: process.env.SCW_SECRET_KEY!,
})
const ws = new Workspace({ '/bucket/': scw }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `ScalewayResource` is secret-free, your backend signs each operation using your Scaleway keys and returns a URL. Scaleway accepts AWS Signature V4, so `@aws-sdk/s3-request-presigner` works, pointed at the Scaleway endpoint.
### 1. Server: sign URLs with the Scaleway endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const REGION = process.env.SCW_REGION!
const client = new S3Client({
region: REGION,
endpoint: `https://s3.${REGION}.scw.cloud`,
credentials: {
accessKeyId: process.env.SCW_ACCESS_KEY!,
secretAccessKey: process.env.SCW_SECRET_KEY!,
},
})
const BUCKET = process.env.SCW_BUCKET!
app.post('/presign/scw', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
### 2. Browser: wire it up
```ts
import { MountMode, ScalewayResource, Workspace } from '@struktoai/mirage-browser'
const scw = new ScalewayResource({
bucket: 'my-bucket',
region: 'fr-par',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/scw', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': scw }, { mode: MountMode.READ })
```
<Tip>
`region` on the browser config is only used for display/logging; the actual endpoint is baked into the presigned URLs your backend returns.
</Tip>
### 3. CORS
Scaleway supports the S3 `PutBucketCors` call, so the AWS CLI works against the Scaleway endpoint:
```bash
aws s3api put-bucket-cors --bucket "$SCW_BUCKET" \
--endpoint-url "https://s3.$SCW_REGION.scw.cloud" \
--cors-configuration '{"CORSRules":[{"AllowedOrigins":["http://localhost:5173","https://app.example.com"],"AllowedMethods":["GET","PUT","HEAD","DELETE","POST"],"AllowedHeaders":["*"],"ExposeHeaders":["ETag","Content-Length","Content-Type","Last-Modified"]}]}'
```
See the [Scaleway resource](/python/resource/scaleway) docs for the equivalent Python wiring.
+121
View File
@@ -0,0 +1,121 @@
---
title: SeaweedFS
icon: /images/seaweedfs-logo.svg
description: Set up SeaweedFS as a MIRAGE resource from Node or the browser.
---
SeaweedFS speaks the S3 API with AWS Signature V4 against its S3 gateway endpoint (e.g. `http://localhost:8333`). The gateway is self-hosted, so the endpoint is required and path-style addressing is used by default.
Credentials (access key + secret key from the gateway's `-s3.config`) are created the same way in both runtimes, see [SeaweedFS Credentials](/home/setup/seaweedfs).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { SeaweedFSResource, MountMode, Workspace } from '@struktoai/mirage-node'
const seaweedfs = new SeaweedFSResource({
bucket: process.env.SEAWEEDFS_BUCKET!,
endpoint: process.env.SEAWEEDFS_ENDPOINT!,
accessKeyId: process.env.SEAWEEDFS_ACCESS_KEY!,
secretAccessKey: process.env.SEAWEEDFS_SECRET_KEY!,
})
const ws = new Workspace({ '/bucket/': seaweedfs }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `SeaweedFSResource` is secret-free, your backend signs each operation using your SeaweedFS keys and returns a URL. SeaweedFS accepts AWS Signature V4, so `@aws-sdk/s3-request-presigner` works, pointed at your gateway endpoint.
### 1. Server: sign URLs with the SeaweedFS endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const client = new S3Client({
region: 'us-east-1',
endpoint: process.env.SEAWEEDFS_ENDPOINT,
forcePathStyle: true,
credentials: {
accessKeyId: process.env.SEAWEEDFS_ACCESS_KEY!,
secretAccessKey: process.env.SEAWEEDFS_SECRET_KEY!,
},
})
const BUCKET = process.env.SEAWEEDFS_BUCKET!
app.post('/presign/seaweedfs', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
<Warning>
SeaweedFS uses **path-style** URLs (`forcePathStyle: true`), virtual-hosted style requires extra DNS setup on a self-hosted gateway.
</Warning>
### 2. Browser: wire it up
```ts
import { SeaweedFSResource, MountMode, Workspace } from '@struktoai/mirage-browser'
const seaweedfs = new SeaweedFSResource({
bucket: 'my-bucket',
endpoint: 'http://localhost:8333',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/seaweedfs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': seaweedfs }, { mode: MountMode.READ })
```
<Tip>
`endpoint` on the browser config is only used for display/logging; the actual endpoint is baked into the presigned URLs your backend returns.
</Tip>
See the [SeaweedFS resource](/python/resource/seaweedfs) docs for the equivalent Python wiring.
+52
View File
@@ -0,0 +1,52 @@
---
title: SSH
icon: server
description: Mount a remote SSH host as a Mirage filesystem (Node only).
---
`SSHResource` opens an SFTP session and mounts the remote root as a filesystem.
<Note>
Node only. Requires the `ssh2` peer dependency. The browser SDK doesn't ship an SSH client.
</Note>
## Install
```bash
pnpm add @struktoai/mirage-node ssh2
```
## Config
```ts
import { MountMode, SSHResource, Workspace } from '@struktoai/mirage-node'
const ssh = new SSHResource({
host: 'jump.example.com',
username: 'me',
identityFile: '~/.ssh/id_ed25519',
root: '/srv/data',
})
const ws = new Workspace({ '/remote': ssh }, { mode: MountMode.READ })
await ws.execute('ls /remote')
```
| Field | Default | Notes |
| --- | --- | --- |
| `host` | required | Host alias from `~/.ssh/config` or a real DNS name. |
| `hostname` | `host` | Override if `host` is an alias and the actual DNS name differs. |
| `port` | `22` | |
| `username` | (from ssh-config) | |
| `password` | none | Redacted in snapshots. Prefer `identityFile`. |
| `identityFile` | none | Path to a private key. `~` is expanded. |
| `passphrase` | none | Passphrase for `identityFile`. Redacted in snapshots. |
| `root` | `/` | Path on the remote that becomes the mount root. |
| `timeout` | `10000` | Connect timeout in ms. |
| `knownHosts` | system | Path to a `known_hosts` file. |
## Mount mode
`read`, `write`. Writes flush via SFTP put.
For the full set of supported shell commands and snapshot behavior see the [Python SSH docs](/python/resource/ssh).
+152
View File
@@ -0,0 +1,152 @@
---
title: Supabase Storage
icon: database
description: Set up Supabase Storage as a MIRAGE resource from Node or the browser.
---
Supabase Storage exposes an S3-compatible API at `https://<project-ref>.storage.supabase.co/storage/v1/s3`, signed with AWS Signature V4 using Supabase **S3 Access Keys** (not your project anon / service-role JWT). MIRAGE derives the endpoint from your `project_ref` automatically and forces path-style URLs.
## Credentials
### 1. Create an S3 access key
1. Open the Supabase dashboard → your project → **Storage → Settings → S3 Access Keys**
2. Click **New access key**, scope to the bucket(s) you want MIRAGE to see
3. Copy the **Access key ID** and **Secret access key**, the secret is shown once
### 2. Environment variables
```bash
# .env.development
SUPABASE_BUCKET=my-bucket
SUPABASE_REGION=us-east-1
SUPABASE_PROJECT_REF=abcdefghijklmnop
SUPABASE_ACCESS_KEY_ID=...
SUPABASE_SECRET_ACCESS_KEY=...
```
<Tip>
`SUPABASE_REGION` is metadata only, Supabase Storage accepts any region string but requires one for SigV4 signing. Use the region you selected when creating the project.
</Tip>
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { MountMode, SupabaseResource, Workspace } from '@struktoai/mirage-node'
const sb = new SupabaseResource({
bucket: process.env.SUPABASE_BUCKET!,
region: process.env.SUPABASE_REGION!,
projectRef: process.env.SUPABASE_PROJECT_REF!,
accessKeyId: process.env.SUPABASE_ACCESS_KEY_ID!,
secretAccessKey: process.env.SUPABASE_SECRET_ACCESS_KEY!,
})
const ws = new Workspace({ '/bucket/': sb }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
Same pattern as every other S3-compat backend: the browser calls a `presignedUrlProvider` callback that hits your server, which signs each S3 operation against the Supabase endpoint and returns the URL. The browser `fetch()`es directly.
### 1. Server: sign URLs with the Supabase endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const PROJECT_REF = process.env.SUPABASE_PROJECT_REF!
const REGION = process.env.SUPABASE_REGION!
const client = new S3Client({
region: REGION,
endpoint: `https://${PROJECT_REF}.storage.supabase.co/storage/v1/s3`,
forcePathStyle: true,
credentials: {
accessKeyId: process.env.SUPABASE_ACCESS_KEY_ID!,
secretAccessKey: process.env.SUPABASE_SECRET_ACCESS_KEY!,
},
})
const BUCKET = process.env.SUPABASE_BUCKET!
app.post('/presign/supabase', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
<Warning>
Supabase requires **path-style** URLs (`forcePathStyle: true`). Virtual-hosted style is not supported.
</Warning>
### 2. Browser: wire it up
```ts
import { MountMode, SupabaseResource, Workspace } from '@struktoai/mirage-browser'
const sb = new SupabaseResource({
bucket: 'my-bucket',
projectRef: 'abcdefghijklmnop',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/supabase', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': sb }, { mode: MountMode.READ })
```
### 3. Configure CORS on the bucket
Supabase Storage's S3 API respects the bucket's CORS configuration, which is set in the Supabase dashboard:
1. Project → **Storage → Settings → Policies / CORS configuration**
2. Add `http://localhost:5173` (and any production origins)
3. Methods: `GET, PUT, HEAD, DELETE, POST`
4. Headers: `*`
5. Exposed headers: `ETag, Content-Length, Content-Type, Last-Modified`
See the [Supabase resource](/python/resource/supabase) docs for the equivalent Python wiring.
+122
View File
@@ -0,0 +1,122 @@
---
title: Tencent COS
icon: /images/tencent-logo.svg
description: Set up Tencent Cloud Object Storage as a MIRAGE resource from Node or the browser.
---
Tencent COS uses the S3 API with AWS Signature V4 against `https://cos.<region>.myqcloud.com`. MIRAGE derives this endpoint from your region automatically. COS bucket names include the APPID suffix (e.g. `my-bucket-1250000000`).
Credentials (CAM `SecretId` as the access key and `SecretKey` as the secret) are created the same way in both runtimes, see [Tencent COS Credentials](/home/setup/tencent).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { MountMode, TencentResource, Workspace } from '@struktoai/mirage-node'
const cos = new TencentResource({
bucket: process.env.COS_BUCKET!,
region: process.env.COS_REGION!,
accessKeyId: process.env.COS_SECRET_ID!,
secretAccessKey: process.env.COS_SECRET_KEY!,
})
const ws = new Workspace({ '/bucket/': cos }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `TencentResource` is secret-free, your backend signs each operation using your CAM keys and returns a URL. COS accepts AWS Signature V4, so `@aws-sdk/s3-request-presigner` works, pointed at the COS endpoint.
### 1. Server: sign URLs with the COS endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const REGION = process.env.COS_REGION!
const client = new S3Client({
region: REGION,
endpoint: `https://cos.${REGION}.myqcloud.com`,
credentials: {
accessKeyId: process.env.COS_SECRET_ID!,
secretAccessKey: process.env.COS_SECRET_KEY!,
},
})
const BUCKET = process.env.COS_BUCKET!
app.post('/presign/cos', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
### 2. Browser: wire it up
```ts
import { MountMode, TencentResource, Workspace } from '@struktoai/mirage-browser'
const cos = new TencentResource({
bucket: 'my-bucket-1250000000',
region: 'ap-guangzhou',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/cos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': cos }, { mode: MountMode.READ })
```
<Tip>
`region` on the browser config is only used for display/logging; the actual endpoint is baked into the presigned URLs your backend returns.
</Tip>
### 3. CORS
Configure CORS rules in the Tencent Cloud console: COS -> your bucket -> **Security Management** -> **CORS**. Allow your dev/production origins with methods `GET, PUT, HEAD, DELETE, POST`, allowed headers `*`, and expose headers `ETag, Content-Length, Content-Type, Last-Modified`.
See the [Tencent resource](/python/resource/tencent) docs for the equivalent Python wiring.
+58
View File
@@ -0,0 +1,58 @@
---
title: Trello
icon: trello
description: Mount Trello boards, lists, and cards as a Mirage filesystem.
---
`TrelloResource` exposes a Trello workspace as a tree of boards, lists, and cards.
Setup steps for the API key/token live at [Trello Credentials](/home/setup/trello).
## Node
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { MountMode, TrelloResource, Workspace } from '@struktoai/mirage-node'
const trello = new TrelloResource({
apiKey: process.env.TRELLO_API_KEY!,
apiToken: process.env.TRELLO_API_TOKEN!,
})
const ws = new Workspace({ '/trello': trello }, { mode: MountMode.READ })
await ws.execute('ls /trello/')
```
## Browser
```bash
pnpm add @struktoai/mirage-browser
```
```ts
import { MountMode, TrelloResource, Workspace } from '@struktoai/mirage-browser'
const trello = new TrelloResource({
apiKey: TRELLO_API_KEY,
apiToken: TRELLO_API_TOKEN,
})
```
## Config
| Field | Default | Notes |
| --- | --- | --- |
| `apiKey` | required | Trello API key. Redacted in snapshots. |
| `apiToken` | required | Trello API token. Redacted in snapshots. |
| `workspaceId` | none | Restrict to a single workspace. |
| `boardIds` | none | Optional allowlist of board IDs. |
| `baseUrl` | `https://api.trello.com/1` | Override for Enterprise instances. |
## Mount mode
`read`.
For board/list/card layout see the [Python Trello docs](/python/resource/trello).
+132
View File
@@ -0,0 +1,132 @@
---
title: Wasabi
icon: /images/wasabi-logo.svg
description: Set up Wasabi as a MIRAGE resource from Node or the browser.
---
Wasabi uses the S3 API with AWS Signature V4 against `https://s3.<region>.wasabisys.com` (`https://s3.wasabisys.com` for `us-east-1`). MIRAGE derives this endpoint from your region automatically.
Credentials (Wasabi access key pair) are created the same way in both runtimes, see [Wasabi Credentials](/home/setup/wasabi).
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { MountMode, WasabiResource, Workspace } from '@struktoai/mirage-node'
const wasabi = new WasabiResource({
bucket: process.env.WASABI_BUCKET!,
region: process.env.WASABI_REGION!,
accessKeyId: process.env.WASABI_ACCESS_KEY_ID!,
secretAccessKey: process.env.WASABI_SECRET_ACCESS_KEY!,
})
const ws = new Workspace({ '/bucket/': wasabi }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```
## Browser (presigned URLs)
```bash
pnpm add @struktoai/mirage-browser
```
The browser `WasabiResource` is secret-free, your backend signs each operation using your Wasabi keys and returns a URL. Wasabi accepts AWS Signature V4, so `@aws-sdk/s3-request-presigner` works, pointed at the Wasabi endpoint.
### 1. Server: sign URLs with the Wasabi endpoint
```ts
import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const REGION = process.env.WASABI_REGION!
const client = new S3Client({
region: REGION,
endpoint: `https://s3.${REGION}.wasabisys.com`,
credentials: {
accessKeyId: process.env.WASABI_ACCESS_KEY_ID!,
secretAccessKey: process.env.WASABI_SECRET_ACCESS_KEY!,
},
})
const BUCKET = process.env.WASABI_BUCKET!
app.post('/presign/wasabi', async (req, res) => {
const { path, op, opts } = req.body
const key = path.replace(/^\/+/, '')
const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
let cmd
switch (op) {
case 'GET': cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'PUT': cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
case 'HEAD': cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
case 'LIST': cmd = new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: opts?.listPrefix,
Delimiter: opts?.listDelimiter,
ContinuationToken: opts?.listContinuationToken,
}); break
case 'COPY': cmd = new CopyObjectCommand({
Bucket: BUCKET,
Key: key,
CopySource: `${BUCKET}/${opts?.copySource}`,
}); break
}
res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```
<Note>
For `us-east-1` the endpoint is `https://s3.wasabisys.com` (no region segment).
</Note>
### 2. Browser: wire it up
```ts
import { MountMode, WasabiResource, Workspace } from '@struktoai/mirage-browser'
const wasabi = new WasabiResource({
bucket: 'my-bucket',
region: 'us-east-2',
presignedUrlProvider: async (path, op, opts) => {
const r = await fetch('/presign/wasabi', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, op, opts }),
})
const { url } = await r.json()
return url
},
})
const ws = new Workspace({ '/bucket/': wasabi }, { mode: MountMode.READ })
```
<Tip>
`region` on the browser config is only used for display/logging; the actual endpoint is baked into the presigned URLs your backend returns.
</Tip>
### 3. CORS
Wasabi supports the S3 `PutBucketCors` call, so the AWS CLI works against the Wasabi endpoint:
```bash
aws s3api put-bucket-cors --bucket "$WASABI_BUCKET" \
--endpoint-url "https://s3.$WASABI_REGION.wasabisys.com" \
--cors-configuration '{"CORSRules":[{"AllowedOrigins":["http://localhost:5173","https://app.example.com"],"AllowedMethods":["GET","PUT","HEAD","DELETE","POST"],"AllowedHeaders":["*"],"ExposeHeaders":["ETag","Content-Length","Content-Type","Last-Modified"]}]}'
```
See the [Wasabi resource](/python/resource/wasabi) docs for the equivalent Python wiring.