Initial public release: Mirage v0.0.1-alpha.1
A unified virtual filesystem for AI agents. Mount S3, Google Drive, Slack, Gmail, GitHub, Linear, Notion, Postgres, MongoDB, SSH, and more behind one filesystem so agents read, write, and pipe across services with familiar shell commands. Ships Python (mirage-ai) and TypeScript (@struktoai/mirage-*) SDKs, a CLI, FUSE mounts, and adapters for OpenAI Agents SDK, Vercel AI SDK, LangChain deepagents, Pydantic AI, CAMEL, OpenHands, Mastra, Pi Coding Agent, plus FUSE-based integration with Claude Code and Codex. Apache 2.0 licensed.
@@ -0,0 +1,34 @@
|
||||
R2_ACCOUNT_ID=
|
||||
R2_BUCKET=
|
||||
R2_ACCESS_KEY_ID=
|
||||
R2_SECRET_ACCESS_KEY=
|
||||
R2_REGION=auto
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_S3_BUCKET=mirage-ai-test
|
||||
|
||||
OPENAI_API_KEY=
|
||||
OPENAI_MODEL=gpt-5-mini
|
||||
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
|
||||
TRELLO_API_KEY=
|
||||
TRELLO_API_TOKEN=
|
||||
|
||||
NOTION_API_KEY=
|
||||
|
||||
IMAP_HOST=
|
||||
SMTP_HOST=
|
||||
EMAIL_USERNAME=
|
||||
EMAIL_PASSWORD=
|
||||
|
||||
DROPBOX_APP_KEY=
|
||||
DROPBOX_APP_SECRET=
|
||||
DROPBOX_REFRESH_TOKEN=
|
||||
|
||||
BOX_CLIENT_ID=
|
||||
BOX_CLIENT_SECRET=
|
||||
BOX_REFRESH_TOKEN=
|
||||
BOX_DEVELOPER_TOKEN=
|
||||
@@ -0,0 +1,242 @@
|
||||
name: Examples
|
||||
|
||||
"on":
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
examples-ram:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.32.1
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: pnpm
|
||||
cache-dependency-path: typescript/pnpm-lock.yaml
|
||||
|
||||
- name: Install Python dependencies
|
||||
working-directory: python
|
||||
run: uv sync --all-extras --no-extra camel
|
||||
|
||||
- name: Install TypeScript dependencies
|
||||
working-directory: typescript
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build TypeScript packages
|
||||
working-directory: typescript
|
||||
run: pnpm -r build
|
||||
|
||||
- name: Run RAM example (Python)
|
||||
run: |
|
||||
output=$(./python/.venv/bin/python examples/python/ram/ram.py 2>&1)
|
||||
echo "$output"
|
||||
echo "$output" | grep -q "hello world" || (echo "FAIL: missing 'hello world'" && exit 1)
|
||||
echo "$output" | grep -q "hello.txt" || (echo "FAIL: missing 'hello.txt'" && exit 1)
|
||||
echo "$output" | grep -q '"alice"' || (echo "FAIL: missing jq output" && exit 1)
|
||||
echo "$output" | grep -q "goodbye world" || (echo "FAIL: missing sed output" && exit 1)
|
||||
|
||||
- name: Run RAM example (TypeScript)
|
||||
run: |
|
||||
output=$(pnpm -C examples/typescript exec tsx ram/ram.ts 2>&1)
|
||||
echo "$output"
|
||||
echo "$output" | grep -q "hello world" || (echo "FAIL: missing 'hello world'" && exit 1)
|
||||
echo "$output" | grep -q "hello.txt" || (echo "FAIL: missing 'hello.txt'" && exit 1)
|
||||
echo "$output" | grep -q '"alice"' || (echo "FAIL: missing jq output" && exit 1)
|
||||
echo "$output" | grep -q "goodbye world" || (echo "FAIL: missing sed output" && exit 1)
|
||||
|
||||
- name: Run RAM VFS example (Python)
|
||||
run: |
|
||||
output=$(./python/.venv/bin/python examples/python/ram/ram_vfs.py 2>&1)
|
||||
echo "$output"
|
||||
echo "$output" | grep -q "hello world" || (echo "FAIL: missing 'hello world'" && exit 1)
|
||||
echo "$output" | grep -qi "hello.txt: true" || (echo "FAIL: missing exists check" && exit 1)
|
||||
echo "$output" | grep -qi "nope.txt: false" || (echo "FAIL: missing not-exists check" && exit 1)
|
||||
|
||||
- name: Run RAM VFS example (TypeScript)
|
||||
run: |
|
||||
output=$(pnpm -C examples/typescript exec tsx ram/ram_vfs.ts 2>&1)
|
||||
echo "$output"
|
||||
echo "$output" | grep -q "hello world" || (echo "FAIL: missing 'hello world'" && exit 1)
|
||||
echo "$output" | grep -qi "hello.txt: true" || (echo "FAIL: missing exists check" && exit 1)
|
||||
echo "$output" | grep -qi "nope.txt: false" || (echo "FAIL: missing not-exists check" && exit 1)
|
||||
|
||||
examples-disk:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.32.1
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: pnpm
|
||||
cache-dependency-path: typescript/pnpm-lock.yaml
|
||||
|
||||
- name: Install Python dependencies
|
||||
working-directory: python
|
||||
run: uv sync --all-extras --no-extra camel
|
||||
|
||||
- name: Install TypeScript dependencies
|
||||
working-directory: typescript
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build TypeScript packages
|
||||
working-directory: typescript
|
||||
run: pnpm -r build
|
||||
|
||||
- name: Run Disk example (Python)
|
||||
run: |
|
||||
output=$(./python/.venv/bin/python examples/python/disk/disk.py 2>&1)
|
||||
echo "$output"
|
||||
echo "$output" | grep -q "Strukto" || (echo "FAIL: missing 'Strukto' from JSON" && exit 1)
|
||||
echo "$output" | grep -q "example.json" || (echo "FAIL: missing file listing" && exit 1)
|
||||
|
||||
- name: Run Disk example (TypeScript)
|
||||
run: |
|
||||
output=$(pnpm -C examples/typescript exec tsx disk/disk.ts 2>&1)
|
||||
echo "$output"
|
||||
echo "$output" | grep -q "Strukto" || (echo "FAIL: missing 'Strukto' from JSON" && exit 1)
|
||||
echo "$output" | grep -q "example.json" || (echo "FAIL: missing file listing" && exit 1)
|
||||
|
||||
- name: Run Disk VFS example (Python)
|
||||
run: |
|
||||
output=$(./python/.venv/bin/python examples/python/disk/disk_vfs.py 2>&1)
|
||||
echo "$output"
|
||||
echo "$output" | grep -q "example.json" || (echo "FAIL: missing file listing" && exit 1)
|
||||
|
||||
examples-redis:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
redis:
|
||||
image: redis:7
|
||||
ports:
|
||||
- 6379:6379
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.32.1
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: pnpm
|
||||
cache-dependency-path: typescript/pnpm-lock.yaml
|
||||
|
||||
- name: Install Python dependencies
|
||||
working-directory: python
|
||||
run: uv sync --all-extras --no-extra camel
|
||||
|
||||
- name: Install TypeScript dependencies
|
||||
working-directory: typescript
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build TypeScript packages
|
||||
working-directory: typescript
|
||||
run: pnpm -r build
|
||||
|
||||
- name: Run Redis example (Python)
|
||||
run: |
|
||||
output=$(./python/.venv/bin/python examples/python/redis_resource/example_redis.py 2>&1)
|
||||
echo "$output"
|
||||
echo "$output" | grep -q "hello world" || (echo "FAIL: missing 'hello world'" && exit 1)
|
||||
echo "$output" | grep -q "hello.txt" || (echo "FAIL: missing 'hello.txt'" && exit 1)
|
||||
echo "$output" | grep -q '"alice"' || (echo "FAIL: missing jq output" && exit 1)
|
||||
echo "$output" | grep -q "goodbye world" || (echo "FAIL: missing sed output" && exit 1)
|
||||
|
||||
- name: Run Redis example (TypeScript)
|
||||
env:
|
||||
REDIS_URL: redis://localhost:6379/0
|
||||
run: |
|
||||
output=$(pnpm -C examples/typescript exec tsx redis/redis.ts 2>&1)
|
||||
echo "$output"
|
||||
echo "$output" | grep -q "hello world" || (echo "FAIL: missing 'hello world'" && exit 1)
|
||||
echo "$output" | grep -q "hello.txt" || (echo "FAIL: missing 'hello.txt'" && exit 1)
|
||||
echo "$output" | grep -q '"alice"' || (echo "FAIL: missing jq output" && exit 1)
|
||||
echo "$output" | grep -q "goodbye world" || (echo "FAIL: missing sed output" && exit 1)
|
||||
|
||||
- name: Run Redis VFS example (Python)
|
||||
run: ./python/.venv/bin/python examples/python/redis_resource/example_redis_vfs.py
|
||||
|
||||
examples-python-fs-shim:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.32.1
|
||||
|
||||
- name: Set up Node 24 (for --experimental-wasm-jspi)
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: pnpm
|
||||
cache-dependency-path: typescript/pnpm-lock.yaml
|
||||
|
||||
- name: Install TypeScript dependencies
|
||||
working-directory: typescript
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build TypeScript packages
|
||||
working-directory: typescript
|
||||
run: pnpm -r --filter './packages/*' build
|
||||
|
||||
- name: Run Python FS shim example (no-creds smoke test)
|
||||
working-directory: examples/typescript
|
||||
run: |
|
||||
output=$(node --experimental-wasm-jspi --import tsx/esm python/vfs.ts 2>&1)
|
||||
echo "$output"
|
||||
# Demos that always run (no creds needed): RAM read+write, lazy-on-miss, PIL save.
|
||||
echo "$output" | grep -q "host sees: written from python" || (echo "FAIL: RAM write did not flush" && exit 1)
|
||||
echo "$output" | grep -q "listdir: \['note.md'\]" || (echo "FAIL: lazy-on-miss listdir failed" && exit 1)
|
||||
echo "$output" | grep -q "read: lazy demo" || (echo "FAIL: lazy-on-miss read failed" && exit 1)
|
||||
echo "$output" | grep -q "PNG magic: 89 50 4e 47" || (echo "FAIL: PIL save did not produce a PNG" && exit 1)
|
||||
@@ -0,0 +1,48 @@
|
||||
name: Pre-commit
|
||||
|
||||
"on":
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.32.1
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: pnpm
|
||||
cache-dependency-path: typescript/pnpm-lock.yaml
|
||||
|
||||
- name: Install Python dependencies
|
||||
working-directory: python
|
||||
run: uv sync --all-extras --no-extra camel
|
||||
|
||||
- name: Install TypeScript dependencies
|
||||
working-directory: typescript
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build TypeScript packages
|
||||
working-directory: typescript
|
||||
run: pnpm -r build
|
||||
|
||||
- name: Run pre-commit
|
||||
run: SKIP=no-commit-to-branch ./python/.venv/bin/pre-commit run --all-files
|
||||
@@ -0,0 +1,78 @@
|
||||
name: Test
|
||||
|
||||
"on":
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
redis:
|
||||
image: redis:7
|
||||
ports:
|
||||
- 6379:6379
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.32.1
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: pnpm
|
||||
cache-dependency-path: typescript/pnpm-lock.yaml
|
||||
|
||||
- name: Install Python dependencies
|
||||
working-directory: python
|
||||
run: uv sync --all-extras --no-extra camel
|
||||
|
||||
- name: Install TypeScript dependencies
|
||||
working-directory: typescript
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install package
|
||||
working-directory: python
|
||||
run: uv pip install .
|
||||
|
||||
- name: Run pytest (main suite, camel excluded)
|
||||
working-directory: python
|
||||
env:
|
||||
REDIS_URL: redis://localhost:6379/0
|
||||
run: uv run pytest --ignore=tests/agents/camel
|
||||
|
||||
- name: Sync Python dependencies for camel
|
||||
working-directory: python
|
||||
run: uv sync --extra camel
|
||||
|
||||
- name: Run pytest (camel toolkit tests)
|
||||
working-directory: python
|
||||
run: uv run pytest tests/agents/camel
|
||||
|
||||
- name: Build TypeScript packages
|
||||
working-directory: typescript
|
||||
run: pnpm -r build
|
||||
|
||||
- name: Run TypeScript tests
|
||||
working-directory: typescript
|
||||
run: pnpm test
|
||||
@@ -0,0 +1,231 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[codz]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
/lib/
|
||||
/lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
#poetry.toml
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||
#pdm.lock
|
||||
#pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# pixi
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||
#pixi.lock
|
||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||
.pixi
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.env.development
|
||||
.envrc
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Abstra
|
||||
# Abstra is an AI-powered process automation framework.
|
||||
# Ignore directories containing user credentials, local state, and settings.
|
||||
# Learn more at https://abstra.io/docs
|
||||
.abstra/
|
||||
|
||||
# Visual Studio Code
|
||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||
# you could uncomment the following to ignore the entire vscode folder
|
||||
# .vscode/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# Cursor
|
||||
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
||||
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
||||
# refer to https://docs.cursor.com/context/ignore-files
|
||||
.cursorignore
|
||||
.cursorindexingignore
|
||||
|
||||
# Marimo
|
||||
marimo/_static/
|
||||
marimo/_lsp/
|
||||
__marimo__/
|
||||
|
||||
.DS_Store
|
||||
|
||||
# Audio models
|
||||
models/
|
||||
|
||||
# Internal docs
|
||||
.docs/
|
||||
|
||||
# Git worktrees
|
||||
.worktrees/
|
||||
|
||||
# Local symlink for the TS CLI bin (not committed)
|
||||
/mirage-ts
|
||||
|
||||
# OG image generator (local only, lint-noisy)
|
||||
/scripts/generate_og_images.py
|
||||
|
||||
# Local-only working dirs (untracked but kept on disk)
|
||||
/.claude/
|
||||
/logo/
|
||||
/paper/
|
||||
/plan/
|
||||
@@ -0,0 +1,2 @@
|
||||
[settings]
|
||||
known_first_party = mirage,tests
|
||||
@@ -0,0 +1,124 @@
|
||||
ci:
|
||||
# https://pre-commit.ci/#configuration
|
||||
autofix_prs: true
|
||||
autoupdate_commit_msg: '[pre-commit.ci] pre-commit suggestions'
|
||||
autoupdate_schedule: monthly
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: no-commit-to-branch
|
||||
name: No commits to master
|
||||
- id: end-of-file-fixer
|
||||
name: End-of-file fixer
|
||||
exclude: ^typescript/
|
||||
- name: mixed-line-ending
|
||||
id: mixed-line-ending
|
||||
args: [--fix, lf]
|
||||
exclude: ^typescript/
|
||||
- id: trailing-whitespace
|
||||
name: Remove trailing whitespaces
|
||||
exclude: ^typescript/
|
||||
- id: check-toml
|
||||
name: Check toml
|
||||
- id: check-yaml
|
||||
name: Check yaml
|
||||
exclude: |
|
||||
(?x)^(
|
||||
conda/pytorch-geometric/meta.yaml|
|
||||
conda/pyg/meta.yaml|
|
||||
typescript/.*
|
||||
)$
|
||||
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.37.1
|
||||
hooks:
|
||||
- id: yamllint
|
||||
name: Lint yaml
|
||||
args: [-d, '{extends: default, rules: {line-length: disable, document-start: disable, truthy: {level: error}, braces: {max-spaces-inside: 1}}}']
|
||||
exclude: ^typescript/
|
||||
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.21.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
name: Upgrade Python syntax
|
||||
args: [--py38-plus]
|
||||
|
||||
- repo: https://github.com/PyCQA/autoflake
|
||||
rev: v2.3.1
|
||||
hooks:
|
||||
- id: autoflake
|
||||
name: Remove unused imports and variables
|
||||
args: [
|
||||
--remove-all-unused-imports,
|
||||
--remove-unused-variables,
|
||||
--remove-duplicate-keys,
|
||||
--ignore-init-module-imports,
|
||||
--in-place,
|
||||
]
|
||||
|
||||
- repo: https://github.com/google/yapf
|
||||
rev: v0.43.0
|
||||
hooks:
|
||||
- id: yapf
|
||||
name: Format code
|
||||
additional_dependencies: [toml]
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 7.0.0
|
||||
hooks:
|
||||
- id: isort
|
||||
name: Sort imports
|
||||
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 7.3.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
name: Check PEP8
|
||||
args: [--toml-config=python/pyproject.toml]
|
||||
additional_dependencies: [Flake8-pyproject]
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.14.3
|
||||
hooks:
|
||||
- id: ruff
|
||||
name: Ruff formatting
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
- repo: https://github.com/executablebooks/mdformat
|
||||
rev: 0.7.22
|
||||
hooks:
|
||||
- id: mdformat
|
||||
name: Format Markdown
|
||||
additional_dependencies:
|
||||
- mdformat-gfm
|
||||
- mdformat_frontmatter
|
||||
- mdformat_footnote
|
||||
exclude: |
|
||||
(?x)^(
|
||||
test/data/cursor\.md|
|
||||
typescript/.*
|
||||
)$
|
||||
|
||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||
rev: v1.0.1
|
||||
hooks:
|
||||
- id: sphinx-lint
|
||||
name: Check Sphinx
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: ts-prettier
|
||||
name: Prettier (typescript)
|
||||
entry: typescript/scripts/precommit-run.sh prettier --write
|
||||
language: system
|
||||
files: ^typescript/.*\.(ts|tsx|js|mjs|cjs|json|md|yaml|yml)$
|
||||
pass_filenames: true
|
||||
- id: ts-eslint
|
||||
name: ESLint (typescript)
|
||||
entry: typescript/scripts/precommit-run.sh eslint --fix
|
||||
language: system
|
||||
files: ^typescript/.*\.(ts|tsx|js|mjs|cjs)$
|
||||
pass_filenames: true
|
||||
@@ -0,0 +1,78 @@
|
||||
# AGENTS.md
|
||||
|
||||
MIRAGE is a package that allows you to mount anything as a filesystem and make it usable by AI Agents.
|
||||
|
||||
## Repo Layout
|
||||
|
||||
This monorepo hosts two sibling implementations:
|
||||
|
||||
- `python/` — the Python package (`mirage/`, `tests/`, `pyproject.toml`, `uv.lock`).
|
||||
- `typescript/` — the TypeScript monorepo (`packages/core`, `packages/node`, etc.).
|
||||
- `docs/`, `examples/`, `.github/` — shared across both.
|
||||
|
||||
Run Python commands from `python/`, TypeScript commands from `typescript/`.
|
||||
|
||||
## Development Setup
|
||||
|
||||
This project uses `uv` for Python dependency management. Install dependencies with:
|
||||
|
||||
```bash
|
||||
cd python && uv sync --all-extras
|
||||
```
|
||||
|
||||
### Running examples
|
||||
|
||||
Examples under `examples/python/` load `.env.development` from the repo root (cwd-relative). To keep cwd at the root while using the `python/` venv, invoke the venv interpreter directly:
|
||||
|
||||
```bash
|
||||
./python/.venv/bin/python examples/python/s3/s3.py
|
||||
```
|
||||
|
||||
Avoid `uv --directory python run ...` for examples — it changes cwd to `python/` and breaks `load_dotenv(".env.development")`.
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
- No need to consider backward compatibility for the code.
|
||||
|
||||
## Create a PR
|
||||
|
||||
When asked to create a PR, please follow the following steps:
|
||||
|
||||
1. Run `pre-commit run --all-files` from the repo root to lint and format the code.
|
||||
1. Run `cd python && uv run pytest` to run the Python tests.
|
||||
1. Run `git add -A` to add all changes.
|
||||
1. Run `git checkout -b <branch-name>` to create a new branch.
|
||||
1. Run `git commit -m "<commit-message>"` to commit the changes.
|
||||
1. Run `git push origin <branch-name>` to push the changes to the remote repository.
|
||||
1. Run `gh pr create --title "<pr-title>" --body "<pr-body>"` to create a PR.
|
||||
|
||||
## Commands
|
||||
|
||||
### Linting and Formatting
|
||||
|
||||
After making major changes, run pre-commit from the repo root to ensure code quality:
|
||||
|
||||
```bash
|
||||
./python/.venv/bin/pre-commit run --all-files
|
||||
```
|
||||
|
||||
Invoke the venv's `pre-commit` binary directly (not via `uv --directory python run`) so cwd stays at the repo root — otherwise `git ls-files` only lists files under `python/` and `examples/` gets silently skipped.
|
||||
|
||||
## Type Conventions
|
||||
|
||||
- Paths must always be represented as `PathSpec`, never raw strings. All functions that accept or return paths use `list[str | PathSpec]` where `str` is for text arguments and `PathSpec` is for paths. Never pass a path as a plain `str` — wrap it in `PathSpec`.
|
||||
|
||||
## Rules
|
||||
|
||||
- Avoid add any comments or docstrings on the top of the file.
|
||||
- Do not create nested functions.
|
||||
- Add type to Args for docstring.
|
||||
- Do not add comment after each line of code in the format of "# 10MB - trigger segmentation for files larger than this". The most you can add is "# 10MB".
|
||||
- For all imports you need to put to the top of the file. Don't have imports within each function.
|
||||
- **No circular imports.** If putting an import at the top would cause a cycle, that's a sign the dependency direction is wrong — fix the design (dependency injection, splitting modules, moving the shared piece to a leaf), don't paper over it with function-local lazy imports. Verify by checking that running `cd python && uv run python -c "import <every changed module>"` succeeds without ImportError.
|
||||
- **Never silently swallow exceptions.** `try: ... except: pass` (or `except SomeError: pass`) hides real bugs. If you genuinely need to ignore an error, log it (`logger.debug(...)`) or document loudly why it's safe. Default behavior should be: let the exception propagate. Especially never silently swallow `RuntimeError` — it usually signals something deeper (event loop in wrong state, recursion limit, etc.) that you need to actually fix.
|
||||
- **Never call `asyncio.run()` inside a sync function that might be invoked under an outer event loop.** It will raise `RuntimeError: asyncio.run() cannot be called from a running event loop`. If you need async behavior from a sync API, either: (a) make the calling function `async`, (b) operate on the underlying sync state directly (e.g. write to a dict instead of calling an async setter), or (c) use a sync alternative of the same library (e.g. `redis.Redis` instead of `redis.asyncio.Redis`). Do NOT wrap with `try/except RuntimeError: pass` — that masks the bug AND leaks the unawaited coroutine.
|
||||
- Please don't change any file name unless I ask you to do so.
|
||||
- Don't add too many printings or comments in the code.
|
||||
- Don't add README.md unless I ask you to do so.
|
||||
- Use uv add to install new dependencies.
|
||||
@@ -0,0 +1,80 @@
|
||||
# CLAUDE.md
|
||||
|
||||
MIRAGE is a package that allows you to mount anything as a filesystem and make it usable by AI Agents.
|
||||
|
||||
## Repo Layout
|
||||
|
||||
This monorepo hosts two sibling implementations:
|
||||
|
||||
- `python/` — the Python package (`mirage/`, `tests/`, `pyproject.toml`, `uv.lock`).
|
||||
- `typescript/` — the TypeScript monorepo (`packages/core`, `packages/node`, etc.).
|
||||
- `docs/`, `examples/`, `.github/` — shared across both.
|
||||
|
||||
Run Python commands from `python/`, TypeScript commands from `typescript/`.
|
||||
|
||||
## Development Setup
|
||||
|
||||
This project uses `uv` for Python dependency management. Install dependencies with:
|
||||
|
||||
```bash
|
||||
cd python && uv sync --all-extras --no-extra camel
|
||||
```
|
||||
|
||||
`camel` is declared as conflicting with `openai` (and other extras) in `pyproject.toml`, so `uv sync --all-extras` fails. Exclude `camel` to keep the `openai` stack.
|
||||
|
||||
### Running examples
|
||||
|
||||
Examples under `examples/python/` load `.env.development` from the repo root (cwd-relative). To keep cwd at the root while using the `python/` venv, invoke the venv interpreter directly:
|
||||
|
||||
```bash
|
||||
./python/.venv/bin/python examples/python/s3/s3.py
|
||||
```
|
||||
|
||||
Avoid `uv --directory python run ...` for examples — it changes cwd to `python/` and breaks `load_dotenv(".env.development")`.
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
- No need to consider backward compatibility for the code.
|
||||
|
||||
## Create a PR
|
||||
|
||||
When asked to create a PR, please follow the following steps:
|
||||
|
||||
1. Run `pre-commit run --all-files` from the repo root to lint and format the code.
|
||||
1. Run `cd python && uv run pytest` to run the Python tests.
|
||||
1. Run `git add -A` to add all changes.
|
||||
1. Run `git checkout -b <branch-name>` to create a new branch.
|
||||
1. Run `git commit -m "<commit-message>"` to commit the changes.
|
||||
1. Run `git push origin <branch-name>` to push the changes to the remote repository.
|
||||
1. Run `gh pr create --title "<pr-title>" --body "<pr-body>"` to create a PR.
|
||||
|
||||
## Commands
|
||||
|
||||
### Linting and Formatting
|
||||
|
||||
After making major changes, run pre-commit from the repo root to ensure code quality:
|
||||
|
||||
```bash
|
||||
./python/.venv/bin/pre-commit run --all-files
|
||||
```
|
||||
|
||||
Invoke the venv's `pre-commit` binary directly (not via `uv --directory python run`) so cwd stays at the repo root — otherwise `git ls-files` only lists files under `python/` and `examples/` gets silently skipped.
|
||||
|
||||
## Type Conventions
|
||||
|
||||
- Paths must always be represented as `PathSpec`, never raw strings. All functions that accept or return paths use `list[str | PathSpec]` where `str` is for text arguments and `PathSpec` is for paths. Never pass a path as a plain `str` — wrap it in `PathSpec`.
|
||||
|
||||
## Rules
|
||||
|
||||
- Avoid add any comments or docstrings on the top of the file.
|
||||
- Do not create nested functions.
|
||||
- Add type to Args for docstring.
|
||||
- Do not add comment after each line of code in the format of "# 10MB - trigger segmentation for files larger than this". The most you can add is "# 10MB".
|
||||
- For all imports you need to put to the top of the file. Don't have imports within each function.
|
||||
- **No circular imports.** If putting an import at the top would cause a cycle, that's a sign the dependency direction is wrong — fix the design (dependency injection, splitting modules, moving the shared piece to a leaf), don't paper over it with function-local lazy imports. Verify by checking that running `cd python && uv run python -c "import <every changed module>"` succeeds without ImportError.
|
||||
- **Never silently swallow exceptions.** `try: ... except: pass` (or `except SomeError: pass`) hides real bugs. If you genuinely need to ignore an error, log it (`logger.debug(...)`) or document loudly why it's safe. Default behavior should be: let the exception propagate. Especially never silently swallow `RuntimeError` — it usually signals something deeper (event loop in wrong state, recursion limit, etc.) that you need to actually fix.
|
||||
- **Never call `asyncio.run()` inside a sync function that might be invoked under an outer event loop.** It will raise `RuntimeError: asyncio.run() cannot be called from a running event loop`. If you need async behavior from a sync API, either: (a) make the calling function `async`, (b) operate on the underlying sync state directly (e.g. write to a dict instead of calling an async setter), or (c) use a sync alternative of the same library (e.g. `redis.Redis` instead of `redis.asyncio.Redis`). Do NOT wrap with `try/except RuntimeError: pass` — that masks the bug AND leaks the unawaited coroutine.
|
||||
- Please don't change any file name unless I ask you to do so.
|
||||
- Don't add too many printings or comments in the code.
|
||||
- Don't add README.md unless I ask you to do so.
|
||||
- Use uv add to install new dependencies.
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2026 @ Strukto.AI
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,230 @@
|
||||
<p align="center">
|
||||
<img src="assets/mirage-og-light@2x.png" alt="Mirage: A Unified Virtual File System for AI Agents" width="900">
|
||||
</p>
|
||||
|
||||
Mirage is **a Unified Virtual File System for AI Agents**: a single tree that mounts services and data sources like S3, Google Drive, Slack, Gmail, and Redis side-by-side as one filesystem.
|
||||
|
||||
AI agents reach every backend with the same handful of Unix-like tools, and pipelines compose across services as naturally as on a local disk. It's a simulated environment, agents see one filesystem underneath. Any LLM that already knows bash can use Mirage out of the box, with zero new vocabulary.
|
||||
|
||||
```ts
|
||||
const ws = new Workspace({
|
||||
'/data': new RAMResource(),
|
||||
'/s3': new S3Resource({ bucket: 'logs' }),
|
||||
'/slack': new SlackResource({}),
|
||||
'/github': new GitHubResource({}),
|
||||
})
|
||||
|
||||
await ws.execute('grep alert /slack/general/*.json | wc -l')
|
||||
await ws.execute('cat /github/mirage/README.md')
|
||||
await ws.execute('cp /s3/report.csv /data/local.csv')
|
||||
```
|
||||
|
||||
## About
|
||||
|
||||
- **One filesystem, every backend.** Every service speaks the same filesystem semantics, so agents reason about one abstraction instead of N SDKs and M MCPs, leaning on the filesystem and bash vocabulary LLMs are most fluent in.
|
||||
- **Multiple resources, one filesystem:** RAM, Disk, Redis, S3 / R2 / OCI / Supabase / GCS, Gmail / GDrive / GDocs / GSheets / GSlides, GitHub / Linear / Notion / Trello, Slack / Discord / Telegram / Email, MongoDB, SSH, and more, mounted side-by-side under a single root.
|
||||
- **Familiar bash tools across every mount.** Agents reuse the same handful of Unix-like tools instead of learning a new API per service, and pipelines compose across services as naturally as on a local disk, the exact corpus modern LLMs are most heavily trained on.
|
||||
- **Portable workspaces:** clone, snapshot, and version your environment. Move agent runs between machines without restarting or reconfiguring the system.
|
||||
- **Embed in your apps and services:** Python and TypeScript SDKs let you give your AI agents a virtual filesystem directly inside FastAPI, Express, browser apps, or any async runtime, no separate process required. Clone, snapshot, and version the workspace from inside your code.
|
||||
- **Works with major agent application frameworks:** OpenAI Agents SDK, Vercel AI SDK (TypeScript), LangChain, Pydantic AI, CAMEL, and OpenHands.
|
||||
- **Lightweight CLI + daemon:** plugs into coding agents like Claude Code and Codex so they reach every mounted resource through familiar bash, getting more useful work done per turn.
|
||||
|
||||
## Architecture
|
||||
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="assets/mirage-arch-dark.svg">
|
||||
<img src="assets/mirage-arch-light.svg" alt="Mirage architecture: AI Agent and Application → Mirage Bash and VFS → Dispatcher & Cache → Infrastructure and Remote" width="900">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Python** ≥ 3.12 for the `mirage-ai` package and the `mirage` CLI
|
||||
- **Node.js** ≥ 20 for the TypeScript SDK
|
||||
- **macOS** or **Linux** (FUSE-based mounts require platform support)
|
||||
|
||||
### Python
|
||||
|
||||
```bash
|
||||
uv add mirage-ai
|
||||
```
|
||||
|
||||
This installs both the `mirage` library and the `mirage` CLI binary.
|
||||
|
||||
### TypeScript
|
||||
|
||||
Pick the package that matches your runtime:
|
||||
|
||||
```bash
|
||||
npm install @struktoai/mirage-node # Node.js servers and CLIs
|
||||
npm install @struktoai/mirage-browser # browser / edge runtimes
|
||||
npm install @struktoai/mirage-core # runtime-agnostic primitives
|
||||
```
|
||||
|
||||
`@struktoai/mirage-node` and `@struktoai/mirage-browser` both pull in `@struktoai/mirage-core` automatically.
|
||||
|
||||
### CLI
|
||||
|
||||
```bash
|
||||
curl -fsSL https://strukto.ai/install.sh | sh
|
||||
```
|
||||
|
||||
Or via your package manager of choice:
|
||||
|
||||
```bash
|
||||
brew install mirage
|
||||
```
|
||||
|
||||
```bash
|
||||
uvx mirage-ai
|
||||
```
|
||||
|
||||
```bash
|
||||
npx @struktoai/mirage-cli
|
||||
```
|
||||
|
||||
## Quickstart (Python)
|
||||
|
||||
```python
|
||||
from mirage import Workspace
|
||||
from mirage.resource.gdocs import GDocsConfig, GDocsResource
|
||||
from mirage.resource.ram import RAMResource
|
||||
from mirage.resource.s3 import S3Config, S3Resource
|
||||
from mirage.resource.slack import SlackConfig, SlackResource
|
||||
|
||||
ws = Workspace({
|
||||
"/data": RAMResource(),
|
||||
"/s3": S3Resource(S3Config(bucket="my-bucket")),
|
||||
"/slack": SlackResource(SlackConfig()),
|
||||
"/docs": GDocsResource(GDocsConfig()),
|
||||
})
|
||||
|
||||
await ws.execute("cp /s3/report.csv /data/report.csv")
|
||||
await ws.execute("grep alert /s3/data/log.jsonl | wc -l")
|
||||
|
||||
ws.snapshot("demo.tar")
|
||||
```
|
||||
|
||||
## Quickstart (TypeScript)
|
||||
|
||||
```ts
|
||||
import {
|
||||
Workspace,
|
||||
RAMResource,
|
||||
S3Resource,
|
||||
SlackResource,
|
||||
GDocsResource,
|
||||
} from '@struktoai/mirage-browser'
|
||||
|
||||
const ws = new Workspace({
|
||||
'/data': new RAMResource(),
|
||||
'/s3': new S3Resource({ bucket: 'my-bucket' }),
|
||||
'/slack': new SlackResource({}),
|
||||
'/docs': new GDocsResource({}),
|
||||
})
|
||||
|
||||
await ws.execute('cp /s3/report.csv /data/report.csv')
|
||||
await ws.execute('grep alert /s3/data/log.jsonl | wc -l')
|
||||
```
|
||||
|
||||
## Quickstart (CLI)
|
||||
|
||||
```bash
|
||||
mirage workspace create ws.yaml --id demo
|
||||
mirage execute --workspace_id demo --command "cp /s3/report.csv /data/report.csv"
|
||||
mirage provision --workspace_id demo --command "cat /s3/data/large.jsonl"
|
||||
mirage workspace snapshot demo demo.tar
|
||||
mirage workspace load demo.tar --id demo-restored
|
||||
```
|
||||
|
||||
## Agent Frameworks
|
||||
|
||||
Mirage drops into the major agent application frameworks as a sandbox or tool layer. Your agent runs against the same mount tree it would in bash, so swapping the model or runtime never changes the surface.
|
||||
|
||||
### OpenAI Agents SDK (Python)
|
||||
|
||||
The `MirageSandboxClient` plugs a `Workspace` into the OpenAI Agents SDK as a sandbox: bash commands the agent runs execute against your mounts.
|
||||
|
||||
```python
|
||||
from agents import Runner
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import SandboxAgent, SandboxRunConfig
|
||||
|
||||
from mirage.agents.openai_agents import MirageSandboxClient
|
||||
|
||||
client = MirageSandboxClient(ws)
|
||||
agent = SandboxAgent(
|
||||
name="Mirage Sandbox Agent",
|
||||
model="gpt-5.4-nano",
|
||||
instructions=ws.file_prompt,
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Summarize /s3/data/report.parquet into /report.txt.",
|
||||
run_config=RunConfig(sandbox=SandboxRunConfig(client=client)),
|
||||
)
|
||||
```
|
||||
|
||||
### Vercel AI SDK (TypeScript)
|
||||
|
||||
`mirageTools(ws)` exposes the workspace as a typed AI SDK tool set, so any model wired into the AI SDK can read and write across mounts, in Node or the browser.
|
||||
|
||||
```ts
|
||||
import { generateText } from 'ai'
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
import { mirageTools } from '@struktoai/mirage-agents/vercel'
|
||||
import { buildSystemPrompt } from '@struktoai/mirage-agents/openai'
|
||||
|
||||
const { text } = await generateText({
|
||||
model: openai('gpt-5.4-nano'),
|
||||
system: buildSystemPrompt({ mountInfo: { '/': 'In-memory filesystem' } }),
|
||||
prompt: "Use readFile to read /docs/paper.pdf, then describe what's in it.",
|
||||
tools: mirageTools(ws),
|
||||
})
|
||||
```
|
||||
|
||||
LangChain, Pydantic AI, CAMEL, OpenHands, and Mastra adapters live alongside these.
|
||||
|
||||
## Cache
|
||||
|
||||
Every `Workspace` ships with a **two-layer cache** so repeated work against remote backends (S3, GDrive, Slack, …) hits local state instead of the network:
|
||||
|
||||
- **Index cache.** Listings and metadata. The first directory walk hits the API; subsequent ones serve from the index until TTL expires.
|
||||
- **File cache.** Object bytes. The first read streams from origin; later pipelines read from cache.
|
||||
- **Pluggable backends.** Each layer is a store with two built-ins:
|
||||
- **RAM** (default): in-process, zero setup, 512 MB file cache and 10-minute index TTL. Best for single-process apps and notebooks.
|
||||
- **Redis**: shared across workers, processes, and machines. Best for serverless, multi-replica services, or when you want cache state to survive restarts.
|
||||
|
||||
```ts
|
||||
import { RedisFileCacheStore, RedisIndexCacheStore, Workspace } from 'mirage/node'
|
||||
|
||||
const ws = new Workspace(
|
||||
{ '/s3': new S3Resource({ bucket: 'my-bucket' }) },
|
||||
{
|
||||
cache: new RedisFileCacheStore({ url: 'redis://localhost:6379/0', limit: '8GB' }),
|
||||
index: new RedisIndexCacheStore({ url: 'redis://localhost:6379/0', ttl: 600 }),
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
```ts
|
||||
import { S3Resource, Workspace } from 'mirage/node'
|
||||
|
||||
const ws = new Workspace({ '/s3': new S3Resource({ bucket: 'my-bucket' }) })
|
||||
|
||||
// 1. Index miss → S3 LIST. Listing stored in index cache.
|
||||
await ws.execute('ls /s3/data/')
|
||||
|
||||
// 2. Index hit → 0 network calls.
|
||||
await ws.execute('find /s3/data/ -name "*.jsonl"')
|
||||
|
||||
// 3. File miss → S3 GET. Bytes stored in file cache.
|
||||
await ws.execute('cat /s3/data/log.jsonl | wc -l')
|
||||
|
||||
// 4. File hit → 0 network calls.
|
||||
await ws.execute('grep alert /s3/data/log.jsonl')
|
||||
```
|
||||
@@ -0,0 +1,181 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1010" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif">
|
||||
<defs>
|
||||
<style type="text/css"><![CDATA[
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600;700;800&display=swap');
|
||||
.display {
|
||||
font-family: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
]]></style>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
||||
<path d="M0 0 L10 5 L0 10 z" fill="#A1A1AA"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="1200" height="1010" fill="#0E0E10"/>
|
||||
|
||||
<!-- Title: mirage icon (cube) + "mirage" wordmark in diagram font -->
|
||||
<svg x="407" y="4" width="96" height="110" viewBox="16 7 128 146">
|
||||
<polygon points="19,45 80,10 80,45 50,63" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="80,10 141,45 110,63 80,45" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="141,45 141,115 110,98 110,63" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="141,115 80,150 80,115 110,98" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="80,150 19,115 50,98 80,115" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="19,115 19,45 50,63 50,98" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="80,45 110,63 110,98 80,115 50,98 50,63" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="50" y2="63" stroke="#000000" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="110" y2="63" stroke="#000000" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="80" y2="115" stroke="#000000" stroke-width="2.5"/>
|
||||
</svg>
|
||||
<text x="515" y="84" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif" font-size="72" font-weight="700" letter-spacing="-1">mirage</text>
|
||||
<text x="600" y="140" text-anchor="middle" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif" font-size="22" font-weight="500" letter-spacing="0.4">A Unified Virtual File System for AI Agents</text>
|
||||
|
||||
<!-- ===================== LAYER 1: AGENT ===================== -->
|
||||
<rect x="20" y="160" width="1160" height="170" rx="8" fill="#1A1A1D" stroke="#FFFFFF" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="198" font-size="22" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">AI Agent and Application</text>
|
||||
<text x="44" y="228" font-size="18" fill="#A1A1AA">
|
||||
<tspan x="44" dy="0">Bash commands, VFS</tspan>
|
||||
<tspan x="44" dy="24">calls, or syscalls</tspan>
|
||||
</text>
|
||||
|
||||
<!-- AI Agent (centered over connectors at x=470 and x=680: equal 30px margins on each side) -->
|
||||
<rect x="440" y="210" width="270" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="575" y="242" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">AI Agent</text>
|
||||
|
||||
<!-- OS Processes (positioned over FUSE Adapter x=890 for vertical connector) -->
|
||||
<rect x="791" y="210" width="198" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="890" y="242" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">App & CLI</text>
|
||||
|
||||
|
||||
<!-- ===================== LAYER 2: WORKSPACE ===================== -->
|
||||
<rect x="20" y="376" width="1160" height="200" rx="8" fill="#1A1A1D" stroke="#FFFFFF" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="414" font-size="22" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage Bash and VFS</text>
|
||||
<text x="44" y="444" font-size="18" fill="#A1A1AA">
|
||||
<tspan x="44" dy="0">Mirage Bash &</tspan>
|
||||
<tspan x="44" dy="24">registered commands;</tspan>
|
||||
<tspan x="44" dy="24">Mirage VFS &</tspan>
|
||||
<tspan x="44" dy="24">registered VFS ops</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Mirage Shell -->
|
||||
<rect x="376" y="409" width="188" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="470" y="441" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage Bash</text>
|
||||
|
||||
<!-- Mirage VFS -->
|
||||
<rect x="596" y="409" width="168" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="680" y="441" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage VFS</text>
|
||||
|
||||
<!-- FUSE Adapter -->
|
||||
<rect x="796" y="409" width="188" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="890" y="441" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">FUSE Adapter</text>
|
||||
|
||||
<!-- Command Registry -->
|
||||
<rect x="376" y="491" width="188" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="470" y="523" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Command Registry</text>
|
||||
|
||||
<!-- VFS Registry -->
|
||||
<rect x="596" y="491" width="168" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="680" y="523" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">VFS Registry</text>
|
||||
|
||||
<!-- Shell -> Command Registry -->
|
||||
<line x1="470" y1="461" x2="470" y2="491" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Mirage VFS -> VFS Registry -->
|
||||
<line x1="680" y1="461" x2="680" y2="491" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- FUSE -> Mirage VFS -->
|
||||
<line x1="796" y1="435" x2="764" y2="435" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===================== LAYER 3: DISPATCHER & CACHE ===================== -->
|
||||
<rect x="20" y="622" width="1160" height="140" rx="8" fill="#1A1A1D" stroke="#FFFFFF" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="660" font-size="22" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Dispatcher & Cache</text>
|
||||
<text x="44" y="690" font-size="18" fill="#A1A1AA">
|
||||
<tspan x="44" dy="0">Dispatches by mount;</tspan>
|
||||
<tspan x="44" dy="24">caches repeats</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Mirage Dispatcher (matches AI Agent dimensions: w=270, h=52, x=440) -->
|
||||
<rect x="440" y="666" width="270" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="575" y="698" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage Dispatcher</text>
|
||||
|
||||
<!-- Index & File Cache (merged; aligned x with Application & CLI: x=800, w=180) -->
|
||||
<rect x="791" y="666" width="198" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="890" y="698" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Index & File Cache</text>
|
||||
|
||||
<!-- Dispatcher -> Cache -->
|
||||
<line x1="710" y1="692" x2="791" y2="692" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===================== LAYER 4: BACKEND ===================== -->
|
||||
<rect x="20" y="808" width="1160" height="190" rx="8" fill="#1A1A1D" stroke="#FFFFFF" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="846" font-size="22" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Infrastructure and Remote</text>
|
||||
<text x="44" y="876" font-size="18" fill="#A1A1AA">
|
||||
<tspan x="44" dy="0">Infrastructure & remote,</tspan>
|
||||
<tspan x="44" dy="24">mounted as one tree</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Infrastructure (renamed from Local Backends; aligned x with Mirage Shell at 380, w=180) -->
|
||||
<rect x="380" y="833" width="180" height="140" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="470" y="861" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Infrastructure</text>
|
||||
<line x1="420" y1="873" x2="520" y2="873" stroke="#3F3F46" stroke-width="1"/>
|
||||
<text x="470" y="897" text-anchor="middle" font-size="15" fill="#A1A1AA">RAM</text>
|
||||
<text x="470" y="919" text-anchor="middle" font-size="15" fill="#A1A1AA">Disk</text>
|
||||
<text x="470" y="941" text-anchor="middle" font-size="15" fill="#A1A1AA">OPFS</text>
|
||||
<text x="470" y="963" text-anchor="middle" font-size="15" fill="#A1A1AA">Redis</text>
|
||||
|
||||
<!-- Remote Services (widened by 10px each side: x=790, w=200; center stays at 890) -->
|
||||
<rect x="790" y="833" width="200" height="140" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="890" y="861" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Remote Service</text>
|
||||
<line x1="830" y1="873" x2="950" y2="873" stroke="#3F3F46" stroke-width="1"/>
|
||||
|
||||
<!-- Icon grid: 6 per row, 22x22 colored rect with inlined simple-icons brand SVG -->
|
||||
<g transform="translate(804,883)"><rect width="22" height="22" rx="5" fill="#FF9900"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="m20.913 13.147l.12-.895c.947.576 1.258.922 1.354 1.071c-.16.031-.562.046-1.474-.176m-2.174 7.988l-.005.073c0 .084-.207.405-1.124.768a10 10 0 0 1-1.438.432c-1.405.325-3.128.504-4.853.504c-4.612 0-7.412-1.184-7.412-1.704l-.005-.073L1.81 5.602q.203.117.432.227q.064.03.128.057q.2.093.417.18l.179.069q.232.087.478.168l.13.043q.311.097.646.187l.176.044q.263.066.534.127a23 23 0 0 0 .843.17l.121.023q.378.067.768.122q.107.016.216.03q.299.04.604.077l.24.027q.366.04.74.07l.081.009q.413.033.83.056l.233.012q.316.015.633.025a33 33 0 0 0 2.795-.026l.232-.011q.417-.023.83-.056l.08-.008q.375-.03.742-.072l.238-.026q.307-.036.609-.077l.211-.03q.392-.056.772-.122l.111-.02q.323-.06.634-.125l.212-.047q.279-.062.546-.13l.166-.042q.338-.09.654-.189l.115-.038a11 11 0 0 0 .493-.173q.087-.032.17-.066q.225-.089.43-.185q.059-.025.116-.052q.23-.11.436-.228l-.976 7.245c-2.488-.78-5.805-2.292-7.311-3a1.09 1.09 0 0 0-1.088-1.085c-.6 0-1.088.489-1.088 1.088s.488 1.089 1.088 1.089c.196 0 .378-.056.537-.148c1.72.812 5.144 2.367 7.715 3.15zm-7.42-20.047c5.677 0 9.676 1.759 9.75 2.736l-.014.113c-.01.033-.031.067-.048.101c-.015.028-.026.057-.047.087c-.024.033-.058.068-.09.102c-.028.03-.051.06-.084.09c-.038.035-.087.07-.133.105c-.04.03-.074.06-.119.091c-.053.036-.116.071-.177.107c-.05.03-.095.06-.15.09c-.068.036-.147.073-.222.11c-.059.028-.114.057-.177.085c-.084.038-.177.074-.268.111c-.068.027-.13.054-.203.082c-.097.036-.205.072-.31.107c-.075.026-.148.053-.228.079c-.111.035-.233.069-.35.103c-.085.024-.165.05-.253.073c-.124.034-.258.065-.389.098c-.093.022-.181.046-.278.068c-.139.032-.287.061-.433.091c-.098.02-.191.041-.293.06c-.155.03-.32.057-.482.084c-.1.018-.198.036-.302.052c-.166.026-.342.048-.515.072c-.11.014-.213.03-.325.044c-.181.023-.372.041-.56.06q-.163.019-.332.036c-.188.016-.386.029-.58.043c-.122.009-.24.02-.364.028c-.207.012-.422.02-.635.028c-.12.005-.234.012-.354.016a36 36 0 0 1-2.069 0c-.12-.004-.234-.011-.352-.016c-.214-.008-.43-.016-.637-.028c-.122-.008-.238-.02-.36-.027c-.195-.015-.394-.028-.584-.044c-.11-.01-.215-.024-.324-.035c-.19-.02-.384-.038-.568-.06l-.315-.044c-.176-.024-.355-.046-.525-.073c-.1-.015-.192-.033-.29-.05c-.167-.028-.335-.055-.494-.086c-.096-.018-.183-.038-.276-.056c-.151-.032-.305-.062-.45-.095c-.09-.02-.173-.043-.26-.064c-.138-.034-.277-.067-.407-.102c-.082-.022-.157-.046-.235-.069a12 12 0 0 1-.368-.108c-.075-.024-.141-.049-.213-.073c-.11-.037-.223-.075-.325-.113c-.067-.025-.125-.051-.188-.077c-.096-.038-.195-.076-.282-.115c-.06-.027-.11-.054-.166-.08c-.08-.039-.162-.077-.233-.116c-.052-.028-.094-.055-.142-.084c-.063-.038-.13-.075-.185-.113c-.043-.029-.075-.058-.113-.086c-.048-.037-.098-.073-.139-.11c-.032-.029-.054-.057-.08-.087c-.033-.035-.069-.07-.093-.104c-.02-.03-.031-.058-.046-.086c-.018-.035-.039-.068-.049-.102l-.015-.113c.076-.977 4.074-2.736 9.748-2.736m12.182 12.124c-.118-.628-.84-1.291-2.31-2.128l.963-7.16l.005-.073C22.16 1.581 16.447 0 11.32 0C6.194 0 .482 1.581.482 3.851l.005.072L2.819 21.25c.071 2.002 5.236 2.75 8.5 2.75c1.805 0 3.615-.188 5.098-.531c.598-.138 1.133-.3 1.592-.48c1.18-.467 1.789-1.053 1.813-1.739l.945-7.018c.557.131 1.016.197 1.389.197c.54 0 .902-.137 1.134-.413a.96.96 0 0 0 .21-.804Z"/></svg></g> <!-- S3 -->
|
||||
<g transform="translate(834,883)"><rect width="22" height="22" rx="5" fill="#F38020"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M16.509 16.845c.147-.507.09-.971-.155-1.316c-.225-.316-.605-.499-1.062-.52l-8.66-.113a.16.16 0 0 1-.133-.07a.2.2 0 0 1-.02-.156a.24.24 0 0 1 .203-.156l8.736-.113c1.035-.049 2.16-.886 2.554-1.913l.499-1.302a.27.27 0 0 0 .014-.168a5.689 5.689 0 0 0-10.937-.584a2.58 2.58 0 0 0-1.794-.498a2.56 2.56 0 0 0-2.223 3.18A3.634 3.634 0 0 0 0 16.751q.002.264.035.527a.174.174 0 0 0 .17.148h15.98a.22.22 0 0 0 .204-.155zm2.757-5.564c-.077 0-.161 0-.239.011c-.056 0-.105.042-.127.098l-.337 1.174c-.148.507-.092.971.154 1.317c.225.316.605.498 1.062.52l1.844.113c.056 0 .105.026.133.07a.2.2 0 0 1 .021.156a.24.24 0 0 1-.204.156l-1.92.112c-1.042.049-2.159.887-2.553 1.914l-.141.358c-.028.072.021.142.099.142h6.597a.174.174 0 0 0 .17-.126a5 5 0 0 0 .175-1.28a4.74 4.74 0 0 0-4.734-4.727"/></svg></g> <!-- R2 (Cloudflare) -->
|
||||
<g transform="translate(864,883)"><rect width="22" height="22" rx="5" fill="#C74634"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M16.412 4.412h-8.82a7.588 7.588 0 0 0-.008 15.176h8.828a7.588 7.588 0 0 0 0-15.176m-.193 12.502H7.786a4.915 4.915 0 0 1 0-9.828h8.433a4.914 4.914 0 1 1 0 9.828"/></svg></g> <!-- OCI -->
|
||||
<g transform="translate(894,883)"><rect width="22" height="22" rx="5" fill="#4285F4"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M12.19 2.38a9.344 9.344 0 0 0-9.234 6.893c.053-.02-.055.013 0 0c-3.875 2.551-3.922 8.11-.247 10.941l.006-.007l-.007.03a6.7 6.7 0 0 0 4.077 1.356h5.173l.03.03h5.192c6.687.053 9.376-8.605 3.835-12.35a9.37 9.37 0 0 0-2.821-4.552l-.043.043l.006-.05A9.34 9.34 0 0 0 12.19 2.38m-.358 4.146c1.244-.04 2.518.368 3.486 1.15a5.19 5.19 0 0 1 1.862 4.078v.518c3.53-.07 3.53 5.262 0 5.193h-5.193l-.008.009v-.04H6.785a2.6 2.6 0 0 1-1.067-.23h.001a2.597 2.597 0 1 1 3.437-3.437l3.013-3.012A6.75 6.75 0 0 0 8.11 8.24c.018-.01.04-.026.054-.023a5.2 5.2 0 0 1 3.67-1.69z"/></svg></g> <!-- GCS -->
|
||||
<g transform="translate(924,883)"><rect width="22" height="22" rx="5" fill="#3FCF8E"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M11.9 1.036c-.015-.986-1.26-1.41-1.874-.637L.764 12.05C-.33 13.427.65 15.455 2.409 15.455h9.579l.113 7.51c.014.985 1.259 1.408 1.873.636l9.262-11.653c1.093-1.375.113-3.403-1.645-3.403h-9.642z"/></svg></g> <!-- Supabase -->
|
||||
<g transform="translate(954,883)"><rect width="22" height="22" rx="5" fill="#EA4335"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M24 5.457v13.909c0 .904-.732 1.636-1.636 1.636h-3.819V11.73L12 16.64l-6.545-4.91v9.273H1.636A1.636 1.636 0 0 1 0 19.366V5.457c0-2.023 2.309-3.178 3.927-1.964L5.455 4.64L12 9.548l6.545-4.91l1.528-1.145C21.69 2.28 24 3.434 24 5.457"/></svg></g> <!-- Gmail -->
|
||||
<g transform="translate(804,911)"><rect width="22" height="22" rx="5" fill="#1FA463"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M12.01 1.485c-2.082 0-3.754.02-3.743.047c.01.02 1.708 3.001 3.774 6.62l3.76 6.574h3.76c2.081 0 3.753-.02 3.742-.047c-.005-.02-1.708-3.001-3.775-6.62l-3.76-6.574zm-4.76 1.73a789.828 789.861 0 0 0-3.63 6.319L0 15.868l1.89 3.298l1.885 3.297l3.62-6.335l3.618-6.33l-1.88-3.287C8.1 4.704 7.255 3.22 7.25 3.214zm2.259 12.653l-.203.348c-.114.198-.96 1.672-1.88 3.287a423.93 423.948 0 0 1-1.698 2.97c-.01.026 3.24.042 7.222.042h7.244l1.796-3.157c.992-1.734 1.85-3.23 1.906-3.323l.104-.167h-7.249z"/></svg></g> <!-- GDrive -->
|
||||
<g transform="translate(834,911)"><rect width="22" height="22" rx="5" fill="#4285F4"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M14.727 6.727H14V0H4.91c-.905 0-1.637.732-1.637 1.636v20.728c0 .904.732 1.636 1.636 1.636h14.182c.904 0 1.636-.732 1.636-1.636V6.727zm-.545 10.455H7.09v-1.364h7.09v1.364zm2.727-3.273H7.091v-1.364h9.818zm0-3.273H7.091V9.273h9.818zM14.727 6h6l-6-6z"/></svg></g> <!-- GDocs -->
|
||||
<g transform="translate(864,911)"><rect width="22" height="22" rx="5" fill="#34A853"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M11.318 12.545H7.91v-1.909h3.41v1.91zM14.728 0v6h6zm1.363 10.636h-3.41v1.91h3.41zm0 3.273h-3.41v1.91h3.41zM20.727 6.5v15.864c0 .904-.732 1.636-1.636 1.636H4.909a1.636 1.636 0 0 1-1.636-1.636V1.636C3.273.732 4.005 0 4.909 0h9.318v6.5zm-3.273 2.773H6.545v7.909h10.91v-7.91zm-6.136 4.636H7.91v1.91h3.41v-1.91z"/></svg></g> <!-- GSheets -->
|
||||
<g transform="translate(894,911)"><rect width="22" height="22" rx="5" fill="#FBBC04"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M16.09 15.273H7.91v-4.637h8.18zm1.728-8.523h2.91v15.614c0 .904-.733 1.636-1.637 1.636H4.909a1.636 1.636 0 0 1-1.636-1.636V1.636C3.273.732 4.005 0 4.909 0h9.068v6.75zm-.363 2.523H6.545v7.363h10.91zm-2.728-5.979V6h6.001l-6-6v3.294z"/></svg></g> <!-- GSlides -->
|
||||
<g transform="translate(924,911)"><rect width="22" height="22" rx="5" fill="#4A154B"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M5.042 15.165a2.53 2.53 0 0 1-2.52 2.523A2.53 2.53 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52a2.527 2.527 0 0 1 2.521 2.52v6.313A2.53 2.53 0 0 1 8.834 24a2.53 2.53 0 0 1-2.521-2.522zM8.834 5.042a2.53 2.53 0 0 1-2.521-2.52A2.53 2.53 0 0 1 8.834 0a2.53 2.53 0 0 1 2.521 2.522v2.52zm0 1.271a2.53 2.53 0 0 1 2.521 2.521a2.53 2.53 0 0 1-2.521 2.521H2.522A2.53 2.53 0 0 1 0 8.834a2.53 2.53 0 0 1 2.522-2.521zm10.122 2.521a2.53 2.53 0 0 1 2.522-2.521A2.53 2.53 0 0 1 24 8.834a2.53 2.53 0 0 1-2.522 2.521h-2.522zm-1.268 0a2.53 2.53 0 0 1-2.523 2.521a2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.53 2.53 0 0 1 2.523 2.522zm-2.523 10.122a2.53 2.53 0 0 1 2.523 2.522A2.53 2.53 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522zm0-1.268a2.527 2.527 0 0 1-2.52-2.523a2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.53 2.53 0 0 1-2.522 2.523z"/></svg></g> <!-- Slack -->
|
||||
<g transform="translate(954,911)"><rect width="22" height="22" rx="5" fill="#5865F2"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M20.317 4.37a19.8 19.8 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.3 18.3 0 0 0-5.487 0a13 13 0 0 0-.617-1.25a.08.08 0 0 0-.079-.037A19.7 19.7 0 0 0 3.677 4.37a.1.1 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.08.08 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.08.08 0 0 0 .084-.028a14 14 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13 13 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10 10 0 0 0 .372-.292a.07.07 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.07.07 0 0 1 .078.01q.181.149.373.292a.077.077 0 0 1-.006.127a12.3 12.3 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.08.08 0 0 0 .084.028a19.8 19.8 0 0 0 6.002-3.03a.08.08 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.06.06 0 0 0-.031-.03M8.02 15.33c-1.182 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418m7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418"/></svg></g> <!-- Discord -->
|
||||
<g transform="translate(804,939)"><rect width="22" height="22" rx="5" fill="#26A5E4"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12a12 12 0 0 0 12-12A12 12 0 0 0 12 0zm4.962 7.224c.1-.002.321.023.465.14a.5.5 0 0 1 .171.325c.016.093.036.306.02.472c-.18 1.898-.962 6.502-1.36 8.627c-.168.9-.499 1.201-.82 1.23c-.696.065-1.225-.46-1.9-.902c-1.056-.693-1.653-1.124-2.678-1.8c-1.185-.78-.417-1.21.258-1.91c.177-.184 3.247-2.977 3.307-3.23c.007-.032.014-.15-.056-.212s-.174-.041-.249-.024q-.159.037-5.061 3.345q-.72.495-1.302.48c-.428-.008-1.252-.241-1.865-.44c-.752-.245-1.349-.374-1.297-.789q.04-.324.893-.663q5.247-2.286 6.998-3.014c3.332-1.386 4.025-1.627 4.476-1.635"/></svg></g> <!-- Telegram -->
|
||||
<g transform="translate(834,939)"><rect width="22" height="22" rx="5" fill="#181717"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></g> <!-- GitHub -->
|
||||
<g transform="translate(864,939)"><rect width="22" height="22" rx="5" fill="#5E6AD2"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M2.886 4.18A11.98 11.98 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556q-.787.496-1.65.866L.951 7.277q.371-.863.866-1.65ZM.322 9.163l14.515 14.515q-1.066.26-2.195.322L0 11.358a12 12 0 0 1 .322-2.195m-.17 4.862l9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z"/></svg></g> <!-- Linear -->
|
||||
<g transform="translate(894,939)"><rect width="22" height="22" rx="5" fill="#000000"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.981-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.841-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.748-.887l-15.177.887c-.56.047-.747.327-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514c-.748 0-.935-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233l4.764 7.279v-6.44l-1.215-.139c-.093-.514.28-.887.747-.933zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.934.653.934 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.447-1.632"/></svg></g> <!-- Notion -->
|
||||
<g transform="translate(924,939)"><rect width="22" height="22" rx="5" fill="#47A248"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M17.193 9.555c-1.264-5.58-4.252-7.414-4.573-8.115c-.28-.394-.53-.954-.735-1.44c-.036.495-.055.685-.523 1.184c-.723.566-4.438 3.682-4.74 10.02c-.282 5.912 4.27 9.435 4.888 9.884l.07.05A74 74 0 0 1 11.91 24h.481a29 29 0 0 1 .51-3.07c.417-.296.604-.463.85-.693a11.34 11.34 0 0 0 3.639-8.464c.01-.814-.103-1.662-.197-2.218m-5.336 8.195s0-8.291.275-8.29c.213 0 .49 10.695.49 10.695c-.381-.045-.765-1.76-.765-2.405"/></svg></g> <!-- MongoDB -->
|
||||
<text x="965" y="957" text-anchor="middle" font-size="16" font-weight="700" fill="#A1A1AA" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">…</text>
|
||||
|
||||
<!-- ===================== CROSS-LAYER ARROWS ===================== -->
|
||||
<!-- AI Agent -> Mirage Shell (vertical) -->
|
||||
<line x1="470" y1="262" x2="470" y2="409" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="176" y="282" width="282" height="28" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1"/>
|
||||
<text x="450" y="302" text-anchor="end" font-size="18" font-family="'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace" fill="#A1A1AA">grep alert /s3/log.jsonl</text>
|
||||
|
||||
<!-- AI Agent -> Mirage VFS (vertical) -->
|
||||
<line x1="680" y1="262" x2="680" y2="409" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="490" y="282" width="180" height="28" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1"/>
|
||||
<text x="662" y="302" text-anchor="end" font-size="18" font-family="'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace" fill="#A1A1AA">stat /paper.pdf</text>
|
||||
|
||||
<!-- Application & CLI -> FUSE (vertical) -->
|
||||
<line x1="890" y1="262" x2="890" y2="409" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="902" y="282" width="200" height="28" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1"/>
|
||||
<text x="910" y="302" font-size="18" font-family="'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace" fill="#A1A1AA">read /r2/data.csv</text>
|
||||
|
||||
<!-- Command Registry -> Dispatcher (vertical) -->
|
||||
<line x1="470" y1="543" x2="470" y2="666" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- VFS Registry -> Dispatcher (vertical) -->
|
||||
<line x1="680" y1="543" x2="680" y2="666" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dispatcher -> Infrastructure (vertical, aligned with CR -> Dispatcher connector at x=470) -->
|
||||
<line x1="470" y1="718" x2="470" y2="833" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dispatcher -> Remote Services (start x=680 matches PR -> Dispatcher; end x=890 matches Cache -> RS) -->
|
||||
<line x1="680" y1="718" x2="890" y2="833" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Cache - Remote Services (cache-hit short-circuit; dotted, no arrowhead) -->
|
||||
<line x1="890" y1="718" x2="890" y2="833" stroke="#A1A1AA" stroke-width="1.2" stroke-dasharray="4 3"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,181 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1010" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif">
|
||||
<defs>
|
||||
<style type="text/css"><![CDATA[
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600;700;800&display=swap');
|
||||
.display {
|
||||
font-family: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
]]></style>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
||||
<path d="M0 0 L10 5 L0 10 z" fill="#27272A"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="1200" height="1010" fill="#FFFFFF"/>
|
||||
|
||||
<!-- Title: mirage icon (cube) + "mirage" wordmark in diagram font -->
|
||||
<svg x="407" y="4" width="96" height="110" viewBox="16 7 128 146">
|
||||
<polygon points="19,45 80,10 80,45 50,63" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="80,10 141,45 110,63 80,45" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="141,45 141,115 110,98 110,63" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="141,115 80,150 80,115 110,98" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="80,150 19,115 50,98 80,115" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="19,115 19,45 50,63 50,98" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="80,45 110,63 110,98 80,115 50,98 50,63" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="50" y2="63" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="110" y2="63" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="80" y2="115" stroke="#ffffff" stroke-width="2.5"/>
|
||||
</svg>
|
||||
<text x="515" y="84" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif" font-size="72" font-weight="700" letter-spacing="-1">mirage</text>
|
||||
<text x="600" y="140" text-anchor="middle" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif" font-size="22" font-weight="500" letter-spacing="0.4">A Unified Virtual File System for AI Agents</text>
|
||||
|
||||
<!-- ===================== LAYER 1: AGENT ===================== -->
|
||||
<rect x="20" y="160" width="1160" height="170" rx="8" fill="#FAF9F6" stroke="#000000" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="198" font-size="22" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">AI Agent and Application</text>
|
||||
<text x="44" y="228" font-size="18" fill="#27272A">
|
||||
<tspan x="44" dy="0">Bash commands, VFS</tspan>
|
||||
<tspan x="44" dy="24">calls, or syscalls</tspan>
|
||||
</text>
|
||||
|
||||
<!-- AI Agent (centered over connectors at x=470 and x=680: equal 30px margins on each side) -->
|
||||
<rect x="440" y="210" width="270" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="575" y="242" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">AI Agent</text>
|
||||
|
||||
<!-- OS Processes (positioned over FUSE Adapter x=890 for vertical connector) -->
|
||||
<rect x="791" y="210" width="198" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="890" y="242" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">App & CLI</text>
|
||||
|
||||
|
||||
<!-- ===================== LAYER 2: WORKSPACE ===================== -->
|
||||
<rect x="20" y="376" width="1160" height="200" rx="8" fill="#FAF9F6" stroke="#000000" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="414" font-size="22" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage Bash and VFS</text>
|
||||
<text x="44" y="444" font-size="18" fill="#27272A">
|
||||
<tspan x="44" dy="0">Mirage Bash &</tspan>
|
||||
<tspan x="44" dy="24">registered commands;</tspan>
|
||||
<tspan x="44" dy="24">Mirage VFS &</tspan>
|
||||
<tspan x="44" dy="24">registered VFS ops</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Mirage Shell -->
|
||||
<rect x="376" y="409" width="188" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="470" y="441" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage Bash</text>
|
||||
|
||||
<!-- Mirage VFS -->
|
||||
<rect x="596" y="409" width="168" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="680" y="441" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage VFS</text>
|
||||
|
||||
<!-- FUSE Adapter -->
|
||||
<rect x="796" y="409" width="188" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="890" y="441" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">FUSE Adapter</text>
|
||||
|
||||
<!-- Command Registry -->
|
||||
<rect x="376" y="491" width="188" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="470" y="523" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Command Registry</text>
|
||||
|
||||
<!-- VFS Registry -->
|
||||
<rect x="596" y="491" width="168" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="680" y="523" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">VFS Registry</text>
|
||||
|
||||
<!-- Shell -> Command Registry -->
|
||||
<line x1="470" y1="461" x2="470" y2="491" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Mirage VFS -> VFS Registry -->
|
||||
<line x1="680" y1="461" x2="680" y2="491" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- FUSE -> Mirage VFS -->
|
||||
<line x1="796" y1="435" x2="764" y2="435" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===================== LAYER 3: DISPATCHER & CACHE ===================== -->
|
||||
<rect x="20" y="622" width="1160" height="140" rx="8" fill="#FAF9F6" stroke="#000000" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="660" font-size="22" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Dispatcher & Cache</text>
|
||||
<text x="44" y="690" font-size="18" fill="#27272A">
|
||||
<tspan x="44" dy="0">Dispatches by mount;</tspan>
|
||||
<tspan x="44" dy="24">caches repeats</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Mirage Dispatcher (matches AI Agent dimensions: w=270, h=52, x=440) -->
|
||||
<rect x="440" y="666" width="270" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="575" y="698" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage Dispatcher</text>
|
||||
|
||||
<!-- Index & File Cache (merged; aligned x with Application & CLI: x=800, w=180) -->
|
||||
<rect x="791" y="666" width="198" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="890" y="698" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Index & File Cache</text>
|
||||
|
||||
<!-- Dispatcher -> Cache -->
|
||||
<line x1="710" y1="692" x2="791" y2="692" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===================== LAYER 4: BACKEND ===================== -->
|
||||
<rect x="20" y="808" width="1160" height="190" rx="8" fill="#FAF9F6" stroke="#000000" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="846" font-size="22" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Infrastructure and Remote</text>
|
||||
<text x="44" y="876" font-size="18" fill="#27272A">
|
||||
<tspan x="44" dy="0">Infrastructure & remote,</tspan>
|
||||
<tspan x="44" dy="24">mounted as one tree</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Infrastructure (renamed from Local Backends; aligned x with Mirage Shell at 380, w=180) -->
|
||||
<rect x="380" y="833" width="180" height="140" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="470" y="861" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Infrastructure</text>
|
||||
<line x1="420" y1="873" x2="520" y2="873" stroke="#E4E4E7" stroke-width="1"/>
|
||||
<text x="470" y="897" text-anchor="middle" font-size="15" fill="#27272A">RAM</text>
|
||||
<text x="470" y="919" text-anchor="middle" font-size="15" fill="#27272A">Disk</text>
|
||||
<text x="470" y="941" text-anchor="middle" font-size="15" fill="#27272A">OPFS</text>
|
||||
<text x="470" y="963" text-anchor="middle" font-size="15" fill="#27272A">Redis</text>
|
||||
|
||||
<!-- Remote Services (widened by 10px each side: x=790, w=200; center stays at 890) -->
|
||||
<rect x="790" y="833" width="200" height="140" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="890" y="861" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Remote Service</text>
|
||||
<line x1="830" y1="873" x2="950" y2="873" stroke="#E4E4E7" stroke-width="1"/>
|
||||
|
||||
<!-- Icon grid: 6 per row, 22x22 colored rect with inlined simple-icons brand SVG -->
|
||||
<g transform="translate(804,883)"><rect width="22" height="22" rx="5" fill="#FF9900"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="m20.913 13.147l.12-.895c.947.576 1.258.922 1.354 1.071c-.16.031-.562.046-1.474-.176m-2.174 7.988l-.005.073c0 .084-.207.405-1.124.768a10 10 0 0 1-1.438.432c-1.405.325-3.128.504-4.853.504c-4.612 0-7.412-1.184-7.412-1.704l-.005-.073L1.81 5.602q.203.117.432.227q.064.03.128.057q.2.093.417.18l.179.069q.232.087.478.168l.13.043q.311.097.646.187l.176.044q.263.066.534.127a23 23 0 0 0 .843.17l.121.023q.378.067.768.122q.107.016.216.03q.299.04.604.077l.24.027q.366.04.74.07l.081.009q.413.033.83.056l.233.012q.316.015.633.025a33 33 0 0 0 2.795-.026l.232-.011q.417-.023.83-.056l.08-.008q.375-.03.742-.072l.238-.026q.307-.036.609-.077l.211-.03q.392-.056.772-.122l.111-.02q.323-.06.634-.125l.212-.047q.279-.062.546-.13l.166-.042q.338-.09.654-.189l.115-.038a11 11 0 0 0 .493-.173q.087-.032.17-.066q.225-.089.43-.185q.059-.025.116-.052q.23-.11.436-.228l-.976 7.245c-2.488-.78-5.805-2.292-7.311-3a1.09 1.09 0 0 0-1.088-1.085c-.6 0-1.088.489-1.088 1.088s.488 1.089 1.088 1.089c.196 0 .378-.056.537-.148c1.72.812 5.144 2.367 7.715 3.15zm-7.42-20.047c5.677 0 9.676 1.759 9.75 2.736l-.014.113c-.01.033-.031.067-.048.101c-.015.028-.026.057-.047.087c-.024.033-.058.068-.09.102c-.028.03-.051.06-.084.09c-.038.035-.087.07-.133.105c-.04.03-.074.06-.119.091c-.053.036-.116.071-.177.107c-.05.03-.095.06-.15.09c-.068.036-.147.073-.222.11c-.059.028-.114.057-.177.085c-.084.038-.177.074-.268.111c-.068.027-.13.054-.203.082c-.097.036-.205.072-.31.107c-.075.026-.148.053-.228.079c-.111.035-.233.069-.35.103c-.085.024-.165.05-.253.073c-.124.034-.258.065-.389.098c-.093.022-.181.046-.278.068c-.139.032-.287.061-.433.091c-.098.02-.191.041-.293.06c-.155.03-.32.057-.482.084c-.1.018-.198.036-.302.052c-.166.026-.342.048-.515.072c-.11.014-.213.03-.325.044c-.181.023-.372.041-.56.06q-.163.019-.332.036c-.188.016-.386.029-.58.043c-.122.009-.24.02-.364.028c-.207.012-.422.02-.635.028c-.12.005-.234.012-.354.016a36 36 0 0 1-2.069 0c-.12-.004-.234-.011-.352-.016c-.214-.008-.43-.016-.637-.028c-.122-.008-.238-.02-.36-.027c-.195-.015-.394-.028-.584-.044c-.11-.01-.215-.024-.324-.035c-.19-.02-.384-.038-.568-.06l-.315-.044c-.176-.024-.355-.046-.525-.073c-.1-.015-.192-.033-.29-.05c-.167-.028-.335-.055-.494-.086c-.096-.018-.183-.038-.276-.056c-.151-.032-.305-.062-.45-.095c-.09-.02-.173-.043-.26-.064c-.138-.034-.277-.067-.407-.102c-.082-.022-.157-.046-.235-.069a12 12 0 0 1-.368-.108c-.075-.024-.141-.049-.213-.073c-.11-.037-.223-.075-.325-.113c-.067-.025-.125-.051-.188-.077c-.096-.038-.195-.076-.282-.115c-.06-.027-.11-.054-.166-.08c-.08-.039-.162-.077-.233-.116c-.052-.028-.094-.055-.142-.084c-.063-.038-.13-.075-.185-.113c-.043-.029-.075-.058-.113-.086c-.048-.037-.098-.073-.139-.11c-.032-.029-.054-.057-.08-.087c-.033-.035-.069-.07-.093-.104c-.02-.03-.031-.058-.046-.086c-.018-.035-.039-.068-.049-.102l-.015-.113c.076-.977 4.074-2.736 9.748-2.736m12.182 12.124c-.118-.628-.84-1.291-2.31-2.128l.963-7.16l.005-.073C22.16 1.581 16.447 0 11.32 0C6.194 0 .482 1.581.482 3.851l.005.072L2.819 21.25c.071 2.002 5.236 2.75 8.5 2.75c1.805 0 3.615-.188 5.098-.531c.598-.138 1.133-.3 1.592-.48c1.18-.467 1.789-1.053 1.813-1.739l.945-7.018c.557.131 1.016.197 1.389.197c.54 0 .902-.137 1.134-.413a.96.96 0 0 0 .21-.804Z"/></svg></g> <!-- S3 -->
|
||||
<g transform="translate(834,883)"><rect width="22" height="22" rx="5" fill="#F38020"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M16.509 16.845c.147-.507.09-.971-.155-1.316c-.225-.316-.605-.499-1.062-.52l-8.66-.113a.16.16 0 0 1-.133-.07a.2.2 0 0 1-.02-.156a.24.24 0 0 1 .203-.156l8.736-.113c1.035-.049 2.16-.886 2.554-1.913l.499-1.302a.27.27 0 0 0 .014-.168a5.689 5.689 0 0 0-10.937-.584a2.58 2.58 0 0 0-1.794-.498a2.56 2.56 0 0 0-2.223 3.18A3.634 3.634 0 0 0 0 16.751q.002.264.035.527a.174.174 0 0 0 .17.148h15.98a.22.22 0 0 0 .204-.155zm2.757-5.564c-.077 0-.161 0-.239.011c-.056 0-.105.042-.127.098l-.337 1.174c-.148.507-.092.971.154 1.317c.225.316.605.498 1.062.52l1.844.113c.056 0 .105.026.133.07a.2.2 0 0 1 .021.156a.24.24 0 0 1-.204.156l-1.92.112c-1.042.049-2.159.887-2.553 1.914l-.141.358c-.028.072.021.142.099.142h6.597a.174.174 0 0 0 .17-.126a5 5 0 0 0 .175-1.28a4.74 4.74 0 0 0-4.734-4.727"/></svg></g> <!-- R2 (Cloudflare) -->
|
||||
<g transform="translate(864,883)"><rect width="22" height="22" rx="5" fill="#C74634"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M16.412 4.412h-8.82a7.588 7.588 0 0 0-.008 15.176h8.828a7.588 7.588 0 0 0 0-15.176m-.193 12.502H7.786a4.915 4.915 0 0 1 0-9.828h8.433a4.914 4.914 0 1 1 0 9.828"/></svg></g> <!-- OCI -->
|
||||
<g transform="translate(894,883)"><rect width="22" height="22" rx="5" fill="#4285F4"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M12.19 2.38a9.344 9.344 0 0 0-9.234 6.893c.053-.02-.055.013 0 0c-3.875 2.551-3.922 8.11-.247 10.941l.006-.007l-.007.03a6.7 6.7 0 0 0 4.077 1.356h5.173l.03.03h5.192c6.687.053 9.376-8.605 3.835-12.35a9.37 9.37 0 0 0-2.821-4.552l-.043.043l.006-.05A9.34 9.34 0 0 0 12.19 2.38m-.358 4.146c1.244-.04 2.518.368 3.486 1.15a5.19 5.19 0 0 1 1.862 4.078v.518c3.53-.07 3.53 5.262 0 5.193h-5.193l-.008.009v-.04H6.785a2.6 2.6 0 0 1-1.067-.23h.001a2.597 2.597 0 1 1 3.437-3.437l3.013-3.012A6.75 6.75 0 0 0 8.11 8.24c.018-.01.04-.026.054-.023a5.2 5.2 0 0 1 3.67-1.69z"/></svg></g> <!-- GCS -->
|
||||
<g transform="translate(924,883)"><rect width="22" height="22" rx="5" fill="#3FCF8E"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M11.9 1.036c-.015-.986-1.26-1.41-1.874-.637L.764 12.05C-.33 13.427.65 15.455 2.409 15.455h9.579l.113 7.51c.014.985 1.259 1.408 1.873.636l9.262-11.653c1.093-1.375.113-3.403-1.645-3.403h-9.642z"/></svg></g> <!-- Supabase -->
|
||||
<g transform="translate(954,883)"><rect width="22" height="22" rx="5" fill="#EA4335"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M24 5.457v13.909c0 .904-.732 1.636-1.636 1.636h-3.819V11.73L12 16.64l-6.545-4.91v9.273H1.636A1.636 1.636 0 0 1 0 19.366V5.457c0-2.023 2.309-3.178 3.927-1.964L5.455 4.64L12 9.548l6.545-4.91l1.528-1.145C21.69 2.28 24 3.434 24 5.457"/></svg></g> <!-- Gmail -->
|
||||
<g transform="translate(804,911)"><rect width="22" height="22" rx="5" fill="#1FA463"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M12.01 1.485c-2.082 0-3.754.02-3.743.047c.01.02 1.708 3.001 3.774 6.62l3.76 6.574h3.76c2.081 0 3.753-.02 3.742-.047c-.005-.02-1.708-3.001-3.775-6.62l-3.76-6.574zm-4.76 1.73a789.828 789.861 0 0 0-3.63 6.319L0 15.868l1.89 3.298l1.885 3.297l3.62-6.335l3.618-6.33l-1.88-3.287C8.1 4.704 7.255 3.22 7.25 3.214zm2.259 12.653l-.203.348c-.114.198-.96 1.672-1.88 3.287a423.93 423.948 0 0 1-1.698 2.97c-.01.026 3.24.042 7.222.042h7.244l1.796-3.157c.992-1.734 1.85-3.23 1.906-3.323l.104-.167h-7.249z"/></svg></g> <!-- GDrive -->
|
||||
<g transform="translate(834,911)"><rect width="22" height="22" rx="5" fill="#4285F4"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M14.727 6.727H14V0H4.91c-.905 0-1.637.732-1.637 1.636v20.728c0 .904.732 1.636 1.636 1.636h14.182c.904 0 1.636-.732 1.636-1.636V6.727zm-.545 10.455H7.09v-1.364h7.09v1.364zm2.727-3.273H7.091v-1.364h9.818zm0-3.273H7.091V9.273h9.818zM14.727 6h6l-6-6z"/></svg></g> <!-- GDocs -->
|
||||
<g transform="translate(864,911)"><rect width="22" height="22" rx="5" fill="#34A853"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M11.318 12.545H7.91v-1.909h3.41v1.91zM14.728 0v6h6zm1.363 10.636h-3.41v1.91h3.41zm0 3.273h-3.41v1.91h3.41zM20.727 6.5v15.864c0 .904-.732 1.636-1.636 1.636H4.909a1.636 1.636 0 0 1-1.636-1.636V1.636C3.273.732 4.005 0 4.909 0h9.318v6.5zm-3.273 2.773H6.545v7.909h10.91v-7.91zm-6.136 4.636H7.91v1.91h3.41v-1.91z"/></svg></g> <!-- GSheets -->
|
||||
<g transform="translate(894,911)"><rect width="22" height="22" rx="5" fill="#FBBC04"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M16.09 15.273H7.91v-4.637h8.18zm1.728-8.523h2.91v15.614c0 .904-.733 1.636-1.637 1.636H4.909a1.636 1.636 0 0 1-1.636-1.636V1.636C3.273.732 4.005 0 4.909 0h9.068v6.75zm-.363 2.523H6.545v7.363h10.91zm-2.728-5.979V6h6.001l-6-6v3.294z"/></svg></g> <!-- GSlides -->
|
||||
<g transform="translate(924,911)"><rect width="22" height="22" rx="5" fill="#4A154B"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M5.042 15.165a2.53 2.53 0 0 1-2.52 2.523A2.53 2.53 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52a2.527 2.527 0 0 1 2.521 2.52v6.313A2.53 2.53 0 0 1 8.834 24a2.53 2.53 0 0 1-2.521-2.522zM8.834 5.042a2.53 2.53 0 0 1-2.521-2.52A2.53 2.53 0 0 1 8.834 0a2.53 2.53 0 0 1 2.521 2.522v2.52zm0 1.271a2.53 2.53 0 0 1 2.521 2.521a2.53 2.53 0 0 1-2.521 2.521H2.522A2.53 2.53 0 0 1 0 8.834a2.53 2.53 0 0 1 2.522-2.521zm10.122 2.521a2.53 2.53 0 0 1 2.522-2.521A2.53 2.53 0 0 1 24 8.834a2.53 2.53 0 0 1-2.522 2.521h-2.522zm-1.268 0a2.53 2.53 0 0 1-2.523 2.521a2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.53 2.53 0 0 1 2.523 2.522zm-2.523 10.122a2.53 2.53 0 0 1 2.523 2.522A2.53 2.53 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522zm0-1.268a2.527 2.527 0 0 1-2.52-2.523a2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.53 2.53 0 0 1-2.522 2.523z"/></svg></g> <!-- Slack -->
|
||||
<g transform="translate(954,911)"><rect width="22" height="22" rx="5" fill="#5865F2"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M20.317 4.37a19.8 19.8 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.3 18.3 0 0 0-5.487 0a13 13 0 0 0-.617-1.25a.08.08 0 0 0-.079-.037A19.7 19.7 0 0 0 3.677 4.37a.1.1 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.08.08 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.08.08 0 0 0 .084-.028a14 14 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13 13 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10 10 0 0 0 .372-.292a.07.07 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.07.07 0 0 1 .078.01q.181.149.373.292a.077.077 0 0 1-.006.127a12.3 12.3 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.08.08 0 0 0 .084.028a19.8 19.8 0 0 0 6.002-3.03a.08.08 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.06.06 0 0 0-.031-.03M8.02 15.33c-1.182 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418m7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418"/></svg></g> <!-- Discord -->
|
||||
<g transform="translate(804,939)"><rect width="22" height="22" rx="5" fill="#26A5E4"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12a12 12 0 0 0 12-12A12 12 0 0 0 12 0zm4.962 7.224c.1-.002.321.023.465.14a.5.5 0 0 1 .171.325c.016.093.036.306.02.472c-.18 1.898-.962 6.502-1.36 8.627c-.168.9-.499 1.201-.82 1.23c-.696.065-1.225-.46-1.9-.902c-1.056-.693-1.653-1.124-2.678-1.8c-1.185-.78-.417-1.21.258-1.91c.177-.184 3.247-2.977 3.307-3.23c.007-.032.014-.15-.056-.212s-.174-.041-.249-.024q-.159.037-5.061 3.345q-.72.495-1.302.48c-.428-.008-1.252-.241-1.865-.44c-.752-.245-1.349-.374-1.297-.789q.04-.324.893-.663q5.247-2.286 6.998-3.014c3.332-1.386 4.025-1.627 4.476-1.635"/></svg></g> <!-- Telegram -->
|
||||
<g transform="translate(834,939)"><rect width="22" height="22" rx="5" fill="#181717"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></g> <!-- GitHub -->
|
||||
<g transform="translate(864,939)"><rect width="22" height="22" rx="5" fill="#5E6AD2"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M2.886 4.18A11.98 11.98 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556q-.787.496-1.65.866L.951 7.277q.371-.863.866-1.65ZM.322 9.163l14.515 14.515q-1.066.26-2.195.322L0 11.358a12 12 0 0 1 .322-2.195m-.17 4.862l9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z"/></svg></g> <!-- Linear -->
|
||||
<g transform="translate(894,939)"><rect width="22" height="22" rx="5" fill="#000000"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.981-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.841-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.748-.887l-15.177.887c-.56.047-.747.327-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514c-.748 0-.935-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233l4.764 7.279v-6.44l-1.215-.139c-.093-.514.28-.887.747-.933zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.934.653.934 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.447-1.632"/></svg></g> <!-- Notion -->
|
||||
<g transform="translate(924,939)"><rect width="22" height="22" rx="5" fill="#47A248"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M17.193 9.555c-1.264-5.58-4.252-7.414-4.573-8.115c-.28-.394-.53-.954-.735-1.44c-.036.495-.055.685-.523 1.184c-.723.566-4.438 3.682-4.74 10.02c-.282 5.912 4.27 9.435 4.888 9.884l.07.05A74 74 0 0 1 11.91 24h.481a29 29 0 0 1 .51-3.07c.417-.296.604-.463.85-.693a11.34 11.34 0 0 0 3.639-8.464c.01-.814-.103-1.662-.197-2.218m-5.336 8.195s0-8.291.275-8.29c.213 0 .49 10.695.49 10.695c-.381-.045-.765-1.76-.765-2.405"/></svg></g> <!-- MongoDB -->
|
||||
<text x="965" y="957" text-anchor="middle" font-size="16" font-weight="700" fill="#27272A" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">…</text>
|
||||
|
||||
<!-- ===================== CROSS-LAYER ARROWS ===================== -->
|
||||
<!-- AI Agent -> Mirage Shell (vertical) -->
|
||||
<line x1="470" y1="262" x2="470" y2="409" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="176" y="282" width="282" height="28" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1"/>
|
||||
<text x="450" y="302" text-anchor="end" font-size="18" font-family="'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace" fill="#27272A">grep alert /s3/log.jsonl</text>
|
||||
|
||||
<!-- AI Agent -> Mirage VFS (vertical) -->
|
||||
<line x1="680" y1="262" x2="680" y2="409" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="490" y="282" width="180" height="28" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1"/>
|
||||
<text x="662" y="302" text-anchor="end" font-size="18" font-family="'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace" fill="#27272A">stat /paper.pdf</text>
|
||||
|
||||
<!-- Application & CLI -> FUSE (vertical) -->
|
||||
<line x1="890" y1="262" x2="890" y2="409" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="902" y="282" width="200" height="28" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1"/>
|
||||
<text x="910" y="302" font-size="18" font-family="'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace" fill="#27272A">read /r2/data.csv</text>
|
||||
|
||||
<!-- Command Registry -> Dispatcher (vertical) -->
|
||||
<line x1="470" y1="543" x2="470" y2="666" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- VFS Registry -> Dispatcher (vertical) -->
|
||||
<line x1="680" y1="543" x2="680" y2="666" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dispatcher -> Infrastructure (vertical, aligned with CR -> Dispatcher connector at x=470) -->
|
||||
<line x1="470" y1="718" x2="470" y2="833" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dispatcher -> Remote Services (start x=680 matches PR -> Dispatcher; end x=890 matches Cache -> RS) -->
|
||||
<line x1="680" y1="718" x2="890" y2="833" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Cache - Remote Services (cache-hit short-circuit; dotted, no arrowhead) -->
|
||||
<line x1="890" y1="718" x2="890" y2="833" stroke="#27272A" stroke-width="1.2" stroke-dasharray="4 3"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 70 KiB |
@@ -0,0 +1,257 @@
|
||||
{
|
||||
"company": "Strukto",
|
||||
"founded": "2024-03-01",
|
||||
"valuation": 45000000,
|
||||
"departments": [
|
||||
{
|
||||
"name": "Engineering",
|
||||
"budget": 2500000,
|
||||
"quarterly_spend": [580000, 620000, 640000, 660000],
|
||||
"teams": [
|
||||
{
|
||||
"name": "Platform",
|
||||
"lead": "Alice Chen",
|
||||
"slack_channel": "#platform-eng",
|
||||
"oncall_rotation": ["Alice Chen", "Dan Lee", "Bob Park"],
|
||||
"members": [
|
||||
{"name": "Alice Chen", "role": "lead", "level": "senior", "salary": 185000, "skills": ["python", "go", "kubernetes"], "joined": "2024-03-15", "reviews": [{"quarter": "Q1-2025", "score": 4.8, "feedback": "exceptional technical leadership"}, {"quarter": "Q2-2025", "score": 4.7, "feedback": "drove mirage architecture"}]},
|
||||
{"name": "Bob Park", "role": "backend", "level": "mid", "salary": 140000, "skills": ["python", "postgres"], "joined": "2024-06-01", "reviews": [{"quarter": "Q1-2025", "score": 4.2, "feedback": "solid contributor"}, {"quarter": "Q2-2025", "score": 4.5, "feedback": "growing into senior scope"}]},
|
||||
{"name": "Carol Wu", "role": "backend", "level": "junior", "salary": 105000, "skills": ["python"], "joined": "2025-01-10", "reviews": [{"quarter": "Q1-2025", "score": 3.8, "feedback": "ramping up well"}]},
|
||||
{"name": "Dan Lee", "role": "devops", "level": "senior", "salary": 175000, "skills": ["terraform", "kubernetes", "aws", "gcp"], "joined": "2024-04-01", "reviews": [{"quarter": "Q1-2025", "score": 4.6, "feedback": "infra reliability improved 40%"}, {"quarter": "Q2-2025", "score": 4.4, "feedback": "mentoring junior engineers"}]}
|
||||
],
|
||||
"projects": [
|
||||
{
|
||||
"name": "mirage",
|
||||
"status": "active",
|
||||
"priority": 1,
|
||||
"started": "2025-09-15",
|
||||
"milestones": [
|
||||
{"name": "v0.1-alpha", "date": "2025-11-01", "completed": true},
|
||||
{"name": "v0.5-beta", "date": "2026-02-01", "completed": true},
|
||||
{"name": "v1.0-ga", "date": "2026-06-01", "completed": false}
|
||||
],
|
||||
"metrics": {"commits": 847, "open_issues": 23, "closed_issues": 312, "test_coverage": 0.87},
|
||||
"dependencies": ["pipeline-v2", "agent-sdk"]
|
||||
},
|
||||
{
|
||||
"name": "pipeline-v2",
|
||||
"status": "planning",
|
||||
"priority": 2,
|
||||
"started": null,
|
||||
"milestones": [],
|
||||
"metrics": {"commits": 0, "open_issues": 5, "closed_issues": 0, "test_coverage": null},
|
||||
"dependencies": []
|
||||
}
|
||||
],
|
||||
"incidents": [
|
||||
{"id": "INC-042", "severity": "p1", "title": "S3 mount timeout under load", "date": "2026-01-15", "resolved": true, "ttd_minutes": 12, "ttr_minutes": 45, "root_cause": "connection pool exhaustion", "owner": "Dan Lee"},
|
||||
{"id": "INC-051", "severity": "p2", "title": "Cache invalidation race condition", "date": "2026-02-20", "resolved": true, "ttd_minutes": 35, "ttr_minutes": 120, "root_cause": "missing lock on concurrent reads", "owner": "Alice Chen"},
|
||||
{"id": "INC-063", "severity": "p3", "title": "Memory leak in long-running grep pipes", "date": "2026-03-10", "resolved": false, "ttd_minutes": null, "ttr_minutes": null, "root_cause": null, "owner": "Bob Park"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AI",
|
||||
"lead": "Eve Santos",
|
||||
"slack_channel": "#ai-team",
|
||||
"oncall_rotation": ["Eve Santos", "Grace Kim"],
|
||||
"members": [
|
||||
{"name": "Eve Santos", "role": "lead", "level": "senior", "salary": 195000, "skills": ["python", "pytorch", "mlops", "cuda"], "joined": "2024-05-01", "reviews": [{"quarter": "Q1-2025", "score": 4.9, "feedback": "built ML platform from scratch"}, {"quarter": "Q2-2025", "score": 4.8, "feedback": "agent-sdk is company-defining"}]},
|
||||
{"name": "Frank Liu", "role": "ml-engineer", "level": "mid", "salary": 155000, "skills": ["python", "pytorch", "onnx"], "joined": "2024-09-15", "reviews": [{"quarter": "Q1-2025", "score": 4.0, "feedback": "good model optimization work"}, {"quarter": "Q2-2025", "score": 4.3, "feedback": "latency improvements impressive"}]},
|
||||
{"name": "Grace Kim", "role": "ml-engineer", "level": "senior", "salary": 180000, "skills": ["python", "jax", "triton", "cuda"], "joined": "2024-07-01", "reviews": [{"quarter": "Q1-2025", "score": 4.7, "feedback": "triton kernels are exceptional"}, {"quarter": "Q2-2025", "score": 4.6, "feedback": "eval framework design is solid"}]}
|
||||
],
|
||||
"projects": [
|
||||
{
|
||||
"name": "agent-sdk",
|
||||
"status": "active",
|
||||
"priority": 1,
|
||||
"started": "2025-11-01",
|
||||
"milestones": [
|
||||
{"name": "tool-use-v1", "date": "2026-01-15", "completed": true},
|
||||
{"name": "multi-agent", "date": "2026-04-01", "completed": false},
|
||||
{"name": "production-hardening", "date": "2026-07-01", "completed": false}
|
||||
],
|
||||
"metrics": {"commits": 523, "open_issues": 41, "closed_issues": 189, "test_coverage": 0.82},
|
||||
"dependencies": ["eval-framework"]
|
||||
},
|
||||
{
|
||||
"name": "eval-framework",
|
||||
"status": "active",
|
||||
"priority": 2,
|
||||
"started": "2026-01-10",
|
||||
"milestones": [
|
||||
{"name": "baseline-evals", "date": "2026-02-15", "completed": true},
|
||||
{"name": "regression-suite", "date": "2026-04-15", "completed": false}
|
||||
],
|
||||
"metrics": {"commits": 156, "open_issues": 12, "closed_issues": 67, "test_coverage": 0.91},
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "model-serving",
|
||||
"status": "completed",
|
||||
"priority": 3,
|
||||
"started": "2025-06-01",
|
||||
"milestones": [
|
||||
{"name": "single-model", "date": "2025-08-01", "completed": true},
|
||||
{"name": "multi-model", "date": "2025-10-15", "completed": true},
|
||||
{"name": "auto-scaling", "date": "2025-12-01", "completed": true}
|
||||
],
|
||||
"metrics": {"commits": 412, "open_issues": 3, "closed_issues": 201, "test_coverage": 0.94},
|
||||
"dependencies": []
|
||||
}
|
||||
],
|
||||
"incidents": [
|
||||
{"id": "INC-038", "severity": "p1", "title": "Model serving OOM on large batch", "date": "2025-12-05", "resolved": true, "ttd_minutes": 5, "ttr_minutes": 30, "root_cause": "unbounded batch accumulation", "owner": "Eve Santos"},
|
||||
{"id": "INC-055", "severity": "p2", "title": "Eval pipeline stale cache", "date": "2026-02-28", "resolved": true, "ttd_minutes": 60, "ttr_minutes": 180, "root_cause": "redis TTL misconfigured", "owner": "Frank Liu"}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Product",
|
||||
"budget": 1200000,
|
||||
"quarterly_spend": [280000, 290000, 310000, 320000],
|
||||
"teams": [
|
||||
{
|
||||
"name": "Growth",
|
||||
"lead": "Hank Brown",
|
||||
"slack_channel": "#growth",
|
||||
"oncall_rotation": ["Hank Brown"],
|
||||
"members": [
|
||||
{"name": "Hank Brown", "role": "lead", "level": "senior", "salary": 165000, "skills": ["analytics", "sql", "figma", "amplitude"], "joined": "2024-08-01", "reviews": [{"quarter": "Q1-2025", "score": 4.3, "feedback": "strong product instincts"}, {"quarter": "Q2-2025", "score": 4.5, "feedback": "onboarding conversion up 25%"}]},
|
||||
{"name": "Iris Zhang", "role": "designer", "level": "mid", "salary": 130000, "skills": ["figma", "css", "framer"], "joined": "2024-10-01", "reviews": [{"quarter": "Q1-2025", "score": 4.1, "feedback": "clean design system"}, {"quarter": "Q2-2025", "score": 4.4, "feedback": "user research driving decisions"}]},
|
||||
{"name": "Jack Patel", "role": "pm", "level": "mid", "salary": 145000, "skills": ["sql", "analytics", "linear"], "joined": "2025-02-01", "reviews": [{"quarter": "Q2-2025", "score": 4.0, "feedback": "good stakeholder management"}]}
|
||||
],
|
||||
"projects": [
|
||||
{
|
||||
"name": "onboarding-v3",
|
||||
"status": "active",
|
||||
"priority": 1,
|
||||
"started": "2026-02-01",
|
||||
"milestones": [
|
||||
{"name": "user-research", "date": "2026-02-15", "completed": true},
|
||||
{"name": "prototype", "date": "2026-03-15", "completed": true},
|
||||
{"name": "launch", "date": "2026-05-01", "completed": false}
|
||||
],
|
||||
"metrics": {"commits": 89, "open_issues": 8, "closed_issues": 34, "test_coverage": 0.72},
|
||||
"dependencies": ["agent-sdk"]
|
||||
},
|
||||
{
|
||||
"name": "dashboard-redesign",
|
||||
"status": "planning",
|
||||
"priority": 2,
|
||||
"started": null,
|
||||
"milestones": [],
|
||||
"metrics": {"commits": 0, "open_issues": 3, "closed_issues": 0, "test_coverage": null},
|
||||
"dependencies": ["onboarding-v3"]
|
||||
}
|
||||
],
|
||||
"incidents": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Infrastructure",
|
||||
"budget": 1800000,
|
||||
"quarterly_spend": [420000, 440000, 460000, 480000],
|
||||
"teams": [
|
||||
{
|
||||
"name": "SRE",
|
||||
"lead": "Karen Okafor",
|
||||
"slack_channel": "#sre",
|
||||
"oncall_rotation": ["Karen Okafor", "Leo Martinez", "Mia Tanaka"],
|
||||
"members": [
|
||||
{"name": "Karen Okafor", "role": "lead", "level": "senior", "salary": 190000, "skills": ["kubernetes", "prometheus", "grafana", "terraform"], "joined": "2024-03-15", "reviews": [{"quarter": "Q1-2025", "score": 4.7, "feedback": "observability stack is world-class"}, {"quarter": "Q2-2025", "score": 4.8, "feedback": "zero p1 incidents in Q2"}]},
|
||||
{"name": "Leo Martinez", "role": "sre", "level": "mid", "salary": 150000, "skills": ["kubernetes", "python", "prometheus"], "joined": "2025-03-01", "reviews": [{"quarter": "Q2-2025", "score": 4.2, "feedback": "good runbook documentation"}]},
|
||||
{"name": "Mia Tanaka", "role": "sre", "level": "senior", "salary": 178000, "skills": ["aws", "gcp", "terraform", "ansible"], "joined": "2024-06-15", "reviews": [{"quarter": "Q1-2025", "score": 4.5, "feedback": "multi-cloud migration on track"}, {"quarter": "Q2-2025", "score": 4.6, "feedback": "cost optimization saved 30%"}]}
|
||||
],
|
||||
"projects": [
|
||||
{
|
||||
"name": "multi-cloud",
|
||||
"status": "active",
|
||||
"priority": 1,
|
||||
"started": "2025-07-01",
|
||||
"milestones": [
|
||||
{"name": "aws-baseline", "date": "2025-09-01", "completed": true},
|
||||
{"name": "gcp-parity", "date": "2026-03-01", "completed": true},
|
||||
{"name": "failover-testing", "date": "2026-06-01", "completed": false}
|
||||
],
|
||||
"metrics": {"commits": 334, "open_issues": 15, "closed_issues": 178, "test_coverage": 0.79},
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "cost-optimization",
|
||||
"status": "active",
|
||||
"priority": 2,
|
||||
"started": "2026-01-01",
|
||||
"milestones": [
|
||||
{"name": "audit", "date": "2026-01-15", "completed": true},
|
||||
{"name": "reserved-instances", "date": "2026-02-15", "completed": true},
|
||||
{"name": "spot-fleet", "date": "2026-04-01", "completed": false}
|
||||
],
|
||||
"metrics": {"commits": 67, "open_issues": 4, "closed_issues": 29, "test_coverage": 0.85},
|
||||
"dependencies": ["multi-cloud"]
|
||||
}
|
||||
],
|
||||
"incidents": [
|
||||
{"id": "INC-029", "severity": "p1", "title": "Full cluster outage during upgrade", "date": "2025-10-22", "resolved": true, "ttd_minutes": 3, "ttr_minutes": 90, "root_cause": "incompatible CRD version", "owner": "Karen Okafor"},
|
||||
{"id": "INC-044", "severity": "p1", "title": "DNS resolution failure", "date": "2026-01-20", "resolved": true, "ttd_minutes": 8, "ttr_minutes": 25, "root_cause": "coredns pods evicted by OOM", "owner": "Leo Martinez"},
|
||||
{"id": "INC-058", "severity": "p2", "title": "Prometheus storage full", "date": "2026-03-05", "resolved": true, "ttd_minutes": 45, "ttr_minutes": 60, "root_cause": "retention policy too aggressive", "owner": "Mia Tanaka"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"locations": [
|
||||
{"city": "San Francisco", "country": "US", "timezone": "America/Los_Angeles", "headcount": 7, "office_type": "hq", "monthly_cost": 85000, "amenities": ["gym", "cafeteria", "parking"]},
|
||||
{"city": "Seoul", "country": "KR", "timezone": "Asia/Seoul", "headcount": 3, "office_type": "coworking", "monthly_cost": 12000, "amenities": ["meeting_rooms"]},
|
||||
{"city": "Toronto", "country": "CA", "timezone": "America/Toronto", "headcount": 2, "office_type": "remote", "monthly_cost": 3000, "amenities": []},
|
||||
{"city": "London", "country": "GB", "timezone": "Europe/London", "headcount": 1, "office_type": "remote", "monthly_cost": 2500, "amenities": []}
|
||||
],
|
||||
"vendor_contracts": [
|
||||
{"vendor": "AWS", "service": "cloud", "annual_cost": 960000, "renewal": "2027-01-01", "tier": "enterprise"},
|
||||
{"vendor": "GCP", "service": "cloud", "annual_cost": 480000, "renewal": "2026-09-01", "tier": "premium"},
|
||||
{"vendor": "Datadog", "service": "observability", "annual_cost": 120000, "renewal": "2026-06-01", "tier": "pro"},
|
||||
{"vendor": "Linear", "service": "project-management", "annual_cost": 7200, "renewal": "2026-04-01", "tier": "business"},
|
||||
{"vendor": "Figma", "service": "design", "annual_cost": 4800, "renewal": "2026-07-01", "tier": "organization"},
|
||||
{"vendor": "Anthropic", "service": "ai-api", "annual_cost": 240000, "renewal": "2026-12-01", "tier": "enterprise"}
|
||||
],
|
||||
"okrs": [
|
||||
{
|
||||
"quarter": "Q1-2026",
|
||||
"objectives": [
|
||||
{
|
||||
"title": "Launch mirage v1.0",
|
||||
"key_results": [
|
||||
{"description": "Ship GA release", "target": 1, "actual": 0, "unit": "release"},
|
||||
{"description": "Onboard 10 enterprise customers", "target": 10, "actual": 4, "unit": "customers"},
|
||||
{"description": "Achieve 99.9% uptime", "target": 99.9, "actual": 99.7, "unit": "percent"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Build world-class AI capabilities",
|
||||
"key_results": [
|
||||
{"description": "Launch agent-sdk multi-agent", "target": 1, "actual": 0, "unit": "release"},
|
||||
{"description": "Eval coverage across 50 benchmarks", "target": 50, "actual": 32, "unit": "benchmarks"},
|
||||
{"description": "P95 inference latency under 200ms", "target": 200, "actual": 185, "unit": "ms"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Operational excellence",
|
||||
"key_results": [
|
||||
{"description": "Mean time to detect under 15 min", "target": 15, "actual": 22, "unit": "minutes"},
|
||||
{"description": "Mean time to resolve under 60 min", "target": 60, "actual": 75, "unit": "minutes"},
|
||||
{"description": "Cloud cost reduction 20%", "target": 20, "actual": 30, "unit": "percent"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"version": "3.0",
|
||||
"last_updated": "2026-03-25T00:00:00Z",
|
||||
"total_headcount": 13,
|
||||
"total_budget": 5500000,
|
||||
"fiscal_year": "2026",
|
||||
"data_classification": "internal"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
{
|
||||
"$schema": "https://mintlify.com/docs.json",
|
||||
"theme": "mint",
|
||||
"name": "Mirage",
|
||||
"colors": {
|
||||
"primary": "#0C0C0C",
|
||||
"light": "#FAFAFA",
|
||||
"dark": "#000000"
|
||||
},
|
||||
"logo": {
|
||||
"light": "/logo/light.svg",
|
||||
"dark": "/logo/dark.svg"
|
||||
},
|
||||
"favicon": "/favicon.svg",
|
||||
"contextual": {
|
||||
"options": [
|
||||
"copy",
|
||||
"view",
|
||||
"chatgpt",
|
||||
"claude",
|
||||
"perplexity",
|
||||
"mcp",
|
||||
"cursor",
|
||||
"vscode"
|
||||
]
|
||||
},
|
||||
"seo": {
|
||||
"metatags": {
|
||||
"charset": "UTF-8",
|
||||
"viewport": "width=device-width, initial-scale=1.0",
|
||||
"description": "Mirage gives AI agents one virtual filesystem for S3, Google Drive, Slack, GitHub, Notion, Postgres, and SSH, so shell commands can read, write, and pipe across services.",
|
||||
"keywords": "Mirage, filesystem for AI agents, virtual filesystem, AI agent tools, agent infrastructure, MCP alternative, bash for agents, S3 filesystem, Google Drive filesystem, Slack filesystem, GitHub filesystem, agent sandbox, Strukto",
|
||||
"author": "Strukto",
|
||||
"robots": "index, follow",
|
||||
"googlebot": "index, follow",
|
||||
"canonical": "https://docs.mirage.strukto.ai",
|
||||
"theme-color": "#0C0C0C",
|
||||
"color-scheme": "light dark",
|
||||
"language": "en",
|
||||
"category": "Technology",
|
||||
"target": "developers",
|
||||
"title": "Mirage · Unified Virtual Filesystem for AI Agents | Strukto",
|
||||
"application-name": "Mirage",
|
||||
"apple-mobile-web-app-title": "Mirage",
|
||||
"apple-mobile-web-app-capable": "yes",
|
||||
"apple-mobile-web-app-status-bar-style": "black-translucent",
|
||||
"og:title": "Mirage · Unified Virtual Filesystem for AI Agents | Strukto",
|
||||
"og:type": "website",
|
||||
"og:url": "https://docs.mirage.strukto.ai",
|
||||
"og:image": "https://raw.githubusercontent.com/strukto-ai/mirage/main/assets/mirage-og-light.png",
|
||||
"og:image:alt": "Mirage virtual filesystem for AI agents",
|
||||
"og:description": "Mount S3, Google Drive, Slack, GitHub, Notion, Postgres, SSH, and more behind one filesystem. Agents read, write, and pipe across services with shell commands.",
|
||||
"og:site_name": "Mirage",
|
||||
"og:locale": "en_US",
|
||||
"twitter:card": "summary_large_image",
|
||||
"twitter:site": "@struktoai",
|
||||
"twitter:creator": "@struktoai",
|
||||
"twitter:title": "Mirage · Unified Virtual Filesystem for AI Agents | Strukto",
|
||||
"twitter:description": "Mount S3, Google Drive, Slack, GitHub, Notion, Postgres, SSH, and more behind one filesystem. Agents read, write, and pipe across services with familiar shell commands.",
|
||||
"twitter:image": "https://raw.githubusercontent.com/strukto-ai/mirage/main/assets/mirage-og-light.png",
|
||||
"twitter:image:alt": "Mirage · Unified Virtual Filesystem for AI Agents | Strukto"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Home",
|
||||
"icon": "/images/mirage-icon-light.svg",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Getting Started",
|
||||
"pages": [
|
||||
"home/introduction",
|
||||
"home/install",
|
||||
"home/cli"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Learn",
|
||||
"pages": [
|
||||
"home/architecture",
|
||||
"home/resource-matrix"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Help",
|
||||
"pages": [
|
||||
"home/troubleshooting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Setup",
|
||||
"pages": [
|
||||
{
|
||||
"group": "FUSE",
|
||||
"icon": "hard-drive",
|
||||
"pages": [
|
||||
"home/setup/macos",
|
||||
"home/setup/linux"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Object Storage",
|
||||
"icon": "cloud",
|
||||
"pages": [
|
||||
"home/setup/s3",
|
||||
"home/setup/r2",
|
||||
"home/setup/gcs",
|
||||
"home/setup/oci",
|
||||
"home/setup/supabase"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Google Workspace",
|
||||
"icon": "google",
|
||||
"pages": [
|
||||
"home/setup/google"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Cloud Files",
|
||||
"icon": "folder-open",
|
||||
"pages": [
|
||||
"home/setup/dropbox",
|
||||
"home/setup/box"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Code & DevOps",
|
||||
"icon": "code",
|
||||
"pages": [
|
||||
"home/setup/github",
|
||||
"home/setup/github_ci",
|
||||
"home/setup/linear",
|
||||
"home/setup/langfuse"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Messaging",
|
||||
"icon": "comments",
|
||||
"pages": [
|
||||
"home/setup/slack",
|
||||
"home/setup/discord",
|
||||
"home/setup/telegram",
|
||||
"home/setup/email"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Database",
|
||||
"icon": "database",
|
||||
"pages": [
|
||||
"home/setup/mongodb",
|
||||
"home/setup/postgres"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Notes",
|
||||
"icon": "book",
|
||||
"pages": [
|
||||
"home/setup/notion"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Others",
|
||||
"icon": "ellipsis",
|
||||
"pages": [
|
||||
"home/setup/trello",
|
||||
"home/setup/paperclip",
|
||||
"home/setup/sscholar"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Python",
|
||||
"icon": "/images/python-logo.svg",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Getting Started",
|
||||
"pages": [
|
||||
"python/install",
|
||||
"python/quickstart",
|
||||
"python/setup/fuse"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Agents",
|
||||
"icon": "robot",
|
||||
"pages": [
|
||||
"python/agents/index",
|
||||
"python/agents/openai-agents",
|
||||
"python/agents/langchain",
|
||||
"python/agents/openhands",
|
||||
"python/agents/pydantic-ai",
|
||||
"python/agents/camel",
|
||||
"python/agents/claude-code",
|
||||
"python/agents/codex"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Resources",
|
||||
"pages": [
|
||||
"python/resource/index",
|
||||
{
|
||||
"group": "Infrastructure",
|
||||
"icon": "server",
|
||||
"pages": [
|
||||
"python/resource/disk",
|
||||
"python/resource/ram",
|
||||
"python/resource/redis",
|
||||
"python/resource/ssh"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Object Storage",
|
||||
"icon": "cloud",
|
||||
"pages": [
|
||||
"python/resource/s3",
|
||||
"python/resource/r2",
|
||||
"python/resource/gcs",
|
||||
"python/resource/supabase",
|
||||
"python/resource/oci"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Google Workspace",
|
||||
"icon": "google",
|
||||
"pages": [
|
||||
"python/resource/gmail",
|
||||
"python/resource/gdrive",
|
||||
"python/resource/gdocs",
|
||||
"python/resource/gsheets",
|
||||
"python/resource/gslides"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Code & DevOps",
|
||||
"icon": "code",
|
||||
"pages": [
|
||||
"python/resource/github",
|
||||
"python/resource/github_ci",
|
||||
"python/resource/linear",
|
||||
"python/resource/langfuse"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Messaging",
|
||||
"icon": "comments",
|
||||
"pages": [
|
||||
"python/resource/slack",
|
||||
"python/resource/discord",
|
||||
"python/resource/telegram",
|
||||
"python/resource/email"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Database",
|
||||
"icon": "database",
|
||||
"pages": [
|
||||
"python/resource/mongodb",
|
||||
"python/resource/postgres"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Notes",
|
||||
"icon": "book",
|
||||
"pages": [
|
||||
"python/resource/notion"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Others",
|
||||
"icon": "ellipsis",
|
||||
"pages": [
|
||||
"python/resource/trello",
|
||||
"python/resource/paperclip"
|
||||
]
|
||||
},
|
||||
"python/resource/new"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "TS",
|
||||
"icon": "/images/typescript-logo.svg",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Getting Started",
|
||||
"pages": [
|
||||
"typescript/install",
|
||||
"typescript/quickstart",
|
||||
"typescript/server-and-cli",
|
||||
"typescript/setup/fuse",
|
||||
"typescript/python",
|
||||
"typescript/python-fs",
|
||||
"typescript/limitations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Agents",
|
||||
"icon": "robot",
|
||||
"pages": [
|
||||
"typescript/agents/index",
|
||||
"typescript/agents/openai",
|
||||
"typescript/agents/langchain",
|
||||
"typescript/agents/pi",
|
||||
"typescript/agents/vercel",
|
||||
"typescript/agents/mastra",
|
||||
"typescript/agents/claude-code",
|
||||
"typescript/agents/codex"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Resources",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Infrastructure",
|
||||
"icon": "server",
|
||||
"pages": [
|
||||
"typescript/setup/disk",
|
||||
"typescript/setup/opfs",
|
||||
"typescript/setup/redis",
|
||||
"typescript/setup/ssh"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Object Storage",
|
||||
"icon": "cloud",
|
||||
"pages": [
|
||||
"typescript/setup/s3",
|
||||
"typescript/setup/r2",
|
||||
"typescript/setup/gcs",
|
||||
"typescript/setup/oci",
|
||||
"typescript/setup/supabase"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Google Workspace",
|
||||
"icon": "google",
|
||||
"pages": [
|
||||
"typescript/setup/gmail",
|
||||
"typescript/setup/gdrive",
|
||||
"typescript/setup/gdocs",
|
||||
"typescript/setup/gsheets",
|
||||
"typescript/setup/gslides"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Cloud Files",
|
||||
"icon": "folder-open",
|
||||
"pages": [
|
||||
"typescript/setup/dropbox",
|
||||
"typescript/setup/box"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Code & DevOps",
|
||||
"icon": "code",
|
||||
"pages": [
|
||||
"typescript/setup/github",
|
||||
"typescript/setup/github_ci",
|
||||
"typescript/setup/linear",
|
||||
"typescript/setup/langfuse"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Messaging",
|
||||
"icon": "comments",
|
||||
"pages": [
|
||||
"typescript/slack",
|
||||
"typescript/discord",
|
||||
"typescript/setup/email"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Database",
|
||||
"icon": "database",
|
||||
"pages": [
|
||||
"typescript/setup/mongodb",
|
||||
"typescript/setup/postgres"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Notes",
|
||||
"icon": "book",
|
||||
"pages": [
|
||||
"typescript/setup/notion"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Others",
|
||||
"icon": "ellipsis",
|
||||
"pages": [
|
||||
"typescript/setup/trello",
|
||||
"typescript/setup/sscholar",
|
||||
"typescript/setup/posthog",
|
||||
"typescript/setup/vercel"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"navbar": {
|
||||
"links": [
|
||||
{
|
||||
"label": "mirage",
|
||||
"icon": "globe",
|
||||
"href": "https://www.strukto.ai/mirage"
|
||||
},
|
||||
{
|
||||
"label": "Discord",
|
||||
"icon": "discord",
|
||||
"href": "https://discord.gg/u8BPQ65KsS"
|
||||
},
|
||||
{
|
||||
"label": "",
|
||||
"icon": "x-twitter",
|
||||
"href": "https://x.com/struktoai"
|
||||
},
|
||||
{
|
||||
"label": "strukto-ai/mirage",
|
||||
"icon": "github",
|
||||
"href": "https://github.com/strukto-ai/mirage"
|
||||
}
|
||||
]
|
||||
},
|
||||
"footer": {
|
||||
"socials": {
|
||||
"github": "https://github.com/strukto-ai/mirage"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 160 160"
|
||||
width="160" height="160">
|
||||
|
||||
<polygon points="19,45 80,10 80,45 50,63"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3"/>
|
||||
<polygon points="80,10 141,45 110,63 80,45"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3"/>
|
||||
<polygon points="141,45 141,115 110,98 110,63"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3"/>
|
||||
<polygon points="141,115 80,150 80,115 110,98"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3"/>
|
||||
<polygon points="80,150 19,115 50,98 80,115"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3"/>
|
||||
<polygon points="19,115 19,45 50,63 50,98"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3"/>
|
||||
|
||||
<polygon points="80,45 110,63 110,98 80,115 50,98 50,63"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3"/>
|
||||
|
||||
<line x1="80" y1="80" x2="50" y2="63"
|
||||
stroke="#ffffff" stroke-width="3"/>
|
||||
<line x1="80" y1="80" x2="110" y2="63"
|
||||
stroke="#ffffff" stroke-width="3"/>
|
||||
<line x1="80" y1="80" x2="80" y2="115"
|
||||
stroke="#ffffff" stroke-width="3"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: Architecture
|
||||
description: Four layers, one filesystem. How Mirage turns mounted services into one bash-driven environment.
|
||||
icon: cube
|
||||
---
|
||||
|
||||
<div className="my-6">
|
||||
<img noZoom src="/images/mirage-arch-light.svg" alt="Mirage architecture" className="block dark:hidden w-full" />
|
||||
<img noZoom src="/images/mirage-arch-dark.svg" alt="Mirage architecture" className="hidden dark:block w-full" />
|
||||
</div>
|
||||
|
||||
Mirage stacks four thin layers between an agent and the services it touches.
|
||||
|
||||
## 1. AI Agent and Application
|
||||
|
||||
The agent (or any application embedding Mirage) issues bash commands, VFS calls, or syscalls. One vocabulary, every backend.
|
||||
|
||||
## 2. Mirage Bash and VFS
|
||||
|
||||
The action surface. **Mirage Bash** parses commands with tree-sitter and runs them against the **Mirage VFS**, a unified filesystem API over every mount. A **FUSE Adapter** exposes the same tree to host tools when you want it. A **Command Registry** and **VFS Registry** describe what verbs and resources are available.
|
||||
|
||||
## 3. Dispatcher & Cache
|
||||
|
||||
The **Mirage Dispatcher** routes each operation to the mount that owns the path, joining pipelines that span systems. The **Index & File Cache** absorbs repeats: the first directory walk hits the API, the next serves from cache; the first read streams bytes, later reads are local.
|
||||
|
||||
## 4. Infrastructure and Remote
|
||||
|
||||
Whatever you mount: RAM, Disk, Redis, S3 / R2 / GCS / OCI / Supabase, Gmail / GDrive / GDocs / GSheets / GSlides, GitHub / Linear / Notion / Trello, Slack / Discord / Telegram / Email, MongoDB, SSH, and more. Each speaks the same filesystem semantics from the agent's point of view.
|
||||
|
||||
Browse the [Resource Matrix](/home/resource-matrix) for the full list.
|
||||
|
||||
## Where to go next
|
||||
|
||||
- [Resource Matrix](/home/resource-matrix) to pick a backend to mount.
|
||||
- [Python Quickstart](/python/quickstart) for working code in minutes.
|
||||
- [TypeScript Quickstart](/typescript/quickstart) for the same Workspace API in Node, browser, or edge.
|
||||
@@ -0,0 +1,271 @@
|
||||
---
|
||||
title: CLI
|
||||
description: Drive Mirage from the shell. Spin up a workspace from YAML, run commands against your mounts, snapshot and restore.
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
The `mirage` CLI is a thin httpx wrapper over the Mirage daemon. It
|
||||
auto-spawns the daemon on first `workspace create`, and the daemon
|
||||
auto-exits 30 seconds after the last workspace is deleted. Most users
|
||||
never type a daemon command directly.
|
||||
|
||||
Output is structured JSON to stdout for every verb -- pipe to `jq`,
|
||||
save to file, or read it directly.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
curl -fsSL https://strukto.ai/install.sh | sh
|
||||
```
|
||||
|
||||
Or via your package manager of choice:
|
||||
|
||||
```bash
|
||||
brew install mirage
|
||||
```
|
||||
|
||||
```bash
|
||||
uv add mirage-ai
|
||||
```
|
||||
|
||||
```bash
|
||||
pip install mirage-ai
|
||||
```
|
||||
|
||||
```bash
|
||||
uvx mirage-ai
|
||||
```
|
||||
|
||||
```bash
|
||||
npx mirage
|
||||
```
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
mirage --help
|
||||
```
|
||||
|
||||
## Define a workspace in YAML
|
||||
|
||||
A workspace is a set of prefixed mounts plus some workspace-level
|
||||
settings. Save this as `workspace.yaml`:
|
||||
|
||||
```yaml
|
||||
mode: WRITE
|
||||
|
||||
mounts:
|
||||
/:
|
||||
resource: ram
|
||||
mode: WRITE
|
||||
/s3:
|
||||
resource: s3
|
||||
mode: READ
|
||||
config:
|
||||
bucket: ${AWS_S3_BUCKET}
|
||||
region: ${AWS_DEFAULT_REGION}
|
||||
aws_access_key_id: ${AWS_ACCESS_KEY_ID}
|
||||
aws_secret_access_key: ${AWS_SECRET_ACCESS_KEY}
|
||||
```
|
||||
|
||||
`${VAR}` placeholders are interpolated from your shell environment at
|
||||
`mirage workspace create` time. Missing vars fail fast with the full
|
||||
list, not lazily on first use.
|
||||
|
||||
## Walkthrough
|
||||
|
||||
A guided tour from creating a workspace to snapshotting it. Each
|
||||
step builds on the previous one; you can copy them in order.
|
||||
|
||||
For a runnable end-to-end version against a real multi-mount workspace
|
||||
(`/s3`, `/gdrive`, `/gmail`, `/slack`, `/discord`), see
|
||||
<a href="https://github.com/strukto-ai/mirage/blob/main/examples/python/cross/README.md" target="_blank" rel="noopener noreferrer"><code>examples/python/cross/README.md</code></a>.
|
||||
|
||||
### 1. Source env and create a workspace
|
||||
|
||||
The YAML's `${...}` placeholders resolve from your shell at create
|
||||
time, so source your env first. The daemon auto-spawns on the first
|
||||
`create`.
|
||||
|
||||
```bash
|
||||
set -a && source .env.development && set +a
|
||||
|
||||
mirage workspace create workspace.yaml --id demo
|
||||
```
|
||||
|
||||
### 2. Inspect
|
||||
|
||||
`list` is one line per workspace; `get` returns the full mount and
|
||||
session detail.
|
||||
|
||||
```bash
|
||||
mirage workspace list
|
||||
mirage workspace get demo
|
||||
```
|
||||
|
||||
### 3. Run commands against your mounts
|
||||
|
||||
`execute` runs a shell command inside the workspace. Paths resolve
|
||||
through the mount registry (`/s3/...` hits S3, `/` hits the RAM
|
||||
backing, etc.).
|
||||
|
||||
```bash
|
||||
mirage execute --workspace_id demo --command "ls /s3/"
|
||||
mirage execute --workspace_id demo --command "head -n 1 /s3/data/example.jsonl"
|
||||
```
|
||||
|
||||
### 4. Pipe stdin
|
||||
|
||||
When stdout isn't a TTY, the CLI forwards stdin to the command
|
||||
automatically.
|
||||
|
||||
```bash
|
||||
echo -e "a\nb\nc" | mirage execute --workspace_id demo --command "wc -l"
|
||||
```
|
||||
|
||||
### 5. Dry-run with `provision`
|
||||
|
||||
`provision` returns a `ProvisionResult` (network bytes, cache hits,
|
||||
estimated cost) without running the command -- handy for predicting
|
||||
spend before kicking off an expensive read.
|
||||
|
||||
```bash
|
||||
mirage provision --workspace_id demo \
|
||||
--command "cat /s3/data/example.jsonl | wc -l"
|
||||
```
|
||||
|
||||
### 6. Cache: network → hit after a real read
|
||||
|
||||
After a real `cat`, `provision` flips that path from a network read
|
||||
to a cache hit (`cache_hits=1`).
|
||||
|
||||
```bash
|
||||
mirage execute --workspace_id demo --command "cat /s3/data/example.jsonl > /dev/null"
|
||||
mirage provision --workspace_id demo --command "cat /s3/data/example.jsonl"
|
||||
```
|
||||
|
||||
### 7. Session traces
|
||||
|
||||
Every session writes a JSONL trace under `/.sessions/<utc-yyyy-mm-dd>/`.
|
||||
|
||||
```bash
|
||||
mirage execute --workspace_id demo --command "ls /.sessions/"
|
||||
DAY=$(date -u +%Y-%m-%d)
|
||||
mirage execute --workspace_id demo --command "head -n 1 /.sessions/$DAY/*.jsonl" \
|
||||
| jq -r .stdout | jq
|
||||
```
|
||||
|
||||
### 8. Background jobs
|
||||
|
||||
Long-running commands take `--background` and return a `job_id`
|
||||
immediately. `mirage job wait` blocks until it's done.
|
||||
|
||||
```bash
|
||||
JOB=$(mirage execute --workspace_id demo --background \
|
||||
--command "wc -l /s3/data/example.jsonl" \
|
||||
| jq -r .job_id)
|
||||
mirage job wait $JOB
|
||||
```
|
||||
|
||||
### 9. Snapshot to disk
|
||||
|
||||
```bash
|
||||
mirage workspace snapshot demo /tmp/demo.tar
|
||||
```
|
||||
|
||||
### 10. Restore from snapshot
|
||||
|
||||
Snapshots redact cloud creds at save time, so loading needs fresh
|
||||
creds via `--override`. The same workspace YAML is a valid override.
|
||||
|
||||
```bash
|
||||
mirage workspace load /tmp/demo.tar \
|
||||
--id demo_loaded \
|
||||
--override workspace.yaml
|
||||
|
||||
mirage workspace get demo_loaded --verbose
|
||||
mirage execute --workspace_id demo_loaded \
|
||||
--command "head -n 1 /s3/data/example.jsonl"
|
||||
```
|
||||
|
||||
<Note>
|
||||
`--override` is required for any mount whose resource declares
|
||||
`needs_override=True` (S3, GDrive, Slack, Discord, Redis, ...).
|
||||
The snapshot stores `"<REDACTED>"` in place of secrets at save
|
||||
time. Loading without `--override` 400s with the list of prefixes
|
||||
that need fresh creds. Local resources (RAM, Disk) restore as-is
|
||||
with no override needed.
|
||||
</Note>
|
||||
|
||||
### 11. Clean up
|
||||
|
||||
The daemon exits ~30s after the last workspace is deleted.
|
||||
|
||||
```bash
|
||||
mirage workspace delete demo
|
||||
mirage workspace delete demo_loaded
|
||||
```
|
||||
|
||||
## Verbs at a glance
|
||||
|
||||
| Verb | What it does |
|
||||
| --- | --- |
|
||||
| `mirage workspace create FILE [--id NAME]` | Build resources from YAML, register a workspace, return its id. |
|
||||
| `mirage workspace list` | Brief one-line summary per active workspace. |
|
||||
| `mirage workspace get ID [--verbose]` | Full detail (mounts, sessions, optionally cache / dirty / history internals). |
|
||||
| `mirage workspace delete ID` | Stop the workspace; daemon may exit on the idle timer. |
|
||||
| `mirage workspace clone ID [--id NAME] [--override FILE]` | Fresh local backings, shared remote resources; partial-config override swaps creds or buckets. |
|
||||
| `mirage workspace snapshot ID PATH.tar` | Snapshot to a tar file. |
|
||||
| `mirage workspace load PATH.tar [--id NAME] [--override FILE]` | Restore from tar; override re-supplies redacted creds. |
|
||||
| `mirage session create WS [--id NAME]` | Add a named session (own cwd + env). |
|
||||
| `mirage session list WS` | List sessions for a workspace. |
|
||||
| `mirage session delete WS SESSION` | Close a session. |
|
||||
| `mirage execute --workspace_id WS [--session_id S] [--background] --command "..."` | Run a command. Pipes stdin automatically when stdout is not a TTY. |
|
||||
| `mirage provision --workspace_id WS [--session_id S] --command "..."` | Dry-run / cost estimate -- returns a `ProvisionResult` shape (network bytes, cache hits, estimated cost) without running the command. |
|
||||
| `mirage job list [--workspace_id WS]` | List jobs the daemon has run, plus their status. |
|
||||
| `mirage job get JOB` | Detail for one job. |
|
||||
| `mirage job wait JOB [--timeout SECS]` | Block until the job is done; returns the result. |
|
||||
| `mirage job cancel JOB` | Cancel a running job. |
|
||||
|
||||
## Daemon control
|
||||
|
||||
Most users never run these directly -- the daemon auto-spawns on
|
||||
first `workspace create` and auto-exits after the idle timer fires
|
||||
(default 30s after the last workspace is deleted). When you need to
|
||||
intervene -- typically during development, when you want code changes
|
||||
to take effect:
|
||||
|
||||
```bash
|
||||
mirage daemon status # health, PID, uptime, workspace count
|
||||
mirage daemon stop # graceful: trip exit event, falls back to SIGTERM after --timeout (default 5s)
|
||||
mirage daemon restart # stop + lazy respawn (add --eager to spawn now)
|
||||
mirage daemon kill # SIGKILL via PID file -- last resort
|
||||
```
|
||||
|
||||
| Verb | What it does |
|
||||
| --- | --- |
|
||||
| `mirage daemon status` | Daemon health, PID, uptime, workspace count. Exit 1 if daemon not reachable. |
|
||||
| `mirage daemon stop` | `POST /v1/shutdown` to trip the daemon's exit event. Daemon snapshots active workspaces (if `persist_dir` set), closes them, exits. Falls back to SIGTERM on `--timeout`. |
|
||||
| `mirage daemon restart` | Stop, then either wait for next `workspace create` to auto-spawn (default) or `--eager` to spawn immediately. Workspaces are LOST unless `persist_dir` is configured. |
|
||||
| `mirage daemon kill` | SIGKILL via PID file at `~/.mirage/daemon.pid`. Skips graceful shutdown -- use only when `stop` hangs. |
|
||||
|
||||
<Note>
|
||||
When you change Mirage's source code (commands, providers, etc.),
|
||||
the running daemon won't see your changes -- it loaded the old code
|
||||
at startup. Run `mirage daemon restart` to pick up new code.
|
||||
Same situation as `dockerd` after recompiling Docker.
|
||||
</Note>
|
||||
|
||||
## Where the daemon lives
|
||||
|
||||
Most users never need to think about the daemon. If you do:
|
||||
|
||||
- It listens on `http://127.0.0.1:8765` by default.
|
||||
- Override via `MIRAGE_DAEMON_URL` env var or `~/.mirage/config.toml`:
|
||||
```toml
|
||||
[daemon]
|
||||
url = "http://127.0.0.1:8765"
|
||||
idle_grace_seconds = 30
|
||||
```
|
||||
- Logs go to `~/.mirage/daemon.log` when the CLI auto-spawns it.
|
||||
- It exits 30 seconds after the workspace count hits zero (configurable).
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Concepts
|
||||
description: Learn the core Mirage model before choosing a resource or SDK entrypoint.
|
||||
icon: cube
|
||||
---
|
||||
|
||||
## The Mirage Mental Model
|
||||
|
||||
Mirage turns external systems into a virtual filesystem so agents and tools can use one consistent interface instead of resource-specific APIs.
|
||||
|
||||
## The Pieces, In One Breath
|
||||
|
||||
- <Icon icon="eye" /> **[Eyes](/home/design/eyes)**, how agents see. Mounted resources expose external systems as paths.
|
||||
- <Icon icon="hand" /> **[Hands](/home/design/hands)**, how agents act. Read, write, search, transform, compose through byte streams.
|
||||
- <Icon icon="brain" /> **Brain** (the Agent), orchestrates. Decides which Hands to use, spends tokens reasoning, and owns what gets done.
|
||||
- <Icon icon="user" /> **[Arm](/home/design/arm)**, Mirage's reach. Extends the agent from the Brain into external systems. Expands into Workspace, Mounts, Sessions, and Shell.
|
||||
- <Icon icon="terminal" /> **[Shell](/home/design/pipeline)**, the wiring inside the Arm. Turns a sentence like `cat /s3/*.csv | grep error` into coordinated eye-and-hand movement.
|
||||
- <Icon icon="hand-point-up" /> **[Fingers](/home/design/fingers)**, the primitive gestures every backend implements: ops (`read`, `readdir`, `stat`, `write`, `mkdir`, `unlink`, `rename`) plus FUSE to expose them via POSIX. The fundamental infrastructure layer.
|
||||
- <Icon icon="hand-sparkles" /> **[Gestures](/home/design/gestures)**, named commands built on fingers. `cat`, `grep`, `cp`, `jq`, each is one coordinated motion the agent can name directly. When a gesture reaches across two resources (`cp /s3/a /gdrive/b`), it becomes a <Icon icon="handshake" /> **[Handshake](/home/design/handshakes)**, two hands meeting. (Higher-level agent *Skills*, à la [Anthropic Agent Skills](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview), compose gestures and handshakes into complex workflows at the harness layer.)
|
||||
- <Icon icon="clock-rotate-left" /> **[Recall](/home/design/recall)** (the cache), what the Hands have already produced and the tokens the Brain has already spent, so the same work never happens twice. Split into [Index Store](/home/design/index_cache) and [File Store](/home/design/file_cache).
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Arm
|
||||
|
||||
The Arm is Mirage's reach, the connected limb that extends the agent from the Brain into external systems. It brings together [Eyes](/home/design/eyes), [Hands](/home/design/hands), mounts, sessions, and shell execution into one coherent reach.
|
||||
|
||||
- Deep dive: [Workspace is Arm](/home/design/arm)
|
||||
|
||||
### Workspace
|
||||
|
||||
A `Workspace` is the concrete Python object and shared kernel inside the Arm. It owns the global resources that stay shared across commands and sessions: mounts, cache, command registry, jobs, and execution history.
|
||||
|
||||
- Deep dive: [Workspace](/home/design/workspace)
|
||||
|
||||
### Resources and Mounts
|
||||
|
||||
A resource maps one external system into Mirage. A mount attaches that resource to a prefix such as `/data`, `/s3`, or `/github`. Resources and mounts are the substance of Mirage's **Eyes**, and they are the regions of the world the Arm can reach into.
|
||||
|
||||
- Examples:
|
||||
`RAMResource()` mounted at `/data`
|
||||
`GitHubResource(...)` mounted at `/github`
|
||||
- Deep dive: [Mounts](/home/design/mount)
|
||||
- Deep dive: [Eyes](/home/design/eyes)
|
||||
|
||||
### Sessions
|
||||
|
||||
A `Session` holds the mutable execution context for one terminal-like thread of work: current directory, environment variables, shell functions, and last exit code. The Arm is shared. Sessions are the postures it holds for different agents over time.
|
||||
|
||||
- Deep dive: [Sessions](/home/design/session)
|
||||
|
||||
### PathSpec
|
||||
|
||||
Mirage carries virtual paths through the system using `PathSpec`, which keeps track of the original path, resolved directory, prefix, and pattern information.
|
||||
|
||||
- Deep dive: [PathSpec](/home/design/pathspec)
|
||||
|
||||
### Command Execution
|
||||
|
||||
Mirage parses commands, resolves virtual paths, dispatches operations to resources, and returns an `IOResult`.
|
||||
|
||||
Mirage uses a lightweight async execution model so commands, streams, and cleanup work consistently across local and remote systems without forcing the user into resource-specific APIs. **Fingers** (ops + FUSE) are the fundamental layer every resource implements; **Gestures** are the named commands built on fingers; the Shell composes gestures into plans.
|
||||
|
||||
- Deep dive: [Hands](/home/design/hands)
|
||||
- Deep dive: [Ops are Fingers](/home/design/fingers)
|
||||
- Deep dive: [Commands are Gestures](/home/design/gestures)
|
||||
- Deep dive: [Execution Pipeline](/home/design/pipeline)
|
||||
- Related: [Commands](/home/design/commands)
|
||||
- Related: [Command Spec](/home/design/commandspec)
|
||||
|
||||
### Recall (Cache)
|
||||
|
||||
Remote resources can use cache layers to avoid repeated network reads and to keep directory and file metadata responsive. Mirage calls this system **Recall**.
|
||||
|
||||
- Deep dive: [Recall](/home/design/recall), the overall Recall model
|
||||
- Deep dive: [Index Store](/home/design/index_cache), remembers directory listings and metadata
|
||||
- Deep dive: [File Store](/home/design/file_cache), remembers file content
|
||||
|
||||
### FUSE
|
||||
|
||||
Mirage can also expose the virtual filesystem as a real mount point so non-Mirage tools can read from it directly.
|
||||
|
||||
- Deep dive: [FUSE](/home/design/fuse)
|
||||
|
||||
## Where To Go Next
|
||||
|
||||
- Use the [Resource Matrix](/home/resource-matrix) to choose the right backend.
|
||||
- Start with the [Python Quickstart](/python/quickstart) if you want working code immediately.
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: Workspace is Arm
|
||||
description: Why Mirage treats workspace, mounts, sessions, and shell as one shared Arm that reaches from the agent to external systems.
|
||||
icon: user
|
||||
---
|
||||
|
||||
## The Arm Reaches
|
||||
|
||||
<Icon icon="eye" /> **[Eyes](/home/design/eyes)** see. <Icon icon="hand" /> **[Hands](/home/design/hands)** act. The **Arm** is how the agent reaches between them.
|
||||
|
||||
Mirage is that Arm. It extends the agent's reach from the Brain into the external world, carries gestures out to mounted systems, and brings bytes back. Workspace, mounts, sessions, and shell are the Arm's mechanics: the muscles and joints that make reach possible.
|
||||
|
||||
Arm is a more honest metaphor than "body" would be. Mirage is not the whole agent. It is the single connected limb that extends the agent into files, object stores, APIs, and remote services.
|
||||
|
||||
## What Lives In The Arm
|
||||
|
||||
- **[Workspace](/home/design/workspace)**: the shared kernel. It owns mounts, cache, command registry, jobs, and execution history. The part of the Arm that coordinates every motion.
|
||||
- **[Mounts](/home/design/mount)**: the attachment points for Eyes. They bind resources to prefixes such as `/s3` or `/github`. Each mount is a new region of the world the Arm can reach into.
|
||||
- **[Sessions](/home/design/session)**: the per-terminal execution context. They hold cwd, env, functions, and exit state across commands. One Arm can hold different postures for different agents at once.
|
||||
- **[Shell](/home/design/pipeline)**: the wiring inside the Arm. It parses commands, expands paths and variables, and coordinates Hands against what the Eyes expose.
|
||||
|
||||
## Arm Versus Workspace
|
||||
|
||||
In Mirage's API, `Workspace` is still the concrete object you instantiate. That has not changed.
|
||||
|
||||
`Arm` is the higher-level design metaphor. It explains how `Workspace`, mounts, sessions, and shell fit together into one operating environment: one continuous reach from the Brain to the world.
|
||||
|
||||
If Eyes are perception and Hands are action, the Arm is the connected, coordinated thing between them.
|
||||
|
||||
## Why Arm, Not Body
|
||||
|
||||
Earlier drafts called this the *Body*. Body claimed too much when it covered Workspace, mounts, sessions, and shell. Those are reach, not whole-self. Mirage does not contain the whole agent: the Brain (reasoning), higher-level Skills, and the agent harness all live outside Mirage.
|
||||
|
||||
Arm is honest about scope. Mirage is one limb, the limb that reaches. The framing even makes [Handshakes](/home/design/handshakes) click: two resources meeting across a gesture is literally two Arms reaching, two Hands clasping.
|
||||
|
||||
The runtime piece is described in [Architecture](/home/architecture#workspace-and-observability): the async event loop the Arm operates inside. The Arm is the limb; the runtime is the substrate it moves bytes through.
|
||||
@@ -0,0 +1,196 @@
|
||||
---
|
||||
title: Commands
|
||||
description: How commands are dispatched, how PathSpec flows through the system, and how to register new commands.
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
Commands are the concrete registration of <Icon icon="hand-sparkles" /> **[Gestures](/home/design/gestures)** in Mirage. Each registered command composes one or more <Icon icon="hand-point-up" /> **[Fingers](/home/design/fingers)** into a named, callable gesture: `cat` is a `read`, `ls` is a `readdir`, `cp` is `read` + `write`, `grep` is `read` + an in-process filter.
|
||||
|
||||
This page is the dispatch model: how a command gets looked up, how arguments flow through, and how to register a new one. The [Workspace](/home/design/workspace) is the kernel that stores and resolves commands; every registered command is a function that receives resolved paths and returns a byte stream.
|
||||
|
||||
## Dispatch
|
||||
|
||||
When a command is executed, mirage resolves it through these steps:
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
graph LR
|
||||
A["cat /s3/*.csv"] --> B["Resolve PathSpec"] --> C["Group by mount"] --> D["Dispatch"]
|
||||
```
|
||||
|
||||
1. **Parse** - the shell layer expands variables and globs
|
||||
2. **PathSpec resolution** - each path becomes a `PathSpec` with mount prefix, directory, and pattern
|
||||
3. **Group by resource** - paths are grouped by their mount prefix
|
||||
4. **Lookup** - the command registry finds the handler (see resolution order below)
|
||||
5. **Dispatch** - the handler receives the accessor, `PathSpec` list, and parsed flags
|
||||
|
||||
### Command Resolution Order
|
||||
|
||||
For each command, the registry tries handlers in this order:
|
||||
|
||||
1. **Filetype-specific** - matched by file extension (e.g. `cat` for `.parquet` files uses a table renderer)
|
||||
2. **Resource-specific** - matched by resource name without filetype (e.g. `cat` for S3)
|
||||
3. **General** - resource-agnostic fallback shared across all resources
|
||||
|
||||
The Ops layer follows the same pattern for filesystem operations:
|
||||
filetype-specific ops first, then resource ops without filetype.
|
||||
|
||||
When paths span multiple resources, the registry checks:
|
||||
|
||||
1. **Cross-mount handler** - registered for specific `(src, dst)` resource pairs (e.g. `cp` from S3 to RAM)
|
||||
2. **Aggregator** - runs the command per resource and merges results (e.g. `cat`, `grep`)
|
||||
3. **Error** - no cross-resource support for this command
|
||||
|
||||
## PathSpec Flow
|
||||
|
||||
A `PathSpec` is created at parse time and flows through the entire
|
||||
dispatch chain, accumulating context at each stage:
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
graph LR
|
||||
A["Shell input"] --> B["classify_word()"] --> C["Mount sets prefix"] --> D["resolve_glob()"] --> E["Command handler"] --> F["strip_prefix → Accessor"]
|
||||
```
|
||||
|
||||
| Stage | What happens | PathSpec state |
|
||||
| --- | --- | --- |
|
||||
| `classify_word()` | Creates PathSpec from shell token | `original="/s3/data/*.txt"`, `pattern="*.txt"`, `resolved=False` |
|
||||
| `Mount.execute_cmd()` | Sets mount prefix on all paths | `prefix="/s3"` |
|
||||
| `resolve_glob()` | Expands patterns against directory listing | Produces resolved PathSpecs per matched file |
|
||||
| Command handler | Reads data via accessor | Uses `PathSpec` as-is |
|
||||
| Core / Accessor | Strips prefix for resource call | `path.strip_prefix` → `"/data/file.txt"` |
|
||||
|
||||
The Ops layer and index cache also receive `PathSpec` - the index
|
||||
cache uses `PathSpec.original` as its cache key.
|
||||
|
||||
## Registering a Command
|
||||
|
||||
Commands are registered with the `@command` decorator:
|
||||
|
||||
```python
|
||||
@command("mycommand", resource="ram", spec=MY_SPEC)
|
||||
async def mycommand(
|
||||
accessor,
|
||||
paths: list[PathSpec],
|
||||
*texts: str,
|
||||
stdin: ByteSource | None = None,
|
||||
**kwargs,
|
||||
) -> tuple[ByteSource | None, IOResult]:
|
||||
data = await accessor.read(paths[0])
|
||||
return data, IOResult(reads={paths[0].original: data})
|
||||
```
|
||||
|
||||
### Key parameters
|
||||
|
||||
| Parameter | Description |
|
||||
| ---------- | ---------------------------------------------------- |
|
||||
| `name` | Command name (e.g. `"cat"`, `"grep"`) |
|
||||
| `resource` | Which resource(s) this command works with |
|
||||
| `spec` | `CommandSpec` defining flags and operands |
|
||||
| `write` | Set `True` if the command modifies files |
|
||||
| `aggregate`| Function for cross-resource result merging |
|
||||
|
||||
### What commands receive
|
||||
|
||||
Commands receive fully resolved, fully expanded arguments. They have
|
||||
no concept of variables, globs, cwd, or sessions - the pipeline
|
||||
handles all of that before dispatch.
|
||||
|
||||
### What commands return
|
||||
|
||||
A tuple of `(ByteSource, IOResult)`:
|
||||
|
||||
- **ByteSource** - the output data (`bytes` or `AsyncIterator[bytes]`)
|
||||
- **IOResult** - metadata: which paths were read, written, and should be cached
|
||||
|
||||
## Cross-Resource Dispatch
|
||||
|
||||
When a command receives paths spanning multiple mounts, mirage groups
|
||||
paths by resource and dispatches accordingly.
|
||||
|
||||
### Read-Only Aggregation
|
||||
|
||||
Commands that read multiple files register an `aggregate` function.
|
||||
Each path runs against its resource, then results are combined:
|
||||
|
||||
| Command | Aggregator | Behavior |
|
||||
| ------- | ------------------ | ---------------------------------------- |
|
||||
| `cat` | `concat_aggregate` | Concatenate bytes |
|
||||
| `head` | `header_aggregate` | Add `==> path <==` headers between files |
|
||||
| `tail` | `header_aggregate` | Same headers |
|
||||
| `grep` | `prefix_aggregate` | Prefix each line with `path:` |
|
||||
| `wc` | `wc_aggregate` | Sum counts across files, add total line |
|
||||
|
||||
### Cross-Mount Writes
|
||||
|
||||
Two-path commands that transfer data across resources use cross-mount
|
||||
handlers:
|
||||
|
||||
| Command | Behavior |
|
||||
| ------- | ---------------------------------------------- |
|
||||
| `cp` | Read from source resource, write to destination |
|
||||
| `mv` | Copy then delete source |
|
||||
| `diff` | Read both, compare |
|
||||
|
||||
### Path Expansion
|
||||
|
||||
Globs expand within a single resource only:
|
||||
|
||||
- `cat /s3/*.txt` - expands within S3, single-resource dispatch
|
||||
- `cat /s3/a.txt /github/b.txt` - explicit cross-resource, uses aggregator
|
||||
|
||||
Cross-resource operations only happen when paths from different
|
||||
mounts are explicitly named.
|
||||
|
||||
## Supported Commands
|
||||
|
||||
### Standard Commands
|
||||
|
||||
| Category | Commands |
|
||||
| -------- | -------- |
|
||||
| File I/O | `cat`, `head`, `tail`, `tee`, `cp`, `mv`, `rm`, `mkdir`, `touch`, `ln`, `mktemp`, `split`, `csplit` |
|
||||
| Search | `grep`, `rg`, `find`, `look` |
|
||||
| Text Processing | `awk`, `sed`, `sort`, `uniq`, `cut`, `tr`, `paste`, `join`, `comm`, `column`, `fold`, `expand`, `unexpand`, `fmt`, `nl`, `rev`, `tac`, `shuf`, `tsort`, `wc` |
|
||||
| Path Utilities | `ls`, `tree`, `stat`, `file`, `du`, `basename`, `dirname`, `realpath`, `readlink` |
|
||||
| Data Formats | `jq`, `diff`, `cmp`, `patch` |
|
||||
| Compression | `tar`, `zip`, `unzip`, `gzip`, `gunzip`, `zcat`, `zgrep` |
|
||||
| Encoding | `base64`, `md5`, `sha256sum`, `xxd`, `iconv` |
|
||||
| Math & Misc | `seq`, `expr`, `bc`, `date`, `strings`, `curl`, `wget` |
|
||||
|
||||
### Resource-Specific Commands
|
||||
|
||||
| Resource | Commands |
|
||||
| -------- | -------- |
|
||||
| Slack | `slack-post-message`, `slack-reply-to-thread`, `slack-add-reaction`, `slack-search`, `slack-get-users`, `slack-get-user-profile` |
|
||||
| Discord | `discord-send-message`, `discord-add-reaction`, `discord-get-server-info`, `discord-list-members` |
|
||||
| Telegram | `telegram-send-message` |
|
||||
| Email | `email-send`, `email-read`, `email-reply`, `email-reply-all`, `email-forward`, `email-triage` |
|
||||
| Gmail | `gws-gmail-send`, `gws-gmail-read`, `gws-gmail-reply`, `gws-gmail-reply-all`, `gws-gmail-forward`, `gws-gmail-triage` |
|
||||
| Google Docs | `gws-docs-write`, `gws-docs-documents-create`, `gws-docs-documents-batchUpdate` |
|
||||
| Google Sheets | `gws-sheets-read`, `gws-sheets-write`, `gws-sheets-append`, `gws-sheets-spreadsheets-create`, `gws-sheets-spreadsheets-batchUpdate` |
|
||||
| Google Slides | `gws-slides-presentations-create`, `gws-slides-presentations-batchUpdate` |
|
||||
| Linear | `linear-issue-create`, `linear-issue-update`, `linear-issue-transition`, `linear-issue-assign`, `linear-issue-add-label`, `linear-issue-set-priority`, `linear-issue-set-project`, `linear-issue-comment-add`, `linear-issue-comment-update`, `linear-search` |
|
||||
| Notion | `notion-page-create`, `notion-block-append`, `notion-comment-add`, `notion-search` |
|
||||
| Trello | `trello-card-create`, `trello-card-update`, `trello-card-move`, `trello-card-assign`, `trello-card-comment-add`, `trello-card-comment-update`, `trello-card-label-add`, `trello-card-label-remove` |
|
||||
| Paperclip | `scan`, `search`, `map`, `lookup` |
|
||||
|
||||
### Command Availability by Resource
|
||||
|
||||
Full resources (all standard commands): **Disk**, **RAM**, **Redis**, **SSH**, **S3** (+ R2, GCS, OCI, Supabase)
|
||||
|
||||
| Command | Disk/RAM/Redis/SSH/S3 | GitHub | GDrive | Slack/Discord/Telegram | Email/Gmail | Linear/Notion/Trello | Langfuse | MongoDB |
|
||||
| ------- | --------------------- | ------ | ------ | ---------------------- | ----------- | -------------------- | -------- | ------- |
|
||||
| `ls` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| `cat` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| `head`/`tail` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| `grep`/`rg` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| `find` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| `tree` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| `stat` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| `wc` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| `jq` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| `awk`/`sed`/`sort` | ✓ | ✓ | ✓ | | | | | |
|
||||
| `cp`/`mv`/`rm` | ✓ | | | | | | | |
|
||||
| `mkdir`/`touch`/`tee` | ✓ | | | | | | | |
|
||||
| `diff`/`cmp` | ✓ | ✓ | ✓ | | | | | |
|
||||
| `tar`/`zip`/`gzip` | ✓ | | | | | | | |
|
||||
@@ -0,0 +1,110 @@
|
||||
---
|
||||
title: Command Specs are Grip
|
||||
description: A gesture's command spec is its grip, how it holds onto flags, values, and paths before the motion begins.
|
||||
icon: hand-fist
|
||||
---
|
||||
|
||||
## What A Gesture Holds Before It Moves
|
||||
|
||||
A <Icon icon="hand-sparkles" /> [Gesture](/home/design/gestures) is one coordinated motion, but the motion needs something to hold. `cat` holds a list of paths. `grep` holds a pattern *and* a list of paths. `head -n 5` holds a number that shapes how it reaches. **Grip** is the page where we say what each gesture is allowed to hold and how.
|
||||
|
||||
Mirage names grip in three primitives: `Option`, `Operand`, and `OperandKind`. They map cleanly to the [POSIX Utility Conventions](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html), but the body framing is what tells you why they exist before the standard tells you what to call them.
|
||||
|
||||
## Kinds Of Grip
|
||||
|
||||
| Grip kind | What the gesture is holding | Mirage type | Example |
|
||||
| --------------------- | ---------------------------------------------------------- | --------------------------------- | ---------------- |
|
||||
| **Empty grip** | A flag, a position the gesture takes, holding nothing | `Option(value_kind=NONE)` | `-v`, `-r` |
|
||||
| **Grip on a value** | A flag that carries a text or a path with it | `Option(value_kind=TEXT\|PATH)` | `-n 10`, `-f x` |
|
||||
| **Fixed grip** | A required positional target the gesture always holds | `positional: tuple[Operand, ...]` | `PATTERN` in grep |
|
||||
| **Sweeping grip** | A trailing list of targets the gesture holds across many | `rest: Operand \| None` | `[FILE...]` |
|
||||
|
||||
A gesture's full grip is the sum of these. `grep -i -n -m 5 hello /s3/x.txt /disk/y.txt` is two empty grips (`-i`, `-n`), one grip on a value (`-m 5`), one fixed grip (`hello`), and a sweeping grip over two paths.
|
||||
|
||||
## POSIX Alignment
|
||||
|
||||
Grip is not invented, it tracks the [POSIX Utility Conventions (IEEE Std 1003.1, Chapter 12)](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html) and the [GNU Argument Syntax](https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html) for long options. The POSIX synopsis:
|
||||
|
||||
```
|
||||
utility_name [-option...] [-option option_argument] [operand...]
|
||||
```
|
||||
|
||||
| POSIX term | Grip kind | Mirage type |
|
||||
| --------------------------- | -------------------- | --------------------------------- |
|
||||
| Option (no argument) | Empty grip | `Option(value_kind=NONE)` |
|
||||
| Option with option-argument | Grip on a value | `Option(value_kind=TEXT\|PATH)` |
|
||||
| Operand (fixed) | Fixed grip | `positional: tuple[Operand, ...]` |
|
||||
| Operand (variadic) | Sweeping grip | `rest: Operand \| None` |
|
||||
|
||||
## Why `OperandKind.NONE` Instead of `takes_value: bool`
|
||||
|
||||
POSIX does not define a separate "takes_value" concept. An option simply either has an **option-argument** or it doesn't, the kind tells you both whether and what. This is consistent across major CLI frameworks:
|
||||
|
||||
- **Python [argparse](https://docs.python.org/3/library/argparse.html):** infers from `action` (`store` vs `store_true`)
|
||||
- **Rust [clap](https://docs.rs/clap/latest/clap/):** infers from `ArgAction` enum
|
||||
- **Go [cobra/pflag](https://github.com/spf13/pflag):** infers from the Go type (`BoolVar` vs `StringVar`)
|
||||
|
||||
None use a standalone boolean. We follow the same principle: `value_kind=NONE` is an empty grip; `value_kind=TEXT` or `PATH` is a grip on a value of that type.
|
||||
|
||||
## Three Primitives
|
||||
|
||||
### OperandKind
|
||||
|
||||
```python
|
||||
class OperandKind(str, Enum):
|
||||
NONE = "none" # boolean flag - no option-argument
|
||||
PATH = "path" # file/directory path (resolved by the registry)
|
||||
TEXT = "text" # text value (pattern, number, format string)
|
||||
```
|
||||
|
||||
### Option
|
||||
|
||||
Models a CLI option (flag). Maps to POSIX "option" + optional "option-argument", a position the gesture takes, optionally gripping a value with it.
|
||||
|
||||
```python
|
||||
Option(short="-v") # empty grip
|
||||
Option(short="-n", value_kind=OperandKind.TEXT) # grip on text
|
||||
Option(short="-f", value_kind=OperandKind.PATH) # grip on path (auto-resolved)
|
||||
Option(short="-r", long="--recursive") # short + long alias
|
||||
```
|
||||
|
||||
### Operand
|
||||
|
||||
Models a positional argument, what the gesture grips onto by position. Used in `positional` (fixed) and `rest` (sweeping).
|
||||
|
||||
```python
|
||||
Operand(kind=OperandKind.PATH) # file operand
|
||||
Operand(kind=OperandKind.TEXT) # text operand (e.g., pattern)
|
||||
```
|
||||
|
||||
## Full Example: grep
|
||||
|
||||
POSIX synopsis: `grep [-civlnFEo] [-m count] pattern [file...]`
|
||||
|
||||
Grip: ten empty grips, one grip on a value (`-m count`), one fixed grip (the pattern), one sweeping grip (the file list).
|
||||
|
||||
```python
|
||||
"grep": CommandSpec(
|
||||
options=(
|
||||
Option(short="-r"), Option(short="-i"), Option(short="-v"),
|
||||
Option(short="-n"), Option(short="-c"), Option(short="-l"),
|
||||
Option(short="-w"), Option(short="-F"), Option(short="-E"),
|
||||
Option(short="-o"),
|
||||
Option(short="-m", value_kind=OperandKind.TEXT),
|
||||
),
|
||||
positional=(Operand(kind=OperandKind.TEXT),), # PATTERN
|
||||
rest=Operand(kind=OperandKind.PATH), # [FILE...]
|
||||
)
|
||||
```
|
||||
|
||||
## Path-Typed Grip
|
||||
|
||||
When an option grips a path (like `tar -f archive.tar`), setting `value_kind=OperandKind.PATH` tells the registry to resolve it from virtual path to backend path, the same resolution applied to positional `PATH` operands. The grip on the value reaches through the same resource routing as the gesture's main targets.
|
||||
|
||||
## References
|
||||
|
||||
- [POSIX Utility Conventions - IEEE Std 1003.1, Chapter 12](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html)
|
||||
- [GNU Argument Syntax Conventions](https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html)
|
||||
- [Python argparse](https://docs.python.org/3/library/argparse.html)
|
||||
- [Rust clap](https://docs.rs/clap/latest/clap/)
|
||||
- [Go pflag](https://github.com/spf13/pflag)
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
title: VFS View
|
||||
description: How Mirage represents external systems as one unified filesystem. The eyes for AI agents.
|
||||
icon: eye
|
||||
---
|
||||
|
||||
## One View Across Systems
|
||||
|
||||
Mirage gives agents one way to see external systems.
|
||||
|
||||
Instead of exposing every backend through its own API shape, Mirage represents services, data, and infrastructure through a unified filesystem interface. The agent gets one environment to browse, inspect, and reason about, with no mental-model switch per resource.
|
||||
|
||||
This is the **eyes** for AI agents: how they perceive what exists, where it lives, and how to reach it.
|
||||
|
||||
## Why Representation Matters
|
||||
|
||||
Large language models work best when the environment is legible.
|
||||
|
||||
Mirage makes systems legible by representing them through:
|
||||
|
||||
- mounted paths
|
||||
- directory structure
|
||||
- stable, file-shaped resources
|
||||
- familiar discovery commands such as `ls`, `find`, `grep`, and `cat`
|
||||
|
||||
This is not a UI choice. It is the representation layer that lets agents understand what exists, where it lives, and how the pieces relate.
|
||||
|
||||
## A Unified Interface
|
||||
|
||||
Mirage doesn't ask the agent to remember one shape for GitHub, another for S3, another for Slack, and another for local disk.
|
||||
|
||||
It maps all of them into the same visible environment. Discovery becomes consistent, provenance becomes traceable, and cross-system reasoning gets dramatically simpler.
|
||||
|
||||
For an agent, `/github/repo/README.md` and `/s3/reports/q1.csv` are different resources, but they live behind the same interface, and the same `cat`, `grep`, or `ls` works on both.
|
||||
|
||||
## Why This Fits Agents
|
||||
|
||||
Agents need more than raw access. They need an environment they can inspect and navigate reliably.
|
||||
|
||||
A unified view gives them:
|
||||
|
||||
- one way to discover what is available
|
||||
- stable paths they can revisit and cite
|
||||
- clearer provenance for outputs
|
||||
- less prompt overhead spent translating between resource-specific APIs
|
||||
|
||||
## Mental Shortcut
|
||||
|
||||
Mirage turns external systems into something agents can read the way Unix users read a filesystem.
|
||||
|
||||
That unified view is how agents see. It is the eyes through which the [Hands](/home/design/hands) act, the [Arm](/home/design/arm) reaches, and [Recall](/home/design/recall) remembers.
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: File Store
|
||||
description: Preserves the bytes the Hands produced and the tokens the Brain (Agent) spent, so the same fetch, decode, and reasoning are not repeated.
|
||||
icon: file
|
||||
---
|
||||
|
||||
The File Store is one half of Mirage's <Icon icon="clock-rotate-left" /> [Recall](/home/design/recall) layer (the other is the [Index Store](/home/design/index_cache)). Running `cat /s3/data.parquet` costs something real: the <Icon icon="brain" /> **Brain** (the agent) orchestrates the chain and spends tokens, and the <Icon icon="hand" /> **[Hands](/home/design/hands)** make the network call, decode the format, and return bytes. The File Store keeps the bytes that came out of that work, so the next time the same request appears, Mirage returns the stored bytes directly. The network call, the decode, and the agent round-trip are all saved.
|
||||
|
||||
## What It Does
|
||||
|
||||
The file cache stores the byte output of file-touching commands in RAM the first time they run. When the same request appears later, Mirage returns those stored bytes directly. The <Icon icon="hand" /> **Hands** do not re-fetch or re-decode, and the <Icon icon="brain" /> **Brain** (Agent) does not spend tokens re-triggering the chain.
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
graph LR
|
||||
A["cat /s3/data.txt"] --> B{Cached?}
|
||||
B -->|Yes| C[Serve from RAM]
|
||||
B -->|No| D[Fetch from S3]
|
||||
D --> E[Cache in RAM]
|
||||
E --> C
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
- **Cache hit** - Mirage checks whether every path the command touches is already cached. If all of them are, the command is served entirely from RAM. If any one is missing, the original resource handles the full command.
|
||||
- **Cache miss** - the resource fetches the data. After execution, the result is cached.
|
||||
- **Write invalidation** - any write to a path removes it from cache. The next request re-fetches from the resource.
|
||||
- **LRU eviction** - when total cached bytes exceed the limit (default 512MB), the oldest entries are evicted.
|
||||
|
||||
## Opt-In Caching: Recall Is A Decision, Not A Default
|
||||
|
||||
Mirage does not cache eagerly. Each command declares which of its outputs are cacheable by populating `IOResult.cache` with the paths it wants preserved. A command that produces ephemeral or derived content can leave that list empty and nothing is stored.
|
||||
|
||||
This keeps Recall under the control of the <Icon icon="hand" /> **Hands** (and the <Icon icon="brain" /> **Brain** orchestrating them), not the filesystem layer. The cache is an opportunity the Hand decides to take, not a mandate imposed by the runtime.
|
||||
|
||||
```python
|
||||
return IOResult(
|
||||
stdout=data,
|
||||
cache=[path], # opt in: this path is worth remembering
|
||||
)
|
||||
```
|
||||
|
||||
### Caching by Intent
|
||||
|
||||
Today, Mirage makes this decision heuristically, based on what the command *intends*. `cat /s3/data.csv` is almost certainly a read the agent will want again, the bytes come back, the content is worth remembering, so `cat` opts in. A command like `stat`, whose output is metadata the index cache already tracks, does not. The rule of thumb: if the shape of the command suggests the bytes will be reused, cache; if it is a side-effecting or metadata-only call, skip.
|
||||
|
||||
This is deliberately simple for now. A future direction is to let the Brain itself annotate cacheability at call time (e.g., marking a particular `cat` as ephemeral), but the current default is that each Hand knows its own semantics and declares on its user's behalf.
|
||||
|
||||
## Background Drain
|
||||
|
||||
When `cat bigfile | head -n 1` runs, `head` returns after 1 line but the stream is partially consumed. A background task drains the remaining bytes and caches the full file, so the next read is a cache hit.
|
||||
|
||||
### Bounded Drains: `max_drain_bytes`
|
||||
|
||||
The drain is unbounded by default. A 10 GB file behind a one-line `head` will still be pulled in full to make the next read a cache hit. That is the right trade for most files but a bad one for very large objects the agent never asked for.
|
||||
|
||||
`max_drain_bytes` sets a per-drain ceiling. When a drain exceeds the ceiling without exhausting the source, the partial buffer is **discarded** and the path is **not** cached. The next read fetches fresh from the resource. It does not see a half-file. The ceiling applies independently to each drain task; it is not a workspace-wide pool.
|
||||
|
||||
```python
|
||||
ws = Workspace(
|
||||
{"/gcs/": resource},
|
||||
cache=CacheConfig(max_drain_bytes=100_000_000), # 100 MB ceiling
|
||||
)
|
||||
```
|
||||
|
||||
The setting is also mutable on the fly via `ws.max_drain_bytes`. Changing it affects subsequent drains; in-flight drains keep the budget they were started with.
|
||||
|
||||
```python
|
||||
ws.max_drain_bytes = 1_000_000 # tighten to 1 MB
|
||||
await ws.execute("cat /gcs/huge.jsonl | head -n 3")
|
||||
# huge.jsonl drain trips the 1 MB ceiling → not cached
|
||||
|
||||
ws.max_drain_bytes = None # back to unbounded
|
||||
await ws.execute("cat /gcs/huge.jsonl | head -n 3")
|
||||
# drain completes → huge.jsonl now cached
|
||||
```
|
||||
|
||||
| `max_drain_bytes` | Drain behavior |
|
||||
| ----------------- | --------------------------------------------------------------------------- |
|
||||
| `None` (default) | Always drain to completion. Cache populated regardless of size. |
|
||||
| `int` | Drain until ceiling is exceeded. If exceeded without EOF, nothing is cached. |
|
||||
|
||||
## Freshness
|
||||
|
||||
Freshness is controlled by `ConsistencyPolicy`, a Workspace-level setting.
|
||||
|
||||
| Mode | Behavior |
|
||||
| ---------- | ------------------------------------------------------------------------- |
|
||||
| **LAZY** (default) | Cache hit = serve cached bytes. No remote check. Fast. |
|
||||
| **ALWAYS** | Before serving, call the backend's `stat` and compare `FileStat.fingerprint` against the cached fingerprint. If mismatch, evict and refetch. |
|
||||
|
||||
Under `ALWAYS`, each cached read costs one cheap round-trip (`HEAD` on S3, `stat` on disk, index lookup on GitHub). In exchange, the cache is guaranteed fresh against external mutation. Directory listings are not affected, they use the separate TTL-based [index cache](/home/design/index_cache).
|
||||
|
||||
### Backend fingerprint sources
|
||||
|
||||
| Backend | Fingerprint source | Notes |
|
||||
| ---------------------- | --------------------------------- | ----------------------------------------------------------- |
|
||||
| S3 / GCS / R2 | `ETag` from `HEAD` | Changes on every content write. |
|
||||
| Disk | `mtime` ISO string | Changes on every local write. |
|
||||
| GitHub | Blob `sha` | Changes on every commit that touches the blob. |
|
||||
| SSH (SFTP) | `mtime` ISO string | Same as disk. |
|
||||
| Google Drive | `modifiedTime` | Reflects any edit, including Google-native format updates. |
|
||||
| RAM / Redis |, (no fingerprint) | Under ALWAYS, falls back to LAZY cleanly. |
|
||||
| Slack / Discord / Notion / Linear / Telegram / Gmail / Google Docs/Sheets/Slides / Trello / MongoDB / Email |, (no cheap fingerprint) | Message-shaped backends. Under ALWAYS, falls back to LAZY. |
|
||||
|
||||
Backends that return `stat.fingerprint is None` silently fall back to LAZY, so mixing fingerprint-capable and fingerprint-absent mounts under `ConsistencyPolicy.ALWAYS` is safe.
|
||||
|
||||
### Current implementation note
|
||||
|
||||
The cache's default per-entry fingerprint is `md5(bytes)` (auto-computed at `cache.set` time when a command does not supply one). For S3/GitHub this matches the backend's content-derived fingerprint, cache hits under ALWAYS are efficient. For disk/SSH/GDrive the cached fingerprint is `md5(bytes)` while the backend returns a time-based fingerprint; the two never compare equal, so ALWAYS refetches on every read. This is correct but inefficient; a follow-up can align the two by propagating `FileStat.fingerprint` through `IOResult` at cache-set time.
|
||||
|
||||
## Storage Backends
|
||||
|
||||
| Backend | Use case |
|
||||
| ------- | ------------------- |
|
||||
| RAM | Default, in-process |
|
||||
| Disk | Persistent local |
|
||||
| Redis | Multi-process |
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Ops are Fingers
|
||||
description: Why Mirage treats each filesystem op as a single finger on the Hand, and how commands are compositions of fingers.
|
||||
icon: hand-point-up
|
||||
---
|
||||
|
||||
## Fingers Are The Primitive Gestures
|
||||
|
||||
<Icon icon="hand" /> **[Hands](/home/design/hands)** are how agents act. **Fingers** are the smallest gestures a hand can make.
|
||||
|
||||
In Mirage, fingers are ops: `read`, `readdir`, `stat`, `write`, `mkdir`, `unlink`, `rename`. Each one is a single primitive action, and each one maps cleanly to exactly one kind of backend call (S3 `GetObject`, `list_objects_v2`, `HEAD`, `PutObject`, and so on). They are the atoms of filesystem behavior.
|
||||
|
||||
A higher-level command like `cat`, `grep`, or `cp` is not itself a finger. It is a *phrase* of fingers the Hand strings together.
|
||||
|
||||
## The Finger Set
|
||||
|
||||
| Finger | Gesture | Typical backend call |
|
||||
| ------ | ------- | -------------------- |
|
||||
| `read` | pull bytes from a path | `GetObject`, file read |
|
||||
| `readdir` | list children of a directory | `list_objects_v2`, `scandir` |
|
||||
| `stat` | feel for shape (size, type, mtime) | `HEAD`, `stat` |
|
||||
| `write` | put bytes at a path | `PutObject`, file write |
|
||||
| `mkdir` | make a directory | `mkdir`, prefix marker |
|
||||
| `unlink` | remove a file | `DeleteObject`, `unlink` |
|
||||
| `rename` | move a path | `CopyObject` + `DeleteObject` |
|
||||
| `create` | lay down an empty file | zero-byte `PutObject`, `touch` |
|
||||
| `truncate` | cut a file to zero bytes | `PutObject` empty, `truncate` |
|
||||
| `append` | add bytes at the end | multipart append, file append |
|
||||
|
||||
Each finger's contract is narrow on purpose: a single path, a well-defined input, a well-defined output. Narrow contracts are what let any finger move against any [Eye](/home/design/eyes) without special casing.
|
||||
|
||||
## Fingers Are Fundamental Infrastructure
|
||||
|
||||
The Fingers layer has two pieces:
|
||||
|
||||
- <Icon icon="folder-tree" /> **[Ops](/home/design/vfs)**, the primitive gestures themselves, dispatched through the mount table to the right resource.
|
||||
- <Icon icon="hard-drive" /> **[FUSE](/home/design/fuse)**, a way to expose those same gestures to any process on the host through POSIX. Any shell, editor, or subprocess becomes a legitimate user of fingers, not just Mirage's own dispatcher.
|
||||
|
||||
Both are fundamental in the literal sense: everything above them, every [Gesture](/home/design/gestures), every pipeline, every agent plan, is built on top. Backends only need to implement fingers to become part of Mirage. FUSE then takes those fingers and makes them usable by anything POSIX understands.
|
||||
|
||||
## Why This Foundation Matters
|
||||
|
||||
A small set of fingers supports an unbounded set of gestures above it. Add a backend: implement the finger contract and every existing [Gesture](/home/design/gestures) in the catalog, `cat`, `grep`, `cp`, `jq`, works against it automatically. Add a gesture: it runs against every existing backend for free. The catalog grows without churning the resources, and the resources grow without changing the catalog.
|
||||
|
||||
This is the concrete mechanism behind Mirage's *N + M* promise. Fingers are the small M-side interface every backend speaks. Gestures are the N-side catalog agents draw from. They never multiply into N × M adapters because they don't talk to each other; they both talk to fingers.
|
||||
|
||||
## Related
|
||||
|
||||
- [Ops](/home/design/vfs), how fingers are dispatched through the mount table to resources.
|
||||
- [FUSE](/home/design/fuse), how fingers get exposed to external processes via POSIX.
|
||||
- [Gestures](/home/design/gestures), what Mirage builds on top of fingers.
|
||||
- [Hands](/home/design/hands), the higher-level metaphor fingers belong to.
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: FUSE
|
||||
description: Expose mirage's virtual filesystem as a real OS mount point via FUSE.
|
||||
icon: hard-drive
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
FUSE is part of the <Icon icon="hand-point-up" /> **[Fingers](/home/design/fingers)** layer alongside [Ops](/home/design/vfs). Where Ops dispatch fingers inside Mirage, FUSE exposes the same fingers to the host OS through a real mount point. Once mounted, any process, shells, editors, scripts, can act against mounted resources through standard POSIX file I/O, using the same fingers every [Gesture](/home/design/gestures) is built on.
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
graph LR
|
||||
A["OS Process"] --> B["FUSE Kernel"] --> C["MirageFS"] --> D["Ops"] --> E["Resource"]
|
||||
```
|
||||
|
||||
The mount runs in a background thread and is automatically
|
||||
cleaned up when the Workspace closes.
|
||||
|
||||
## How It Works
|
||||
|
||||
`MirageFS` implements FUSE operations by delegating to the Ops layer:
|
||||
|
||||
| FUSE Call | Ops Call | Description |
|
||||
| ---------- | ------------ | ----------------------------- |
|
||||
| `getattr` | `stat` | File metadata |
|
||||
| `readdir` | `readdir` | Directory listing |
|
||||
| `read` | `read` | Read file content |
|
||||
| `write` | `write` | Write with buffering |
|
||||
| `create` | `create` | Create new file |
|
||||
| `mkdir` | `mkdir` | Create directory |
|
||||
| `unlink` | `unlink` | Delete file |
|
||||
| `rmdir` | `rmdir` | Remove directory |
|
||||
| `rename` | `rename` | Move or rename |
|
||||
| `truncate` | `truncate` | Truncate file |
|
||||
|
||||
## Virtual Directories
|
||||
|
||||
FUSE creates virtual directories from mount prefixes. The directory
|
||||
`/s3/` doesn't physically exist - it's synthesized from the mount
|
||||
table so that `ls /tmp/mirage-xxx/` shows all mounted prefixes.
|
||||
|
||||
## Write Buffering
|
||||
|
||||
Writes are buffered per file handle. Data is flushed to the resource
|
||||
on `flush` or `release` (file close), reducing the number of API calls
|
||||
for sequential writes.
|
||||
|
||||
## Special Files
|
||||
|
||||
FUSE provides `/.mirage/whoami` which returns metadata about the
|
||||
current agent, working directory, and mounted prefixes.
|
||||
|
||||
## Platform Notes
|
||||
|
||||
- **macOS:** Requires macFUSE. Metadata files (`.DS_Store`, `._*`,
|
||||
`.Spotlight-V100`) are automatically filtered.
|
||||
- **Linux:** Uses `fusermount`. No special filtering needed.
|
||||
|
||||
FUSE is optional - mirage works without it for Python-only and
|
||||
virtual shell execution.
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: Commands are Gestures
|
||||
description: Why Mirage treats its command catalog as the Gesture layer, built on top of Fingers, and how gestures differ from higher-level agent Skills.
|
||||
icon: hand-sparkles
|
||||
---
|
||||
|
||||
## Gestures Sit On Top Of Fingers
|
||||
|
||||
<Icon icon="hand-point-up" /> **[Fingers](/home/design/fingers)** are the primitive ops, `read`, `readdir`, `stat`, `write`. <Icon icon="hard-drive" /> **[FUSE](/home/design/fuse)** is the infrastructure that makes those fingers available to every process on the host. **Gestures** are what Mirage builds on top: the commands an agent actually names.
|
||||
|
||||
`cat`, `grep`, `ls`, `head`, `tail`, `cp`, `mv`, `jq`, `awk`, `sed`, each is a gesture. A gesture is a single coordinated motion composed from one or more fingers, with a name the [Brain](/home/concepts) can call directly.
|
||||
|
||||
## The Layered Picture
|
||||
|
||||
| Layer | What it is | Example |
|
||||
| ----- | ---------- | ------- |
|
||||
| **Fundamental** | Fingers (ops) + FUSE | `read`, `readdir`, `stat`; the POSIX mount surface |
|
||||
| **Gestures** | Named commands composed from fingers | `cat`, `grep`, `cp`, `jq` |
|
||||
| **Shell** | The glue that chains gestures | `cat x \| grep y \| head` |
|
||||
|
||||
The fundamental layer is small on purpose. A handful of fingers plus a FUSE mount is all a backend needs to become part of Mirage. Every gesture in the catalog then works against that backend automatically, because gestures talk to fingers, not to backend SDKs.
|
||||
|
||||
## Why Call Them Gestures
|
||||
|
||||
A gesture is *one coordinated motion*. That is the right size for `cat` (read a path), `cp` (move bytes from A to B), or `grep` (read-and-filter). Each is a single recognizable move the Hand performs, not a plan, not a workflow, just one gesture.
|
||||
|
||||
The word also keeps the anatomical metaphor coherent. Fingers make gestures. Hands express themselves through sequences of gestures. The vocabulary stays inside one register.
|
||||
|
||||
## Gestures Versus Skills
|
||||
|
||||
Mirage gestures are deliberately atomic. They correspond to the Unix command tradition: small, composable, one job per gesture.
|
||||
|
||||
**Skills**, as used by [Anthropic Agent Skills](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview) and the broader [agent-skill open standard](https://github.com/anthropics/skills), are a higher-level unit. A skill bundles instructions, scripts, and resources an agent can load to perform a *complex* task end to end: "format this spreadsheet," "write a professional letter," "analyze this dataset." Skills compose many gestures (plus reasoning, plus external knowledge) into a workflow.
|
||||
|
||||
| | Gesture | Skill |
|
||||
| --- | --- | --- |
|
||||
| **Size** | One motion | A workflow |
|
||||
| **Example** | `grep`, `cat`, `cp` | "summarize this CSV", "format this pitch deck" |
|
||||
| **Where it lives** | Mirage command catalog | Agent harness (Claude Code, Claude SDK, etc.) |
|
||||
| **Who composes it** | Shell pipelines, control flow | Agent reasoning + tool use |
|
||||
|
||||
A useful mental model: gestures are *what the Hand can do*; skills are *what the Brain has learned to do with sequences of gestures*. Mirage owns the gesture layer. Skill-level composition is the agent harness's job.
|
||||
|
||||
See [Anthropic's engineering post on Agent Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills) for more on the skill concept.
|
||||
|
||||
## Adding A Gesture
|
||||
|
||||
A new gesture is a registered function that receives resolved paths and arguments, uses fingers to read or write, and returns a byte stream. See [Commands](/home/design/commands) for the dispatch model and [Command Spec](/home/design/commandspec) for how a gesture declares its flags and operands.
|
||||
|
||||
Because every gesture is built on fingers, adding one does not require resource-specific code. It works against every backend that implements the finger set.
|
||||
|
||||
## Why The Split Matters
|
||||
|
||||
Splitting **Fingers (fundamental)** from **Gestures (composed)** is what lets Mirage scale:
|
||||
|
||||
- Backends implement fingers. They do not need to know any gesture exists.
|
||||
- Gestures compose fingers. They do not need to know any backend exists.
|
||||
- New backend: every existing gesture works automatically.
|
||||
- New gesture: every existing backend is already a viable target.
|
||||
|
||||
This is the concrete mechanism behind Mirage's *N + M* promise. Fingers are the small M-side interface every backend speaks; gestures are the N-side catalog the agent draws from. The catalog grows without churning the backends, and the backends grow without changing the catalog.
|
||||
|
||||
## Handshakes: Gestures Across Two Resources
|
||||
|
||||
When a gesture reaches across two resources, `cp /s3/a /gdrive/b`, `diff /ram/a /disk/b`, `cat /s3/a /github/b`, it becomes a <Icon icon="handshake" /> **[Handshake](/home/design/handshakes)**. Still a gesture, but one the Hand performs by using two fingers on two different Eyes at once. The dispatcher handles the coordination; see the dedicated page for how.
|
||||
|
||||
## Related
|
||||
|
||||
- [Fingers](/home/design/fingers), the primitive gestures' building blocks.
|
||||
- [Handshakes](/home/design/handshakes), gestures that cross two resources.
|
||||
- [Commands](/home/design/commands), the dispatch model for registering and resolving gestures.
|
||||
- [Command Spec](/home/design/commandspec), how a gesture declares its flags and operands.
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: Hands
|
||||
description: Why Mirage gives agents one lightweight way to act across systems.
|
||||
icon: hand
|
||||
---
|
||||
|
||||
## Unified Hands
|
||||
|
||||
Mirage gives agents one unified way to act across systems.
|
||||
|
||||
Instead of learning a different action surface for every backend, agents can work through mounted paths, familiar Unix commands, and a lightweight stream-based execution model.
|
||||
|
||||
These commands are Mirage's hands. They stay simple on purpose: read, write, search, transform, and compose across systems through one interface.
|
||||
|
||||
## Why The Hands Stay Lightweight
|
||||
|
||||
Mirage is not trying to expose every backend as a giant in-process client library. It is trying to give agents a practical way to do work.
|
||||
|
||||
That is why the design stays lightweight:
|
||||
|
||||
- commands operate on paths and byte streams
|
||||
- remote and local systems use the same shell-shaped interface
|
||||
- composition happens through pipes, files, and command output
|
||||
- cleanup and lifecycle stay explicit
|
||||
|
||||
This avoids pushing agents into resource-specific method trees or backend-specific action models for every system they touch.
|
||||
|
||||
## Why Async Fits The Hands
|
||||
|
||||
Mirage has to work well across local files, remote APIs, object storage, streamed command output, and cleanup steps that may continue after a command returns.
|
||||
|
||||
A synchronous, fully buffered model would either block too much or hide too much. Mirage instead treats command execution and I/O as naturally asynchronous, so the same hands can work consistently across systems.
|
||||
|
||||
This keeps the action surface simple while still supporting long reads, pipelines, and non-blocking cleanup.
|
||||
|
||||
## Hands Work In Bytes, Not Objects
|
||||
|
||||
The hands move bytes, not objects. That choice is deliberate, and it is what lets the same small set of commands act across every backend.
|
||||
|
||||
Consider the alternative. If every tool returned a backend-specific object shape, like `S3Object`, `SlackMessage`, or `LinearIssue`, then every new command has to learn every backend's type, and every new backend has to be taught to every command. (This is the M × N integration problem the [Architecture](/home/architecture#from-m-n-to-m--n) page covers in full.)
|
||||
|
||||
Bytes collapse it. Every command reads bytes and writes bytes. Every backend produces and consumes bytes. `grep` does not need to know whether its input came from local disk, an S3 object, or a generated stream. It just reads bytes. Add a new command and it works with every existing source. Add a new source and every existing command can already act on it.
|
||||
|
||||
Two other reasons this fits the hands:
|
||||
|
||||
- **In-distribution for LLMs.** Models have seen an enormous amount of shell and text-stream interaction in training. Byte-shaped hands feel native; bespoke object schemas are a sparser, newer signal.
|
||||
- **Composable through pipes.** Bytes chain. A CSV in object storage, a Markdown file on GitHub, and the output of a transform command can all flow through the same pipeline. Custom response types cannot.
|
||||
|
||||
Mirage is built around that reality:
|
||||
|
||||
- commands consume and produce byte streams
|
||||
- resources expose files and paths instead of custom response types
|
||||
- higher-level structure stays optional at the edge
|
||||
|
||||
Byte-shaped hands are why the same small set of operations (read, write, search, transform, compose) works everywhere.
|
||||
|
||||
## Why This Fits Python and TypeScript
|
||||
|
||||
Both Python and TypeScript are increasingly used inside async frameworks, async servers, and agent applications that already expect non-blocking I/O. Mirage aligns with that direction instead of fighting it.
|
||||
|
||||
- **Python.** `await`-driven command execution and cleanup, compatible with the async stacks that dominate modern Python: [FastAPI](https://fastapi.tiangolo.com), [Starlette](https://www.starlette.io), and the [asyncio](https://docs.python.org/3/library/asyncio.html) runtime they build on.
|
||||
- **TypeScript.** The same model maps cleanly onto promises, [Node.js streams](https://nodejs.org/api/stream.html), and modern frameworks like [Hono](https://hono.dev) or [Express](https://expressjs.com) that expect event-loop-based I/O.
|
||||
|
||||
The important part is not the syntax. Mirage keeps the same hands across Python and TypeScript.
|
||||
|
||||
## What This Buys Agents
|
||||
|
||||
- One interface for acting across local and remote systems.
|
||||
- Better composability across resources.
|
||||
- Streaming behavior that works for pipelines and large reads.
|
||||
- Explicit lifecycle control for execution, caching, and cleanup.
|
||||
- Finer-grained control over how agents read and write, which is better for security than scattered implicit backend mutations.
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Cross-Resource Commands are Handshakes
|
||||
description: Why Mirage treats cross-resource commands like cp, mv, and diff as handshakes, two resources meeting through a single gesture.
|
||||
icon: handshake
|
||||
---
|
||||
|
||||
## Two Resources Meeting Through One Gesture
|
||||
|
||||
<Icon icon="hand-sparkles" /> **[Gestures](/home/design/gestures)** usually act on one resource at a time. A **handshake** is the special case where a single gesture reaches across *two* resources and makes them cooperate.
|
||||
|
||||
- `cp /gdrive/data.csv /s3/data.csv`, Google Drive and S3 coordinate to move a file
|
||||
- `mv /ram/scratch.txt /disk/archive.txt`, in-memory and local disk hand off
|
||||
- `diff /s3/a.txt /github/b.txt`, two resources both produce bytes for comparison
|
||||
- `cat /s3/a.txt /github/b.txt`, aggregator; both feed the same output stream
|
||||
|
||||
In Arm-metaphor terms: two Arms are reaching at once, and their Hands meet, one coordinated motion, two endpoints, two different <Icon icon="eye" /> **[Eyes](/home/design/eyes)** being touched.
|
||||
|
||||
## Why Handshakes Are Special
|
||||
|
||||
A normal gesture like `cat /s3/data.csv` is a single-handed motion: one resource, one <Icon icon="hand-point-up" /> **[Finger](/home/design/fingers)** (`read`), one result.
|
||||
|
||||
A handshake requires two resources to cooperate. That means:
|
||||
|
||||
- **Two fingers, possibly of different kinds.** `cp /s3/a /gdrive/b` is `read` on S3 plus `write` on GDrive.
|
||||
- **A meeting point.** Bytes stream from one resource through Mirage into the other. A direct resource-to-resource path (e.g., S3→S3 server-side copy) is a future optimization; the default is stream-through.
|
||||
- **A shared failure model.** If one side breaks mid-transfer, the gesture must clean up on both sides without leaving partial state.
|
||||
|
||||
## Handshake Types
|
||||
|
||||
| Type | When it fires | Example | Constraint |
|
||||
| --------------- | ------------------------------------ | ----------------------------------- | --------------------------------------------------------------- |
|
||||
| **Transfer** | Write gesture across resources | `cp`, `mv` | Destination must support `write`; `mv` also needs `delete` on source |
|
||||
| **Comparison** | Two reads feeding one comparison | `diff`, `cmp` | Both sides need `read` only, works for every resource |
|
||||
| **Aggregation** | Many reads, one merged output stream | `cat`, `grep`, `wc` across mounts | All sides need `read` only, works for every resource |
|
||||
|
||||
Transfer handshakes own both sides of the motion. Comparison and aggregation handshakes only read, the output lives in Mirage, not in the resources.
|
||||
|
||||
### Asymmetric Resources
|
||||
|
||||
Not every resource can play both sides of a transfer. A read-only resource (today: Google Drive) can be the *source* of `cp` but never the *destination*, and can never participate in `mv` at all (since `mv` deletes the source after writing the destination). The dispatcher detects this at execution time and surfaces a clean failure rather than corrupting state. Aggregation and comparison handshakes are unaffected, they only need reads.
|
||||
|
||||
## How Mirage Handles Them
|
||||
|
||||
Handshakes are implemented as cross-mount handlers inside the command dispatcher. When a gesture receives paths that span resources, Mirage:
|
||||
|
||||
1. **Groups paths by resource** so each side's finger work can run locally.
|
||||
2. **Picks a handler**, a registered cross-mount handler for the specific `(src, dst)` pair, or a generic aggregator across resources, or an error if no handshake exists for this gesture.
|
||||
3. **Coordinates the transfer** as one streaming pipeline, with cleanup on failure.
|
||||
|
||||
See [Commands, Cross-Resource Dispatch](/home/design/commands#cross-resource-dispatch) for the concrete dispatch rules and the current cross-mount gesture list.
|
||||
|
||||
## Why Handshakes Are Still One Motion
|
||||
|
||||
The handshake metaphor lands because the two resources are both within reach of the same <Icon icon="user" /> **[Arm](/home/design/arm)**. A resource is an Eye the Arm can reach toward; a Hand at the end of that reach is what actually touches it. A handshake is the Arm (or two Arms, if you prefer the symmetric read) performing one coordinated motion whose endpoints happen to live on two different resources.
|
||||
|
||||
Without this framing, cross-resource commands feel like a special case bolted on. With it, they are just the gesture the Arm performs when two of its reachable Eyes need to exchange what they see.
|
||||
|
||||
## Tested Matrix
|
||||
|
||||
`tests/integration/test_cross_mount_matrix.py` exercises every handshake type across 14 ordered `(src, dst)` pairs spanning RAM, disk, Redis, S3, and Google Drive, 126 parametrized assertions in total. The matrix confirms both the working combinations and that the unsupported ones (e.g. `cp` into Google Drive, `mv` touching any read-only end) fail cleanly rather than leaving partial state behind.
|
||||
|
||||
## Related
|
||||
|
||||
- [Gestures](/home/design/gestures), single-resource commands, of which handshakes are a variant.
|
||||
- [Commands, Cross-Resource Dispatch](/home/design/commands#cross-resource-dispatch), the concrete dispatch rules.
|
||||
- [Fingers](/home/design/fingers), the `read`/`write` ops a handshake composes.
|
||||
- [Eyes](/home/design/eyes), the resources a handshake reaches across.
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: Index Store
|
||||
description: Caches directory listings and file metadata to eliminate redundant and expensive API calls.
|
||||
icon: list-tree
|
||||
---
|
||||
|
||||
The Index Store is one half of Mirage's <Icon icon="clock-rotate-left" /> [Recall](/home/design/recall) layer (the other is the [File Store](/home/design/file_cache)). It remembers *directory listings and metadata* that the <Icon icon="eye" /> **[Eyes](/home/design/eyes)** have already seen, so `ls`, `stat`, and glob expansion don't trigger network calls on every use.
|
||||
|
||||
## What It Does
|
||||
|
||||
The index cache stores directory listings and file metadata from remote resources. Without it, every `ls`, glob expansion, or `stat` call triggers a network request.
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
graph LR
|
||||
A["ls /s3/data/"] --> B{Listing cached?}
|
||||
B -->|Yes, fresh| C[Return cached entries]
|
||||
B -->|Expired/missing| D[list_objects from S3]
|
||||
D --> E[Cache with TTL]
|
||||
E --> C
|
||||
```
|
||||
|
||||
## What It Caches
|
||||
|
||||
- **Directory listings**, children of a directory with metadata
|
||||
- **File metadata**, name, type, size, timestamps
|
||||
|
||||
This enables fast `stat` lookups. If the parent directory is cached, mirage knows instantly whether a file exists without a network call.
|
||||
|
||||
## TTL Per Resource
|
||||
|
||||
| Resource | TTL | Reason |
|
||||
| --------- | ---- | ----------------------------------------- |
|
||||
| RAM | 0s | In-process, always fresh |
|
||||
| Disk | 60s | Local changes are fast to detect |
|
||||
| S3/GCS/R2 | 600s | API calls are slow and costly |
|
||||
| GitHub | 600s | Repo tree rarely changes within a session |
|
||||
|
||||
After TTL expires, the next directory read re-fetches from the resource.
|
||||
|
||||
### Why TTL, Not Fingerprints
|
||||
|
||||
A directory listing is a *collection*, not a single blob, and resources do not expose a cheap "did this directory change?" fingerprint. The only way to verify staleness is to re-run `list_objects_v2`, which is the call you were trying to cache in the first place. TTL is a time-based bet instead: assume nothing changed for N seconds. The cost of being wrong is bounded. A newly-created file is invisible for at most N seconds, then the listing re-fetches.
|
||||
|
||||
## Invalidation
|
||||
|
||||
Writes invalidate the **parent directory listing** only:
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
graph LR
|
||||
A["touch /s3/data/new.txt"] --> B["Invalidate /s3/data/"]
|
||||
B --> C["Next ls /s3/data/"] --> D["Re-fetch from S3"]
|
||||
```
|
||||
|
||||
## Storage Backends
|
||||
|
||||
| Backend | Use case |
|
||||
| ------- | ------------------- |
|
||||
| RAM | Default, in-process |
|
||||
| Redis | Multi-process |
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: Internals
|
||||
description: Call stack, ByteSource streaming, barriers, and deferred exit codes.
|
||||
icon: microchip
|
||||
---
|
||||
|
||||
## Call Stack
|
||||
|
||||
Shell functions receive arguments and can declare local variables. Each
|
||||
function call needs its own `$1`, `$2`, and `local` variables. Mirage uses
|
||||
a call stack - a stack of frames where each frame holds the local state
|
||||
for one function invocation.
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
graph LR
|
||||
A["greet Alice"] --> B["push Frame 1"] --> C["shout Alice"] --> D["push Frame 2"] --> E["pop Frame 2"] --> F["pop Frame 1"]
|
||||
```
|
||||
|
||||
Each frame holds:
|
||||
|
||||
- **positional** - the `$1`, `$2`, etc. for that function call
|
||||
- **locals** - variables declared with `local`
|
||||
- **function_name** - for debugging
|
||||
- **loop_level** - tracks loop nesting, reset on function entry
|
||||
|
||||
The call stack is created fresh at the start of each `ws.execute()` call
|
||||
and discarded when execution finishes.
|
||||
|
||||
### Variable Resolution Order
|
||||
|
||||
1. `call_stack.get_local(name)` - local variables take priority
|
||||
2. `env.get(name)` - environment variables
|
||||
3. For `$1`, `$@`, `$#` - read from `call_stack.current.positional`
|
||||
|
||||
### Builtins That Use the Call Stack
|
||||
|
||||
| Builtin | Effect |
|
||||
| --------- | --------------------------------------------------- |
|
||||
| `shift` | Remove first N positional params from current frame |
|
||||
| `set --` | Replace positional params in current frame |
|
||||
| `local` | Set variable on current frame |
|
||||
| `source` | Pass same call stack (caller's scope) |
|
||||
| `eval` | Pass same call stack (caller's scope) |
|
||||
|
||||
## ByteSource
|
||||
|
||||
Every command produces and consumes `ByteSource`:
|
||||
|
||||
```python
|
||||
ByteSource = bytes | AsyncIterator[bytes]
|
||||
```
|
||||
|
||||
Data is either materialized (`bytes`) or lazy (`AsyncIterator[bytes]`).
|
||||
|
||||
### Pull-Based Pipes
|
||||
|
||||
In Unix, `cat huge_file | head -n 1` eagerly reads the entire file until
|
||||
SIGPIPE kills the producer. In mirage, `head` pulls chunks from `cat` on
|
||||
demand - when `head` stops pulling, `cat` is never resumed. Zero wasted
|
||||
network I/O.
|
||||
|
||||
| Aspect | Unix (bash) | Mirage |
|
||||
| --------------- | ------------------------------ | --------------------------------- |
|
||||
| Data flow | Push (producer writes eagerly) | Pull (consumer requests on demand)|
|
||||
| Early stop | SIGPIPE kills producer | Producer never resumed |
|
||||
| Wasted I/O | Producer may read ahead | Only requested bytes flow |
|
||||
|
||||
### Barriers
|
||||
|
||||
Pipes stream lazily, but shell operators (`&&`, `||`, `;`) need to inspect
|
||||
results before deciding what to run next. Barriers force lazy streams to
|
||||
materialize at these synchronization points.
|
||||
|
||||
```python
|
||||
class BarrierPolicy(Enum):
|
||||
STREAM = "stream" # no barrier - keep lazy
|
||||
STATUS = "status" # drain stream, sync exit code
|
||||
VALUE = "value" # drain and materialize bytes
|
||||
```
|
||||
|
||||
| Operator | Barrier | Why |
|
||||
| ---------------- | ------------- | -------------------------------------- |
|
||||
| `cmd1 && cmd2` | VALUE on left | Need exit code before running right |
|
||||
| `cmd1 \|\| cmd2` | VALUE on left | Need exit code to decide |
|
||||
| `cmd1 ; cmd2` | VALUE on left | Left must complete first |
|
||||
| `cmd1 \| cmd2` | STREAM | Stages stay lazy |
|
||||
|
||||
### Deferred Exit Codes
|
||||
|
||||
Some commands determine exit codes from output. `grep` returns exit 1
|
||||
when no lines match - but with lazy streaming, it doesn't know until
|
||||
the consumer has pulled all data. `exit_on_empty(stream, io)` wraps
|
||||
the stream and sets `io.exit_code = 1` when it ends empty. The barrier
|
||||
at the operator boundary triggers consumption.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: Limitations
|
||||
description: Shell features not currently supported.
|
||||
icon: triangle-exclamation
|
||||
---
|
||||
|
||||
Mirage supports most common shell features. The following are intentionally
|
||||
not supported due to the async streaming architecture or because they
|
||||
don't apply to a virtual filesystem.
|
||||
|
||||
## How streaming works
|
||||
|
||||
Pipes are **demand-driven**: when downstream stops reading, upstream
|
||||
stops being asked for chunks. The exception is the cache-drain task,
|
||||
when a file is flagged for caching, a background task continues
|
||||
downloading even if downstream exited early, so the cache entry is
|
||||
complete for the next read. This is a deliberate trade-off between
|
||||
bandwidth and cache fill rate, not a bug.
|
||||
|
||||
## Unsupported Shell Syntax
|
||||
|
||||
| Feature | Workaround |
|
||||
| ------- | ---------- |
|
||||
| `>(cmd)` output process substitution | Use pipes: `cmd1 \| cmd2` |
|
||||
| `for ((i=0; i<10; i++))` C-style loops | `for i in $(seq 0 9)` |
|
||||
| Array indexing `${arr[0]}`, `${arr[@]}` | Use separate variables |
|
||||
| `trap` signal handling | No-op (no real processes) |
|
||||
| `tail -f` follow mode | `tail -n` for last N lines (infinite producers need a cancellation protocol mirage doesn't have yet) |
|
||||
| Background cache drain after partial read (`cat file \| head -n 1`) | Drain runs to completion to populate cache; sequential `execute()` calls naturally observe the drain finishing |
|
||||
| `for f in $(find ...)` materializes full output before iterating | For very large outputs, pipe instead: `find ... \| while read f; do ...; done` |
|
||||
| `while read line` loops capped at 10000 iterations | Process larger streams with `awk`/`grep` instead of shell-level loops |
|
||||
| `echo /data/*.json` no glob expansion | `ls /data/*.json` |
|
||||
| `rm -i` interactive prompt | `rm` (non-interactive by default) |
|
||||
| Backtick substitution `` `cmd` `` | `$(cmd)` |
|
||||
| Brace expansion `{a,b,c}` | List items explicitly |
|
||||
|
||||
## Parser Limitations
|
||||
|
||||
| Feature | Workaround |
|
||||
| ------- | ---------- |
|
||||
| `grep -e pattern file` | `cat file \| grep -e pattern` or `grep "pattern" file` |
|
||||
| `awk -e program file` | `awk 'program' file` |
|
||||
| `sed -f script file` | `sed 's/foo/bar/' file` |
|
||||
|
||||
These flags change how operands are classified (TEXT vs PATH) at parse
|
||||
time, which conflicts with mirage's resource routing.
|
||||
|
||||
## Cross-Mount Limitations
|
||||
|
||||
| Feature | Workaround |
|
||||
| ------- | ---------- |
|
||||
| `cp`/`mv` between S3 mounts | Downloads then re-uploads (no server-side copy) |
|
||||
| `diff -r` on S3 | `diff` individual files |
|
||||
| `cp` into a read-only resource (e.g. `cp X /gdrive/...`) | Read-only resources can be source only; pick a writable destination |
|
||||
| `mv` touching a read-only resource on either side | `mv` requires `delete` on source and `write` on destination; use `cp` from the read-only side instead |
|
||||
| `cp`/`mv` writes to GCS via the S3-compat endpoint | GCS rejects boto3's streaming-payload signature with `SignatureDoesNotMatch`; reads work fine. Use the native GCS endpoint or write through a different mount |
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: Mounts
|
||||
description: How resources attach to virtual prefixes and how access modes shape what agents can do.
|
||||
icon: hard-drive
|
||||
---
|
||||
|
||||
A mount is how Mirage attaches one resource to one visible prefix. If [Eyes](/home/design/eyes) are how agents see, a mount is the attachment point that puts a new region of the world within reach of the [Arm](/home/design/arm).
|
||||
|
||||
## Resource Versus Mount
|
||||
|
||||
| Concept | Role |
|
||||
| ------- | ---- |
|
||||
| Resource | Knows how to read and write one external system |
|
||||
| Mount | Binds that resource to a prefix and access mode inside a `Workspace` |
|
||||
|
||||
The same resource type can be mounted more than once at different prefixes, with different access policies.
|
||||
|
||||
## Prefix Routing
|
||||
|
||||
Mirage resolves paths with longest-prefix match.
|
||||
|
||||
That means `/data/sub/file.txt` resolves to `/data/sub/` if both `/data/` and `/data/sub/` exist as mounts.
|
||||
|
||||
```python
|
||||
reg.mount("/data/", p1, MountMode.WRITE)
|
||||
reg.mount("/data/sub/", p2, MountMode.WRITE)
|
||||
```
|
||||
|
||||
This lets Mirage combine broad mounts with narrower overrides.
|
||||
|
||||
## What A Mount Carries
|
||||
|
||||
Each mount bundles the pieces needed for dispatch at one prefix:
|
||||
|
||||
- a normalized prefix such as `/s3/`
|
||||
- the resource instance
|
||||
- an access mode
|
||||
- resource-specific command registrations
|
||||
- resource-specific VFS operation registrations
|
||||
- optional cross-mount command handlers
|
||||
|
||||
In practice, this means Mirage can answer both questions at once:
|
||||
|
||||
- Which backend owns this path?
|
||||
- Which command or filesystem operation should handle it?
|
||||
|
||||
## Mount Modes
|
||||
|
||||
Mount modes control what the [Hands](/home/design/hands) are allowed to do at a prefix.
|
||||
|
||||
| Mode | Behavior |
|
||||
| ---- | -------- |
|
||||
| `READ` | Read only. Write commands are blocked. |
|
||||
| `WRITE` | Read and write. Standard filesystem operations are enabled. |
|
||||
| `EXEC` | Read, write, and execute. |
|
||||
|
||||
## Examples
|
||||
|
||||
### One Global Default
|
||||
|
||||
```python
|
||||
ws = Workspace({"/s3": s3}, mode=MountMode.READ)
|
||||
```
|
||||
|
||||
Every mounted resource inherits `READ` unless a mount overrides it.
|
||||
|
||||
### Per-Mount Control
|
||||
|
||||
```python
|
||||
ws = Workspace({
|
||||
"/s3": (s3, MountMode.READ),
|
||||
"/scratch": (RAMResource(), MountMode.WRITE),
|
||||
})
|
||||
```
|
||||
|
||||
This is the common pattern: remote data stays read only, scratch space stays writable.
|
||||
|
||||
## Related
|
||||
|
||||
- [Workspace](/home/design/workspace) owns the mount registry.
|
||||
- [Sessions](/home/design/session) decide where commands run from.
|
||||
- [Eyes](/home/design/eyes) explain why mounted prefixes are the right representation layer.
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: Execution
|
||||
description: How mirage parses and executes shell commands - from input to result.
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
The Shell is the wiring inside Mirage's [Arm](/home/design/arm), between agents, [Eyes](/home/design/eyes), and [Hands](/home/design/hands). When an agent types `cat /s3/*.csv | grep error`, the Shell figures out which paths the Eyes need to look at, which Hands to use, and in what order, turning a sentence into coordinated execution.
|
||||
|
||||
Under the hood, Mirage includes a shell execution layer that parses and runs commands using tree-sitter-bash. It handles variable expansion, control flow, pipes, and dispatches to registered command handlers.
|
||||
|
||||
## Flow
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
graph LR
|
||||
A["Shell input"] --> B["tree-sitter parse"] --> C["AST walk"] --> D["Expand & resolve"] --> E["Dispatch"]
|
||||
```
|
||||
|
||||
1. **Parse** - tree-sitter-bash produces an AST from the input string
|
||||
2. **Walk** - the executor walks AST nodes, routing each to the appropriate handler
|
||||
3. **Expand** - variables (`$VAR`, `${VAR:-default}`), command substitution (`$(cmd)`), and arithmetic (`$((expr))`) are expanded
|
||||
4. **Resolve** - paths become `PathSpec`, globs are expanded
|
||||
5. **Dispatch** - the command registry finds the handler and executes it
|
||||
|
||||
## Two-Layer Design
|
||||
|
||||
### Shell layer
|
||||
|
||||
Handles anything that requires session state or control flow:
|
||||
|
||||
| Responsibility | Examples |
|
||||
| ------------------ | ------------------------------- |
|
||||
| Variable expansion | `$FOO`, `${FOO:-default}` |
|
||||
| Path resolution | `./file` → `/mirage/data/file` |
|
||||
| Glob expansion | `*.csv` → `a.csv b.csv` |
|
||||
| Control flow | `&&`, `||`, `;`, `|`, `&` |
|
||||
| Shell builtins | `cd`, `export`, `echo`, `test` |
|
||||
|
||||
### Command layer
|
||||
|
||||
Handles stateless data transformation:
|
||||
|
||||
| Responsibility | Examples |
|
||||
| ----------------- | --------------------------- |
|
||||
| File processing | `cat`, `head`, `grep` |
|
||||
| Format conversion | parquet → text |
|
||||
| Data search | `find`, `grep -r` |
|
||||
|
||||
Commands receive fully resolved arguments - they have no concept of
|
||||
variables, globs, or sessions.
|
||||
|
||||
## Shell Features
|
||||
|
||||
### Pipes & Redirection
|
||||
|
||||
| Feature | Example |
|
||||
| ------- | ------- |
|
||||
| Pipe | `cmd1 \| cmd2` |
|
||||
| Stderr pipe | `cmd \|& cmd2` |
|
||||
| Redirect stdout | `cmd > file`, `cmd >> file` |
|
||||
| Redirect stderr | `cmd 2> file`, `cmd 2>&1` |
|
||||
| Redirect both | `cmd &> file` |
|
||||
| Stdin from file | `cmd < file` |
|
||||
| Heredoc | `cmd <<EOF` |
|
||||
| Herestring | `cmd <<< "text"` |
|
||||
| Process substitution | `cmd <(other_cmd)` |
|
||||
|
||||
### Control Flow
|
||||
|
||||
| Feature | Example |
|
||||
| ------- | ------- |
|
||||
| AND / OR | `cmd1 && cmd2`, `cmd1 \|\| cmd2` |
|
||||
| Sequential | `cmd1 ; cmd2` |
|
||||
| If/elif/else | `if cmd; then ...; elif ...; else ...; fi` |
|
||||
| For loop | `for x in a b c; do ...; done` |
|
||||
| While / Until | `while cmd; do ...; done` |
|
||||
| Case | `case $x in pat) ...;; esac` |
|
||||
| Subshell | `(cmd1; cmd2)` |
|
||||
| Group | `{ cmd1; cmd2; }` |
|
||||
| Background | `cmd &` |
|
||||
| Negate | `! cmd` |
|
||||
|
||||
### Variable Expansion
|
||||
|
||||
| Feature | Example |
|
||||
| ------- | ------- |
|
||||
| Simple | `$VAR`, `${VAR}` |
|
||||
| Default value | `${VAR:-default}` |
|
||||
| Special vars | `$?`, `$#`, `$@`, `$*`, `$0`, `$1`..`$9` |
|
||||
| Command substitution | `$(cmd)` |
|
||||
| Arithmetic | `$((a + b))` |
|
||||
|
||||
### Functions
|
||||
|
||||
```bash
|
||||
greet() {
|
||||
local prefix=">>>"
|
||||
echo "$prefix $1"
|
||||
}
|
||||
greet Alice
|
||||
```
|
||||
|
||||
Functions support `local` variables, positional parameters (`$1`, `$@`),
|
||||
`shift`, `return`, and a per-call call stack.
|
||||
|
||||
## Builtins
|
||||
|
||||
| Builtin | Description |
|
||||
| ------- | ----------- |
|
||||
| `cd` | Change working directory |
|
||||
| `pwd` | Print working directory |
|
||||
| `export` | Set environment variable |
|
||||
| `unset` | Remove environment variable |
|
||||
| `printenv` | Print environment |
|
||||
| `echo` | Print text |
|
||||
| `printf` | Formatted output |
|
||||
| `test` / `[` / `[[` | Conditional expressions |
|
||||
| `read` | Read line from stdin into variable |
|
||||
| `local` | Declare function-scoped variable |
|
||||
| `set` | Set positional parameters |
|
||||
| `shift` | Remove first positional parameter |
|
||||
| `source` / `.` | Execute script in current session |
|
||||
| `eval` | Evaluate string as command |
|
||||
| `return` | Return from function |
|
||||
| `break` / `continue` | Loop control |
|
||||
| `true` / `false` | Exit with 0 / 1 |
|
||||
| `sleep` | Sleep for N seconds |
|
||||
| `python` | Execute Python code |
|
||||
| `xargs` | Build and execute commands from stdin |
|
||||
| `timeout` | Run command with time limit |
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Cache Store is Recall
|
||||
description: Why Mirage calls its cache layer Recall, and how the Index Store and File Store divide the work.
|
||||
icon: clock-rotate-left
|
||||
---
|
||||
|
||||
## The Cache Is Recall
|
||||
|
||||
<Icon icon="eye" /> **[Eyes](/home/design/eyes)** see. <Icon icon="hand" /> **[Hands](/home/design/hands)** act. <Icon icon="brain" /> **Brain** (the Agent) decides. **Recall** is what those three don't have to do again.
|
||||
|
||||
Every `cat /s3/data.csv` costs something real: a network round trip, a format decode, and tokens the Brain spent orchestrating the chain. Recall preserves the bytes that came out of that work so the next time the same request appears, Mirage returns the stored bytes directly. The fetch, the decode, and the reasoning round-trip are all saved.
|
||||
|
||||
We call it **Recall** instead of "cache" because that is what it does for the agent: retrieve prior work so the same effort is not spent twice.
|
||||
|
||||
## Two Stores, Two Kinds Of Work
|
||||
|
||||
Recall splits into two layers. Each targets a different kind of redundant work.
|
||||
|
||||
- **[Index Store](/home/design/index_cache)**, remembers what the Eyes have *seen*: directory listings and file metadata. Without it, every `ls`, glob expansion, or `stat` call is a network round trip.
|
||||
- **[File Store](/home/design/file_cache)**, remembers what the Hands have *produced*: the byte output of file-touching commands. Without it, every repeat `cat /s3/data.csv` re-fetches, re-decodes, and re-triggers the agent chain.
|
||||
|
||||
The two stores have different freshness strategies because they face different problems:
|
||||
|
||||
| Store | Freshness | Why |
|
||||
| ----------- | ------------------- | ------------------------------------------------------------------------------- |
|
||||
| Index Store | TTL | Directory listings are collections. No cheap per-directory fingerprint exists. |
|
||||
| File Store | Fingerprint | Files have cheap per-blob identifiers (S3 ETag, filesystem mtime, GitHub SHA). |
|
||||
|
||||
See each store's page for the full reasoning.
|
||||
|
||||
## Recall Is A Decision, Not A Default
|
||||
|
||||
Mirage does not cache eagerly. Each <Icon icon="hand" /> **Hand** declares what is worth remembering by populating `IOResult.cache`. Today this is done heuristically, by the shape of the command: `cat /s3/data.csv` opts in because the bytes are very likely to be reused; `stat` does not because its output is metadata the Index Store already tracks.
|
||||
|
||||
This keeps Recall under the control of the Hands (and the Brain orchestrating them), not the filesystem layer. A future direction is to let the Brain annotate cacheability at call time, but the current default is that each Hand knows its own semantics.
|
||||
|
||||
## Related
|
||||
|
||||
- [Index Store](/home/design/index_cache), the directory-listing and metadata half of Recall.
|
||||
- [File Store](/home/design/file_cache), the file-content half of Recall.
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
title: Sessions
|
||||
description: "Per-terminal execution context inside Mirage: cwd, env, functions, and last exit state."
|
||||
icon: computer
|
||||
---
|
||||
|
||||
A `Session` is the mutable execution context inside a `Workspace`. The [Arm](/home/design/arm) is shared. A session is the posture the Arm holds for one agent as it runs commands.
|
||||
|
||||
In Mirage, a session has two parallel pieces:
|
||||
|
||||
- mutable in-memory execution state such as cwd, env, functions, and exit code
|
||||
- a read-only observer stream at `/.sessions/<utc-yyyy-mm-dd>/<session_id>.jsonl` that records what the session did
|
||||
|
||||
## What A Session Stores
|
||||
|
||||
| Field | Meaning |
|
||||
| ----- | ------- |
|
||||
| `session_id` | Stable identifier for the session |
|
||||
| `cwd` | Current working directory |
|
||||
| `env` | Environment variables |
|
||||
| `functions` | Shell functions defined in that session |
|
||||
| `last_exit_code` | Exit status from the previous command |
|
||||
| `created_at` | Creation timestamp |
|
||||
|
||||
## Unix Analogy
|
||||
|
||||
| Unix shell session | Mirage Session |
|
||||
| ------------------ | -------------- |
|
||||
| Current directory | `Session.cwd` |
|
||||
| Environment vars | `Session.env` |
|
||||
| Shell functions | `Session.functions` |
|
||||
| `$?` | `Session.last_exit_code` |
|
||||
| Open a terminal | `ws.create_session()` |
|
||||
| Reattach to a terminal | `ws.get_session(id)` |
|
||||
|
||||
## What A Session Does Not Own
|
||||
|
||||
A session does not own mounts, cache, or command registrations. It also does not execute commands by itself.
|
||||
|
||||
The `Workspace` owns the execution machinery. The session just carries the mutable state that execution reads and updates.
|
||||
|
||||
## Isolation And Sharing
|
||||
|
||||
Sessions are isolated by default:
|
||||
|
||||
- one session can `cd /data` without changing another session
|
||||
- one session can `export DEBUG=1` without leaking that value elsewhere
|
||||
- one session can define shell functions that do not appear in another session
|
||||
|
||||
Sessions are also identity-light. They are keyed by `session_id`, not permanently bound to one agent identity. If two agents intentionally use the same session id, they share the same cwd, env, and shell state.
|
||||
|
||||
## Default Session
|
||||
|
||||
Every `Workspace` starts with a default session, so single-agent use cases work without any setup:
|
||||
|
||||
```python
|
||||
ws = Workspace({"/data": RAMResource()}, mode=MountMode.WRITE)
|
||||
await ws.execute("pwd")
|
||||
```
|
||||
|
||||
## Lifecycle
|
||||
|
||||
```python
|
||||
session = ws.create_session("agent-1")
|
||||
session = ws.get_session("agent-1")
|
||||
sessions = ws.list_sessions()
|
||||
await ws.close_session("agent-1")
|
||||
```
|
||||
|
||||
## Observer Stream
|
||||
|
||||
Each session is mirrored into a JSONL file at:
|
||||
|
||||
```text
|
||||
/.sessions/<utc-yyyy-mm-dd>/<session_id>.jsonl
|
||||
```
|
||||
|
||||
The date folder reflects the UTC date when each line was appended, so a session
|
||||
that crosses midnight UTC writes into two folders. Mirage appends both
|
||||
command-level and operation-level records there as the session runs. The
|
||||
session id appears in two places:
|
||||
|
||||
- in the filename, such as `/.sessions/2026-04-29/default.jsonl`
|
||||
- in each JSONL record under the `session` field
|
||||
|
||||
This observer stream is not the session's mutable state. It is the execution
|
||||
trace attached to that session.
|
||||
|
||||
## Related
|
||||
|
||||
- [Workspace](/home/design/workspace) owns session lifecycle.
|
||||
- [Shell](/home/design/pipeline) reads and updates session state during execution.
|
||||
@@ -0,0 +1,244 @@
|
||||
---
|
||||
title: Snapshot
|
||||
description: Save, load, and copy a Workspace, full state capture in a portable tar bundle.
|
||||
icon: floppy-disk
|
||||
---
|
||||
|
||||
A `Workspace` can be **saved** to a tar archive, **loaded** back into a new
|
||||
Workspace, or **copied** in-process. The same machinery powers OpenAI
|
||||
Agents sandbox checkpoints (`persist_workspace` / `hydrate_workspace`).
|
||||
|
||||
## Three methods
|
||||
|
||||
```python
|
||||
ws.snapshot("snap.tar") # serialize to disk
|
||||
ws.snapshot("snap.tar.gz", compress="gz") # gzipped tar
|
||||
ws.snapshot(buf) # serialize to a BytesIO
|
||||
|
||||
new_ws = Workspace.load("snap.tar",
|
||||
resources={"/s3": fresh_s3_resource})
|
||||
|
||||
forked = ws.copy() # in-process deep copy
|
||||
forked = copy.deepcopy(ws) # same path via stdlib
|
||||
copy.copy(ws) # raises NotImplementedError
|
||||
```
|
||||
|
||||
`copy()` is **always deep**; shallow copy would alias `_cache`,
|
||||
`_session_mgr`, resources, history, etc., that gives an alias, not a
|
||||
copy. `copy.copy(ws)` raises with a message pointing to `ws.copy()`
|
||||
and `copy.deepcopy(ws)`.
|
||||
|
||||
## What's in a snapshot
|
||||
|
||||
| Captured | Notes |
|
||||
|---|---|
|
||||
| Mounts (per resource state) | Content for RAM/Disk/Redis; config-only for cloud/token resources |
|
||||
| Sessions (cwd, env per session) | All active sessions |
|
||||
| Dirty inode tracker | `_tracker._inodes` |
|
||||
| File cache (RAM-backed entries) | Bytes + fingerprint + ttl + cached_at |
|
||||
| `max_drain_bytes` setting | The cancellable-cache-drain budget |
|
||||
| Execution history | `ExecutionRecord`s with command, stdout, exit code, timestamps |
|
||||
| Finished jobs | Job table entries with status `COMPLETED` / `KILLED` |
|
||||
|
||||
| **NOT** captured | Why |
|
||||
|---|---|
|
||||
| Running jobs | Pending tasks can't be serialized; only finished jobs survive |
|
||||
| Drain tasks | `asyncio.Task` objects, runtime-only |
|
||||
| FUSE mountpoint | Environment-specific |
|
||||
| Cloud credentials | Redacted with `"<REDACTED>"` sentinel |
|
||||
|
||||
## Format: tar + JSON manifest + raw blobs
|
||||
|
||||
A snapshot is a **tar archive** containing a `manifest.json` and a set
|
||||
of binary blob files:
|
||||
|
||||
```
|
||||
snap.tar
|
||||
├── manifest.json # state metadata; bytes replaced by {"__file": ...} refs
|
||||
├── mounts/
|
||||
│ ├── 0/files/0.bin # RAM mount file (numbered blob)
|
||||
│ ├── 1/files/data/a.txt # Disk mount file (tree-preserving)
|
||||
│ └── 2/data/0.bin # Redis mount value (numbered blob)
|
||||
└── cache/blobs/0.bin # cache entry bytes
|
||||
```
|
||||
|
||||
The `manifest.json` contains everything except the bytes; binary fields
|
||||
are replaced with `{"__file": "<tar-relative-path>"}` placeholders that
|
||||
the loader resolves by reading from the tar.
|
||||
|
||||
### Why tar + JSON instead of pickle?
|
||||
|
||||
| Property | Pickle | Tar + JSON manifest |
|
||||
|---|---|---|
|
||||
| Inspectable with standard tools | no | yes (`tar tf`, `jq`) |
|
||||
| Portable to TS / Go / Rust | no | yes |
|
||||
| Safe to load from untrusted source | **no, RCE** | yes (no eval) |
|
||||
| Streaming-friendly | no | yes (tar streams) |
|
||||
| Native bytes support | yes | yes (raw blob files) |
|
||||
| OpenAI Agents sandbox compat | no | yes (their unix_local/docker/vercel backends use tar too) |
|
||||
|
||||
The only cost is a few hundred extra LOC for tar bundling.
|
||||
|
||||
### Disk mounts: tree-preserving layout
|
||||
|
||||
For `DiskResource` mounts, files are placed at their *relative path*
|
||||
inside the tar (not as numbered blobs), so `tar -xf snap.tar -C
|
||||
/somewhere` extracts a usable directory tree:
|
||||
|
||||
```
|
||||
/somewhere/mounts/1/files/data/a.txt # actual file content, original name
|
||||
/somewhere/mounts/1/files/sub/b.txt
|
||||
```
|
||||
|
||||
This makes Mirage snapshots compatible with tools that expect to
|
||||
extract real files.
|
||||
|
||||
## Backend serialization policy
|
||||
|
||||
| Backend | What's saved | `needs_override` at load |
|
||||
|---|---|---|
|
||||
| RAM | full files dict + dirs (bytes as side-files) | no |
|
||||
| Disk | full file tree (real files inside the tar) | no (default = fresh tmpdir; or pass `DiskResource(root=...)`) |
|
||||
| Redis | full key+value dump | yes (caller supplies target Redis URL) |
|
||||
| S3, R2, OCI, GCS, Supabase | bucket/region; creds stripped | yes |
|
||||
| GDrive, Gmail, GDocs, GSheets, GSlides | client_id; secret + refresh_token stripped | yes |
|
||||
| Slack, Discord, Telegram, Notion, Linear, Trello, GitHub, GitHub CI, Email, Langfuse, MongoDB | endpoint config; token stripped | yes |
|
||||
| SSH | host config; key path stripped | no (path-based, no embedded secret) |
|
||||
| Paperclip | path config | no |
|
||||
|
||||
### `needs_override=True` semantics
|
||||
|
||||
When loading a snapshot, every mount with `needs_override=True` MUST
|
||||
have an entry in `resources={...}`:
|
||||
|
||||
```python
|
||||
ws = Workspace.load(
|
||||
"snap.tar",
|
||||
resources={
|
||||
"/s3": S3Resource(config=S3Config(... fresh creds ...)),
|
||||
"/redis": RedisResource(url="redis://prod:6379/0"),
|
||||
"/gdrive": GoogleDriveResource(config=...),
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
If any are missing, **load fails fast** with a single `ValueError`
|
||||
listing all missing prefixes:
|
||||
|
||||
```
|
||||
Workspace.load: resources= must include overrides for: ['/s3', '/gdrive'].
|
||||
These mounts were saved with redacted creds or transient connection
|
||||
state and need fresh resources.
|
||||
```
|
||||
|
||||
For RAM/Disk/SSH/Paperclip, no override is required, the snapshot
|
||||
already contains everything needed to reconstruct them.
|
||||
|
||||
### Credential redaction
|
||||
|
||||
Cloud and token resource configs replace sensitive fields with the
|
||||
literal string `"<REDACTED>"`:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "s3",
|
||||
"needs_override": true,
|
||||
"redacted_fields": ["aws_access_key_id", "aws_secret_access_key"],
|
||||
"config": {
|
||||
"bucket": "my-bucket",
|
||||
"region": "us-east-1",
|
||||
"aws_access_key_id": "<REDACTED>",
|
||||
"aws_secret_access_key": "<REDACTED>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A test asserts the literal cred bytes never appear anywhere in the
|
||||
saved tar. Distinct from `None` so the loader can tell "deliberately
|
||||
stripped" from "legitimately empty."
|
||||
|
||||
## `copy()` vs `save()` + `load()`, one important divergence
|
||||
|
||||
| | `copy()` | `save → load` |
|
||||
|---|---|---|
|
||||
| Local backends (RAM, Disk) | independent, writes to copy don't reach original | independent, same |
|
||||
| Remote backends (S3, GDrive, ...) | **reuses the original Resource instance**, both copies share the same bucket/folder | requires `resources=` override; loader picks fresh creds (typically same bucket, but it's the caller's choice) |
|
||||
| Redis | reuses the original Resource, both copies see the same Redis instance | requires `resources=` override; data is dumped + restored to caller-supplied URL |
|
||||
| Cache | independent (each fork gets its own cache restored from snapshot) | independent, same |
|
||||
| Speed | fast, no serialization | slower, full tar I/O |
|
||||
| Use case | speculative execution, parallel agents within one process | resume across processes, share with another machine, OpenAI Agents checkpoint |
|
||||
|
||||
`copy()` is for "fork the local view"; `save/load` is for "ship state
|
||||
elsewhere." The Redis/S3 sharing in `copy()` is intentional, we
|
||||
assume you don't want a copy to silently fork your remote bucket.
|
||||
|
||||
## Path-traversal defense on load
|
||||
|
||||
Every `{"__file": "<path>"}` reference in the manifest is validated
|
||||
against an allowlist before being read from the tar:
|
||||
|
||||
- Reject empty strings
|
||||
- Reject leading `/` (absolute paths)
|
||||
- Reject `..` segments
|
||||
- Reject NUL byte
|
||||
|
||||
A maliciously crafted tar with `{"__file": "../../etc/passwd"}` is
|
||||
rejected before any read with a clear `ValueError("Unsafe blob path")`.
|
||||
We never use `tarfile.extractall`, only `extractfile(named_member)`
|
||||
for entries the manifest explicitly references.
|
||||
|
||||
Spaces and unicode in filenames work fine (only the *structural*
|
||||
characters above are blocked).
|
||||
|
||||
## OpenAI Agents integration
|
||||
|
||||
`MirageSandboxSession.persist_workspace()` and `hydrate_workspace(data)`
|
||||
plug into the OpenAI Agents SDK's `BaseSandboxSession` checkpoint API
|
||||
, the same surface every other backend (`unix_local`, `docker`,
|
||||
`vercel`, `runloop`, `cloudflare`) implements:
|
||||
|
||||
```python
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import SandboxRunConfig
|
||||
from mirage.agents.openai_agents import MirageSandboxClient
|
||||
|
||||
client = MirageSandboxClient(workspace)
|
||||
session = await client.create()
|
||||
|
||||
# Agent does work in the session...
|
||||
|
||||
# Then checkpoint:
|
||||
snapshot = await session.persist_workspace() # BytesIO of a tar
|
||||
|
||||
# Later, in a fresh session:
|
||||
fresh_session = await fresh_client.create()
|
||||
await fresh_session.hydrate_workspace(snapshot)
|
||||
# fresh_session's workspace now has the original's files, cache, history
|
||||
```
|
||||
|
||||
`hydrate_workspace` mutates the existing session's workspace in place
|
||||
(rather than constructing a new one). This is required by the SDK's
|
||||
`BaseSandboxSession` API; for general use prefer `Workspace.load(...)`
|
||||
which returns a fresh Workspace.
|
||||
|
||||
The sandbox session's `hydrate_workspace` is implemented in terms of
|
||||
the public `read_tar` + `apply_state_dict` primitives, same building
|
||||
blocks `Workspace.load` uses, just without `Workspace` construction.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- **`copy.copy(ws)`**, raises. Workspace has no useful shallow copy.
|
||||
- **Hand-editing the tar**, manifest validation may reject the result.
|
||||
Use `to_state_dict` + `split_manifest_and_blobs` + `write_tar` to
|
||||
build snapshots programmatically.
|
||||
- **Calling `Workspace.load` on an untrusted source assuming it's safe
|
||||
because it's not pickle**, it's safer than pickle (no RCE), but
|
||||
still validate provenance before loading anything.
|
||||
- **Loading a snapshot from a different Mirage version**, v1
|
||||
refuses non-`version: 1` snapshots; migrations come when needed.
|
||||
|
||||
## Related
|
||||
|
||||
- [Workspace](/home/design/workspace), the kernel that owns the state being snapshotted.
|
||||
- [File Store](/home/design/file_cache), the cache layer captured in a snapshot.
|
||||
- [Mounts](/home/design/mount), resource-prefix routing that must be re-supplied at load time.
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: Ops
|
||||
description: The virtual filesystem layer that routes operations to resources through a mount table.
|
||||
icon: folder-tree
|
||||
---
|
||||
|
||||
The Ops layer is mirage's resource-agnostic dispatch layer. It maintains a mount table mapping path prefixes to resources and routes every file operation to the correct backend.
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
graph LR
|
||||
A[Agent] --> B[Workspace]
|
||||
B --> C[Ops]
|
||||
C --> D{Mount Table}
|
||||
D --> E["/s3/ → S3Resource"]
|
||||
D --> F["/github/ → GitHubResource"]
|
||||
D --> G["/slack/ → SlackResource"]
|
||||
```
|
||||
|
||||
## Mount Table
|
||||
|
||||
Mounts are sorted by prefix length (longest first), so overlapping
|
||||
prefixes resolve correctly. A path like `/s3/data/file.txt` resolves
|
||||
to the S3 resource with relative key `data/file.txt`.
|
||||
|
||||
## Path Resolution
|
||||
|
||||
Each virtual path is resolved to:
|
||||
|
||||
- **Mount point** - the matching prefix
|
||||
- **PathSpec** - carries the original path, prefix, and resource-relative key via `strip_prefix`
|
||||
- **Accessor** - the resource's data access layer
|
||||
- **Cache store** - the resource's cache backend
|
||||
- **Mount mode** - READ, WRITE, or EXEC
|
||||
|
||||
## Operations
|
||||
|
||||
| Operation | Description |
|
||||
| ----------- | ---------------------------------------- |
|
||||
| `read` | Read file content (supports range reads) |
|
||||
| `write` | Write data to a file |
|
||||
| `stat` | Get file metadata (size, type, mtime) |
|
||||
| `readdir` | List directory contents |
|
||||
| `mkdir` | Create a directory |
|
||||
| `unlink` | Delete a file |
|
||||
| `rmdir` | Remove a directory |
|
||||
| `rename` | Move or rename a file |
|
||||
| `create` | Create a new empty file |
|
||||
| `truncate` | Truncate a file to zero bytes |
|
||||
| `append` | Append data to a file |
|
||||
|
||||
## Concurrency
|
||||
|
||||
Each path gets its own async lock to prevent cache corruption during
|
||||
concurrent reads and writes. Operations on different paths run in
|
||||
parallel; operations on the same path are serialized.
|
||||
|
||||
## File Type Detection
|
||||
|
||||
The Ops layer detects file types from extensions and delegates to
|
||||
type-specific handlers. For example, reading a `.parquet` file
|
||||
returns a formatted table rather than raw bytes.
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: Workspace
|
||||
description: The shared kernel inside Mirage's Arm. It owns mounts, cache, command registry, jobs, and execution history.
|
||||
icon: server
|
||||
---
|
||||
|
||||
The `Workspace` is the shared kernel inside Mirage's [Arm](/home/design/arm).
|
||||
It is the concrete Python object that holds the resources shared across
|
||||
mounts, commands, and sessions.
|
||||
|
||||
Technically, `Workspace` owns the global coordination layer of Mirage:
|
||||
mounts, cache, command registry, job tracking, execution history, and session
|
||||
lifecycle.
|
||||
|
||||
## Unix Analogy
|
||||
|
||||
| Unix kernel | Mirage Workspace |
|
||||
| ----------- | ---------------- |
|
||||
| VFS mount table | `MountRegistry` |
|
||||
| Page cache | File cache and index cache |
|
||||
| Process table | Job table |
|
||||
| System call dispatch | Command registry |
|
||||
| Shell host | Session manager plus executor |
|
||||
|
||||
Just as the kernel is shared across all shells and processes, the `Workspace`
|
||||
is shared across all sessions. Multiple sessions read from the same mounts,
|
||||
share the same cache, and have their jobs recorded in the same job table.
|
||||
|
||||
## What Workspace Owns
|
||||
|
||||
- **Mount registry** - prefix-based routing of virtual paths to resources
|
||||
- **Cache** - shared file and index cache used across commands and sessions
|
||||
- **Command registry** - command and op lookup through mounted resources
|
||||
- **Job table** - background job tracking
|
||||
- **Execution history** - recorded command runs and I/O metadata
|
||||
- **Observer stream** - read-only session traces exposed at `/.sessions`
|
||||
- **Session manager** - creation, lookup, and closing of sessions
|
||||
|
||||
## What Workspace Coordinates
|
||||
|
||||
- `execute()` parses a shell command, runs it against the right mounts, and returns an `IOResult`
|
||||
- `dispatch()` routes low-level ops to the right mounted resource
|
||||
- `sync()` flushes dirty data back to resources when the sync policy requires it
|
||||
- `close()` shuts down FUSE state, background tasks, and cache state
|
||||
|
||||
## Session Lifecycle Through Workspace
|
||||
|
||||
- `create_session()` creates a new session with its own cwd and env
|
||||
- `get_session()` retrieves a session by id
|
||||
- `list_sessions()` lists all active sessions
|
||||
- `close_session()` closes one session
|
||||
- `close_all_sessions()` closes every non-default session
|
||||
|
||||
Session details live on the separate [Sessions](/home/design/session) page.
|
||||
|
||||
## What Workspace Does Not Own
|
||||
|
||||
- **Per-session mutable state** - cwd, env, functions, and last exit code live on `Session`
|
||||
- **Mount semantics** - prefix binding and access policy belong to [Mounts](/home/design/mount)
|
||||
- **Shell syntax** - parsing, expansion, and control flow belong to the [Shell](/home/design/pipeline)
|
||||
|
||||
## Common Shape
|
||||
|
||||
```python
|
||||
ws = Workspace({
|
||||
"/": (RAMResource(), MountMode.WRITE),
|
||||
"/s3": (s3, MountMode.READ),
|
||||
})
|
||||
```
|
||||
|
||||
This is the common split: writable scratch space at `/`, read-only remote data
|
||||
at named prefixes.
|
||||
|
||||
## `execute()`
|
||||
|
||||
```python
|
||||
async def execute(
|
||||
self,
|
||||
command: str,
|
||||
session_id: str = DEFAULT_SESSION_ID,
|
||||
stdin: AsyncIterator[bytes] | bytes | None = None,
|
||||
provision: bool = False,
|
||||
agent_id: str = DEFAULT_AGENT_ID,
|
||||
native: bool | None = None,
|
||||
):
|
||||
```
|
||||
|
||||
`execute()` is the main entrypoint into the kernel. It parses the command,
|
||||
looks up the target session, resolves mounts, runs the shell executor, applies
|
||||
I/O side effects, and records history.
|
||||
|
||||
## Persistence: save, load, copy
|
||||
|
||||
A `Workspace` can be serialized to a tar archive and reloaded later, or
|
||||
copied in-process for speculative execution / parallel agent forks:
|
||||
|
||||
```python
|
||||
ws.snapshot("snap.tar") # full state to disk
|
||||
new_ws = Workspace.load("snap.tar", # fresh Workspace from snapshot
|
||||
resources={"/s3": fresh_s3})
|
||||
forked = ws.copy() # in-process deep copy
|
||||
```
|
||||
|
||||
The snapshot captures mounts (with resource content for RAM/Disk/Redis),
|
||||
cache entries, sessions, dirty inodes, history, and finished jobs. Cloud
|
||||
credentials are redacted at save time and must be re-supplied via
|
||||
`resources=...` at load time. The same machinery powers OpenAI Agents
|
||||
sandbox checkpoints (`persist_workspace` / `hydrate_workspace`).
|
||||
|
||||
See [Snapshot](/home/design/snapshot) for the format, override rules,
|
||||
copy-vs-save divergence, and OpenAI Agents integration.
|
||||
|
||||
## Related
|
||||
|
||||
- [Mounts](/home/design/mount) explain prefix routing and access modes.
|
||||
- [Sessions](/home/design/session) explain per-terminal state.
|
||||
- [Snapshot](/home/design/snapshot) covers save/load/copy and the OpenAI Agents sandbox checkpoint integration.
|
||||
- [Arm](/home/design/arm) explains the higher-level metaphor.
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: Installation
|
||||
description: Install Mirage with the Python package mirage-ai or the TypeScript packages @struktoai/mirage-node and @struktoai/mirage-browser.
|
||||
keywords: ["install Mirage", "mirage-ai", "@struktoai/mirage-node", "virtual filesystem", "AI agents"]
|
||||
icon: download
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Python** ≥ 3.12 for the `mirage-ai` package and the `mirage` CLI
|
||||
- **Node.js** ≥ 20 for the TypeScript SDK
|
||||
- **macOS** or **Linux** (FUSE-based mounts require platform support)
|
||||
|
||||
## Python
|
||||
|
||||
```bash
|
||||
uv add mirage-ai
|
||||
```
|
||||
|
||||
This installs both the `mirage` library and the `mirage` CLI binary.
|
||||
|
||||
## TypeScript
|
||||
|
||||
```bash
|
||||
npm install mirage
|
||||
```
|
||||
|
||||
One package, three entrypoints. Pick the one that matches your runtime:
|
||||
|
||||
- `mirage/node`: Node.js servers and CLIs
|
||||
- `mirage/browser`: browser / edge runtimes
|
||||
- `mirage/core`: runtime-agnostic primitives
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
curl -fsSL https://strukto.ai/install.sh | sh
|
||||
```
|
||||
|
||||
Or via your package manager of choice:
|
||||
|
||||
```bash
|
||||
brew install mirage
|
||||
```
|
||||
|
||||
```bash
|
||||
uvx mirage-ai
|
||||
```
|
||||
|
||||
```bash
|
||||
npx mirage
|
||||
```
|
||||
|
||||
## More Details
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Python" icon="/images/python-logo.svg" href="/python/install">
|
||||
Resource extras, virtualenv setup, and `uv` workflow.
|
||||
</Card>
|
||||
<Card title="TypeScript" icon="/images/typescript-logo.svg" href="/typescript/install">
|
||||
Native peers (FUSE, Redis) and per-runtime notes for Node, browser, and edge.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,390 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: Unified Virtual Filesystem for AI Agents.
|
||||
icon: /images/mirage-icon-light.svg
|
||||
mode: "frame"
|
||||
---
|
||||
|
||||
<div className="px-4 py-1 mt-0 lg:pb-16 max-w-6xl mx-auto">
|
||||
|
||||
<div className="mx-auto w-full max-w-[1200px]">
|
||||
<img noZoom src="/images/mirage-text-logo-light.svg" alt="Mirage" className="block dark:hidden w-full" />
|
||||
<img noZoom src="/images/mirage-text-logo-dark.svg" alt="Mirage" className="hidden dark:block w-full" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="max-w-6xl mx-auto px-4 lg:px-6 text-black dark:text-white">
|
||||
|
||||
<h2 className="text-2xl font-bold mb-6">Mirage in 30 Seconds</h2>
|
||||
|
||||
<div className="max-w-none text-[15px] leading-7 mb-4">
|
||||
<p className="my-0">
|
||||
Mount your resources, then run shell commands across them. The same <code>echo</code>, <code>ls</code>, <code>grep</code> work against an in-memory mount, <Icon icon="aws" /> S3, <Icon icon="slack" /> Slack, etc.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python" icon="/images/python-logo.svg">
|
||||
```python
|
||||
from mirage import Workspace
|
||||
from mirage.resource.ram import RAMResource
|
||||
from mirage.resource.s3 import S3Resource
|
||||
from mirage.resource.slack import SlackResource
|
||||
|
||||
ws = Workspace({
|
||||
"/data": RAMResource(),
|
||||
"/s3": S3Resource(bucket="my-bucket"),
|
||||
"/slack": SlackResource(),
|
||||
})
|
||||
|
||||
# Same shell vocabulary, three different backends.
|
||||
await ws.execute('echo "hello mirage" > /data/hello.txt')
|
||||
await ws.execute("ls /s3/reports/")
|
||||
result = await ws.execute('grep -r "release" /slack/eng')
|
||||
print(await result.stdout_str())
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="TypeScript" icon="/images/typescript-logo.svg">
|
||||
```typescript
|
||||
import {
|
||||
RAMResource,
|
||||
S3Resource,
|
||||
SlackResource,
|
||||
Workspace,
|
||||
} from '@struktoai/mirage-node'
|
||||
|
||||
const ws = new Workspace({
|
||||
'/data': new RAMResource(),
|
||||
'/s3': new S3Resource({ bucket: 'my-bucket' }),
|
||||
'/slack': new SlackResource(),
|
||||
})
|
||||
|
||||
// Same shell vocabulary, three different backends.
|
||||
await ws.execute('echo "hello mirage" > /data/hello.txt')
|
||||
await ws.execute('ls /s3/reports/')
|
||||
const res = await ws.execute('grep -r "release" /slack/eng')
|
||||
console.log(new TextDecoder().decode(res.stdout))
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="CLI" icon="terminal">
|
||||
```bash
|
||||
cat > workspace.yaml <<'EOF'
|
||||
mode: WRITE
|
||||
mounts:
|
||||
/data:
|
||||
resource: ram
|
||||
mode: WRITE
|
||||
/s3:
|
||||
resource: s3
|
||||
mode: READ
|
||||
config:
|
||||
bucket: ${AWS_S3_BUCKET}
|
||||
/slack:
|
||||
resource: slack
|
||||
mode: READ
|
||||
EOF
|
||||
|
||||
mirage workspace create workspace.yaml --id demo
|
||||
mirage execute -w demo -c 'echo "hello mirage" > /data/hello.txt'
|
||||
mirage execute -w demo -c 'ls /s3/reports/'
|
||||
mirage execute -w demo -c 'grep -r "release" /slack/eng'
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<div className="max-w-none text-[15px] leading-7 mt-4 mb-2">
|
||||
<p className="my-0">
|
||||
Add <Icon icon="github" /> GitHub, Postgres, SSH, <Icon icon="book" /> Notion, <Icon icon="google-drive" /> Google Drive, ... and the same shell vocabulary keeps working. That's the whole pitch.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold mt-16 mb-6">What is Mirage?</h2>
|
||||
|
||||
<div className="max-w-none space-y-1 text-[15px] leading-7">
|
||||
<p className="my-0">
|
||||
<img noZoom src="/images/mirage-icon-light.svg" alt="Mirage" className="inline-block h-4 align-middle mr-1 dark:hidden" /><img noZoom src="/images/mirage-icon-dark.svg" alt="Mirage" className="hidden h-4 align-middle mr-1 dark:inline-block" /><b>Mirage</b> is a <b>Unified Virtual Filesystem</b> for AI agents. It mounts your apps, services, and systems (<Icon icon="aws" /> S3, <Icon icon="cloudflare" /> R2, <Icon icon="google" /> GCS, <Icon icon="envelope" /> Gmail, <Icon icon="google-drive" /> Google Drive, <Icon icon="github" /> GitHub, <Icon icon="linear" /> Linear, <Icon icon="book" /> Notion, <Icon icon="slack" /> Slack, <Icon icon="discord" /> Discord, <Icon icon="telegram" /> Telegram, <Icon icon="database" /> MongoDB, Redis, SSH, local disk, and more) behind one filesystem interface. Agents reach every backend with the same handful of Unix-like tools, pipelines compose across services as naturally as on a local disk, and the workspace embeds in any application, sandbox, or coding agent runtime.
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h4 className="text-lg font-bold mt-4 mb-1"><Icon icon="cubes" size={20} /> One Filesystem</h4>
|
||||
<p className="my-0">
|
||||
Every service speaks the same filesystem semantics, so agents reason about one abstraction instead of N SDKs and M MCPs. <Icon icon="aws" /> S3, <Icon icon="cloudflare" /> R2, <Icon icon="google-drive" /> Google Drive, <Icon icon="github" /> GitHub, <Icon icon="linear" /> Linear, <Icon icon="book" /> Notion, <Icon icon="slack" /> Slack, <Icon icon="discord" /> Discord, <Icon icon="database" /> MongoDB, Redis, SSH, and more mount side-by-side under a single root.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-bold mt-4 mb-1"><Icon icon="terminal" size={20} /> Familiar Bash Tools</h4>
|
||||
<p className="my-0">
|
||||
Agents reuse the same handful of Unix-like tools (<code>ls</code>, <code>find</code>, <code>grep</code>, <code>cat</code>, ...) instead of learning a new API per service. Pipelines compose across services as naturally as on a local disk, the exact corpus modern LLMs are most heavily trained on.
|
||||
</p>
|
||||
|
||||
```bash
|
||||
# Find every mention of "mirage" across three services
|
||||
grep -r "mirage" /slack /gmail /github
|
||||
```
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-bold mt-4 mb-1"><Icon icon="box-archive" size={20} /> Portable Workspaces</h4>
|
||||
<p className="my-0">
|
||||
Clone, snapshot, and version your environment. Move agent runs between machines without restarting or reconfiguring the system, and replay any past state on demand.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-bold mt-4 mb-1"><Icon icon="puzzle-piece" size={20} /> Embed in Apps and Agents</h4>
|
||||
<p className="my-0">
|
||||
Python and TypeScript SDKs give your AI agents a virtual filesystem directly inside FastAPI, Express, browser apps, or any async runtime, no separate process required. Works with the major agent frameworks (OpenAI Agents SDK, Vercel AI SDK, LangChain, Pydantic AI, CAMEL, OpenHands) and a lightweight CLI plugs into coding agents like <img noZoom src="/images/claude-logo.svg" alt="" className="inline-block h-4 align-middle mr-1" />Claude Code and <img noZoom src="/images/openai-logo.svg" alt="" className="inline-block h-4 align-middle mr-1 brightness-0 dark:invert" />Codex.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-bold mt-4 mb-1"><Icon icon="code-branch" size={20} /> Git-style Versioning</h4>
|
||||
<p className="my-0">
|
||||
Snapshot and clone your workspace the way git treats source. Fork from any past state and replay an agent run.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold mt-16 mb-6">A Real-world Example</h2>
|
||||
|
||||
<div className="max-w-none text-[15px] leading-7 mb-4">
|
||||
<p className="my-0">
|
||||
An agent watches your team's <Icon icon="slack" /> Slack <code>#incident</code> channel. A user posts a screenshot of <code>mirage --help</code> with the message <i>"the CLI design is confusing and hard to follow"</i>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-none text-[15px] leading-7 mt-6 mb-2">
|
||||
<p className="my-0">
|
||||
Built with the <a href="https://github.com/openai/openai-agents-python" target="_blank" rel="noopener noreferrer"><img noZoom src="/images/openai-logo.svg" alt="" className="inline-block h-4 align-middle mr-1 brightness-0 dark:invert" />OpenAI Agents SDK</a>, the agent walks <Icon icon="slack" /> Slack, <Icon icon="github" /> GitHub, and <Icon icon="linear" /> Linear through one bash tool.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h4 className="text-lg font-bold mt-8 mb-2">Code</h4>
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python" icon="/images/python-logo.svg">
|
||||
```python
|
||||
from agents import Runner
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import SandboxAgent, SandboxRunConfig
|
||||
|
||||
from mirage import MountMode, Workspace
|
||||
from mirage.agents.openai_agents import MirageSandboxClient
|
||||
from mirage.resource.github import GitHubResource
|
||||
from mirage.resource.linear import LinearResource
|
||||
from mirage.resource.slack import SlackResource
|
||||
|
||||
slack = SlackResource(...)
|
||||
github = GitHubResource(repo="strukto-ai/mirage")
|
||||
linear = LinearResource(...)
|
||||
|
||||
ws = Workspace({
|
||||
"/slack": (slack, MountMode.READ),
|
||||
"/github": (github, MountMode.READ),
|
||||
"/linear": (linear, MountMode.WRITE),
|
||||
})
|
||||
|
||||
agent = SandboxAgent(
|
||||
name="Design feedback triage",
|
||||
model="gpt-5.5",
|
||||
instructions=ws.file_prompt,
|
||||
)
|
||||
config = RunConfig(sandbox=SandboxRunConfig(client=MirageSandboxClient(ws)))
|
||||
|
||||
task = (
|
||||
"Scan recent messages in the Slack #incident channel. If anyone posts "
|
||||
"feedback about Mirage with a screenshot, read the image, locate the "
|
||||
"relevant CLI code in the Mirage GitHub repo, and file a design issue "
|
||||
"in Linear with the screenshot, the user's feedback, and links to the "
|
||||
"offending source files."
|
||||
)
|
||||
result = await Runner.run(agent, task, run_config=config)
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
<div className="max-w-none text-[15px] leading-7 mt-4 mb-4">
|
||||
<p className="my-0">
|
||||
Full runnable source: <a href="https://github.com/strukto-ai/mirage/blob/main/examples/python/demo/design_feedback.py" target="_blank" rel="noopener noreferrer"><code>examples/python/demo/design_feedback.py</code></a>.
|
||||
</p>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab title="TypeScript" icon="/images/typescript-logo.svg">
|
||||
```typescript
|
||||
import {
|
||||
GitHubResource,
|
||||
LinearResource,
|
||||
MountMode,
|
||||
SlackResource,
|
||||
Workspace,
|
||||
} from '@struktoai/mirage-node'
|
||||
import { Agent, run, shellTool } from '@openai/agents'
|
||||
import { MirageShell, buildSystemPrompt } from '@struktoai/mirage-agents/openai'
|
||||
|
||||
const slack = new SlackResource({ token: process.env.SLACK_BOT_TOKEN! })
|
||||
const github = new GitHubResource({
|
||||
token: process.env.GITHUB_TOKEN!,
|
||||
owner: 'strukto-ai',
|
||||
repo: 'mirage',
|
||||
})
|
||||
const linear = new LinearResource({ apiKey: process.env.LINEAR_API_KEY! })
|
||||
|
||||
const ws = new Workspace(
|
||||
{ '/slack': slack, '/github': github, '/linear': linear },
|
||||
{
|
||||
mode: MountMode.READ,
|
||||
modeOverrides: { '/linear': MountMode.WRITE },
|
||||
},
|
||||
)
|
||||
|
||||
const agent = new Agent({
|
||||
name: 'Design feedback triage',
|
||||
model: 'gpt-5.5',
|
||||
instructions: buildSystemPrompt({ workspace: ws }),
|
||||
tools: [shellTool({ shell: new MirageShell(ws) })],
|
||||
})
|
||||
|
||||
const task =
|
||||
'Scan recent messages in the Slack #incident channel. If anyone posts ' +
|
||||
'feedback about Mirage with a screenshot, read the image, locate the ' +
|
||||
'relevant CLI code in the Mirage GitHub repo, and file a design issue ' +
|
||||
'in Linear with the screenshot, the user\'s feedback, and links to the ' +
|
||||
'offending source files.'
|
||||
|
||||
const result = await run(agent, task)
|
||||
console.log(result.finalOutput)
|
||||
```
|
||||
|
||||
<div className="max-w-none text-[15px] leading-7 mt-4 mb-4">
|
||||
<p className="my-0">
|
||||
API preview. Runnable TypeScript example coming soon. See <a href="https://github.com/strukto-ai/mirage/blob/main/examples/typescript/agents/openai/multi_resource_agent.ts" target="_blank" rel="noopener noreferrer"><code>multi_resource_agent.ts</code></a> for the same shell-tool pattern over Slack + S3 today.
|
||||
</p>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<h4 className="text-lg font-bold mt-10 mb-2">Walk-through</h4>
|
||||
|
||||
<div className="max-w-none text-[15px] leading-7 mb-2">
|
||||
<p className="my-0">
|
||||
Inside the workspace shell, the agent runs three steps:
|
||||
</p>
|
||||
</div>
|
||||
|
||||
```bash
|
||||
# 1. Read the latest #incident message + list its attachments
|
||||
$ cat /slack/channels/incident__C0B0DB9K11T/2026-04-28/chat.jsonl
|
||||
$ ls /slack/channels/incident__C0B0DB9K11T/2026-04-28/files/
|
||||
# image__F0B01A3R171.png <- the agent reads this via the model's vision input
|
||||
|
||||
# 2. Find the CLI source the screenshot is complaining about
|
||||
$ rg -n "Mirage daemon CLI|workspace|session|provision" /github/typescript
|
||||
$ cat /github/typescript/packages/cli/src/main.ts
|
||||
|
||||
# 3. File a design issue in Linear with the feedback + code refs
|
||||
$ linear-issue-create --team_id <team-id> \
|
||||
--title "[Design] Rework Mirage CLI top-level command surface" \
|
||||
--description "$(cat <<'EOF'
|
||||
... feedback, screenshot summary, and links to the offending files ...
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
<div className="max-w-none text-[15px] leading-7 mt-6 mb-2">
|
||||
<p className="my-0">
|
||||
The agent files a new issue in <Icon icon="linear" /> Linear with the user's feedback and links to the relevant source files.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold mt-16 mb-6">Use Cases</h2>
|
||||
|
||||
<div className="max-w-none text-[15px] leading-7 mb-4">
|
||||
<p className="my-0">
|
||||
Mirage shows up wherever an agent needs to read, write, or stitch together data that doesn't already live on a local disk.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Cross-app triage agents" icon="bell">
|
||||
Watch Slack, search GitHub, file Linear issues. One shell, no per-service wiring.
|
||||
</Card>
|
||||
<Card title="SWE agents on remote data" icon="code">
|
||||
Point Claude Code or Codex at S3, Postgres, or SSH hosts as if they were files.
|
||||
</Card>
|
||||
<Card title="RAG with writes" icon="pen-to-square">
|
||||
READ + WRITE mounts on Notion, Drive, and Linear so the agent can edit and comment back.
|
||||
</Card>
|
||||
<Card title="Ops & observability copilots" icon="chart-line">
|
||||
<code>tail</code>, <code>grep</code>, <code>jq</code> over remote logs, metrics, and config without per-source plugins.
|
||||
</Card>
|
||||
<Card title="Sandbox-native workflows" icon="box">
|
||||
Embed inside Daytona, E2B, Modal, or Vercel sandboxes as the data plane.
|
||||
</Card>
|
||||
<Card title="Reproducible agent runs" icon="wand-magic-sparkles">
|
||||
Snapshot, restore, and clone workspaces for branchable, replayable agent runs.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<h2 className="text-2xl font-bold mt-16 mb-6">FAQ</h2>
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Do I need FUSE?">
|
||||
No. Mirage runs the workspace in-process: <code>ws.execute(...)</code> parses and dispatches commands without mounting anything on the host. FUSE is an optional surface if you also want host tools (editors, language servers, <code>rg</code>) to see the workspace. See <a href="/home/design/fuse">FUSE</a> for the optional integration.
|
||||
</Accordion>
|
||||
<Accordion title="Where does the shell run? Is it really bash?">
|
||||
The shell runs in your Mirage process. It's a tree-sitter bash parser plus a custom executor that routes commands to per-mount handlers, so there is no subshell to <code>/bin/bash</code> and no <code>os.system</code>. Most common Unix verbs work (<code>ls</code>, <code>cat</code>, <code>grep</code>, <code>find</code>, <code>head</code>, <code>wc</code>, <code>jq</code>, ...) plus pipes, redirects, globs, and <code>&&</code>/<code>||</code>. See <a href="/home/design/limitations">Shell limitations</a> for the gaps.
|
||||
</Accordion>
|
||||
<Accordion title="Is it sandboxed?">
|
||||
The shell only sees your mounted resources: no arbitrary host filesystem, no shelling out to host binaries. For untrusted code you'd still want a real sandbox (Daytona, E2B, Modal); Mirage embeds inside those rather than replacing them.
|
||||
</Accordion>
|
||||
<Accordion title="What about latency and cost?">
|
||||
Backend-bound. <code>cat /s3/...</code> is one GetObject; <code>find /postgres/...</code> is a SQL query. Reads cache per session, and <code>mirage provision</code> returns a dry-run estimate (network bytes, cache hits, projected cost) before you commit to an expensive operation.
|
||||
</Accordion>
|
||||
<Accordion title="Hosted or self-hosted?">
|
||||
Self-hosted. Mirage is a library plus a thin local daemon. The daemon lives in your process or sandbox; data only leaves your network if a mount you configured already does (e.g. an S3 read).
|
||||
</Accordion>
|
||||
<Accordion title="Which agent frameworks are supported?">
|
||||
Anything that exposes a shell tool: Claude Code, Codex / OpenAI Agents SDK, Cursor, OpenHands, Pydantic deepagents. Direct SDK integrations live under <a href="/python/agents">Python agents</a> and <a href="/typescript/agents">TypeScript agents</a>.
|
||||
</Accordion>
|
||||
<Accordion title="Python or TypeScript?">
|
||||
Both. The Python package is the reference implementation; the TypeScript SDK (<code>@struktoai/mirage-node</code>) ships the same <code>Workspace</code>/<code>execute</code> surface and most resources. Some agent integrations land on Python first.
|
||||
</Accordion>
|
||||
<Accordion title="What if a resource I want isn't supported yet?">
|
||||
Resources are pluggable. The <a href="/python/resource/new">"Add a resource"</a> guide walks through the read/write/stat surface a new backend implements. PRs welcome.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<h2 className="text-2xl font-bold mt-16 mb-6">Explore Mirage</h2>
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Python Quickstart" icon="python" href="/python/quickstart">
|
||||
Create a workspace, mount resources, and run shell commands in minutes.
|
||||
</Card>
|
||||
<Card title="TypeScript Quickstart" icon="js" href="/typescript/quickstart">
|
||||
Same Workspace API in Node, browser, and edge runtimes.
|
||||
</Card>
|
||||
<Card title="CLI" icon="terminal" href="/home/cli">
|
||||
Drive workspaces from the shell: create, execute, snapshot, and restore.
|
||||
</Card>
|
||||
<Card title="Resource Matrix" icon="grid-2" href="/home/resource-matrix">
|
||||
Compare resources by mount mode, setup path, and common use cases.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<h2 className="text-2xl font-bold mt-16 mb-6">Community & Support</h2>
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Need Help?" icon="circle-question" href="/home/troubleshooting">
|
||||
Setup failures, credential issues, and FUSE gotchas.
|
||||
</Card>
|
||||
<Card title="Book a Call" icon="calendar" href="https://cal.com/strukto/30min">
|
||||
Talk to the team directly if you are blocked.
|
||||
</Card>
|
||||
<Card title="Join our Discord" icon="discord" href="https://discord.gg/u8BPQ65KsS">
|
||||
Chat with the community and get help from the team.
|
||||
</Card>
|
||||
<Card title="GitHub Issues" icon="github" href="https://github.com/strukto-ai/mirage/issues">
|
||||
Report bugs, request features, or ask questions.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Python SDK
|
||||
url: /python/quickstart
|
||||
icon: python
|
||||
---
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
title: Resource Matrix
|
||||
description: Compare Mirage resources by mount mode and setup path, organized by category.
|
||||
icon: grid-2
|
||||
---
|
||||
|
||||
## How To Read This Matrix
|
||||
|
||||
Use this page to pick the first resource to try. If you want the fastest path, start with RAM or disk. If you need real integrations, jump from the setup guide to the resource docs.
|
||||
|
||||
The **Mount Mode** column shows which `MountMode` values the resource supports (`read`, `write`, and `exec`, the last makes a mount executable so commands can launch binaries from it; typically used with Disk or RAM). The **Docs** column shows where each resource is available, with one icon per supported runtime: <Icon icon="python" /> Python, <Icon icon="node-js" /> TypeScript (Node), and <Icon icon="globe" /> TypeScript (Browser). Click an icon to jump to that runtime's docs.
|
||||
|
||||
Sections below mirror the **Setup** sidebar so you can pick a category and follow the same path through credentials → resource docs.
|
||||
|
||||
## Infrastructure
|
||||
|
||||
No external setup, these run locally or against a connection string you already have.
|
||||
|
||||
| Resource | Mount Mode | Docs | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| RAM | read, write, exec | [<Icon icon="python" />](/python/resource/ram) [<Icon icon="node-js" />](/typescript/quickstart) [<Icon icon="globe" />](/typescript/quickstart) | Best first-run option |
|
||||
| Disk | read, write, exec | [<Icon icon="python" />](/python/resource/disk) [<Icon icon="node-js" />](/typescript/setup/disk) | Local filesystem bridge |
|
||||
| OPFS | read, write, exec | [<Icon icon="globe" />](/typescript/setup/opfs) | Browser-only persistent filesystem |
|
||||
| Redis | read, write, exec | [<Icon icon="python" />](/python/resource/redis) [<Icon icon="node-js" />](/typescript/setup/redis) | Persistent cache-backed workspace |
|
||||
| SSH | read, write | [<Icon icon="python" />](/python/resource/ssh) [<Icon icon="node-js" />](/typescript/setup/ssh) | Remote filesystem access |
|
||||
|
||||
## Object Storage
|
||||
|
||||
| Resource | Mount Mode | Setup | Docs | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| S3 | read, write | [S3](/home/setup/s3) | [<Icon icon="python" />](/python/resource/s3) [<Icon icon="node-js" />](/typescript/setup/s3) [<Icon icon="globe" />](/typescript/setup/s3) | Common cloud object store |
|
||||
| R2 | read, write | [R2](/home/setup/r2) | [<Icon icon="python" />](/python/resource/r2) [<Icon icon="node-js" />](/typescript/setup/r2) [<Icon icon="globe" />](/typescript/setup/r2) | S3-style API |
|
||||
| GCS | read, write | [GCS](/home/setup/gcs) | [<Icon icon="python" />](/python/resource/gcs) [<Icon icon="node-js" />](/typescript/setup/gcs) [<Icon icon="globe" />](/typescript/setup/gcs) | GCP object storage |
|
||||
| OCI | read, write | [OCI](/home/setup/oci) | [<Icon icon="python" />](/python/resource/oci) [<Icon icon="node-js" />](/typescript/setup/oci) [<Icon icon="globe" />](/typescript/setup/oci) | Oracle object storage |
|
||||
| Supabase | read, write | [Supabase](/home/setup/supabase) | [<Icon icon="python" />](/python/resource/supabase) [<Icon icon="node-js" />](/typescript/setup/supabase) [<Icon icon="globe" />](/typescript/setup/supabase) | Storage-oriented workflows |
|
||||
|
||||
## Google Workspace
|
||||
|
||||
| Resource | Mount Mode | Setup | Docs | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Gmail | read, partial write | [Google](/home/setup/google) | [<Icon icon="python" />](/python/resource/gmail) [<Icon icon="node-js" />](/typescript/setup/gmail) [<Icon icon="globe" />](/typescript/setup/gmail) | Mailbox access |
|
||||
| Drive | read, partial write | [Google](/home/setup/google) | [<Icon icon="python" />](/python/resource/gdrive) [<Icon icon="node-js" />](/typescript/setup/gdrive) [<Icon icon="globe" />](/typescript/setup/gdrive) | File tree and metadata |
|
||||
| Docs | read, partial write | [Google](/home/setup/google) | [<Icon icon="python" />](/python/resource/gdocs) [<Icon icon="node-js" />](/typescript/setup/gdocs) [<Icon icon="globe" />](/typescript/setup/gdocs) | Document content |
|
||||
| Sheets | read, partial write | [Google](/home/setup/google) | [<Icon icon="python" />](/python/resource/gsheets) [<Icon icon="node-js" />](/typescript/setup/gsheets) [<Icon icon="globe" />](/typescript/setup/gsheets) | Spreadsheet data |
|
||||
| Slides | read, partial write | [Google](/home/setup/google) | [<Icon icon="python" />](/python/resource/gslides) [<Icon icon="node-js" />](/typescript/setup/gslides) [<Icon icon="globe" />](/typescript/setup/gslides) | Presentation content |
|
||||
|
||||
## Cloud Files
|
||||
|
||||
| Resource | Mount Mode | Setup | Docs | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Dropbox | read | [Dropbox](/home/setup/dropbox) | [<Icon icon="node-js" />](/typescript/setup/dropbox) [<Icon icon="globe" />](/typescript/setup/dropbox) | OAuth2 + PKCE; Node and browser |
|
||||
| Box | read | [Box](/home/setup/box) | [<Icon icon="node-js" />](/typescript/setup/box) [<Icon icon="globe" />](/typescript/setup/box) | OAuth2 + PKCE or developer token; `.boxnote.json` / `.boxcanvas.json` / `.gdoc.json` decoded |
|
||||
|
||||
## Code & DevOps
|
||||
|
||||
| Resource | Mount Mode | Setup | Docs | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| GitHub | read | [GitHub](/home/setup/github) | [<Icon icon="python" />](/python/resource/github) [<Icon icon="node-js" />](/typescript/setup/github) [<Icon icon="globe" />](/typescript/setup/github) | Repository browsing |
|
||||
| GitHub CI | read | [GitHub CI](/home/setup/github_ci) | [<Icon icon="python" />](/python/resource/github_ci) [<Icon icon="node-js" />](/typescript/setup/github_ci) [<Icon icon="globe" />](/typescript/setup/github_ci) | Runs, logs, artifacts |
|
||||
| Linear | read | [Linear](/home/setup/linear) | [<Icon icon="python" />](/python/resource/linear) [<Icon icon="node-js" />](/typescript/setup/linear) [<Icon icon="globe" />](/typescript/setup/linear) | Issues and projects |
|
||||
| Langfuse | read | [Langfuse](/home/setup/langfuse) | [<Icon icon="python" />](/python/resource/langfuse) [<Icon icon="node-js" />](/typescript/setup/langfuse) [<Icon icon="globe" />](/typescript/setup/langfuse) | Trace exploration |
|
||||
|
||||
## Messaging
|
||||
|
||||
| Resource | Mount Mode | Setup | Docs | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Slack | read | [Slack](/home/setup/slack) | [<Icon icon="python" />](/python/resource/slack) [<Icon icon="node-js" />](/typescript/slack) [<Icon icon="globe" />](/typescript/slack) | Channels, messages, files |
|
||||
| Discord | read | [Discord](/home/setup/discord) | [<Icon icon="python" />](/python/resource/discord) [<Icon icon="node-js" />](/typescript/discord) [<Icon icon="globe" />](/typescript/discord) | Guild and channel history |
|
||||
| Telegram | read | [Telegram](/home/setup/telegram) | [<Icon icon="python" />](/python/resource/telegram) | Chat and file access |
|
||||
| Email | read, write | [Email](/home/setup/email) | [<Icon icon="python" />](/python/resource/email) [<Icon icon="node-js" />](/typescript/setup/email) | Mailbox workflows; browser blocked by raw TCP requirement |
|
||||
|
||||
## Database
|
||||
|
||||
| Resource | Mount Mode | Setup | Docs | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| MongoDB | read | [MongoDB](/home/setup/mongodb) | [<Icon icon="python" />](/python/resource/mongodb) [<Icon icon="node-js" />](/typescript/setup/mongodb) [<Icon icon="globe" />](/typescript/setup/mongodb) | Collection-backed views |
|
||||
| Postgres | read | [Postgres](/home/setup/postgres) | [<Icon icon="python" />](/python/resource/postgres) [<Icon icon="node-js" />](/typescript/setup/postgres) [<Icon icon="globe" />](/typescript/setup/postgres) | SQL tables exposed as paths |
|
||||
|
||||
## Notes
|
||||
|
||||
| Resource | Mount Mode | Setup | Docs | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Notion | read, write | [Notion](/home/setup/notion) | [<Icon icon="python" />](/python/resource/notion) [<Icon icon="globe" />](/typescript/setup/notion) | Pages and blocks; TS support is browser-only |
|
||||
|
||||
## Others
|
||||
|
||||
| Resource | Mount Mode | Setup | Docs | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Trello | read | [Trello](/home/setup/trello) | [<Icon icon="python" />](/python/resource/trello) [<Icon icon="node-js" />](/typescript/setup/trello) [<Icon icon="globe" />](/typescript/setup/trello) | Boards and cards |
|
||||
| Paperclip | read | [Paperclip](/home/setup/paperclip) | [<Icon icon="python" />](/python/resource/paperclip) | Research workflows |
|
||||
| Semantic Scholar | read | [Semantic Scholar](/home/setup/sscholar) | [<Icon icon="node-js" />](/typescript/setup/sscholar) [<Icon icon="globe" />](/typescript/setup/sscholar) | Paper search and metadata |
|
||||
| PostHog | read | | [<Icon icon="node-js" />](/typescript/setup/posthog) [<Icon icon="globe" />](/typescript/setup/posthog) | Projects, insights, recent events |
|
||||
| Vercel | read | | [<Icon icon="node-js" />](/typescript/setup/vercel) [<Icon icon="globe" />](/typescript/setup/vercel) | Projects, deployments, build logs |
|
||||
|
||||
## Agent Frameworks
|
||||
|
||||
Mirage drops into the major agent application frameworks. Each adapter exposes a `Workspace` as the agent's filesystem and shell. Click an icon to jump to the integration docs.
|
||||
|
||||
| Framework | Docs | Notes |
|
||||
| --- | --- | --- |
|
||||
| OpenAI Agents SDK | [<Icon icon="python" />](/python/agents/openai-agents) [<Icon icon="node-js" />](/typescript/agents/openai) [<Icon icon="globe" />](/typescript/agents/openai) | `MirageShell` and `MirageEditor` plug into `shellTool` and `applyPatchTool`. |
|
||||
| Vercel AI SDK | [<Icon icon="node-js" />](/typescript/agents/vercel) [<Icon icon="globe" />](/typescript/agents/vercel) | `mirageTools()` returns five typed tools for `generateText` / `streamText`. |
|
||||
| LangChain (deepagents) | [<Icon icon="python" />](/python/agents/langchain) [<Icon icon="node-js" />](/typescript/agents/langchain) | `LangchainWorkspace` backend for deepagents. Node only. |
|
||||
| Pi Coding Agent | [<Icon icon="node-js" />](/typescript/agents/pi) | Mirage extension for `@mariozechner/pi-coding-agent`. Node only. |
|
||||
| Mastra | [<Icon icon="node-js" />](/typescript/agents/mastra) | `mirageTools()` for Mastra `Agent` definitions. Node only. |
|
||||
| Pydantic AI | [<Icon icon="python" />](/python/agents/pydantic-ai) | For pydantic-ai and pydantic-deepagents. |
|
||||
| CAMEL-AI | [<Icon icon="python" />](/python/agents/camel) | For CAMEL `ChatAgent`. |
|
||||
| OpenHands | [<Icon icon="python" />](/python/agents/openhands) | For the OpenHands agent SDK. |
|
||||
| Claude Code (CLI) | [<Icon icon="python" />](/python/agents/claude-code) [<Icon icon="node-js" />](/typescript/agents/claude-code) | Mount via FUSE; run `claude` against the mountpoint. |
|
||||
| Codex (CLI) | [<Icon icon="python" />](/python/agents/codex) [<Icon icon="node-js" />](/typescript/agents/codex) | Mount via FUSE; run `codex` against the mountpoint. |
|
||||
|
||||
## Recommended Starting Points
|
||||
|
||||
- Use [RAM](/python/resource/ram) if you want to learn Mirage itself.
|
||||
- Use [Disk](/python/resource/disk) if you want local file interoperability.
|
||||
- Use [S3](/python/resource/s3) or [GitHub](/python/resource/github) if you want a realistic first external integration.
|
||||
@@ -0,0 +1,212 @@
|
||||
---
|
||||
title: Box
|
||||
icon: box
|
||||
description: Set up Box OAuth2 credentials and obtain a refresh token (Node and browser).
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Box resource uses OAuth2 against the [Box v2 API](https://developer.box.com/reference/) to:
|
||||
|
||||
- **`/folders/{id}/items`**, list folder items (root id is `0`)
|
||||
- **`/files/{id}/content`**, download file bytes
|
||||
- **`/files/{id}`** / **`/folders/{id}`**, fetch entry metadata
|
||||
- **`/search`**, search files by name/content
|
||||
- **`/oauth2/token`**, refresh access tokens
|
||||
|
||||
Box is similar to Dropbox in shape but uses **numeric folder/file IDs** rather than paths.
|
||||
Mirage caches the path → ID mapping in the index store, so once you `ls /box/` the IDs are
|
||||
remembered and subsequent reads don't pay the lookup cost.
|
||||
|
||||
Three credential modes are supported:
|
||||
|
||||
- **Developer token**, fastest first-run path. Click one button in the Box app console, paste
|
||||
the 32-char access token, and you're done. Lives 60 minutes; you regenerate by hand. No
|
||||
client_id / refresh_token / OAuth flow needed. Recommended for `mirage` exploration.
|
||||
- **Code flow with client secret**, for Node/server long-running use. Needs `client_id`,
|
||||
`client_secret`, `refresh_token`.
|
||||
- **PKCE flow**, for browsers. Needs only `client_id` and `refresh_token` (no secret in the bundle).
|
||||
|
||||
<Note>
|
||||
**Box rotates the refresh token on every refresh.** The token you copy out of the initial code
|
||||
exchange is only valid until the next refresh, after that, only the new rotated token works.
|
||||
The `BoxTokenManager` keeps the latest token in memory; pass `onRefreshTokenRotated` if you
|
||||
want to persist it across restarts (the browser example does this with `localStorage`).
|
||||
</Note>
|
||||
|
||||
## Quick Start: Developer Token (60 minutes, no OAuth)
|
||||
|
||||
If you just want to try the resource against your own Box account, the developer token is the
|
||||
fastest path, no redirect URI, no client secret, no curl exchange:
|
||||
|
||||
1. Go to https://app.box.com/developers/console -> **Create New App** -> **Custom App** ->
|
||||
**User Authentication (OAuth 2.0)** -> name it "Mirage" -> **Create App**
|
||||
1. On the **Configuration** tab, scroll to the **Developer Token** section near the bottom.
|
||||
1. Click **Generate Developer Token**. Copy the 32-char token.
|
||||
1. Paste into your `.env.development`:
|
||||
```bash
|
||||
BOX_DEVELOPER_TOKEN=...
|
||||
```
|
||||
1. Run any of the examples in [`examples/typescript/box/`](https://github.com/strukto-ai/mirage/tree/main/examples/typescript/box).
|
||||
|
||||
The token expires in **60 minutes** and there is no programmatic way to renew it; you regenerate
|
||||
by hand. For long-running processes, use the OAuth flow below instead.
|
||||
|
||||
When `BOX_DEVELOPER_TOKEN` is set, the `BoxResource` skips the OAuth refresh path entirely and
|
||||
calls Box directly with the token in the `Authorization: Bearer` header.
|
||||
|
||||
## Setup (long-running OAuth flow)
|
||||
|
||||
### 1. Create a Box App
|
||||
|
||||
1. Go to https://app.box.com/developers/console
|
||||
1. Click **Create New App**
|
||||
1. Choose **Custom App**
|
||||
1. **Authentication Method**: **User Authentication (OAuth 2.0)**
|
||||
1. Name it (e.g. "Mirage") -> **Create App**
|
||||
|
||||
### 2. Configure the App
|
||||
|
||||
Open the app's **Configuration** tab:
|
||||
|
||||
- **OAuth 2.0 Redirect URIs**: add every destination you'll redirect back to. Box accepts
|
||||
multiple values, register them all up front so you don't bounce between dev and prod:
|
||||
- For the CLI flow below: `http://localhost:1`
|
||||
- For the browser PKCE example: `http://localhost:5173/box_pkce.html`
|
||||
- For production: e.g. `https://yourapp.com/box/callback`
|
||||
- **Allowed Origins** (only needed if you'll call from a browser; older Box docs call this
|
||||
"CORS Domains"):
|
||||
- `http://localhost:5173`
|
||||
- Any production origins (e.g. `https://yourapp.com`)
|
||||
- **Application Scopes**:
|
||||
- **Read all files and folders stored in Box** (required)
|
||||
- **Write all files and folders stored in Box** (only if you'll add write commands later)
|
||||
- Click **Save Changes**
|
||||
|
||||
### 3. Submit for Authorization (enterprise apps only)
|
||||
|
||||
If your Box account is part of an enterprise, the **Authorization** tab requires an admin to
|
||||
approve the app before it can issue tokens. Personal/free Box accounts skip this step.
|
||||
|
||||
### 4. Copy the Client Credentials
|
||||
|
||||
Still on **Configuration**, copy:
|
||||
|
||||
- **Client ID** -> `BOX_CLIENT_ID`
|
||||
- **Client Secret** -> `BOX_CLIENT_SECRET` (skip if you only need PKCE)
|
||||
|
||||
### 5. Get the Refresh Token (CLI flow with client secret)
|
||||
|
||||
**A) Open this URL in a browser** (replace `YOUR_CLIENT_ID`):
|
||||
|
||||
```
|
||||
https://account.box.com/api/oauth2/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:1
|
||||
```
|
||||
|
||||
**B) Authorize:** sign in -> click **Grant access**.
|
||||
|
||||
**C) Copy the code:** browser redirects to `http://localhost:1?code=...` (page won't load,
|
||||
expected). Copy the `code` from the URL bar.
|
||||
|
||||
**D) Exchange the code:**
|
||||
|
||||
```bash
|
||||
curl https://api.box.com/oauth2/token \
|
||||
-d "code=THE_CODE" \
|
||||
-d "grant_type=authorization_code" \
|
||||
-d "client_id=YOUR_CLIENT_ID" \
|
||||
-d "client_secret=YOUR_CLIENT_SECRET" \
|
||||
-d "redirect_uri=http://localhost:1"
|
||||
```
|
||||
|
||||
Response shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "T...",
|
||||
"expires_in": 3884,
|
||||
"refresh_token": "R...",
|
||||
"restricted_to": [],
|
||||
"token_type": "bearer"
|
||||
}
|
||||
```
|
||||
|
||||
Save the `refresh_token`.
|
||||
|
||||
### 6. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
BOX_CLIENT_ID=...
|
||||
BOX_CLIENT_SECRET=...
|
||||
BOX_REFRESH_TOKEN=...
|
||||
```
|
||||
|
||||
## Alternative: PKCE Flow (browser, no client secret)
|
||||
|
||||
1. Add `http://localhost:5173/box_pkce.html` to **OAuth 2.0 Redirect URIs**
|
||||
1. Add `http://localhost:5173` to **Allowed Origins**
|
||||
1. Set only `BOX_CLIENT_ID` in `.env.development`
|
||||
1. From `examples/typescript/browser/`, run `pnpm dev`
|
||||
1. Open `http://localhost:5173/box_pkce.html` and click **Connect Box**
|
||||
|
||||
The example persists the rotated refresh token to `localStorage` via the
|
||||
`onRefreshTokenRotated` callback so subsequent page loads work without re-auth.
|
||||
|
||||
## Token Lifetime
|
||||
|
||||
| Token | Lifetime |
|
||||
| ------------- | -------------------------------------------------------- |
|
||||
| Access token | ~1 hour (`expires_in: ~3600`) |
|
||||
| Refresh token | 60 days from issue, OR 60 days from last use (whichever) |
|
||||
|
||||
A refresh token gets revoked when:
|
||||
|
||||
- 60 days pass without use
|
||||
- The user revokes the app at https://app.box.com/account/security
|
||||
- The Box account password changes
|
||||
- A new refresh token is issued (the old one expires after a short grace period)
|
||||
|
||||
The `BoxTokenManager` always uses the latest token. For long-running processes restart-safe,
|
||||
provide `onRefreshTokenRotated` to persist the new token to disk / a vault.
|
||||
|
||||
## CORS Notes
|
||||
|
||||
Unlike Dropbox, Box has first-class CORS but **only for origins you've explicitly allowlisted**
|
||||
in the **Allowed Origins** section of the app configuration. If you see browser-side
|
||||
`Access-Control-Allow-Origin` errors, double-check the origin matches exactly (no trailing
|
||||
slash; protocol matters).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
||||
| `400 invalid_grant` on token exchange | The `code` is single-use and short-lived, redo Step 5A with a fresh URL |
|
||||
| `403 access_denied` | Enterprise admin hasn't approved the app on the **Authorization** tab |
|
||||
| `401 unauthorized` after a few hours | Refresh token rotation, the original token is invalid; persist via `onRefreshTokenRotated` |
|
||||
| Browser: `CORS error` even though origin set | Check that the origin in **Allowed Origins** is exact (no trailing slash, http vs https) |
|
||||
| `path/not_found` on `ls /box/<folder>/` | The folder name was constructed wrong, use `ls /box/` first, then drill into a name from the list |
|
||||
|
||||
For TypeScript usage:
|
||||
|
||||
```ts
|
||||
import { BoxResource } from '@struktoai/mirage-node'
|
||||
|
||||
const box = new BoxResource({
|
||||
clientId: process.env.BOX_CLIENT_ID!,
|
||||
clientSecret: process.env.BOX_CLIENT_SECRET!,
|
||||
refreshToken: process.env.BOX_REFRESH_TOKEN!,
|
||||
})
|
||||
```
|
||||
|
||||
Or browser (PKCE):
|
||||
|
||||
```ts
|
||||
import { BoxResource } from '@struktoai/mirage-browser'
|
||||
|
||||
const box = new BoxResource({
|
||||
clientId: 'YOUR_CLIENT_ID',
|
||||
refreshToken: refreshTokenFromLocalStorage,
|
||||
onRefreshTokenRotated: (next) => localStorage.setItem('box-refresh', next),
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Discord
|
||||
icon: discord
|
||||
description: Set up a Discord Bot Token for the Discord resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
### 1. Create a Discord Application
|
||||
|
||||
1. Go to https://discord.com/developers/applications
|
||||
1. **New Application** -> name it (e.g., "Mirage")
|
||||
|
||||
### 2. Create a Bot
|
||||
|
||||
1. **Bot** in the left sidebar
|
||||
1. **Reset Token** -> copy the **Bot Token**
|
||||
1. Under **Privileged Gateway Intents**, enable:
|
||||
- **Message Content Intent** (required to read message content)
|
||||
|
||||
### 3. Invite the Bot to Your Server
|
||||
|
||||
1. **OAuth2** -> **URL Generator** in the left sidebar
|
||||
1. Select scopes: `bot`
|
||||
1. Select bot permissions: `Read Messages/View Channels`, `Read Message History`
|
||||
1. Copy the generated URL and open it in a browser to invite
|
||||
|
||||
### 4. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
DISCORD_BOT_TOKEN=MTIx...
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python Discord Setup](/python/setup/discord) guide.
|
||||
@@ -0,0 +1,199 @@
|
||||
---
|
||||
title: Dropbox
|
||||
icon: dropbox
|
||||
description: Set up Dropbox OAuth2 credentials and obtain a long-lived refresh token.
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Dropbox resource uses OAuth2 with a long-lived refresh token to authenticate against the
|
||||
[Dropbox v2 HTTP API](https://www.dropbox.com/developers/documentation/http/documentation):
|
||||
|
||||
- **`/2/files/list_folder`**, list folder entries
|
||||
- **`/2/files/download`**, download file bytes
|
||||
- **`/2/files/search_v2`**, search files by name/content
|
||||
- **`/2/files/get_metadata`**, fetch entry metadata
|
||||
|
||||
Two flows are supported:
|
||||
|
||||
- **Code flow with client secret**, for Node/server. Needs `client_id`, `client_secret`, `refresh_token`.
|
||||
- **PKCE flow**, for browsers. Needs only `client_id` and `refresh_token` (no secret in the bundle).
|
||||
|
||||
Both produce the same long-lived refresh token; the resource auto-refreshes short-lived access
|
||||
tokens (≈4h) behind the scenes.
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Create a Dropbox App
|
||||
|
||||
1. Go to https://www.dropbox.com/developers/apps
|
||||
1. Click **Create app**
|
||||
1. Choose:
|
||||
- API: **Scoped access**
|
||||
- Access type: **App folder** (sandboxed to one folder Dropbox creates for you) or
|
||||
**Full Dropbox** (your entire account). Pick App folder for least privilege.
|
||||
- Name it (e.g., "Mirage") -> **Create app**
|
||||
|
||||
### 2. Configure Permissions
|
||||
|
||||
On the app's settings page, click the **Permissions** tab and check:
|
||||
|
||||
- `files.metadata.read`, list folders, read file metadata
|
||||
- `files.content.read`, download file bytes
|
||||
|
||||
If you also want write/delete (not used by the read-only resource yet), check
|
||||
`files.content.write` and `files.metadata.write`.
|
||||
|
||||
Click **Submit** at the bottom of the Permissions tab. **Required**: Dropbox does not include
|
||||
unchecked scopes in tokens, even if the auth URL requests them.
|
||||
|
||||
### 3. Configure OAuth 2 Redirect URI
|
||||
|
||||
On the **Settings** tab, scroll to **OAuth 2 -> Redirect URIs** and add:
|
||||
|
||||
- For the CLI flow below: `http://localhost:1`
|
||||
- For the browser PKCE example: `http://localhost:5173/dropbox_pkce.html`
|
||||
|
||||
Click **Add** after each.
|
||||
|
||||
### 4. Copy the App Key (and Secret)
|
||||
|
||||
Still on **Settings**, copy:
|
||||
|
||||
- **App key**, this is your `DROPBOX_APP_KEY`
|
||||
- **App secret**, click **Show** -> copy. This is your `DROPBOX_APP_SECRET`. **Skip if you
|
||||
only need the PKCE flow.**
|
||||
|
||||
### 5. Get the Refresh Token (CLI flow with client secret)
|
||||
|
||||
This mirrors the Google flow, easiest path for Node/server use.
|
||||
|
||||
**A) Open this URL in a browser** (replace `YOUR_APP_KEY`):
|
||||
|
||||
```
|
||||
https://www.dropbox.com/oauth2/authorize?client_id=YOUR_APP_KEY&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1
|
||||
```
|
||||
|
||||
- `token_access_type=offline`, **required** to receive a refresh token. Without this you only
|
||||
get a short-lived access token.
|
||||
|
||||
**B) Authorize:** sign in -> click **Allow**.
|
||||
|
||||
**C) Copy the code:** the browser redirects to `http://localhost:1?code=ABC...` (the page won't
|
||||
load, expected). Copy the `code` value from the URL bar.
|
||||
|
||||
**D) Exchange the code for a refresh token:**
|
||||
|
||||
```bash
|
||||
curl https://api.dropboxapi.com/oauth2/token \
|
||||
-d "code=THE_CODE_FROM_STEP_C" \
|
||||
-d "grant_type=authorization_code" \
|
||||
-d "client_id=YOUR_APP_KEY" \
|
||||
-d "client_secret=YOUR_APP_SECRET" \
|
||||
-d "redirect_uri=http://localhost:1"
|
||||
```
|
||||
|
||||
The response contains `refresh_token`, save it. Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "sl.B...",
|
||||
"expires_in": 14400,
|
||||
"refresh_token": "abc123...",
|
||||
"scope": "files.metadata.read files.content.read",
|
||||
"token_type": "bearer",
|
||||
"uid": "12345",
|
||||
"account_id": "dbid:..."
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
DROPBOX_APP_KEY=abc123app4key
|
||||
DROPBOX_APP_SECRET=def456app4secret
|
||||
DROPBOX_REFRESH_TOKEN=ghi789refresh4token
|
||||
```
|
||||
|
||||
## Alternative: PKCE Flow (browser, no client secret)
|
||||
|
||||
If you're mounting Dropbox from a browser SPA and don't want to ship a client secret, use the
|
||||
PKCE flow. The bundled example does the dance end-to-end.
|
||||
|
||||
1. In the Dropbox app **Settings** tab, ensure the redirect URI
|
||||
`http://localhost:5173/dropbox_pkce.html` is registered (Step 3).
|
||||
1. Set only `DROPBOX_APP_KEY` in `.env.development` at the repo root (no secret needed).
|
||||
1. From `examples/typescript/browser/`, run `pnpm dev`.
|
||||
1. Open `http://localhost:5173/dropbox_pkce.html` and click **Connect Dropbox**.
|
||||
|
||||
The example persists the refresh token to `localStorage`, then mounts a `DropboxResource` with
|
||||
just `{ clientId, refreshToken }` and runs `ls /dropbox/`. Inspect DevTools -> Network -> filter
|
||||
`token` to confirm refresh calls don't include `client_secret`.
|
||||
|
||||
The same `refresh_token` can be reused for headless setups, copy it out of `localStorage` and
|
||||
set it as `DROPBOX_REFRESH_TOKEN` in your env.
|
||||
|
||||
## Token Lifetime
|
||||
|
||||
| Token | Lifetime |
|
||||
| ------------- | ------------------------------------------ |
|
||||
| Access token | ~4 hours (`expires_in: 14400`) |
|
||||
| Refresh token | Long-lived; survives until manually revoked |
|
||||
|
||||
Refresh tokens get revoked if:
|
||||
|
||||
- The user revokes the app at https://www.dropbox.com/account/connected_apps
|
||||
- The Dropbox account password is changed
|
||||
- The app is deleted in the developer console
|
||||
|
||||
The `DropboxTokenManager` in the resource transparently exchanges the refresh token for a fresh
|
||||
access token whenever the cached one is within 5 minutes of expiry.
|
||||
|
||||
## Scopes Reference
|
||||
|
||||
| Scope | Purpose |
|
||||
| ------------------------ | ------------------------------------ |
|
||||
| `files.metadata.read` | List folders, read file metadata |
|
||||
| `files.metadata.write` | Move, rename, create folders |
|
||||
| `files.content.read` | Download file bytes |
|
||||
| `files.content.write` | Upload, overwrite, delete files |
|
||||
| `sharing.read` | List shared links and shared folders |
|
||||
| `account_info.read` | Account email, country, user info |
|
||||
|
||||
The Mirage resource only needs `files.metadata.read` + `files.content.read` for the read-only
|
||||
mount. Add the `*.write` scopes if/when write commands land.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| `400 invalid_grant` on token exchange | The `code` is single-use and expires fast, re-do Step 5A with a fresh URL |
|
||||
| No `refresh_token` in token response | Add `token_access_type=offline` to the auth URL |
|
||||
| `path/not_found/.` on `ls /dropbox/` | Account is empty, or app is **App folder** scoped and the sandbox folder hasn't been created |
|
||||
| `missing_scope` on download | Check `files.content.read` is enabled in the **Permissions** tab and **submitted** |
|
||||
| Browser PKCE: redirect URI mismatch | Dropbox is strict, `http://localhost:5173/dropbox_pkce.html` must match exactly (no trailing slash) |
|
||||
| Token works locally but fails after a while | Access token expired, the `TokenManager` should auto-refresh; if not, refresh token may be revoked |
|
||||
|
||||
For TypeScript usage, mount with:
|
||||
|
||||
```ts
|
||||
import { DropboxResource } from '@struktoai/mirage-node'
|
||||
|
||||
const dropbox = new DropboxResource({
|
||||
clientId: process.env.DROPBOX_APP_KEY!,
|
||||
clientSecret: process.env.DROPBOX_APP_SECRET!,
|
||||
refreshToken: process.env.DROPBOX_REFRESH_TOKEN!,
|
||||
})
|
||||
```
|
||||
|
||||
Or browser (PKCE):
|
||||
|
||||
```ts
|
||||
import { DropboxResource } from '@struktoai/mirage-browser'
|
||||
|
||||
const dropbox = new DropboxResource({
|
||||
clientId: 'YOUR_APP_KEY',
|
||||
refreshToken: refreshTokenFromLocalStorage,
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Email
|
||||
icon: envelope
|
||||
description: Set up IMAP/SMTP credentials for the Email resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
The Email resource connects to any email account via IMAP (reading)
|
||||
and SMTP (sending). You need the server hostnames and a password or
|
||||
app password.
|
||||
|
||||
### 1. Find Your IMAP/SMTP Settings
|
||||
|
||||
| Resource | IMAP Host | IMAP Port | SMTP Host | SMTP Port |
|
||||
| ----------- | ----------------------- | --------- | --------------------- | --------- |
|
||||
| Outlook/365 | `outlook.office365.com` | 993 | `smtp.office365.com` | 587 |
|
||||
| Yahoo | `imap.mail.yahoo.com` | 993 | `smtp.mail.yahoo.com` | 587 |
|
||||
| Fastmail | `imap.fastmail.com` | 993 | `smtp.fastmail.com` | 587 |
|
||||
| iCloud | `imap.mail.me.com` | 993 | `smtp.mail.me.com` | 587 |
|
||||
| ProtonMail | `127.0.0.1` | 1143 | `127.0.0.1` | 1025 |
|
||||
| Self-hosted | Your server hostname | 993 | Your server hostname | 587 |
|
||||
|
||||
ProtonMail requires [ProtonMail Bridge](https://proton.me/mail/bridge)
|
||||
running locally.
|
||||
|
||||
### 2. Create an App Password
|
||||
|
||||
Most resources require an **app password** instead of your regular
|
||||
account password. This is a one-time password specifically for
|
||||
third-party apps.
|
||||
|
||||
**Outlook / Microsoft 365:**
|
||||
|
||||
1. Go to https://account.microsoft.com/security
|
||||
1. **Security** -> **Advanced security options**
|
||||
1. **App passwords** -> **Create a new app password**
|
||||
1. Copy the generated password
|
||||
|
||||
**Yahoo:**
|
||||
|
||||
1. Go to https://login.yahoo.com/account/security
|
||||
1. **Generate app password**
|
||||
1. Select **Other App**, name it (e.g., "Mirage")
|
||||
1. Copy the generated password
|
||||
|
||||
**Fastmail:**
|
||||
|
||||
1. Go to https://www.fastmail.com/settings/security/tokens
|
||||
1. **New App Password**
|
||||
1. Select **IMAP/SMTP** access
|
||||
1. Copy the generated password
|
||||
|
||||
**iCloud:**
|
||||
|
||||
1. Go to https://appleid.apple.com/account/manage
|
||||
1. **Sign-In and Security** -> **App-Specific Passwords**
|
||||
1. **Generate an app-specific password**
|
||||
1. Copy the generated password
|
||||
|
||||
**Self-hosted / Other:**
|
||||
|
||||
Use your regular email password, or whatever your mail server
|
||||
requires.
|
||||
|
||||
### 3. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
IMAP_HOST=imap.fastmail.com
|
||||
SMTP_HOST=smtp.fastmail.com
|
||||
EMAIL_USERNAME=you@fastmail.com
|
||||
EMAIL_PASSWORD=your-app-password
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"Authentication failed"** - Make sure you're using an app password,
|
||||
not your regular account password. Most resources block regular
|
||||
passwords for IMAP/SMTP access.
|
||||
|
||||
**"Connection refused"** - Check the hostname and port. Some networks
|
||||
block port 993/587. For ProtonMail, make sure Bridge is running.
|
||||
|
||||
**"SSL handshake failed"** - If your server uses STARTTLS on port 143
|
||||
instead of SSL on port 993, set `use_ssl=False` and `imap_port=143`.
|
||||
|
||||
For Python configuration, see the [Python Email Setup](/python/setup/email) guide.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Google Cloud Storage
|
||||
icon: google
|
||||
description: Set up Google Cloud Storage credentials for the GCS resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
### 1. Create HMAC Keys
|
||||
|
||||
1. Go to https://console.cloud.google.com/storage/settings -> **Interoperability**
|
||||
1. Under **Service account HMAC**, click **Create a key for a service account**
|
||||
1. Select a service account with Storage Object Viewer (read) or
|
||||
Storage Object Admin (read/write)
|
||||
1. Copy the **Access key** and **Secret**
|
||||
|
||||
### 2. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
GCS_BUCKET=mirage-ai
|
||||
GCS_ACCESS_KEY_ID=GOOG...
|
||||
GCS_SECRET_ACCESS_KEY=...
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python GCS Setup](/python/setup/gcs) guide.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: GitHub
|
||||
icon: github
|
||||
description: Set up a GitHub Personal Access Token for the GitHub resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
### 1. Create a Personal Access Token
|
||||
|
||||
1. Go to https://github.com/settings/tokens
|
||||
1. **Generate new token (classic)** or **Fine-grained token**
|
||||
1. For classic tokens, select scopes:
|
||||
- `repo` (read access to repositories)
|
||||
1. For fine-grained tokens:
|
||||
- **Repository access**: select the repos you need
|
||||
- **Permissions**: Contents -> Read-only
|
||||
1. Copy the token
|
||||
|
||||
### 2. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
GITHUB_TOKEN=ghp_xxxx...
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python GitHub Setup](/python/setup/github) guide.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: GitHub CI
|
||||
icon: circle-play
|
||||
description: Set up a GitHub Personal Access Token for the GitHub CI resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
### 1. Create a Personal Access Token
|
||||
|
||||
1. Go to https://github.com/settings/tokens
|
||||
1. **Generate new token (classic)** or **Fine-grained token**
|
||||
1. For classic tokens, select scopes:
|
||||
- `repo` (includes `actions:read`)
|
||||
1. For fine-grained tokens:
|
||||
- **Repository access**: select the repos you need
|
||||
- **Permissions**: Actions -> Read-only
|
||||
1. Copy the token
|
||||
|
||||
### 2. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
GITHUB_TOKEN=ghp_xxxx...
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python GitHub CI Setup](/python/setup/github_ci) guide.
|
||||
@@ -0,0 +1,142 @@
|
||||
---
|
||||
title: Google Workspace
|
||||
icon: google
|
||||
description: Set up Google OAuth2 credentials for Docs, Sheets, Slides, Drive, and Gmail resources.
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Google resources use OAuth2 with a refresh token to authenticate against:
|
||||
|
||||
- **Google Drive API v3** - lists documents, reads metadata
|
||||
- **Google Docs API v1** - reads/writes document content
|
||||
|
||||
Authentication requires three values: `client_id`, `client_secret`, and `refresh_token`.
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Create a Google Cloud Project
|
||||
|
||||
1. Go to https://console.cloud.google.com/
|
||||
1. Click the project selector dropdown -> **New Project**
|
||||
1. Name it (e.g., "Mirage") -> **Create**
|
||||
1. Select it from the dropdown
|
||||
|
||||
### 2. Enable APIs
|
||||
|
||||
1. Go to https://console.cloud.google.com/apis/library
|
||||
1. Search **Google Drive API** -> click -> **Enable**
|
||||
1. Search **Google Docs API** -> click -> **Enable**
|
||||
|
||||
### 3. Configure Google Auth Platform
|
||||
|
||||
The OAuth settings are under **Google Auth Platform** in the Cloud Console left sidebar (or go to https://console.cloud.google.com/auth/overview).
|
||||
|
||||
**A) Branding** (left sidebar):
|
||||
|
||||
1. Fill in: App name (e.g., "Mirage"), support email, developer contact email
|
||||
1. Save
|
||||
|
||||
**B) Audience** (left sidebar):
|
||||
|
||||
1. Set user type to **External**
|
||||
1. Add your own Google email as a **test user**
|
||||
1. Save
|
||||
|
||||
**C) Data Access** (left sidebar) - this is where scopes are configured:
|
||||
|
||||
1. Click **Add or Remove Scopes**
|
||||
1. Search for or paste these scopes:
|
||||
- `https://www.googleapis.com/auth/drive` (full Drive access)
|
||||
- `https://www.googleapis.com/auth/documents` (Google Docs)
|
||||
- `https://www.googleapis.com/auth/presentations` (Google Slides)
|
||||
1. Select them -> Save
|
||||
|
||||
**D) Publish** (to avoid 7-day token expiry):
|
||||
|
||||
1. Go to **Audience** -> click **Publish App**
|
||||
1. See [Token Lifetime](#token-lifetime) for details
|
||||
|
||||
### 4. Create OAuth2 Client
|
||||
|
||||
1. Go to **Clients** in the left sidebar (or click **Create OAuth client** on the Overview page)
|
||||
1. Application type: **Desktop app**
|
||||
1. Name it -> **Create**
|
||||
1. Copy the **Client ID** and **Client Secret**
|
||||
|
||||
### 5. Get the Refresh Token
|
||||
|
||||
**A) Open this URL in a browser** (replace `YOUR_CLIENT_ID`):
|
||||
|
||||
```
|
||||
https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:1&response_type=code&scope=https://www.googleapis.com/auth/drive%20https://www.googleapis.com/auth/documents%20https://www.googleapis.com/auth/presentations&access_type=offline&prompt=consent
|
||||
```
|
||||
|
||||
- `access_type=offline` - required to receive a refresh token
|
||||
- `prompt=consent` - forces refresh token issuance on re-auth
|
||||
|
||||
**B) Authorize:** Sign in -> click through "unverified app" warning -> grant permissions.
|
||||
|
||||
**C) Copy the code:** The browser redirects to `http://localhost:1?code=4/0AXXXX...` (page won't load - expected). Copy the `code` value from the URL bar.
|
||||
|
||||
**D) Exchange for tokens:**
|
||||
|
||||
```bash
|
||||
curl -X POST https://oauth2.googleapis.com/token \
|
||||
-d "client_id=YOUR_CLIENT_ID" \
|
||||
-d "client_secret=YOUR_CLIENT_SECRET" \
|
||||
-d "code=THE_CODE_FROM_STEP_C" \
|
||||
-d "grant_type=authorization_code" \
|
||||
-d "redirect_uri=http://localhost:1"
|
||||
```
|
||||
|
||||
The response JSON contains `refresh_token` - save it.
|
||||
|
||||
### 6. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
GOOGLE_CLIENT_ID=123456789-abc.apps.googleusercontent.com
|
||||
GOOGLE_CLIENT_SECRET=GOCSPx-xxx
|
||||
GOOGLE_REFRESH_TOKEN=1//0abc...
|
||||
```
|
||||
|
||||
## Token Lifetime
|
||||
|
||||
| App Publishing Status | Refresh Token Lifetime |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| Testing (default) | 7 days - must re-authorize |
|
||||
| In Production | Never expires (with continued use) |
|
||||
|
||||
To publish: go to the OAuth consent screen page and click **Publish App**. Users will see an "unverified app" warning during auth, which is fine for personal use.
|
||||
|
||||
A published refresh token only gets revoked if:
|
||||
|
||||
- Manually revoked at https://myaccount.google.com/permissions
|
||||
- Google account password is changed
|
||||
- Token unused for 6 months
|
||||
- 100+ outstanding refresh tokens per account per client (oldest revoked)
|
||||
|
||||
For personal use with a published app, you do the auth flow **once** and it works indefinitely. The `TokenManager` in the resource automatically uses the refresh token to obtain fresh access tokens (which expire hourly) behind the scenes.
|
||||
|
||||
## Scopes Reference
|
||||
|
||||
| Scope | Purpose |
|
||||
| ---------------------------------------------------- | ---------------------------------------------- |
|
||||
| `https://www.googleapis.com/auth/drive` | Full Drive access (list, create, delete files) |
|
||||
| `https://www.googleapis.com/auth/drive.readonly` | List files, read metadata only (alternative) |
|
||||
| `https://www.googleapis.com/auth/documents` | Read and write Google Docs |
|
||||
| `https://www.googleapis.com/auth/documents.readonly` | Read-only Docs access (alternative) |
|
||||
| `https://www.googleapis.com/auth/presentations` | Read and write Google Slides |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
| ---------------------------------- | ----------------------------------------------------------------- |
|
||||
| 403 during OAuth flow | Add yourself as a test user in Step 3.7 |
|
||||
| No `refresh_token` in response | Ensure `access_type=offline` and `prompt=consent` in the auth URL |
|
||||
| Token exchange fails | `redirect_uri` must exactly match between auth URL and curl |
|
||||
| Refresh token expires after 7 days | Publish the app (Step 3.9) |
|
||||
| Auth code doesn't work twice | Codes are single-use - re-authorize if exchange fails |
|
||||
|
||||
For Python configuration, see the [Python Google Workspace Setup](/python/setup/google) guide.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Langfuse
|
||||
icon: chart-line
|
||||
description: Set up Langfuse API keys for the Langfuse resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
### 1. Get API Keys
|
||||
|
||||
#### Langfuse Cloud
|
||||
|
||||
1. Go to https://cloud.langfuse.com
|
||||
1. Select your project -> **Settings** -> **API Keys**
|
||||
1. Copy the **Public Key** and **Secret Key**
|
||||
|
||||
#### Self-hosted
|
||||
|
||||
1. Go to your Langfuse instance (e.g., `https://langfuse.yourcompany.com`)
|
||||
1. Select your project -> **Settings** -> **API Keys**
|
||||
1. Copy the **Public Key** and **Secret Key**
|
||||
|
||||
### 2. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
LANGFUSE_PUBLIC_KEY=pk-lf-...
|
||||
LANGFUSE_SECRET_KEY=sk-lf-...
|
||||
LANGFUSE_HOST=https://cloud.langfuse.com
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python Langfuse Setup](/python/setup/langfuse) guide.
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Linear
|
||||
icon: chart-gantt
|
||||
description: Set up a Linear personal API key for the Linear resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
### 1. Create a Personal API Key
|
||||
|
||||
1. Go to Settings -> Security & Access
|
||||
1. Create a new personal API key
|
||||
1. Copy the key
|
||||
|
||||
### 2. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
LINEAR_API_KEY=lin_api_xxxx...
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python Linear Setup](/python/setup/linear) guide.
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Linux
|
||||
icon: linux
|
||||
description: Set up FUSE on Linux for MIRAGE.
|
||||
---
|
||||
|
||||
## FUSE Setup
|
||||
|
||||
FUSE mode mounts virtual filesystems as real directories,
|
||||
allowing any tool (not just MIRAGE commands) to access mounted data.
|
||||
|
||||
### Install FUSE
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Ubuntu / Debian">
|
||||
```bash
|
||||
sudo apt-get install fuse3 libfuse3-dev
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Fedora / RHEL">
|
||||
```bash
|
||||
sudo dnf install fuse3 fuse3-devel
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Arch Linux">
|
||||
```bash
|
||||
sudo pacman -S fuse3
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Allow User Mounts
|
||||
|
||||
Edit `/etc/fuse.conf` and uncomment `user_allow_other`:
|
||||
|
||||
```bash
|
||||
sudo sed -i 's/#user_allow_other/user_allow_other/' /etc/fuse.conf
|
||||
```
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
fusermount3 --version
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
title: macOS
|
||||
icon: apple
|
||||
description: Set up FUSE on macOS for MIRAGE.
|
||||
---
|
||||
|
||||
## Install macFUSE
|
||||
|
||||
```bash
|
||||
brew install --cask macfuse
|
||||
```
|
||||
|
||||
## Enable Kernel Extension
|
||||
|
||||
<Steps>
|
||||
<Step title="Allow the Extension">
|
||||
Open **System Settings - Privacy & Security**. Scroll to the bottom, you will see:
|
||||
|
||||
> "System software from developer 'Benjamin Fleischer' was blocked from loading."
|
||||
|
||||
Click **Allow**.
|
||||
</Step>
|
||||
<Step title="Restart">
|
||||
Restart your Mac. macOS requires a reboot to load the kernel extension.
|
||||
</Step>
|
||||
<Step title="Verify">
|
||||
```bash
|
||||
ls /Library/Filesystems/macfuse.fs
|
||||
```
|
||||
If this directory exists, macFUSE is installed.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Apple Silicon (M1/M2/M3/M4)
|
||||
|
||||
If you don't see the **Allow** button:
|
||||
|
||||
<Steps>
|
||||
<Step title="Shut Down">
|
||||
Shut down your Mac completely.
|
||||
</Step>
|
||||
<Step title="Enter Recovery Mode">
|
||||
Hold the power button until "Loading startup options" appears. Click **Options - Continue**.
|
||||
</Step>
|
||||
<Step title="Enable Kernel Extensions">
|
||||
In Recovery: **Utilities - Startup Security Utility**. Set to **Reduced Security** and check **"Allow user management of kernel extensions"**.
|
||||
</Step>
|
||||
<Step title="Restart">
|
||||
Restart, then go back to **System Settings - Privacy & Security** to allow the extension.
|
||||
</Step>
|
||||
</Steps>
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: MongoDB
|
||||
icon: database
|
||||
description: Set up a MongoDB connection for the MongoDB resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
### 1. Get Your Connection URI
|
||||
|
||||
#### Local MongoDB
|
||||
|
||||
```bash
|
||||
# Default local instance
|
||||
MONGODB_URI=mongodb://localhost:27017
|
||||
```
|
||||
|
||||
#### MongoDB Atlas (Cloud)
|
||||
|
||||
1. Go to https://cloud.mongodb.com
|
||||
1. Select your cluster -> **Connect** -> **Drivers**
|
||||
1. Copy the connection string:
|
||||
```
|
||||
mongodb+srv://<username>:<password>@cluster0.xxxxx.mongodb.net/
|
||||
```
|
||||
|
||||
#### Self-hosted with Authentication
|
||||
|
||||
```bash
|
||||
MONGODB_URI=mongodb://username:password@host:27017/?authSource=admin
|
||||
```
|
||||
|
||||
### 2. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
MONGODB_URI=mongodb+srv://user:pass@cluster0.xxxxx.mongodb.net/
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python MongoDB Setup](/python/setup/mongodb) guide.
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: Notion
|
||||
icon: book
|
||||
description: Set up a Notion internal integration for the Notion resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
### 1. Create an Internal Integration
|
||||
|
||||
1. Go to [My Integrations](https://www.notion.com/my-integrations)
|
||||
1. Click **+ New integration**
|
||||
1. Name it (e.g. "mirage"), select your workspace
|
||||
1. Under **Content Capabilities**, enable:
|
||||
- **Read content** (required)
|
||||
- **Update content** (for write commands)
|
||||
- **Insert content** (for `notion-comment-add`)
|
||||
1. Click **Save changes**
|
||||
1. Copy the **Internal Integration Secret** (starts with `ntn_`)
|
||||
|
||||
### 2. Share Pages with the Integration
|
||||
|
||||
The integration has no access by default. You must share each page:
|
||||
|
||||
1. Open a Notion page you want to mount
|
||||
1. Click `...` (top-right) -> **+ Add connections**
|
||||
1. Search for your integration name and select it
|
||||
1. Child pages inherit the connection - share a top-level page to expose its entire subtree
|
||||
|
||||
### 3. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
NOTION_API_KEY=ntn_xxxx...
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python Notion Setup](/python/setup/notion) guide.
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: OCI Object Storage
|
||||
icon: server
|
||||
description: Set up Oracle Cloud Infrastructure Object Storage credentials for the OCI resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
The OCI resource needs:
|
||||
|
||||
- an Object Storage bucket
|
||||
- your Object Storage namespace
|
||||
- your OCI region
|
||||
- a Customer Secret Key pair for the S3 Compatibility API
|
||||
|
||||
### 1. Create a Bucket
|
||||
|
||||
1. Open the OCI Console
|
||||
1. Go to `Storage` -> `Buckets`
|
||||
1. Choose the target compartment
|
||||
1. Click `Create bucket`
|
||||
1. Enter the bucket name, such as `mirage`
|
||||
1. Keep the storage tier as `Standard` unless you specifically want archive
|
||||
|
||||
If you want MIRAGE data under a prefix like `data/`, create objects under that
|
||||
prefix inside the bucket. The prefix is not part of the bucket name.
|
||||
|
||||
### 2. Get the Namespace
|
||||
|
||||
1. Open the profile menu in the OCI Console
|
||||
1. Go to `Tenancy`
|
||||
1. Copy the Object Storage namespace string, such as `idsiqbdbcr4i`
|
||||
|
||||
You can also fetch it with:
|
||||
|
||||
```bash
|
||||
oci os ns get
|
||||
```
|
||||
|
||||
### 3. Get S3-compatible Keys
|
||||
|
||||
1. Open the profile menu
|
||||
1. Go to `User settings`
|
||||
1. Open `Customer secret keys`
|
||||
1. Click `Generate secret key`
|
||||
1. Copy both the `Access Key` and `Secret Key`
|
||||
|
||||
Oracle only shows the secret once, so save it immediately.
|
||||
|
||||
### 4. Find the Region
|
||||
|
||||
Use the OCI Console region selector in the top-right. For example:
|
||||
|
||||
- `us-ashburn-1`
|
||||
- `us-phoenix-1`
|
||||
- `eu-frankfurt-1`
|
||||
|
||||
### 5. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
OCI_BUCKET=mirage
|
||||
OCI_NAMESPACE=idsiqbdbcr4i
|
||||
OCI_REGION=us-ashburn-1
|
||||
OCI_ACCESS_KEY_ID=...
|
||||
OCI_SECRET_ACCESS_KEY=...
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python OCI Setup](/python/setup/oci) guide.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Paperclip
|
||||
icon: paperclip
|
||||
description: Set up Paperclip credentials for the Paperclip resource.
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install the Paperclip CLI:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://paperclip.gxl.ai/install.sh | bash
|
||||
```
|
||||
|
||||
## Credentials
|
||||
|
||||
Run the login command to authenticate via OAuth:
|
||||
|
||||
```bash
|
||||
paperclip login
|
||||
```
|
||||
|
||||
This opens a browser window for OAuth authentication and stores
|
||||
credentials at `~/.paperclip/credentials.json`.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
paperclip search "test"
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python Paperclip Setup](/python/setup/paperclip) guide.
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: Postgres
|
||||
icon: database
|
||||
description: Set up a Postgres connection for the Postgres resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
### 1. Get Your Connection DSN
|
||||
|
||||
#### Local Postgres
|
||||
|
||||
```bash
|
||||
# Default local instance
|
||||
DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres
|
||||
```
|
||||
|
||||
#### Managed Postgres (Supabase, Neon, RDS, ...)
|
||||
|
||||
Copy the connection string from your provider's dashboard. It usually looks like:
|
||||
|
||||
```
|
||||
postgres://<user>:<password>@<host>:5432/<database>?sslmode=require
|
||||
```
|
||||
|
||||
### 2. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
DATABASE_URL=postgres://user:pass@host:5432/db?sslmode=require
|
||||
```
|
||||
|
||||
## Permissions
|
||||
|
||||
The Postgres resource is read-only. Grant the role you connect with at minimum:
|
||||
|
||||
```sql
|
||||
GRANT USAGE ON SCHEMA public TO mirage_reader;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mirage_reader;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO mirage_reader;
|
||||
```
|
||||
|
||||
If you only want to expose a subset of schemas, set `schemas` in the resource config:
|
||||
|
||||
```python
|
||||
PostgresConfig(dsn=os.environ["DATABASE_URL"], schemas=["public", "analytics"])
|
||||
```
|
||||
|
||||
## Limits
|
||||
|
||||
The resource has built-in safety limits to keep an agent from accidentally pulling a whole table:
|
||||
|
||||
- `default_row_limit` (1,000): default LIMIT applied to ad-hoc reads.
|
||||
- `max_read_rows` (10,000): hard ceiling per read.
|
||||
- `max_read_bytes` (10 MiB): hard ceiling per read.
|
||||
- `default_search_limit` (100): default LIMIT for search-style queries.
|
||||
|
||||
Override in the config if you need bigger reads.
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Cloudflare R2
|
||||
icon: cloud
|
||||
description: Set up Cloudflare R2 credentials for the R2 resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
### 1. Create R2 API Token
|
||||
|
||||
1. Go to https://dash.cloudflare.com/ -> **R2 Object Storage**
|
||||
1. **Manage R2 API Tokens** -> **Create API Token**
|
||||
1. Set permissions: **Object Read** (or **Object Read & Write**)
|
||||
1. Scope to your bucket
|
||||
1. Copy the **Access Key ID** and **Secret Access Key**
|
||||
1. Note your **Account ID** from the Cloudflare dashboard URL
|
||||
|
||||
### 2. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
R2_BUCKET=my-bucket
|
||||
R2_ACCOUNT_ID=abc123...
|
||||
R2_ACCESS_KEY_ID=...
|
||||
R2_SECRET_ACCESS_KEY=...
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python R2 Setup](/python/setup/r2) guide.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: S3
|
||||
icon: aws
|
||||
description: Set up AWS S3 credentials for the S3 resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
The S3 resource needs an AWS access key pair with read (and optionally write) access to your bucket.
|
||||
|
||||
### 1. Create an IAM User
|
||||
|
||||
1. Go to https://console.aws.amazon.com/iam/
|
||||
1. **Users** -> **Create user**
|
||||
1. Attach the `AmazonS3ReadOnlyAccess` policy (or `AmazonS3FullAccess` for write)
|
||||
1. **Security credentials** -> **Create access key** -> **Application running outside AWS**
|
||||
1. Copy the **Access key ID** and **Secret access key**
|
||||
|
||||
### 2. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
AWS_S3_BUCKET=my-bucket
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_ACCESS_KEY_ID=AKIA...
|
||||
AWS_SECRET_ACCESS_KEY=wJal...
|
||||
```
|
||||
|
||||
### Alternative: AWS Profile
|
||||
|
||||
If you have `~/.aws/credentials` configured, you can use a profile name instead of explicit access keys.
|
||||
|
||||
For the Python resource API, see the [S3 resource doc](/python/resource/s3).
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: Slack
|
||||
icon: slack
|
||||
description: Set up a Slack Bot Token for the Slack resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
### 1. Create a Slack App
|
||||
|
||||
1. Go to https://api.slack.com/apps
|
||||
1. **Create New App** -> **From scratch**
|
||||
1. Name it (e.g., "Mirage") and select your workspace
|
||||
|
||||
### 2. Configure Bot Permissions
|
||||
|
||||
1. **OAuth & Permissions** in the left sidebar
|
||||
1. Under **Bot Token Scopes**, add:
|
||||
- `channels:history` - read messages in public channels
|
||||
- `channels:read` - list public channels
|
||||
- `groups:history` - read messages in private channels (optional)
|
||||
- `groups:read` - list private channels (optional)
|
||||
- `chat:write` - send messages (for write mode)
|
||||
|
||||
### 3. Install to Workspace
|
||||
|
||||
1. **Install App** in the left sidebar -> **Install to Workspace**
|
||||
1. Authorize the requested permissions
|
||||
1. Copy the **Bot User OAuth Token** (`xoxb-...`)
|
||||
|
||||
### 4. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
SLACK_BOT_TOKEN=xoxb-xxxx...
|
||||
```
|
||||
|
||||
### 5. Invite the Bot
|
||||
|
||||
The bot must be invited to channels it needs to read:
|
||||
|
||||
```
|
||||
/invite @Mirage
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python Slack Setup](/python/setup/slack) guide.
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: Semantic Scholar
|
||||
icon: book
|
||||
description: Set up a Semantic Scholar API key for the sscholar resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
The Semantic Scholar resource works without an API key for low-volume use, but you should request a key for any production workload to avoid rate limiting.
|
||||
|
||||
### 1. Request an API Key
|
||||
|
||||
1. Go to https://www.semanticscholar.org/product/api
|
||||
2. Click **Request an API Key** and fill in the form.
|
||||
3. Wait for the email with your key (usually a business day or two).
|
||||
|
||||
### 2. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
SEMANTIC_SCHOLAR_API_KEY=your-api-key-here
|
||||
```
|
||||
|
||||
If you skip the key, leave the env var unset; the resource falls back to the public unauthenticated endpoint.
|
||||
|
||||
## Limits
|
||||
|
||||
The resource exposes tunable defaults to keep an agent from blowing through your rate limit on first contact:
|
||||
|
||||
- `defaultListLimit` (100): default page size for list operations.
|
||||
- `defaultSearchLimit` (20): default page size for search.
|
||||
- `defaultSnippetLimit` (10): default snippet count per paper.
|
||||
|
||||
Override in the resource config:
|
||||
|
||||
```ts
|
||||
new SSCholarResource({
|
||||
apiKey: process.env.SEMANTIC_SCHOLAR_API_KEY,
|
||||
defaultSearchLimit: 50,
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: Supabase Storage
|
||||
icon: bolt
|
||||
description: Set up Supabase Storage credentials for the Supabase resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
Supabase Storage exposes an S3-compatible API at `https://<project-ref>.storage.supabase.co/storage/v1/s3`, signed with **S3 Access Keys** scoped to a Supabase project (not your `anon` or `service_role` JWT).
|
||||
|
||||
### 1. Create an S3 access key
|
||||
|
||||
1. Open the [Supabase dashboard](https://supabase.com/dashboard) and pick your project.
|
||||
2. **Storage** -> **Settings** -> **S3 Access Keys**.
|
||||
3. **New access key** -> scope to the bucket(s) you want Mirage to mount.
|
||||
4. Copy the **Access key ID** and **Secret access key**, the secret is shown once.
|
||||
|
||||
### 2. Note the project reference and region
|
||||
|
||||
- **Project reference** is in the dashboard URL: `https://supabase.com/dashboard/project/<project-ref>`.
|
||||
- **Region** is the region you selected when creating the project. Supabase requires a region string for SigV4 signing even though it is metadata only.
|
||||
|
||||
### 3. Set 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 requires **path-style** S3 URLs. The Mirage resource sets `forcePathStyle: true` automatically when you pass `project_ref`.
|
||||
</Tip>
|
||||
|
||||
For Python configuration, see the [Python Supabase Resource](/python/resource/supabase) guide. For Node and browser wiring, see the [TypeScript Supabase Setup](/typescript/setup/supabase) guide.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Telegram
|
||||
icon: paper-plane
|
||||
description: Set up a Telegram Bot Token for the Telegram resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
### 1. Create a Bot via BotFather
|
||||
|
||||
1. Open Telegram and message [@BotFather](https://t.me/BotFather)
|
||||
1. Send `/newbot` and follow the prompts to name your bot
|
||||
1. Copy the **Bot Token** that BotFather returns
|
||||
|
||||
### 2. Add the Bot to Groups or Channels
|
||||
|
||||
1. Open the group or channel in Telegram
|
||||
1. Add the bot as a member
|
||||
1. For **channels**, add the bot as an **administrator** (channels require admin access for the bot to read messages)
|
||||
|
||||
### 3. Send a Message
|
||||
|
||||
Send at least one message in the group or channel so the bot receives an update and can discover the chat.
|
||||
|
||||
### 4. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python Telegram Setup](/python/setup/telegram) guide.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Trello
|
||||
icon: table-columns
|
||||
description: Set up Trello API credentials for the Trello resource.
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
### 1. Get an API Key
|
||||
|
||||
1. Go to [https://trello.com/power-ups/admin](https://trello.com/power-ups/admin)
|
||||
1. Create a new Power-Up (or use an existing one)
|
||||
1. Copy the **API Key** from the Power-Up's API Key page
|
||||
|
||||
### 2. Generate a Token
|
||||
|
||||
Open the following URL in your browser, replacing `YOUR_API_KEY`:
|
||||
|
||||
```
|
||||
https://trello.com/1/authorize?expiration=never&scope=read,write&response_type=token&key=YOUR_API_KEY
|
||||
```
|
||||
|
||||
Click **Allow** and copy the token string.
|
||||
|
||||
### 3. Set Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.development
|
||||
TRELLO_API_KEY=your-api-key
|
||||
TRELLO_API_TOKEN=your-api-token
|
||||
```
|
||||
|
||||
For Python configuration, see the [Python Trello Setup](/python/setup/trello) guide.
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
description: Common Mirage setup failures and the fastest path to getting unblocked.
|
||||
icon: life-ring
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Import Errors After Install (Python)
|
||||
|
||||
- Make sure you installed the package into the environment you are actually using.
|
||||
- If you need resource-specific dependencies, install the matching extra such as `mirage-ai[s3]`, `mirage-ai[redis]`, or `mirage-ai[fuse]`.
|
||||
- For source installs, run `uv sync --all-extras --no-extra camel` (the `camel` extra conflicts with the `openai` stack, so excluding it keeps the rest installable).
|
||||
|
||||
### Import Errors After Install (TypeScript)
|
||||
|
||||
- Pick the entrypoint that matches your runtime: `mirage/node` for Node servers and CLIs, `mirage/browser` for browser and edge runtimes, `mirage/core` for runtime-agnostic primitives.
|
||||
- Native peers like FUSE and Redis are opt-in. Install them only on Node, alongside `mirage`, when you need those resources.
|
||||
- If your bundler complains about Node built-ins, you are probably importing `mirage/node` from a browser entry. Switch to `mirage/browser` or `mirage/core`.
|
||||
|
||||
### Credential Errors
|
||||
|
||||
- Check that the resource-specific environment variables are set in the current shell.
|
||||
- Confirm you followed the right setup guide from the [Setup](/home/setup/s3) section.
|
||||
- If a resource depends on Google OAuth or bot tokens, verify the scopes and app installation steps.
|
||||
|
||||
### FUSE Does Not Mount
|
||||
|
||||
- Follow the OS-level setup first: [macOS](/home/setup/macos) or [Linux](/home/setup/linux).
|
||||
- Then confirm you installed `mirage[fuse]`.
|
||||
- If Mirage falls back to virtual mode, check the FUSE installation before debugging command behavior.
|
||||
|
||||
### Commands Return Empty Output
|
||||
|
||||
- Verify the mount prefix and the path you are querying.
|
||||
- Start with `ls`, `tree`, or `stat` before assuming the resource is broken.
|
||||
- For remote systems, confirm the underlying account has access to the target resource.
|
||||
|
||||
### Docs and Examples Disagree
|
||||
|
||||
- Prefer the Python quickstart and the current resource docs over older snippets copied from issues or experiments.
|
||||
- If something still looks inconsistent, open an issue or contact the team directly.
|
||||
|
||||
## Book Time With The Team
|
||||
|
||||
If you are blocked and want direct help, book a troubleshooting call:
|
||||
|
||||
<Card title="30-Minute Troubleshooting Call" icon="calendar" href="https://cal.com/strukto/30min">
|
||||
Book time with the Mirage team on Cal.com.
|
||||
</Card>
|
||||
@@ -0,0 +1,20 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<style>
|
||||
svg { color: #374151; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
svg { color: #ffffff; }
|
||||
}
|
||||
</style>
|
||||
<path stroke="currentColor" d="M12.409 13.017A5 5 0 0 1 22 15c0 3.866-4 7-9 7-4.077 0-8.153-.82-10.371-2.462-.426-.316-.631-.832-.62-1.362C2.118 12.723 2.627 2 10 2a3 3 0 0 1 3 3 2 2 0 0 1-2 2c-1.105 0-1.64-.444-2-1" />
|
||||
<path stroke="currentColor" d="M15 14a5 5 0 0 0-7.584 2" />
|
||||
<path stroke="currentColor" d="M9.964 6.825C8.019 7.977 9.5 13 8 15" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 669 B |
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg"><title>CAMEL-AI</title><g transform="translate(0,800) scale(0.1,-0.1)" fill="#9CA3AF" stroke="none"><path d="M2970 6694 c-69 -14 -320 -58 -559 -100 -473 -81 -513 -89 -545 -106 -72 -37 -151 -117 -245 -246 l-99 -137 21 -17 c43 -33 151 -80 221 -95 50 -10 99 -13 165 -9 82 5 98 3 126 -14 51 -31 142 -50 245 -50 l95 0 0 -648 c1 -679 5 -777 47 -957 89 -384 350 -782 667 -1014 34 -25 72 -53 83 -61 19 -15 18 -23 -66 -468 l-85 -453 85 -516 c46 -284 86 -518 88 -520 2 -2 111 -2 242 -1 l240 3 -109 515 -109 515 109 453 c69 292 110 447 115 435 4 -10 98 -436 208 -948 110 -511 202 -940 205 -952 l5 -23 246 0 247 0 -7 33 c-19 97 -398 1885 -402 1900 -5 16 25 17 525 17 361 0 531 -3 531 -10 0 -9 -388 -1857 -404 -1922 -4 -17 13 -18 247 -18 l252 0 205 970 c112 533 207 967 210 962 4 -4 66 -206 139 -450 l132 -443 -131 -502 c-72 -276 -133 -510 -136 -519 -5 -17 11 -18 245 -18 l251 0 5 22 c3 13 50 247 105 520 l99 497 -109 455 -109 455 34 84 c36 90 66 198 86 317 19 109 16 364 -5 484 -40 222 -149 486 -263 638 -32 42 -162 182 -289 310 -243 246 -278 272 -399 304 -103 28 -250 12 -352 -37 -24 -11 -69 -41 -100 -66 -115 -90 -190 -105 -278 -54 -27 16 -132 111 -233 210 -201 200 -274 259 -360 291 -78 30 -196 36 -292 16 -146 -30 -170 -49 -511 -391 l-309 -310 0 771 0 772 46 63 c26 35 51 70 57 77 5 6 8 12 6 11 -2 0 -60 -12 -129 -25z m-410 -409 l-60 -60 -55 55 c-30 30 -55 60 -55 66 0 7 26 35 58 63 l57 50 58 -57 57 -57 -60 -60z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Claude</title><path fill="#CC785C" d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 343 KiB |
|
After Width: | Height: | Size: 177 KiB |
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>LangChain</title><path fill="#1C9B7A" d="M6.0988 5.9175C2.7359 5.9175 0 8.6462 0 12s2.736 6.0825 6.0988 6.0825h11.8024C21.2641 18.0825 24 15.3538 24 12s-2.736-6.0825-6.0988-6.0825ZM5.9774 7.851c.493.0124 1.02.2496 1.273.6228.3673.4592.4778 1.0668.8944 1.4932.5604.6118 1.199 1.1505 1.7161 1.802.4892.5954.8386 1.2937 1.1436 1.9975.1244.2335.1257.5202.31.7197.0908.1204.5346.4483.4383.5645.0555.1204.4702.286.3263.4027-.1944.04-.4129.0476-.5616-.1074-.0549.126-.183.0596-.2819.0432a4 4 0 0 0-.025.0736c-.3288.0219-.5754-.3126-.732-.565-.3111-.168-.6642-.2702-.982-.446-.0182.2895.0452.6485-.231.8353-.014.5565.8436.0656.9222.4804-.061.0067-.1286-.0095-.1774.0373-.2239.2172-.4805-.1645-.7385-.007-.3464.174-.3808.3161-.8096.352-.0237-.0359-.0143-.0592.0059-.0811.1207-.1399.1295-.3046.3356-.3643-.2122-.0334-.3899.0833-.5686.1757-.2323.095-.2304-.2141-.5878.0164-.0396-.0322-.0208-.0615.0018-.0864.0908-.1107.2102-.127.345-.1208-.663-.3686-.9751.4507-1.2813.0432-.092.0243-.1265.1068-.1845.1652-.05-.0548-.0123-.1212-.0099-.1857-.0598-.028-.1356-.041-.1179-.1366-.1171-.0395-.1988.0295-.286.0952-.0787-.0608.0532-.1492.0776-.2125.0702-.1216.23-.025.3111-.1126.2306-.1308.552.0814.8155.0455.203.0255.4544-.1825.3526-.39-.2171-.2767-.179-.6386-.1839-.9695-.0268-.1929-.491-.4382-.6252-.6462-.1659-.1873-.295-.4047-.4243-.6182-.4666-.9008-.3198-2.0584-.9077-2.8947-.266.1466-.6125.0774-.8418-.119-.1238.1125-.1292.2598-.139.4161-.297-.2962-.2593-.8559-.022-1.1855.0969-.1302.2127-.2373.342-.3316.0292-.0213.0391-.0419.0385-.0747.1174-.5267.5764-.7391 1.0694-.7267m12.4071.46c.5575 0 1.0806.2159 1.474.6082s.61.9145.61 1.4704c0 .556-.2167 1.078-.61 1.4698v.0006l-.902.8995a2.08 2.08 0 0 1-.8597.5166l-.0164.0047-.0058.0164a2.05 2.05 0 0 1-.474.7308l-.9018.8995c-.3934.3924-.917.6083-1.4745.6083s-1.0806-.216-1.474-.6083c-.813-.8107-.813-2.1294 0-2.9402l.9019-.8995a2.056 2.056 0 0 1 .858-.5143l.017-.0053.0058-.0158a2.07 2.07 0 0 1 .4752-.7337l.9018-.8995c.3934-.3924.9171-.6083 1.4745-.6083zm0 .8965a1.18 1.18 0 0 0-.8388.3462l-.9018.8995a1.181 1.181 0 0 0-.3427.9252l.0053.0572c.0323.2652.149.5044.3374.6917.13.1296.2733.2114.4471.2686a.9.9 0 0 1 .014.1582.884.884 0 0 1-.2609.6304l-.0554.0554c-.3013-.1028-.5525-.253-.7794-.4792a2.06 2.06 0 0 1-.5761-1.0968l-.0099-.0578-.0461.0368a1.1 1.1 0 0 0-.0876.0794l-.9024.8995c-.4623.461-.4623 1.212 0 1.673.2311.2305.535.346.8394.3461.3043 0 .6077-.1156.8388-.3462l.9019-.8995c.4623-.461.4623-1.2113 0-1.673a1.17 1.17 0 0 0-.4367-.2749 1 1 0 0 1-.014-.1611c0-.2591.1023-.505.2901-.6923.3019.1028.57.2694.7962.495.3007.2999.4994.679.5756 1.0968l.0105.0578.0455-.0373a1.1 1.1 0 0 0 .0887-.0794l.902-.8996c.4622-.461.4628-1.2124 0-1.6735a1.18 1.18 0 0 0-.8395-.3462Zm-9.973 5.1567-.0006.0006c-.0793.3078-.1048.8318-.506.847-.033.1776.1228.2445.2655.1874.141-.0645.2081.0508.2557.1657.2177.0317.5394-.0725.5516-.3298-.325-.1867-.4253-.5418-.5662-.8709"/></svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,181 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1010" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif">
|
||||
<defs>
|
||||
<style type="text/css"><![CDATA[
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600;700;800&display=swap');
|
||||
.display {
|
||||
font-family: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
]]></style>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
||||
<path d="M0 0 L10 5 L0 10 z" fill="#A1A1AA"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="1200" height="1010" fill="#0E0E10"/>
|
||||
|
||||
<!-- Title: mirage icon (cube) + "mirage" wordmark in diagram font -->
|
||||
<svg x="407" y="4" width="96" height="110" viewBox="16 7 128 146">
|
||||
<polygon points="19,45 80,10 80,45 50,63" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="80,10 141,45 110,63 80,45" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="141,45 141,115 110,98 110,63" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="141,115 80,150 80,115 110,98" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="80,150 19,115 50,98 80,115" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="19,115 19,45 50,63 50,98" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="80,45 110,63 110,98 80,115 50,98 50,63" fill="#FFFFFF" stroke="#000000" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="50" y2="63" stroke="#000000" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="110" y2="63" stroke="#000000" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="80" y2="115" stroke="#000000" stroke-width="2.5"/>
|
||||
</svg>
|
||||
<text x="515" y="84" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif" font-size="72" font-weight="700" letter-spacing="-1">mirage</text>
|
||||
<text x="600" y="140" text-anchor="middle" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif" font-size="22" font-weight="500" letter-spacing="0.4">A Unified Virtual File System for AI Agents</text>
|
||||
|
||||
<!-- ===================== LAYER 1: AGENT ===================== -->
|
||||
<rect x="20" y="160" width="1160" height="170" rx="8" fill="#1A1A1D" stroke="#FFFFFF" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="198" font-size="22" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">AI Agent and Application</text>
|
||||
<text x="44" y="228" font-size="18" fill="#A1A1AA">
|
||||
<tspan x="44" dy="0">Bash commands, VFS</tspan>
|
||||
<tspan x="44" dy="24">calls, or syscalls</tspan>
|
||||
</text>
|
||||
|
||||
<!-- AI Agent (centered over connectors at x=470 and x=680: equal 30px margins on each side) -->
|
||||
<rect x="440" y="210" width="270" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="575" y="242" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">AI Agent</text>
|
||||
|
||||
<!-- OS Processes (positioned over FUSE Adapter x=890 for vertical connector) -->
|
||||
<rect x="791" y="210" width="198" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="890" y="242" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">App & CLI</text>
|
||||
|
||||
|
||||
<!-- ===================== LAYER 2: WORKSPACE ===================== -->
|
||||
<rect x="20" y="376" width="1160" height="200" rx="8" fill="#1A1A1D" stroke="#FFFFFF" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="414" font-size="22" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage Bash and VFS</text>
|
||||
<text x="44" y="444" font-size="18" fill="#A1A1AA">
|
||||
<tspan x="44" dy="0">Mirage Bash &</tspan>
|
||||
<tspan x="44" dy="24">registered commands;</tspan>
|
||||
<tspan x="44" dy="24">Mirage VFS &</tspan>
|
||||
<tspan x="44" dy="24">registered VFS ops</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Mirage Shell -->
|
||||
<rect x="376" y="409" width="188" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="470" y="441" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage Bash</text>
|
||||
|
||||
<!-- Mirage VFS -->
|
||||
<rect x="596" y="409" width="168" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="680" y="441" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage VFS</text>
|
||||
|
||||
<!-- FUSE Adapter -->
|
||||
<rect x="796" y="409" width="188" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="890" y="441" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">FUSE Adapter</text>
|
||||
|
||||
<!-- Command Registry -->
|
||||
<rect x="376" y="491" width="188" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="470" y="523" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Command Registry</text>
|
||||
|
||||
<!-- VFS Registry -->
|
||||
<rect x="596" y="491" width="168" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="680" y="523" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">VFS Registry</text>
|
||||
|
||||
<!-- Shell -> Command Registry -->
|
||||
<line x1="470" y1="461" x2="470" y2="491" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Mirage VFS -> VFS Registry -->
|
||||
<line x1="680" y1="461" x2="680" y2="491" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- FUSE -> Mirage VFS -->
|
||||
<line x1="796" y1="435" x2="764" y2="435" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===================== LAYER 3: DISPATCHER & CACHE ===================== -->
|
||||
<rect x="20" y="622" width="1160" height="140" rx="8" fill="#1A1A1D" stroke="#FFFFFF" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="660" font-size="22" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Dispatcher & Cache</text>
|
||||
<text x="44" y="690" font-size="18" fill="#A1A1AA">
|
||||
<tspan x="44" dy="0">Dispatches by mount;</tspan>
|
||||
<tspan x="44" dy="24">caches repeats</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Mirage Dispatcher (matches AI Agent dimensions: w=270, h=52, x=440) -->
|
||||
<rect x="440" y="666" width="270" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="575" y="698" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage Dispatcher</text>
|
||||
|
||||
<!-- Index & File Cache (merged; aligned x with Application & CLI: x=800, w=180) -->
|
||||
<rect x="791" y="666" width="198" height="52" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="890" y="698" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Index & File Cache</text>
|
||||
|
||||
<!-- Dispatcher -> Cache -->
|
||||
<line x1="710" y1="692" x2="791" y2="692" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===================== LAYER 4: BACKEND ===================== -->
|
||||
<rect x="20" y="808" width="1160" height="190" rx="8" fill="#1A1A1D" stroke="#FFFFFF" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="846" font-size="22" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Infrastructure and Remote</text>
|
||||
<text x="44" y="876" font-size="18" fill="#A1A1AA">
|
||||
<tspan x="44" dy="0">Infrastructure & remote,</tspan>
|
||||
<tspan x="44" dy="24">mounted as one tree</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Infrastructure (renamed from Local Backends; aligned x with Mirage Shell at 380, w=180) -->
|
||||
<rect x="380" y="833" width="180" height="140" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="470" y="861" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Infrastructure</text>
|
||||
<line x1="420" y1="873" x2="520" y2="873" stroke="#3F3F46" stroke-width="1"/>
|
||||
<text x="470" y="897" text-anchor="middle" font-size="15" fill="#A1A1AA">RAM</text>
|
||||
<text x="470" y="919" text-anchor="middle" font-size="15" fill="#A1A1AA">Disk</text>
|
||||
<text x="470" y="941" text-anchor="middle" font-size="15" fill="#A1A1AA">OPFS</text>
|
||||
<text x="470" y="963" text-anchor="middle" font-size="15" fill="#A1A1AA">Redis</text>
|
||||
|
||||
<!-- Remote Services (widened by 10px each side: x=790, w=200; center stays at 890) -->
|
||||
<rect x="790" y="833" width="200" height="140" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1.2"/>
|
||||
<text x="890" y="861" text-anchor="middle" font-size="19" font-weight="500" fill="#FFFFFF" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Remote Service</text>
|
||||
<line x1="830" y1="873" x2="950" y2="873" stroke="#3F3F46" stroke-width="1"/>
|
||||
|
||||
<!-- Icon grid: 6 per row, 22x22 colored rect with inlined simple-icons brand SVG -->
|
||||
<g transform="translate(804,883)"><rect width="22" height="22" rx="5" fill="#FF9900"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="m20.913 13.147l.12-.895c.947.576 1.258.922 1.354 1.071c-.16.031-.562.046-1.474-.176m-2.174 7.988l-.005.073c0 .084-.207.405-1.124.768a10 10 0 0 1-1.438.432c-1.405.325-3.128.504-4.853.504c-4.612 0-7.412-1.184-7.412-1.704l-.005-.073L1.81 5.602q.203.117.432.227q.064.03.128.057q.2.093.417.18l.179.069q.232.087.478.168l.13.043q.311.097.646.187l.176.044q.263.066.534.127a23 23 0 0 0 .843.17l.121.023q.378.067.768.122q.107.016.216.03q.299.04.604.077l.24.027q.366.04.74.07l.081.009q.413.033.83.056l.233.012q.316.015.633.025a33 33 0 0 0 2.795-.026l.232-.011q.417-.023.83-.056l.08-.008q.375-.03.742-.072l.238-.026q.307-.036.609-.077l.211-.03q.392-.056.772-.122l.111-.02q.323-.06.634-.125l.212-.047q.279-.062.546-.13l.166-.042q.338-.09.654-.189l.115-.038a11 11 0 0 0 .493-.173q.087-.032.17-.066q.225-.089.43-.185q.059-.025.116-.052q.23-.11.436-.228l-.976 7.245c-2.488-.78-5.805-2.292-7.311-3a1.09 1.09 0 0 0-1.088-1.085c-.6 0-1.088.489-1.088 1.088s.488 1.089 1.088 1.089c.196 0 .378-.056.537-.148c1.72.812 5.144 2.367 7.715 3.15zm-7.42-20.047c5.677 0 9.676 1.759 9.75 2.736l-.014.113c-.01.033-.031.067-.048.101c-.015.028-.026.057-.047.087c-.024.033-.058.068-.09.102c-.028.03-.051.06-.084.09c-.038.035-.087.07-.133.105c-.04.03-.074.06-.119.091c-.053.036-.116.071-.177.107c-.05.03-.095.06-.15.09c-.068.036-.147.073-.222.11c-.059.028-.114.057-.177.085c-.084.038-.177.074-.268.111c-.068.027-.13.054-.203.082c-.097.036-.205.072-.31.107c-.075.026-.148.053-.228.079c-.111.035-.233.069-.35.103c-.085.024-.165.05-.253.073c-.124.034-.258.065-.389.098c-.093.022-.181.046-.278.068c-.139.032-.287.061-.433.091c-.098.02-.191.041-.293.06c-.155.03-.32.057-.482.084c-.1.018-.198.036-.302.052c-.166.026-.342.048-.515.072c-.11.014-.213.03-.325.044c-.181.023-.372.041-.56.06q-.163.019-.332.036c-.188.016-.386.029-.58.043c-.122.009-.24.02-.364.028c-.207.012-.422.02-.635.028c-.12.005-.234.012-.354.016a36 36 0 0 1-2.069 0c-.12-.004-.234-.011-.352-.016c-.214-.008-.43-.016-.637-.028c-.122-.008-.238-.02-.36-.027c-.195-.015-.394-.028-.584-.044c-.11-.01-.215-.024-.324-.035c-.19-.02-.384-.038-.568-.06l-.315-.044c-.176-.024-.355-.046-.525-.073c-.1-.015-.192-.033-.29-.05c-.167-.028-.335-.055-.494-.086c-.096-.018-.183-.038-.276-.056c-.151-.032-.305-.062-.45-.095c-.09-.02-.173-.043-.26-.064c-.138-.034-.277-.067-.407-.102c-.082-.022-.157-.046-.235-.069a12 12 0 0 1-.368-.108c-.075-.024-.141-.049-.213-.073c-.11-.037-.223-.075-.325-.113c-.067-.025-.125-.051-.188-.077c-.096-.038-.195-.076-.282-.115c-.06-.027-.11-.054-.166-.08c-.08-.039-.162-.077-.233-.116c-.052-.028-.094-.055-.142-.084c-.063-.038-.13-.075-.185-.113c-.043-.029-.075-.058-.113-.086c-.048-.037-.098-.073-.139-.11c-.032-.029-.054-.057-.08-.087c-.033-.035-.069-.07-.093-.104c-.02-.03-.031-.058-.046-.086c-.018-.035-.039-.068-.049-.102l-.015-.113c.076-.977 4.074-2.736 9.748-2.736m12.182 12.124c-.118-.628-.84-1.291-2.31-2.128l.963-7.16l.005-.073C22.16 1.581 16.447 0 11.32 0C6.194 0 .482 1.581.482 3.851l.005.072L2.819 21.25c.071 2.002 5.236 2.75 8.5 2.75c1.805 0 3.615-.188 5.098-.531c.598-.138 1.133-.3 1.592-.48c1.18-.467 1.789-1.053 1.813-1.739l.945-7.018c.557.131 1.016.197 1.389.197c.54 0 .902-.137 1.134-.413a.96.96 0 0 0 .21-.804Z"/></svg></g> <!-- S3 -->
|
||||
<g transform="translate(834,883)"><rect width="22" height="22" rx="5" fill="#F38020"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M16.509 16.845c.147-.507.09-.971-.155-1.316c-.225-.316-.605-.499-1.062-.52l-8.66-.113a.16.16 0 0 1-.133-.07a.2.2 0 0 1-.02-.156a.24.24 0 0 1 .203-.156l8.736-.113c1.035-.049 2.16-.886 2.554-1.913l.499-1.302a.27.27 0 0 0 .014-.168a5.689 5.689 0 0 0-10.937-.584a2.58 2.58 0 0 0-1.794-.498a2.56 2.56 0 0 0-2.223 3.18A3.634 3.634 0 0 0 0 16.751q.002.264.035.527a.174.174 0 0 0 .17.148h15.98a.22.22 0 0 0 .204-.155zm2.757-5.564c-.077 0-.161 0-.239.011c-.056 0-.105.042-.127.098l-.337 1.174c-.148.507-.092.971.154 1.317c.225.316.605.498 1.062.52l1.844.113c.056 0 .105.026.133.07a.2.2 0 0 1 .021.156a.24.24 0 0 1-.204.156l-1.92.112c-1.042.049-2.159.887-2.553 1.914l-.141.358c-.028.072.021.142.099.142h6.597a.174.174 0 0 0 .17-.126a5 5 0 0 0 .175-1.28a4.74 4.74 0 0 0-4.734-4.727"/></svg></g> <!-- R2 (Cloudflare) -->
|
||||
<g transform="translate(864,883)"><rect width="22" height="22" rx="5" fill="#C74634"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M16.412 4.412h-8.82a7.588 7.588 0 0 0-.008 15.176h8.828a7.588 7.588 0 0 0 0-15.176m-.193 12.502H7.786a4.915 4.915 0 0 1 0-9.828h8.433a4.914 4.914 0 1 1 0 9.828"/></svg></g> <!-- OCI -->
|
||||
<g transform="translate(894,883)"><rect width="22" height="22" rx="5" fill="#4285F4"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M12.19 2.38a9.344 9.344 0 0 0-9.234 6.893c.053-.02-.055.013 0 0c-3.875 2.551-3.922 8.11-.247 10.941l.006-.007l-.007.03a6.7 6.7 0 0 0 4.077 1.356h5.173l.03.03h5.192c6.687.053 9.376-8.605 3.835-12.35a9.37 9.37 0 0 0-2.821-4.552l-.043.043l.006-.05A9.34 9.34 0 0 0 12.19 2.38m-.358 4.146c1.244-.04 2.518.368 3.486 1.15a5.19 5.19 0 0 1 1.862 4.078v.518c3.53-.07 3.53 5.262 0 5.193h-5.193l-.008.009v-.04H6.785a2.6 2.6 0 0 1-1.067-.23h.001a2.597 2.597 0 1 1 3.437-3.437l3.013-3.012A6.75 6.75 0 0 0 8.11 8.24c.018-.01.04-.026.054-.023a5.2 5.2 0 0 1 3.67-1.69z"/></svg></g> <!-- GCS -->
|
||||
<g transform="translate(924,883)"><rect width="22" height="22" rx="5" fill="#3FCF8E"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M11.9 1.036c-.015-.986-1.26-1.41-1.874-.637L.764 12.05C-.33 13.427.65 15.455 2.409 15.455h9.579l.113 7.51c.014.985 1.259 1.408 1.873.636l9.262-11.653c1.093-1.375.113-3.403-1.645-3.403h-9.642z"/></svg></g> <!-- Supabase -->
|
||||
<g transform="translate(954,883)"><rect width="22" height="22" rx="5" fill="#EA4335"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M24 5.457v13.909c0 .904-.732 1.636-1.636 1.636h-3.819V11.73L12 16.64l-6.545-4.91v9.273H1.636A1.636 1.636 0 0 1 0 19.366V5.457c0-2.023 2.309-3.178 3.927-1.964L5.455 4.64L12 9.548l6.545-4.91l1.528-1.145C21.69 2.28 24 3.434 24 5.457"/></svg></g> <!-- Gmail -->
|
||||
<g transform="translate(804,911)"><rect width="22" height="22" rx="5" fill="#1FA463"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M12.01 1.485c-2.082 0-3.754.02-3.743.047c.01.02 1.708 3.001 3.774 6.62l3.76 6.574h3.76c2.081 0 3.753-.02 3.742-.047c-.005-.02-1.708-3.001-3.775-6.62l-3.76-6.574zm-4.76 1.73a789.828 789.861 0 0 0-3.63 6.319L0 15.868l1.89 3.298l1.885 3.297l3.62-6.335l3.618-6.33l-1.88-3.287C8.1 4.704 7.255 3.22 7.25 3.214zm2.259 12.653l-.203.348c-.114.198-.96 1.672-1.88 3.287a423.93 423.948 0 0 1-1.698 2.97c-.01.026 3.24.042 7.222.042h7.244l1.796-3.157c.992-1.734 1.85-3.23 1.906-3.323l.104-.167h-7.249z"/></svg></g> <!-- GDrive -->
|
||||
<g transform="translate(834,911)"><rect width="22" height="22" rx="5" fill="#4285F4"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M14.727 6.727H14V0H4.91c-.905 0-1.637.732-1.637 1.636v20.728c0 .904.732 1.636 1.636 1.636h14.182c.904 0 1.636-.732 1.636-1.636V6.727zm-.545 10.455H7.09v-1.364h7.09v1.364zm2.727-3.273H7.091v-1.364h9.818zm0-3.273H7.091V9.273h9.818zM14.727 6h6l-6-6z"/></svg></g> <!-- GDocs -->
|
||||
<g transform="translate(864,911)"><rect width="22" height="22" rx="5" fill="#34A853"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M11.318 12.545H7.91v-1.909h3.41v1.91zM14.728 0v6h6zm1.363 10.636h-3.41v1.91h3.41zm0 3.273h-3.41v1.91h3.41zM20.727 6.5v15.864c0 .904-.732 1.636-1.636 1.636H4.909a1.636 1.636 0 0 1-1.636-1.636V1.636C3.273.732 4.005 0 4.909 0h9.318v6.5zm-3.273 2.773H6.545v7.909h10.91v-7.91zm-6.136 4.636H7.91v1.91h3.41v-1.91z"/></svg></g> <!-- GSheets -->
|
||||
<g transform="translate(894,911)"><rect width="22" height="22" rx="5" fill="#FBBC04"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M16.09 15.273H7.91v-4.637h8.18zm1.728-8.523h2.91v15.614c0 .904-.733 1.636-1.637 1.636H4.909a1.636 1.636 0 0 1-1.636-1.636V1.636C3.273.732 4.005 0 4.909 0h9.068v6.75zm-.363 2.523H6.545v7.363h10.91zm-2.728-5.979V6h6.001l-6-6v3.294z"/></svg></g> <!-- GSlides -->
|
||||
<g transform="translate(924,911)"><rect width="22" height="22" rx="5" fill="#4A154B"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M5.042 15.165a2.53 2.53 0 0 1-2.52 2.523A2.53 2.53 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52a2.527 2.527 0 0 1 2.521 2.52v6.313A2.53 2.53 0 0 1 8.834 24a2.53 2.53 0 0 1-2.521-2.522zM8.834 5.042a2.53 2.53 0 0 1-2.521-2.52A2.53 2.53 0 0 1 8.834 0a2.53 2.53 0 0 1 2.521 2.522v2.52zm0 1.271a2.53 2.53 0 0 1 2.521 2.521a2.53 2.53 0 0 1-2.521 2.521H2.522A2.53 2.53 0 0 1 0 8.834a2.53 2.53 0 0 1 2.522-2.521zm10.122 2.521a2.53 2.53 0 0 1 2.522-2.521A2.53 2.53 0 0 1 24 8.834a2.53 2.53 0 0 1-2.522 2.521h-2.522zm-1.268 0a2.53 2.53 0 0 1-2.523 2.521a2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.53 2.53 0 0 1 2.523 2.522zm-2.523 10.122a2.53 2.53 0 0 1 2.523 2.522A2.53 2.53 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522zm0-1.268a2.527 2.527 0 0 1-2.52-2.523a2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.53 2.53 0 0 1-2.522 2.523z"/></svg></g> <!-- Slack -->
|
||||
<g transform="translate(954,911)"><rect width="22" height="22" rx="5" fill="#5865F2"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M20.317 4.37a19.8 19.8 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.3 18.3 0 0 0-5.487 0a13 13 0 0 0-.617-1.25a.08.08 0 0 0-.079-.037A19.7 19.7 0 0 0 3.677 4.37a.1.1 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.08.08 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.08.08 0 0 0 .084-.028a14 14 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13 13 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10 10 0 0 0 .372-.292a.07.07 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.07.07 0 0 1 .078.01q.181.149.373.292a.077.077 0 0 1-.006.127a12.3 12.3 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.08.08 0 0 0 .084.028a19.8 19.8 0 0 0 6.002-3.03a.08.08 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.06.06 0 0 0-.031-.03M8.02 15.33c-1.182 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418m7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418"/></svg></g> <!-- Discord -->
|
||||
<g transform="translate(804,939)"><rect width="22" height="22" rx="5" fill="#26A5E4"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12a12 12 0 0 0 12-12A12 12 0 0 0 12 0zm4.962 7.224c.1-.002.321.023.465.14a.5.5 0 0 1 .171.325c.016.093.036.306.02.472c-.18 1.898-.962 6.502-1.36 8.627c-.168.9-.499 1.201-.82 1.23c-.696.065-1.225-.46-1.9-.902c-1.056-.693-1.653-1.124-2.678-1.8c-1.185-.78-.417-1.21.258-1.91c.177-.184 3.247-2.977 3.307-3.23c.007-.032.014-.15-.056-.212s-.174-.041-.249-.024q-.159.037-5.061 3.345q-.72.495-1.302.48c-.428-.008-1.252-.241-1.865-.44c-.752-.245-1.349-.374-1.297-.789q.04-.324.893-.663q5.247-2.286 6.998-3.014c3.332-1.386 4.025-1.627 4.476-1.635"/></svg></g> <!-- Telegram -->
|
||||
<g transform="translate(834,939)"><rect width="22" height="22" rx="5" fill="#181717"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></g> <!-- GitHub -->
|
||||
<g transform="translate(864,939)"><rect width="22" height="22" rx="5" fill="#5E6AD2"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M2.886 4.18A11.98 11.98 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556q-.787.496-1.65.866L.951 7.277q.371-.863.866-1.65ZM.322 9.163l14.515 14.515q-1.066.26-2.195.322L0 11.358a12 12 0 0 1 .322-2.195m-.17 4.862l9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z"/></svg></g> <!-- Linear -->
|
||||
<g transform="translate(894,939)"><rect width="22" height="22" rx="5" fill="#000000"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.981-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.841-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.748-.887l-15.177.887c-.56.047-.747.327-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514c-.748 0-.935-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233l4.764 7.279v-6.44l-1.215-.139c-.093-.514.28-.887.747-.933zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.934.653.934 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.447-1.632"/></svg></g> <!-- Notion -->
|
||||
<g transform="translate(924,939)"><rect width="22" height="22" rx="5" fill="#47A248"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M17.193 9.555c-1.264-5.58-4.252-7.414-4.573-8.115c-.28-.394-.53-.954-.735-1.44c-.036.495-.055.685-.523 1.184c-.723.566-4.438 3.682-4.74 10.02c-.282 5.912 4.27 9.435 4.888 9.884l.07.05A74 74 0 0 1 11.91 24h.481a29 29 0 0 1 .51-3.07c.417-.296.604-.463.85-.693a11.34 11.34 0 0 0 3.639-8.464c.01-.814-.103-1.662-.197-2.218m-5.336 8.195s0-8.291.275-8.29c.213 0 .49 10.695.49 10.695c-.381-.045-.765-1.76-.765-2.405"/></svg></g> <!-- MongoDB -->
|
||||
<text x="965" y="957" text-anchor="middle" font-size="16" font-weight="700" fill="#A1A1AA" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">…</text>
|
||||
|
||||
<!-- ===================== CROSS-LAYER ARROWS ===================== -->
|
||||
<!-- AI Agent -> Mirage Shell (vertical) -->
|
||||
<line x1="470" y1="262" x2="470" y2="409" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="176" y="282" width="282" height="28" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1"/>
|
||||
<text x="450" y="302" text-anchor="end" font-size="18" font-family="'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace" fill="#A1A1AA">grep alert /s3/log.jsonl</text>
|
||||
|
||||
<!-- AI Agent -> Mirage VFS (vertical) -->
|
||||
<line x1="680" y1="262" x2="680" y2="409" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="490" y="282" width="180" height="28" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1"/>
|
||||
<text x="662" y="302" text-anchor="end" font-size="18" font-family="'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace" fill="#A1A1AA">stat /paper.pdf</text>
|
||||
|
||||
<!-- Application & CLI -> FUSE (vertical) -->
|
||||
<line x1="890" y1="262" x2="890" y2="409" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="902" y="282" width="200" height="28" rx="4" fill="#0E0E10" stroke="#FFFFFF" stroke-width="1"/>
|
||||
<text x="910" y="302" font-size="18" font-family="'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace" fill="#A1A1AA">read /r2/data.csv</text>
|
||||
|
||||
<!-- Command Registry -> Dispatcher (vertical) -->
|
||||
<line x1="470" y1="543" x2="470" y2="666" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- VFS Registry -> Dispatcher (vertical) -->
|
||||
<line x1="680" y1="543" x2="680" y2="666" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dispatcher -> Infrastructure (vertical, aligned with CR -> Dispatcher connector at x=470) -->
|
||||
<line x1="470" y1="718" x2="470" y2="833" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dispatcher -> Remote Services (start x=680 matches PR -> Dispatcher; end x=890 matches Cache -> RS) -->
|
||||
<line x1="680" y1="718" x2="890" y2="833" stroke="#A1A1AA" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Cache - Remote Services (cache-hit short-circuit; dotted, no arrowhead) -->
|
||||
<line x1="890" y1="718" x2="890" y2="833" stroke="#A1A1AA" stroke-width="1.2" stroke-dasharray="4 3"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,181 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1010" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif">
|
||||
<defs>
|
||||
<style type="text/css"><![CDATA[
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600;700;800&display=swap');
|
||||
.display {
|
||||
font-family: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
]]></style>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
||||
<path d="M0 0 L10 5 L0 10 z" fill="#27272A"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="1200" height="1010" fill="#FFFFFF"/>
|
||||
|
||||
<!-- Title: mirage icon (cube) + "mirage" wordmark in diagram font -->
|
||||
<svg x="407" y="4" width="96" height="110" viewBox="16 7 128 146">
|
||||
<polygon points="19,45 80,10 80,45 50,63" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="80,10 141,45 110,63 80,45" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="141,45 141,115 110,98 110,63" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="141,115 80,150 80,115 110,98" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="80,150 19,115 50,98 80,115" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="19,115 19,45 50,63 50,98" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="80,45 110,63 110,98 80,115 50,98 50,63" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="50" y2="63" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="110" y2="63" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="80" y2="115" stroke="#ffffff" stroke-width="2.5"/>
|
||||
</svg>
|
||||
<text x="515" y="84" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif" font-size="72" font-weight="700" letter-spacing="-1">mirage</text>
|
||||
<text x="600" y="140" text-anchor="middle" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif" font-size="22" font-weight="500" letter-spacing="0.4">A Unified Virtual File System for AI Agents</text>
|
||||
|
||||
<!-- ===================== LAYER 1: AGENT ===================== -->
|
||||
<rect x="20" y="160" width="1160" height="170" rx="8" fill="#FAF9F6" stroke="#000000" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="198" font-size="22" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">AI Agent and Application</text>
|
||||
<text x="44" y="228" font-size="18" fill="#27272A">
|
||||
<tspan x="44" dy="0">Bash commands, VFS</tspan>
|
||||
<tspan x="44" dy="24">calls, or syscalls</tspan>
|
||||
</text>
|
||||
|
||||
<!-- AI Agent (centered over connectors at x=470 and x=680: equal 30px margins on each side) -->
|
||||
<rect x="440" y="210" width="270" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="575" y="242" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">AI Agent</text>
|
||||
|
||||
<!-- OS Processes (positioned over FUSE Adapter x=890 for vertical connector) -->
|
||||
<rect x="791" y="210" width="198" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="890" y="242" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">App & CLI</text>
|
||||
|
||||
|
||||
<!-- ===================== LAYER 2: WORKSPACE ===================== -->
|
||||
<rect x="20" y="376" width="1160" height="200" rx="8" fill="#FAF9F6" stroke="#000000" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="414" font-size="22" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage Bash and VFS</text>
|
||||
<text x="44" y="444" font-size="18" fill="#27272A">
|
||||
<tspan x="44" dy="0">Mirage Bash &</tspan>
|
||||
<tspan x="44" dy="24">registered commands;</tspan>
|
||||
<tspan x="44" dy="24">Mirage VFS &</tspan>
|
||||
<tspan x="44" dy="24">registered VFS ops</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Mirage Shell -->
|
||||
<rect x="376" y="409" width="188" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="470" y="441" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage Bash</text>
|
||||
|
||||
<!-- Mirage VFS -->
|
||||
<rect x="596" y="409" width="168" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="680" y="441" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage VFS</text>
|
||||
|
||||
<!-- FUSE Adapter -->
|
||||
<rect x="796" y="409" width="188" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="890" y="441" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">FUSE Adapter</text>
|
||||
|
||||
<!-- Command Registry -->
|
||||
<rect x="376" y="491" width="188" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="470" y="523" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Command Registry</text>
|
||||
|
||||
<!-- VFS Registry -->
|
||||
<rect x="596" y="491" width="168" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="680" y="523" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">VFS Registry</text>
|
||||
|
||||
<!-- Shell -> Command Registry -->
|
||||
<line x1="470" y1="461" x2="470" y2="491" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Mirage VFS -> VFS Registry -->
|
||||
<line x1="680" y1="461" x2="680" y2="491" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- FUSE -> Mirage VFS -->
|
||||
<line x1="796" y1="435" x2="764" y2="435" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===================== LAYER 3: DISPATCHER & CACHE ===================== -->
|
||||
<rect x="20" y="622" width="1160" height="140" rx="8" fill="#FAF9F6" stroke="#000000" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="660" font-size="22" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Dispatcher & Cache</text>
|
||||
<text x="44" y="690" font-size="18" fill="#27272A">
|
||||
<tspan x="44" dy="0">Dispatches by mount;</tspan>
|
||||
<tspan x="44" dy="24">caches repeats</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Mirage Dispatcher (matches AI Agent dimensions: w=270, h=52, x=440) -->
|
||||
<rect x="440" y="666" width="270" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="575" y="698" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Mirage Dispatcher</text>
|
||||
|
||||
<!-- Index & File Cache (merged; aligned x with Application & CLI: x=800, w=180) -->
|
||||
<rect x="791" y="666" width="198" height="52" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="890" y="698" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Index & File Cache</text>
|
||||
|
||||
<!-- Dispatcher -> Cache -->
|
||||
<line x1="710" y1="692" x2="791" y2="692" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===================== LAYER 4: BACKEND ===================== -->
|
||||
<rect x="20" y="808" width="1160" height="190" rx="8" fill="#FAF9F6" stroke="#000000" stroke-width="1" stroke-dasharray="6 4"/>
|
||||
<text x="44" y="846" font-size="22" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Infrastructure and Remote</text>
|
||||
<text x="44" y="876" font-size="18" fill="#27272A">
|
||||
<tspan x="44" dy="0">Infrastructure & remote,</tspan>
|
||||
<tspan x="44" dy="24">mounted as one tree</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Infrastructure (renamed from Local Backends; aligned x with Mirage Shell at 380, w=180) -->
|
||||
<rect x="380" y="833" width="180" height="140" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="470" y="861" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Infrastructure</text>
|
||||
<line x1="420" y1="873" x2="520" y2="873" stroke="#E4E4E7" stroke-width="1"/>
|
||||
<text x="470" y="897" text-anchor="middle" font-size="15" fill="#27272A">RAM</text>
|
||||
<text x="470" y="919" text-anchor="middle" font-size="15" fill="#27272A">Disk</text>
|
||||
<text x="470" y="941" text-anchor="middle" font-size="15" fill="#27272A">OPFS</text>
|
||||
<text x="470" y="963" text-anchor="middle" font-size="15" fill="#27272A">Redis</text>
|
||||
|
||||
<!-- Remote Services (widened by 10px each side: x=790, w=200; center stays at 890) -->
|
||||
<rect x="790" y="833" width="200" height="140" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1.2"/>
|
||||
<text x="890" y="861" text-anchor="middle" font-size="19" font-weight="500" fill="#000000" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">Remote Service</text>
|
||||
<line x1="830" y1="873" x2="950" y2="873" stroke="#E4E4E7" stroke-width="1"/>
|
||||
|
||||
<!-- Icon grid: 6 per row, 22x22 colored rect with inlined simple-icons brand SVG -->
|
||||
<g transform="translate(804,883)"><rect width="22" height="22" rx="5" fill="#FF9900"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="m20.913 13.147l.12-.895c.947.576 1.258.922 1.354 1.071c-.16.031-.562.046-1.474-.176m-2.174 7.988l-.005.073c0 .084-.207.405-1.124.768a10 10 0 0 1-1.438.432c-1.405.325-3.128.504-4.853.504c-4.612 0-7.412-1.184-7.412-1.704l-.005-.073L1.81 5.602q.203.117.432.227q.064.03.128.057q.2.093.417.18l.179.069q.232.087.478.168l.13.043q.311.097.646.187l.176.044q.263.066.534.127a23 23 0 0 0 .843.17l.121.023q.378.067.768.122q.107.016.216.03q.299.04.604.077l.24.027q.366.04.74.07l.081.009q.413.033.83.056l.233.012q.316.015.633.025a33 33 0 0 0 2.795-.026l.232-.011q.417-.023.83-.056l.08-.008q.375-.03.742-.072l.238-.026q.307-.036.609-.077l.211-.03q.392-.056.772-.122l.111-.02q.323-.06.634-.125l.212-.047q.279-.062.546-.13l.166-.042q.338-.09.654-.189l.115-.038a11 11 0 0 0 .493-.173q.087-.032.17-.066q.225-.089.43-.185q.059-.025.116-.052q.23-.11.436-.228l-.976 7.245c-2.488-.78-5.805-2.292-7.311-3a1.09 1.09 0 0 0-1.088-1.085c-.6 0-1.088.489-1.088 1.088s.488 1.089 1.088 1.089c.196 0 .378-.056.537-.148c1.72.812 5.144 2.367 7.715 3.15zm-7.42-20.047c5.677 0 9.676 1.759 9.75 2.736l-.014.113c-.01.033-.031.067-.048.101c-.015.028-.026.057-.047.087c-.024.033-.058.068-.09.102c-.028.03-.051.06-.084.09c-.038.035-.087.07-.133.105c-.04.03-.074.06-.119.091c-.053.036-.116.071-.177.107c-.05.03-.095.06-.15.09c-.068.036-.147.073-.222.11c-.059.028-.114.057-.177.085c-.084.038-.177.074-.268.111c-.068.027-.13.054-.203.082c-.097.036-.205.072-.31.107c-.075.026-.148.053-.228.079c-.111.035-.233.069-.35.103c-.085.024-.165.05-.253.073c-.124.034-.258.065-.389.098c-.093.022-.181.046-.278.068c-.139.032-.287.061-.433.091c-.098.02-.191.041-.293.06c-.155.03-.32.057-.482.084c-.1.018-.198.036-.302.052c-.166.026-.342.048-.515.072c-.11.014-.213.03-.325.044c-.181.023-.372.041-.56.06q-.163.019-.332.036c-.188.016-.386.029-.58.043c-.122.009-.24.02-.364.028c-.207.012-.422.02-.635.028c-.12.005-.234.012-.354.016a36 36 0 0 1-2.069 0c-.12-.004-.234-.011-.352-.016c-.214-.008-.43-.016-.637-.028c-.122-.008-.238-.02-.36-.027c-.195-.015-.394-.028-.584-.044c-.11-.01-.215-.024-.324-.035c-.19-.02-.384-.038-.568-.06l-.315-.044c-.176-.024-.355-.046-.525-.073c-.1-.015-.192-.033-.29-.05c-.167-.028-.335-.055-.494-.086c-.096-.018-.183-.038-.276-.056c-.151-.032-.305-.062-.45-.095c-.09-.02-.173-.043-.26-.064c-.138-.034-.277-.067-.407-.102c-.082-.022-.157-.046-.235-.069a12 12 0 0 1-.368-.108c-.075-.024-.141-.049-.213-.073c-.11-.037-.223-.075-.325-.113c-.067-.025-.125-.051-.188-.077c-.096-.038-.195-.076-.282-.115c-.06-.027-.11-.054-.166-.08c-.08-.039-.162-.077-.233-.116c-.052-.028-.094-.055-.142-.084c-.063-.038-.13-.075-.185-.113c-.043-.029-.075-.058-.113-.086c-.048-.037-.098-.073-.139-.11c-.032-.029-.054-.057-.08-.087c-.033-.035-.069-.07-.093-.104c-.02-.03-.031-.058-.046-.086c-.018-.035-.039-.068-.049-.102l-.015-.113c.076-.977 4.074-2.736 9.748-2.736m12.182 12.124c-.118-.628-.84-1.291-2.31-2.128l.963-7.16l.005-.073C22.16 1.581 16.447 0 11.32 0C6.194 0 .482 1.581.482 3.851l.005.072L2.819 21.25c.071 2.002 5.236 2.75 8.5 2.75c1.805 0 3.615-.188 5.098-.531c.598-.138 1.133-.3 1.592-.48c1.18-.467 1.789-1.053 1.813-1.739l.945-7.018c.557.131 1.016.197 1.389.197c.54 0 .902-.137 1.134-.413a.96.96 0 0 0 .21-.804Z"/></svg></g> <!-- S3 -->
|
||||
<g transform="translate(834,883)"><rect width="22" height="22" rx="5" fill="#F38020"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M16.509 16.845c.147-.507.09-.971-.155-1.316c-.225-.316-.605-.499-1.062-.52l-8.66-.113a.16.16 0 0 1-.133-.07a.2.2 0 0 1-.02-.156a.24.24 0 0 1 .203-.156l8.736-.113c1.035-.049 2.16-.886 2.554-1.913l.499-1.302a.27.27 0 0 0 .014-.168a5.689 5.689 0 0 0-10.937-.584a2.58 2.58 0 0 0-1.794-.498a2.56 2.56 0 0 0-2.223 3.18A3.634 3.634 0 0 0 0 16.751q.002.264.035.527a.174.174 0 0 0 .17.148h15.98a.22.22 0 0 0 .204-.155zm2.757-5.564c-.077 0-.161 0-.239.011c-.056 0-.105.042-.127.098l-.337 1.174c-.148.507-.092.971.154 1.317c.225.316.605.498 1.062.52l1.844.113c.056 0 .105.026.133.07a.2.2 0 0 1 .021.156a.24.24 0 0 1-.204.156l-1.92.112c-1.042.049-2.159.887-2.553 1.914l-.141.358c-.028.072.021.142.099.142h6.597a.174.174 0 0 0 .17-.126a5 5 0 0 0 .175-1.28a4.74 4.74 0 0 0-4.734-4.727"/></svg></g> <!-- R2 (Cloudflare) -->
|
||||
<g transform="translate(864,883)"><rect width="22" height="22" rx="5" fill="#C74634"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M16.412 4.412h-8.82a7.588 7.588 0 0 0-.008 15.176h8.828a7.588 7.588 0 0 0 0-15.176m-.193 12.502H7.786a4.915 4.915 0 0 1 0-9.828h8.433a4.914 4.914 0 1 1 0 9.828"/></svg></g> <!-- OCI -->
|
||||
<g transform="translate(894,883)"><rect width="22" height="22" rx="5" fill="#4285F4"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M12.19 2.38a9.344 9.344 0 0 0-9.234 6.893c.053-.02-.055.013 0 0c-3.875 2.551-3.922 8.11-.247 10.941l.006-.007l-.007.03a6.7 6.7 0 0 0 4.077 1.356h5.173l.03.03h5.192c6.687.053 9.376-8.605 3.835-12.35a9.37 9.37 0 0 0-2.821-4.552l-.043.043l.006-.05A9.34 9.34 0 0 0 12.19 2.38m-.358 4.146c1.244-.04 2.518.368 3.486 1.15a5.19 5.19 0 0 1 1.862 4.078v.518c3.53-.07 3.53 5.262 0 5.193h-5.193l-.008.009v-.04H6.785a2.6 2.6 0 0 1-1.067-.23h.001a2.597 2.597 0 1 1 3.437-3.437l3.013-3.012A6.75 6.75 0 0 0 8.11 8.24c.018-.01.04-.026.054-.023a5.2 5.2 0 0 1 3.67-1.69z"/></svg></g> <!-- GCS -->
|
||||
<g transform="translate(924,883)"><rect width="22" height="22" rx="5" fill="#3FCF8E"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M11.9 1.036c-.015-.986-1.26-1.41-1.874-.637L.764 12.05C-.33 13.427.65 15.455 2.409 15.455h9.579l.113 7.51c.014.985 1.259 1.408 1.873.636l9.262-11.653c1.093-1.375.113-3.403-1.645-3.403h-9.642z"/></svg></g> <!-- Supabase -->
|
||||
<g transform="translate(954,883)"><rect width="22" height="22" rx="5" fill="#EA4335"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M24 5.457v13.909c0 .904-.732 1.636-1.636 1.636h-3.819V11.73L12 16.64l-6.545-4.91v9.273H1.636A1.636 1.636 0 0 1 0 19.366V5.457c0-2.023 2.309-3.178 3.927-1.964L5.455 4.64L12 9.548l6.545-4.91l1.528-1.145C21.69 2.28 24 3.434 24 5.457"/></svg></g> <!-- Gmail -->
|
||||
<g transform="translate(804,911)"><rect width="22" height="22" rx="5" fill="#1FA463"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M12.01 1.485c-2.082 0-3.754.02-3.743.047c.01.02 1.708 3.001 3.774 6.62l3.76 6.574h3.76c2.081 0 3.753-.02 3.742-.047c-.005-.02-1.708-3.001-3.775-6.62l-3.76-6.574zm-4.76 1.73a789.828 789.861 0 0 0-3.63 6.319L0 15.868l1.89 3.298l1.885 3.297l3.62-6.335l3.618-6.33l-1.88-3.287C8.1 4.704 7.255 3.22 7.25 3.214zm2.259 12.653l-.203.348c-.114.198-.96 1.672-1.88 3.287a423.93 423.948 0 0 1-1.698 2.97c-.01.026 3.24.042 7.222.042h7.244l1.796-3.157c.992-1.734 1.85-3.23 1.906-3.323l.104-.167h-7.249z"/></svg></g> <!-- GDrive -->
|
||||
<g transform="translate(834,911)"><rect width="22" height="22" rx="5" fill="#4285F4"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M14.727 6.727H14V0H4.91c-.905 0-1.637.732-1.637 1.636v20.728c0 .904.732 1.636 1.636 1.636h14.182c.904 0 1.636-.732 1.636-1.636V6.727zm-.545 10.455H7.09v-1.364h7.09v1.364zm2.727-3.273H7.091v-1.364h9.818zm0-3.273H7.091V9.273h9.818zM14.727 6h6l-6-6z"/></svg></g> <!-- GDocs -->
|
||||
<g transform="translate(864,911)"><rect width="22" height="22" rx="5" fill="#34A853"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M11.318 12.545H7.91v-1.909h3.41v1.91zM14.728 0v6h6zm1.363 10.636h-3.41v1.91h3.41zm0 3.273h-3.41v1.91h3.41zM20.727 6.5v15.864c0 .904-.732 1.636-1.636 1.636H4.909a1.636 1.636 0 0 1-1.636-1.636V1.636C3.273.732 4.005 0 4.909 0h9.318v6.5zm-3.273 2.773H6.545v7.909h10.91v-7.91zm-6.136 4.636H7.91v1.91h3.41v-1.91z"/></svg></g> <!-- GSheets -->
|
||||
<g transform="translate(894,911)"><rect width="22" height="22" rx="5" fill="#FBBC04"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M16.09 15.273H7.91v-4.637h8.18zm1.728-8.523h2.91v15.614c0 .904-.733 1.636-1.637 1.636H4.909a1.636 1.636 0 0 1-1.636-1.636V1.636C3.273.732 4.005 0 4.909 0h9.068v6.75zm-.363 2.523H6.545v7.363h10.91zm-2.728-5.979V6h6.001l-6-6v3.294z"/></svg></g> <!-- GSlides -->
|
||||
<g transform="translate(924,911)"><rect width="22" height="22" rx="5" fill="#4A154B"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M5.042 15.165a2.53 2.53 0 0 1-2.52 2.523A2.53 2.53 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52a2.527 2.527 0 0 1 2.521 2.52v6.313A2.53 2.53 0 0 1 8.834 24a2.53 2.53 0 0 1-2.521-2.522zM8.834 5.042a2.53 2.53 0 0 1-2.521-2.52A2.53 2.53 0 0 1 8.834 0a2.53 2.53 0 0 1 2.521 2.522v2.52zm0 1.271a2.53 2.53 0 0 1 2.521 2.521a2.53 2.53 0 0 1-2.521 2.521H2.522A2.53 2.53 0 0 1 0 8.834a2.53 2.53 0 0 1 2.522-2.521zm10.122 2.521a2.53 2.53 0 0 1 2.522-2.521A2.53 2.53 0 0 1 24 8.834a2.53 2.53 0 0 1-2.522 2.521h-2.522zm-1.268 0a2.53 2.53 0 0 1-2.523 2.521a2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.53 2.53 0 0 1 2.523 2.522zm-2.523 10.122a2.53 2.53 0 0 1 2.523 2.522A2.53 2.53 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522zm0-1.268a2.527 2.527 0 0 1-2.52-2.523a2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.53 2.53 0 0 1-2.522 2.523z"/></svg></g> <!-- Slack -->
|
||||
<g transform="translate(954,911)"><rect width="22" height="22" rx="5" fill="#5865F2"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M20.317 4.37a19.8 19.8 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.3 18.3 0 0 0-5.487 0a13 13 0 0 0-.617-1.25a.08.08 0 0 0-.079-.037A19.7 19.7 0 0 0 3.677 4.37a.1.1 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.08.08 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.08.08 0 0 0 .084-.028a14 14 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13 13 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10 10 0 0 0 .372-.292a.07.07 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.07.07 0 0 1 .078.01q.181.149.373.292a.077.077 0 0 1-.006.127a12.3 12.3 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.08.08 0 0 0 .084.028a19.8 19.8 0 0 0 6.002-3.03a.08.08 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.06.06 0 0 0-.031-.03M8.02 15.33c-1.182 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418m7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418"/></svg></g> <!-- Discord -->
|
||||
<g transform="translate(804,939)"><rect width="22" height="22" rx="5" fill="#26A5E4"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12a12 12 0 0 0 12-12A12 12 0 0 0 12 0zm4.962 7.224c.1-.002.321.023.465.14a.5.5 0 0 1 .171.325c.016.093.036.306.02.472c-.18 1.898-.962 6.502-1.36 8.627c-.168.9-.499 1.201-.82 1.23c-.696.065-1.225-.46-1.9-.902c-1.056-.693-1.653-1.124-2.678-1.8c-1.185-.78-.417-1.21.258-1.91c.177-.184 3.247-2.977 3.307-3.23c.007-.032.014-.15-.056-.212s-.174-.041-.249-.024q-.159.037-5.061 3.345q-.72.495-1.302.48c-.428-.008-1.252-.241-1.865-.44c-.752-.245-1.349-.374-1.297-.789q.04-.324.893-.663q5.247-2.286 6.998-3.014c3.332-1.386 4.025-1.627 4.476-1.635"/></svg></g> <!-- Telegram -->
|
||||
<g transform="translate(834,939)"><rect width="22" height="22" rx="5" fill="#181717"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></g> <!-- GitHub -->
|
||||
<g transform="translate(864,939)"><rect width="22" height="22" rx="5" fill="#5E6AD2"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M2.886 4.18A11.98 11.98 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556q-.787.496-1.65.866L.951 7.277q.371-.863.866-1.65ZM.322 9.163l14.515 14.515q-1.066.26-2.195.322L0 11.358a12 12 0 0 1 .322-2.195m-.17 4.862l9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z"/></svg></g> <!-- Linear -->
|
||||
<g transform="translate(894,939)"><rect width="22" height="22" rx="5" fill="#000000"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.981-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.841-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.748-.887l-15.177.887c-.56.047-.747.327-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514c-.748 0-.935-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233l4.764 7.279v-6.44l-1.215-.139c-.093-.514.28-.887.747-.933zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.934.653.934 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.447-1.632"/></svg></g> <!-- Notion -->
|
||||
<g transform="translate(924,939)"><rect width="22" height="22" rx="5" fill="#47A248"/><svg x="3" y="3" width="16" height="16" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M17.193 9.555c-1.264-5.58-4.252-7.414-4.573-8.115c-.28-.394-.53-.954-.735-1.44c-.036.495-.055.685-.523 1.184c-.723.566-4.438 3.682-4.74 10.02c-.282 5.912 4.27 9.435 4.888 9.884l.07.05A74 74 0 0 1 11.91 24h.481a29 29 0 0 1 .51-3.07c.417-.296.604-.463.85-.693a11.34 11.34 0 0 0 3.639-8.464c.01-.814-.103-1.662-.197-2.218m-5.336 8.195s0-8.291.275-8.29c.213 0 .49 10.695.49 10.695c-.381-.045-.765-1.76-.765-2.405"/></svg></g> <!-- MongoDB -->
|
||||
<text x="965" y="957" text-anchor="middle" font-size="16" font-weight="700" fill="#27272A" font-family="Avenir Next, Helvetica Neue, Arial, sans-serif">…</text>
|
||||
|
||||
<!-- ===================== CROSS-LAYER ARROWS ===================== -->
|
||||
<!-- AI Agent -> Mirage Shell (vertical) -->
|
||||
<line x1="470" y1="262" x2="470" y2="409" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="176" y="282" width="282" height="28" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1"/>
|
||||
<text x="450" y="302" text-anchor="end" font-size="18" font-family="'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace" fill="#27272A">grep alert /s3/log.jsonl</text>
|
||||
|
||||
<!-- AI Agent -> Mirage VFS (vertical) -->
|
||||
<line x1="680" y1="262" x2="680" y2="409" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="490" y="282" width="180" height="28" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1"/>
|
||||
<text x="662" y="302" text-anchor="end" font-size="18" font-family="'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace" fill="#27272A">stat /paper.pdf</text>
|
||||
|
||||
<!-- Application & CLI -> FUSE (vertical) -->
|
||||
<line x1="890" y1="262" x2="890" y2="409" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="902" y="282" width="200" height="28" rx="4" fill="#FFFFFF" stroke="#000000" stroke-width="1"/>
|
||||
<text x="910" y="302" font-size="18" font-family="'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace" fill="#27272A">read /r2/data.csv</text>
|
||||
|
||||
<!-- Command Registry -> Dispatcher (vertical) -->
|
||||
<line x1="470" y1="543" x2="470" y2="666" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- VFS Registry -> Dispatcher (vertical) -->
|
||||
<line x1="680" y1="543" x2="680" y2="666" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dispatcher -> Infrastructure (vertical, aligned with CR -> Dispatcher connector at x=470) -->
|
||||
<line x1="470" y1="718" x2="470" y2="833" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dispatcher -> Remote Services (start x=680 matches PR -> Dispatcher; end x=890 matches Cache -> RS) -->
|
||||
<line x1="680" y1="718" x2="890" y2="833" stroke="#27272A" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Cache - Remote Services (cache-hit short-circuit; dotted, no arrowhead) -->
|
||||
<line x1="890" y1="718" x2="890" y2="833" stroke="#27272A" stroke-width="1.2" stroke-dasharray="4 3"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="16 7 128 146" width="128" height="146">
|
||||
<polygon points="19,45 80,10 80,45 50,63" fill="#ffffff" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="80,10 141,45 110,63 80,45" fill="#ffffff" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="141,45 141,115 110,98 110,63" fill="#ffffff" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="141,115 80,150 80,115 110,98" fill="#ffffff" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="80,150 19,115 50,98 80,115" fill="#ffffff" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="19,115 19,45 50,63 50,98" fill="#ffffff" stroke="#000000" stroke-width="2.5"/>
|
||||
<polygon points="80,45 110,63 110,98 80,115 50,98 50,63" fill="#ffffff" stroke="#000000" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="50" y2="63" stroke="#000000" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="110" y2="63" stroke="#000000" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="80" y2="115" stroke="#000000" stroke-width="2.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="16 7 128 146" width="128" height="146">
|
||||
<polygon points="19,45 80,10 80,45 50,63" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="80,10 141,45 110,63 80,45" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="141,45 141,115 110,98 110,63" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="141,115 80,150 80,115 110,98" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="80,150 19,115 50,98 80,115" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="19,115 19,45 50,63 50,98" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<polygon points="80,45 110,63 110,98 80,115 50,98 50,63" fill="#000000" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="50" y2="63" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="110" y2="63" stroke="#ffffff" stroke-width="2.5"/>
|
||||
<line x1="80" y1="80" x2="80" y2="115" stroke="#ffffff" stroke-width="2.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160" width="48" height="48">
|
||||
<rect width="160" height="160" rx="24" fill="#000000"/>
|
||||
<polygon points="19,45 80,10 80,45 50,63" fill="#ffffff" stroke="#000000" stroke-width="1"/>
|
||||
<polygon points="80,10 141,45 110,63 80,45" fill="#ffffff" stroke="#000000" stroke-width="1"/>
|
||||
<polygon points="141,45 141,115 110,98 110,63" fill="#ffffff" stroke="#000000" stroke-width="1"/>
|
||||
<polygon points="141,115 80,150 80,115 110,98" fill="#ffffff" stroke="#000000" stroke-width="1"/>
|
||||
<polygon points="80,150 19,115 50,98 80,115" fill="#ffffff" stroke="#000000" stroke-width="1"/>
|
||||
<polygon points="19,115 19,45 50,63 50,98" fill="#ffffff" stroke="#000000" stroke-width="1"/>
|
||||
<polygon points="80,45 110,63 110,98 80,115 50,98 50,63" fill="#ffffff" stroke="#000000" stroke-width="1"/>
|
||||
<line x1="80" y1="80" x2="50" y2="63" stroke="#000000" stroke-width="1"/>
|
||||
<line x1="80" y1="80" x2="110" y2="63" stroke="#000000" stroke-width="1"/>
|
||||
<line x1="80" y1="80" x2="80" y2="115" stroke="#000000" stroke-width="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,58 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="-2.437 31.892 555.485 197.108"
|
||||
width="549.485" height="197.108">
|
||||
|
||||
<rect x="0.563" y="27.892" width="549.485" height="201.108" fill="#0E0E10"/>
|
||||
|
||||
<g transform="translate(31 26)">
|
||||
<g transform="translate(80 80) scale(0.6428571429) translate(-80 -80)">
|
||||
<polygon points="19,45 80,10 80,45 50,63"
|
||||
fill="#ffffff" stroke="#000000" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="80,10 141,45 110,63 80,45"
|
||||
fill="#ffffff" stroke="#000000" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="141,45 141,115 110,98 110,63"
|
||||
fill="#ffffff" stroke="#000000" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="141,115 80,150 80,115 110,98"
|
||||
fill="#ffffff" stroke="#000000" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="80,150 19,115 50,98 80,115"
|
||||
fill="#ffffff" stroke="#000000" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="19,115 19,45 50,63 50,98"
|
||||
fill="#ffffff" stroke="#000000" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
|
||||
<polygon points="80,45 110,63 110,98 80,115 50,98 50,63"
|
||||
fill="#ffffff" stroke="#000000" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
|
||||
<line x1="80" y1="80" x2="50" y2="63"
|
||||
stroke="#000000" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<line x1="80" y1="80" x2="110" y2="63"
|
||||
stroke="#000000" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<line x1="80" y1="80" x2="80" y2="115"
|
||||
stroke="#000000" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<text x="174" y="106"
|
||||
fill="#ffffff"
|
||||
font-family="Avenir Next, Helvetica Neue, Arial, sans-serif"
|
||||
font-size="90"
|
||||
font-weight="700"
|
||||
letter-spacing="-1"
|
||||
dominant-baseline="middle">mirage</text>
|
||||
|
||||
<text x="275.5" y="178"
|
||||
fill="#ffffff"
|
||||
font-family="Avenir Next, Helvetica Neue, Arial, sans-serif"
|
||||
font-size="16"
|
||||
font-weight="500"
|
||||
letter-spacing="0.4"
|
||||
text-anchor="middle">A Unified Virtual Filesystem for AI Agents</text>
|
||||
|
||||
<text x="275.5" y="205"
|
||||
fill="#ffffff"
|
||||
font-family="Avenir Next, Helvetica Neue, Arial, sans-serif"
|
||||
font-size="14"
|
||||
font-weight="400"
|
||||
letter-spacing="0.2"
|
||||
text-anchor="middle">It seems real, but it isn't; it's a mirage.</text>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,58 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="-2.437 31.892 555.485 197.108"
|
||||
width="549.485" height="197.108">
|
||||
|
||||
<rect x="0.563" y="27.892" width="549.485" height="201.108" fill="#ffffff"/>
|
||||
|
||||
<g transform="translate(31 26)">
|
||||
<g transform="translate(80 80) scale(0.6428571429) translate(-80 -80)">
|
||||
<polygon points="19,45 80,10 80,45 50,63"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="80,10 141,45 110,63 80,45"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="141,45 141,115 110,98 110,63"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="141,115 80,150 80,115 110,98"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="80,150 19,115 50,98 80,115"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="19,115 19,45 50,63 50,98"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
|
||||
<polygon points="80,45 110,63 110,98 80,115 50,98 50,63"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
|
||||
<line x1="80" y1="80" x2="50" y2="63"
|
||||
stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<line x1="80" y1="80" x2="110" y2="63"
|
||||
stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<line x1="80" y1="80" x2="80" y2="115"
|
||||
stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<text x="174" y="106"
|
||||
fill="#000000"
|
||||
font-family="Avenir Next, Helvetica Neue, Arial, sans-serif"
|
||||
font-size="90"
|
||||
font-weight="700"
|
||||
letter-spacing="-1"
|
||||
dominant-baseline="middle">mirage</text>
|
||||
|
||||
<text x="275.5" y="178"
|
||||
fill="#000000"
|
||||
font-family="Avenir Next, Helvetica Neue, Arial, sans-serif"
|
||||
font-size="16"
|
||||
font-weight="500"
|
||||
letter-spacing="0.4"
|
||||
text-anchor="middle">A Unified Virtual Filesystem for AI Agents</text>
|
||||
|
||||
<text x="275.5" y="205"
|
||||
fill="#000000"
|
||||
font-family="Avenir Next, Helvetica Neue, Arial, sans-serif"
|
||||
font-size="14"
|
||||
font-weight="400"
|
||||
letter-spacing="0.2"
|
||||
text-anchor="middle">It seems real, but it isn't; it's a mirage.</text>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,58 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="-2.437 31.892 555.485 197.108"
|
||||
width="549.485" height="197.108">
|
||||
|
||||
<rect x="0.563" y="27.892" width="549.485" height="201.108" fill="#ffffff"/>
|
||||
|
||||
<g transform="translate(31 26)">
|
||||
<g transform="translate(80 80) scale(0.6428571429) translate(-80 -80)">
|
||||
<polygon points="19,45 80,10 80,45 50,63"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="80,10 141,45 110,63 80,45"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="141,45 141,115 110,98 110,63"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="141,115 80,150 80,115 110,98"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="80,150 19,115 50,98 80,115"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<polygon points="19,115 19,45 50,63 50,98"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
|
||||
<polygon points="80,45 110,63 110,98 80,115 50,98 50,63"
|
||||
fill="#000000" stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
|
||||
<line x1="80" y1="80" x2="50" y2="63"
|
||||
stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<line x1="80" y1="80" x2="110" y2="63"
|
||||
stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
<line x1="80" y1="80" x2="80" y2="115"
|
||||
stroke="#ffffff" stroke-width="3" vector-effect="non-scaling-stroke"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<text x="174" y="106"
|
||||
fill="#000000"
|
||||
font-family="Avenir Next, Helvetica Neue, Arial, sans-serif"
|
||||
font-size="90"
|
||||
font-weight="700"
|
||||
letter-spacing="-1"
|
||||
dominant-baseline="middle">mirage</text>
|
||||
|
||||
<text x="275.5" y="178"
|
||||
fill="#000000"
|
||||
font-family="Avenir Next, Helvetica Neue, Arial, sans-serif"
|
||||
font-size="16"
|
||||
font-weight="500"
|
||||
letter-spacing="0.4"
|
||||
text-anchor="middle">A Unified Virtual Filesystem for AI Agents</text>
|
||||
|
||||
<text x="275.5" y="205"
|
||||
fill="#000000"
|
||||
font-family="Avenir Next, Helvetica Neue, Arial, sans-serif"
|
||||
font-size="12"
|
||||
font-weight="400"
|
||||
letter-spacing="0.2"
|
||||
text-anchor="middle">It seems real, but it isn't; it's a mirage.</text>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>OpenAI</title><path fill="#9CA3AF" d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg role="img" viewBox="0 -24 148 148" xmlns="http://www.w3.org/2000/svg"><title>OpenHands</title>
|
||||
<path d="M71.7542 16.863V2.97414C71.7542 1.82355 72.6872 0.890503 73.8378 0.890503C74.9884 0.890503 75.9214 1.82355 75.9214 2.97414V16.863C75.9214 18.0136 74.9884 18.9466 73.8378 18.9466C72.6872 18.9466 71.7542 18.0136 71.7542 16.863Z" fill="#9CA3AF"/>
|
||||
<path d="M82.5272 18.9329L89.4716 6.90477C90.0469 5.90832 91.3215 5.5668 92.3179 6.1421C93.3144 6.7174 93.6559 7.99197 93.0806 8.98841L86.1362 21.0165C85.5609 22.0129 84.2863 22.3545 83.2899 21.7792C82.2934 21.2039 81.9519 19.9293 82.5272 18.9329Z" fill="#9CA3AF"/>
|
||||
<path d="M65.1481 18.9329L58.2037 6.90477C57.6284 5.90832 56.3538 5.5668 55.3574 6.1421C54.3609 6.7174 54.0194 7.99197 54.5947 8.98841L61.5391 21.0165C62.1144 22.0129 63.389 22.3545 64.3854 21.7792C65.3819 21.2039 65.7234 19.9293 65.1481 18.9329Z" fill="#9CA3AF"/>
|
||||
<path d="M140.606 62.0292C140.606 58.409 141.583 47.6748 141.89 44.1323C142.097 41.7374 141.809 40.4247 141.424 39.7542C141.141 39.2626 140.699 38.915 139.634 38.8436C138.865 38.7921 138.027 39.0114 137.401 39.5761C136.814 40.1052 136.159 41.1682 136.159 43.3176L136.155 43.4388L135.198 59.758C135.164 60.3451 134.883 60.8911 134.424 61.2599C133.966 61.6284 133.374 61.7859 132.793 61.6941L122.764 60.1068L111.948 58.6703C110.949 58.5376 110.188 57.7084 110.142 56.7016L109.561 44.1323C109.535 43.621 109.51 43.1141 109.484 42.6146C109.241 37.9294 109.022 33.7805 109.022 32.4282C109.022 28.3859 108.338 26.6806 107.74 25.9634C107.263 25.3915 106.577 25.1402 105.11 25.1402C104.583 25.1402 104.212 25.2481 103.933 25.4111C103.659 25.5714 103.346 25.8587 103.049 26.4208C102.41 27.6257 101.945 29.891 102.118 33.8479C102.342 38.9804 102.692 42.8146 103.035 46.2718C103.377 49.7231 103.718 52.8561 103.908 56.4971C104.204 62.1966 104.178 66.1256 103.945 68.7924C103.828 70.124 103.656 71.1996 103.423 72.0501C103.202 72.8558 102.871 73.6757 102.296 74.2887C101.6 75.0303 100.608 75.3844 99.577 75.136C98.7592 74.9389 98.1847 74.4215 97.8706 74.0916C97.2141 73.4017 96.7501 72.5106 96.568 72.0512C95.5097 69.3812 92.2352 63.1808 87.8023 59.6811C86.5089 58.6599 85.5666 58.3652 84.9736 58.3204C84.4148 58.2783 84.0094 58.4436 83.6909 58.6967C83.34 58.9756 83.0781 59.3811 82.9479 59.7643C82.9019 59.8999 82.8823 59.9968 82.8741 60.0584C84.0759 62.0865 88.8421 69.5222 91.0896 77.069C92.7648 82.6941 96.8038 88.4259 99.8194 90.8809C102.74 93.258 107.988 94.7313 113.9 95.0218C119.756 95.3095 125.788 94.4121 130.033 92.5092C138.233 88.8334 139.903 80.7382 140.651 77.2292C141.232 74.5057 141.243 71.5987 141.087 68.9009C141.01 67.5551 140.894 66.2969 140.793 65.1373C140.695 64.0105 140.606 62.9215 140.606 62.0292ZM120.986 27.0953C120.986 25.8314 120.648 24.7049 120.089 23.9514C119.583 23.27 118.84 22.7987 117.646 22.7984C116.668 22.7982 116.011 22.9187 115.546 23.1167C115.13 23.2943 114.781 23.5699 114.463 24.0831C113.73 25.2671 113.192 27.6455 113.189 32.384L113.721 43.9088C113.91 47.5661 114.106 51.4922 114.235 54.7707L120.986 55.6666V27.0953ZM125.153 56.2652L131.172 57.218L131.992 43.267V32.5083C131.992 31.031 131.39 30.1275 130.678 29.5489C129.884 28.9039 128.957 28.6731 128.519 28.6731C127.722 28.6731 126.899 28.797 126.306 29.2179C125.849 29.5421 125.153 30.3087 125.153 32.5083V56.2652ZM136.159 35.4278C137.406 34.8069 138.74 34.6083 139.912 34.6868C142.037 34.8292 143.91 35.718 145.037 37.6779C146.06 39.4592 146.273 41.8136 146.041 44.4927C145.72 48.1949 144.772 58.6457 144.772 62.0292C144.772 62.708 144.843 63.6116 144.944 64.7758C145.042 65.907 145.165 67.2389 145.247 68.6606C145.411 71.4987 145.422 74.8383 144.727 78.0987C144.002 81.4953 142.041 91.6918 131.738 96.3108C126.731 98.5551 120.002 99.4936 113.696 99.1838C107.445 98.8767 101.128 97.3189 97.1887 94.1122C93.4809 91.0937 88.9938 84.6307 87.0962 78.2589C84.9529 71.0619 80.3109 63.9646 79.1527 61.9533C78.4706 60.7689 78.684 59.3628 79.0019 58.4258C79.3607 57.3688 80.0554 56.2631 81.0993 55.4337C82.1758 54.5784 83.6043 54.0377 85.2876 54.1647C86.9369 54.2893 88.6462 55.0393 90.3834 56.4107C94.8541 59.9401 98.1342 65.5082 99.7424 68.9231C99.759 68.7664 99.779 68.6024 99.7941 68.4298C100.003 66.0435 100.039 62.3344 99.7467 56.7132C99.5635 53.1942 99.2356 50.1809 98.8888 46.6828C98.5425 43.1904 98.184 39.2713 97.955 34.0302C97.7722 29.8481 98.2012 26.6722 99.3672 24.471C99.9716 23.3302 100.79 22.4223 101.83 21.814C102.866 21.2087 103.995 20.974 105.11 20.974C106.759 20.974 108.813 21.2062 110.448 22.7678C110.593 22.4576 110.75 22.1652 110.921 21.8899C111.676 20.6698 112.681 19.8084 113.912 19.2835C115.095 18.7791 116.378 18.6309 117.646 18.6311C120.195 18.6315 122.164 19.7567 123.434 21.4683C124.256 22.576 124.75 23.8775 124.985 25.1982C126.338 24.5876 127.691 24.5068 128.519 24.5068C129.933 24.5068 131.784 25.0791 133.305 26.3154C134.908 27.6179 136.159 29.6733 136.159 32.5083V35.4278Z" fill="#9CA3AF"/>
|
||||
<path d="M7.15661 62.0292C7.15661 58.409 6.17994 47.6748 5.87291 44.1323C5.6654 41.7374 5.95357 40.4247 6.33875 39.7542C6.62116 39.2626 7.06336 38.915 8.12834 38.8436C8.89759 38.7921 9.73544 39.0114 10.3616 39.5761C10.9484 40.1052 11.6032 41.1682 11.6032 43.3176L11.6074 43.4388L12.5644 59.758C12.5988 60.3451 12.8798 60.8911 13.338 61.2599C13.7961 61.6284 14.3887 61.7859 14.9695 61.6941L24.9988 60.1068L35.8143 58.6703C36.8135 58.5376 37.5741 57.7084 37.6208 56.7016L38.2015 44.1323C38.2279 43.621 38.2525 43.1141 38.2784 42.6146C38.5218 37.9294 38.7401 33.7805 38.7401 32.4282C38.7401 28.3859 39.4246 26.6806 40.0227 25.9634C40.4996 25.3915 41.185 25.1402 42.6523 25.1402C43.1794 25.1402 43.5505 25.2481 43.8295 25.4111C44.1038 25.5714 44.416 25.8587 44.7138 26.4208C45.3521 27.6257 45.8173 29.891 45.6444 33.8479C45.4201 38.9804 45.0703 42.8146 44.7275 46.2718C44.3853 49.7231 44.0443 52.8561 43.8548 56.4971C43.5582 62.1966 43.5847 66.1256 43.8179 68.7924C43.9344 70.124 44.1069 71.1996 44.3396 72.0501C44.5601 72.8558 44.891 73.6757 45.4663 74.2887C46.1625 75.0303 47.1546 75.3844 48.1855 75.136C49.0033 74.9389 49.5778 74.4215 49.8918 74.0916C50.5484 73.4017 51.0123 72.5106 51.1945 72.0512C52.2527 69.3812 55.5272 63.1808 59.9601 59.6811C61.2536 58.6599 62.1958 58.3652 62.7889 58.3204C63.3476 58.2783 63.753 58.4436 64.0715 58.6967C64.4225 58.9756 64.6844 59.3811 64.8146 59.7643C64.8606 59.8999 64.8801 59.9968 64.8883 60.0584C63.6866 62.0865 58.9204 69.5222 56.6729 77.069C54.9977 82.6941 50.9586 88.4259 47.9431 90.8809C45.0229 93.258 39.7747 94.7313 33.8624 95.0218C28.0068 95.3095 21.9748 94.4121 17.7297 92.5092C9.52988 88.8334 7.85961 80.7382 7.11129 77.2292C6.53054 74.5057 6.5195 71.5987 6.67496 68.9009C6.75251 67.5551 6.86809 66.2969 6.96901 65.1373C7.06707 64.0105 7.1566 62.9215 7.15661 62.0292ZM26.7768 27.0953C26.7768 25.8314 27.1147 24.7049 27.6737 23.9514C28.1792 23.27 28.9221 22.7987 30.1167 22.7984C31.0942 22.7982 31.7518 22.9187 32.2162 23.1167C32.6326 23.2943 32.9817 23.5699 33.2996 24.0831C34.0328 25.2671 34.5705 27.6455 34.5738 32.384L34.0416 43.9088C33.8524 47.5661 33.6565 51.4922 33.5273 54.7707L26.7768 55.6666V27.0953ZM22.6095 56.2652L16.5904 57.218L15.7705 43.267V32.5083C15.7705 31.031 16.3726 30.1275 17.0847 29.5489C17.8785 28.9039 18.8058 28.6731 19.2432 28.6731C20.0404 28.6731 20.8634 28.797 21.4565 29.2179C21.9131 29.5421 22.6095 30.3087 22.6095 32.5083V56.2652ZM11.6032 35.4278C10.3568 34.8069 9.02265 34.6083 7.8501 34.6868C5.72541 34.8292 3.85197 35.718 2.72584 37.6779C1.70247 39.4592 1.48924 41.8136 1.72143 44.4927C2.0423 48.1949 2.99038 58.6457 2.99038 62.0292C2.99037 62.708 2.91991 63.6116 2.81859 64.7758C2.72014 65.907 2.59699 67.2389 2.51505 68.6606C2.3515 71.4987 2.34041 74.8383 3.0357 78.0987C3.76005 81.4953 5.72154 91.6918 16.0245 96.3108C21.0311 98.5551 27.7601 99.4936 34.0669 99.1838C40.3172 98.8767 46.6346 97.3189 50.5737 94.1122C54.2816 91.0937 58.7686 84.6307 60.6662 78.2589C62.8095 71.0619 67.4515 63.9646 68.6098 61.9533C69.2919 60.7689 69.0785 59.3628 68.7605 58.4258C68.4018 57.3688 67.707 56.2631 66.6632 55.4337C65.5867 54.5784 64.1582 54.0377 62.4748 54.1647C60.8256 54.2893 59.1162 55.0393 57.379 56.4107C52.9083 59.9401 49.6283 65.5082 48.02 68.9231C48.0034 68.7664 47.9835 68.6024 47.9684 68.4298C47.7597 66.0435 47.7232 62.3344 48.0158 56.7132C48.1989 53.1942 48.5269 50.1809 48.8737 46.6828C49.22 43.1904 49.5784 39.2713 49.8075 34.0302C49.9903 29.8481 49.5612 26.6722 48.3952 24.471C47.7909 23.3302 46.9729 22.4223 45.9321 21.814C44.8964 21.2087 43.7676 20.974 42.6523 20.974C41.0038 20.974 38.9497 21.2062 37.3141 22.7678C37.1698 22.4576 37.0124 22.1652 36.8419 21.8899C36.0863 20.6698 35.0817 19.8084 33.8508 19.2835C32.6679 18.7791 31.3849 18.6309 30.1167 18.6311C27.5677 18.6315 25.5986 19.7567 24.3285 21.4683C23.5066 22.576 23.0121 23.8775 22.7771 25.1982C21.4247 24.5876 20.0718 24.5068 19.2432 24.5068C17.8298 24.5068 15.9788 25.0791 14.4573 26.3154C12.8542 27.6179 11.6032 29.6733 11.6032 32.5083V35.4278Z" fill="#9CA3AF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.7 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><text x="128" y="200" font-family="Georgia, 'Times New Roman', serif" font-size="240" font-style="italic" font-weight="600" text-anchor="middle" fill="#9CA3AF">π</text></svg>
|
||||
|
After Width: | Height: | Size: 238 B |
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Pydantic</title><path fill="#E92063" d="m23.826 17.316-4.23-5.866-6.847-9.496c-.348-.48-1.151-.48-1.497 0l-6.845 9.494-4.233 5.868a.925.925 0 0 0 .46 1.417l11.078 3.626h.002a.92.92 0 0 0 .572 0h.002l11.077-3.626c.28-.092.5-.31.59-.592a.916.916 0 0 0-.13-.825h.002ZM12.001 4.07l4.44 6.158-4.152-1.36c-.032-.01-.066-.008-.098-.016a.8.8 0 0 0-.096-.016c-.032-.004-.062-.016-.094-.016s-.062.012-.094.016a.74.74 0 0 0-.096.016c-.032.006-.066.006-.096.016L7.59 10.221l-.026.008 4.44-6.158h-.002Zm-6.273 8.7 4.834-1.583.516-.168v9.19L2.41 17.372l3.317-4.6Zm7.197 7.437V11.02l5.35 1.752 3.316 4.598-8.666 2.838Z"/></svg>
|
||||
|
After Width: | Height: | Size: 691 B |