@optima-chat/browser-cli 0.7.0 → 0.9.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.
Files changed (2) hide show
  1. package/dist/index.js +84 -68
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -174,10 +174,9 @@ async function requestBinary(path) {
174
174
  return Buffer.from(arrayBuffer);
175
175
  }
176
176
  var BrowserAPI = class {
177
- async createSession(url, profileId, force, connector, ziniaoProfileId, bitbrowserProfileId) {
177
+ async createSession(url, force, connector, ziniaoProfileId, bitbrowserProfileId) {
178
178
  const body = {};
179
179
  if (url) body.url = url;
180
- if (profileId) body.profile_id = profileId;
181
180
  if (force) body.force = true;
182
181
  if (connector) body.connector = connector;
183
182
  if (ziniaoProfileId) body.ziniao_profile_id = ziniaoProfileId;
@@ -187,14 +186,6 @@ var BrowserAPI = class {
187
186
  body: JSON.stringify(body)
188
187
  });
189
188
  }
190
- async listProfiles() {
191
- return request("/api/profiles");
192
- }
193
- async deleteProfile(profileId) {
194
- return request("/api/profiles/" + encodeURIComponent(profileId), {
195
- method: "DELETE"
196
- });
197
- }
198
189
  async setZiNiaoConfig(company, username, password) {
199
190
  return request("/api/user/ziniao-config", {
200
191
  method: "PUT",
@@ -258,22 +249,29 @@ var BrowserAPI = class {
258
249
  async getScreenshot(sessionId) {
259
250
  return requestBinary(`/api/sessions/${sessionId}/screenshot`);
260
251
  }
261
- async runRecipeScript(name, params, pollMs = 2e3) {
252
+ async runRecipeScript(name, params, pollMs = 2e3, timeoutMs = 36e4) {
262
253
  const started = await request(`/api/scripts/${name}/run`, {
263
254
  method: "POST",
264
255
  body: JSON.stringify({ params })
265
256
  });
257
+ const deadline = Date.now() + timeoutMs;
266
258
  for (; ; ) {
267
- const task = await request(
268
- `/api/script-tasks/${started.id}`
269
- );
259
+ const task = await request(`/api/script-tasks/${started.id}`);
270
260
  if (task.status === "completed") return task.output;
271
261
  if (task.status === "failed" || task.status === "needs_fallback") {
272
- throw new Error(task.error || `recipe ${task.status}`);
262
+ throw new Error(task.error || task.output?.error || `recipe ${task.status}`);
263
+ }
264
+ if (Date.now() >= deadline) {
265
+ throw new Error(
266
+ `recipe ${name} did not finish within ${Math.round(timeoutMs / 1e3)}s (task ${started.id})`
267
+ );
273
268
  }
274
269
  await new Promise((r) => setTimeout(r, pollMs));
275
270
  }
276
271
  }
272
+ async fetchArtifact(artifactPath) {
273
+ return requestBinary(artifactPath);
274
+ }
277
275
  async search(sessionId, pattern) {
278
276
  return request(`/api/sessions/${sessionId}/search`, {
279
277
  method: "POST",
@@ -328,10 +326,10 @@ var BrowserAPI = class {
328
326
  async deleteScript(name) {
329
327
  await request(`/api/scripts/${encodeURIComponent(name)}`, { method: "DELETE" });
330
328
  }
331
- async runScript(scriptName, params = {}, profileId, connector, bitbrowserProfileId) {
329
+ async runScript(scriptName, params = {}, connector, ziniaoProfileId, bitbrowserProfileId) {
332
330
  const payload = { params };
333
- if (profileId) payload.profile_id = profileId;
334
331
  if (connector) payload.connector = connector;
332
+ if (ziniaoProfileId) payload.ziniao_profile_id = ziniaoProfileId;
335
333
  if (bitbrowserProfileId) payload.bitbrowser_profile_id = bitbrowserProfileId;
336
334
  return request(`/api/scripts/${encodeURIComponent(scriptName)}/run`, {
337
335
  method: "POST",
@@ -446,26 +444,12 @@ function formatError(err) {
446
444
  function formatTabsInline(tabs) {
447
445
  return tabs.map((t) => `[${t.tab_id}] ${t.title}`).join(", ");
448
446
  }
449
- function formatProfiles(result) {
450
- if (result.profiles.length === 0) {
451
- return "No browser profiles found.";
452
- }
453
- const lines = result.profiles.map((p) => {
454
- const sizeMb = (p.size_bytes / (1024 * 1024)).toFixed(1);
455
- return ` ${p.profile_id} (${sizeMb} MB)`;
456
- });
457
- return `Browser profiles:
458
- ${lines.join("\n")}`;
459
- }
460
- function formatProfileDelete(profileId) {
461
- return `Profile '${profileId}' deleted.`;
462
- }
463
447
 
464
448
  // src/commands/lifecycle.ts
465
449
  function registerLifecycleCommands(program2) {
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) => {
450
+ 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 bitbrowser").option("--ziniao-profile <id>", "ZiNiao browser profile ID").option("--bitbrowser-profile <id>", "BitBrowser profile ID").action(async (opts) => {
467
451
  try {
468
- const state = await api.createSession(opts.url, opts.profile, opts.force, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile);
452
+ const state = await api.createSession(opts.url, opts.force, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile);
469
453
  saveSessionId(state.session_id);
470
454
  console.log(formatLaunch(state));
471
455
  } catch (err) {
@@ -682,15 +666,38 @@ function registerTabCommands(program2) {
682
666
  }
683
667
 
684
668
  // src/commands/script.ts
685
- import { readFileSync as readFileSync3 } from "fs";
669
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
670
+ import { basename, dirname, resolve } from "path";
686
671
  var POLL_INTERVAL_MS = 2e3;
687
672
  var POLL_TIMEOUT_MS = 18e4;
673
+ var REQUIRED_ARG_FLAG = {
674
+ ziniao_profile_id: "--ziniao-profile <id>",
675
+ bitbrowser_profile_id: "--bitbrowser-profile <id>"
676
+ };
677
+ function formatRequiredArgs(s) {
678
+ if (!s.required_args?.length) return null;
679
+ const flags = s.required_args.map((a) => REQUIRED_ARG_FLAG[a] ?? `--param ${a}=<value>`).join(" ");
680
+ return ` requires: ${flags}`;
681
+ }
682
+ async function deliverArtifact(task, out) {
683
+ const output = task.output;
684
+ if (!output) return null;
685
+ if (output.path) return output.path;
686
+ if (!output.artifact_path) return null;
687
+ const target = resolve(out ?? `./${basename(output.filename ?? "artifact")}`);
688
+ const content = await api.fetchArtifact(output.artifact_path);
689
+ mkdirSync2(dirname(target), { recursive: true });
690
+ writeFileSync3(target, content);
691
+ return target;
692
+ }
688
693
  function formatScriptList(scripts) {
689
694
  if (scripts.length === 0) return "No scripts registered.";
690
695
  const lines = [];
691
696
  for (const s of scripts) {
692
697
  const tag = s.source ? ` [${s.source}]` : "";
693
698
  lines.push(`${s.name} \u2014 ${s.description}${tag}`);
699
+ const requires = formatRequiredArgs(s);
700
+ if (requires) lines.push(requires);
694
701
  for (let i = 0; i < s.steps.length; i++) {
695
702
  lines.push(` [${i + 1}] ${s.steps[i].id} \u2014 ${s.steps[i].description}`);
696
703
  }
@@ -781,7 +788,7 @@ function registerScriptCommands(program2) {
781
788
  process.exit(1);
782
789
  }
783
790
  });
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) => {
791
+ 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 bitbrowser").option("--ziniao-profile <id>", "ZiNiao browser profile ID (with --connector ziniao)").option("--bitbrowser-profile <id>", "BitBrowser profile ID (with --connector bitbrowser)").option("--out <path>", "Where to write the script's exported file (default: ./<filename>)").option("--no-poll", "Return immediately without polling").action(async (name, opts) => {
785
792
  try {
786
793
  const params = {};
787
794
  if (opts.param) {
@@ -790,8 +797,9 @@ function registerScriptCommands(program2) {
790
797
  params[k] = rest.join("=");
791
798
  }
792
799
  }
800
+ if (opts.out) params.out_path = resolve(opts.out);
793
801
  clearSessionId();
794
- const task = await api.runScript(name, params, opts.profile, opts.connector, opts.bitbrowserProfile);
802
+ const task = await api.runScript(name, params, opts.connector, opts.ziniaoProfile, opts.bitbrowserProfile);
795
803
  console.log(`Script started (task: ${task.id})`);
796
804
  if (!opts.poll) {
797
805
  console.log(`Poll with: browser-cli script-task ${task.id}`);
@@ -812,6 +820,18 @@ function registerScriptCommands(program2) {
812
820
  saveSessionId(result.session_id);
813
821
  }
814
822
  console.log(formatScriptTask(result));
823
+ if (result.status === "completed") {
824
+ try {
825
+ const saved = await deliverArtifact(result, opts.out);
826
+ if (saved) console.log(`Saved -> ${saved}`);
827
+ } catch (err) {
828
+ const staged = result.output?.artifact_path;
829
+ console.error(`${formatError(err)}
830
+ Script completed; only saving the file failed.`);
831
+ if (staged) console.error(`The artifact is still at ${staged}.`);
832
+ process.exit(1);
833
+ }
834
+ }
815
835
  } catch (err) {
816
836
  console.error(formatError(err));
817
837
  process.exit(1);
@@ -828,29 +848,7 @@ function registerScriptCommands(program2) {
828
848
  });
829
849
  }
830
850
  function sleep(ms) {
831
- return new Promise((resolve) => setTimeout(resolve, ms));
832
- }
833
-
834
- // src/commands/profile.ts
835
- function registerProfileCommands(program2) {
836
- program2.command("profiles").description("List browser profiles").action(async () => {
837
- try {
838
- const result = await api.listProfiles();
839
- console.log(formatProfiles(result));
840
- } catch (err) {
841
- console.error(formatError(err));
842
- process.exit(1);
843
- }
844
- });
845
- program2.command("delete-profile").description("Delete a named browser profile").argument("<profile-id>", "Profile ID to delete").action(async (profileId) => {
846
- try {
847
- const result = await api.deleteProfile(profileId);
848
- console.log(formatProfileDelete(result.profile_id));
849
- } catch (err) {
850
- console.error(formatError(err));
851
- process.exit(1);
852
- }
853
- });
851
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
854
852
  }
855
853
 
856
854
  // src/ziniao-doctor.ts
@@ -999,7 +997,8 @@ function registerZiNiaoCommands(program2) {
999
997
  console.log(`ZiNiao profiles (${result.profiles.length}):
1000
998
  `);
1001
999
  for (const p of result.profiles) {
1002
- console.log(` ${p.id} ${p.name}`);
1000
+ const site = p.platform_name ? ` [${p.platform_name}]` : "";
1001
+ console.log(` ${p.id} ${p.name}${site}`);
1003
1002
  }
1004
1003
  console.log("\nUse: browser-cli launch --connector ziniao --ziniao-profile <id> --url <url>");
1005
1004
  } catch (err) {
@@ -1026,15 +1025,33 @@ function registerZiNiaoCommands(program2) {
1026
1025
  }
1027
1026
 
1028
1027
  // src/commands/recipes.ts
1029
- import { writeFileSync as writeFileSync3 } from "fs";
1028
+ import { writeFileSync as writeFileSync4 } from "fs";
1029
+ import { resolve as resolve2 } from "path";
1030
+ async function runExport(script, params, out, timeoutMs) {
1031
+ const outPath = resolve2(out);
1032
+ const result = await api.runRecipeScript(script, { ...params, out_path: outPath }, 2e3, timeoutMs);
1033
+ if (!result.path) {
1034
+ writeFileSync4(outPath, await api.fetchArtifact(result.artifact_path));
1035
+ }
1036
+ console.log(`Saved ${result.filename} -> ${outPath} (${result.size} bytes)`);
1037
+ }
1030
1038
  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) => {
1039
+ 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("--report-url <url>", "Ad dashboard URL (default: TikTok Shop MY local site)").option("--out <path>", "Output CSV path", "./tt_video_ad_7d.csv").action(async (opts) => {
1032
1040
  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)`);
1041
+ const params = { ziniao_profile_id: opts.ziniaoProfile };
1042
+ if (opts.reportUrl) params.report_url = opts.reportUrl;
1043
+ await runExport("tiktok_video_ad_export", params, opts.out, 36e4);
1044
+ } catch (err) {
1045
+ console.error(formatError(err));
1046
+ process.exit(1);
1047
+ }
1048
+ });
1049
+ 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) => {
1050
+ try {
1051
+ const params = { ziniao_profile_id: opts.ziniaoProfile };
1052
+ if (opts.reportUrl) params.report_url = opts.reportUrl;
1053
+ if (opts.days) params.days = opts.days;
1054
+ await runExport("tiktok_order_export", params, opts.out, 54e4);
1038
1055
  } catch (err) {
1039
1056
  console.error(formatError(err));
1040
1057
  process.exit(1);
@@ -1086,7 +1103,6 @@ registerInteractionCommands(program);
1086
1103
  registerInfoCommands(program);
1087
1104
  registerTabCommands(program);
1088
1105
  registerScriptCommands(program);
1089
- registerProfileCommands(program);
1090
1106
  registerZiNiaoCommands(program);
1091
1107
  registerRecipeCommands(program);
1092
1108
  registerBitBrowserCommands(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optima-chat/browser-cli",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {