@optima-chat/browser-cli 0.6.0 → 0.7.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/dist/index.js +198 -11
- package/package.json +2 -3
- package/.claude/skills/browser/SKILL.md +0 -242
package/dist/index.js
CHANGED
|
@@ -98,6 +98,16 @@ var BrowserAPIError = class extends Error {
|
|
|
98
98
|
this.name = "BrowserAPIError";
|
|
99
99
|
}
|
|
100
100
|
};
|
|
101
|
+
function toAPIError(status, statusText, body) {
|
|
102
|
+
const detail = body.detail ?? body;
|
|
103
|
+
if (typeof detail === "string") {
|
|
104
|
+
return new BrowserAPIError(status, "http_error", `HTTP ${status}: ${detail}`);
|
|
105
|
+
}
|
|
106
|
+
if (detail?.message) {
|
|
107
|
+
return new BrowserAPIError(status, detail.error ?? "unknown", detail.message);
|
|
108
|
+
}
|
|
109
|
+
return new BrowserAPIError(status, "unknown", `HTTP ${status}: ${statusText}`);
|
|
110
|
+
}
|
|
101
111
|
async function request(path, options = {}) {
|
|
102
112
|
const url = `${BROWSER_API_URL}${path}`;
|
|
103
113
|
let response;
|
|
@@ -126,8 +136,7 @@ async function request(path, options = {}) {
|
|
|
126
136
|
`HTTP ${response.status}: ${response.statusText}`
|
|
127
137
|
);
|
|
128
138
|
}
|
|
129
|
-
|
|
130
|
-
throw new BrowserAPIError(response.status, detail.error, detail.message);
|
|
139
|
+
throw toAPIError(response.status, response.statusText, body);
|
|
131
140
|
}
|
|
132
141
|
if (response.status === 204) {
|
|
133
142
|
return void 0;
|
|
@@ -159,20 +168,20 @@ async function requestBinary(path) {
|
|
|
159
168
|
`HTTP ${response.status}: ${response.statusText}`
|
|
160
169
|
);
|
|
161
170
|
}
|
|
162
|
-
|
|
163
|
-
throw new BrowserAPIError(response.status, detail.error, detail.message);
|
|
171
|
+
throw toAPIError(response.status, response.statusText, body);
|
|
164
172
|
}
|
|
165
173
|
const arrayBuffer = await response.arrayBuffer();
|
|
166
174
|
return Buffer.from(arrayBuffer);
|
|
167
175
|
}
|
|
168
176
|
var BrowserAPI = class {
|
|
169
|
-
async createSession(url, profileId, force, connector, ziniaoProfileId) {
|
|
177
|
+
async createSession(url, profileId, force, connector, ziniaoProfileId, bitbrowserProfileId) {
|
|
170
178
|
const body = {};
|
|
171
179
|
if (url) body.url = url;
|
|
172
180
|
if (profileId) body.profile_id = profileId;
|
|
173
181
|
if (force) body.force = true;
|
|
174
182
|
if (connector) body.connector = connector;
|
|
175
183
|
if (ziniaoProfileId) body.ziniao_profile_id = ziniaoProfileId;
|
|
184
|
+
if (bitbrowserProfileId) body.bitbrowser_profile_id = bitbrowserProfileId;
|
|
176
185
|
return request("/api/sessions", {
|
|
177
186
|
method: "POST",
|
|
178
187
|
body: JSON.stringify(body)
|
|
@@ -201,6 +210,9 @@ var BrowserAPI = class {
|
|
|
201
210
|
async getZiNiaoProfiles() {
|
|
202
211
|
return request("/api/user/ziniao-profiles");
|
|
203
212
|
}
|
|
213
|
+
async getBitBrowserProfiles() {
|
|
214
|
+
return request("/api/user/bitbrowser-profiles");
|
|
215
|
+
}
|
|
204
216
|
async closeSession(sessionId) {
|
|
205
217
|
await request(`/api/sessions/${sessionId}`, { method: "DELETE" });
|
|
206
218
|
}
|
|
@@ -246,6 +258,22 @@ var BrowserAPI = class {
|
|
|
246
258
|
async getScreenshot(sessionId) {
|
|
247
259
|
return requestBinary(`/api/sessions/${sessionId}/screenshot`);
|
|
248
260
|
}
|
|
261
|
+
async runRecipeScript(name, params, pollMs = 2e3) {
|
|
262
|
+
const started = await request(`/api/scripts/${name}/run`, {
|
|
263
|
+
method: "POST",
|
|
264
|
+
body: JSON.stringify({ params })
|
|
265
|
+
});
|
|
266
|
+
for (; ; ) {
|
|
267
|
+
const task = await request(
|
|
268
|
+
`/api/script-tasks/${started.id}`
|
|
269
|
+
);
|
|
270
|
+
if (task.status === "completed") return task.output;
|
|
271
|
+
if (task.status === "failed" || task.status === "needs_fallback") {
|
|
272
|
+
throw new Error(task.error || `recipe ${task.status}`);
|
|
273
|
+
}
|
|
274
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
249
277
|
async search(sessionId, pattern) {
|
|
250
278
|
return request(`/api/sessions/${sessionId}/search`, {
|
|
251
279
|
method: "POST",
|
|
@@ -300,9 +328,11 @@ var BrowserAPI = class {
|
|
|
300
328
|
async deleteScript(name) {
|
|
301
329
|
await request(`/api/scripts/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
302
330
|
}
|
|
303
|
-
async runScript(scriptName, params = {}, profileId) {
|
|
331
|
+
async runScript(scriptName, params = {}, profileId, connector, bitbrowserProfileId) {
|
|
304
332
|
const payload = { params };
|
|
305
333
|
if (profileId) payload.profile_id = profileId;
|
|
334
|
+
if (connector) payload.connector = connector;
|
|
335
|
+
if (bitbrowserProfileId) payload.bitbrowser_profile_id = bitbrowserProfileId;
|
|
306
336
|
return request(`/api/scripts/${encodeURIComponent(scriptName)}/run`, {
|
|
307
337
|
method: "POST",
|
|
308
338
|
body: JSON.stringify(payload)
|
|
@@ -404,6 +434,9 @@ function formatFind(result) {
|
|
|
404
434
|
}
|
|
405
435
|
return lines.join("\n");
|
|
406
436
|
}
|
|
437
|
+
function formatDesktopNotConnected(result) {
|
|
438
|
+
return result.message ?? "\u684C\u9762\u5E94\u7528\u672A\u8FDE\u63A5\uFF0C\u8BF7\u5148\u5B89\u88C5\u5E76\u542F\u52A8\u684C\u9762\u5E94\u7528\u540E\u91CD\u8BD5\u3002";
|
|
439
|
+
}
|
|
407
440
|
function formatError(err) {
|
|
408
441
|
if (err instanceof Error) {
|
|
409
442
|
return `\u2717 Error: ${err.message}`;
|
|
@@ -430,9 +463,9 @@ function formatProfileDelete(profileId) {
|
|
|
430
463
|
|
|
431
464
|
// src/commands/lifecycle.ts
|
|
432
465
|
function registerLifecycleCommands(program2) {
|
|
433
|
-
program2.command("launch").description("Launch a browser session").option("--url <url>", "Navigate to URL after launch").option("--profile <id>", "Use a named browser profile").option("--force", "Close existing session before launching").option("--connector <type>", "Browser connector: chrome or
|
|
466
|
+
program2.command("launch").description("Launch a browser session").option("--url <url>", "Navigate to URL after launch").option("--profile <id>", "Use a named browser profile").option("--force", "Close existing session before launching").option("--connector <type>", "Browser connector: chrome, ziniao, or bitbrowser").option("--ziniao-profile <id>", "ZiNiao browser profile ID").option("--bitbrowser-profile <id>", "BitBrowser profile ID").action(async (opts) => {
|
|
434
467
|
try {
|
|
435
|
-
const state = await api.createSession(opts.url, opts.profile, opts.force, opts.connector, opts.ziniaoProfile);
|
|
468
|
+
const state = await api.createSession(opts.url, opts.profile, opts.force, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile);
|
|
436
469
|
saveSessionId(state.session_id);
|
|
437
470
|
console.log(formatLaunch(state));
|
|
438
471
|
} catch (err) {
|
|
@@ -748,7 +781,7 @@ function registerScriptCommands(program2) {
|
|
|
748
781
|
process.exit(1);
|
|
749
782
|
}
|
|
750
783
|
});
|
|
751
|
-
program2.command("runscript").argument("<name>", "Script name").description("Run a script (polls until complete)").option("--param <params...>", "Parameters as key=value").option("--profile <id>", "Use a named browser profile").option("--no-poll", "Return immediately without polling").action(async (name, opts) => {
|
|
784
|
+
program2.command("runscript").argument("<name>", "Script name").description("Run a script (polls until complete)").option("--param <params...>", "Parameters as key=value").option("--profile <id>", "Use a named browser profile").option("--connector <type>", "Browser connector: chrome, ziniao, or bitbrowser").option("--bitbrowser-profile <id>", "BitBrowser profile ID (with --connector bitbrowser)").option("--no-poll", "Return immediately without polling").action(async (name, opts) => {
|
|
752
785
|
try {
|
|
753
786
|
const params = {};
|
|
754
787
|
if (opts.param) {
|
|
@@ -758,7 +791,7 @@ function registerScriptCommands(program2) {
|
|
|
758
791
|
}
|
|
759
792
|
}
|
|
760
793
|
clearSessionId();
|
|
761
|
-
const task = await api.runScript(name, params, opts.profile);
|
|
794
|
+
const task = await api.runScript(name, params, opts.profile, opts.connector, opts.bitbrowserProfile);
|
|
762
795
|
console.log(`Script started (task: ${task.id})`);
|
|
763
796
|
if (!opts.poll) {
|
|
764
797
|
console.log(`Poll with: browser-cli script-task ${task.id}`);
|
|
@@ -820,6 +853,105 @@ function registerProfileCommands(program2) {
|
|
|
820
853
|
});
|
|
821
854
|
}
|
|
822
855
|
|
|
856
|
+
// src/ziniao-doctor.ts
|
|
857
|
+
var ZINIAO_WEBDRIVER_DOC_URL = "https://open.ziniao.com/docSupport?docId=99#%E7%AC%AC%E4%B8%80%E6%AD%A5%EF%BC%9A%E7%99%BB%E5%BD%95%E5%BC%80%E6%94%BE%E5%B9%B3%E5%8F%B0%E6%8E%A7%E5%88%B6%E5%8F%B00";
|
|
858
|
+
var BOSS_GUIDANCE = [
|
|
859
|
+
"\u9700\u8981\u7D2B\u9E1F BOSS \u8D26\u53F7\u5B8C\u6210\u5F00\u901A\uFF08\u4E00\u6B21\u6027\u914D\u7F6E\uFF09\uFF1A",
|
|
860
|
+
" 1. \u767B\u5F55\u7D2B\u9E1F\u5F00\u653E\u5E73\u53F0\u63A7\u5236\u53F0\uFF0C\u5F00\u901A Webdriver \u6743\u9650\uFF1B",
|
|
861
|
+
" 2. \u521B\u5EFA\u4E13\u7528\u81EA\u52A8\u5316\u6210\u5458\u8D26\u53F7\uFF08robot\uFF09\uFF0C\u89D2\u8272\u9700\u542B\u300C\u67E5\u770B\u8D26\u53F7\u300D\u6743\u9650\uFF1B",
|
|
862
|
+
" 3. \u4E3A\u8BE5\u8D26\u53F7\u6388\u6743\u9700\u8981\u64CD\u4F5C\u7684\u5E97\u94FA\u3002",
|
|
863
|
+
`\u53C2\u8003\u6587\u6863: ${ZINIAO_WEBDRIVER_DOC_URL}`
|
|
864
|
+
].join("\n");
|
|
865
|
+
function ziniaoErrorHint(message) {
|
|
866
|
+
if (/webdriver|开通|权限|permission/i.test(message)) {
|
|
867
|
+
return `\u7591\u4F3C\u672A\u5F00\u901A Webdriver \u6743\u9650\u6216\u8D26\u53F7\u6743\u9650\u4E0D\u8DB3\u3002${BOSS_GUIDANCE}`;
|
|
868
|
+
}
|
|
869
|
+
if (/错误次数太多/.test(message)) {
|
|
870
|
+
return "\u7D2B\u9E1F\u98CE\u63A7\u5DF2\u4E34\u65F6\u9501\u5B9A\uFF0C\u8BF7\u7B49\u5F85\u7EA6 10 \u5206\u949F\u540E\u91CD\u8BD5\uFF08\u7EE7\u7EED\u5931\u8D25\u4F1A\u5EF6\u957F\u9501\u5B9A\uFF09\u3002";
|
|
871
|
+
}
|
|
872
|
+
if (/用户名或密码错误|密码错误/.test(message)) {
|
|
873
|
+
return "\u8BF7\u6838\u5BF9\u51ED\u636E\uFF08company=\u4F01\u4E1A\u540D\u79F0\uFF0Cusername/password=\u6210\u5458\u8D26\u53F7\uFF0C\u63A8\u8350 robot \u8D26\u53F7\uFF09\uFF0C\u7136\u540E\u7528 browser-cli ziniao set \u91CD\u65B0\u914D\u7F6E\u3002";
|
|
874
|
+
}
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
async function runZiNiaoDoctor(client) {
|
|
878
|
+
const checks = [];
|
|
879
|
+
const done = () => ({ checks, ok: checks.every((c) => c.ok === true) });
|
|
880
|
+
let cfg;
|
|
881
|
+
try {
|
|
882
|
+
cfg = await client.getZiNiaoConfig();
|
|
883
|
+
} catch (err) {
|
|
884
|
+
checks.push({
|
|
885
|
+
name: "\u540E\u7AEF\u8FDE\u63A5",
|
|
886
|
+
ok: false,
|
|
887
|
+
detail: err.message,
|
|
888
|
+
hint: "\u786E\u8BA4 browser-backend \u6B63\u5728\u8FD0\u884C\uFF0C\u4E14 BROWSER_API_URL \u6307\u5411\u6B63\u786E\u5730\u5740\u3002"
|
|
889
|
+
});
|
|
890
|
+
return done();
|
|
891
|
+
}
|
|
892
|
+
checks.push({ name: "\u540E\u7AEF\u8FDE\u63A5", ok: true });
|
|
893
|
+
if (!cfg.configured) {
|
|
894
|
+
checks.push({
|
|
895
|
+
name: "\u7D2B\u9E1F\u51ED\u636E",
|
|
896
|
+
ok: false,
|
|
897
|
+
hint: "browser-cli ziniao set --company <\u4F01\u4E1A\u540D\u79F0> --username <\u6210\u5458\u8D26\u53F7> --password <\u5BC6\u7801>\uFF08\u63A8\u8350\u4F7F\u7528\u4E13\u7528 robot \u6210\u5458\u8D26\u53F7\uFF09"
|
|
898
|
+
});
|
|
899
|
+
return done();
|
|
900
|
+
}
|
|
901
|
+
checks.push({ name: "\u7D2B\u9E1F\u51ED\u636E", ok: true, detail: `${cfg.company} / ${cfg.username}` });
|
|
902
|
+
try {
|
|
903
|
+
const r = await client.getZiNiaoProfiles();
|
|
904
|
+
if (!r.connected) {
|
|
905
|
+
checks.push({
|
|
906
|
+
name: "\u684C\u9762\u5E94\u7528",
|
|
907
|
+
ok: false,
|
|
908
|
+
detail: r.message,
|
|
909
|
+
hint: `\u8BF7\u542F\u52A8 Optima \u684C\u9762\u5E94\u7528\u5E76\u5B8C\u6210\u8FDE\u63A5\u3002${r.download_url ? `\u4E0B\u8F7D: ${r.download_url}` : ""}`.trim()
|
|
910
|
+
});
|
|
911
|
+
return done();
|
|
912
|
+
}
|
|
913
|
+
checks.push({ name: "\u684C\u9762\u5E94\u7528", ok: true });
|
|
914
|
+
if (!r.ziniao_available) {
|
|
915
|
+
checks.push({
|
|
916
|
+
name: "\u7D2B\u9E1F\u5BA2\u6237\u7AEF",
|
|
917
|
+
ok: false,
|
|
918
|
+
hint: "\u684C\u9762\u7AEF\u672A\u68C0\u6D4B\u5230\u53EF\u7528\u7684\u7D2B\u9E1F\uFF1A\u786E\u8BA4\u672C\u673A\u5DF2\u5B89\u88C5\u7D2B\u9E1F\u5BA2\u6237\u7AEF\uFF0C\u4E14\u51ED\u636E\u5DF2\u540C\u6B65\u5230\u684C\u9762\uFF08\u91CD\u542F\u684C\u9762\u5E94\u7528\u53EF\u89E6\u53D1\u540C\u6B65\uFF09\u3002"
|
|
919
|
+
});
|
|
920
|
+
return done();
|
|
921
|
+
}
|
|
922
|
+
checks.push({ name: "\u7D2B\u9E1F\u5BA2\u6237\u7AEF", ok: true });
|
|
923
|
+
if (r.profiles.length === 0) {
|
|
924
|
+
checks.push({
|
|
925
|
+
name: "\u5E97\u94FA\u5217\u8868",
|
|
926
|
+
ok: "warn",
|
|
927
|
+
detail: "\u767B\u5F55\u6210\u529F\uFF0C\u4F46\u5E97\u94FA\u5217\u8868\u4E3A\u7A7A",
|
|
928
|
+
hint: `robot \u8D26\u53F7\u7684\u89D2\u8272\u53EF\u80FD\u7F3A\u300C\u67E5\u770B\u8D26\u53F7\u300D\u6743\u9650\uFF0C\u6216\u5C1A\u672A\u6388\u6743\u4EFB\u4F55\u5E97\u94FA\u3002${BOSS_GUIDANCE}`
|
|
929
|
+
});
|
|
930
|
+
return done();
|
|
931
|
+
}
|
|
932
|
+
checks.push({ name: "\u5E97\u94FA\u5217\u8868", ok: true, detail: `${r.profiles.length} \u4E2A\u5E97\u94FA\u53EF\u7528` });
|
|
933
|
+
} catch (err) {
|
|
934
|
+
checks.push({
|
|
935
|
+
name: "\u7D2B\u9E1F Webdriver \u901A\u9053",
|
|
936
|
+
ok: false,
|
|
937
|
+
detail: err.message,
|
|
938
|
+
hint: ziniaoErrorHint(err.message ?? "") ?? void 0
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
return done();
|
|
942
|
+
}
|
|
943
|
+
function formatDoctorReport(checks) {
|
|
944
|
+
const lines = [];
|
|
945
|
+
for (const c of checks) {
|
|
946
|
+
const mark = c.ok === true ? "\u2713" : c.ok === "warn" ? "\u26A0" : "\u2717";
|
|
947
|
+
lines.push(`${mark} ${c.name}${c.detail ? `: ${c.detail}` : ""}`);
|
|
948
|
+
if (c.hint) {
|
|
949
|
+
lines.push(...c.hint.split("\n").map((l) => ` ${l}`));
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return lines.join("\n");
|
|
953
|
+
}
|
|
954
|
+
|
|
823
955
|
// src/commands/ziniao.ts
|
|
824
956
|
function registerZiNiaoCommands(program2) {
|
|
825
957
|
const ziniao = program2.command("ziniao").description("Manage ZiNiao browser credentials");
|
|
@@ -851,15 +983,17 @@ function registerZiNiaoCommands(program2) {
|
|
|
851
983
|
try {
|
|
852
984
|
const result = await api.getZiNiaoProfiles();
|
|
853
985
|
if (!result.connected) {
|
|
854
|
-
console.log(
|
|
986
|
+
console.log(formatDesktopNotConnected(result));
|
|
855
987
|
process.exit(1);
|
|
856
988
|
}
|
|
857
989
|
if (!result.ziniao_available) {
|
|
858
990
|
console.log("ZiNiao not available on Desktop app. Start ZiNiao WebDriver mode first.");
|
|
991
|
+
console.log("Run: browser-cli ziniao doctor \uFF08\u9010\u9879\u6392\u67E5\u7D2B\u9E1F\u901A\u9053\uFF09");
|
|
859
992
|
process.exit(1);
|
|
860
993
|
}
|
|
861
994
|
if (result.profiles.length === 0) {
|
|
862
995
|
console.log("No ZiNiao profiles found.");
|
|
996
|
+
console.log("Run: browser-cli ziniao doctor \uFF08\u9010\u9879\u6392\u67E5\u7D2B\u9E1F\u901A\u9053\uFF0C\u5E38\u89C1\u539F\u56E0\uFF1Arobot \u8D26\u53F7\u7F3A\u300C\u67E5\u770B\u8D26\u53F7\u300D\u6743\u9650\u6216\u672A\u6388\u6743\u5E97\u94FA\uFF09");
|
|
863
997
|
return;
|
|
864
998
|
}
|
|
865
999
|
console.log(`ZiNiao profiles (${result.profiles.length}):
|
|
@@ -870,9 +1004,16 @@ function registerZiNiaoCommands(program2) {
|
|
|
870
1004
|
console.log("\nUse: browser-cli launch --connector ziniao --ziniao-profile <id> --url <url>");
|
|
871
1005
|
} catch (err) {
|
|
872
1006
|
console.error(formatError(err));
|
|
1007
|
+
const hint = ziniaoErrorHint(err?.message ?? "");
|
|
1008
|
+
if (hint) console.error(hint);
|
|
873
1009
|
process.exit(1);
|
|
874
1010
|
}
|
|
875
1011
|
});
|
|
1012
|
+
ziniao.command("doctor").description("Diagnose the ZiNiao Webdriver chain (credentials / desktop / permission / stores)").action(async () => {
|
|
1013
|
+
const { checks, ok } = await runZiNiaoDoctor(api);
|
|
1014
|
+
console.log(formatDoctorReport(checks));
|
|
1015
|
+
if (!ok) process.exit(1);
|
|
1016
|
+
});
|
|
876
1017
|
ziniao.command("clear").description("Remove ZiNiao credentials").action(async () => {
|
|
877
1018
|
try {
|
|
878
1019
|
await api.deleteZiNiaoConfig();
|
|
@@ -884,6 +1025,50 @@ function registerZiNiaoCommands(program2) {
|
|
|
884
1025
|
});
|
|
885
1026
|
}
|
|
886
1027
|
|
|
1028
|
+
// src/commands/recipes.ts
|
|
1029
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
1030
|
+
function registerRecipeCommands(program2) {
|
|
1031
|
+
program2.command("export-ad-report").description("Export TikTok Shop last-7-days video ad data into the session workspace").requiredOption("--ziniao-profile <id>", "ZiNiao browser profile ID").option("--out <path>", "Output CSV path", "./tt_video_ad_7d.csv").action(async (opts) => {
|
|
1032
|
+
try {
|
|
1033
|
+
const out = await api.runRecipeScript("tiktok_video_ad_export", {
|
|
1034
|
+
ziniao_profile_id: opts.ziniaoProfile
|
|
1035
|
+
});
|
|
1036
|
+
writeFileSync3(opts.out, Buffer.from(out.content_b64, "base64"));
|
|
1037
|
+
console.log(`Saved ${out.filename} -> ${opts.out} (${out.size} bytes)`);
|
|
1038
|
+
} catch (err) {
|
|
1039
|
+
console.error(formatError(err));
|
|
1040
|
+
process.exit(1);
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/commands/bitbrowser.ts
|
|
1046
|
+
function registerBitBrowserCommands(program2) {
|
|
1047
|
+
const bitbrowser = program2.command("bitbrowser").description("Manage BitBrowser (\u6BD4\u7279\u6D4F\u89C8\u5668) anti-detect browser");
|
|
1048
|
+
bitbrowser.command("profiles").description("List BitBrowser profiles (requires Desktop app online + BitBrowser running)").action(async () => {
|
|
1049
|
+
try {
|
|
1050
|
+
const result = await api.getBitBrowserProfiles();
|
|
1051
|
+
if (!result.connected) {
|
|
1052
|
+
console.log(formatDesktopNotConnected(result));
|
|
1053
|
+
process.exit(1);
|
|
1054
|
+
}
|
|
1055
|
+
if (result.profiles.length === 0) {
|
|
1056
|
+
console.log("No BitBrowser profiles found. Is BitBrowser running with its local API enabled?");
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
console.log(`BitBrowser profiles (${result.profiles.length}):
|
|
1060
|
+
`);
|
|
1061
|
+
for (const p of result.profiles) {
|
|
1062
|
+
console.log(` ${p.id} ${p.name}`);
|
|
1063
|
+
}
|
|
1064
|
+
console.log("\nUse: browser-cli launch --connector bitbrowser --bitbrowser-profile <id> --url <url>");
|
|
1065
|
+
} catch (err) {
|
|
1066
|
+
console.error(formatError(err));
|
|
1067
|
+
process.exit(1);
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
|
|
887
1072
|
// src/index.ts
|
|
888
1073
|
var require2 = createRequire(import.meta.url);
|
|
889
1074
|
var { version } = require2("../package.json");
|
|
@@ -903,4 +1088,6 @@ registerTabCommands(program);
|
|
|
903
1088
|
registerScriptCommands(program);
|
|
904
1089
|
registerProfileCommands(program);
|
|
905
1090
|
registerZiNiaoCommands(program);
|
|
1091
|
+
registerRecipeCommands(program);
|
|
1092
|
+
registerBitBrowserCommands(program);
|
|
906
1093
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optima-chat/browser-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"browser-cli": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
11
|
-
".claude"
|
|
10
|
+
"dist"
|
|
12
11
|
],
|
|
13
12
|
"scripts": {
|
|
14
13
|
"build": "tsup",
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: browser
|
|
3
|
-
description: "Browser automation — controls real browsers (Chrome, ZiNiao/紫鸟) via browser-cli. MUST USE when user mentions: 紫鸟, ZiNiao, 反指纹浏览器, 店铺浏览器, TikTok Shop 后台操作, 多店铺管理, or needs to operate any logged-in platform. Also use for opening websites, filling forms, posting content, testing websites. Do NOT use for simple web scraping (use WebFetch)."
|
|
4
|
-
version: "1.1.0"
|
|
5
|
-
owner_repo: Optima-Chat/optima-browser-use
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# 浏览器自动化 Skill
|
|
9
|
-
|
|
10
|
-
**重要:本 Skill 可以直接控制紫鸟浏览器和 Chrome。当用户提到紫鸟、店铺、TikTok Shop 后台时,按下方"紫鸟浏览器"章节操作。**
|
|
11
|
-
|
|
12
|
-
## 使用场景
|
|
13
|
-
|
|
14
|
-
- 用户提到紫鸟浏览器、ZiNiao、反指纹浏览器、多店铺管理
|
|
15
|
-
- 用户要求操作 TikTok Shop 卖家后台、领星 ERP 等需要登录态的平台
|
|
16
|
-
- 用户要求打开网站或访问某个 URL
|
|
17
|
-
- 用户要求登录平台、填写表单或发布内容
|
|
18
|
-
- 用户需要操作没有 API 的第三方平台
|
|
19
|
-
|
|
20
|
-
**不适用场景:**
|
|
21
|
-
- 简单网页抓取 → 使用 WebFetch
|
|
22
|
-
|
|
23
|
-
## 脚本优先原则
|
|
24
|
-
|
|
25
|
-
每次执行浏览器任务前,**先检查是否有现成脚本**:
|
|
26
|
-
|
|
27
|
-
1. `browser-cli scripts` — 查看已有脚本(显示名称、描述、步骤)
|
|
28
|
-
2. 如果有匹配的脚本 → 直接用 `browser-cli runscript <name> --param ...`
|
|
29
|
-
3. 如果没有 → 手动用 browser-cli 命令完成任务
|
|
30
|
-
|
|
31
|
-
脚本执行零 token 消耗,比手动操作快 10 倍以上。
|
|
32
|
-
|
|
33
|
-
## 前提条件:Desktop 桌面应用
|
|
34
|
-
|
|
35
|
-
**Browser Skill 需要用户安装 Optima Browser Operator 桌面应用。** 所有浏览器操作都通过 Desktop app 控制用户本地的 Chrome 或紫鸟浏览器。
|
|
36
|
-
|
|
37
|
-
如果 `browser-cli launch` 返回 `desktop_not_connected` 错误,提示用户:
|
|
38
|
-
|
|
39
|
-
> "需要安装 Optima Browser Operator 桌面应用才能使用浏览器功能。下载地址: https://github.com/Optima-Chat/optima-browser-use/releases"
|
|
40
|
-
|
|
41
|
-
### 紫鸟浏览器(跨境电商多店铺管理)
|
|
42
|
-
|
|
43
|
-
当用户提到紫鸟、多店铺、反指纹浏览器、TikTok Shop 店铺、或需要操作特定店铺时,按以下步骤操作:
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
# 1. 检查紫鸟凭证是否已配置
|
|
47
|
-
browser-cli ziniao show
|
|
48
|
-
|
|
49
|
-
# 2. 如果未配置,设置凭证(需要用户提供)
|
|
50
|
-
browser-cli ziniao set --company "公司名" --username "用户名" --password "密码"
|
|
51
|
-
|
|
52
|
-
# 3. 查看可用的紫鸟店铺列表(需要 Desktop app 在线)
|
|
53
|
-
browser-cli ziniao profiles
|
|
54
|
-
# 输出示例:
|
|
55
|
-
# 27774259938707 迪士尼授权林总店铺
|
|
56
|
-
# 27772540507986 Gigilli-US
|
|
57
|
-
# 27732948017158 马来本土店3SAVEZO
|
|
58
|
-
|
|
59
|
-
# 4. 用店铺 profile 启动浏览器(根据用户描述匹配店铺名)
|
|
60
|
-
browser-cli launch --connector ziniao --ziniao-profile 27774259938707 --url https://seller.tiktokshopglobalselling.com/homepage --force
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
**重要**:
|
|
64
|
-
- 用户说"迪士尼店铺"、"林总店铺"等 → 先 `ziniao profiles` 查列表,按名称模糊匹配找到 profile_id
|
|
65
|
-
- 用户说"打开紫鸟"但没指定店铺 → 先列出 profiles 让用户选
|
|
66
|
-
- 紫鸟需要 Desktop app 在线(紫鸟 WebDriver 会按需自动启动)
|
|
67
|
-
|
|
68
|
-
## 命令
|
|
69
|
-
|
|
70
|
-
### 会话管理
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
browser-cli launch [--url <url>] [--force] # 启动浏览器(自动选择模式)
|
|
74
|
-
browser-cli launch --connector ziniao --ziniao-profile <id> [--url <url>] # 指定紫鸟店铺
|
|
75
|
-
browser-cli launch --connector chrome [--url <url>] # 指定 Chrome
|
|
76
|
-
browser-cli close # 关闭浏览器并释放资源
|
|
77
|
-
browser-cli status # 查看当前会话状态
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### 导航
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
browser-cli navigate <url> # 跳转到 URL
|
|
84
|
-
browser-cli back # 后退
|
|
85
|
-
browser-cli refresh # 刷新页面
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### 交互
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
browser-cli click <index> # 按索引点击元素
|
|
92
|
-
browser-cli input <index> <text> [--no-clear] # 输入文本(--no-clear 追加而非替换)
|
|
93
|
-
browser-cli select <index> <option> # 选择下拉选项
|
|
94
|
-
browser-cli scroll <up|down> [--pages <n>] # 滚动页面(默认 1 页)
|
|
95
|
-
browser-cli keys <key> # 键盘操作(Enter、Tab 等)
|
|
96
|
-
browser-cli upload <index> <file...> # 上传文件到 file input(路径自动转换)
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### 页面检查
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
browser-cli dom # 获取当前 DOM 状态
|
|
103
|
-
browser-cli screenshot [file] # 截图(默认:/tmp/screenshot.png)
|
|
104
|
-
browser-cli search <pattern> # 搜索页面文本
|
|
105
|
-
browser-cli find <css_selector> # 通过 CSS 选择器查询
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### 标签页管理
|
|
109
|
-
|
|
110
|
-
```bash
|
|
111
|
-
browser-cli tabs # 列出所有标签页
|
|
112
|
-
browser-cli switch <tab_id> # 切换标签页
|
|
113
|
-
browser-cli close-tab <tab_id> # 关闭标签页
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### 脚本
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
browser-cli scripts # 列出所有脚本(含描述和步骤)
|
|
120
|
-
browser-cli script-code <name> # 查看脚本源码
|
|
121
|
-
browser-cli runscript <name> --param key=value [...] # 执行脚本(自动轮询到完成)
|
|
122
|
-
browser-cli runscript <name> --param key=value --no-poll # 执行但不等待
|
|
123
|
-
browser-cli script-task <task-id> # 查询执行状态
|
|
124
|
-
browser-cli submit-script --name <name> --code-file <path> # 提交新脚本(新建或覆盖)
|
|
125
|
-
browser-cli delete-script <name> # 删除脚本
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Profile 管理
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
browser-cli profiles # 列出浏览器 profile
|
|
132
|
-
browser-cli delete-profile <id> # 删除 profile
|
|
133
|
-
browser-cli launch --profile <id> # 用指定 profile 启动
|
|
134
|
-
browser-cli runscript <name> --profile <id> --param ... # 用指定 profile 执行脚本
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
## DOM 索引格式
|
|
138
|
-
|
|
139
|
-
每个操作都会返回当前 DOM 状态,格式如下:
|
|
140
|
-
|
|
141
|
-
```
|
|
142
|
-
[N]<tag attributes />
|
|
143
|
-
content
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
- 索引号 (N) **不连续**(如 [7]、[20]、[30])— 这是正常的
|
|
147
|
-
- 每次操作后索引会**重新分配** — 务必使用最新的索引
|
|
148
|
-
- 使用这些索引来执行 `click`、`input` 和 `select` 命令
|
|
149
|
-
|
|
150
|
-
## 操作流程
|
|
151
|
-
|
|
152
|
-
### 流程一:有脚本(常见)
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
# 1. 检查脚本
|
|
156
|
-
browser-cli scripts
|
|
157
|
-
|
|
158
|
-
# 2. 直接执行
|
|
159
|
-
browser-cli runscript xiaohongshu-image-note \
|
|
160
|
-
--param title="今日分享" \
|
|
161
|
-
--param body="内容..." \
|
|
162
|
-
--param 'images=["~/images/photo1.jpg", "~/images/photo2.jpg"]'
|
|
163
|
-
# 输出: ✓ Script completed
|
|
164
|
-
|
|
165
|
-
# 小红书评论
|
|
166
|
-
browser-cli runscript xiaohongshu-comment \
|
|
167
|
-
--param search_keyword="辞职做跨境电商" \
|
|
168
|
-
--param comment="写得好!"
|
|
169
|
-
|
|
170
|
-
# 小红书数据采集
|
|
171
|
-
browser-cli runscript xiaohongshu-data-collect
|
|
172
|
-
|
|
173
|
-
# Instagram 发帖
|
|
174
|
-
browser-cli runscript instagram-image-post \
|
|
175
|
-
--param 'images=["~/images/photo.jpg"]' \
|
|
176
|
-
--param body="Morning vibes ☕"
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### 流程二:无脚本(手动操作)
|
|
180
|
-
|
|
181
|
-
```bash
|
|
182
|
-
# 1. 启动浏览器
|
|
183
|
-
browser-cli launch --url https://example.com
|
|
184
|
-
|
|
185
|
-
# 2. 查看 DOM,找到要操作的元素
|
|
186
|
-
browser-cli dom
|
|
187
|
-
|
|
188
|
-
# 3. 操作元素(用 DOM 返回的索引)
|
|
189
|
-
browser-cli click 15
|
|
190
|
-
browser-cli input 22 "内容"
|
|
191
|
-
browser-cli keys Enter
|
|
192
|
-
|
|
193
|
-
# 4. 完成后关闭
|
|
194
|
-
browser-cli close
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### 流程三:脚本失败(Fallback)
|
|
198
|
-
|
|
199
|
-
```bash
|
|
200
|
-
# 1. 执行脚本
|
|
201
|
-
browser-cli runscript xiaohongshu-image-note --param ...
|
|
202
|
-
# 输出: ⚠ Script needs fallback
|
|
203
|
-
# → 显示已完成和剩余步骤
|
|
204
|
-
|
|
205
|
-
# 2. Session 仍然打开 — 用手动命令继续完成剩余步骤
|
|
206
|
-
browser-cli dom
|
|
207
|
-
browser-cli click 15
|
|
208
|
-
browser-cli input 22 "内容"
|
|
209
|
-
|
|
210
|
-
# 3. 完成后关闭
|
|
211
|
-
browser-cli close
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
## 文件路径
|
|
215
|
-
|
|
216
|
-
浏览器后端可以直接读取用户工作空间的文件。传文件路径时直接使用你看到的路径即可,后端会自动转换:
|
|
217
|
-
|
|
218
|
-
```
|
|
219
|
-
~/images/photo.jpg → 自动转换 ✓
|
|
220
|
-
/home/aiuser/images/photo.jpg → 自动转换 ✓
|
|
221
|
-
/mnt/efs/{userId}/images/... → 自动转换 ✓
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
## Desktop 桌面应用未连接时
|
|
225
|
-
|
|
226
|
-
如果 `browser-cli launch` 返回错误,提示用户安装 Desktop app:
|
|
227
|
-
|
|
228
|
-
1. 下载安装(Mac .dmg / Windows .exe):https://github.com/Optima-Chat/optima-browser-use/releases
|
|
229
|
-
2. 首次启动自动打开浏览器登录授权
|
|
230
|
-
3. 授权后自动连接
|
|
231
|
-
|
|
232
|
-
**跨境电商用户(多店铺)**:推荐安装紫鸟浏览器 + Desktop app,可以按店铺 profile 分别操作,每个店铺独立登录态。
|
|
233
|
-
|
|
234
|
-
## 注意事项
|
|
235
|
-
|
|
236
|
-
- **脚本优先**:每次操作前先 `browser-cli scripts`,有脚本就用脚本
|
|
237
|
-
- **DOM 自动返回**:每个操作会自动返回 DOM 状态 — 无需单独调用 `browser-cli dom`
|
|
238
|
-
- **避免不必要的截图**:截图消耗 token,日常导航用 DOM 输出即可
|
|
239
|
-
- **5 分钟超时**:无操作 5 分钟后会话自动关闭
|
|
240
|
-
- **务必关闭**:操作完成后运行 `browser-cli close` 释放资源
|
|
241
|
-
- **login_required 处理**:如果 runscript 返回 `login_required` 错误,说明该平台未登录。让用户在自己的浏览器里手动登录该平台,然后重新 `runscript`
|
|
242
|
-
- **Fallback 后更新脚本**:如果脚本频繁失败,重新生成并 submit-script 覆盖
|