@optima-chat/browser-cli 0.5.6 → 0.6.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/.claude/skills/browser/SKILL.md +10 -21
- package/dist/index.js +37 -42
- package/package.json +1 -1
|
@@ -30,17 +30,13 @@ owner_repo: Optima-Chat/optima-browser-use
|
|
|
30
30
|
|
|
31
31
|
脚本执行零 token 消耗,比手动操作快 10 倍以上。
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## 前提条件:Desktop 桌面应用
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
**Browser Skill 需要用户安装 Optima Browser Operator 桌面应用。** 所有浏览器操作都通过 Desktop app 控制用户本地的 Chrome 或紫鸟浏览器。
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|------|----------|------|
|
|
39
|
-
| **Desktop + 紫鸟** | 用户安装了 Desktop app 且配置了紫鸟 | 通过紫鸟反指纹浏览器操作,保留多店铺登录态 |
|
|
40
|
-
| **Desktop + Chrome** | 用户安装了 Desktop app | 通过用户本地 Chrome 操作 |
|
|
41
|
-
| **Cloud** | 无 Desktop app | 后端 headless Chromium,无登录态 |
|
|
37
|
+
如果 `browser-cli launch` 返回 `desktop_not_connected` 错误,提示用户:
|
|
42
38
|
|
|
43
|
-
|
|
39
|
+
> "需要安装 Optima Browser Operator 桌面应用才能使用浏览器功能。下载地址: https://github.com/Optima-Chat/optima-browser-use/releases"
|
|
44
40
|
|
|
45
41
|
### 紫鸟浏览器(跨境电商多店铺管理)
|
|
46
42
|
|
|
@@ -67,7 +63,7 @@ browser-cli launch --connector ziniao --ziniao-profile 27774259938707 --url http
|
|
|
67
63
|
**重要**:
|
|
68
64
|
- 用户说"迪士尼店铺"、"林总店铺"等 → 先 `ziniao profiles` 查列表,按名称模糊匹配找到 profile_id
|
|
69
65
|
- 用户说"打开紫鸟"但没指定店铺 → 先列出 profiles 让用户选
|
|
70
|
-
- 紫鸟需要 Desktop app
|
|
66
|
+
- 紫鸟需要 Desktop app 在线(紫鸟 WebDriver 会按需自动启动)
|
|
71
67
|
|
|
72
68
|
## 命令
|
|
73
69
|
|
|
@@ -225,22 +221,16 @@ browser-cli close
|
|
|
225
221
|
/mnt/efs/{userId}/images/... → 自动转换 ✓
|
|
226
222
|
```
|
|
227
223
|
|
|
228
|
-
##
|
|
224
|
+
## Desktop 桌面应用未连接时
|
|
229
225
|
|
|
230
|
-
|
|
226
|
+
如果 `browser-cli launch` 返回错误,提示用户安装 Desktop app:
|
|
231
227
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
1. 用户下载安装桌面应用(Mac .dmg / Windows .exe):https://github.com/Optima-Chat/optima-browser-use/releases
|
|
228
|
+
1. 下载安装(Mac .dmg / Windows .exe):https://github.com/Optima-Chat/optima-browser-use/releases
|
|
235
229
|
2. 首次启动自动打开浏览器登录授权
|
|
236
|
-
3.
|
|
230
|
+
3. 授权后自动连接
|
|
237
231
|
|
|
238
232
|
**跨境电商用户(多店铺)**:推荐安装紫鸟浏览器 + Desktop app,可以按店铺 profile 分别操作,每个店铺独立登录态。
|
|
239
233
|
|
|
240
|
-
**备选方案:Chrome 扩展**
|
|
241
|
-
|
|
242
|
-
安装 Optima Browser Operator Chrome 扩展,直接用 Chrome 已登录的账号操作。
|
|
243
|
-
|
|
244
234
|
## 注意事项
|
|
245
235
|
|
|
246
236
|
- **脚本优先**:每次操作前先 `browser-cli scripts`,有脚本就用脚本
|
|
@@ -248,6 +238,5 @@ browser-cli close
|
|
|
248
238
|
- **避免不必要的截图**:截图消耗 token,日常导航用 DOM 输出即可
|
|
249
239
|
- **5 分钟超时**:无操作 5 分钟后会话自动关闭
|
|
250
240
|
- **务必关闭**:操作完成后运行 `browser-cli close` 释放资源
|
|
251
|
-
- **login_required 处理**:如果 runscript 返回 `login_required`
|
|
241
|
+
- **login_required 处理**:如果 runscript 返回 `login_required` 错误,说明该平台未登录。让用户在自己的浏览器里手动登录该平台,然后重新 `runscript`
|
|
252
242
|
- **Fallback 后更新脚本**:如果脚本频繁失败,重新生成并 submit-script 覆盖
|
|
253
|
-
- **Extension 模式**:标记了 `REQUIRES_USER_SESSION=True` 的脚本会自动使用用户的真实 Chrome(通过 Optima Browser Operator 扩展),未标记的脚本走 Chromium。这由后端自动判断,无需手动指定
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,11 @@ var ENV_CONFIG = {
|
|
|
17
17
|
},
|
|
18
18
|
development: {
|
|
19
19
|
apiUrl: "http://127.0.0.1:7292"
|
|
20
|
+
},
|
|
21
|
+
// 阿里云 cn-prod(yzsgo 品牌域;权威映射见 optima-terraform
|
|
22
|
+
// alicloud/stacks/cn-prod-ingress-sae/main.tf 的 yzsgo_sub=browser-api)。
|
|
23
|
+
"cn-prod": {
|
|
24
|
+
apiUrl: "https://browser-api.yzsgo.com"
|
|
20
25
|
}
|
|
21
26
|
};
|
|
22
27
|
var OPTIMA_TOKEN_FILE = join(homedir(), ".optima", "token.json");
|
|
@@ -30,6 +35,8 @@ function tokenEnvToBrowserEnv(tokenEnv) {
|
|
|
30
35
|
case "ci":
|
|
31
36
|
case "development":
|
|
32
37
|
return "development";
|
|
38
|
+
case "cn-prod":
|
|
39
|
+
return "cn-prod";
|
|
33
40
|
default:
|
|
34
41
|
return null;
|
|
35
42
|
}
|
|
@@ -44,23 +51,27 @@ function readOptimaToken() {
|
|
|
44
51
|
}
|
|
45
52
|
function getCurrentEnv() {
|
|
46
53
|
const browserEnv = process.env.BROWSER_ENV;
|
|
47
|
-
if (browserEnv && ["production", "stage", "development"].includes(browserEnv)) {
|
|
54
|
+
if (browserEnv && ["production", "stage", "development", "cn-prod"].includes(browserEnv)) {
|
|
48
55
|
return browserEnv;
|
|
49
56
|
}
|
|
50
57
|
const optimaEnv = process.env.OPTIMA_ENV;
|
|
51
|
-
if (optimaEnv && ["production", "stage", "development"].includes(optimaEnv)) {
|
|
58
|
+
if (optimaEnv && ["production", "stage", "development", "cn-prod"].includes(optimaEnv)) {
|
|
52
59
|
return optimaEnv;
|
|
53
60
|
}
|
|
54
61
|
const optimaToken = readOptimaToken();
|
|
55
62
|
if (optimaToken?.env) {
|
|
56
63
|
const mapped = tokenEnvToBrowserEnv(optimaToken.env);
|
|
57
64
|
if (mapped) return mapped;
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Unknown environment "${optimaToken.env}" in ~/.optima/token.json. Known environments: prod, stage, ci, cn-prod. Upgrade browser-cli to a version that knows this environment, or set BROWSER_API_URL to override the endpoint explicitly.`
|
|
67
|
+
);
|
|
58
68
|
}
|
|
59
69
|
return "development";
|
|
60
70
|
}
|
|
61
71
|
function loadConfig() {
|
|
62
|
-
const
|
|
63
|
-
const
|
|
72
|
+
const apiUrlOverride = process.env.BROWSER_API_URL;
|
|
73
|
+
const env = apiUrlOverride ? void 0 : getCurrentEnv();
|
|
74
|
+
const apiUrl = apiUrlOverride ?? ENV_CONFIG[env].apiUrl;
|
|
64
75
|
let token = process.env.BROWSER_TOKEN || null;
|
|
65
76
|
if (!token) {
|
|
66
77
|
const optimaToken = readOptimaToken();
|
|
@@ -69,7 +80,7 @@ function loadConfig() {
|
|
|
69
80
|
}
|
|
70
81
|
}
|
|
71
82
|
return {
|
|
72
|
-
apiUrl
|
|
83
|
+
apiUrl,
|
|
73
84
|
token,
|
|
74
85
|
env
|
|
75
86
|
};
|
|
@@ -196,53 +207,38 @@ var BrowserAPI = class {
|
|
|
196
207
|
async getStatus(sessionId) {
|
|
197
208
|
return request(`/api/sessions/${sessionId}/status`);
|
|
198
209
|
}
|
|
199
|
-
|
|
200
|
-
|
|
210
|
+
/** Execute an action then fetch DOM state separately.
|
|
211
|
+
* Decoupled to prevent EventBus overflow on remote CDP tunnels. */
|
|
212
|
+
async actThenState(sessionId, path, body, timeout) {
|
|
213
|
+
await request(`/api/sessions/${sessionId}/${path}`, {
|
|
201
214
|
method: "POST",
|
|
202
|
-
body: JSON.stringify(
|
|
215
|
+
body: JSON.stringify(body)
|
|
203
216
|
});
|
|
217
|
+
return this.getState(sessionId);
|
|
218
|
+
}
|
|
219
|
+
async navigate(sessionId, url) {
|
|
220
|
+
return this.actThenState(sessionId, "navigate", { url });
|
|
204
221
|
}
|
|
205
222
|
async back(sessionId) {
|
|
206
|
-
return
|
|
207
|
-
method: "POST",
|
|
208
|
-
body: JSON.stringify({})
|
|
209
|
-
});
|
|
223
|
+
return this.actThenState(sessionId, "back", {});
|
|
210
224
|
}
|
|
211
225
|
async refresh(sessionId) {
|
|
212
|
-
return
|
|
213
|
-
method: "POST",
|
|
214
|
-
body: JSON.stringify({})
|
|
215
|
-
});
|
|
226
|
+
return this.actThenState(sessionId, "refresh", {});
|
|
216
227
|
}
|
|
217
228
|
async click(sessionId, index) {
|
|
218
|
-
return
|
|
219
|
-
method: "POST",
|
|
220
|
-
body: JSON.stringify({ index })
|
|
221
|
-
});
|
|
229
|
+
return this.actThenState(sessionId, "click", { index });
|
|
222
230
|
}
|
|
223
231
|
async input(sessionId, index, text, clear = true) {
|
|
224
|
-
return
|
|
225
|
-
method: "POST",
|
|
226
|
-
body: JSON.stringify({ index, text, clear })
|
|
227
|
-
});
|
|
232
|
+
return this.actThenState(sessionId, "input", { index, text, clear });
|
|
228
233
|
}
|
|
229
234
|
async scroll(sessionId, down, pages = 1) {
|
|
230
|
-
return
|
|
231
|
-
method: "POST",
|
|
232
|
-
body: JSON.stringify({ down, pages })
|
|
233
|
-
});
|
|
235
|
+
return this.actThenState(sessionId, "scroll", { down, pages });
|
|
234
236
|
}
|
|
235
237
|
async keys(sessionId, keys) {
|
|
236
|
-
return
|
|
237
|
-
method: "POST",
|
|
238
|
-
body: JSON.stringify({ keys })
|
|
239
|
-
});
|
|
238
|
+
return this.actThenState(sessionId, "keys", { keys });
|
|
240
239
|
}
|
|
241
240
|
async select(sessionId, index, text) {
|
|
242
|
-
return
|
|
243
|
-
method: "POST",
|
|
244
|
-
body: JSON.stringify({ index, text })
|
|
245
|
-
});
|
|
241
|
+
return this.actThenState(sessionId, "select", { index, text });
|
|
246
242
|
}
|
|
247
243
|
async getState(sessionId) {
|
|
248
244
|
return request(`/api/sessions/${sessionId}/state`);
|
|
@@ -266,22 +262,21 @@ var BrowserAPI = class {
|
|
|
266
262
|
return request(`/api/sessions/${sessionId}/tabs`);
|
|
267
263
|
}
|
|
268
264
|
async switchTab(sessionId, tabId) {
|
|
269
|
-
return
|
|
270
|
-
method: "POST",
|
|
271
|
-
body: JSON.stringify({ tab_id: tabId })
|
|
272
|
-
});
|
|
265
|
+
return this.actThenState(sessionId, "switch", { tab_id: tabId });
|
|
273
266
|
}
|
|
274
267
|
async closeTab(sessionId, tabId) {
|
|
275
|
-
|
|
268
|
+
await request(
|
|
276
269
|
`/api/sessions/${sessionId}/tabs/${tabId}`,
|
|
277
270
|
{ method: "DELETE" }
|
|
278
271
|
);
|
|
272
|
+
return this.getState(sessionId);
|
|
279
273
|
}
|
|
280
274
|
async uploadFiles(sessionId, index, filePaths) {
|
|
281
|
-
|
|
275
|
+
await request(`/api/sessions/${sessionId}/upload`, {
|
|
282
276
|
method: "POST",
|
|
283
277
|
body: JSON.stringify({ index, file_paths: filePaths })
|
|
284
278
|
});
|
|
279
|
+
return this.getState(sessionId);
|
|
285
280
|
}
|
|
286
281
|
// Script methods
|
|
287
282
|
async submitScript(name, code) {
|