@optima-chat/browser-cli 0.14.0 → 0.15.1
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 +123 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -183,11 +183,12 @@ async function requestBinary(path) {
|
|
|
183
183
|
return Buffer.from(arrayBuffer);
|
|
184
184
|
}
|
|
185
185
|
var BrowserAPI = class {
|
|
186
|
-
async createSession(url, force, connector, ziniaoProfileId, bitbrowserProfileId, feikuaProfileId) {
|
|
186
|
+
async createSession(url, force, connector, ziniaoProfileId, bitbrowserProfileId, feikuaProfileId, chromeProfile) {
|
|
187
187
|
const body = {};
|
|
188
188
|
if (url) body.url = url;
|
|
189
189
|
if (force) body.force = true;
|
|
190
190
|
if (connector) body.connector = connector;
|
|
191
|
+
if (chromeProfile) body.chrome_profile = chromeProfile;
|
|
191
192
|
if (ziniaoProfileId) body.ziniao_profile_id = ziniaoProfileId;
|
|
192
193
|
if (bitbrowserProfileId) body.bitbrowser_profile_id = bitbrowserProfileId;
|
|
193
194
|
if (feikuaProfileId) body.feikua_profile_id = feikuaProfileId;
|
|
@@ -211,6 +212,10 @@ var BrowserAPI = class {
|
|
|
211
212
|
async getZiNiaoProfiles() {
|
|
212
213
|
return request("/api/user/ziniao-profiles");
|
|
213
214
|
}
|
|
215
|
+
// logged_in 缺席 = 没探(未开 check、无 homeUrl 或探测失败),下游按未知处理,别当 false。
|
|
216
|
+
async getChromeProfiles(check) {
|
|
217
|
+
return request(`/api/user/chrome-profiles${check ? "?check=true" : ""}`);
|
|
218
|
+
}
|
|
214
219
|
async getBitBrowserProfiles() {
|
|
215
220
|
return request("/api/user/bitbrowser-profiles");
|
|
216
221
|
}
|
|
@@ -370,9 +375,10 @@ var BrowserAPI = class {
|
|
|
370
375
|
async deleteScript(name) {
|
|
371
376
|
await request(`/api/scripts/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
372
377
|
}
|
|
373
|
-
async runScript(scriptName, params = {}, connector, ziniaoProfileId, bitbrowserProfileId, feikuaProfileId) {
|
|
378
|
+
async runScript(scriptName, params = {}, connector, ziniaoProfileId, bitbrowserProfileId, feikuaProfileId, chromeProfile) {
|
|
374
379
|
const payload = { params };
|
|
375
380
|
if (connector) payload.connector = connector;
|
|
381
|
+
if (chromeProfile) payload.chrome_profile = chromeProfile;
|
|
376
382
|
if (ziniaoProfileId) payload.ziniao_profile_id = ziniaoProfileId;
|
|
377
383
|
if (bitbrowserProfileId) payload.bitbrowser_profile_id = bitbrowserProfileId;
|
|
378
384
|
if (feikuaProfileId) payload.feikua_profile_id = feikuaProfileId;
|
|
@@ -430,9 +436,13 @@ function clearSessionId(onlyIfEquals) {
|
|
|
430
436
|
}
|
|
431
437
|
|
|
432
438
|
// src/formatter.ts
|
|
439
|
+
var URL_STALE_WARNING = "\u26A0 URL/title may be stale: the live page probe failed, values above are cached. Verify with a screenshot before concluding a navigation failed.";
|
|
433
440
|
function formatPageState(state) {
|
|
434
441
|
const lines = [];
|
|
435
442
|
lines.push(`Page: ${state.title} (${state.url})`);
|
|
443
|
+
if (state.url_stale) {
|
|
444
|
+
lines.push(URL_STALE_WARNING);
|
|
445
|
+
}
|
|
436
446
|
lines.push("");
|
|
437
447
|
lines.push("DOM:");
|
|
438
448
|
lines.push(state.dom);
|
|
@@ -472,6 +482,9 @@ function formatTabs(tabList) {
|
|
|
472
482
|
for (const tab of tabList.tabs) {
|
|
473
483
|
lines.push(` [${tab.tab_id}] ${tab.title} (${tab.url})`);
|
|
474
484
|
}
|
|
485
|
+
if (tabList.url_stale) {
|
|
486
|
+
lines.push(URL_STALE_WARNING);
|
|
487
|
+
}
|
|
475
488
|
return lines.join("\n");
|
|
476
489
|
}
|
|
477
490
|
function formatSearch(result) {
|
|
@@ -515,9 +528,9 @@ function formatTabsInline(tabs) {
|
|
|
515
528
|
|
|
516
529
|
// src/commands/lifecycle.ts
|
|
517
530
|
function registerLifecycleCommands(program2) {
|
|
518
|
-
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) => {
|
|
531
|
+
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").option("--chrome-profile <name>", "Chrome named profile (per-store login state; default: shared profile)").action(async (opts) => {
|
|
519
532
|
try {
|
|
520
|
-
const state = await api.createSession(opts.url, opts.force, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile, opts.feikuaProfile);
|
|
533
|
+
const state = await api.createSession(opts.url, opts.force, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile, opts.feikuaProfile, opts.chromeProfile);
|
|
521
534
|
saveSessionId(state.session_id);
|
|
522
535
|
console.log(formatLaunch(state));
|
|
523
536
|
} catch (err) {
|
|
@@ -758,8 +771,9 @@ function registerTabCommands(program2) {
|
|
|
758
771
|
}
|
|
759
772
|
|
|
760
773
|
// src/commands/script.ts
|
|
761
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
762
|
-
import {
|
|
774
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
775
|
+
import { tmpdir } from "os";
|
|
776
|
+
import { basename, dirname, join as join3, resolve } from "path";
|
|
763
777
|
var POLL_INTERVAL_MS = 2e3;
|
|
764
778
|
var POLL_TIMEOUT_MS = 18e4;
|
|
765
779
|
var REQUIRED_ARG_FLAG = {
|
|
@@ -772,16 +786,50 @@ function formatRequiredArgs(s) {
|
|
|
772
786
|
const flags = s.required_args.map((a) => REQUIRED_ARG_FLAG[a] ?? `--param ${a}=<value>`).join(" ");
|
|
773
787
|
return ` requires: ${flags}`;
|
|
774
788
|
}
|
|
789
|
+
var ArtifactLostError = class extends Error {
|
|
790
|
+
};
|
|
791
|
+
var errMsg = (err) => err instanceof Error ? err.message : String(err);
|
|
792
|
+
function resolveArtifactTarget(out, name) {
|
|
793
|
+
if (!out) return resolve(`./${name}`);
|
|
794
|
+
const wantsDir = /[\\/]$/.test(out) || existsSync3(out) && statSync(out).isDirectory();
|
|
795
|
+
return wantsDir ? resolve(out, name) : resolve(out);
|
|
796
|
+
}
|
|
775
797
|
async function deliverArtifact(task, out) {
|
|
776
798
|
const output = task.output;
|
|
777
799
|
if (!output) return null;
|
|
778
|
-
if (output.path) return output.path;
|
|
800
|
+
if (output.path) return { path: output.path, size: output.size };
|
|
779
801
|
if (!output.artifact_path) return null;
|
|
780
|
-
const
|
|
781
|
-
const
|
|
802
|
+
const name = basename(output.filename ?? "") || "artifact";
|
|
803
|
+
const target = resolveArtifactTarget(out, name);
|
|
782
804
|
mkdirSync2(dirname(target), { recursive: true });
|
|
783
|
-
|
|
784
|
-
|
|
805
|
+
const content = await api.fetchArtifact(output.artifact_path);
|
|
806
|
+
try {
|
|
807
|
+
writeFileSync3(target, content);
|
|
808
|
+
} catch (err) {
|
|
809
|
+
const fallback = join3(tmpdir(), name);
|
|
810
|
+
try {
|
|
811
|
+
writeFileSync3(fallback, content);
|
|
812
|
+
} catch (err2) {
|
|
813
|
+
throw new ArtifactLostError(
|
|
814
|
+
`Could not write ${target} (${errMsg(err)}) nor ${fallback} (${errMsg(err2)}); the artifact was already fetched and is no longer on the server`
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
console.error(`Could not write ${target} (${errMsg(err)}); saved to ${fallback} instead`);
|
|
818
|
+
return { path: fallback, size: content.length };
|
|
819
|
+
}
|
|
820
|
+
return { path: target, size: content.length };
|
|
821
|
+
}
|
|
822
|
+
function artifactUrl(artifactPath) {
|
|
823
|
+
return `${BROWSER_API_URL}${artifactPath}`;
|
|
824
|
+
}
|
|
825
|
+
function hasStagedArtifact(task) {
|
|
826
|
+
const output = task.output;
|
|
827
|
+
return task.status === "completed" && !!output?.artifact_path;
|
|
828
|
+
}
|
|
829
|
+
function downloadHint(task) {
|
|
830
|
+
const filename = task.output?.filename;
|
|
831
|
+
const dest = filename ? ` ./${basename(filename)}` : "";
|
|
832
|
+
return `Download with: browser-cli script-task ${task.id} --download${dest}`;
|
|
785
833
|
}
|
|
786
834
|
function formatScriptList(scripts) {
|
|
787
835
|
if (scripts.length === 0) return "No scripts registered.";
|
|
@@ -804,7 +852,7 @@ function formatScriptTask(task) {
|
|
|
804
852
|
lines.push("\u2713 Script completed");
|
|
805
853
|
if (task.output) {
|
|
806
854
|
for (const [k, v] of Object.entries(task.output)) {
|
|
807
|
-
lines.push(`${k}: ${v}`);
|
|
855
|
+
lines.push(k === "artifact_path" ? `${k}: ${artifactUrl(String(v))}` : `${k}: ${v}`);
|
|
808
856
|
}
|
|
809
857
|
}
|
|
810
858
|
} else if (task.status === "needs_fallback") {
|
|
@@ -881,7 +929,7 @@ function registerScriptCommands(program2) {
|
|
|
881
929
|
process.exit(1);
|
|
882
930
|
}
|
|
883
931
|
});
|
|
884
|
-
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) => {
|
|
932
|
+
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("--chrome-profile <name>", "Chrome named profile (per-store login state, with --connector chrome)").option("--out <path>", "Where to write the script's exported file (default: ./<filename>)").option("--no-poll", "Return immediately without polling").action(async (name, opts) => {
|
|
885
933
|
try {
|
|
886
934
|
const params = {};
|
|
887
935
|
if (opts.param) {
|
|
@@ -891,7 +939,7 @@ function registerScriptCommands(program2) {
|
|
|
891
939
|
}
|
|
892
940
|
}
|
|
893
941
|
if (opts.out) params.out_path = resolve(opts.out);
|
|
894
|
-
const task = await api.runScript(name, params, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile, opts.feikuaProfile);
|
|
942
|
+
const task = await api.runScript(name, params, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile, opts.feikuaProfile, opts.chromeProfile);
|
|
895
943
|
console.log(`Script started (task: ${task.id})`);
|
|
896
944
|
if (!opts.poll) {
|
|
897
945
|
console.log(`Poll with: browser-cli script-task ${task.id}`);
|
|
@@ -915,12 +963,15 @@ function registerScriptCommands(program2) {
|
|
|
915
963
|
if (result.status === "completed") {
|
|
916
964
|
try {
|
|
917
965
|
const saved = await deliverArtifact(result, opts.out);
|
|
918
|
-
if (saved) console.log(`Saved -> ${saved}`);
|
|
966
|
+
if (saved) console.log(`Saved -> ${saved.path}`);
|
|
919
967
|
} catch (err) {
|
|
920
968
|
const staged = result.output?.artifact_path;
|
|
921
969
|
console.error(`${formatError(err)}
|
|
922
970
|
Script completed; only saving the file failed.`);
|
|
923
|
-
if (staged
|
|
971
|
+
if (staged && !(err instanceof ArtifactLostError)) {
|
|
972
|
+
console.error(`The artifact is still at ${artifactUrl(staged)}.
|
|
973
|
+
${downloadHint(result)}`);
|
|
974
|
+
}
|
|
924
975
|
process.exit(1);
|
|
925
976
|
}
|
|
926
977
|
}
|
|
@@ -929,12 +980,26 @@ Script completed; only saving the file failed.`);
|
|
|
929
980
|
process.exit(1);
|
|
930
981
|
}
|
|
931
982
|
});
|
|
932
|
-
program2.command("script-task").argument("<task-id>", "Task ID").description("Check script task status").action(async (taskId) => {
|
|
983
|
+
program2.command("script-task").argument("<task-id>", "Task ID").description("Check script task status").option("--download [path]", "Save the task's exported file (default: ./<filename>)").action(async (taskId, opts) => {
|
|
933
984
|
try {
|
|
934
985
|
const task = await api.getScriptTask(taskId);
|
|
935
|
-
|
|
986
|
+
if (opts.download === void 0) {
|
|
987
|
+
console.log(formatScriptTask(task));
|
|
988
|
+
if (hasStagedArtifact(task)) console.log(downloadHint(task));
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
if (task.status !== "completed") {
|
|
992
|
+
throw new Error(`Task ${taskId} is ${task.status} \u2014 nothing to download yet`);
|
|
993
|
+
}
|
|
994
|
+
const saved = await deliverArtifact(task, typeof opts.download === "string" ? opts.download : void 0);
|
|
995
|
+
if (!saved) throw new Error(`Task ${taskId} produced no file`);
|
|
996
|
+
console.log(`Saved -> ${saved.path}${saved.size === void 0 ? "" : ` (${saved.size} bytes)`}`);
|
|
936
997
|
} catch (err) {
|
|
937
|
-
|
|
998
|
+
const gone = err.errorCode === "artifact_gone";
|
|
999
|
+
console.error(
|
|
1000
|
+
gone ? `${formatError(err)}
|
|
1001
|
+
Each artifact can be fetched once; if runscript printed "Saved ->", the file is already local.` : formatError(err)
|
|
1002
|
+
);
|
|
938
1003
|
process.exit(1);
|
|
939
1004
|
}
|
|
940
1005
|
});
|
|
@@ -1116,6 +1181,44 @@ function registerZiNiaoCommands(program2) {
|
|
|
1116
1181
|
});
|
|
1117
1182
|
}
|
|
1118
1183
|
|
|
1184
|
+
// src/commands/chrome.ts
|
|
1185
|
+
function registerChromeCommands(program2) {
|
|
1186
|
+
const chrome = program2.command("chrome").description("Manage Chrome named profiles (per-store login state)");
|
|
1187
|
+
chrome.command("profiles").description("List Chrome named profiles (requires Desktop app online)").option("--check", "Probe login state for profiles with a homeUrl (opens a headed Chrome per profile, ~10s each)").action(async (opts) => {
|
|
1188
|
+
try {
|
|
1189
|
+
const result = await api.getChromeProfiles(opts.check);
|
|
1190
|
+
if (!result.connected) {
|
|
1191
|
+
console.log(formatDesktopNotConnected(result));
|
|
1192
|
+
process.exit(1);
|
|
1193
|
+
}
|
|
1194
|
+
console.log(`Chrome profiles (${result.profiles.length}):
|
|
1195
|
+
`);
|
|
1196
|
+
for (const p of result.profiles) {
|
|
1197
|
+
const label = p.name === "default" ? "\u5171\u4EAB\u9ED8\u8BA4" : p.label ?? "-";
|
|
1198
|
+
const login = p.logged_in === void 0 ? "-" : p.logged_in ? "\u2705" : "\u274C \u9700\u4EBA\u5DE5\u91CD\u767B";
|
|
1199
|
+
const home = p.homeUrl ? ` ${p.homeUrl}` : "";
|
|
1200
|
+
console.log(` ${p.name.padEnd(12)} ${label.padEnd(18)} running: ${p.running ? "yes" : "no"} logged_in: ${login}${home}`);
|
|
1201
|
+
}
|
|
1202
|
+
console.log("\nUse: browser-cli launch --connector chrome --chrome-profile <name> --url <url>");
|
|
1203
|
+
if (!opts.check) console.log("Add --check to probe login state (profiles with a homeUrl only).");
|
|
1204
|
+
} catch (err) {
|
|
1205
|
+
console.error(formatError(err));
|
|
1206
|
+
process.exit(1);
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
chrome.command("open").description("Open a named profile's headed Chrome for manual login (the official login entry)").requiredOption("--profile <name>", "Chrome named profile to open (created on first use)").option("--url <url>", "Page to open, e.g. the seller center login page").action(async (opts) => {
|
|
1210
|
+
try {
|
|
1211
|
+
const state = await api.createSession(opts.url, false, "chrome", void 0, void 0, void 0, opts.profile);
|
|
1212
|
+
saveSessionId(state.session_id);
|
|
1213
|
+
console.log(`\u2713 Opened Chrome profile "${opts.profile}"${opts.url ? ` at ${opts.url}` : ""} (session: ${state.session_id})`);
|
|
1214
|
+
console.log("\u5728\u5F39\u51FA\u7684 Chrome \u7A97\u53E3\u91CC\u5B8C\u6210\u767B\u5F55;\u767B\u5F55\u6001\u4F1A\u7559\u5728\u8BE5 profile,\u4E4B\u540E\u5E26 --chrome-profile \u7684\u8C03\u7528\u76F4\u63A5\u590D\u7528\u3002");
|
|
1215
|
+
} catch (err) {
|
|
1216
|
+
console.error(formatError(err));
|
|
1217
|
+
process.exit(1);
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1119
1222
|
// src/commands/recipes.ts
|
|
1120
1223
|
import { writeFileSync as writeFileSync4 } from "fs";
|
|
1121
1224
|
import { resolve as resolve2 } from "path";
|
|
@@ -1339,6 +1442,7 @@ registerInfoCommands(program);
|
|
|
1339
1442
|
registerTabCommands(program);
|
|
1340
1443
|
registerScriptCommands(program);
|
|
1341
1444
|
registerZiNiaoCommands(program);
|
|
1445
|
+
registerChromeCommands(program);
|
|
1342
1446
|
registerRecipeCommands(program);
|
|
1343
1447
|
registerBitBrowserCommands(program);
|
|
1344
1448
|
registerFeikuaCommands(program);
|