<div align="center">

# Closure

> 「没有就自己造一个呗。」  
> —— 可露希尔，罗德岛总工程师

一个加密文件 + 一个密码，  
所有 Coding Agent 的接入方式集中管理、一键切换。

[![Rust](https://img.shields.io/badge/Rust-edition%202024-000000?style=for-the-badge&logo=rust&logoColor=white)](https://www.rust-lang.org/)
[![CLI](https://img.shields.io/badge/clap-4-8333B2?style=for-the-badge)](https://github.com/clap-rs/clap)
[![TUI](https://img.shields.io/badge/ratatui-0.29-CC4400?style=for-the-badge)](https://ratatui.rs/)
[![Crypto](https://img.shields.io/badge/AES--256--GCM-encrypted-336633?style=for-the-badge)](https://docs.rs/aes-gcm)

[Why](#-why) · [Screens](#-screens) · [Quick Start](#-quick-start) · [CLI](#-cli-reference) · [Data Model](#-data-model) · [Dev](#-development)

</div>

* * *

## ✨ What is this?

日常使用 Coding Agent 的时候，经常要在多个 API 接入点之间切换——kimi 的、deepseek 的、自己搭的代理。每次换接入点就要去改 `~/.claude/settings.json` 或 `~/.codex/config.toml`，改完字段再重启 agent，很烦。

Closure 把这件事简化成一条命令：把所有接入点的 key、url、模型名存进**一个加密文件**，切 provider 的时候只改 agent 配置文件里跟接入方式相关的字段，其他设置完全不碰。

-   所有 provider 的信息存在 `~/.kozbox/closure/workdata/providers.enc`，用 Argon2id + AES-256-GCM 加密，可以在不同机器间安全地传。
-   每个 provider 在定义的时候就标明了是给哪个 agent 用的（`claude-code` 还是 `codex`），字段格式跟着 agent 走。
-   不带参数运行 `closure` 会进入交互式 TUI，带参数就是传统 CLI。
-   `closure sync` 在 workdata 和 sync 目录之间做双向同步（保留时间戳更新的那个），纯 Rust 实现，不依赖 rsync。

* * *

## 🖥️ Screens

**TUI — Provider List**

```
┌───────────────────────────────────────────────────────────────┐
│  Rhodes Island Engineering - Access Control                   │
│                                                               │
│  [claude-code]    samuka-kimi-anth                    (*)     │
│  [claude-code]    mimo-anth                                   │
│  [claude-code]    longcat-anth                                │
│  [codex]          coco-gpt-openai                     (*)     │
│  [codex]          hiyo-openai                                 │
│                                                               │
│  Switched to samuka-kimi-anth. Signal strong.                 │
│  Closure v0.1  |  Chief Engineer, Rhodes Island               │
└───────────────────────────────────────────────────────────────┘
```

**TUI — Auth**

```
┌───────────────────────────────────────────────────────────────┐
│  Closure - Access Terminal                                    │
│                                                               │
│           Password:  ************                             │
│                                                               │
│  Rhodes Island Eng. v0.1  |  Esc to quit                      │
└───────────────────────────────────────────────────────────────┘
```

* * *

## 🚀 Why?

### 痛点

`~/.claude/` 下面经常攒了一堆 settings 文件，换接入点就要手动改名或者编辑 JSON：

```
~/.claude/
  settings.json              ← 当前在用
  samuka.settings.json       ← 换 samuka 的时候手动 mv 过来
  kimi.settings.json         ← 换 kimi 的时候再 mv
  longcat.settings.json
  ds.settings.json
  settings.json.lucas        ← 别人给的备用配置
```

同理，`~/.codex/config.toml` 和 `auth.json` 也得手工改 TOML 和 API key。

### Closure 怎么解决

| 痛点 | 处理方式 |
| --- | --- |
| 多个 agent，配置格式各不相同 | 为每种 agent 写一个 adapter，分别处理 JSON（Claude Code）和 TOML + JSON（Codex） |
| 换接入点要手工改名或编辑文件 | `closure cc p s <name>` 一键切换，TUI 里选中按 Enter 就生效 |
| API key 和 URL 明晃晃放在 JSON 里 | AES-256-GCM 加密，Argon2id 做密钥派生。解密后的临时文件权限 0600 |
| 多台机器之间想同步 provider 列表 | `closure sync`，比较 mtime 双向保留最新，不需要 rsync |
| 命令太长不想敲 | 全程支持缩写：`cc` / `cx`，`p`，`s` / `l` / `ls` / `a` / `r` |

* * *

## ⚡ Quick Start

```bash
# 克隆并编译
git clone https://github.com/Kozmosa/closure.git
cd closure
cargo build --release

# 把二进制文件放到 PATH 里
cp target/release/closure ~/.local/bin/

# 第一次运行会弹出 TUI 让你输密码
closure

# 不想用 TUI 的话也可以命令行认证
closure auth
# => 已解锁。欢迎回来，博士。

# 查看 claude-code 有哪些可用 provider
closure cc p l

# 切换到指定 provider
closure cc p s samuka-kimi-anth
# => 已切换到 samuka-kimi-anth | claude-code 端点信号良好。

# 把 workdata 同步到 cloud sync 目录
closure sync
# => 同步完成。就跟当年在卡兹戴尔阁楼里搭的那些中继器一样。
```

### Provider 数据格式

`~/.kozbox/closure/workdata/providers.json` 的结构（先 `closure auth` 认证才能看到明文）：

```json
{
  "version": 1,
  "providers": [
    {
      "name": "samuka-kimi-anth",
      "target": "claude-code",
      "model": "kimi-for-coding",
      "env": {
        "ANTHROPIC_AUTH_TOKEN": "sk-bf-xxxx",
        "ANTHROPIC_BASE_URL": "https://bifrost.samuka007.top/anthropic",
        "ANTHROPIC_DEFAULT_HAIKU_MODEL": "kimi-for-coding",
        "ANTHROPIC_DEFAULT_SONNET_MODEL": "kimi-for-coding",
        "ANTHROPIC_DEFAULT_OPUS_MODEL": "kimi-for-coding"
      }
    },
    {
      "name": "coco-gpt-openai",
      "target": "codex",
      "api_key": "sk-xxxx",
      "base_url": "https://newapi.freeapp.tech/v1",
      "model": "gpt-5.5",
      "model_provider": "OpenAI",
      "review_model": "gpt-5.4"
    }
  ]
}
```

编辑后调用一次 `closure auth`，输入密码就会重新加密。

* * *

## 📟 CLI Reference

### Agent 名称

| 全称 | 缩写 |
| --- | --- |
| `claude-code` | `cc` |
| `codex` | `cx` |

### Provider 子命令

| 全称 | 缩写 | 别名 |
| --- | --- | --- |
| `provider` | `p` | — |
| `select <name>` | `s <name>` | — |
| `list` | `l` | `ls`, `list` |
| `add` | `a` | — |
| `edit` | `e` | — |
| `remove <name>` | `r <name>` | — |
| `show <name>` | `sh <name>` | — |
| `check <name>` | `ch <name>` | — |
| `preset <tpl> <name>` | `pt <tpl> <name>` | — |
| `list-presets` | `lsp` | `presets` |

### 顶层命令

| 命令 | 说明 |
| --- | --- |
| `closure` | 启动 TUI |
| `closure --tui` | 强制启动 TUI |
| `closure auth` | 解密 provider 存储 |
| `closure auth logout` | 删除临时解密文件 |
| `closure sync` | 双向同步 workdata ↔ sync |
| `closure backup list` | 列出现有备份快照 |
| `closure backup inspect <id>` | 查看备份详情 |
| `closure backup restore <id>` | 恢复到指定备份 |
| `closure doctor` | 检查 vault / sync / backup 健康状态 |
| `closure vault verify` | 校验 vault 文件完整性（SHA-256） |
| `closure session export <path>` | 导出 Claude Code sessions 到 ZIP |

### 完整示例

```bash
# 完整写法
closure claude-code provider select samuka-kimi-anth

# 缩写写法（等价）
closure cc p s samuka-kimi-anth

# 查看 codex 的 provider 列表
closure cx p ls

# 删掉一个不再用的 provider
closure cc p r old-provider
```

* * *

## 📐 Data Model

### Agent 适配策略

每个 provider 在定义时就指定了 `target`，决定了往哪个 agent 的配置文件里写、写哪些字段。

**Claude Code** — 目标文件 `~/.claude/settings.json`  
写入 `.env` 和 `.model`，其余字段（plugins、hooks、statusLine 等）完全保留不动。

**Codex** — 目标文件 `~/.codex/config.toml` + `~/.codex/auth.json`  
写入 `model` / `model_provider` / `review_model` 和 `[model_providers.*].base_url` 到 TOML，`OPENAI_API_KEY` 到 auth.json。

### 加密

```
salt_len || salt || nonce(12B) || ciphertext || tag(16B)
          └─ Argon2id(t=4) ─┘          └─ AES-256-GCM ─┘
```

- 密钥派生：Argon2id（memory=19456 KiB, t=4, p=1, 256-bit output）
- 加密：AES-256-GCM，每次加密随机 96-bit nonce
- 解密临时文件权限：0600（Linux / macOS）
- 详细设计文档 → [docs/encryption.md](docs/encryption.md)

### 同步

扫描 `workdata/` 和 `sync/` 两个目录，mtime 更新的覆盖更旧的，只有一边有就直接复制过去。`providers.json`（解密后的临时文件）不会参与同步。

* * *

## 🏗️ Project Layout

```
apps/closure/
├── Cargo.toml
└── src/
    ├── main.rs                    # entry point: args -> CLI, no args -> TUI
    ├── lib.rs                     # crate root
    ├── core/
    │   ├── mod.rs                 # module declarations, re-exports
    │   ├── error.rs               # ClosureError enum (thiserror)
    │   ├── crypto.rs              # Argon2id + AES-256-GCM
    │   ├── provider.rs            # Provider / ProviderStore model
    │   ├── store.rs               # encrypted file I/O, auth session, manifest
    │   ├── agent.rs               # Agent trait, ClaudeCode / Codex adapters
    │   ├── sync.rs                # manifest-based bidirectional file sync
    │   ├── backup.rs              # local backup snapshots with manifest
    │   ├── preset.rs              # provider preset templates
    │   └── fsutil.rs              # atomic write utility
    ├── cli/
    │   ├── mod.rs
    │   ├── args.rs                # clap derives with aliases
    │   └── commands.rs            # command dispatch & execution
    └── tui/
        ├── mod.rs                 # run_tui entry
        ├── app.rs                 # event loop, screen routing
        └── screens/
            ├── mod.rs
            ├── auth.rs            # password prompt screen
            └── list.rs            # provider list screen
tests/
├── crypto_test.rs              (2 tests)
├── store_test.rs               (6 tests)
├── agent_test.rs               (7 tests)
├── sync_test.rs                (3 tests)
├── cli_test.rs                 (5 tests)
└── session_test.rs             (2 tests)
```

* * *

## 🛠️ Tech Stack

| 层 | 选型 |
| --- | --- |
| 语言 | Rust（edition 2024） |
| TUI | ratatui 0.29 + crossterm 0.28 |
| CLI | clap 4（derive 模式，内置 alias） |
| 序列化 | serde / serde_json，toml |
| 加密 | argon2（Argon2id），aes-gcm（AES-256-GCM） |
| 文件同步 | 纯 Rust 实现 |
| 测试 | cargo test，tempfile，assert_cmd |

* * *

## ✅ Development

```bash
cd apps/closure

# 编译检查
cargo check

# 运行全部测试（目前 30 个）
cargo test

# 跑起来
cargo run              # TUI
cargo run -- cc p l    # CLI
cargo run -- --help    # 查看帮助

# 自动修复 warning
cargo fix --lib -p closure
```

* * *

## 🧭 Status

TUI / CLI、加解密、双向同步、双 agent 适配器、provider 预设、端点检测、会话导出都已经完整可用，30 个集成测试覆盖了所有核心模块。作为日常 Coding Agent 的接入切换工具已经可以正常使用。

### 接下来

-   [ ] 接入更多 agent（aider，continue.dev 等）
-   [ ] 远程同步后端（S3，rclone 等）
-   [ ] `closure <agent> p show` 查看 provider 详细信息

* * *

## 🤝 Contributing

本仓库是 kozbox 的 Git 子模块，开发请在其独立 repo 中进行。

提交信息使用 Conventional Commits：

```
feat(agent): add aider adapter
fix(crypto): validate salt length
refactor(cli): extract apply_provider helper
```

* * *

## 关于名字

**Closure** 取自《明日方舟》角色[可露希尔](https://prts.wiki/w/%E5%8F%AF%E9%9C%B2%E5%B8%8C%E5%B0%94)——罗德岛总工程师、采购部负责人，也是最早在卡兹戴尔的阁楼里用回收零件搭建通讯网络的技术先驱。

她在游戏里的英文代号就是 "Closure"。

一个管 API key、管 endpoint、管模型切换的工具，交给这位总工程师来主持，多少也算专业对口。至于那些命令行的反馈文案，就当是她在采购部和工程部之间搬东西的时候顺路丢给你的。

> 「不是已经把原因讲完了吗？」  
> —— 可露希尔

* * *

## 📄 License

MIT
