@optima-chat/browser-cli 0.10.0 → 0.10.2
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/dist/index.js +121 -29
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,24 +22,33 @@ var ENV_CONFIG = {
|
|
|
22
22
|
// alicloud/stacks/cn-prod-ingress-sae/main.tf 的 yzsgo_sub=browser-api)。
|
|
23
23
|
"cn-prod": {
|
|
24
24
|
apiUrl: "https://browser-api.yzsgo.com"
|
|
25
|
+
},
|
|
26
|
+
// 阿里云 cn-stage 预发(独立于 AWS stage 的 .optima.onl;权威映射见 optima-terraform
|
|
27
|
+
// alicloud/stacks/cn-stage-ingress-sae/main.tf:browser-backend → sub=browser-api,
|
|
28
|
+
// 通配证书 *.stage.optima.chat 覆盖)。
|
|
29
|
+
// 漏这一档的后果:cn-stage 容器 OPTIMA_ENV=cn-stage → agent-runtime 写
|
|
30
|
+
// token.json.env="cn-stage" → 下面 fail-loud 抛 Unknown environment,而那句 throw
|
|
31
|
+
// 在模块加载期就执行,browser-cli 在任何子命令跑起来之前直接死掉。
|
|
32
|
+
// optima-scout 撞过同一个 bug(optima-gateway#1384 P0-2)。
|
|
33
|
+
"cn-stage": {
|
|
34
|
+
apiUrl: "https://browser-api.stage.optima.chat"
|
|
25
35
|
}
|
|
26
36
|
};
|
|
37
|
+
function isBrowserEnv(value) {
|
|
38
|
+
return Object.prototype.hasOwnProperty.call(ENV_CONFIG, value);
|
|
39
|
+
}
|
|
40
|
+
var TOKEN_ENV_MAP = {
|
|
41
|
+
prod: "production",
|
|
42
|
+
production: "production",
|
|
43
|
+
stage: "stage",
|
|
44
|
+
ci: "development",
|
|
45
|
+
development: "development",
|
|
46
|
+
"cn-prod": "cn-prod",
|
|
47
|
+
"cn-stage": "cn-stage"
|
|
48
|
+
};
|
|
27
49
|
var OPTIMA_TOKEN_FILE = join(homedir(), ".optima", "token.json");
|
|
28
50
|
function tokenEnvToBrowserEnv(tokenEnv) {
|
|
29
|
-
|
|
30
|
-
case "prod":
|
|
31
|
-
case "production":
|
|
32
|
-
return "production";
|
|
33
|
-
case "stage":
|
|
34
|
-
return "stage";
|
|
35
|
-
case "ci":
|
|
36
|
-
case "development":
|
|
37
|
-
return "development";
|
|
38
|
-
case "cn-prod":
|
|
39
|
-
return "cn-prod";
|
|
40
|
-
default:
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
51
|
+
return Object.prototype.hasOwnProperty.call(TOKEN_ENV_MAP, tokenEnv) ? TOKEN_ENV_MAP[tokenEnv] : null;
|
|
43
52
|
}
|
|
44
53
|
function readOptimaToken() {
|
|
45
54
|
if (!existsSync(OPTIMA_TOKEN_FILE)) return null;
|
|
@@ -51,11 +60,11 @@ function readOptimaToken() {
|
|
|
51
60
|
}
|
|
52
61
|
function getCurrentEnv() {
|
|
53
62
|
const browserEnv = process.env.BROWSER_ENV;
|
|
54
|
-
if (browserEnv &&
|
|
63
|
+
if (browserEnv && isBrowserEnv(browserEnv)) {
|
|
55
64
|
return browserEnv;
|
|
56
65
|
}
|
|
57
66
|
const optimaEnv = process.env.OPTIMA_ENV;
|
|
58
|
-
if (optimaEnv &&
|
|
67
|
+
if (optimaEnv && isBrowserEnv(optimaEnv)) {
|
|
59
68
|
return optimaEnv;
|
|
60
69
|
}
|
|
61
70
|
const optimaToken = readOptimaToken();
|
|
@@ -63,7 +72,7 @@ function getCurrentEnv() {
|
|
|
63
72
|
const mapped = tokenEnvToBrowserEnv(optimaToken.env);
|
|
64
73
|
if (mapped) return mapped;
|
|
65
74
|
throw new Error(
|
|
66
|
-
`Unknown environment "${optimaToken.env}" in ~/.optima/token.json. Known environments:
|
|
75
|
+
`Unknown environment "${optimaToken.env}" in ~/.optima/token.json. Known environments: ${Object.keys(TOKEN_ENV_MAP).join(", ")}. Upgrade browser-cli to a version that knows this environment, or set BROWSER_API_URL to override the endpoint explicitly.`
|
|
67
76
|
);
|
|
68
77
|
}
|
|
69
78
|
return "development";
|
|
@@ -174,13 +183,14 @@ async function requestBinary(path) {
|
|
|
174
183
|
return Buffer.from(arrayBuffer);
|
|
175
184
|
}
|
|
176
185
|
var BrowserAPI = class {
|
|
177
|
-
async createSession(url, force, connector, ziniaoProfileId, bitbrowserProfileId) {
|
|
186
|
+
async createSession(url, force, connector, ziniaoProfileId, bitbrowserProfileId, feikuaProfileId) {
|
|
178
187
|
const body = {};
|
|
179
188
|
if (url) body.url = url;
|
|
180
189
|
if (force) body.force = true;
|
|
181
190
|
if (connector) body.connector = connector;
|
|
182
191
|
if (ziniaoProfileId) body.ziniao_profile_id = ziniaoProfileId;
|
|
183
192
|
if (bitbrowserProfileId) body.bitbrowser_profile_id = bitbrowserProfileId;
|
|
193
|
+
if (feikuaProfileId) body.feikua_profile_id = feikuaProfileId;
|
|
184
194
|
return request("/api/sessions", {
|
|
185
195
|
method: "POST",
|
|
186
196
|
body: JSON.stringify(body)
|
|
@@ -204,6 +214,11 @@ var BrowserAPI = class {
|
|
|
204
214
|
async getBitBrowserProfiles() {
|
|
205
215
|
return request("/api/user/bitbrowser-profiles");
|
|
206
216
|
}
|
|
217
|
+
// paired_count = 配对过的店铺浏览器总数(含没在运行的)。老桌面端不返回它,
|
|
218
|
+
// 所以是可选的 —— 缺失时调用方只能退回「从未配对」的指引。
|
|
219
|
+
async getFeikuaProfiles() {
|
|
220
|
+
return request("/api/user/feikua-profiles");
|
|
221
|
+
}
|
|
207
222
|
async closeSession(sessionId) {
|
|
208
223
|
await request(`/api/sessions/${sessionId}`, { method: "DELETE" });
|
|
209
224
|
}
|
|
@@ -234,8 +249,18 @@ var BrowserAPI = class {
|
|
|
234
249
|
async input(sessionId, index, text, clear = true) {
|
|
235
250
|
return this.actThenState(sessionId, "input", { index, text, clear });
|
|
236
251
|
}
|
|
237
|
-
async scroll(sessionId, down, pages = 1) {
|
|
238
|
-
return this.actThenState(sessionId, "scroll", {
|
|
252
|
+
async scroll(sessionId, down, pages = 1, index) {
|
|
253
|
+
return this.actThenState(sessionId, "scroll", {
|
|
254
|
+
down,
|
|
255
|
+
pages,
|
|
256
|
+
...index === void 0 ? {} : { index }
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
async clickTo(sessionId, selector, native = false) {
|
|
260
|
+
return this.actThenState(sessionId, "click_to", { selector, ...native ? { native } : {} });
|
|
261
|
+
}
|
|
262
|
+
async scrollTo(sessionId, selector) {
|
|
263
|
+
return this.actThenState(sessionId, "scroll_to", { selector });
|
|
239
264
|
}
|
|
240
265
|
async keys(sessionId, keys) {
|
|
241
266
|
return this.actThenState(sessionId, "keys", { keys });
|
|
@@ -333,11 +358,12 @@ var BrowserAPI = class {
|
|
|
333
358
|
async deleteScript(name) {
|
|
334
359
|
await request(`/api/scripts/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
335
360
|
}
|
|
336
|
-
async runScript(scriptName, params = {}, connector, ziniaoProfileId, bitbrowserProfileId) {
|
|
361
|
+
async runScript(scriptName, params = {}, connector, ziniaoProfileId, bitbrowserProfileId, feikuaProfileId) {
|
|
337
362
|
const payload = { params };
|
|
338
363
|
if (connector) payload.connector = connector;
|
|
339
364
|
if (ziniaoProfileId) payload.ziniao_profile_id = ziniaoProfileId;
|
|
340
365
|
if (bitbrowserProfileId) payload.bitbrowser_profile_id = bitbrowserProfileId;
|
|
366
|
+
if (feikuaProfileId) payload.feikua_profile_id = feikuaProfileId;
|
|
341
367
|
return request(`/api/scripts/${encodeURIComponent(scriptName)}/run`, {
|
|
342
368
|
method: "POST",
|
|
343
369
|
body: JSON.stringify(payload)
|
|
@@ -441,7 +467,12 @@ function formatFind(result) {
|
|
|
441
467
|
const lines = [`Found ${result.total} element(s):`, ""];
|
|
442
468
|
for (const match of result.matches) {
|
|
443
469
|
const attrs = Object.entries(match.attributes).map(([k, v]) => `${k}="${v}"`).join(" ");
|
|
444
|
-
|
|
470
|
+
const state = Object.entries(match.state ?? {}).filter(([, v]) => v).map(([k]) => k).join(" ");
|
|
471
|
+
const head = ` [${match.index}] <${match.tag} ${attrs}>`;
|
|
472
|
+
lines.push(state ? `${head} [${state}]` : head);
|
|
473
|
+
if (match.text) {
|
|
474
|
+
lines.push(` ${match.text}`);
|
|
475
|
+
}
|
|
445
476
|
}
|
|
446
477
|
return lines.join("\n");
|
|
447
478
|
}
|
|
@@ -460,9 +491,9 @@ function formatTabsInline(tabs) {
|
|
|
460
491
|
|
|
461
492
|
// src/commands/lifecycle.ts
|
|
462
493
|
function registerLifecycleCommands(program2) {
|
|
463
|
-
program2.command("launch").description("Launch a browser session").option("--url <url>", "Navigate to URL after launch").option("--force", "Close existing session before launching").option("--connector <type>", "Browser connector: chrome, ziniao, or
|
|
494
|
+
program2.command("launch").description("Launch a browser session").option("--url <url>", "Navigate to URL after launch").option("--force", "Close existing session before launching").option("--connector <type>", "Browser connector: chrome, ziniao, bitbrowser, or feikua").option("--ziniao-profile <id>", "ZiNiao browser profile ID").option("--bitbrowser-profile <id>", "BitBrowser profile ID").option("--feikua-profile <id>", "Feikua (\u98DE\u8DE8) shop browser instance ID").action(async (opts) => {
|
|
464
495
|
try {
|
|
465
|
-
const state = await api.createSession(opts.url, opts.force, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile);
|
|
496
|
+
const state = await api.createSession(opts.url, opts.force, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile, opts.feikuaProfile);
|
|
466
497
|
saveSessionId(state.session_id);
|
|
467
498
|
console.log(formatLaunch(state));
|
|
468
499
|
} catch (err) {
|
|
@@ -554,12 +585,36 @@ function registerInteractionCommands(program2) {
|
|
|
554
585
|
process.exit(1);
|
|
555
586
|
}
|
|
556
587
|
});
|
|
557
|
-
program2.command("scroll").description("Scroll the page up or down").argument("<direction>", "Scroll direction: up or down").option("--pages <n>", "Number of pages to scroll", "1").
|
|
588
|
+
program2.command("scroll").description("Scroll the page up or down").argument("<direction>", "Scroll direction: up or down").option("--pages <n>", "Number of pages to scroll", "1").option(
|
|
589
|
+
"--index <n>",
|
|
590
|
+
"Scroll inside this element instead of the window (for pages with inner scroll containers)"
|
|
591
|
+
).action(async (direction, opts) => {
|
|
558
592
|
try {
|
|
559
593
|
const sessionId = loadSessionId();
|
|
560
594
|
const down = direction.toLowerCase() === "down";
|
|
561
595
|
const pages = parseInt(opts.pages, 10);
|
|
562
|
-
const
|
|
596
|
+
const index = opts.index === void 0 ? void 0 : parseInt(opts.index, 10);
|
|
597
|
+
const state = await api.scroll(sessionId, down, pages, index);
|
|
598
|
+
console.log(formatPageState(state));
|
|
599
|
+
} catch (err) {
|
|
600
|
+
console.error(formatError(err));
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
program2.command("click-to").description("Click the element matching a CSS selector (for elements with no DOM index)").argument("<selector>", "CSS selector of the element to click").option("--native", "Dispatch real mouse events (for controls that ignore synthetic clicks)").action(async (selector, opts) => {
|
|
605
|
+
try {
|
|
606
|
+
const sessionId = loadSessionId();
|
|
607
|
+
const state = await api.clickTo(sessionId, selector, opts.native === true);
|
|
608
|
+
console.log(formatPageState(state));
|
|
609
|
+
} catch (err) {
|
|
610
|
+
console.error(formatError(err));
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
program2.command("scroll-to").description("Scroll the element matching a CSS selector into view").argument("<selector>", "CSS selector of the element to scroll into view").action(async (selector) => {
|
|
615
|
+
try {
|
|
616
|
+
const sessionId = loadSessionId();
|
|
617
|
+
const state = await api.scrollTo(sessionId, selector);
|
|
563
618
|
console.log(formatPageState(state));
|
|
564
619
|
} catch (err) {
|
|
565
620
|
console.error(formatError(err));
|
|
@@ -685,7 +740,8 @@ var POLL_INTERVAL_MS = 2e3;
|
|
|
685
740
|
var POLL_TIMEOUT_MS = 18e4;
|
|
686
741
|
var REQUIRED_ARG_FLAG = {
|
|
687
742
|
ziniao_profile_id: "--ziniao-profile <id>",
|
|
688
|
-
bitbrowser_profile_id: "--bitbrowser-profile <id>"
|
|
743
|
+
bitbrowser_profile_id: "--bitbrowser-profile <id>",
|
|
744
|
+
feikua_profile_id: "--feikua-profile <id>"
|
|
689
745
|
};
|
|
690
746
|
function formatRequiredArgs(s) {
|
|
691
747
|
if (!s.required_args?.length) return null;
|
|
@@ -801,7 +857,7 @@ function registerScriptCommands(program2) {
|
|
|
801
857
|
process.exit(1);
|
|
802
858
|
}
|
|
803
859
|
});
|
|
804
|
-
program2.command("runscript").argument("<name>", "Script name").description("Run a script (polls until complete)").option("--param <params...>", "Parameters as key=value").option("--connector <type>", "Browser connector: chrome, ziniao, or
|
|
860
|
+
program2.command("runscript").argument("<name>", "Script name").description("Run a script (polls until complete)").option("--param <params...>", "Parameters as key=value").option("--connector <type>", "Browser connector: chrome, ziniao, bitbrowser, or feikua").option("--ziniao-profile <id>", "ZiNiao browser profile ID (with --connector ziniao)").option("--bitbrowser-profile <id>", "BitBrowser profile ID (with --connector bitbrowser)").option("--feikua-profile <id>", "Feikua (\u98DE\u8DE8) shop browser instance ID (with --connector feikua)").option("--out <path>", "Where to write the script's exported file (default: ./<filename>)").option("--no-poll", "Return immediately without polling").action(async (name, opts) => {
|
|
805
861
|
try {
|
|
806
862
|
const params = {};
|
|
807
863
|
if (opts.param) {
|
|
@@ -812,7 +868,7 @@ function registerScriptCommands(program2) {
|
|
|
812
868
|
}
|
|
813
869
|
if (opts.out) params.out_path = resolve(opts.out);
|
|
814
870
|
clearSessionId();
|
|
815
|
-
const task = await api.runScript(name, params, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile);
|
|
871
|
+
const task = await api.runScript(name, params, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile, opts.feikuaProfile);
|
|
816
872
|
console.log(`Script started (task: ${task.id})`);
|
|
817
873
|
if (!opts.poll) {
|
|
818
874
|
console.log(`Poll with: browser-cli script-task ${task.id}`);
|
|
@@ -1104,6 +1160,41 @@ function registerBitBrowserCommands(program2) {
|
|
|
1104
1160
|
});
|
|
1105
1161
|
}
|
|
1106
1162
|
|
|
1163
|
+
// src/commands/feikua.ts
|
|
1164
|
+
function registerFeikuaCommands(program2) {
|
|
1165
|
+
const feikua = program2.command("feikua").description("Manage Feikua (\u98DE\u8DE8) anti-detect browser");
|
|
1166
|
+
feikua.command("profiles").description("List paired Feikua shop browsers (requires Desktop app online + extension paired)").action(async () => {
|
|
1167
|
+
try {
|
|
1168
|
+
const result = await api.getFeikuaProfiles();
|
|
1169
|
+
if (!result.connected) {
|
|
1170
|
+
console.log(formatDesktopNotConnected(result));
|
|
1171
|
+
process.exit(1);
|
|
1172
|
+
}
|
|
1173
|
+
if (result.profiles.length === 0) {
|
|
1174
|
+
if (result.paired_count) {
|
|
1175
|
+
console.log(
|
|
1176
|
+
`${result.paired_count} paired shop browser(s), but none are running. Open the Feikua shop browser you paired, then retry.`
|
|
1177
|
+
);
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
console.log(
|
|
1181
|
+
'No paired shop browsers. Open the desktop app tray and choose "Pair a Shop Browser", then follow the on-screen code.'
|
|
1182
|
+
);
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
console.log(`Feikua shop browsers (${result.profiles.length}):
|
|
1186
|
+
`);
|
|
1187
|
+
for (const p of result.profiles) {
|
|
1188
|
+
console.log(` ${p.id} ${p.name}`);
|
|
1189
|
+
}
|
|
1190
|
+
console.log("\nUse: browser-cli launch --connector feikua --feikua-profile <id> --url <url>");
|
|
1191
|
+
} catch (err) {
|
|
1192
|
+
console.error(formatError(err));
|
|
1193
|
+
process.exit(1);
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1107
1198
|
// src/commands/files.ts
|
|
1108
1199
|
function registerFileCommands(program2) {
|
|
1109
1200
|
program2.command("send-file").description("Send a file from your workspace to your local download folder").requiredOption("--src <path>", "Source path in your workspace (e.g. ~/output/report.pdf)").requiredOption("--dest <path>", "Destination path relative to the download folder").action(async (opts) => {
|
|
@@ -1137,5 +1228,6 @@ registerScriptCommands(program);
|
|
|
1137
1228
|
registerZiNiaoCommands(program);
|
|
1138
1229
|
registerRecipeCommands(program);
|
|
1139
1230
|
registerBitBrowserCommands(program);
|
|
1231
|
+
registerFeikuaCommands(program);
|
|
1140
1232
|
registerFileCommands(program);
|
|
1141
1233
|
program.parse();
|