Mirage 是给 AI 智能体用的虚拟终端:一层 shell,统管数据、命令行和运行时。Unix…
70.7/100
综合分
3,762
GitHub 星标
370
npm 周下载
—/5
用户评分
数据截至 2026/11/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
自动兼容检查未通过:engines.node 要求 ^22.20 || ^24.12 || >=25,不满足 Node 22.19.0;安装清单取自 typescript/packages/dsh/package.json。以下命令仍可尝试,但可能失败。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add strukto-ai/mirage该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:需留意
是什么
dsh 原生插件 · security
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 0 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装engines.node 要求 ^22.20 || ^24.12 || >=25,不满足 Node 22.19.0;安装清单取自 typescript/packages/dsh/package.json
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包@struktoai/mirage-dsh @ 0.0.7
✗Node 引擎要求 ^22.20 || ^24.12 || >=25 · 基线 Node 22.19 不满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/2 02:35:20
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 127 处,展示前 97 条)
- 写入本地文件integ/access/inapp.ts:83
writeFileSync(path, JSON.stringify(config)) - 写入本地文件integ/access/serve.ts:44
writeFileSync(lagFile, JSON.stringify(lag.stop())) - 发起网络请求integ/airtable_atlas.ts:76
const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` } }) - 读取本地文件integ/airtable_atlas.ts:98
const corpus = JSON.parse(readFileSync(CORPUS, 'utf8')) as Corpus - 写入本地文件integ/airtable_atlas.ts:101
mkdirSync(join(root, 'airtable')) - 写入本地文件integ/airtable_atlas.ts:102
writeFileSync(join(root, 'airtable', 'atlas.json'), JSON.stringify(corpus.workspace)) - 发起网络请求integ/airtable_atlas.ts:117
const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` } }) - 删除本地文件递归integ/airtable_atlas.ts:149
rmSync(root, { recursive: true, force: true }) - 写入本地文件integ/browser/run.ts:43
await writeFile(join(output, 'index.html'), '<!doctype html><title>Mirage shell</title>') - 写入本地文件integ/browser/run.ts:48
await mkdir(join(output, 'quickjs')) - 写入本地文件integ/browser/run.ts:49
await copyFile(join(quickjs, 'qjs-wasi.wasm'), join(output, 'quickjs', 'qjs-wasi.wasm')) - 写入本地文件integ/browser/run.ts:50
await copyFile( - 删除本地文件递归integ/browser/run.ts:117
await rm(output, { recursive: true, force: true }) - 读取本地文件integ/config/run.ts:54
JSON.parse(readFileSync(SPEC, 'utf8')) as { configs: Record<string, ConfigFacts | null> } - 读取本地文件integ/config/run.ts:132
const suite = JSON.parse(readFileSync(SUITE, 'utf8')) as Suite - 读取密钥环境变量integ/cross_commands.ts:29
const ACCESS = process.env.AWS_ACCESS_KEY_ID ?? 'minio' - 读取密钥环境变量integ/cross_commands.ts:30
const SECRET = process.env.AWS_SECRET_ACCESS_KEY ?? 'minio123' - 读取本地文件integ/fuse/discord.ts:48
await readFile(new URL('./discord_cases.json', import.meta.url), 'utf8'), - 读取本地文件integ/fuse/fuse.ts:66
override async readdir(path: PathSpec): Promise<FileStat[]> { - 读取本地文件integ/fuse/fuse.ts:70
override async stat(path: PathSpec): Promise<FileStat> { - 读取本地文件integ/fuse/fuse.ts:90
const pre = (await stat(apiFile)).size - 读取本地文件integ/fuse/fuse.ts:93
result.api_cat = (await readFile(apiFile, 'utf8')).trim() - 读取本地文件integ/fuse/fuse.ts:94
result.api_size_postread = (await stat(apiFile)).size - 读取本地文件integ/fuse/fuse.ts:139
result.big_write_body_matches = Buffer.compare(await readFile(path), want) === 0 - 删除本地文件integ/fuse/fuse.ts:256
await unlink(`${mp}/data/lk.pinned`) - 删除本地文件integ/fuse/fuse.ts:266
await unlink(`${mp}/data/lk.plain`) - 写入本地文件integ/fuse/fuse.ts:340
writeFile(`${data}/vault/new.txt`, 'x\n'), - 写入本地文件integ/fuse/fuse.ts:346
await writeFile(`${data}/pub.txt`, 'x\n') - 写入本地文件integ/fuse/fuse.ts:372
await writeFile(documentPath, 'changed') - 删除本地文件递归integ/fuse/fuse.ts:502
rmSync(pinned, { recursive: true, force: true }) - 写入本地文件integ/fuse/fuse.ts:525
await writeFile(`${dataMp}/t.txt`, 'AAAAAAAAAAAAAAAAAAAA\n') - 写入本地文件integ/fuse/fuse.ts:526
await writeFile(`${dataMp}/t.txt`, 'BB\n') - 写入本地文件integ/fuse/fuse.ts:553
await chmod(`${dataMp}/a.txt`, 0o600) - 删除本地文件递归integ/gridfs_trash.ts:103
rmSync(dir, { recursive: true, force: true }) - 读取密钥环境变量integ/hosting/mounts.ts:36
const accessKeyId = process.env.AWS_ACCESS_KEY_ID ?? 'minio' - 读取密钥环境变量integ/hosting/mounts.ts:37
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY ?? 'minio123' - 读取本地文件integ/hosting/run.ts:47
const cases = JSON.parse(await readFile(join(here, 'cases.json'), 'utf8')) as Case[] - 读取本地文件integ/hosting/run.ts:48
const montyCases = JSON.parse(await readFile(join(here, 'monty.json'), 'utf8')) as Case[] - 读取本地文件integ/hosting/run.ts:50
await readFile(join(here, 'tracking.json'), 'utf8'), - 执行外部命令来自配置/变量integ/hosting/run.ts:85
? spawn( - 执行外部命令来自配置/变量integ/hosting/run.ts:90
: spawn(process.execPath, ['--import', 'tsx', join(here, 'host.ts')], { - 发起网络请求integ/hosting/run.ts:133
const response = await fetch(url + path, { - 发起网络请求integ/hosting/run.ts:153
const response = await fetch(url + path, { - 发起网络请求integ/hosting/run.ts:457
const tar = await fetch(`${url}/v1/workspaces/a/snapshot`, { - 发起网络请求integ/hosting/run.ts:464
const loaded = await fetch(`${url}/v1/workspaces/load`, { - 删除本地文件递归integ/hosting/run.ts:514
await rm(home, { recursive: true, force: true }) - 读取本地文件integ/lifecycle/run.ts:923
const suite = JSON.parse(readFileSync(corpus, 'utf8')) as { - 读取密钥环境变量integ/metadata.ts:49
const accessKeyId = process.env.AWS_ACCESS_KEY_ID - 读取密钥环境变量integ/metadata.ts:50
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY - 删除本地文件递归integ/metadata.ts:93
rmSync(dir, { recursive: true, force: true }) - 删除本地文件递归integ/metadata.ts:134
rmSync(dir, { recursive: true, force: true }) - 读取本地文件integ/notion_atlas.ts:350
const corpus = JSON.parse(readFileSync(CORPUS, 'utf8')) as Corpus - 写入本地文件integ/notion_atlas.ts:357
mkdirSync(join(root, 'notion')) - 写入本地文件integ/notion_atlas.ts:358
writeFileSync(join(root, 'notion', `${FIXTURE}.json`), JSON.stringify(fixtureOf(corpus, rows))) - 发起网络请求integ/notion_atlas.ts:371
const response = await fetch(`${fake.endpoint}${req.path}`, { - 删除本地文件递归integ/notion_atlas.ts:407
rmSync(root, { recursive: true, force: true }) - 读取本地文件integ/ntn_conformance.ts:50
const doc = JSON.parse(readFileSync(TARGETS, 'utf8')) as { - 执行外部命令固定命令integ/ntn_conformance.ts:90
const found = spawnSync('which', ['ntn'], { encoding: 'utf8' }) - 执行外部命令固定命令integ/ntn_conformance.ts:110
const child = spawn('bash', ['-c', command], { env }) - 执行外部命令来自配置/变量integ/ntn_conformance.ts:135
const version = execFileSync(ntn, ['--version'], { encoding: 'utf8' }).trim() - 读取本地文件integ/ntn_conformance.ts:141
const battery = JSON.parse(readFileSync(BATTERY, 'utf8')) as { cases: Case[] } - 删除本地文件递归integ/ntn_conformance.ts:192
rmSync(home, { recursive: true, force: true }) - 写入本地文件integ/policy/benchmark-chrome.ts:27
await writeFile(join(output, 'index.html'), '<!doctype html><title>Policy benchmark</title>') - 删除本地文件递归integ/policy/benchmark-chrome.ts:76
await rm(output, { recursive: true, force: true }) - 发起网络请求integ/policy/composition.ts:47
const request = stream.request() - 发起网络请求integ/policy/composition.ts:56
equal(stream.request(), null, row.name) - 发起网络请求integ/policy/composition.ts:70
equal(stream.request(), null, row.name) - 删除本地文件递归integ/root.ts:55
rmSync(dir, { recursive: true, force: true }) - 读取本地文件integ/root.ts:108
check('disk-root: write at / persisted to the real disk path', existsSync(onDisk)) - 读取本地文件integ/root.ts:109
if (existsSync(onDisk)) { - 读取本地文件integ/root.ts:112
readFileSync(onDisk, 'utf8').trim() === 'persisted', - 读取本地文件integ/root.ts:128
check('yaml: write at / persisted to disk', existsSync(join(tmp, 'y.txt'))) - 读取密钥环境变量integ/runtime/run.ts:612
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? 'minio', - 读取密钥环境变量integ/runtime/run.ts:613
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? 'minio123', - 读取密钥环境变量integ/runtime/run.ts:820
const identityFile = process.env.MIRAGE_INTEG_SSH_KEY - 读取密钥环境变量integ/runtime/run.ts:851
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? 'minio', - 读取密钥环境变量integ/runtime/run.ts:852
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? 'minio123', - 读取本地文件integ/runtime/run.ts:1252
const source = readFileSync(join(SUITE_DIR, '../fixtures/runtime', step.script), 'utf8') - 读取本地文件integ/runtime/run.ts:1485
? readFileSync(join(SUITE_DIR, '../fixtures/runtime', source), 'utf8') - 删除本地文件递归integ/runtime/run.ts:1615
if (vfs instanceof DiskVFS) rmSync(vfs.root, { recursive: true }) - 读取本地文件integ/runtime/run.ts:1713
const files = readdirSync(SUITE_DIR, { recursive: true, encoding: 'utf8' }) - 读取本地文件integ/runtime/run.ts:1718
const suite = JSON.parse(readFileSync(join(SUITE_DIR, file), 'utf8')) as Suite - 读取本地文件integ/slack_atlas.ts:241
const corpus = JSON.parse(readFileSync(CORPUS, 'utf8')) as Corpus - 写入本地文件integ/slack_atlas.ts:243
mkdirSync(join(root, 'slack')) - 写入本地文件integ/slack_atlas.ts:244
writeFileSync(join(root, 'slack', 'atlas.json'), JSON.stringify(corpus.workspace)) - 发起网络请求integ/slack_atlas.ts:250
const response = await fetch(`${fake.endpoint}/api/${method}`, { - 删除本地文件递归integ/slack_atlas.ts:284
rmSync(root, { recursive: true, force: true }) - 读取本地文件integ/snapshot_smoke.ts:30
await readFile(new URL('./lifecycle/cases.json', import.meta.url), 'utf8'), - 读取密钥环境变量integ/state_store.ts:41
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? 'minio', - 读取密钥环境变量integ/state_store.ts:42
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? 'minio123', - 执行外部命令固定命令integ/wandb/conformance.ts:10
const child = spawn('uv', args, { env, stdio: ['ignore', 'pipe', 'pipe'] }) - 读取本地文件integ/wandb/requests.ts:29
readFileSync(new URL('./requests.json', import.meta.url), 'utf8'), - 执行外部命令来自配置/变量integ/wandb/run.ts:35
const child = spawn(executable, [fileURLToPath(new URL('./python.py', import.meta.url))], { - 读取本地文件integ/wandb/run.ts:75
const cases = JSON.parse(readFileSync(new URL('./cases.json', import.meta.url), 'utf8')) as Case[] - 读取本地文件integ/wandb/schema.ts:28
readFileSync(new URL('./schema.json', import.meta.url), 'utf8'), - 读取本地文件integ/wandb/schema.ts:105
const python = readFileSync( - 发起网络请求integ/wandb/schema.ts:133
const response = await fetch(capture.source, {
扫描于 2026/11/2(1 天前) · 范围 全包扫描 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
界面预览
5 张 · 点击查看大图全球首个面向 AI 智能体的虚拟终端
README
由 DeepSeek 最新模型翻译生成Mirage 是给 AI 智能体用的虚拟终端:一层 shell,统管数据、命令行和运行时。Unix 工具能触达每个服务,grep 直接跑在服务自己的搜索上,比如 Gmail 或 Slack 的搜索。Mirage 把每条命令路由到合适的运行时,多数命令行在进程内执行,只有重活才进沙箱,基础设施成本和 token 消耗都降下来。终端本身很轻:一个工作区占约 100 KB 内存,每个新会话约 10 KB,一个进程能同时服务多个智能体。配置档决定智能体能跑什么、甚至能看见什么,可脚本化的策略则拒绝那些规则表达不了的情况。
下面是在应用里启动 Mirage 的例子:
ws = Workspace(
{
"/s3": (S3VFS(S3Config(bucket="my-bucket")), MountMode.WRITE),
"/slack": (SlackVFS(SlackConfig(token=slack_bot_token)), MountMode.EXEC),
"/gmail": (GmailVFS(GmailConfig(**google_credentials)), MountMode.READ),
},
Install the Slack CLI, run by name and listed by man and which like any program
展开完整文档(README 剩余 1 节)
Mirage 是给 AI 智能体用的虚拟终端:一层 shell,统管数据、命令行和运行时。Unix 工具能触达每个服务,grep 直接跑在服务自己的搜索上,比如 Gmail 或 Slack 的搜索。Mirage 把每条命令路由到合适的运行时,多数命令行在进程内执行,只有重活才进沙箱,基础设施成本和 token 消耗都降下来。终端本身很轻:一个工作区占约 100 KB 内存,每个新会话约 10 KB,一个进程能同时服务多个智能体。配置档决定智能体能跑什么、甚至能看见什么,可脚本化的策略则拒绝那些规则表达不了的情况。
下面是在应用里启动 Mirage 的例子:
ws = Workspace(
{
"/s3": (S3VFS(S3Config(bucket="my-bucket")), MountMode.WRITE),
"/slack": (SlackVFS(SlackConfig(token=slack_bot_token)), MountMode.EXEC),
"/gmail": (GmailVFS(GmailConfig(**google_credentials)), MountMode.READ),
},
Install the Slack CLI, run by name and listed by man and which like any program
clis={"slack": (SLACK, {"token": slack_bot_token})},
the analyst profile hides /s3/secrets and denies posting to Slack and curl
profiles={
"analyst": {
"paths": {"hide": ["/s3/secrets"]},
"commands": {"deny": ["slack send-message", "curl"]},
},
},
runtimes=[
MontyRuntime(captures=["python", "python3"]),
E2BRuntime(
captures=["python", "python3"],
config={"sandbox_id": sandbox_id, "mount_type": "ssh"},
),
],
路由底层计算:numpy 行在 e2b 沙箱中运行,其余在 monty 上运行
route_policy=lambda ctx: "e2b" if "numpy" in ctx.line else None,
)
用 analyst 配置档添加智能体
alice = await ws.session("alice", profile="analyst")
一条 grep 扫过 S3、Slack 和 Gmail,每个服务有自己搜索能力时就用它自己的 await alice.shell("grep -rlw invoice /s3 /slack /gmail")
运行一个存放在 Slack 里的脚本,在 monty 上跑,把报告归档到 S3 await alice.shell("python3 /slack/channels/general__C0123/2026-10-21/files/report__F0456.py > /s3/report.txt")
monty 里没有 numpy,所以路由策略把这一行发给 e2b await alice.shell("python3 -c 'import numpy; print(numpy.arange(10).sum())'")
以及 alice 在 shell 里看到的内容:
$ grep -rlw invoice /s3 /slack /gmail
/s3/billing/2026-10.csv
/gmail/INBOX/2026-10-21/Invoice_1042__18f3a9c2b7d1e0f4.gmail.json
$ ls /s3
billing
report.txt
$ cat /s3/secrets/aws.txt
cat: /s3/secrets/aws.txt: No such file or directory
$ slack send-message --channel general --text "report is up"
slack: Permission denied
关于
- 统一的虚拟终端接口,而不是 N 个 SDK 加 M 个 MCP。 每个后端都遵循同一套文件系统语义,所以流水线可以跨服务组合。
- 覆盖每个数据源的虚拟文件系统。 S3、Google Drive、Slack、Gmail、Redis 等并排挂载在同一个根目录下,智能体用已经熟悉的 unix 工具(如 ls、grep、find 和 jq)就能通过统一接口访问它们。
- 虚拟命令行工具(CLI)。 git、slack 和 ntn 由 Mirage 自己应答,所以智能体无需安装任何东西就能驱动服务,跨不同运行时和机器都行;一个工具还能虚拟化成两个或多个,各自有独立名称和独立凭据。
- 可路由的动态运行时。 Python、JavaScript 以及任何其他命令都能发往配置好的运行时——进程内、沙箱或远程——从而把计算与存储解耦,让两者各自变化而互不影响。
- 虚拟化的 Mirage shell。 它把文件系统、CLI 和运行时绑定到同一条命令行,所以管道、重定向、变量、作业和历史记录在三者之间都能用。
- 设计上轻量。 一个工作区(也就是虚拟终端本身)约占 100 KB 内存,每个新会话约占 10 KB,所以一个进程能服务多个智能体,无需为每个智能体单独开容器或 VM。
- 为智能体设计的配置档。 allow、ask 和 deny 管控命令和 CLI,hide 和 show 管控文件和文件夹,所以隐藏路径不只是不可读,而是根本不出现在智能体看到的文件系统里。
- 可脚本化的策略引擎。 策略脚本能在任何危险操作运行前将其禁止,同一套栈还会拦截每次 VFS 操作和会话写入,所以文件和環境变量都不会泄漏。
- 通知已接入 VFS 和智能体。 外部变化会变成挂载点上的事件流,因此 Slack 里来了新回复,就会表现为虚拟文件系统中聊天文件的一次变更,智能体直接响应这次变更,不用重新扫描整棵目录树。
虚拟文件系统
Mirage 把一切都「挂载」成同一个虚拟文件系统,供 AI 智能体使用。每项服务并排放在同一个根目录下,遵循同一套 POSIX 语义。
| | VFS |
| 对象存储 | |
| 文件与文档 | |
| 消息与工作 | |
| 数据库与数据平台 | |
| 可观测性 | |
| Local and Remote | |
Agents reach it through the Python and TypeScript SDKs, the mirage CLI, or a real
mountpoint over FUSE and FSKit, then work it with the unix tools they already know,
like ls, grep, find and jq.
Virtual Command Line Tool
These command line tools are virtualized: Mirage answers git, slack or ntn
itself, so an agent drives the service without that program being installed on the
machine. Each one mimics the real tool, so an agent that knows the CLI needs nothing
new. Because they are virtual, the same tool can be installed more than once under
different names, each with its own credentials, so every agent gets exactly the
accounts it is given.
| | CLIs |
| Code | |
| Communication | |
| Work and Data | |
Virtual Runtime
Runtimes are virtualized the same way, and not only for coding languages. Any
command on the line can be redirected to a configured runtime, so Python might
run in-process with Monty while
another command, say kubectl, is sent to a remote machine over SSH. Which runtime
serves a given line can be decided by a
scripted runtime router.
| | Runtimes |
| Python | |
| JavaScript | |
| Sandboxes | |
Security
A profile decides what a session may run and what it may see. Commands are governed
by customizable allow, ask and deny rules, and paths by hide and show, so a
hidden file is not merely unreadable but absent from the filesystem the agent sees.
For anything those rules cannot express, a profile can name a policy script that runs
at the gate on every command and answers allow, deny or ask itself, though like every
rule it can only restrict and never grant. Separately, a host can register its own
policies on the policy engine, an
ordered stack the workspace consults on every command, VFS op and session write. See
the policy docs.
Authentication
Credentials and authentication integrate with the stores secrets already live in,
including AWS Secrets Manager, 1Password, Auth0 and dotenv, so an environment variable
in Mirage can resolve straight to a credential held in one of them.
| | Sources |
| Built in | |
| Custom | |
Installation
- Python ≥ 3.12 for the mirage-ai package and the mirage CLI
- Node.js 22.20+ on Node 22, 24.12+ on Node 24, or 25+ for the TypeScript SDK
Python
uv add mirage-ai # installs the mirage library and the mirage CLI binary
uv add 'mirage-ai[openai]' # OpenAI Agents adapter; each agent framework is an extra
TypeScript
npm install @struktoai/mirage-node # Node.js servers and CLIs
npm install @struktoai/mirage-browser # browser / edge runtimes
npm install @struktoai/mirage-vercel # Vercel AI adapter; each agent framework has its own package
Both runtime packages pull in @struktoai/mirage-core automatically.
CLI
npm install -g @struktoai/mirage-cli
or
uvx --from mirage-ai mirage
or
npx @struktoai/mirage-cli
To get started, see the quickstarts for
Python,
TypeScript and the
CLI.
Scriptable Policy
A profile can name policy scripts for what its rules cannot express. A script
reads the command as JSON, can open the files it names, and prints a deny, an
ask or nothing. This one disables rm, and a Python script whose code
deletes files:
import json, os
ctx = json.loads(os.environ["MIRAGE_POLICY_CTX"])
def deletes(path):
try:
code = open(path).read()
except OSError:
return False
return any(call in code for call in ("os.remove", "os.unlink", "shutil.rmtree"))
if ctx["command"] == "rm" or (
ctx["command"].startswith("python") and any(deletes(p) for p in ctx["paths"])
):
print(json.dumps({"kind": "deny", "reason": "deleting files is disabled"}))
Register it in a profile, here to run on Monty before every command:
ws = Workspace(
{"/s3": (S3VFS(S3Config(bucket="my-bucket")), MountMode.WRITE)},
profiles={
"analyst": {
"policy": {
"pre_command": [{"script": ScriptSource(Path("guard.py").read_text()), "runtime": "monty"}],
},
},
},
profile="analyst",
)
$ cat /s3/clean.py
import os
os.remove("/s3/report.txt")
$ rm /s3/report.txt
rm: Permission denied
$ python3 /s3/clean.py
python3: Permission denied
See policy scripts for
the other stages a script can answer at.
Embedded Terminal
Most of the time an agent does not need a sandbox, only a terminal-like
interface, and Mirage embeds one in an application server. Each terminal is a
Mirage session with its own working directory, environment and history, and Python
runs in-process on Monty, so a terminal starts with no container or VM. Here
it is in Express; the FastAPI version is in the
docs.
npm install @struktoai/mirage-node @pydantic/monty express
import express from 'express'
import { MontyRuntime, MountMode, RAMVFS, Workspace } from '@struktoai/mirage-node'
const ws = new Workspace(
{ '/work': new RAMVFS() },
{ mode: MountMode.EXEC, runtimes: [new MontyRuntime({ captures: ['python', 'python3'] })] },
)
const app = express()
app.use(express.json())
app.post('/sessions/:id', (req, res) => {
ws.createSession(req.params.id)
res.sendStatus(201)
})
app.post('/sessions/:id/run', async (req, res) => {
const r = await ws.shell(req.body.command, { sessionId: req.params.id })
res.json({ stdout: r.stdoutText, stderr: r.stderrText, exitCode: r.exitCode })
})
app.listen(3000, '127.0.0.1')
Contributors
Thanks to everyone who has contributed to Mirage.预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
strukto-ai/mirage
Mirage 是给 AI 智能体用的虚拟终端:一层 shell,统管数据、命令行和运行时。Unix…
相关插件 · 安全 / 权限
智能体逆向工程morluto/rea
用智能体逆向工程任何东西,从应用行为到原生二进制。
★ 422,036已汉化DSH
73.3
智能体集群框架ruvnet/ruflo
面向 Claude Code 与 Codex 的智能体元基座meta-harness。
★ 80,619已汉化MCP
71.8
WorkBuddy 模型接入corrinehu/dsh-workbuddy-connect
2026 年 10 月 20 日起,WorkBuddy…
★ 1,604已汉化DSH
71.7
破甲一键通z91772524-ai/pojia-next
十五个 AI…
★ 1,004已汉化DSH
71.6
Agent 操作层agentic-os-org/ANOLISA
ANOLISA(Agentic Nexus Operating Layer & Interface System Architecture)|智能体操作系统,带运行时、安全、可观测性,以及 Tokenless 响应压缩,降低 token 用量与成本。
★ 653已汉化DSH
70.7
AI 代理杀毒防护hashgraph-online/hol-guard
HOL Guard:面向 AI 智能体的开源杀毒软件
★ 728已汉化DSHDeepSeek Harness
70.1