@optima-chat/browser-cli 0.10.1 → 0.11.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 +147 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -183,13 +183,14 @@ 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) {
|
|
186
|
+
async createSession(url, force, connector, ziniaoProfileId, bitbrowserProfileId, feikuaProfileId) {
|
|
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
191
|
if (ziniaoProfileId) body.ziniao_profile_id = ziniaoProfileId;
|
|
192
192
|
if (bitbrowserProfileId) body.bitbrowser_profile_id = bitbrowserProfileId;
|
|
193
|
+
if (feikuaProfileId) body.feikua_profile_id = feikuaProfileId;
|
|
193
194
|
return request("/api/sessions", {
|
|
194
195
|
method: "POST",
|
|
195
196
|
body: JSON.stringify(body)
|
|
@@ -213,6 +214,11 @@ var BrowserAPI = class {
|
|
|
213
214
|
async getBitBrowserProfiles() {
|
|
214
215
|
return request("/api/user/bitbrowser-profiles");
|
|
215
216
|
}
|
|
217
|
+
// paired_count = 配对过的店铺浏览器总数(含没在运行的)。老桌面端不返回它,
|
|
218
|
+
// 所以是可选的 —— 缺失时调用方只能退回「从未配对」的指引。
|
|
219
|
+
async getFeikuaProfiles() {
|
|
220
|
+
return request("/api/user/feikua-profiles");
|
|
221
|
+
}
|
|
216
222
|
async closeSession(sessionId) {
|
|
217
223
|
await request(`/api/sessions/${sessionId}`, { method: "DELETE" });
|
|
218
224
|
}
|
|
@@ -243,8 +249,18 @@ var BrowserAPI = class {
|
|
|
243
249
|
async input(sessionId, index, text, clear = true) {
|
|
244
250
|
return this.actThenState(sessionId, "input", { index, text, clear });
|
|
245
251
|
}
|
|
246
|
-
async scroll(sessionId, down, pages = 1) {
|
|
247
|
-
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 });
|
|
248
264
|
}
|
|
249
265
|
async keys(sessionId, keys) {
|
|
250
266
|
return this.actThenState(sessionId, "keys", { keys });
|
|
@@ -342,11 +358,12 @@ var BrowserAPI = class {
|
|
|
342
358
|
async deleteScript(name) {
|
|
343
359
|
await request(`/api/scripts/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
344
360
|
}
|
|
345
|
-
async runScript(scriptName, params = {}, connector, ziniaoProfileId, bitbrowserProfileId) {
|
|
361
|
+
async runScript(scriptName, params = {}, connector, ziniaoProfileId, bitbrowserProfileId, feikuaProfileId) {
|
|
346
362
|
const payload = { params };
|
|
347
363
|
if (connector) payload.connector = connector;
|
|
348
364
|
if (ziniaoProfileId) payload.ziniao_profile_id = ziniaoProfileId;
|
|
349
365
|
if (bitbrowserProfileId) payload.bitbrowser_profile_id = bitbrowserProfileId;
|
|
366
|
+
if (feikuaProfileId) payload.feikua_profile_id = feikuaProfileId;
|
|
350
367
|
return request(`/api/scripts/${encodeURIComponent(scriptName)}/run`, {
|
|
351
368
|
method: "POST",
|
|
352
369
|
body: JSON.stringify(payload)
|
|
@@ -361,6 +378,9 @@ var BrowserAPI = class {
|
|
|
361
378
|
body: JSON.stringify({ src, dest })
|
|
362
379
|
});
|
|
363
380
|
}
|
|
381
|
+
async listVideos() {
|
|
382
|
+
return request("/api/files/videos");
|
|
383
|
+
}
|
|
364
384
|
};
|
|
365
385
|
var api = new BrowserAPI();
|
|
366
386
|
|
|
@@ -450,7 +470,12 @@ function formatFind(result) {
|
|
|
450
470
|
const lines = [`Found ${result.total} element(s):`, ""];
|
|
451
471
|
for (const match of result.matches) {
|
|
452
472
|
const attrs = Object.entries(match.attributes).map(([k, v]) => `${k}="${v}"`).join(" ");
|
|
453
|
-
|
|
473
|
+
const state = Object.entries(match.state ?? {}).filter(([, v]) => v).map(([k]) => k).join(" ");
|
|
474
|
+
const head = ` [${match.index}] <${match.tag} ${attrs}>`;
|
|
475
|
+
lines.push(state ? `${head} [${state}]` : head);
|
|
476
|
+
if (match.text) {
|
|
477
|
+
lines.push(` ${match.text}`);
|
|
478
|
+
}
|
|
454
479
|
}
|
|
455
480
|
return lines.join("\n");
|
|
456
481
|
}
|
|
@@ -469,9 +494,9 @@ function formatTabsInline(tabs) {
|
|
|
469
494
|
|
|
470
495
|
// src/commands/lifecycle.ts
|
|
471
496
|
function registerLifecycleCommands(program2) {
|
|
472
|
-
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
|
|
497
|
+
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) => {
|
|
473
498
|
try {
|
|
474
|
-
const state = await api.createSession(opts.url, opts.force, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile);
|
|
499
|
+
const state = await api.createSession(opts.url, opts.force, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile, opts.feikuaProfile);
|
|
475
500
|
saveSessionId(state.session_id);
|
|
476
501
|
console.log(formatLaunch(state));
|
|
477
502
|
} catch (err) {
|
|
@@ -563,12 +588,36 @@ function registerInteractionCommands(program2) {
|
|
|
563
588
|
process.exit(1);
|
|
564
589
|
}
|
|
565
590
|
});
|
|
566
|
-
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").
|
|
591
|
+
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(
|
|
592
|
+
"--index <n>",
|
|
593
|
+
"Scroll inside this element instead of the window (for pages with inner scroll containers)"
|
|
594
|
+
).action(async (direction, opts) => {
|
|
567
595
|
try {
|
|
568
596
|
const sessionId = loadSessionId();
|
|
569
597
|
const down = direction.toLowerCase() === "down";
|
|
570
598
|
const pages = parseInt(opts.pages, 10);
|
|
571
|
-
const
|
|
599
|
+
const index = opts.index === void 0 ? void 0 : parseInt(opts.index, 10);
|
|
600
|
+
const state = await api.scroll(sessionId, down, pages, index);
|
|
601
|
+
console.log(formatPageState(state));
|
|
602
|
+
} catch (err) {
|
|
603
|
+
console.error(formatError(err));
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
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) => {
|
|
608
|
+
try {
|
|
609
|
+
const sessionId = loadSessionId();
|
|
610
|
+
const state = await api.clickTo(sessionId, selector, opts.native === true);
|
|
611
|
+
console.log(formatPageState(state));
|
|
612
|
+
} catch (err) {
|
|
613
|
+
console.error(formatError(err));
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
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) => {
|
|
618
|
+
try {
|
|
619
|
+
const sessionId = loadSessionId();
|
|
620
|
+
const state = await api.scrollTo(sessionId, selector);
|
|
572
621
|
console.log(formatPageState(state));
|
|
573
622
|
} catch (err) {
|
|
574
623
|
console.error(formatError(err));
|
|
@@ -694,7 +743,8 @@ var POLL_INTERVAL_MS = 2e3;
|
|
|
694
743
|
var POLL_TIMEOUT_MS = 18e4;
|
|
695
744
|
var REQUIRED_ARG_FLAG = {
|
|
696
745
|
ziniao_profile_id: "--ziniao-profile <id>",
|
|
697
|
-
bitbrowser_profile_id: "--bitbrowser-profile <id>"
|
|
746
|
+
bitbrowser_profile_id: "--bitbrowser-profile <id>",
|
|
747
|
+
feikua_profile_id: "--feikua-profile <id>"
|
|
698
748
|
};
|
|
699
749
|
function formatRequiredArgs(s) {
|
|
700
750
|
if (!s.required_args?.length) return null;
|
|
@@ -810,7 +860,7 @@ function registerScriptCommands(program2) {
|
|
|
810
860
|
process.exit(1);
|
|
811
861
|
}
|
|
812
862
|
});
|
|
813
|
-
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
|
|
863
|
+
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) => {
|
|
814
864
|
try {
|
|
815
865
|
const params = {};
|
|
816
866
|
if (opts.param) {
|
|
@@ -821,7 +871,7 @@ function registerScriptCommands(program2) {
|
|
|
821
871
|
}
|
|
822
872
|
if (opts.out) params.out_path = resolve(opts.out);
|
|
823
873
|
clearSessionId();
|
|
824
|
-
const task = await api.runScript(name, params, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile);
|
|
874
|
+
const task = await api.runScript(name, params, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile, opts.feikuaProfile);
|
|
825
875
|
console.log(`Script started (task: ${task.id})`);
|
|
826
876
|
if (!opts.poll) {
|
|
827
877
|
console.log(`Poll with: browser-cli script-task ${task.id}`);
|
|
@@ -1049,6 +1099,7 @@ function registerZiNiaoCommands(program2) {
|
|
|
1049
1099
|
// src/commands/recipes.ts
|
|
1050
1100
|
import { writeFileSync as writeFileSync4 } from "fs";
|
|
1051
1101
|
import { resolve as resolve2 } from "path";
|
|
1102
|
+
var PUBLISH_TIMEOUT_MS = 144e4;
|
|
1052
1103
|
async function runExport(script, params, out, timeoutMs) {
|
|
1053
1104
|
const outPath = resolve2(out);
|
|
1054
1105
|
const result = await api.runRecipeScript(script, { ...params, out_path: outPath }, 2e3, timeoutMs);
|
|
@@ -1084,6 +1135,32 @@ function registerRecipeCommands(program2) {
|
|
|
1084
1135
|
process.exit(1);
|
|
1085
1136
|
}
|
|
1086
1137
|
});
|
|
1138
|
+
program2.command("publish-video").description("Publish one shoppable video to TikTok Shop").requiredOption("--ziniao-profile <id>", "ZiNiao browser profile ID").requiredOption("--video <name>", "Video file name (from list-videos)").requiredOption("--product-query <keyword>", "Product search keyword, narrow enough to hit exactly one product (ask the user)").requiredOption(
|
|
1139
|
+
"--product-name <text>",
|
|
1140
|
+
"Display name on the video: max 30 chars, letters/digits/spaces only (ask the user)"
|
|
1141
|
+
).requiredOption("--caption <text>", "Video caption").option("--hashtags <text>", "Comma-separated hashtags").option("--publish-url <url>", "Video publish page URL").action(async (opts) => {
|
|
1142
|
+
try {
|
|
1143
|
+
const params = {
|
|
1144
|
+
ziniao_profile_id: opts.ziniaoProfile,
|
|
1145
|
+
video: opts.video,
|
|
1146
|
+
product_query: opts.productQuery,
|
|
1147
|
+
product_name: opts.productName,
|
|
1148
|
+
caption: opts.caption
|
|
1149
|
+
};
|
|
1150
|
+
if (opts.hashtags) params.hashtags = opts.hashtags;
|
|
1151
|
+
if (opts.publishUrl) params.publish_url = opts.publishUrl;
|
|
1152
|
+
const result = await api.runRecipeScript(
|
|
1153
|
+
"tiktok_video_publish",
|
|
1154
|
+
params,
|
|
1155
|
+
2e3,
|
|
1156
|
+
PUBLISH_TIMEOUT_MS
|
|
1157
|
+
);
|
|
1158
|
+
console.log(result.detail);
|
|
1159
|
+
} catch (err) {
|
|
1160
|
+
console.error(formatError(err));
|
|
1161
|
+
process.exit(1);
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1087
1164
|
}
|
|
1088
1165
|
|
|
1089
1166
|
// src/commands/bitbrowser.ts
|
|
@@ -1113,7 +1190,55 @@ function registerBitBrowserCommands(program2) {
|
|
|
1113
1190
|
});
|
|
1114
1191
|
}
|
|
1115
1192
|
|
|
1193
|
+
// src/commands/feikua.ts
|
|
1194
|
+
function registerFeikuaCommands(program2) {
|
|
1195
|
+
const feikua = program2.command("feikua").description("Manage Feikua (\u98DE\u8DE8) anti-detect browser");
|
|
1196
|
+
feikua.command("profiles").description("List paired Feikua shop browsers (requires Desktop app online + extension paired)").action(async () => {
|
|
1197
|
+
try {
|
|
1198
|
+
const result = await api.getFeikuaProfiles();
|
|
1199
|
+
if (!result.connected) {
|
|
1200
|
+
console.log(formatDesktopNotConnected(result));
|
|
1201
|
+
process.exit(1);
|
|
1202
|
+
}
|
|
1203
|
+
if (result.profiles.length === 0) {
|
|
1204
|
+
if (result.paired_count) {
|
|
1205
|
+
console.log(
|
|
1206
|
+
`${result.paired_count} paired shop browser(s), but none are running. Open the Feikua shop browser you paired, then retry.`
|
|
1207
|
+
);
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
console.log(
|
|
1211
|
+
'No paired shop browsers. Open the desktop app tray and choose "Pair a Shop Browser", then follow the on-screen code.'
|
|
1212
|
+
);
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
console.log(`Feikua shop browsers (${result.profiles.length}):
|
|
1216
|
+
`);
|
|
1217
|
+
for (const p of result.profiles) {
|
|
1218
|
+
console.log(` ${p.id} ${p.name}`);
|
|
1219
|
+
}
|
|
1220
|
+
console.log("\nUse: browser-cli launch --connector feikua --feikua-profile <id> --url <url>");
|
|
1221
|
+
} catch (err) {
|
|
1222
|
+
console.error(formatError(err));
|
|
1223
|
+
process.exit(1);
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1116
1228
|
// src/commands/files.ts
|
|
1229
|
+
function formatVideoList(videos) {
|
|
1230
|
+
if (videos.length === 0) {
|
|
1231
|
+
return `No videos found. Put your videos in:
|
|
1232
|
+
macOS/Linux: ~/Downloads/OptimaConnect/videos
|
|
1233
|
+
Windows: C:\\Users\\<\u7528\u6237\u540D>\\Downloads\\OptimaConnect\\videos
|
|
1234
|
+
Then try again.`;
|
|
1235
|
+
}
|
|
1236
|
+
return videos.map((v) => {
|
|
1237
|
+
const mb = (v.size / 1024 / 1024).toFixed(1);
|
|
1238
|
+
const time = v.mtime.slice(0, 19).replace("T", " ") + " UTC";
|
|
1239
|
+
return `${v.name} \u2014 ${mb} MB, ${time}`;
|
|
1240
|
+
}).join("\n");
|
|
1241
|
+
}
|
|
1117
1242
|
function registerFileCommands(program2) {
|
|
1118
1243
|
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) => {
|
|
1119
1244
|
try {
|
|
@@ -1124,6 +1249,15 @@ function registerFileCommands(program2) {
|
|
|
1124
1249
|
process.exit(1);
|
|
1125
1250
|
}
|
|
1126
1251
|
});
|
|
1252
|
+
program2.command("list-videos").description("List videos in your local video folder (requires Desktop app online)").action(async () => {
|
|
1253
|
+
try {
|
|
1254
|
+
const { videos } = await api.listVideos();
|
|
1255
|
+
console.log(formatVideoList(videos));
|
|
1256
|
+
} catch (err) {
|
|
1257
|
+
console.error(formatError(err));
|
|
1258
|
+
process.exit(1);
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1127
1261
|
}
|
|
1128
1262
|
|
|
1129
1263
|
// src/index.ts
|
|
@@ -1146,5 +1280,6 @@ registerScriptCommands(program);
|
|
|
1146
1280
|
registerZiNiaoCommands(program);
|
|
1147
1281
|
registerRecipeCommands(program);
|
|
1148
1282
|
registerBitBrowserCommands(program);
|
|
1283
|
+
registerFeikuaCommands(program);
|
|
1149
1284
|
registerFileCommands(program);
|
|
1150
1285
|
program.parse();
|