@tunnelbox/claude-code 0.1.10 → 0.1.16
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 +137 -137
- package/README.zh-CN.md +137 -137
- package/dist/index.mjs +68 -3
- package/official-plugin/skills/status/SKILL.md +10 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,137 +1,137 @@
|
|
|
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/`.
|
|
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/`.
|
package/README.zh-CN.md
CHANGED
|
@@ -1,137 +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/` 下。
|
|
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/` 下。
|
package/dist/index.mjs
CHANGED
|
@@ -14,6 +14,8 @@ function envelope(type, payload, id) {
|
|
|
14
14
|
}
|
|
15
15
|
var BASE_DELAY = 1e3;
|
|
16
16
|
var MAX_DELAY = 15e3;
|
|
17
|
+
var KEEPALIVE_INTERVAL_MS = 15e3;
|
|
18
|
+
var KEEPALIVE_TIMEOUT_MS = 25e3;
|
|
17
19
|
var RelayClient = class {
|
|
18
20
|
ws = null;
|
|
19
21
|
url = "";
|
|
@@ -23,6 +25,11 @@ var RelayClient = class {
|
|
|
23
25
|
retry = 0;
|
|
24
26
|
stopped = false;
|
|
25
27
|
connected = false;
|
|
28
|
+
reconnectScheduled = false;
|
|
29
|
+
/** 保活心跳定时器(onopen 启动 / onclose 停止) */
|
|
30
|
+
keepaliveTimer = null;
|
|
31
|
+
/** 最近一次收到服务端帧的时间(含心跳回执 pong),用于静默断流检测 */
|
|
32
|
+
lastInbound = 0;
|
|
26
33
|
get isConnected() {
|
|
27
34
|
return this.connected;
|
|
28
35
|
}
|
|
@@ -32,6 +39,8 @@ var RelayClient = class {
|
|
|
32
39
|
this.handlers = handlers;
|
|
33
40
|
this.stopped = false;
|
|
34
41
|
this.retry = 0;
|
|
42
|
+
this.reconnectScheduled = false;
|
|
43
|
+
this.stopKeepalive();
|
|
35
44
|
this.open();
|
|
36
45
|
}
|
|
37
46
|
send(env) {
|
|
@@ -47,6 +56,8 @@ var RelayClient = class {
|
|
|
47
56
|
}
|
|
48
57
|
close() {
|
|
49
58
|
this.stopped = true;
|
|
59
|
+
this.reconnectScheduled = false;
|
|
60
|
+
this.stopKeepalive();
|
|
50
61
|
if (this.timer) clearTimeout(this.timer);
|
|
51
62
|
this.timer = null;
|
|
52
63
|
if (this.ws) {
|
|
@@ -66,11 +77,14 @@ var RelayClient = class {
|
|
|
66
77
|
ws.onopen = () => {
|
|
67
78
|
this.connected = true;
|
|
68
79
|
this.retry = 0;
|
|
80
|
+
this.startKeepalive();
|
|
69
81
|
this.handlers?.onOpen();
|
|
70
82
|
};
|
|
71
83
|
ws.onmessage = (ev) => {
|
|
84
|
+
this.lastInbound = Date.now();
|
|
72
85
|
try {
|
|
73
86
|
const msg = JSON.parse(String(ev.data));
|
|
87
|
+
if (msg.type === "pong") return;
|
|
74
88
|
this.handlers?.onMessage(msg);
|
|
75
89
|
} catch {
|
|
76
90
|
}
|
|
@@ -79,6 +93,7 @@ var RelayClient = class {
|
|
|
79
93
|
ws.onclose = () => {
|
|
80
94
|
this.connected = false;
|
|
81
95
|
this.ws = null;
|
|
96
|
+
this.stopKeepalive();
|
|
82
97
|
this.handlers?.onClose();
|
|
83
98
|
this.scheduleReconnect();
|
|
84
99
|
};
|
|
@@ -88,10 +103,52 @@ var RelayClient = class {
|
|
|
88
103
|
}
|
|
89
104
|
}
|
|
90
105
|
scheduleReconnect() {
|
|
91
|
-
if (this.stopped) return;
|
|
106
|
+
if (this.stopped || this.reconnectScheduled) return;
|
|
107
|
+
this.reconnectScheduled = true;
|
|
92
108
|
const delay = Math.min(BASE_DELAY * 2 ** this.retry, MAX_DELAY);
|
|
93
109
|
this.retry++;
|
|
94
|
-
this.timer = setTimeout(() =>
|
|
110
|
+
this.timer = setTimeout(() => {
|
|
111
|
+
this.reconnectScheduled = false;
|
|
112
|
+
this.open();
|
|
113
|
+
}, delay);
|
|
114
|
+
}
|
|
115
|
+
/** 启动保活心跳:每周期发 ping,长时间收不到任何帧则判定断流主动重连。 */
|
|
116
|
+
startKeepalive() {
|
|
117
|
+
this.stopKeepalive();
|
|
118
|
+
this.lastInbound = Date.now();
|
|
119
|
+
this.keepaliveTimer = setInterval(() => {
|
|
120
|
+
if (!this.connected || !this.ws) return;
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
if (now - this.lastInbound > KEEPALIVE_TIMEOUT_MS) {
|
|
123
|
+
this.forceReconnect();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
this.ws.send(JSON.stringify({ type: "ping", payload: {}, ts: now }));
|
|
128
|
+
} catch {
|
|
129
|
+
}
|
|
130
|
+
}, KEEPALIVE_INTERVAL_MS);
|
|
131
|
+
}
|
|
132
|
+
stopKeepalive() {
|
|
133
|
+
if (this.keepaliveTimer !== null) {
|
|
134
|
+
clearInterval(this.keepaliveTimer);
|
|
135
|
+
this.keepaliveTimer = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** 判定连接已死:关闭当前 socket 并调度重连(沿用指数退避,onclose 去重)。 */
|
|
139
|
+
forceReconnect() {
|
|
140
|
+
if (this.stopped) return;
|
|
141
|
+
this.connected = false;
|
|
142
|
+
this.stopKeepalive();
|
|
143
|
+
const w = this.ws;
|
|
144
|
+
this.ws = null;
|
|
145
|
+
if (w) {
|
|
146
|
+
try {
|
|
147
|
+
w.close();
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
this.scheduleReconnect();
|
|
95
152
|
}
|
|
96
153
|
};
|
|
97
154
|
var zh_CN_default = {
|
|
@@ -110,6 +167,7 @@ var zh_CN_default = {
|
|
|
110
167
|
"agent.log.pendingResent": "session.pending: \u8865\u53D1 {session} \u79EF\u538B\u8BF7\u6C42 {count} \u6761",
|
|
111
168
|
"agent.log.unhandledType": "\u672A\u5904\u7406\u7684\u6D88\u606F\u7C7B\u578B: {type}",
|
|
112
169
|
"events.log.fwdPermission": "\u8F6C\u53D1\u6743\u9650\u8BF7\u6C42\u5230\u624B\u673A (id={id} tool={tool} {version})",
|
|
170
|
+
"events.log.sessionDeleted": "\u8F6C\u53D1 session.deleted \u5230\u624B\u673A (session={session})",
|
|
113
171
|
"events.log.permissionReplied": "opencode \u56DE\u6267 permission.replied (id={id} {response})",
|
|
114
172
|
"events.log.questionRejected": "opencode \u56DE\u6267 question.rejected (id={id})",
|
|
115
173
|
"events.log.questionReplied": "opencode \u56DE\u6267 question.replied (id={id})",
|
|
@@ -275,6 +333,7 @@ var zh_TW_default = {
|
|
|
275
333
|
"agent.log.pendingResent": "session.pending: \u88DC\u767C {session} \u7A4D\u58D3\u8ACB\u6C42 {count} \u689D",
|
|
276
334
|
"agent.log.unhandledType": "\u672A\u8655\u7406\u7684\u8A0A\u606F\u985E\u578B: {type}",
|
|
277
335
|
"events.log.fwdPermission": "\u8F49\u767C\u6B0A\u9650\u8ACB\u6C42\u5230\u624B\u6A5F (id={id} tool={tool} {version})",
|
|
336
|
+
"events.log.sessionDeleted": "\u8F49\u767C session.deleted \u5230\u624B\u6A5F (session={session})",
|
|
278
337
|
"events.log.permissionReplied": "opencode \u56DE\u57F7 permission.replied (id={id} {response})",
|
|
279
338
|
"events.log.questionRejected": "opencode \u56DE\u57F7 question.rejected (id={id})",
|
|
280
339
|
"events.log.questionReplied": "opencode \u56DE\u57F7 question.replied (id={id})",
|
|
@@ -440,6 +499,7 @@ var en_US_default = {
|
|
|
440
499
|
"agent.log.pendingResent": "session.pending: resent {count} pending request(s) for {session}",
|
|
441
500
|
"agent.log.unhandledType": "Unhandled message type: {type}",
|
|
442
501
|
"events.log.fwdPermission": "Forwarded permission request to phone (id={id} tool={tool} {version})",
|
|
502
|
+
"events.log.sessionDeleted": "Forwarded session.deleted to phone (session={session})",
|
|
443
503
|
"events.log.permissionReplied": "opencode acked permission.replied (id={id} {response})",
|
|
444
504
|
"events.log.questionRejected": "opencode acked question.rejected (id={id})",
|
|
445
505
|
"events.log.questionReplied": "opencode acked question.replied (id={id})",
|
|
@@ -605,6 +665,7 @@ var ja_JP_default = {
|
|
|
605
665
|
"agent.log.pendingResent": "session.pending: {session} \u306E\u4FDD\u7559\u30EA\u30AF\u30A8\u30B9\u30C8 {count} \u4EF6\u3092\u518D\u9001",
|
|
606
666
|
"agent.log.unhandledType": "\u672A\u51E6\u7406\u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u30BF\u30A4\u30D7: {type}",
|
|
607
667
|
"events.log.fwdPermission": "\u6A29\u9650\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u643A\u5E2F\u3078\u8EE2\u9001 (id={id} tool={tool} {version})",
|
|
668
|
+
"events.log.sessionDeleted": "session.deleted \u3092\u643A\u5E2F\u3078\u8EE2\u9001 (session={session})",
|
|
608
669
|
"events.log.permissionReplied": "opencode \u5FDC\u7B54 permission.replied (id={id} {response})",
|
|
609
670
|
"events.log.questionRejected": "opencode \u5FDC\u7B54 question.rejected (id={id})",
|
|
610
671
|
"events.log.questionReplied": "opencode \u5FDC\u7B54 question.replied (id={id})",
|
|
@@ -770,6 +831,7 @@ var ko_KR_default = {
|
|
|
770
831
|
"agent.log.pendingResent": "session.pending: {session} \uBCF4\uB958 \uC694\uCCAD {count}\uAC74 \uC7AC\uC804\uC1A1",
|
|
771
832
|
"agent.log.unhandledType": "\uCC98\uB9AC\uB418\uC9C0 \uC54A\uC740 \uBA54\uC2DC\uC9C0 \uC720\uD615: {type}",
|
|
772
833
|
"events.log.fwdPermission": "\uAD8C\uD55C \uC694\uCCAD\uC744 \uD734\uB300\uD3F0\uC73C\uB85C \uC804\uB2EC (id={id} tool={tool} {version})",
|
|
834
|
+
"events.log.sessionDeleted": "session.deleted\uB97C \uD734\uB300\uD3F0\uC73C\uB85C \uC804\uB2EC (session={session})",
|
|
773
835
|
"events.log.permissionReplied": "opencode \uC751\uB2F5 permission.replied (id={id} {response})",
|
|
774
836
|
"events.log.questionRejected": "opencode \uC751\uB2F5 question.rejected (id={id})",
|
|
775
837
|
"events.log.questionReplied": "opencode \uC751\uB2F5 question.replied (id={id})",
|
|
@@ -935,6 +997,7 @@ var fr_FR_default = {
|
|
|
935
997
|
"agent.log.pendingResent": "session.pending : renvoi de {count} demande(s) en attente pour {session}",
|
|
936
998
|
"agent.log.unhandledType": "Type de message non g\xE9r\xE9 : {type}",
|
|
937
999
|
"events.log.fwdPermission": "Demande d'autorisation transmise au t\xE9l\xE9phone (id={id} tool={tool} {version})",
|
|
1000
|
+
"events.log.sessionDeleted": "session.deleted transmise au t\xE9l\xE9phone (session={session})",
|
|
938
1001
|
"events.log.permissionReplied": "opencode a accus\xE9 permission.replied (id={id} {response})",
|
|
939
1002
|
"events.log.questionRejected": "opencode a accus\xE9 question.rejected (id={id})",
|
|
940
1003
|
"events.log.questionReplied": "opencode a accus\xE9 question.replied (id={id})",
|
|
@@ -1100,6 +1163,7 @@ var de_DE_default = {
|
|
|
1100
1163
|
"agent.log.pendingResent": "session.pending: {count} ausstehende Anfrage(n) f\xFCr {session} erneut gesendet",
|
|
1101
1164
|
"agent.log.unhandledType": "Nicht behandelter Nachrichtentyp: {type}",
|
|
1102
1165
|
"events.log.fwdPermission": "Berechtigungsanfrage an Telefon weitergeleitet (id={id} tool={tool} {version})",
|
|
1166
|
+
"events.log.sessionDeleted": "session.deleted an Telefon weitergeleitet (session={session})",
|
|
1103
1167
|
"events.log.permissionReplied": "opencode quittierte permission.replied (id={id} {response})",
|
|
1104
1168
|
"events.log.questionRejected": "opencode quittierte question.rejected (id={id})",
|
|
1105
1169
|
"events.log.questionReplied": "opencode quittierte question.replied (id={id})",
|
|
@@ -1265,6 +1329,7 @@ var es_ES_default = {
|
|
|
1265
1329
|
"agent.log.pendingResent": "session.pending: reenviadas {count} solicitud(es) pendientes para {session}",
|
|
1266
1330
|
"agent.log.unhandledType": "Tipo de mensaje no gestionado: {type}",
|
|
1267
1331
|
"events.log.fwdPermission": "Solicitud de permiso reenviada al tel\xE9fono (id={id} tool={tool} {version})",
|
|
1332
|
+
"events.log.sessionDeleted": "session.deleted reenviada al tel\xE9fono (session={session})",
|
|
1268
1333
|
"events.log.permissionReplied": "opencode confirm\xF3 permission.replied (id={id} {response})",
|
|
1269
1334
|
"events.log.questionRejected": "opencode confirm\xF3 question.rejected (id={id})",
|
|
1270
1335
|
"events.log.questionReplied": "opencode confirm\xF3 question.replied (id={id})",
|
|
@@ -2111,7 +2176,7 @@ function newSessionId() {
|
|
|
2111
2176
|
}
|
|
2112
2177
|
|
|
2113
2178
|
// src/bridge.ts
|
|
2114
|
-
var VERSION = true ? "0.1.
|
|
2179
|
+
var VERSION = true ? "0.1.16" : "0.0.0-dev";
|
|
2115
2180
|
var AGENT_TYPE = "claude-code";
|
|
2116
2181
|
var APPROVAL_TIMEOUT_MS = 12e4;
|
|
2117
2182
|
var ADAPTER_COMMANDS = [
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: 显示 tunnelbox 中继连接与手机绑定状态。当用户询问本机与手机的连接状态 / 是否已绑定账号 / 中继地址时自动调用。
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# tunnelbox 连接状态
|
|
6
|
-
|
|
7
|
-
本技能用于查看本机与手机 tunnelbox 的中继连接与绑定状态。
|
|
8
|
-
|
|
9
|
-
1. 运行命令:`tunnelbox status`
|
|
10
|
-
2. 把命令输出的状态(中继地址、agent 标识、是否已绑定、配对信息文件位置)完整、原样展示给用户。
|
|
1
|
+
---
|
|
2
|
+
description: 显示 tunnelbox 中继连接与手机绑定状态。当用户询问本机与手机的连接状态 / 是否已绑定账号 / 中继地址时自动调用。
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# tunnelbox 连接状态
|
|
6
|
+
|
|
7
|
+
本技能用于查看本机与手机 tunnelbox 的中继连接与绑定状态。
|
|
8
|
+
|
|
9
|
+
1. 运行命令:`tunnelbox status`
|
|
10
|
+
2. 把命令输出的状态(中继地址、agent 标识、是否已绑定、配对信息文件位置)完整、原样展示给用户。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tunnelbox/claude-code",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "tunnelbox 的 Claude Code 集成(官方 Agent SDK 桥 + 官方插件 official-plugin):手机远程驱动 Claude Code(会话/流式/审批/中止)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"qrcode": "^1.5.3"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@tunnelbox/core": "0.1.
|
|
37
|
+
"@tunnelbox/core": "0.1.16",
|
|
38
38
|
"@types/node": "^22.0.0",
|
|
39
39
|
"@types/qrcode": "^1.5.5",
|
|
40
40
|
"esbuild": "^0.24.0",
|