@tunnelbox/claude-code 0.1.3 → 0.1.5

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 CHANGED
@@ -1,147 +1,137 @@
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(默认注入 production 中继)
23
- node dist/index.mjs # npm start
24
- ```
25
-
26
- 本地连 dev 中继:`npm run build:dev` 后 `node dist/index.mjs`(构建已注入 `ws://192.168.1.124:8081`,无需再设环境变量)。
27
-
28
- **配对引导(智能,无需任何环境变量)**:启动自动连接中继并以 relay 的真实绑定状态为准——**未绑定** → 自动申请配对码并打印二维码框引导配对;**已绑定** → 打印「已绑定账号,从手机「我的电脑」直接连接」,不重复刷码。需要随时再取一个码:`node dist/index.mjs --pair`(强制申请并打印一次,已绑定时附提示)。
29
-
30
- > 首次运行时若 SDK 缺失会提示先 `npm install`。
31
-
32
- ## 配置
33
-
34
- | 配置 | 默认 | 说明 |
35
- |---|---|---|
36
- | `TUNNELBOX_RELAY_URL` | state 里保存的地址(`~/.tunnelbox/remote-state.claude-code.json`) | 中继地址(如 `wss://chat.example.com`) |
37
- | `TUNNELBOX_CWD` | `process.cwd()` | 默认工作区;也可在手机上通过工作区列表切换 |
38
- | `TUNNELBOX_CLAUDE_MODE` | `default` | 每回合 `permissionMode`:`default/plan/acceptEdits/bypassPermissions/dontAsk/auto`。`bypassPermissions` 全放行且能力位 `permission=false`(危险,慎用);该模式会同时设置 SDK 要求的 `allowDangerouslySkipPermissions=true`。`dontAsk`/`auto` 下未预批准的工具不会触发 `canUseTool`,手机审批通道基本不工作 |
39
- | `TUNNELBOX_CLAUDE_MODEL` | claude 默认 | 模型覆盖(`options.model`) |
40
- | `TUNNELBOX_CLAUDE_THINKING` | claude 默认 | `off \| adaptive \| enabled[:budget]` → `options.thinking` / `maxThinkingTokens` |
41
- | `TUNNELBOX_CLAUDE_MAX_THINKING_TOKENS` | - | 思考 token 上限 |
42
- | `TUNNELBOX_CLAUDE_ENV_*` | - | 透传给 claude 子进程的环境变量(前缀去掉,如 `TUNNELBOX_CLAUDE_ENV_ANTHROPIC_BASE_URL=…` → `options.env`)。**子进程 env 继承自适配器 `process.env`**(SDK `options.env` 是整体替换而非合并,适配器会自动并入 `process.env`,此配置仅在其上覆盖) |
43
- | `TUNNELBOX_CLAUDE_DIALOG_KINDS` | | `request_user_dialog` kind 白名单(逗号分隔,实验性,需实测后填真实 kind) |
44
- | `TUNNELBOX_CLAUDE_ASK_USER_QUESTION` | 开 | Claude `AskUserQuestion`(模型向你提问)→ 手机**结构化提问卡**(question.request,多题/多选/自定义),作答后以 `allow+updatedInput.answers` 回填(实测该工具经 `canUseTool` 送达);设 `0` 关闭(自动拒绝,不做审批) |
45
- | `TUNNELBOX_CLAUDE_PLUGIN_DIR` | 包内 `official-plugin/` | 覆盖要加载的**官方 Claude Code 插件**目录(Agent SDK `options.plugins`) |
46
- | `TUNNELBOX_CLAUDE_WARM` | | (已停用)常驻长活会话天然免逐条冷启动,预热池不再参与驱动;保留 env 仅为兼容旧配置 |
47
-
48
- ```bash
49
- TUNNELBOX_RELAY_URL=wss://chat.example.com TUNNELBOX_CWD=D:/workspace/project \
50
- TUNNELBOX_CLAUDE_MODE=default TUNNELBOX_CLAUDE_THINKING=adaptive node dist/index.mjs
51
- ```
52
-
53
- > 模式为会话级:`session.prompt.agent:"plan"` 会把该会话切到 plan 模式,`"build"` 切回默认。
54
-
55
- ## 多语言
56
-
57
- 适配器用户可见输出遵循仓库硬性约定(8 语言 `zh-CN / zh-TW / en-US / ja-JP / ko-KR / fr-FR / de-DE / es-ES`;文案唯一数据源 `plugin/core/locales/*.json`,缺 key 回退 `en-US`)。**核心进程(`node dist/index.mjs`)输出已全量本地化**,随语言切换:
58
-
59
- - **语言解析**:`TUNNELBOX_LANG` > `remote-state.claude-code.json` `lang` > 系统 locale 检测 > 默认 `en-US`(`resolveLang`,`index.ts`/`bridge.ts` 一致)。
60
- - **设置运行语言**(语言不打包,与 opencode 一致):`npm run setup -- --lang en-US` 把语言写入 state(保留 agentID/绑定),之后 `node dist/index.mjs` 即按该语言输出;`--lang help` 列出支持语言:
61
- ```bash
62
- npm run setup -- --lang en-US # 设为英文
63
- npm run setup -- --lang zh-CN # 改回中文
64
- npm run setup # 查看当前语言
65
- ```
66
- - 已覆盖:启动横幅与参数告警(index)、连接/配对/断线/重连日志、`/help` 与会话标题、工作区错误、审批卡 prompt 与各类 fail-closed message、AskUserQuestion/结构化提问与对话框日志、MCP elicitation、会话/历史读取错误、工具结果截断提示等(`bridge.ts`/`sessions.ts`/`map.ts`/`dialog.ts`)。
67
- - 终端配对框 `printPairing` 按同一语言输出。
68
- - 官方插件 `bin/tunnelbox.mjs`(独立 `claude --plugin-dir` 会话的审批决策 message)为自包含纯源码分发(不经 esbuild,无法读 `core/locales`),内置精简双语字典(`zh-CN`/回退 `en-US`)。
69
-
70
- ## 能力位(agent.info.capabilities)
71
-
72
- | 能力 | 值 | 实现 |
73
- |---|---|---|
74
- | streaming | true | `includePartialMessages` 流事件(text/thinking delta)→ `message.part` |
75
- | thinking | true | 模型开启思考时 `thinking_delta` → thinking 部件 |
76
- | permission | 默认 true | `canUseTool` → 手机审批(离线/断线/超时 fail-closed 拒绝);`mode=bypassPermissions` 时为 false(不注册 canUseTool) |
77
- | commands | true | 适配器命令 `/new` `/help` |
78
- | abort | **true** | 手机 `session.abort` → 常驻 query `interrupt()` 打断当前轮(会话进程保持存活) |
79
-
80
- ## 会话与工作区
81
-
82
- - **会话 id = Claude 会话 UUID**:新会话由适配器生成 UUID(`query options.sessionId` 固定 id,首次 prompt 落盘);也能在列表里看到并 resume 你在终端里开过的既有会话。
83
- - 数据源:`claude-agent-sdk` 的 `listSessions / getSessionInfo / getSessionMessages / deleteSession`(不手读 JSONL),会话/凭证沿用 `~/.claude`,与本地 Claude Code 互不干扰。
84
- - 工作区 = 会话的 `cwd`;跨会话并行(每会话一个**常驻子进程**),同一会话运行中禁止再发。
85
- - **常驻长活会话(streaming-input)**:每个会话首次手机消息时 `query({ prompt: 常开 AsyncIterable })` 拉起一个 claude 子进程并**保持存活**(官方 Streaming Input Mode / 长活进程,无顶层会话超时);后续消息经 `PushChannel` 推入**同一进程**续聊,无逐条冷启动延迟、上下文连续。每轮以 `result` 消息结束(广播 `session.updated` + idle),进程继续等待下一轮。
86
- - `session.abort``query.interrupt()` 打断当前轮;`session.delete` / 适配器退出`channel.end()` + `query.close()` 结束子进程。子进程异常退出后,下一次消息以 `resume` 自动重建常驻会话续上下文。
87
- - 历史拉取会把 `tool_result` 折叠成独立的 `role:"tool"` 卡(结果截断 ≤2KB);断线期间的流式增量不补发,重连后由手机重拉 `session.messages` 恢复。
88
-
89
- ## 工具审批(安全)
90
-
91
- - 每个会话的 `permissionMode` 由配置/agent 决定(会话建立时生效;手机切 plan/build 会在会话内 `setPermissionMode` 热切)+ `canUseTool` 拦截:中继在线 → 推手机审批卡(允许/拒绝/总是允许→写入 session 级建议规则);**中继断线 / 120s 超时 / 中止 → 一律自动拒绝(fail-closed)**,绝不自动放行;断线瞬间会即时清掉所有待审批,无需等满超时。
92
- - CLI 交互提示一致:审批卡片展示 Claude 的原生提示(`title`/`description`)。
93
- - 手机在线时审批**优先于 PC**:适配器是 headless 独立进程,claude 子进程无本地 TUI,审批全部经 `canUseTool` 回包;离线时没有本地兜底(拒绝),与 opencode(TUI 兜底)/ dsh(dsh web 兜底)不同,详见 `docs/适配器接口规范.md` §3.3。
94
-
95
- ## 对话框 / MCP elicitation(实验性)
96
-
97
- - `request_user_dialog`(Claude 的"选择/确认"阻塞对话框):已实现 `onUserDialog` + `supportedDialogKinds`;kind 白名单走 `TUNNELBOX_CLAUDE_DIALOG_KINDS`(**默认空 → SDK 不发任何对话框**,fail-closed)。启用后按 `dialog.ts` 的启发式映射把 payload 转成手机 `choice/input` 卡,作答值回填 `UserDialogResult.completed`。
98
- - MCP elicitation:`mode=url` 弹手机审批卡(允许=accept);`form` 及其余保持自动拒绝。
99
- - ⚠️ 对话框 payload/result kind 相关结构、随 CLI 版本演进:以上映射为**实验性**,需装有 claude CLI 后按实际抓包校正 `dialog.ts` 与 `bridge.ts` 的 `onUserDialog`。
100
-
101
- ## 官方插件(official-plugin)
102
-
103
- 本包同时包含一个**符合 Claude Code 官方插件规范**的插件(`official-plugin/`,随 npm 发布),
104
- Agent SDK 在每个驱动会话里加载(`options.plugins`)——功能需求主通道仍是 SDK 桥(流式/会话/审批),插件提供会话内扩展:
105
-
106
- - 斜杠技能 `/tunnelbox:pair`、`/tunnelbox:status`(`skills/pair`、`skills/status`,Claude Code 插件中 `commands/` `skills/` 都注册为技能,统一放 `skills/` 避免同名重复):在 Claude 会话里直接取/查手机配对状态(`bin/tunnelbox.mjs`,自包含 Node,状态存 `CLAUDE_PLUGIN_DATA`)。
107
- - 审批**双通道规避**:SDK 桥会注入 `TUNNELBOX_SDK_SESSION=1`,插件 `PermissionRequest` hook 检测到后**不拦截**(交给 `canUseTool`),避免双弹;
108
- 仅当独立 `claude --plugin-dir ./official-plugin` 本地会话时,hook 才把当回合工具审批转发到手机(未绑定/中继不可达时不输出,交本地询问)。
109
- - hook 输出为 **PermissionRequest 官方 schema**:`{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"|"deny","message":…,"updatedPermissions":[…]}}}`(exit 0 + 单行 JSON);deny 原因放 `message`;手机「总是允许」(`always`)时把 hook 输入 `permission_suggestions` 原样回填 `updatedPermissions`。`hooks/hooks.json` 拆两条 matcher:精确工具列表(`Bash|Edit|Write|WebFetch|WebSearch|NotebookEdit|Task|Agent`)+ 正则 `mcp__.*`(真实 MCP 工具名前缀是小写 `mcp__`,`MCP.*` 匹配不到)。
110
- - `bin` 提示文案精简双语(`zh-CN`/其余回退 `en-US`,取 `TUNNELBOX_LANG`/系统 locale);完整 8 语言与唯一数据源(`plugin/core/locales/*.json`)见下「多语言」。
111
-
112
- 本地直接体验插件:`claude --plugin-dir ./plugin/claude-code/official-plugin`(校验:`claude plugin validate ./plugin/claude-code/official-plugin`)。
113
-
114
- > 决策:功能主体始终由 Agent SDK 桥承担(列表/流式/审批/中止);official-plugin 只是**会话内扩展子集**(配对/状态/可选审批),不是实现远程功能所必需。
115
-
116
- ## 目录结构
117
-
118
- ```
119
- plugin/claude-code/
120
- ├── package.json
121
- ├── tsconfig.json
122
- ├── scripts/build.mjs # esbuild 单文件 dist/index.mjs(external @anthropic-ai/*、qrcode)
123
- ├── scripts/setup.mjs # 设置运行语言(--lang state,对齐 opencode;语言不打包)
124
- ├── scripts/smoke.mjs # 冒烟:连本地中继验证配对流程(不驱动 claude)
125
- ├── official-plugin/ # 官方 Claude Code 插件(随包发布,SDK options.plugins 加载)
126
- │ ├── .claude-plugin/plugin.json
127
- │ ├── skills/ # 斜杠技能(commands/ 与 skills/ 都注册为技能,统一用 skills/,避免同名重复)
128
- │ │ ├── pair/SKILL.md
129
- │ │ └── status/SKILL.md
130
- │ ├── hooks/hooks.json # PermissionRequest → bin/tunnelbox.mjs ask(SDK 会话不拦截)
131
- │ └── bin/ # tunnelbox.mjs / tunnelbox(.cmd) PATH 壳
132
- └── src/
133
- ├── index.ts # 入口:env 解析(mode/model/thinking/dialogKinds…)+ 连接中继
134
- ├── bridge.ts # relay 连接/配对/消息路由/常驻长活会话(query+PushChannel)/审批/对话框/elicitation
135
- ├── dialog.ts # request_user_dialog kind → 手机卡 启发式映射(实验性)
136
- ├── warm.ts # (已停用,保留)CLI 子进程预热池,常驻会话不再需要
137
- ├── map.ts # SDK 流事件/内容块 协议 Part/ChatMessage(tool_result→tool 卡;TurnStreamer 跨轮复用)
138
- ├── sessions.ts # claude-agent-sdk 会话接口封装
139
- └── globals.d.ts # WebSocket 全局类型
140
- ```
141
-
142
- ## 备注
143
-
144
- - 基准依赖:`@anthropic-ai/claude-agent-sdk`(当前 ^0.3.258,随包 Claude Code 2.1.x;SDK 版本与 Claude Code 版本同步对应)。
145
- - 本适配器状态文件独立:`~/.tunnelbox/remote-state.claude-code.json`(tunnelbox 独立根目录,与各适配器并存不冲突)。
146
- - 已按官方契约修正/明确的点:子进程 `env` 自动并入适配器 `process.env`(SDK `options.env` 为整体替换);`bypassPermissions` 自动置 `allowDangerouslySkipPermissions`;**常驻长活会话**(streaming-input:每会话一个 `query({prompt:常开 AsyncIterable})`,`result` 为轮界、进程跨轮存活,`session.abort` 走 `query.interrupt()`);`warm` 预热池已停用(常驻会话免冷启动)。
147
- - 尚未验证项(需装有 claude CLI 并登录后可实跑验证):流式增量粒度与 assistant 兜底去重(SDK 自述一次 turn 多个 assistant 消息共享 message.id、每条 content 只装一块)、`Options.sessionId` 固定 UUID 能否进 `listSessions`、`redacted_thinking`/`signature_delta` 展示、`TUNNELBOX_CLAUDE_DIALOG_KINDS` 真实 kind 载荷、approval 帧——相关兜底/实验性代码均已就位。
1
+ # @tunnelbox/claude-code
2
+
3
+ [English](README.md) · [简体中文](README.zh-CN.md)
4
+
5
+ **Official website:** [www.tunnelbox.top](https://www.tunnelbox.top)
6
+
7
+ **tunnelbox Claude Code adapter (standalone daemon):** drive Claude Code on your computer from your phone — sessions, streaming, tool approval, abort, delete. It runs as a background Node process that dials **out** to a relay only (no public IP, no port forwarding).
8
+
9
+ ```
10
+ Phone app ──WSS──► relay ──WSS──► tunnelbox-claude-code (this adapter)
11
+ └─ @anthropic-ai/claude-agent-sdk query()
12
+ └─ spawns your local claude CLI
13
+ ```
14
+
15
+ ## Quick start (install → run → pair → use)
16
+
17
+ Follow these steps once on a computer that has Claude Code installed and signed in.
18
+
19
+ ### Step 0 — Prepare your phone
20
+
21
+ Install the tunnelbox App (Android & iOS) from the official website: **https://www.tunnelbox.top**
22
+
23
+ - Sign in / create an account when prompted.
24
+
25
+ ### Step 1 — Install the adapter on your computer
26
+
27
+ Prerequisites: Node.js ≥ 22, and **Claude Code CLI** installed & signed in (`claude --version`, `~/.claude/.credentials.json` exists). The adapter drives your local `claude` binary — the phone never needs Claude installed.
28
+
29
+ ```bash
30
+ npm install -g @tunnelbox/claude-code
31
+ ```
32
+
33
+ ### Step 2 — Run it
34
+
35
+ ```bash
36
+ tunnelbox-claude-code
37
+ ```
38
+
39
+ On first start the adapter connects to the relay and prints a **pairing QR code + pairing code** in the terminal.
40
+
41
+ ### Step 3 Pair with your phone
42
+
43
+ In the phone app tap **Scan to pair** and scan the terminal QR code (or type the pairing code manually). The code is single-use and valid for about 10 minutes.
44
+
45
+ - Once bound, the adapter prints "connected account open the app and pick this computer" on later starts and does not refresh the code.
46
+ - Want a fresh code any time? Run `tunnelbox-claude-code --pair`.
47
+
48
+ ### Step 4 — Use it from your phone
49
+
50
+ In the app, open this computer and you can start new sessions, send messages, watch streaming output (text + thinking), approve risky tool calls, answer Claude's structured questions, and abort a running session.
51
+
52
+ ## Configuration
53
+
54
+ | Env var | Default | Description |
55
+ |---|---|---|
56
+ | `TUNNELBOX_RELAY_URL` | saved relay URL | Relay address (e.g. `wss://chat.example.com`) |
57
+ | `TUNNELBOX_CWD` | `process.cwd()` | Default workspace; switch per-session from the phone workspace picker |
58
+ | `TUNNELBOX_CLAUDE_MODE` | `default` | Per-turn `permissionMode`: `default/plan/acceptEdits/bypassPermissions/dontAsk/auto`. `bypassPermissions` allows everything (dangerous) and reports `permission=false` |
59
+ | `TUNNELBOX_CLAUDE_MODEL` | claude default | Model override (`options.model`) |
60
+ | `TUNNELBOX_CLAUDE_THINKING` | claude default | `off \| adaptive \| enabled[:budget]` |
61
+ | `TUNNELBOX_CLAUDE_MAX_THINKING_TOKENS` | - | Thinking token budget |
62
+ | `TUNNELBOX_CLAUDE_ENV_*` | - | Extra env vars passed to the claude subprocess (prefix stripped, e.g. `TUNNELBOX_CLAUDE_ENV_ANTHROPIC_BASE_URL=…`) |
63
+ | `TUNNELBOX_CLAUDE_DIALOG_KINDS` | empty | Whitelist of `request_user_dialog` kinds (experimental) |
64
+ | `TUNNELBOX_CLAUDE_ASK_USER_QUESTION` | on | Claude `AskUserQuestion` → phone structured question card; set `0` to disable (auto-reject) |
65
+ | `TUNNELBOX_CLAUDE_PLUGIN_DIR` | bundled `official-plugin/` | Claude Code plugin directory loaded via `options.plugins` |
66
+
67
+ ```bash
68
+ TUNNELBOX_RELAY_URL=wss://chat.example.com TUNNELBOX_CWD=D:/workspace/project tunnelbox-claude-code
69
+ ```
70
+
71
+ > Sessions are per-agent: sending `plan` mode switches that session to plan mode; `build` switches back.
72
+
73
+ ## Capabilities
74
+
75
+ | Capability | Value | Notes |
76
+ |---|---|---|
77
+ | streaming | | `includePartialMessages` stream events → live deltas |
78
+ | thinking | | `thinking_delta` → thinking parts |
79
+ | permission | ✅ default | `canUseTool` → phone approval card (fail-closed); `false` under `mode=bypassPermissions` |
80
+ | commands | ✅ | `/new`, `/help` |
81
+ | abort | ✅ | `session.abort` interrupts the running turn (resident process stays alive) |
82
+ | workspaces | | per-session `cwd` |
83
+
84
+ ## Phone approval (security)
85
+
86
+ - Approval goes through `canUseTool`: relay online push approval card (allow / deny / always-allow); **relay offline / 120s timeout / aborted always denied (fail-closed)** never auto-approved. Pending approvals are cleared immediately on disconnect.
87
+ - Approval cards show Claude's native prompt (`title`/`description`).
88
+ - While the phone is online it decides first — the adapter is headless, so there is no local fallback when offline (requests are denied).
89
+
90
+ ## Sessions & workspaces
91
+
92
+ - **Session id = Claude session UUID.** New sessions are created by the adapter; sessions you opened in the terminal also appear in the list and can be resumed.
93
+ - Data is managed via the official `claude-agent-sdk` (`listSessions`/`getSessionMessages`/…), reusing `~/.claude` no conflicts with your local Claude Code.
94
+ - **Resident long-lived sessions:** the first phone message spawns a claude subprocess that stays alive (official Streaming Input Mode); later messages continue in the same process — no per-message cold start. `session.abort` interrupts the current turn only.
95
+ - Workspace = the session's `cwd`; sessions run in parallel (one resident subprocess each); a running session can't receive another message.
96
+
97
+ ## Questions & dialogs (experimental)
98
+
99
+ - Claude `AskUserQuestion` a **structured question card** on the phone (multiple questions/choices/custom input).
100
+ - `request_user_dialog` is whitelist-gated by `TUNNELBOX_CLAUDE_DIALOG_KINDS` (empty by default → the SDK sends no dialogs, fail-closed).
101
+
102
+ > ⚠️ Dialog payloads are kind-specific and evolve with the CLI; these mappings are experimental.
103
+
104
+ ## Official plugin (bundled)
105
+
106
+ This package ships a Claude Code plugin (`official-plugin/`) that the SDK loads into every driven session (`options.plugins`) — slash skills `/tunnelbox:pair` and `/tunnelbox:status` let you view/pair phone status inside a Claude session. The main remote feature set comes from the SDK bridge; the plugin is an in-session extension.
107
+
108
+ ## Multilingual
109
+
110
+ User-visible output supports 8 languages (`zh-CN` / `zh-TW` / `en-US` / `ja-JP` / `ko-KR` / `fr-FR` / `de-DE` / `es-ES`). Resolution order: `TUNNELBOX_LANG` > saved `remote-state.claude-code.json.lang` > system locale > `en-US`.
111
+
112
+ ## Troubleshooting
113
+
114
+ | Problem | Fix |
115
+ |---|---|
116
+ | No QR / pairing code on screen | Run `tunnelbox-claude-code --pair` to print a fresh one |
117
+ | Need to pair a different phone | Unbind in the app settings, then run with `--pair` again |
118
+ | Already bound but phone doesn't connect | Confirm both use the same relay; check `~/.tunnelbox/tunnelbox.log` |
119
+ | Use your own relay | Start with `TUNNELBOX_RELAY_URL=wss://<your-relay>` |
120
+
121
+ ## State files
122
+
123
+ - `~/.tunnelbox/remote-state.claude-code.json` agentID / relay / bound state / language
124
+ - `~/.tunnelbox/remote-pairing.claude-code.txt` + `remote-pairing.json` — latest pairing info
125
+ - `~/.tunnelbox/tunnelbox.log` adapter log
126
+
127
+ ## Uninstall
128
+
129
+ ```bash
130
+ npm uninstall -g @tunnelbox/claude-code
131
+ ```
132
+
133
+ Remove `~/.tunnelbox` (all adapters' shared state) only if you no longer use any tunnelbox adapter.
134
+
135
+ ## Development
136
+
137
+ Implementation details (protocol mapping, source layout, design decisions, untested items) are kept in the source repository as `DEV.md` next to this package, plus the protocol spec under the repo's `docs/`.
@@ -0,0 +1,137 @@
1
+ # @tunnelbox/claude-code
2
+
3
+ [English](README.md) · [简体中文](README.zh-CN.md)
4
+
5
+ **官网:** [www.tunnelbox.top](https://www.tunnelbox.top)
6
+
7
+ **tunnelbox Claude Code 适配器(独立进程)**:用手机远程驱动电脑上的 Claude Code——会话 / 流式 / 工具审批 / 中止 / 删除。它以常驻 Node 进程运行,只向中继发出站连接(无需公网 IP / 端口映射)。
8
+
9
+ ```
10
+ 手机 App ──WSS──► relay ──WSS──► tunnelbox-claude-code(本适配器)
11
+ └─ @anthropic-ai/claude-agent-sdk query()
12
+ └─ 拉起本机 claude CLI 子进程
13
+ ```
14
+
15
+ ## 快速上手(安装 → 运行 → 配对 → 使用)
16
+
17
+ 在装有 Claude Code 并已登录的电脑上,按以下步骤操作一次即可。
18
+
19
+ ### 第 0 步 — 准备手机
20
+
21
+ 在官网下载并安装 tunnelbox App(Android / iOS 均已上架):**https://www.tunnelbox.top**
22
+
23
+ - 按提示登录/注册账号。
24
+
25
+ ### 第 1 步 — 电脑上安装适配器
26
+
27
+ 前置条件:Node.js ≥ 22,且本机已安装并登录 **Claude Code CLI**(`claude --version` 可用、`~/.claude/.credentials.json` 存在)。适配器驱动本机 `claude` 子进程——手机端无需安装 Claude。
28
+
29
+ ```bash
30
+ npm install -g @tunnelbox/claude-code
31
+ ```
32
+
33
+ ### 第 2 步 — 运行
34
+
35
+ ```bash
36
+ tunnelbox-claude-code
37
+ ```
38
+
39
+ 首次启动会连接中继,并在终端打印**配对二维码 + 配对码**。
40
+
41
+ ### 第 3 步 — 与手机配对
42
+
43
+ 在手机 App 点「扫一扫配对」,扫描终端二维码(或手动输入配对码)。配对码一次性有效,约 10 分钟。
44
+
45
+ - 绑定后再次启动会打印「已绑定账号,从手机「我的电脑」直接连接」,不再刷新码;
46
+ - 随时需要新码:运行 `tunnelbox-claude-code --pair`。
47
+
48
+ ### 第 4 步 — 在手机上使用
49
+
50
+ 在 App 里打开这台电脑,即可新建会话、发消息、看流式输出(文本 + 思考)、审批危险工具调用、回答 Claude 的结构化提问、中止运行中的会话。
51
+
52
+ ## 配置
53
+
54
+ | 环境变量 | 默认 | 说明 |
55
+ |---|---|---|
56
+ | `TUNNELBOX_RELAY_URL` | state 保存的地址 | 中继地址(如 `wss://chat.example.com`) |
57
+ | `TUNNELBOX_CWD` | `process.cwd()` | 默认工作区;手机上可按会话切换工作区 |
58
+ | `TUNNELBOX_CLAUDE_MODE` | `default` | 每回合 `permissionMode`:`default/plan/acceptEdits/bypassPermissions/dontAsk/auto`。`bypassPermissions` 全放行(危险),并上报 `permission=false` |
59
+ | `TUNNELBOX_CLAUDE_MODEL` | claude 默认 | 模型覆盖(`options.model`) |
60
+ | `TUNNELBOX_CLAUDE_THINKING` | claude 默认 | `off \| adaptive \| enabled[:budget]` |
61
+ | `TUNNELBOX_CLAUDE_MAX_THINKING_TOKENS` | - | 思考 token 上限 |
62
+ | `TUNNELBOX_CLAUDE_ENV_*` | - | 透传给 claude 子进程的环境变量(前缀去掉,如 `TUNNELBOX_CLAUDE_ENV_ANTHROPIC_BASE_URL=…`) |
63
+ | `TUNNELBOX_CLAUDE_DIALOG_KINDS` | 空 | `request_user_dialog` kind 白名单(实验性) |
64
+ | `TUNNELBOX_CLAUDE_ASK_USER_QUESTION` | 开 | Claude `AskUserQuestion` → 手机**结构化提问卡**;设 `0` 关闭(自动拒绝) |
65
+ | `TUNNELBOX_CLAUDE_PLUGIN_DIR` | 包内 `official-plugin/` | Claude Code 插件目录(`options.plugins` 加载) |
66
+
67
+ ```bash
68
+ TUNNELBOX_RELAY_URL=wss://chat.example.com TUNNELBOX_CWD=D:/workspace/project tunnelbox-claude-code
69
+ ```
70
+
71
+ > 模式为会话级:发 `plan` 把该会话切到 plan 模式,`build` 切回默认。
72
+
73
+ ## 能力位
74
+
75
+ | 能力 | 值 | 说明 |
76
+ |---|---|---|
77
+ | streaming | ✅ | `includePartialMessages` 流事件 → 实时增量 |
78
+ | thinking | ✅ | `thinking_delta` → thinking 部件 |
79
+ | permission | ✅ 默认 | `canUseTool` → 手机审批卡(fail-closed);`mode=bypassPermissions` 时为 false |
80
+ | commands | ✅ | `/new`、`/help` |
81
+ | abort | ✅ | `session.abort` 打断当前回合(常驻进程保持存活) |
82
+ | workspaces | ✅ | 每会话一个 `cwd` |
83
+
84
+ ## 手机审批(安全)
85
+
86
+ - 审批经 `canUseTool`:中继在线 → 推审批卡(允许 / 拒绝 / 总是允许);**中继断线 / 120s 超时 / 中止 → 一律自动拒绝(fail-closed)**,绝不自动放行;断线瞬间即清空待审批。
87
+ - 审批卡展示 Claude 的原生提示(`title`/`description`)。
88
+ - 手机在线时审批优先于 PC:适配器 headless,离线时无本地兜底(一律拒绝)。
89
+
90
+ ## 会话与工作区
91
+
92
+ - **会话 id = Claude 会话 UUID**。新会话由适配器创建;终端里开过的会话也会出现在列表并可 resume。
93
+ - 数据经官方 `claude-agent-sdk`(`listSessions`/`getSessionMessages`/…)读写,沿用 `~/.claude`,与本地 Claude Code 互不干扰。
94
+ - **常驻长活会话**:首次手机消息拉起一个 claude 子进程并**保持存活**(官方 Streaming Input Mode);后续消息在**同一进程**续聊,无逐条冷启动。`session.abort` 只打断当前回合。
95
+ - 工作区 = 会话的 `cwd`;跨会话并行(每会话一个常驻子进程),运行中的会话不能再发消息。
96
+
97
+ ## 提问与对话框(实验性)
98
+
99
+ - Claude `AskUserQuestion` → 手机**结构化提问卡**(多题/多选/自定义输入)。
100
+ - `request_user_dialog` 由 `TUNNELBOX_CLAUDE_DIALOG_KINDS` 白名单门控(默认空 → SDK 不发对话框,fail-closed)。
101
+
102
+ > ⚠️ 对话框载荷是 kind 相关结构、随 CLI 演进;以上映射为实验性。
103
+
104
+ ## 官方插件(随包内置)
105
+
106
+ 本包同时包含一个符合 Claude Code 官方规范的插件(`official-plugin/`),由 Agent SDK 在每个驱动会话里加载(`options.plugins`)——斜杠技能 `/tunnelbox:pair`、`/tunnelbox:status` 可在 Claude 会话内取/查手机配对状态。远程功能主体由 SDK 桥承担,插件只是会话内扩展。
107
+
108
+ ## 多语言
109
+
110
+ 用户可见输出支持 8 种语言(`zh-CN` / `zh-TW` / `en-US` / `ja-JP` / `ko-KR` / `fr-FR` / `de-DE` / `es-ES`)。解析顺序:`TUNNELBOX_LANG` > `remote-state.claude-code.json` 的 `lang` > 系统 locale > 默认 `en-US`。
111
+
112
+ ## 故障排查
113
+
114
+ | 问题 | 解决 |
115
+ |---|---|
116
+ | 终端没有二维码/配对码 | 运行 `tunnelbox-claude-code --pair` 打印新码 |
117
+ | 需要换手机配对 | 在 App 设置里解绑,再带 `--pair` 运行 |
118
+ | 已绑定但手机连不上 | 确认两端同一中继;查看 `~/.tunnelbox/tunnelbox.log` |
119
+ | 使用自建中继 | 启动时带 `TUNNELBOX_RELAY_URL=wss://<你的中继>` |
120
+
121
+ ## 状态文件
122
+
123
+ - `~/.tunnelbox/remote-state.claude-code.json` — agentID / 中继地址 / 绑定状态 / 语言
124
+ - `~/.tunnelbox/remote-pairing.claude-code.txt` + `remote-pairing.json` — 最新配对信息
125
+ - `~/.tunnelbox/tunnelbox.log` — 适配器日志
126
+
127
+ ## 卸载
128
+
129
+ ```bash
130
+ npm uninstall -g @tunnelbox/claude-code
131
+ ```
132
+
133
+ 仅当不再使用任何 tunnelbox 适配器时,才删除 `~/.tunnelbox`(各适配器共享状态)。
134
+
135
+ ## 开发
136
+
137
+ 实现细节(协议映射、源码结构、决策记录、待实测项)保存在源码仓库中本包旁的 `DEV.md` 与仓库 `docs/` 下。