@mearl/cloud-server 3.0.0-beta.0 → 3.0.0-beta.10

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,74 +1,71 @@
1
1
  # @mearl/cloud-server
2
2
 
3
- 云端 WebSocket Server,用于中转云端 Agent 请求到本地连接器。
3
+ [English](README.md) | [简体中文](README.zh-CN.md)
4
4
 
5
- ## 安装
5
+ A cloud WebSocket server routing cloud Agent requests to local connectors.
6
6
 
7
- 推荐通过 setup 安装完整的云端 Agent 环境(cloud-server、统一 client 与 Agent Skill):
7
+ ## Installation
8
+
9
+ Use setup to install a complete cloud Agent environment: cloud-server, the unified client, and Agent Skill.
8
10
 
9
11
  ```bash
10
12
  npx @mearl/setup --cloud
11
13
  ```
12
14
 
13
- Qoder Cloud Agent 使用同一套包和 server 命令,只在 setup 时选择配对模式:
14
-
15
- ```bash
16
- npx @mearl/setup --qoder-cloud --yes
17
- ```
15
+ Installed capability packages provide optional connection protocols. See [`@mearl/plugin-qoder`](../../plugin-qoder/README.md) for Qoder pairing; the core does not include its pairing implementation.
18
16
 
19
- 在 Docker 或自定义进程托管等只需要 Server 包的场景,可单独安装 `@mearl/cloud-server`。
17
+ Install `@mearl/cloud-server` separately when only the server package is needed, such as Docker or custom process management.
20
18
 
21
- ## 使用
19
+ ## Usage
22
20
 
23
- ### 命令行方式
21
+ ### Command line
24
22
 
25
- Server **默认以后台守护进程方式运行**,启动后立即返回,并打印本地机器需要执行的连接命令。
26
- POSIX 环境下后台进程会进入独立 session,可在启动命令结束后继续运行。
27
- Qoder 模式保持相同的 `start/status/restart/stop/logs` 交互;`start` 和 `restart` 生成新的
28
- pair/connector 信息后退出,不监听端口或创建守护进程。
23
+ The server **runs as a background daemon by default**, returns immediately after startup, and prints the connection command to run on the local machine.
24
+ On POSIX systems, the background process enters its own session and continues after the launching command exits.
25
+ Other cloud modes load through installed `cloud.server.<id>` capabilities and fail explicitly if absent.
29
26
 
30
27
  ```bash
31
28
  mearl-cloud-server [command] [options]
32
29
  ```
33
30
 
34
- **子命令:**
31
+ **Subcommands:**
35
32
 
36
- - `start` - 后台启动 server(默认,不写也等价于 `start`)
37
- - `stop` - 停止后台 server
38
- - `restart` - 重启后台 server
39
- - `status` - 查看运行状态及连接信息
40
- - `logs` - 打印 server 日志末尾
33
+ - `start` — Start in the background (default when no subcommand is given).
34
+ - `stop` — Stop the background server.
35
+ - `restart` — Restart the background server.
36
+ - `status` — Show status and connection information.
37
+ - `logs` — Print the end of the server log.
41
38
 
42
- **选项:**
39
+ **Options:**
43
40
 
44
- - `--foreground` / `-f` - 前台运行(不守护化,适合 systemd / Docker / PM2)
45
- - `--port <port>` - 服务器端口(默认 8080)
46
- - `--path <path>` - WebSocket 路径(默认 /ws)
47
- - `--token <token>` - 鉴权密钥(未指定时自动生成)
48
- - `--heartbeat <seconds>` - 心跳超时时间(默认 90 秒)
49
- - `--max-connections <n>` - 最大连接数(默认 100)
41
+ - `--foreground` / `-f` — Run without daemonizing, suitable for systemd / Docker / PM2.
42
+ - `--port <port>` — Server port (default 8080).
43
+ - `--path <path>` — WebSocket path (default /ws).
44
+ - `--token <token>` — Authentication token (generated if omitted).
45
+ - `--heartbeat <seconds>` — Heartbeat timeout (default 90 seconds).
46
+ - `--max-connections <n>` — Maximum connections (default 100).
50
47
 
51
- **示例:**
48
+ **Examples:**
52
49
 
53
50
  ```bash
54
- # 后台启动(自动生成 token),打印连接命令后立即返回
51
+ # Start in background, generate a token, print the connection command, and return
55
52
  mearl-cloud-server
56
53
 
57
- # 查看状态与连接信息
54
+ # Status and connection information
58
55
  mearl-cloud-server status
59
56
 
60
- # 停止 / 重启
57
+ # Stop / restart
61
58
  mearl-cloud-server stop
62
59
  mearl-cloud-server restart
63
60
 
64
- # 自定义端口和鉴权
61
+ # Custom port and authentication
65
62
  mearl-cloud-server start --port 9000 --token your-secret-token
66
63
 
67
- # 前台运行(交给外部进程管理器托管)
64
+ # Foreground execution for an external process manager
68
65
  mearl-cloud-server --foreground
69
66
  ```
70
67
 
71
- 后台启动后会打印供本地机器使用的连接命令,例如:
68
+ Background startup prints a connection command for the local machine, for example:
72
69
 
73
70
  ```
74
71
  [CloudServer] Started in background (pid 12345)
@@ -78,30 +75,30 @@ mearl-cloud-server --foreground
78
75
  npx @mearl/cloud-connector start "ws://10.0.1.100:8080/ws?token=xxx"
79
76
  ```
80
77
 
81
- 部署在反向代理或沙箱中时,由部署宿主或 preset 安装器通过 `MEARL_CLOUD_PUBLIC_URL` 提供外部入口,可使用 `{port}` 占位符。核心不探测组织专属沙箱变量或内部端口映射接口;相关自动发现由独立能力包处理。当前阶段尚未接通这些内部适配,内部产品仍保留原实现。
78
+ Behind a reverse proxy or in a sandbox, set `MEARL_CLOUD_PUBLIC_URL` to the external endpoint, optionally using `{port}`. The deployment host or Preset installer provides the connection address.
82
79
 
83
- 普通入口的连接命令保持双引号参数形式。外部路径含 `$` 等 shell 元字符时,输出命令使用 POSIX 字面量引用;这类命令在 POSIX 兼容终端执行,其他终端应将完整 publicUrl 作为一个字面量参数传给 `start`。
80
+ Normal connection commands use double-quoted arguments. When an external path contains shell metacharacters such as `$`, output uses POSIX literal quoting. Run these commands in a POSIX-compatible terminal; other terminals must pass the full publicUrl as one literal argument to `start`.
84
81
 
85
- 运行时文件位于 `~/.mearl/`:
82
+ Runtime files live in `~/.mearl/`:
86
83
 
87
- - `cloud-server.json` - 连接配置(含 server URL、pid、端口等),统一 `@mearl/client` 自动读取
88
- - `cloud-server-mode.json` - setup 在 Qoder 模式下写入的标识;普通 `--cloud` 会删除它
89
- - `cloud-server.log` - 后台进程日志(`logs` 子命令读取,或 `tail -f` 跟踪);超过 5MB 会在下次启动时滚动为 `cloud-server.log.1`
84
+ - `cloud-server.json` — Connection configuration, including URL, PID, and port; read automatically by `@mearl/client`.
85
+ - `cloud-server-mode.json` — Optional cloud mode marker written by setup; ordinary `--cloud` removes it.
86
+ - `cloud-server.log` — Background log, read with `logs` or `tail -f`; rotates to `cloud-server.log.1` on the next startup if over 5MB.
90
87
 
91
- > 守护进程的启停、状态、日志滚动等生命周期逻辑由共享包 [`@mearl/daemon-core`](../../daemon-core) 提供(与 cloud-connector 复用同一套实现)。
88
+ > The shared [`@mearl/daemon-core`](../../daemon-core/README.md) supplies lifecycle operations, status, and log rotation, also used by cloud-connector.
92
89
 
93
- **关于 token:**
90
+ **Tokens:**
94
91
 
95
- - 未指定 `--token` 时会自动生成。`restart` 会**沿用当前 token**,因此之前打印的连接命令重启后仍然有效。
96
- - `stop` 后再 `start` 属于全新启动,会重新生成 token;若希望连接命令长期固定,请显式传入 `--token` 或设置 `MEARL_TOKEN`。
92
+ - A token is generated when `--token` is omitted. `restart` **preserves the current token**, keeping previously printed connection commands valid.
93
+ - `stop` followed by `start` is a fresh startup and generates a new token. For a stable connection command, supply `--token` or set `MEARL_TOKEN`.
97
94
 
98
- **环境变量:**
95
+ **Environment variables:**
99
96
 
100
- - `MEARL_TOKEN` - 鉴权密钥(覆盖 --token 选项)
101
- - `CLOUD_SERVER_HOST` - 公网主机名(默认自动获取)
102
- - `MEARL_CLOUD_PUBLIC_URL` - 明确配置的外部入口,支持 `{port}` 占位符;有显式路径和查询参数时保留,根路径使用 `path` 选项,鉴权 token 由 Server 设置。HTTP/WS 入口生成 `ws://`,HTTPS/WSS 入口生成 `wss://`。
97
+ - `MEARL_TOKEN` — Authentication token, overriding --token.
98
+ - `CLOUD_SERVER_HOST` — Public hostname (automatically detected by default).
99
+ - `MEARL_CLOUD_PUBLIC_URL` — Explicit external endpoint supporting `{port}`. Explicit paths and query parameters are preserved; a root path uses the `path` option. The server sets the authentication token. HTTP/WS endpoints produce `ws://`; HTTPS/WSS endpoints produce `wss://`.
103
100
 
104
- ### 编程方式
101
+ ### Programmatic usage
105
102
 
106
103
  ```typescript
107
104
  import { CloudServer } from '@mearl/cloud-server';
@@ -117,52 +114,50 @@ const server = new CloudServer({
117
114
 
118
115
  await server.start();
119
116
 
120
- // 获取统计信息
117
+ // Get statistics
121
118
  const stats = server.getStats();
122
119
  console.log(stats);
123
120
  // { connections: 2, agents: 1, connectors: 1, pendingRequests: 0 }
124
121
 
125
- // 优雅关闭
122
+ // Graceful shutdown
126
123
  await server.stop();
127
124
  ```
128
125
 
129
- Qoder 模式只复用 cloud-server CLI 作为建联入口。配对完成后的 action 继续通过 Qoder
130
- Client-Side `mearl` 工具、Session API 和本地 connector 转发,不会连接一个本地
131
- cloud-server 端口。
126
+ The corresponding capability package handles handshakes and forwarding for optional modes.
132
127
 
133
- ## 架构
128
+ ## Architecture
134
129
 
135
130
  ```
136
- 云端 Agent
131
+ Cloud Agent
137
132
  ↓
138
133
  @mearl/client
139
134
  ↓ (WebSocket)
140
- @mearl/cloud-server (本进程)
135
+ @mearl/cloud-server (this process)
141
136
  ↓ (WebSocket)
142
- @mearl/cloud-connector (本地)
137
+ @mearl/cloud-connector (local)
143
138
  ↓ (Unix Socket)
144
139
  @mearl/native-host
145
140
  ↓
146
141
  Chrome Extension / CDP
147
142
  ```
148
143
 
149
- ## 连接类型
144
+ ## Connection types
150
145
 
151
- Server 接受两种类型的连接:
146
+ The server accepts two connection types:
152
147
 
153
- 1. **Agent 连接** - 发起请求的云端客户端
154
- 2. **Connector 连接** - 转发请求到 native-host 的本地连接器
148
+ 1. **Agent connection** — A cloud client issuing requests.
149
+ 2. **Connector connection** — A local connector forwarding requests to native-host.
155
150
 
156
- 两类客户端建连后会分别发送 `agent_hello` 和 `connector_hello`。Connector 会注册
157
- 稳定 ID、名称和版本;同一 ID 重连时,新连接会替换旧连接。
151
+ After connecting, clients send `agent_hello` and `connector_hello`, respectively. Connectors register
152
+ a stable ID, name, and version; a reconnection with the same ID replaces the previous connection.
158
153
 
159
- 只有一个 Connector 在线时,Server 自动选择它。多个 Connector 同时在线时,Agent
160
- 先调用 `connector_list` 获取清单,再在请求中传 `connector`;未指定目标时
161
- Server 会拒绝请求,避免把浏览器操作发到错误机器。
154
+ With one online Connector, the server selects it automatically. With multiple Connectors, the Agent
155
+ first calls `connector_list`, then passes `connector` with requests. Without an explicit target,
156
+ the server rejects the request to avoid sending browser operations to the wrong machine.
162
157
 
163
- ## 消息协议
158
+ ## Message protocol
164
159
 
165
- ### 请求格式(Agent → Server)
160
+ ### Request format (Agent → Server)
166
161
 
167
162
  ```json
168
163
  {
@@ -174,10 +169,10 @@ Server 会拒绝请求,避免把浏览器操作发到错误机器。
174
169
  }
175
170
  ```
176
171
 
177
- `connector` 接受 connector ID 或可唯一匹配的名称。`connector_list` 是
178
- Server 内置命令,不会转发到浏览器侧。
172
+ `connector` accepts an ID or a uniquely matching name. `connector_list` is
173
+ a built-in server command and is not forwarded to the browser.
179
174
 
180
- ### 响应格式(Server → Agent)
175
+ ### Response format (Server → Agent)
181
176
 
182
177
  ```json
183
178
  {
@@ -188,30 +183,30 @@ Server 内置命令,不会转发到浏览器侧。
188
183
  }
189
184
  ```
190
185
 
191
- ### 心跳格式
186
+ ### Heartbeat format
192
187
 
193
188
  ```json
194
189
  { "type": "ping", "timestamp": 1234567890 }
195
190
  { "type": "pong", "timestamp": 1234567890 }
196
191
  ```
197
192
 
198
- ## 鉴权
193
+ ## Authentication
199
194
 
200
- 设置 `token` 后,所有连接必须携带正确的 token:
195
+ When `token` is set, every connection must supply the correct token:
201
196
 
202
197
  ```bash
203
- # Agent 连接
198
+ # Agent connection
204
199
  ws://server.com/ws?token=your-secret-token
205
200
 
206
- # Connector 连接
201
+ # Connector connection
207
202
  ws://server.com/ws?token=your-secret-token
208
203
  ```
209
204
 
210
- ## 部署建议
205
+ ## Deployment recommendations
211
206
 
212
- > 交给外部进程管理器(systemd / Docker / PM2)托管时,请加 `--foreground`,让 server 在前台运行,由管理器负责守护与重启。
207
+ > For systemd / Docker / PM2, add `--foreground` and let the external manager supervise and restart the server.
213
208
 
214
- ### 使用 systemd(Linux)
209
+ ### systemd (Linux)
215
210
 
216
211
  ```ini
217
212
  [Unit]
@@ -229,7 +224,7 @@ RestartSec=5
229
224
  WantedBy=multi-user.target
230
225
  ```
231
226
 
232
- ### 使用 Docker
227
+ ### Docker
233
228
 
234
229
  ```dockerfile
235
230
  FROM node:20-alpine
@@ -238,17 +233,17 @@ EXPOSE 8080
238
233
  CMD ["mearl-cloud-server", "--foreground", "--token", "your-token"]
239
234
  ```
240
235
 
241
- ### 使用 PM2
236
+ ### PM2
242
237
 
243
238
  ```bash
244
- pm2 start mearl-cloud-server --name mtop-cloud-server -- --foreground --port 8080 --token your-token
239
+ pm2 start mearl-cloud-server --name mearl-cloud-server -- --foreground --port 8080 --token your-token
245
240
  ```
246
241
 
247
- ## 注意事项
242
+ ## Notes
248
243
 
249
- - 建议在生产环境使用 WSS(WebSocket Secure)加密传输
250
- - 使用反向代理(如 Nginx)处理 WSS 升级
251
- - 设置合理的 `maxConnections` 限制资源占用
252
- - 启用 `token` 鉴权保护服务安全
253
- - 在沙箱环境中启动时,会自动检测并使用平台分配的公网地址
254
- - 如果沙箱端口映射 API 不可用,会回退到使用本地 IP 地址
244
+ - Use WSS (WebSocket Secure) in production for encrypted transport.
245
+ - Use a reverse proxy such as Nginx for WSS upgrades.
246
+ - Set an appropriate `maxConnections` limit to bound resource use.
247
+ - Enable `token` authentication.
248
+ - Sandbox startup automatically detects and uses the platform-assigned public address.
249
+ - If the sandbox port-mapping API is unavailable, it falls back to the local IP address.
@@ -0,0 +1,249 @@
1
+ # @mearl/cloud-server
2
+
3
+ [English](README.md) | [简体中文](README.zh-CN.md)
4
+
5
+ 云端 WebSocket Server,用于中转云端 Agent 请求到本地连接器。
6
+
7
+ ## 安装
8
+
9
+ 推荐通过 setup 安装完整的云端 Agent 环境(cloud-server、统一 client 与 Agent Skill):
10
+
11
+ ```bash
12
+ npx @mearl/setup --cloud
13
+ ```
14
+
15
+ 可选连接协议由已安装能力包提供。Qoder 配对见 [`@mearl/plugin-qoder`](../../plugin-qoder/README.zh-CN.md);核心不内置其配对实现。
16
+
17
+ 在 Docker 或自定义进程托管等只需要 Server 包的场景,可单独安装 `@mearl/cloud-server`。
18
+
19
+ ## 使用
20
+
21
+ ### 命令行方式
22
+
23
+ Server **默认以后台守护进程方式运行**,启动后立即返回,并打印本地机器需要执行的连接命令。
24
+ POSIX 环境下后台进程会进入独立 session,可在启动命令结束后继续运行。
25
+ 其他 cloud mode 通过已安装的 `cloud.server.<id>` 加载,未安装时明确失败。
26
+
27
+ ```bash
28
+ mearl-cloud-server [command] [options]
29
+ ```
30
+
31
+ **子命令:**
32
+
33
+ - `start` - 后台启动 server(默认,不写也等价于 `start`)
34
+ - `stop` - 停止后台 server
35
+ - `restart` - 重启后台 server
36
+ - `status` - 查看运行状态及连接信息
37
+ - `logs` - 打印 server 日志末尾
38
+
39
+ **选项:**
40
+
41
+ - `--foreground` / `-f` - 前台运行(不守护化,适合 systemd / Docker / PM2)
42
+ - `--port <port>` - 服务器端口(默认 8080)
43
+ - `--path <path>` - WebSocket 路径(默认 /ws)
44
+ - `--token <token>` - 鉴权密钥(未指定时自动生成)
45
+ - `--heartbeat <seconds>` - 心跳超时时间(默认 90 秒)
46
+ - `--max-connections <n>` - 最大连接数(默认 100)
47
+
48
+ **示例:**
49
+
50
+ ```bash
51
+ # 后台启动(自动生成 token),打印连接命令后立即返回
52
+ mearl-cloud-server
53
+
54
+ # 查看状态与连接信息
55
+ mearl-cloud-server status
56
+
57
+ # 停止 / 重启
58
+ mearl-cloud-server stop
59
+ mearl-cloud-server restart
60
+
61
+ # 自定义端口和鉴权
62
+ mearl-cloud-server start --port 9000 --token your-secret-token
63
+
64
+ # 前台运行(交给外部进程管理器托管)
65
+ mearl-cloud-server --foreground
66
+ ```
67
+
68
+ 后台启动后会打印供本地机器使用的连接命令,例如:
69
+
70
+ ```
71
+ [CloudServer] Started in background (pid 12345)
72
+ [CloudServer] Logs: ~/.mearl/cloud-server.log
73
+
74
+ Connect from your local machine:
75
+ npx @mearl/cloud-connector start "ws://10.0.1.100:8080/ws?token=xxx"
76
+ ```
77
+
78
+ 部署在反向代理或沙箱中时,使用 `MEARL_CLOUD_PUBLIC_URL` 指定外部入口,可使用 `{port}` 占位符。连接地址由部署宿主或 Preset 安装器提供。
79
+
80
+ 普通入口的连接命令保持双引号参数形式。外部路径含 `$` 等 shell 元字符时,输出命令使用 POSIX 字面量引用;这类命令在 POSIX 兼容终端执行,其他终端应将完整 publicUrl 作为一个字面量参数传给 `start`。
81
+
82
+ 运行时文件位于 `~/.mearl/`:
83
+
84
+ - `cloud-server.json` - 连接配置(含 server URL、pid、端口等),统一 `@mearl/client` 自动读取
85
+ - `cloud-server-mode.json` - setup 为可选 cloud mode 写入的标识;普通 `--cloud` 会删除它
86
+ - `cloud-server.log` - 后台进程日志(`logs` 子命令读取,或 `tail -f` 跟踪);超过 5MB 会在下次启动时滚动为 `cloud-server.log.1`
87
+
88
+ > 守护进程的启停、状态、日志滚动等生命周期逻辑由共享包 [`@mearl/daemon-core`](../../daemon-core/README.zh-CN.md) 提供(与 cloud-connector 复用同一套实现)。
89
+
90
+ **关于 token:**
91
+
92
+ - 未指定 `--token` 时会自动生成。`restart` 会**沿用当前 token**,因此之前打印的连接命令重启后仍然有效。
93
+ - `stop` 后再 `start` 属于全新启动,会重新生成 token;若希望连接命令长期固定,请显式传入 `--token` 或设置 `MEARL_TOKEN`。
94
+
95
+ **环境变量:**
96
+
97
+ - `MEARL_TOKEN` - 鉴权密钥(覆盖 --token 选项)
98
+ - `CLOUD_SERVER_HOST` - 公网主机名(默认自动获取)
99
+ - `MEARL_CLOUD_PUBLIC_URL` - 明确配置的外部入口,支持 `{port}` 占位符;有显式路径和查询参数时保留,根路径使用 `path` 选项,鉴权 token 由 Server 设置。HTTP/WS 入口生成 `ws://`,HTTPS/WSS 入口生成 `wss://`。
100
+
101
+ ### 编程方式
102
+
103
+ ```typescript
104
+ import { CloudServer } from '@mearl/cloud-server';
105
+
106
+ const server = new CloudServer({
107
+ port: 8080,
108
+ path: '/ws',
109
+ token: 'your-secret-token',
110
+ heartbeatTimeout: 90,
111
+ maxConnections: 100,
112
+ maxMessageSize: 10 * 1024 * 1024,
113
+ });
114
+
115
+ await server.start();
116
+
117
+ // 获取统计信息
118
+ const stats = server.getStats();
119
+ console.log(stats);
120
+ // { connections: 2, agents: 1, connectors: 1, pendingRequests: 0 }
121
+
122
+ // 优雅关闭
123
+ await server.stop();
124
+ ```
125
+
126
+ 可选 mode 的握手和转发由对应能力包负责。
127
+
128
+ ## 架构
129
+
130
+ ```
131
+ 云端 Agent
132
+ ↓
133
+ @mearl/client
134
+ ↓ (WebSocket)
135
+ @mearl/cloud-server (本进程)
136
+ ↓ (WebSocket)
137
+ @mearl/cloud-connector (本地)
138
+ ↓ (Unix Socket)
139
+ @mearl/native-host
140
+ ↓
141
+ Chrome Extension / CDP
142
+ ```
143
+
144
+ ## 连接类型
145
+
146
+ Server 接受两种类型的连接:
147
+
148
+ 1. **Agent 连接** - 发起请求的云端客户端
149
+ 2. **Connector 连接** - 转发请求到 native-host 的本地连接器
150
+
151
+ 两类客户端建连后会分别发送 `agent_hello` 和 `connector_hello`。Connector 会注册
152
+ 稳定 ID、名称和版本;同一 ID 重连时,新连接会替换旧连接。
153
+
154
+ 只有一个 Connector 在线时,Server 自动选择它。多个 Connector 同时在线时,Agent
155
+ 先调用 `connector_list` 获取清单,再在请求中传 `connector`;未指定目标时
156
+ Server 会拒绝请求,避免把浏览器操作发到错误机器。
157
+
158
+ ## 消息协议
159
+
160
+ ### 请求格式(Agent → Server)
161
+
162
+ ```json
163
+ {
164
+ "id": "unique-id",
165
+ "action": "get_requests",
166
+ "data": { "count": 5 },
167
+ "connector": "work-mac",
168
+ "version": "1.30.0"
169
+ }
170
+ ```
171
+
172
+ `connector` 接受 connector ID 或可唯一匹配的名称。`connector_list` 是
173
+ Server 内置命令,不会转发到浏览器侧。
174
+
175
+ ### 响应格式(Server → Agent)
176
+
177
+ ```json
178
+ {
179
+ "id": "unique-id",
180
+ "success": true,
181
+ "data": { ... },
182
+ "versionWarning": "..."
183
+ }
184
+ ```
185
+
186
+ ### 心跳格式
187
+
188
+ ```json
189
+ { "type": "ping", "timestamp": 1234567890 }
190
+ { "type": "pong", "timestamp": 1234567890 }
191
+ ```
192
+
193
+ ## 鉴权
194
+
195
+ 设置 `token` 后,所有连接必须携带正确的 token:
196
+
197
+ ```bash
198
+ # Agent 连接
199
+ ws://server.com/ws?token=your-secret-token
200
+
201
+ # Connector 连接
202
+ ws://server.com/ws?token=your-secret-token
203
+ ```
204
+
205
+ ## 部署建议
206
+
207
+ > 交给外部进程管理器(systemd / Docker / PM2)托管时,请加 `--foreground`,让 server 在前台运行,由管理器负责守护与重启。
208
+
209
+ ### 使用 systemd(Linux)
210
+
211
+ ```ini
212
+ [Unit]
213
+ Description=Mearl Cloud Server
214
+ After=network.target
215
+
216
+ [Service]
217
+ Type=simple
218
+ User=your-user
219
+ ExecStart=/usr/bin/mearl-cloud-server --foreground --port 8080 --token your-token
220
+ Restart=always
221
+ RestartSec=5
222
+
223
+ [Install]
224
+ WantedBy=multi-user.target
225
+ ```
226
+
227
+ ### 使用 Docker
228
+
229
+ ```dockerfile
230
+ FROM node:20-alpine
231
+ RUN npm install -g @mearl/cloud-server
232
+ EXPOSE 8080
233
+ CMD ["mearl-cloud-server", "--foreground", "--token", "your-token"]
234
+ ```
235
+
236
+ ### 使用 PM2
237
+
238
+ ```bash
239
+ pm2 start mearl-cloud-server --name mearl-cloud-server -- --foreground --port 8080 --token your-token
240
+ ```
241
+
242
+ ## 注意事项
243
+
244
+ - 建议在生产环境使用 WSS(WebSocket Secure)加密传输
245
+ - 使用反向代理(如 Nginx)处理 WSS 升级
246
+ - 设置合理的 `maxConnections` 限制资源占用
247
+ - 启用 `token` 鉴权保护服务安全
248
+ - 在沙箱环境中启动时,会自动检测并使用平台分配的公网地址
249
+ - 如果沙箱端口映射 API 不可用,会回退到使用本地 IP 地址
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { CloudServerOptions } from './index.js';
2
- import { type CloudServerCommand } from './qoder.js';
2
+ type CloudServerCommand = 'start' | 'stop' | 'restart' | 'status' | 'logs';
3
3
  export interface ParsedCloudServerArgs {
4
4
  command: CloudServerCommand;
5
5
  daemonArgs: string[];
@@ -10,3 +10,4 @@ export interface ParsedCloudServerArgs {
10
10
  }
11
11
  export declare function parseCloudServerArgs(argv: string[]): ParsedCloudServerArgs;
12
12
  export declare function main(argv?: string[]): Promise<number>;
13
+ export {};
package/dist/cli.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  } from "./chunks/chunk-LXICEBLN.js";
9
9
 
10
10
  // src/cli.ts
11
+ import { capabilityDirectory } from "@mearl/daemon-core/page-runtimes";
11
12
  import { Command, CommanderError, InvalidArgumentError } from "commander";
12
13
 
13
14
  // src/daemon.ts
@@ -82,7 +83,7 @@ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
82
83
  import { dirname } from "node:path";
83
84
  var CLOUD_SERVER_MODE_ENV = "MEARL_CLOUD_MODE";
84
85
  function isCloudServerMode(value) {
85
- return value === "websocket" || value === "qoder";
86
+ return typeof value === "string" && /^[a-z][a-z0-9-]{0,63}$/.test(value);
86
87
  }
87
88
  function readStoredMode(storage) {
88
89
  const modeFile = storage.modeFile ?? MODE_FILE;
@@ -112,72 +113,18 @@ function resolveCloudServerMode(env = process.env, storage = {}) {
112
113
  const requested = env[CLOUD_SERVER_MODE_ENV];
113
114
  if (requested !== void 0) {
114
115
  if (!isCloudServerMode(requested)) {
115
- throw new Error(`${CLOUD_SERVER_MODE_ENV} must be "websocket" or "qoder"`);
116
+ throw new Error(`${CLOUD_SERVER_MODE_ENV} must be a valid installed cloud mode ID`);
116
117
  }
117
- if (requested === "qoder") writeStoredMode(requested, storage);
118
+ if (requested !== "websocket") writeStoredMode(requested, storage);
118
119
  else clearStoredMode(storage);
119
120
  return requested;
120
121
  }
121
122
  return readStoredMode(storage) ?? "websocket";
122
123
  }
123
124
 
124
- // src/qoder.ts
125
- import { randomBytes } from "node:crypto";
126
- function createQoderPairingInfo(options = {}) {
127
- const pairingCode = (options.createCode ?? (() => randomBytes(24).toString("base64url")))();
128
- if (!/^[A-Za-z0-9_-]{16,128}$/.test(pairingCode)) {
129
- throw new Error("Qoder pairing code must be 16-128 URL-safe characters");
130
- }
131
- const createdAt = (options.now ?? Date.now)();
132
- if (!Number.isSafeInteger(createdAt) || createdAt < 1e12 || createdAt > 9999999999999) {
133
- throw new Error("Qoder pairing timestamp must be a 13-digit epoch millisecond value");
134
- }
135
- return {
136
- pairingCode,
137
- createdAt,
138
- connectorUrl: `qoder://pair/${pairingCode}?created=${createdAt}`,
139
- toolInput: { action: "pair", payload: { code: pairingCode } }
140
- };
141
- }
142
- function formatQoderPairingOutput(info) {
143
- const connectorCommand = `npx @mearl/cloud-connector start "${info.connectorUrl}"`;
144
- return `[CloudServer] Qoder pairing mode is ready.
145
-
146
- Connect from your local machine:
147
- ${connectorCommand}
148
-
149
- Then immediately invoke the Qoder client-side custom tool named "mearl" with this input:
150
- ${JSON.stringify(info.toolInput)}
151
-
152
- The connector resolves QODER_PERSONAL_ACCESS_TOKEN from the local environment, ~/.mearl/.env, or supported local credential storage. Never add the PAT to the pairing URL.`;
153
- }
154
- async function runQoderCloudServerCommand(command, dependencies) {
155
- const writeLine = dependencies.writeLine ?? console.log;
156
- switch (command) {
157
- case "start":
158
- case "restart":
159
- if (dependencies.isServerRunning()) await dependencies.stopServer();
160
- writeLine(
161
- formatQoderPairingOutput(
162
- (dependencies.createPairing ?? (() => createQoderPairingInfo()))()
163
- )
164
- );
165
- break;
166
- case "status":
167
- dependencies.printStatus();
168
- writeLine("[CloudServer] Mode: qoder pairing (no listening service)");
169
- break;
170
- case "stop":
171
- await dependencies.stopServer();
172
- break;
173
- case "logs":
174
- dependencies.printLogs();
175
- break;
176
- }
177
- }
178
-
179
125
  // src/cli.ts
180
- var CLOUD_SERVER_VERSION = true ? "3.0.0-beta.0" : "unknown";
126
+ import { NodeCapabilityRegistry } from "@mearl/daemon-core/capabilities";
127
+ var CLOUD_SERVER_VERSION = true ? "3.0.0-beta.10" : "unknown";
181
128
  var COMMANDS = ["start", "stop", "restart", "status", "logs"];
182
129
  var START_OPTION_NAMES = /* @__PURE__ */ new Set([
183
130
  "foreground",
@@ -322,8 +269,11 @@ async function main(argv = process.argv.slice(2)) {
322
269
  return 0;
323
270
  }
324
271
  const mode = resolveCloudServerMode();
325
- if (mode === "qoder") {
326
- await runQoderCloudServerCommand(parsed.command, {
272
+ if (mode !== "websocket") {
273
+ const hook = `cloud.server.${mode}`;
274
+ const registry = new NodeCapabilityRegistry({ version: CLOUD_SERVER_VERSION }, capabilityDirectory(), { hooks: [hook] });
275
+ await registry.refresh();
276
+ await registry.require(hook)(parsed.command, {
327
277
  isServerRunning: isDaemonRunning,
328
278
  stopServer: stopDaemon,
329
279
  printStatus: statusDaemon,
package/dist/mode.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type CloudServerMode = 'websocket' | 'qoder';
1
+ export type CloudServerMode = string;
2
2
  export declare const CLOUD_SERVER_MODE_ENV = "MEARL_CLOUD_MODE";
3
3
  interface CloudServerModeStorage {
4
4
  modeFile?: string;
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@mearl/cloud-server",
3
- "version": "3.0.0-beta.0",
3
+ "version": "3.0.0-beta.10",
4
4
  "description": "Cloud WebSocket server for Mearl — bridges cloud agents to local connectors",
5
5
  "type": "module",
6
6
  "engines": {
7
- "node": ">=22.12.0"
7
+ "node": ">=20.18.0"
8
8
  },
9
9
  "main": "dist/index.js",
10
10
  "types": "dist/index.d.ts",
@@ -33,11 +33,11 @@
33
33
  "registry": "https://registry.npmjs.org"
34
34
  },
35
35
  "dependencies": {
36
- "commander": "^15.0.0",
36
+ "commander": "^14.0.3",
37
37
  "ws": "^8.18.0",
38
- "@mearl/client": "3.0.0-beta.0",
39
- "@mearl/cloud-types": "3.0.0-beta.0",
40
- "@mearl/daemon-core": "3.0.0-beta.0"
38
+ "@mearl/client": "3.0.0-beta.10",
39
+ "@mearl/cloud-types": "3.0.0-beta.10",
40
+ "@mearl/daemon-core": "3.0.0-beta.10"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^24.9.1",
@@ -45,6 +45,11 @@
45
45
  "esbuild": "^0.28.2",
46
46
  "typescript": "^5.8.3"
47
47
  },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/mearlhq/mearl.git",
51
+ "directory": "packages/cloud/cloud-server"
52
+ },
48
53
  "scripts": {
49
54
  "build": "rm -rf dist/ && node build.mjs",
50
55
  "dev": "tsc --watch",
package/dist/qoder.d.ts DELETED
@@ -1,29 +0,0 @@
1
- export interface QoderPairingInfo {
2
- pairingCode: string;
3
- createdAt: number;
4
- connectorUrl: string;
5
- toolInput: {
6
- action: 'pair';
7
- payload: {
8
- code: string;
9
- };
10
- };
11
- }
12
- export interface QoderPairingOptions {
13
- now?: () => number;
14
- createCode?: () => string;
15
- }
16
- export type CloudServerCommand = 'start' | 'stop' | 'restart' | 'status' | 'logs';
17
- interface QoderCloudServerDependencies {
18
- createPairing?: () => QoderPairingInfo;
19
- isServerRunning: () => boolean;
20
- printLogs: () => void;
21
- printStatus: () => void;
22
- stopServer: () => Promise<void>;
23
- writeLine?: (message: string) => void;
24
- }
25
- /** Create a short-lived correlation handle without embedding Qoder credentials in the URL. */
26
- export declare function createQoderPairingInfo(options?: QoderPairingOptions): QoderPairingInfo;
27
- export declare function formatQoderPairingOutput(info: QoderPairingInfo): string;
28
- export declare function runQoderCloudServerCommand(command: CloudServerCommand, dependencies: QoderCloudServerDependencies): Promise<void>;
29
- export {};