内容
<div align="center">
<img src="site/binky.svg" alt="" width="88" />
# Binky
**每个 Git 工作树一个独立的本地环境 — 端口、环境变量、数据库、URL**
这样一群编码代理可以在一台机器上并行运行而不会发生冲突。
[](https://github.com/andreisilva1/binky/actions/workflows/ci.yml)
[](https://pypi.org/project/binky/)
[](pyproject.toml)
[](LICENSE)
[文档](https://binky-dev.vercel.app/) · [快速入门](#快速入门) · [适配器](#适配器) · [命令](#命令) · [适用于代理](#适用于代理) · [工作原理](#工作原理) · [故障排除](TROUBLESHOOTING.md)
</div>
---
`docker-compose` 是为一次运行一个进程而设计的。Binky 是为同时运行十五个进程而设计的。
> **状态:alpha.**核心功能已经实现,可以端到端运行 — 工作树隔离、端口、适配器、监督、
> 协调器、准入、代理、HTTPS。配置文件稳定;CLI 可能会变动。
## 问题
你将三个代理指向同一个仓库。第二个代理运行 `pnpm dev`,结果出现 `EADDRINUSE` 错误。
第三个代理运行迁移脚本,却删除了第一个代理正在读取的表。你开始手动编辑 `.env` 文件,
现在你成了调度器。
Binky 就是调度器。每个代理运行 `binky up <branch>`,每个代理都有自己的端口、自己的数据库和自己的 URL —
来自同一个已提交的配置文件。
## 安装
Python 3.11+ 和 git,在 **Linux 或 Windows** 上 — Binky 在这两个平台上经过了实际测试。
macOS 目前还不是支持的目标平台;如果有人能成功移植,欢迎提交 [贡献](CONTRIBUTING.md)。
```bash
uv tool install binky # 或:pipx install binky
binky --version
```
或者,从克隆仓库开始,运行未发布的 `main` 分支或进行修改:
```bash
git clone https://github.com/andreisilva1/binky && cd binky
uv tool install . # 或:pipx install .
```
需要驱动器的适配器会作为额外依赖安装 — 安装 Binky for Redis 不会拉取 `boto3:`
```bash
uv tool install "binky[postgres]" # 或 [redis], [mysql], [mongodb], [s3], [kafka]
uv tool install "binky[all-adapters]" # 整个矩阵
```
## 快速入门
已经有 `docker-compose.yml` 了吗?从它开始:
```bash
binky init # 编写一个 binky.toml,您可以阅读和编辑
binky check # 验证它
```
或者手动编写 — 这是整个配置文件:
```toml
[project]
name = "acme"
[services.web]
cmd = "pnpm dev --port ${self.port}"
url = "web" # → web.<worktree>.acme.localhost
depends_on = ["api"]
env = { API_URL = "${api.url}" }
[services.api]
cmd = "go run ./cmd/server"
url = "api" # 请求 URL 就是请求端口
depends_on = ["db"]
env = { DATABASE_URL = "${db.url}" }
[services.db]
adapter = "postgres" # 每个工作树的私有数据库
admin_url = "${env.POSTGRES_ADMIN_URL}"
migrate = "make migrate"
seed = "make seed"
clone_from = "golden" # 迁移 + 种子一次,然后复制每个工作树
```
然后并行启动工作树:
```console
$ binky up login-fix
api pid 16560 :4846
web pid 29732 :4504
✓ up: login-fix (2 service(s))
$ binky up checkout-v2
api pid 21768 :4137
web pid 46336 :4605
✓ up: checkout-v2 (2 service(s))
$ binky status
acme
checkout-v2 [running]
api :4137
web :4605
login-fix [running]
api :4846
web :4504
```
两个分支,四个进程,四个端口 — 没有一个是手动选择的。将工作树的坐标交给需要它们的工具 —
一个 shell、一个代理、一个测试运行:
```console
$ binky env login-fix --dotenv
WEB_URL=http://web.login-fix.acme.localhost
API_URL=http://api.login-fix.acme.localhost
```
和生命周期管理的其余部分:
```bash
binky down login-fix # 暂停(保留文件 + 端口);`up` 再次启动是瞬间的
binky remove login-fix # 销毁它(如果有未提交的更改,则拒绝)
```
没有什么硬编码,运行时也不会发现任何东西:每个进程在启动时都已经知道整个端口映射、
它自己的和依赖的。
> **一个需要牢记的规则。** 服务在声明 `url` 或 `ports` 时会获得一个端口 — 请求是明确的。
没有声明的服务不会获得端口,这是正确的,因为它是工作器或一次性作业,如果你在 `cmd` 中写入 `${self.port}` 会是一个陷阱。
未知的令牌不会被替换,而是保持原样,因此字面上的 `${self.port}` 会传递给 shell,进程会在自己的参数解析中死亡。
`binky check` 在服务执行此操作时会发出警告。
## 运行 N 个世界的成本
声明 N 个隔离的世界不应该花费 N 倍于一个世界。经过测量,而不是断言 —
`benchmarks/worlds.py` 位于此仓库中,可以重现下面的表格 (`postgres:16`, 每个世界 200,000 行,
Windows 11 上的 Docker Desktop):
| 世界 | 内存:每个容器 | 内存:Binky | 时间:每个容器 | 时间:Binky |
| ---: | ---: | ---: | ---: | ---: |
| 1 | 150 MiB | 174 MiB | 6s | 4s |
| 2 | 298 MiB | 182 MiB | 11s | 6s |
| 4 | 596 MiB | 228 MiB | 20s | 10s |
| 8 | 1197 MiB | 295 MiB | 40s | 16s |
**第 8 个世界仅占用 +150 MiB 和 +4.8s 作为自己的容器,或 +17 MiB 和 +1.7s 在 Binky 下。**
阅读第一行。在 **一个** 世界中,Binky 处于劣势 — 它为一个尚未使用的服务器和黄金模板支付费用,
而这些费用仅在第二个世界时才赚回来。**磁盘不是赢家**:`CREATE DATABASE ... TEMPLATE` 是一个文件复制,
不是写时复制,因此每个世界仍然是行的完整副本。Binky 删除的是 **服务器**,而不是 **字节**。
它也不使您的应用程序进程更便宜 — 每个工作树一个开发服务器,无论如何成本都相同。
```bash
python benchmarks/worlds.py --sweep 1,2,4,8 --rows 200000
```
## 不需要 Binky 的情况
值得明确说明,因为该表的第一行已经说明:
- **一次一个分支。** 如果你一次只使用一个分支,Binky 就是一个守护进程和一个配置文件,
而这些都是不必要的。在第二个代理出现时使用它。
- **所有内容都已在容器中运行,而且这样就足够了。** Binky 的优势在于删除每个世界的 **服务器**。
如果你的 compose 堆栈足够小,可以舒适地容纳 N 个副本,那么就没有可回收的资源。
- **您需要真正的隔离,而不是本地隔离。** Binky 在您的机器上以您的身份运行您的命令。
它不是一个沙盒 — 参见 [安全](#安全)。
## 适配器
适配器会划分一个隔离的 **切片**,即一个共享服务器 — 数据库、键前缀、存储桶 —
而不是为每个工作树运行一个服务器。**16 个名称,10 个实现,每个实现都在 CI 中针对真实服务器进行了测试。
| 切片 | 适配器 |
| --- | --- |
| 数据库 | `postgres` · `mysql` · `mariadb` · `percona` · `mongodb` · `clickhouse` |
| 键前缀 | `redis` · `valkey` |
| 存储桶/前缀 | `s3` · `minio` |
| vhost/主题前缀 | `rabbitmq` · `kafka` · `redpanda` |
| 集合 | `qdrant` |
| 索引前缀 | `elasticsearch` · `opensearch` |
协议兼容的名称共享一个实现,而不是复制它 — `valkey` 是 Redis 适配器,`percona` 是 MySQL 适配器,
`opensearch` 是 Elasticsearch 适配器。
### `clone_from`:仅需一次迁移+种子
使用 `clone_from`,第一个 `binky up` 会构建一个 **黄金模板** — 配置、迁移、种子 —
每个工作树之后都是它的副本。模板的键控哈希值是 `migrate` 和 `seed` 命令的哈希值,
因此更改任一命令会自动重建它。
支持服务器可以在服务器端复制切片(`postgres`、`mysql`、`mariadb`、`percona`、`mongodb`)。
如果不支持,或者克隆因环境问题而失败 — Binky **回退** 到提供+迁移+种子以获取该工作树,
并记录 `clone_fallback`。正确性永远不会依赖克隆;只有速度依赖于此。
一个需要注意的锋利边缘:哈希涵盖了 `migrate` 和 `seed` **命令**,而不是它们调用的文件。
编辑脚本会运行这些命令不会重建模板 — 删除其下的标记以强制执行 `~/.binky/golden/`。
### 编写自己的适配器
两个层级,一个契约。两者都实现相同的四个动词:
| 动词 | 做 |
| --- | --- |
| `provision(slice)` | 创建切片 (`CREATE DATABASE ...`) |
| `resolve(slice)` | 返回其地址 → `${db.url}` |
| `teardown(slice)` | 销毁它 |
| `capabilities()` | 它支持什么,例如 `{"clone": true}` |
**进程内** 适配器是此仓库中的 Python 类,按名称发现。
**外部** 适配器是任何语言的可执行文件,通过 `binky-adapter-<name>` 在 `PATH` 上发现,并通过
一次性子进程 + JSON 协议驱动:
```console
$ binky-adapter-filestore provision <<< '{"slice": "acme_login-fix", "config": {}}'
{"ok": true}
$ binky-adapter-filestore resolve <<< '{"slice": "acme_login-fix", "config": {}}'
{"ok": true, "address": "file:///tmp/binky-filestore/acme_login-fix"}
```
Python 作者免费获得协议 — `binky.adapter_sdk.run()` 将普通适配器类桥接到它。
参见 [examples/adapters/filestore.py](examples/adapters/filestore.py) 以获取一个完整的、
依赖无关的示例,代码只有 30 行。
## 命令
| 命令 | 功能 |
|---|---|
| `binky init [dir] [--force]` | 从 `docker-compose.yml` + `.env` 编写起始 `binky.toml` |
| `binky check [path]` | 验证 `binky.toml` 并标记 footguns |
| `binky up <name> [--group g]` | 创建/使用分支的工作树,分配端口,提供资源,启动服务 |
| `binky reload <name> [--group g]` | 原地重启 — 停止,然后在相同的端口和数据上重启 |
| `binky down <name>` | 停止工作树进程(保留文件和端口) |
| `binky remove <name> [--force]` | 销毁工作树 — 停止,删除切片,删除文件 |
| `binky status [.]` | 列出工作树(按项目分组);`.` = 当前项目仅 |
| `binky env <name> [--dotenv]` | 打印工作树的公开坐标,供您或代理使用 |
| `binky curl <name> <service> [path]` | 通过代理调用服务,填充 `Host` 头 (`-X`/`-d`/`-H`/`-i`/`-f`) |
| `binky verify <name>` | 运行项目的 `agents.verify` 检查;失败时退出非零 |
| `binky logs <name>` | 打印工作树的捕获服务日志 |
| `binky gc` | 清除适配器切片的孤儿 |
| `binky config` | 显示机器全局设置(RAM/磁盘预留,代理端口) |
| `binky reserve [--ram %] [--disk gb]` | 设置 Binky 永久保留多少 RAM/磁盘 |
| `binky port [--proxy p] [--https p]` | 设置代理/HTTPS 端口 |
| `binky dashboard` | 实时 TUI 显示所有工作树 |
| `binky doctor` | 诊断安装和守护进程;如果有问题则退出非零 |
| `binky metrics` | 将守护进程指标作为 JSON 输出 |
| `binky mcp` | 运行 MCP 服务器,以便代理可以调用 Binky 的动词作为工具 |
| `binky daemon start\|stop\|status` | 管理后台守护进程 |
## 适用于代理
`binky mcp` 通过 stdio 使用 MCP,公开 `up`、`down`、`status`、`env` 和 `verify` —
与 CLI 调用相同的动词,针对同一个守护进程。
在 Claude Code 中,一行代码将其连接起来:
```bash
claude mcp add binky -- binky mcp
```
任何其他支持该协议的客户端都将相同的 stdio 服务器作为配置块 — 每个地方都相同,只是文件不同:
```json
{ "mcpServers": { "binky": { "command": "binky", "args": ["mcp"] } } }
```
要使用未安装的内容,请将其交给 `uvx` — `claude mcp add binky -- uvx --from
"binky[mcp]" binky mcp`,或作为块,`"command": "uvx"`,`"args": ["--from", "binky[mcp]",
"binky", "mcp"]`。对于提供适配器切片的项目,请添加那些额外依赖项,例如
`binky[mcp,postgres]`(或 `binky[mcp,all-adapters]` 以获取整个矩阵)。
Binky 已在 [MCP 注册表](https://registry.modelcontextprotocol.io) 上发布为
`io.github.andreisilva1/binky`,因此 registry-aware 客户端可以从那里发现并安装它。
有关工具列表和代理端逐步指南,请参阅 [AGENTS.md](AGENTS.md)。
<!-- mcp-name: io.github.andreisilva1/binky -->
## 工作原理
单个机器全局守护进程位于工作树和机器之间:
```mermaid
flowchart TB
CLI["代理 · CLI · MCP"] -->|"up · down · status"| D(("binky daemon<br/>机器全局"))
D --> ADM{"准入<br/>RAM · 磁盘 · max_parallel"}
ADM -.->|"否 → 队列 / 挂起"| Q["等待中"]
ADM -->|"准入"| W["工作树<br/>git 工作树 per 名称"]
W --> P["端口<br/>整个映射启动前"]
P --> R["适配器<br/>工作树隔离切片"]
R --> S["监督<br/>depends_on · 健康 · 日志"]
S --> X["代理<br/>svc.worktree.domain → 端口"]
D -.->|"读取 · 写入"| ST[("~/.binky<br/>SQLite + 设置")]
```
1. **工作树** — 为每个名称创建/使用 git 工作树。
2. **端口** — 全局分配器为每个进程分配端口 *before* 任何东西启动并注入
整个映射,以便不会发生冲突(`${self.port}`、`${api.url}`,…)。
3. **资源** — 适配器为每个工作树切割隔离切片并在删除时销毁。
4. **监督** — 按 `depends_on` 顺序启动服务,等待健康,捕获日志。
5. **代理** — `<service>.<worktree>.<domain>` 路由到正确的 < route>。<工作树>.<domain>` -> 端口(WebSockets/HMR 包括在内)。
如果 Caddy 在 PATH 中,Binky 将其用作 TLS 的前端,通过本地 CA 提供 TLS。
6. **准入** — 一个 FIFO 队列,仅当机器有空间时才启动工作树:少于
`max_parallel` 运行 **和** 足够的空闲 RAM(保留百分比)**和** 足够的空闲磁盘(固定 GB 余量)。
如果一个运行中的集群后来超过了 RAM 预留,守护进程会挂起最新的工作树以缓解压力,并按公平顺序恢复它
— 当 RAM 恢复时。按 `binky reserve` 调整预留;用 `binky config` 查看。
状态存储在 `~/.binky/`(一个 SQLite 注册表和一个设置存储库,随升级原地迁移)。
什么也不需要在云中运行,Binky 也从不联网。
## 示例
- [examples/hello](examples/hello) — 最简单的可能项目。
- [examples/rag](examples/rag) — 向量搜索,每个工作树都有自己的嵌入式语料库。
这是共享数据库实际上会伤害的情况:两个代理重新索引,一个 `TRUNCATE`。
- [examples/adapters/filestore.py](examples/adapters/filestore.py) — 外部适配器,完整。
## 安全
Binky 在 **您** 的机器上以 **您** 的身份运行 **您** 的命令。它是一个适用于单个用户工作站的开发工具,
而不是多租户沙盒 — `binky.toml` 和 `Makefile` 一样可信,`binky up` 在不受信任的仓库上运行与在不受信任的仓库上运行 `make` 相同。
威胁模型和明确排除的范围写在 [SECURITY.md](SECURITY.md)。
唯一需要注意的规则:**永远不要在提交的 `binky.toml` 中直接写下凭证**。
使用 `${env.NAME}` 或 `env_file`。`binky check` 会在您直接编写时发出警告,
`binky init` 从不写入。
## 故障排除
[TROUBLESHOOTING.md](TROOLUCSHOOTING.md) 涵盖了实际出现的问题 — 保留的端口来自外部,
开发服务器忽略 `$PORT`,过时的黄金模板,`*.localhost` 未解析。
从 `binky doctor` 开始。
## 开发
```bash
python -m venv .venv && . .venv/bin/activate # 或 .venv\Scripts\activate on Windows
pip install -e ".[dev,all-adapters]"
pytest
ruff check src tests examples benchmarks
```
适配器集成测试除非您将它们指向真正的服务器,否则跳过 —
`BINKY_TEST_PG`、`BINKY_TEST_REDIS`、`BINKY_TEST_MYSQL`、`BINKY_TEST_MONGO`、`BINKY_TEST_S3`、
`BINKY_TEST_RABBITMQ`、`BINKY_TEST_KAFKA`、`BINKY_TEST_QDRANT`、`BINKY_TEST_CLICKHOUSE`、
`BINKY_TEST_ELASTICSEARCH`。CI 提供所有这些。
## 贡献
欢迎提交 Bug 报告、新适配器和 PR — 请参阅 [CONTRIBUTING.md](CONTRIBUTING.md) 开始使用,
并参阅 [ARCHITECTURE.md](ARCHITECTURE.md) 了解内部结构。
## License
Apache-2.0 — 参见 [LICENSE](LICENSE) 和 [NOTICE](NOTICE)。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器