@tunnelbox/claude-code 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -0
- package/dist/index.mjs +1351 -0
- package/official-plugin/.claude-plugin/plugin.json +10 -0
- package/official-plugin/bin/tunnelbox +4 -0
- package/official-plugin/bin/tunnelbox.cmd +3 -0
- package/official-plugin/bin/tunnelbox.mjs +274 -0
- package/official-plugin/commands/pair.md +10 -0
- package/official-plugin/commands/status.md +9 -0
- package/official-plugin/hooks/hooks.json +16 -0
- package/official-plugin/skills/pair/SKILL.md +12 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @tunnelbox/claude-code
|
|
2
|
+
|
|
3
|
+
tunnelbox 的 **Claude Code 适配器**(B 类,独立进程):手机远程驱动电脑上的 Claude Code(会话/流式/工具审批/中止/删除),复用 `@tunnelbox/core`(RelayClient/状态/二维码)。
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
手机 PWA ──WSS──► relay ──WSS──► 本适配器(电脑上常驻 Node 进程)
|
|
7
|
+
└─ @anthropic-ai/claude-agent-sdk query()
|
|
8
|
+
└─ 拉起本机 claude CLI 子进程
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 前置条件
|
|
12
|
+
|
|
13
|
+
- Node.js ≥ 22(`RelayClient` 依赖全局 `WebSocket`);
|
|
14
|
+
- 本机已安装并登录 **Claude Code CLI**(`claude --version` 可用、`~/.claude/.credentials.json` 存在)。SDK 每次 `query()` 会在本机拉起 `claude` 子进程。
|
|
15
|
+
|
|
16
|
+
> 适配器运行在**电脑**上,与 opencode/dsh 一样只向中继发出站 WebSocket;手机端无需安装任何 Claude SDK。
|
|
17
|
+
|
|
18
|
+
## 构建与运行
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd plugin && npm install # workspaces 根(含 claude-code 及 claude-agent-sdk)
|
|
22
|
+
cd claude-code && npm run build # 产出 dist/index.mjs
|
|
23
|
+
node dist/index.mjs # 或 npm start
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
> 首次运行时若 SDK 缺失会提示先 `npm install`。
|
|
27
|
+
|
|
28
|
+
## 配置
|
|
29
|
+
|
|
30
|
+
| 配置 | 默认 | 说明 |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `TUNNELBOX_RELAY_URL` | state 里保存的地址(`~/.config/opencode/remote-state.claude-code.json`) | 中继地址(如 `wss://chat.example.com`) |
|
|
33
|
+
| `TUNNELBOX_CWD` | `process.cwd()` | 默认工作区;也可在手机上通过工作区列表切换 |
|
|
34
|
+
| `TUNNELBOX_CLAUDE_MODE` | `default` | 每回合 `permissionMode`:`default/plan/acceptEdits/bypassPermissions/dontAsk/auto`。`bypassPermissions` 全放行且能力位 `permission=false`(危险,慎用) |
|
|
35
|
+
| `TUNNELBOX_CLAUDE_MODEL` | claude 默认 | 模型覆盖(`options.model`) |
|
|
36
|
+
| `TUNNELBOX_CLAUDE_THINKING` | claude 默认 | `off \| adaptive \| enabled[:budget]` → `options.thinking` / `maxThinkingTokens` |
|
|
37
|
+
| `TUNNELBOX_CLAUDE_MAX_THINKING_TOKENS` | - | 思考 token 上限 |
|
|
38
|
+
| `TUNNELBOX_CLAUDE_ENV_*` | - | 透传给 claude 子进程的环境变量(前缀去掉,如 `TUNNELBOX_CLAUDE_ENV_ANTHROPIC_BASE_URL=…` → `options.env`) |
|
|
39
|
+
| `TUNNELBOX_CLAUDE_DIALOG_KINDS` | 空 | `request_user_dialog` kind 白名单(逗号分隔,实验性,需实测后填真实 kind) |
|
|
40
|
+
| `TUNNELBOX_CLAUDE_PLUGIN_DIR` | 包内 `official-plugin/` | 覆盖要加载的**官方 Claude Code 插件**目录(Agent SDK `options.plugins`) |
|
|
41
|
+
| `TUNNELBOX_CLAUDE_WARM` | 关 | 开启 CLI 子进程预热(alpha) |
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
TUNNELBOX_RELAY_URL=wss://chat.example.com TUNNELBOX_CWD=D:/workspace/project \
|
|
45
|
+
TUNNELBOX_CLAUDE_MODE=default TUNNELBOX_CLAUDE_THINKING=adaptive node dist/index.mjs
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
> 模式为会话级:`session.prompt.agent:"plan"` 会把该会话切到 plan 模式,`"build"` 切回默认。
|
|
49
|
+
|
|
50
|
+
## 能力位(agent.info.capabilities)
|
|
51
|
+
|
|
52
|
+
| 能力 | 值 | 实现 |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| streaming | true | `includePartialMessages` 流事件(text/thinking delta)→ `message.part` |
|
|
55
|
+
| thinking | true | 模型开启思考时 `thinking_delta` → thinking 部件 |
|
|
56
|
+
| permission | 默认 true | `canUseTool` → 手机审批(离线/断线/超时 fail-closed 拒绝);`mode=bypassPermissions` 时为 false(不注册 canUseTool) |
|
|
57
|
+
| commands | true | 适配器命令 `/new` `/help` |
|
|
58
|
+
| abort | **true** | `AbortController.abort()` 中止本次回合 |
|
|
59
|
+
|
|
60
|
+
## 会话与工作区
|
|
61
|
+
|
|
62
|
+
- **会话 id = Claude 会话 UUID**:新会话由适配器生成 UUID(`query options.sessionId` 固定 id,首次 prompt 落盘);也能在列表里看到并 resume 你在终端里开过的既有会话。
|
|
63
|
+
- 数据源:`claude-agent-sdk` 的 `listSessions / getSessionInfo / getSessionMessages / deleteSession`(不手读 JSONL),会话/凭证沿用 `~/.claude`,与本地 Claude Code 互不干扰。
|
|
64
|
+
- 工作区 = 会话的 `cwd`;跨会话并行(每会话一个 `query`/`AbortController`),同一会话运行中禁止再发。
|
|
65
|
+
- 每轮对话是**一条手机消息 = 一次 query**(resume 式续聊),首次拉起 CLI 有秒级延迟属正常;回合结束会广播 `session.updated`(标题/时间),手机端原地 patch。
|
|
66
|
+
- 历史拉取会把 `tool_result` 折叠成独立的 `role:"tool"` 卡(结果截断 ≤2KB);断线期间的流式增量不补发,重连后由手机重拉 `session.messages` 恢复。
|
|
67
|
+
|
|
68
|
+
## 工具审批(安全)
|
|
69
|
+
|
|
70
|
+
- 每次 query 默认 `permissionMode` 由配置/agent 决定 + `canUseTool` 拦截:中继在线 → 推手机审批卡(允许/拒绝/总是允许→写入 session 级建议规则);**中继断线 / 120s 超时 / 中止 → 一律自动拒绝(fail-closed)**,绝不自动放行;断线瞬间会即时清掉所有待审批,无需等满超时。
|
|
71
|
+
- 与 CLI 交互提示一致:审批卡片展示 Claude 的原生提示(`title`/`description`)。
|
|
72
|
+
- 手机在线时审批**优先于 PC**:适配器是 headless 独立进程,claude 子进程无本地 TUI,审批全部经 `canUseTool` 回包;离线时没有本地兜底(拒绝),与 opencode(TUI 兜底)/ dsh(dsh web 兜底)不同,详见 `docs/适配器接口规范.md` §3.3。
|
|
73
|
+
|
|
74
|
+
## 对话框 / MCP elicitation(实验性)
|
|
75
|
+
|
|
76
|
+
- `request_user_dialog`(Claude 的"选择/确认"阻塞对话框):已实现 `onUserDialog` + `supportedDialogKinds`;kind 白名单走 `TUNNELBOX_CLAUDE_DIALOG_KINDS`(**默认空 → SDK 不发任何对话框**,fail-closed)。启用后按 `dialog.ts` 的启发式映射把 payload 转成手机 `choice/input` 卡,作答值回填 `UserDialogResult.completed`。
|
|
77
|
+
- MCP elicitation:`mode=url` 弹手机审批卡(允许=accept);`form` 及其余保持自动拒绝。
|
|
78
|
+
- ⚠️ 对话框 payload/result 是 kind 相关结构、随 CLI 版本演进:以上映射为**实验性**,需装有 claude CLI 后按实际抓包校正 `dialog.ts` 与 `bridge.ts` 的 `onUserDialog`。
|
|
79
|
+
|
|
80
|
+
## 官方插件(official-plugin)
|
|
81
|
+
|
|
82
|
+
本包同时包含一个**符合 Claude Code 官方插件规范**的插件(`official-plugin/`,随 npm 发布),
|
|
83
|
+
由 Agent SDK 在每个驱动会话里加载(`options.plugins`)——功能需求主通道仍是 SDK 桥(流式/会话/审批),插件提供会话内扩展:
|
|
84
|
+
|
|
85
|
+
- `/tunnelbox:pair`、`/tunnelbox:status`(commands + skill):在 Claude 会话里直接取/查手机配对状态(`bin/tunnelbox.mjs`,自包含 Node,状态存 `CLAUDE_PLUGIN_DATA`)。
|
|
86
|
+
- 审批**双通道规避**:SDK 桥会注入 `TUNNELBOX_SDK_SESSION=1`,插件 `PermissionRequest` hook 检测到后**不拦截**(交给 `canUseTool`),避免双弹;
|
|
87
|
+
仅当独立 `claude --plugin-dir ./official-plugin` 本地会话时,hook 才把当回合工具审批转发到手机(未绑定/中继不可达时不输出,交本地询问)。
|
|
88
|
+
|
|
89
|
+
本地直接体验插件:`claude --plugin-dir ./plugin/claude-code/official-plugin`(校验:`claude plugin validate ./plugin/claude-code/official-plugin`)。
|
|
90
|
+
|
|
91
|
+
> 决策:功能主体始终由 Agent SDK 桥承担(列表/流式/审批/中止);official-plugin 只是**会话内扩展子集**(配对/状态/可选审批),不是实现远程功能所必需。
|
|
92
|
+
|
|
93
|
+
## 目录结构
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
plugin/claude-code/
|
|
97
|
+
├── package.json
|
|
98
|
+
├── tsconfig.json
|
|
99
|
+
├── scripts/build.mjs # esbuild 单文件 dist/index.mjs(external @anthropic-ai/*、qrcode)
|
|
100
|
+
├── scripts/smoke.mjs # 冒烟:连本地中继验证配对流程(不驱动 claude)
|
|
101
|
+
├── official-plugin/ # ★ 官方 Claude Code 插件(随包发布,SDK options.plugins 加载)
|
|
102
|
+
│ ├── .claude-plugin/plugin.json
|
|
103
|
+
│ ├── commands/ # pair.md / status.md
|
|
104
|
+
│ ├── skills/pair/SKILL.md
|
|
105
|
+
│ ├── hooks/hooks.json # PermissionRequest → bin/tunnelbox.mjs ask(SDK 会话不拦截)
|
|
106
|
+
│ └── bin/ # tunnelbox.mjs / tunnelbox(.cmd) PATH 壳
|
|
107
|
+
└── src/
|
|
108
|
+
├── index.ts # 入口:env 解析(mode/model/thinking/dialogKinds/warm…)+ 连接中继
|
|
109
|
+
├── bridge.ts # relay 连接/配对/消息路由/query 回合/审批/对话框/elicitation
|
|
110
|
+
├── dialog.ts # request_user_dialog kind → 手机卡 启发式映射(实验性)
|
|
111
|
+
├── warm.ts # CLI 子进程预热池(alpha,TUNNELBOX_CLAUDE_WARM 开启)
|
|
112
|
+
├── map.ts # SDK 流事件/内容块 → 协议 Part/ChatMessage(tool_result→tool 卡)
|
|
113
|
+
├── sessions.ts # claude-agent-sdk 会话接口封装
|
|
114
|
+
└── globals.d.ts # WebSocket 全局类型
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 备注
|
|
118
|
+
|
|
119
|
+
- 本适配器状态文件独立:`~/.config/opencode/remote-state.claude-code.json`(与 opencode/dsh 并存不冲突)。
|
|
120
|
+
- 尚未验证项:需本机装有 claude CLI 后实测(流式增量粒度、`Options.sessionId` 固定 UUID、`session.updated` 触发时机、approval 帧、`TUNNELBOX_CLAUDE_DIALOG_KINDS` 真实 kind、`TUNNELBOX_CLAUDE_WARM` 预热进程生命周期)——相关兜底/实验性代码均已就位。
|