@mearl/cloud-server 3.0.0-beta.1 → 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 +86 -91
- package/README.zh-CN.md +249 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +11 -61
- package/dist/mode.d.ts +1 -1
- package/package.json +7 -7
- package/dist/qoder.d.ts +0 -29
package/README.md
CHANGED
|
@@ -1,74 +1,71 @@
|
|
|
1
1
|
# @mearl/cloud-server
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
POSIX
|
|
27
|
-
|
|
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`
|
|
37
|
-
- `stop`
|
|
38
|
-
- `restart`
|
|
39
|
-
- `status`
|
|
40
|
-
- `logs`
|
|
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`
|
|
45
|
-
- `--port <port>`
|
|
46
|
-
- `--path <path>`
|
|
47
|
-
- `--token <token>`
|
|
48
|
-
- `--heartbeat <seconds>`
|
|
49
|
-
- `--max-connections <n>`
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
+
Runtime files live in `~/.mearl/`:
|
|
86
83
|
|
|
87
|
-
- `cloud-server.json`
|
|
88
|
-
- `cloud-server-mode.json`
|
|
89
|
-
- `cloud-server.log`
|
|
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
|
-
>
|
|
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
|
-
|
|
90
|
+
**Tokens:**
|
|
94
91
|
|
|
95
|
-
-
|
|
96
|
-
- `stop`
|
|
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`
|
|
101
|
-
- `CLOUD_SERVER_HOST`
|
|
102
|
-
- `MEARL_CLOUD_PUBLIC_URL`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
146
|
+
The server accepts two connection types:
|
|
152
147
|
|
|
153
|
-
1. **Agent
|
|
154
|
-
2. **Connector
|
|
148
|
+
1. **Agent connection** — A cloud client issuing requests.
|
|
149
|
+
2. **Connector connection** — A local connector forwarding requests to native-host.
|
|
155
150
|
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
###
|
|
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`
|
|
178
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
>
|
|
207
|
+
> For systemd / Docker / PM2, add `--foreground` and let the external manager supervise and restart the server.
|
|
213
208
|
|
|
214
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
236
|
+
### PM2
|
|
242
237
|
|
|
243
238
|
```bash
|
|
244
|
-
pm2 start mearl-cloud-server --name
|
|
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
|
-
-
|
|
250
|
-
-
|
|
251
|
-
-
|
|
252
|
-
-
|
|
253
|
-
-
|
|
254
|
-
-
|
|
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.
|
package/README.zh-CN.md
ADDED
|
@@ -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
|
-
|
|
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 === "
|
|
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
|
|
116
|
+
throw new Error(`${CLOUD_SERVER_MODE_ENV} must be a valid installed cloud mode ID`);
|
|
116
117
|
}
|
|
117
|
-
if (requested
|
|
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
|
-
|
|
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
|
|
326
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/cloud-server",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
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": ">=
|
|
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": "^
|
|
36
|
+
"commander": "^14.0.3",
|
|
37
37
|
"ws": "^8.18.0",
|
|
38
|
-
"@mearl/client": "3.0.0-beta.
|
|
39
|
-
"@mearl/cloud-types": "3.0.0-beta.
|
|
40
|
-
"@mearl/daemon-core": "3.0.0-beta.
|
|
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",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"repository": {
|
|
49
49
|
"type": "git",
|
|
50
|
-
"url": "git+https://github.com/
|
|
50
|
+
"url": "git+https://github.com/mearlhq/mearl.git",
|
|
51
51
|
"directory": "packages/cloud/cloud-server"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
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 {};
|