@caoruhua/open-claude-remote 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/LICENSE +21 -0
- package/README.md +449 -0
- package/dist/api/auth-routes.d.ts +4 -0
- package/dist/api/auth-routes.d.ts.map +1 -0
- package/dist/api/auth-routes.js +7 -0
- package/dist/api/auth-routes.js.map +1 -0
- package/dist/api/config-routes.d.ts +4 -0
- package/dist/api/config-routes.d.ts.map +1 -0
- package/dist/api/config-routes.js +180 -0
- package/dist/api/config-routes.js.map +1 -0
- package/dist/api/health-routes.d.ts +3 -0
- package/dist/api/health-routes.d.ts.map +1 -0
- package/dist/api/health-routes.js +9 -0
- package/dist/api/health-routes.js.map +1 -0
- package/dist/api/hook-routes.d.ts +4 -0
- package/dist/api/hook-routes.d.ts.map +1 -0
- package/dist/api/hook-routes.js +32 -0
- package/dist/api/hook-routes.js.map +1 -0
- package/dist/api/instance-routes.d.ts +20 -0
- package/dist/api/instance-routes.d.ts.map +1 -0
- package/dist/api/instance-routes.js +128 -0
- package/dist/api/instance-routes.js.map +1 -0
- package/dist/api/push-routes.d.ts +5 -0
- package/dist/api/push-routes.d.ts.map +1 -0
- package/dist/api/push-routes.js +45 -0
- package/dist/api/push-routes.js.map +1 -0
- package/dist/api/router.d.ts +19 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +37 -0
- package/dist/api/router.js.map +1 -0
- package/dist/api/status-routes.d.ts +5 -0
- package/dist/api/status-routes.d.ts.map +1 -0
- package/dist/api/status-routes.js +17 -0
- package/dist/api/status-routes.js.map +1 -0
- package/dist/attach.d.ts +9 -0
- package/dist/attach.d.ts.map +1 -0
- package/dist/attach.js +155 -0
- package/dist/attach.js.map +1 -0
- package/dist/auth/auth-middleware.d.ts +52 -0
- package/dist/auth/auth-middleware.d.ts.map +1 -0
- package/dist/auth/auth-middleware.js +124 -0
- package/dist/auth/auth-middleware.js.map +1 -0
- package/dist/auth/rate-limiter.d.ts +37 -0
- package/dist/auth/rate-limiter.d.ts.map +1 -0
- package/dist/auth/rate-limiter.js +81 -0
- package/dist/auth/rate-limiter.js.map +1 -0
- package/dist/auth/token-generator.d.ts +9 -0
- package/dist/auth/token-generator.d.ts.map +1 -0
- package/dist/auth/token-generator.js +15 -0
- package/dist/auth/token-generator.js.map +1 -0
- package/dist/cli-utils.d.ts +19 -0
- package/dist/cli-utils.d.ts.map +1 -0
- package/dist/cli-utils.js +132 -0
- package/dist/cli-utils.js.map +1 -0
- package/dist/cli.d.ts +21 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +58 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +146 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +329 -0
- package/dist/config.js.map +1 -0
- package/dist/hooks/hook-receiver.d.ts +39 -0
- package/dist/hooks/hook-receiver.d.ts.map +1 -0
- package/dist/hooks/hook-receiver.js +46 -0
- package/dist/hooks/hook-receiver.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +353 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/logger.d.ts +8 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +90 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/notification/dingtalk-service.d.ts +24 -0
- package/dist/notification/dingtalk-service.d.ts.map +1 -0
- package/dist/notification/dingtalk-service.js +94 -0
- package/dist/notification/dingtalk-service.js.map +1 -0
- package/dist/pty/output-buffer.d.ts +25 -0
- package/dist/pty/output-buffer.d.ts.map +1 -0
- package/dist/pty/output-buffer.js +58 -0
- package/dist/pty/output-buffer.js.map +1 -0
- package/dist/pty/pty-manager.d.ts +45 -0
- package/dist/pty/pty-manager.d.ts.map +1 -0
- package/dist/pty/pty-manager.js +108 -0
- package/dist/pty/pty-manager.js.map +1 -0
- package/dist/pty/types.d.ts +11 -0
- package/dist/pty/types.d.ts.map +1 -0
- package/dist/pty/types.js +2 -0
- package/dist/pty/types.js.map +1 -0
- package/dist/pty/virtual-pty.d.ts +37 -0
- package/dist/pty/virtual-pty.d.ts.map +1 -0
- package/dist/pty/virtual-pty.js +161 -0
- package/dist/pty/virtual-pty.js.map +1 -0
- package/dist/push/push-service.d.ts +87 -0
- package/dist/push/push-service.d.ts.map +1 -0
- package/dist/push/push-service.js +301 -0
- package/dist/push/push-service.js.map +1 -0
- package/dist/registry/instance-registry.d.ts +32 -0
- package/dist/registry/instance-registry.d.ts.map +1 -0
- package/dist/registry/instance-registry.js +115 -0
- package/dist/registry/instance-registry.js.map +1 -0
- package/dist/registry/instance-spawner.d.ts +33 -0
- package/dist/registry/instance-spawner.d.ts.map +1 -0
- package/dist/registry/instance-spawner.js +91 -0
- package/dist/registry/instance-spawner.js.map +1 -0
- package/dist/registry/port-finder.d.ts +8 -0
- package/dist/registry/port-finder.d.ts.map +1 -0
- package/dist/registry/port-finder.js +35 -0
- package/dist/registry/port-finder.js.map +1 -0
- package/dist/registry/shared-token.d.ts +11 -0
- package/dist/registry/shared-token.d.ts.map +1 -0
- package/dist/registry/shared-token.js +82 -0
- package/dist/registry/shared-token.js.map +1 -0
- package/dist/registry/stop-instances.d.ts +27 -0
- package/dist/registry/stop-instances.d.ts.map +1 -0
- package/dist/registry/stop-instances.js +212 -0
- package/dist/registry/stop-instances.js.map +1 -0
- package/dist/session/session-controller.d.ts +58 -0
- package/dist/session/session-controller.d.ts.map +1 -0
- package/dist/session/session-controller.js +273 -0
- package/dist/session/session-controller.js.map +1 -0
- package/dist/terminal/terminal-relay.d.ts +29 -0
- package/dist/terminal/terminal-relay.d.ts.map +1 -0
- package/dist/terminal/terminal-relay.js +106 -0
- package/dist/terminal/terminal-relay.js.map +1 -0
- package/dist/utils/ansi-filter.d.ts +41 -0
- package/dist/utils/ansi-filter.d.ts.map +1 -0
- package/dist/utils/ansi-filter.js +147 -0
- package/dist/utils/ansi-filter.js.map +1 -0
- package/dist/utils/file-lock.d.ts +23 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +125 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/dist/utils/ip-monitor.d.ts +39 -0
- package/dist/utils/ip-monitor.d.ts.map +1 -0
- package/dist/utils/ip-monitor.js +114 -0
- package/dist/utils/ip-monitor.js.map +1 -0
- package/dist/utils/network.d.ts +19 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +54 -0
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/pid-file.d.ts +10 -0
- package/dist/utils/pid-file.d.ts.map +1 -0
- package/dist/utils/pid-file.js +30 -0
- package/dist/utils/pid-file.js.map +1 -0
- package/dist/utils/qrcode-banner.d.ts +6 -0
- package/dist/utils/qrcode-banner.d.ts.map +1 -0
- package/dist/utils/qrcode-banner.js +16 -0
- package/dist/utils/qrcode-banner.js.map +1 -0
- package/dist/ws/ws-handler.d.ts +10 -0
- package/dist/ws/ws-handler.d.ts.map +1 -0
- package/dist/ws/ws-handler.js +36 -0
- package/dist/ws/ws-handler.js.map +1 -0
- package/dist/ws/ws-server.d.ts +78 -0
- package/dist/ws/ws-server.d.ts.map +1 -0
- package/dist/ws/ws-server.js +223 -0
- package/dist/ws/ws-server.js.map +1 -0
- package/frontend-dist/assets/index-BKudo1Dw.css +32 -0
- package/frontend-dist/assets/index-BqqB1hYe.js +141 -0
- package/frontend-dist/index.html +15 -0
- package/frontend-dist/sw.js +46 -0
- package/package.json +79 -0
- package/shared-dist/constants.d.ts +10 -0
- package/shared-dist/constants.d.ts.map +1 -0
- package/shared-dist/constants.js +10 -0
- package/shared-dist/constants.js.map +1 -0
- package/shared-dist/defaults.d.ts +30 -0
- package/shared-dist/defaults.d.ts.map +1 -0
- package/shared-dist/defaults.js +31 -0
- package/shared-dist/defaults.js.map +1 -0
- package/shared-dist/index.d.ts +5 -0
- package/shared-dist/index.d.ts.map +1 -0
- package/shared-dist/index.js +5 -0
- package/shared-dist/index.js.map +1 -0
- package/shared-dist/instance.d.ts +25 -0
- package/shared-dist/instance.d.ts.map +1 -0
- package/shared-dist/instance.js +7 -0
- package/shared-dist/instance.js.map +1 -0
- package/shared-dist/question-utils.d.ts +15 -0
- package/shared-dist/question-utils.d.ts.map +1 -0
- package/shared-dist/question-utils.js +24 -0
- package/shared-dist/question-utils.js.map +1 -0
- package/shared-dist/ws-protocol.d.ts +60 -0
- package/shared-dist/ws-protocol.d.ts.map +1 -0
- package/shared-dist/ws-protocol.js +5 -0
- package/shared-dist/ws-protocol.js.map +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Anthropic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# Claude Code Remote
|
|
2
|
+
|
|
3
|
+
**手机远程掌控 PC 终端的 Claude Code**
|
|
4
|
+
|
|
5
|
+
在局域网内通过手机浏览器实时查看终端输出、发送指令、审批工具调用——离开工位也能继续工作。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 快速开始 (3 步)
|
|
10
|
+
|
|
11
|
+
### 0. 镜像配置(国内用户)
|
|
12
|
+
|
|
13
|
+
国内网络可能较慢,建议先配置淘宝镜像:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cp .npmrc.example .npmrc
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
或手动设置:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm config set registry https://registry.npmmirror.com
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 1. 安装
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git clone <repo-url> claude-code-remote
|
|
29
|
+
cd claude-code-remote
|
|
30
|
+
./install.sh
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. 启动
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
claude-remote
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 3. 扫码连接
|
|
40
|
+
|
|
41
|
+
PC 终端显示 ASCII 二维码 → 手机扫码 → 自动填充 Token → 开始使用
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 功能亮点
|
|
46
|
+
|
|
47
|
+
### 实时终端同步
|
|
48
|
+
- PC 终端完整输出实时同步到手机
|
|
49
|
+
- 支持 ANSI 颜色渲染
|
|
50
|
+
- 10K 行历史记录,重连自动恢复
|
|
51
|
+
- **IP 变化通知**:PC 局域网 IP 变化时自动通知,提供新连接地址
|
|
52
|
+
|
|
53
|
+
### 快捷按键栏
|
|
54
|
+
- 一键发送 Esc、方向键、Ctrl+C 等
|
|
55
|
+
- 自定义快捷键(在设置中配置)
|
|
56
|
+
- 预设常用命令(/help, /clear 等)
|
|
57
|
+
|
|
58
|
+
### 多实例管理
|
|
59
|
+
- 启动多个claude-remote
|
|
60
|
+
- 浏览器网页打开,顶部Tab切换无需重新认证
|
|
61
|
+
- 实例掉线自动切换到下一个
|
|
62
|
+
- **Web 创建实例**:通过 UI 创建新实例(点击 Tab 栏的 "+" 按钮,工作目录限制`workspaces`白名单和已启动实例的工作目录)
|
|
63
|
+
- **PC Attach**:`claude-remote attach <port|name>` 接管 web 创建的实例
|
|
64
|
+
|
|
65
|
+
### Claude Code窗口自适应
|
|
66
|
+
|
|
67
|
+
**优先级**:WebApp(手机端)> Attach(PC 端)> PC 终端
|
|
68
|
+
|
|
69
|
+
| 场景 | 窗口控制者 | 说明 |
|
|
70
|
+
|------|-------|------|
|
|
71
|
+
| 无客户端连接 | PC 终端 | 正常本地使用 |
|
|
72
|
+
| 仅 WebApp 连接 | WebApp | 手机端调整窗口大小|
|
|
73
|
+
| 仅 Attach 连接 | Attach | PC 端 attach 接管尺寸 |
|
|
74
|
+
| WebApp + Attach | WebApp | 手机端为主控,PC 端跟随 |
|
|
75
|
+
|
|
76
|
+
**行为说明**:
|
|
77
|
+
- 当 WebApp 在线时,Attach 客户端的 resize 请求会被忽略
|
|
78
|
+
- Attach 连接后会自动同步 WebApp 设置的尺寸
|
|
79
|
+
- 调整 WebApp 窗口大小,Attach 终端会跟随变化
|
|
80
|
+
|
|
81
|
+
### 通知提醒
|
|
82
|
+
|
|
83
|
+
Claude 等待输入时,可通过多种方式收到提醒:
|
|
84
|
+
|
|
85
|
+
#### Web 推送通知(手机浏览器)
|
|
86
|
+
|
|
87
|
+
手机浏览器接收实时推送通知,即使切换到其他应用或锁屏也能收到。
|
|
88
|
+
|
|
89
|
+
**启用步骤**:
|
|
90
|
+
1. 手机浏览器访问 Web UI
|
|
91
|
+
2. 浏览器会自动请求通知权限,点击"允许"
|
|
92
|
+
3. 后台自动完成订阅,无需手动配置
|
|
93
|
+
|
|
94
|
+
**工作原理**:
|
|
95
|
+
- 浏览器使用 Push API 订阅服务器
|
|
96
|
+
- p256dh 密钥由浏览器自动生成(65 字节 ECDH 公钥)
|
|
97
|
+
- 订阅数据存储在 `~/.claude-remote/push-subscriptions.json`
|
|
98
|
+
|
|
99
|
+
**故障排查**:
|
|
100
|
+
- 确保浏览器已授予通知权限
|
|
101
|
+
- 清理无效订阅:`rm ~/.claude-remote/push-subscriptions.json`,然后重新访问 Web UI
|
|
102
|
+
|
|
103
|
+
#### 钉钉群通知
|
|
104
|
+
|
|
105
|
+
配置钉钉群机器人 Webhook,即使手机不在身边也能收到提醒。
|
|
106
|
+
|
|
107
|
+
**配置步骤**:
|
|
108
|
+
1. 打开钉钉群 → 群设置 → 智能群助手 → 添加机器人 → 自定义
|
|
109
|
+
2. 安全设置选择"自定义关键词",添加关键词 `Claude`(消息标题已包含此词)
|
|
110
|
+
3. 复制 Webhook URL
|
|
111
|
+
4. 在 Web UI 设置界面 → 钉钉 Tab 中粘贴,或直接写入配置文件:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"dingtalk": {
|
|
116
|
+
"webhookUrl": "https://oapi.dingtalk.com/robot/send?access_token=your-token"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Web UI 设置界面
|
|
122
|
+
- 可视化配置快捷键
|
|
123
|
+
- 命令管理(自动补足 `/` 前缀)
|
|
124
|
+
- 配置即时生效
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 常见问题
|
|
129
|
+
|
|
130
|
+
### 安装失败排查
|
|
131
|
+
|
|
132
|
+
#### 网络超时/连接失败
|
|
133
|
+
|
|
134
|
+
配置淘宝镜像:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
cp .npmrc.example .npmrc
|
|
138
|
+
pnpm install
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### node-pty 编译失败
|
|
142
|
+
|
|
143
|
+
安装编译工具:
|
|
144
|
+
|
|
145
|
+
| 系统 | 命令 |
|
|
146
|
+
|------|------|
|
|
147
|
+
| macOS | `xcode-select --install` |
|
|
148
|
+
| Ubuntu/Debian | `sudo apt-get install build-essential python3` |
|
|
149
|
+
| Fedora/RHEL | `sudo dnf groupinstall 'Development Tools'` |
|
|
150
|
+
| Arch Linux | `sudo pacman -S base-devel python` |
|
|
151
|
+
|
|
152
|
+
#### pnpm 未找到
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npm install -g pnpm@latest
|
|
156
|
+
# 或使用 corepack
|
|
157
|
+
corepack enable && corepack prepare pnpm@latest --activate
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### 清理缓存后重试
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
pnpm store prune
|
|
164
|
+
rm -rf node_modules pnpm-lock.yaml
|
|
165
|
+
pnpm install
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### 找不到 `claude-remote` 命令?
|
|
169
|
+
|
|
170
|
+
确保已执行 `pnpm link -g`,或使用 `node backend/dist/cli.js` 直接运行。
|
|
171
|
+
|
|
172
|
+
### 二维码扫不出来?
|
|
173
|
+
|
|
174
|
+
1. 确保手机和 PC 在同一局域网
|
|
175
|
+
2. 手动访问终端显示的 URL,输入 Token
|
|
176
|
+
|
|
177
|
+
### 手机无法连接?
|
|
178
|
+
|
|
179
|
+
1. 检查 PC 防火墙是否放行端口(默认 3000)
|
|
180
|
+
2. 检查终端显示的 URL 是否为正确的局域网 IP
|
|
181
|
+
3. 如果使用 VPN,可能需要配置 `host` 选项(见下方配置说明)
|
|
182
|
+
|
|
183
|
+
### 如何配置 Hook(审批通知)?
|
|
184
|
+
|
|
185
|
+
首次启动后执行:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
./scripts/setup-hooks.sh
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Hook 配置详情请参见 [ARCHITECTURE.md#routes](./ARCHITECTURE.md#5-routes)。
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 基本用法
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# 直接运行(等同于 claude)
|
|
199
|
+
claude-remote
|
|
200
|
+
|
|
201
|
+
# 透传参数
|
|
202
|
+
claude-remote chat
|
|
203
|
+
claude-remote -- --dangerously-skip-permissions
|
|
204
|
+
|
|
205
|
+
# 自定义选项
|
|
206
|
+
claude-remote --port 8080
|
|
207
|
+
claude-remote --name my-project
|
|
208
|
+
|
|
209
|
+
# 接管 web 创建的实例
|
|
210
|
+
claude-remote attach 3001 # 按端口
|
|
211
|
+
claude-remote attach my-project # 按名称
|
|
212
|
+
|
|
213
|
+
# Web 创建实例(无终端模式)
|
|
214
|
+
claude-remote --no-terminal # 不启动本地终端,仅提供 Web 界面
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 停止服务
|
|
218
|
+
|
|
219
|
+
- 单次 Ctrl+C:发送给 Claude Code(取消当前任务)
|
|
220
|
+
- **两次 Ctrl+C(间隔 < 500ms)**:停止代理层
|
|
221
|
+
|
|
222
|
+
停止所有实例:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
pnpm stop
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 安全
|
|
231
|
+
|
|
232
|
+
- **Token 认证**:32 字节随机 Token,首次启动生成
|
|
233
|
+
- **Session Cookie**:HttpOnly + SameSite=Lax,24 小时有效
|
|
234
|
+
- **网络隔离**:仅绑定局域网 IP,无公网暴露
|
|
235
|
+
- **速率限制**:认证接口 5 次/分钟/IP
|
|
236
|
+
- **Hook 安全**:仅接受 localhost 请求
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 配置文件
|
|
241
|
+
|
|
242
|
+
配置文件位于 `~/.claude-remote/config.json`。项目根目录提供 `config.example.json` 作为模板:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
cp config.example.json ~/.claude-remote/config.json
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 完整配置项
|
|
249
|
+
|
|
250
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
251
|
+
|--------|------|--------|------|
|
|
252
|
+
| `port` | number | 3000 | 服务监听端口(被占用自动递增) |
|
|
253
|
+
| `host` | string | "0.0.0.0" | 绑定地址 |
|
|
254
|
+
| `token` | string \| null | null | 认证 Token,`null` 自动生成共享 Token |
|
|
255
|
+
| `claudeCommand` | string | "claude" | Claude CLI 命令路径(可用绝对路径) |
|
|
256
|
+
| `claudeArgs` | string[] | [] | Claude CLI 额外参数 |
|
|
257
|
+
| `claudeCwd` | string \| null | null | Claude 工作目录,`null` 使用当前目录 |
|
|
258
|
+
| `sessionTtlMs` | number | 86400000 | Session 有效期(毫秒,默认 24 小时) |
|
|
259
|
+
| `authRateLimit` | number | 20 | 认证速率限制(每分钟每 IP 次数) |
|
|
260
|
+
| `maxBufferLines` | number | 10000 | 输出缓冲区最大行数 |
|
|
261
|
+
| `instanceName` | string \| null | null | 实例名称,`null` 使用工作目录名 |
|
|
262
|
+
| `shortcuts` | array | 见下方 | 快捷输入列表(缺失时 API 返回默认值,不写入文件) |
|
|
263
|
+
| `commands` | array | 见下方 | 自定义命令列表(缺失时 API 返回默认值,不写入文件) |
|
|
264
|
+
| `workspaces` | string[] | [] | 预设工作目录列表(Web 创建实例的白名单) |
|
|
265
|
+
| `dingtalk` | object | - | 钉钉通知配置(见下方) |
|
|
266
|
+
|
|
267
|
+
**优先级**:CLI 参数 > 配置文件 > 默认值
|
|
268
|
+
|
|
269
|
+
### 快捷输入 (shortcuts)
|
|
270
|
+
|
|
271
|
+
快捷输入显示在终端下方的快捷栏,用于快速发送常用按键或文本。
|
|
272
|
+
|
|
273
|
+
**当配置文件中未定义 `shortcuts` 字段时,API 返回时自动填充以下默认值:**
|
|
274
|
+
|
|
275
|
+
| 按键 | 数据 | 说明 |
|
|
276
|
+
|------|------|------|
|
|
277
|
+
| Esc | `\u001b` | ESC 键 |
|
|
278
|
+
| Enter | `\r` | 回车键 |
|
|
279
|
+
| Tab | `\t` | Tab 键 |
|
|
280
|
+
| ↑ | `\u001b[A` | 上箭头 |
|
|
281
|
+
| ↓ | `\u001b[B` | 下箭头 |
|
|
282
|
+
| ← | `\u001b[D` | 左箭头 |
|
|
283
|
+
| → | `\u001b[C` | 右箭头 |
|
|
284
|
+
| S-Tab | `\u001b[Z` | Shift+Tab |
|
|
285
|
+
|
|
286
|
+
**自定义示例:**
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"shortcuts": [
|
|
291
|
+
{ "label": "确认", "data": "y", "enabled": true, "desc": "确认操作" },
|
|
292
|
+
{ "label": "取消", "data": "\u001b", "enabled": true, "desc": "取消操作 (ESC)" },
|
|
293
|
+
{ "label": "继续", "data": "\r", "enabled": true, "desc": "继续执行 (Enter)" },
|
|
294
|
+
{ "label": "Ctrl+C", "data": "\u0003", "enabled": true, "desc": "中断当前操作" },
|
|
295
|
+
{ "label": "是的", "data": "yes", "enabled": false, "desc": "完整 yes 输入" }
|
|
296
|
+
]
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
| 字段 | 类型 | 说明 |
|
|
301
|
+
|------|------|------|
|
|
302
|
+
| `label` | string | 按钮显示名称 |
|
|
303
|
+
| `data` | string | 发送的数据(支持转义字符如 `\u001b` 表示 ESC) |
|
|
304
|
+
| `enabled` | boolean | 是否启用 |
|
|
305
|
+
| `desc` | string | 描述(可选) |
|
|
306
|
+
|
|
307
|
+
**常用转义值**:
|
|
308
|
+
- `\u001b` - ESC 键
|
|
309
|
+
- `\r` - Enter 键
|
|
310
|
+
- `\u0003` - Ctrl+C
|
|
311
|
+
- `\u0004` - Ctrl+D
|
|
312
|
+
|
|
313
|
+
### 自定义命令 (commands)
|
|
314
|
+
|
|
315
|
+
自定义命令显示在快捷栏的命令区域。
|
|
316
|
+
|
|
317
|
+
**当配置文件中未定义 `commands` 字段时,API 返回时自动填充以下默认值:**
|
|
318
|
+
|
|
319
|
+
| 命令 | 说明 |
|
|
320
|
+
|------|------|
|
|
321
|
+
| /clear | 清屏 |
|
|
322
|
+
| /compact | 紧凑对话 |
|
|
323
|
+
| /resume | 恢复会话 |
|
|
324
|
+
| /stats | 统计信息 |
|
|
325
|
+
| /exit | 退出 |
|
|
326
|
+
| /commit-commands:commit | Git 提交 |
|
|
327
|
+
| /feature-dev:feature-dev | 功能开发 |
|
|
328
|
+
| /auto-doc | 自动文档 |
|
|
329
|
+
| /code-review-expert | 代码审查 |
|
|
330
|
+
| /systematic-debugging | 系统调试 |
|
|
331
|
+
|
|
332
|
+
**自定义示例:**
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"commands": [
|
|
337
|
+
{ "label": "/help", "command": "/help", "enabled": true, "desc": "帮助说明" },
|
|
338
|
+
{ "label": "/clear", "command": "/clear", "enabled": true },
|
|
339
|
+
{ "label": "Git Status", "command": "git status", "enabled": true, "autoSend": false, "desc": "填入输入框编辑后发送" },
|
|
340
|
+
{ "label": "/feature-dev:feature-dev", "command": "/feature-dev:feature-dev", "enabled": false, "desc": "启动feature-dev SKILL" }
|
|
341
|
+
]
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
346
|
+
|------|------|--------|------|
|
|
347
|
+
| `label` | string | - | 按钮显示名称 |
|
|
348
|
+
| `command` | string | - | 要执行的命令 |
|
|
349
|
+
| `enabled` | boolean | - | 是否启用 |
|
|
350
|
+
| `autoSend` | boolean | `true` | `true`=点击直接发送,`false`=填入输入框 |
|
|
351
|
+
| `desc` | string | - | 描述(可选) |
|
|
352
|
+
|
|
353
|
+
### 工作区白名单 (workspaces)
|
|
354
|
+
|
|
355
|
+
配置后,通过 Web 创建实例时只能选择这些目录:
|
|
356
|
+
|
|
357
|
+
```json
|
|
358
|
+
{
|
|
359
|
+
"workspaces": [
|
|
360
|
+
"/Users/tom/projects/api",
|
|
361
|
+
"/Users/tom/projects/web",
|
|
362
|
+
"/Users/tom/projects/cli"
|
|
363
|
+
]
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
若未配置,Web 创建实例时只能选择 home 目录下的项目。
|
|
368
|
+
|
|
369
|
+
### 钉钉通知 (dingtalk)
|
|
370
|
+
|
|
371
|
+
配置钉钉群机器人 Webhook,当 Claude 需要输入时发送通知到钉钉群:
|
|
372
|
+
|
|
373
|
+
```json
|
|
374
|
+
{
|
|
375
|
+
"dingtalk": {
|
|
376
|
+
"webhookUrl": "https://oapi.dingtalk.com/robot/send?access_token=your-token"
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
获取 Webhook URL:
|
|
382
|
+
1. 打开钉钉群 → 群设置 → 智能群助手 → 添加机器人 → 自定义
|
|
383
|
+
2. 安全设置选择"自定义关键词",添加关键词 `Claude`(消息标题已包含此词)
|
|
384
|
+
3. 复制 Webhook URL 到配置文件
|
|
385
|
+
|
|
386
|
+
也可以在 Web UI 设置界面中配置(推荐)。
|
|
387
|
+
|
|
388
|
+
> **注意**:修改钉钉配置后需要重启服务才能生效。
|
|
389
|
+
|
|
390
|
+
### 完整示例
|
|
391
|
+
|
|
392
|
+
```json
|
|
393
|
+
{
|
|
394
|
+
"port": 3000,
|
|
395
|
+
"host": "0.0.0.0",
|
|
396
|
+
"token": null,
|
|
397
|
+
|
|
398
|
+
"claudeCommand": "claude",
|
|
399
|
+
"claudeArgs": ["--no-telemetry"],
|
|
400
|
+
"claudeCwd": null,
|
|
401
|
+
|
|
402
|
+
"sessionTtlMs": 86400000,
|
|
403
|
+
"authRateLimit": 20,
|
|
404
|
+
"maxBufferLines": 10000,
|
|
405
|
+
"instanceName": null,
|
|
406
|
+
|
|
407
|
+
"shortcuts": [
|
|
408
|
+
{ "label": "确认", "data": "y", "enabled": true },
|
|
409
|
+
{ "label": "取消", "data": "\u001b", "enabled": true },
|
|
410
|
+
{ "label": "继续", "data": "\r", "enabled": true },
|
|
411
|
+
{ "label": "Ctrl+C", "data": "\u0003", "enabled": true }
|
|
412
|
+
],
|
|
413
|
+
|
|
414
|
+
"commands": [
|
|
415
|
+
{ "label": "Git Status", "command": "git status", "enabled": true },
|
|
416
|
+
{ "label": "Git Log", "command": "git log --oneline -10", "enabled": true }
|
|
417
|
+
],
|
|
418
|
+
|
|
419
|
+
"workspaces": [
|
|
420
|
+
"/Users/tom/projects/api",
|
|
421
|
+
"/Users/tom/projects/web"
|
|
422
|
+
]
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## 前置条件
|
|
429
|
+
|
|
430
|
+
- Node.js >= 20
|
|
431
|
+
- pnpm >= 9
|
|
432
|
+
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) 已安装
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## 开发者文档
|
|
437
|
+
|
|
438
|
+
参见 [ARCHITECTURE.md](./ARCHITECTURE.md),包含:
|
|
439
|
+
- 技术栈详情
|
|
440
|
+
- 架构分层
|
|
441
|
+
- 数据流图
|
|
442
|
+
- API 路由
|
|
443
|
+
- 部署说明
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## License
|
|
448
|
+
|
|
449
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-routes.d.ts","sourceRoot":"","sources":["../../src/api/auth-routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAExD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAM/D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-routes.js","sourceRoot":"","sources":["../../src/api/auth-routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,MAAM,UAAU,gBAAgB,CAAC,UAAsB;IACrD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAE5C,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-routes.d.ts","sourceRoot":"","sources":["../../src/api/config-routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAKjC,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AA0GxD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CA4EjE"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
3
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
import { createAuthRoutes } from './auth-routes.js';
|
|
7
|
+
import { logger } from '../logger/logger.js';
|
|
8
|
+
import { withFileLockAsync } from '../utils/file-lock.js';
|
|
9
|
+
import { fillDefaultShortcuts, fillDefaultCommands, } from '../config.js';
|
|
10
|
+
const CONFIG_DIR = join(homedir(), '.claude-remote');
|
|
11
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
12
|
+
const CONFIG_LOCK = CONFIG_FILE + '.lock';
|
|
13
|
+
/**
|
|
14
|
+
* 验证配置结构
|
|
15
|
+
*/
|
|
16
|
+
function validateConfigStructure(config) {
|
|
17
|
+
if (!config || typeof config !== 'object')
|
|
18
|
+
return false;
|
|
19
|
+
const cfg = config;
|
|
20
|
+
// 所有字段都是可选的,但如果存在必须类型正确
|
|
21
|
+
// 服务配置
|
|
22
|
+
if ('port' in cfg && typeof cfg.port !== 'number')
|
|
23
|
+
return false;
|
|
24
|
+
if ('host' in cfg && typeof cfg.host !== 'string')
|
|
25
|
+
return false;
|
|
26
|
+
if ('token' in cfg && typeof cfg.token !== 'string')
|
|
27
|
+
return false;
|
|
28
|
+
// Claude CLI 配置
|
|
29
|
+
if ('claudeCommand' in cfg && typeof cfg.claudeCommand !== 'string')
|
|
30
|
+
return false;
|
|
31
|
+
if ('claudeCwd' in cfg && typeof cfg.claudeCwd !== 'string')
|
|
32
|
+
return false;
|
|
33
|
+
if ('claudeArgs' in cfg && !Array.isArray(cfg.claudeArgs))
|
|
34
|
+
return false;
|
|
35
|
+
// 运行时配置
|
|
36
|
+
if ('sessionTtlMs' in cfg && typeof cfg.sessionTtlMs !== 'number')
|
|
37
|
+
return false;
|
|
38
|
+
if ('authRateLimit' in cfg && typeof cfg.authRateLimit !== 'number')
|
|
39
|
+
return false;
|
|
40
|
+
if ('maxBufferLines' in cfg && typeof cfg.maxBufferLines !== 'number')
|
|
41
|
+
return false;
|
|
42
|
+
if ('instanceName' in cfg && typeof cfg.instanceName !== 'string')
|
|
43
|
+
return false;
|
|
44
|
+
// shortcuts 可选,如果存在必须是数组
|
|
45
|
+
if ('shortcuts' in cfg && cfg.shortcuts !== undefined) {
|
|
46
|
+
if (!Array.isArray(cfg.shortcuts))
|
|
47
|
+
return false;
|
|
48
|
+
for (const item of cfg.shortcuts) {
|
|
49
|
+
if (!item || typeof item !== 'object')
|
|
50
|
+
return false;
|
|
51
|
+
const s = item;
|
|
52
|
+
if (typeof s.label !== 'string' || typeof s.data !== 'string' || typeof s.enabled !== 'boolean') {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (s.desc !== undefined && typeof s.desc !== 'string')
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// commands 可选,如果存在必须是数组
|
|
60
|
+
if ('commands' in cfg && cfg.commands !== undefined) {
|
|
61
|
+
if (!Array.isArray(cfg.commands))
|
|
62
|
+
return false;
|
|
63
|
+
for (const item of cfg.commands) {
|
|
64
|
+
if (!item || typeof item !== 'object')
|
|
65
|
+
return false;
|
|
66
|
+
const c = item;
|
|
67
|
+
if (typeof c.label !== 'string' || typeof c.command !== 'string' || typeof c.enabled !== 'boolean') {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (c.desc !== undefined && typeof c.desc !== 'string')
|
|
71
|
+
return false;
|
|
72
|
+
if (c.autoSend !== undefined && typeof c.autoSend !== 'boolean')
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// dingtalk 可选,如果存在必须是对象且包含 webhookUrl
|
|
77
|
+
if ('dingtalk' in cfg && cfg.dingtalk !== undefined) {
|
|
78
|
+
if (typeof cfg.dingtalk !== 'object' || cfg.dingtalk === null)
|
|
79
|
+
return false;
|
|
80
|
+
const dt = cfg.dingtalk;
|
|
81
|
+
if (typeof dt.webhookUrl !== 'string')
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 读取用户配置文件
|
|
88
|
+
*/
|
|
89
|
+
async function loadUserConfig() {
|
|
90
|
+
try {
|
|
91
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const content = await readFile(CONFIG_FILE, 'utf-8');
|
|
95
|
+
return JSON.parse(content);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
logger.error({ error, path: CONFIG_FILE }, 'Failed to load user config');
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 保存配置文件
|
|
104
|
+
*/
|
|
105
|
+
async function saveUserConfig(config) {
|
|
106
|
+
// 确保目录存在
|
|
107
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
108
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
109
|
+
}
|
|
110
|
+
// 直接写入文件
|
|
111
|
+
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
112
|
+
}
|
|
113
|
+
export function createConfigRoutes(authModule) {
|
|
114
|
+
const router = Router();
|
|
115
|
+
// 复用 auth 路由以支持测试认证
|
|
116
|
+
router.use(createAuthRoutes(authModule));
|
|
117
|
+
/**
|
|
118
|
+
* GET /api/config - 获取用户配置
|
|
119
|
+
*/
|
|
120
|
+
router.get('/config', authModule.requireAuth.bind(authModule), async (req, res) => {
|
|
121
|
+
try {
|
|
122
|
+
const config = await loadUserConfig();
|
|
123
|
+
if (!config) {
|
|
124
|
+
// 配置文件不存在,返回 null 让前端使用自己的默认值
|
|
125
|
+
res.json({ config: null, configPath: CONFIG_FILE });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// 懒填充默认值(不持久化,仅返回时填充)
|
|
129
|
+
// 好处:配置文件保持精简,默认值更新时用户自动受益
|
|
130
|
+
let filledConfig = config;
|
|
131
|
+
if (!config.shortcuts) {
|
|
132
|
+
filledConfig = fillDefaultShortcuts(filledConfig);
|
|
133
|
+
}
|
|
134
|
+
if (!config.commands) {
|
|
135
|
+
filledConfig = fillDefaultCommands(filledConfig);
|
|
136
|
+
}
|
|
137
|
+
// 安全处理:不暴露完整 webhook URL,只返回是否已配置
|
|
138
|
+
const { token: _, dingtalk: dtConfig, ...safeConfig } = filledConfig;
|
|
139
|
+
const responseConfig = {
|
|
140
|
+
...safeConfig,
|
|
141
|
+
dingtalk: {
|
|
142
|
+
configured: !!(dtConfig?.webhookUrl),
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
res.json({ config: responseConfig, configPath: CONFIG_FILE });
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
logger.error({ error }, 'Failed to get config');
|
|
149
|
+
res.status(500).json({ error: 'Failed to load config' });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
/**
|
|
153
|
+
* PUT /api/config - 更新用户配置
|
|
154
|
+
*/
|
|
155
|
+
router.put('/config', authModule.requireAuth.bind(authModule), async (req, res) => {
|
|
156
|
+
try {
|
|
157
|
+
const newConfig = req.body;
|
|
158
|
+
// 验证配置结构
|
|
159
|
+
if (!validateConfigStructure(newConfig)) {
|
|
160
|
+
res.status(400).json({ error: 'Invalid config structure' });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// 文件锁保护 read-modify-write,防止与其他模块并发写入冲突
|
|
164
|
+
await withFileLockAsync(CONFIG_LOCK, async () => {
|
|
165
|
+
// 合并现有配置和新配置(前端可能只发送部分字段)
|
|
166
|
+
const existingConfig = await loadUserConfig();
|
|
167
|
+
const mergedConfig = { ...existingConfig, ...newConfig };
|
|
168
|
+
await saveUserConfig(mergedConfig);
|
|
169
|
+
});
|
|
170
|
+
logger.info({ configPath: CONFIG_FILE }, 'User config updated');
|
|
171
|
+
res.json({ success: true, configPath: CONFIG_FILE });
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
logger.error({ error }, 'Failed to update config');
|
|
175
|
+
res.status(500).json({ error: 'Failed to save config' });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
return router;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=config-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-routes.js","sourceRoot":"","sources":["../../src/api/config-routes.ts"],"names":[],"mappings":"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"}
|