@optima-chat/browser-cli 0.13.0 → 0.14.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 +46 -18
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -219,8 +219,20 @@ var BrowserAPI = class {
|
|
|
219
219
|
async getFeikuaProfiles() {
|
|
220
220
|
return request("/api/user/feikua-profiles");
|
|
221
221
|
}
|
|
222
|
+
/** true = 后端确实关了它;false = 它本来就没了(idle reaper 已经收走)。
|
|
223
|
+
*
|
|
224
|
+
* 「已经没了」不是错误。抛出去的话调用方连本地记录都不敢清,下一次 close 再撞
|
|
225
|
+
* 同一堵墙 —— 而 close 是 SKILL.md 写给 agent 的标准恢复动作,必须幂等(#225)。 */
|
|
222
226
|
async closeSession(sessionId) {
|
|
223
|
-
|
|
227
|
+
try {
|
|
228
|
+
await request(`/api/sessions/${sessionId}`, { method: "DELETE" });
|
|
229
|
+
return true;
|
|
230
|
+
} catch (err) {
|
|
231
|
+
if (err instanceof BrowserAPIError && err.errorCode === "session_not_found") {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
throw err;
|
|
235
|
+
}
|
|
224
236
|
}
|
|
225
237
|
async getStatus(sessionId) {
|
|
226
238
|
return request(`/api/sessions/${sessionId}/status`);
|
|
@@ -404,15 +416,17 @@ function loadSessionId() {
|
|
|
404
416
|
}
|
|
405
417
|
if (!existsSync2(SESSION_FILE)) {
|
|
406
418
|
throw new Error(
|
|
407
|
-
"No active browser session. Run 'browser-cli launch' first."
|
|
419
|
+
"No active browser session. Run 'browser-cli launch' first. (If the backend says you already have one, close it by id: browser-cli --session <id> close)"
|
|
408
420
|
);
|
|
409
421
|
}
|
|
410
422
|
return readFileSync2(SESSION_FILE, "utf-8").trim();
|
|
411
423
|
}
|
|
412
|
-
function clearSessionId() {
|
|
413
|
-
if (existsSync2(SESSION_FILE))
|
|
414
|
-
|
|
424
|
+
function clearSessionId(onlyIfEquals) {
|
|
425
|
+
if (!existsSync2(SESSION_FILE)) return;
|
|
426
|
+
if (onlyIfEquals !== void 0 && readFileSync2(SESSION_FILE, "utf-8").trim() !== onlyIfEquals) {
|
|
427
|
+
return;
|
|
415
428
|
}
|
|
429
|
+
unlinkSync(SESSION_FILE);
|
|
416
430
|
}
|
|
417
431
|
|
|
418
432
|
// src/formatter.ts
|
|
@@ -435,12 +449,19 @@ function formatLaunch(state) {
|
|
|
435
449
|
function formatClose(sessionId) {
|
|
436
450
|
return `\u2713 Browser closed (session: ${sessionId})`;
|
|
437
451
|
}
|
|
452
|
+
function formatAlreadyClosed(sessionId) {
|
|
453
|
+
return `\u2713 Session ${sessionId} was already closed (local record cleared)`;
|
|
454
|
+
}
|
|
438
455
|
function formatStatus(status) {
|
|
439
456
|
const lines = [];
|
|
440
457
|
lines.push(`Session: ${status.session_id}`);
|
|
441
458
|
lines.push(`Status: ${status.status}`);
|
|
442
459
|
lines.push(`Created: ${status.created_at}`);
|
|
443
460
|
lines.push(`Idle: ${status.idle_seconds}s`);
|
|
461
|
+
if (status.execution) {
|
|
462
|
+
const where = status.execution === "sidecar" ? "user's machine" : "server-driven";
|
|
463
|
+
lines.push(`Execution: ${status.execution} (${where})`);
|
|
464
|
+
}
|
|
444
465
|
return lines.join("\n");
|
|
445
466
|
}
|
|
446
467
|
function formatScreenshot(filePath) {
|
|
@@ -507,9 +528,9 @@ function registerLifecycleCommands(program2) {
|
|
|
507
528
|
program2.command("close").description("Close the active browser session").action(async () => {
|
|
508
529
|
try {
|
|
509
530
|
const sessionId = loadSessionId();
|
|
510
|
-
await api.closeSession(sessionId);
|
|
511
|
-
clearSessionId();
|
|
512
|
-
console.log(formatClose(sessionId));
|
|
531
|
+
const closed = await api.closeSession(sessionId);
|
|
532
|
+
clearSessionId(sessionId);
|
|
533
|
+
console.log(closed ? formatClose(sessionId) : formatAlreadyClosed(sessionId));
|
|
513
534
|
} catch (err) {
|
|
514
535
|
console.error(formatError(err));
|
|
515
536
|
process.exit(1);
|
|
@@ -870,7 +891,6 @@ function registerScriptCommands(program2) {
|
|
|
870
891
|
}
|
|
871
892
|
}
|
|
872
893
|
if (opts.out) params.out_path = resolve(opts.out);
|
|
873
|
-
clearSessionId();
|
|
874
894
|
const task = await api.runScript(name, params, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile, opts.feikuaProfile);
|
|
875
895
|
console.log(`Script started (task: ${task.id})`);
|
|
876
896
|
if (!opts.poll) {
|
|
@@ -1100,6 +1120,7 @@ function registerZiNiaoCommands(program2) {
|
|
|
1100
1120
|
import { writeFileSync as writeFileSync4 } from "fs";
|
|
1101
1121
|
import { resolve as resolve2 } from "path";
|
|
1102
1122
|
var PUBLISH_TIMEOUT_MS = 18e5;
|
|
1123
|
+
var AD_REPORT_TIMEOUT_MS = 132e4;
|
|
1103
1124
|
var PUBLISH_TIMEOUT_HINT = "\u4EFB\u52A1\u4ECD\u5728\u540E\u7AEF\u8FD0\u884C,\u89C6\u9891\u53EF\u80FD\u5DF2\u7ECF\u53D1\u51FA\u53BB\u4E86\u3002\u53D1\u5E03\u72B6\u6001\u672A\u77E5 \u2014\u2014 \u8BF7\u5230\u5356\u5BB6\u540E\u53F0\u786E\u8BA4,\u4E0D\u8981\u91CD\u8DD1\u672C\u547D\u4EE4(\u4F1A\u53D1\u51FA\u91CD\u590D\u89C6\u9891)\u3002";
|
|
1104
1125
|
async function runExport(script, params, out, timeoutMs) {
|
|
1105
1126
|
const outPath = resolve2(out);
|
|
@@ -1115,16 +1136,23 @@ async function runExport(script, params, out, timeoutMs) {
|
|
|
1115
1136
|
console.log(`Saved ${result.filename} -> ${outPath} (${result.size} bytes)`);
|
|
1116
1137
|
}
|
|
1117
1138
|
function registerRecipeCommands(program2) {
|
|
1118
|
-
program2.command("export-ad-report").description("Export TikTok Shop
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1139
|
+
program2.command("export-ad-report").description("Export TikTok Shop ad data (per-creative by default) into the session workspace").requiredOption("--ziniao-profile <id>", "ZiNiao browser profile ID").option("--report-url <url>", "Ad dashboard URL (default: TikTok Shop MY local site)").option("--days <n>", "Date range: 7 / 30 / 90 / 180 / 365 (default: 7)").option(
|
|
1140
|
+
"--scope <scope>",
|
|
1141
|
+
"creative = per-creative rows (default) | daily = account-level daily totals"
|
|
1142
|
+
).option("--out <path>", "Output CSV path", "./tt_ad_report.csv").action(
|
|
1143
|
+
async (opts) => {
|
|
1144
|
+
try {
|
|
1145
|
+
const params = { ziniao_profile_id: opts.ziniaoProfile };
|
|
1146
|
+
if (opts.reportUrl) params.report_url = opts.reportUrl;
|
|
1147
|
+
if (opts.days) params.days = opts.days;
|
|
1148
|
+
if (opts.scope) params.scope = opts.scope;
|
|
1149
|
+
await runExport("tiktok_video_ad_export", params, opts.out, AD_REPORT_TIMEOUT_MS);
|
|
1150
|
+
} catch (err) {
|
|
1151
|
+
console.error(formatError(err));
|
|
1152
|
+
process.exit(1);
|
|
1153
|
+
}
|
|
1126
1154
|
}
|
|
1127
|
-
|
|
1155
|
+
);
|
|
1128
1156
|
program2.command("export-order-report").description("Export TikTok Shop order data into the session workspace").requiredOption("--ziniao-profile <id>", "ZiNiao browser profile ID").option("--report-url <url>", "Order list URL (default: TikTok Shop MY local site)").option("--days <n>", "Export the last N days (default: 7, max 365)").option("--out <path>", "Output CSV path", "./tt_orders.csv").action(async (opts) => {
|
|
1129
1157
|
try {
|
|
1130
1158
|
const params = { ziniao_profile_id: opts.ziniaoProfile };
|