@optima-chat/browser-cli 0.21.0 → 0.23.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 +81 -40
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -116,6 +116,48 @@ var config = loadConfig();
116
116
  var BROWSER_API_URL = config.apiUrl;
117
117
  var AUTH = config.auth;
118
118
 
119
+ // src/session.ts
120
+ import { readFileSync as readFileSync2, writeFileSync, unlinkSync, existsSync as existsSync2, mkdirSync } from "fs";
121
+ import { join as join2 } from "path";
122
+ import { homedir as homedir2 } from "os";
123
+ var OPTIMA_DIR = join2(homedir2(), ".optima");
124
+ function conversationId() {
125
+ const raw = process.env.SESSION_ID;
126
+ return raw ? raw.replace(/[^A-Za-z0-9_-]/g, "_") : null;
127
+ }
128
+ function sessionFile() {
129
+ const conv = conversationId();
130
+ return join2(OPTIMA_DIR, conv ? `browser-session-id.${conv}` : "browser-session-id");
131
+ }
132
+ var _sessionOverride = null;
133
+ function setSessionOverride(id) {
134
+ _sessionOverride = id;
135
+ }
136
+ function saveSessionId(id) {
137
+ mkdirSync(OPTIMA_DIR, { recursive: true });
138
+ writeFileSync(sessionFile(), id, "utf-8");
139
+ }
140
+ function loadSessionId() {
141
+ if (_sessionOverride) {
142
+ return _sessionOverride;
143
+ }
144
+ const file = sessionFile();
145
+ if (!existsSync2(file)) {
146
+ throw new Error(
147
+ "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)"
148
+ );
149
+ }
150
+ return readFileSync2(file, "utf-8").trim();
151
+ }
152
+ function clearSessionId(onlyIfEquals) {
153
+ const file = sessionFile();
154
+ if (!existsSync2(file)) return;
155
+ if (onlyIfEquals !== void 0 && readFileSync2(file, "utf-8").trim() !== onlyIfEquals) {
156
+ return;
157
+ }
158
+ unlinkSync(file);
159
+ }
160
+
119
161
  // src/api.ts
120
162
  var BrowserAPIError = class extends Error {
121
163
  constructor(statusCode, errorCode, message) {
@@ -153,6 +195,10 @@ function withAuthContext(err) {
153
195
  err.message += `\uFF08\u51ED\u8BC1\u6765\u6E90 ${AUTH.source}${exp}\uFF0Cfp=${fp}\uFF09`;
154
196
  return err;
155
197
  }
198
+ function conversationHeader() {
199
+ const conv = conversationId();
200
+ return conv ? { "X-Optima-Conversation": conv } : {};
201
+ }
156
202
  async function request(path, options = {}) {
157
203
  const url = `${BROWSER_API_URL}${path}`;
158
204
  const auth = authHeader();
@@ -163,6 +209,7 @@ async function request(path, options = {}) {
163
209
  headers: {
164
210
  "Content-Type": "application/json",
165
211
  ...auth,
212
+ ...conversationHeader(),
166
213
  ...options.headers
167
214
  }
168
215
  });
@@ -195,7 +242,7 @@ async function requestBinary(path) {
195
242
  let response;
196
243
  try {
197
244
  response = await fetch(url, {
198
- headers: auth
245
+ headers: { ...auth, ...conversationHeader() }
199
246
  });
200
247
  } catch {
201
248
  throw new Error(
@@ -411,39 +458,6 @@ var BrowserAPI = class {
411
458
  };
412
459
  var api = new BrowserAPI();
413
460
 
414
- // src/session.ts
415
- import { readFileSync as readFileSync2, writeFileSync, unlinkSync, existsSync as existsSync2, mkdirSync } from "fs";
416
- import { join as join2 } from "path";
417
- import { homedir as homedir2 } from "os";
418
- var OPTIMA_DIR = join2(homedir2(), ".optima");
419
- var SESSION_FILE = join2(OPTIMA_DIR, "browser-session-id");
420
- var _sessionOverride = null;
421
- function setSessionOverride(id) {
422
- _sessionOverride = id;
423
- }
424
- function saveSessionId(id) {
425
- mkdirSync(OPTIMA_DIR, { recursive: true });
426
- writeFileSync(SESSION_FILE, id, "utf-8");
427
- }
428
- function loadSessionId() {
429
- if (_sessionOverride) {
430
- return _sessionOverride;
431
- }
432
- if (!existsSync2(SESSION_FILE)) {
433
- throw new Error(
434
- "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)"
435
- );
436
- }
437
- return readFileSync2(SESSION_FILE, "utf-8").trim();
438
- }
439
- function clearSessionId(onlyIfEquals) {
440
- if (!existsSync2(SESSION_FILE)) return;
441
- if (onlyIfEquals !== void 0 && readFileSync2(SESSION_FILE, "utf-8").trim() !== onlyIfEquals) {
442
- return;
443
- }
444
- unlinkSync(SESSION_FILE);
445
- }
446
-
447
461
  // src/formatter.ts
448
462
  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.";
449
463
  function formatPageState(state) {
@@ -789,10 +803,21 @@ var WAIT_DEFAULT_TIMEOUT_S = 90;
789
803
  var WAIT_MAX_TIMEOUT_S = 570;
790
804
  var EXIT_TASK_STOPPED = 2;
791
805
  var EXIT_STILL_RUNNING = 3;
792
- async function waitForScriptTask(taskId, timeoutMs, first) {
806
+ var EXIT_WAITING_HUMAN = 4;
807
+ function waitingHumanLine(task) {
808
+ const w = task.waiting_human;
809
+ if (!w) return null;
810
+ return `WAITING_HUMAN kind=${w.kind} since=${w.since} message=${JSON.stringify(w.message)}`;
811
+ }
812
+ function waitingHumanMessage(task) {
813
+ return `${waitingHumanLine(task)}
814
+ The script is paused waiting for the store owner to act in their browser. Tell the owner now, then call as a separate command: browser-cli script-task ${task.id} --wait --through-human`;
815
+ }
816
+ async function waitForScriptTask(taskId, timeoutMs, first, throughHuman = false) {
793
817
  const deadline = Date.now() + timeoutMs;
794
818
  let task = first ?? await api.getScriptTask(taskId);
795
819
  while (task.status === "running") {
820
+ if (task.waiting_human && !throughHuman) return { task, timedOut: false, waitingHuman: true };
796
821
  const left = deadline - Date.now();
797
822
  if (left <= 0) return { task, timedOut: true };
798
823
  await sleep(Math.min(POLL_INTERVAL_MS, left));
@@ -811,7 +836,9 @@ function stillRunningMessage(task, waitedS, nowMs = Date.now()) {
811
836
  const started = task.created_at ? Date.parse(task.created_at) : Number.NaN;
812
837
  const elapsed = Number.isFinite(started) ? `running for ${Math.max(0, Math.round((nowMs - started) / 1e3))}s` : `waited ${waitedS}s`;
813
838
  const step = task.total_steps ? `, step ${task.current_step}/${task.total_steps}` : "";
814
- return `Still running (${elapsed}${step}). Call again as a separate command: browser-cli script-task ${task.id} --wait`;
839
+ const waiting = waitingHumanLine(task);
840
+ return `Still running (${elapsed}${step}). Call again as a separate command: browser-cli script-task ${task.id} --wait` + (waiting ? ` --through-human
841
+ ${waiting}` : "");
815
842
  }
816
843
  var REQUIRED_ARG_FLAG = {
817
844
  ziniao_profile_id: "--ziniao-profile <id>",
@@ -974,6 +1001,8 @@ function formatScriptTask(task) {
974
1001
  }
975
1002
  lines.push(...haltOutputFields(task));
976
1003
  } else {
1004
+ const waiting = waitingHumanLine(task);
1005
+ if (waiting) lines.push(waiting);
977
1006
  lines.push(`Script status: ${task.status} (step ${task.current_step}/${task.total_steps})`);
978
1007
  }
979
1008
  return lines.join("\n");
@@ -1049,7 +1078,12 @@ function registerScriptCommands(program2) {
1049
1078
  console.log(`Wait with: browser-cli script-task ${task.id} --wait`);
1050
1079
  return;
1051
1080
  }
1052
- const { task: result, timedOut } = await waitForScriptTask(task.id, POLL_TIMEOUT_MS, task);
1081
+ const { task: result, timedOut, waitingHuman } = await waitForScriptTask(task.id, POLL_TIMEOUT_MS, task);
1082
+ if (waitingHuman) {
1083
+ console.log(waitingHumanMessage(result));
1084
+ process.exit(EXIT_WAITING_HUMAN);
1085
+ return;
1086
+ }
1053
1087
  if (timedOut) {
1054
1088
  console.log(`Polling timed out. Task ${task.id} still running.`);
1055
1089
  console.log(`Wait with: browser-cli script-task ${task.id} --wait`);
@@ -1084,18 +1118,25 @@ ${downloadHint(result)}`);
1084
1118
  });
1085
1119
  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>)").option("--json", "Print the raw task (status / error / full output) as JSON").option(
1086
1120
  "--wait",
1087
- `Block until the task finishes (completed / failed / needs_fallback), at most --timeout seconds. Exit code: 0 completed, 2 failed / needs_fallback, 3 still running (call again), 1 error`
1088
- ).option("--timeout <seconds>", `With --wait: max seconds to wait (default ${WAIT_DEFAULT_TIMEOUT_S}, max ${WAIT_MAX_TIMEOUT_S})`).action(async (taskId, opts) => {
1121
+ `Block until the task finishes (completed / failed / needs_fallback), at most --timeout seconds. Exit code: 0 completed, 2 failed / needs_fallback, 3 still running (call again), 4 waiting for the store owner (e.g. captcha; tell them, then --wait --through-human), 1 error`
1122
+ ).option("--through-human", "With --wait: keep waiting while the script waits for the store owner (exit 4 otherwise)").option("--timeout <seconds>", `With --wait: max seconds to wait (default ${WAIT_DEFAULT_TIMEOUT_S}, max ${WAIT_MAX_TIMEOUT_S})`).action(async (taskId, opts) => {
1089
1123
  try {
1090
1124
  if (opts.json && opts.download !== void 0) {
1091
1125
  throw new Error("--json \u4E0E --download \u53EA\u80FD\u7ED9\u4E00\u4E2A");
1092
1126
  }
1093
1127
  if (opts.timeout !== void 0 && !opts.wait) throw new Error("--timeout \u53EA\u80FD\u548C --wait \u4E00\u8D77\u7528");
1128
+ if (opts.throughHuman && !opts.wait) throw new Error("--through-human \u53EA\u80FD\u548C --wait \u4E00\u8D77\u7528");
1094
1129
  const timeoutS = opts.timeout === void 0 ? WAIT_DEFAULT_TIMEOUT_S : parseWaitTimeout(opts.timeout);
1095
1130
  let task;
1096
1131
  if (opts.wait) {
1097
- const waited = await waitForScriptTask(taskId, timeoutS * 1e3);
1132
+ const waited = await waitForScriptTask(taskId, timeoutS * 1e3, void 0, opts.throughHuman);
1098
1133
  task = waited.task;
1134
+ if (waited.waitingHuman) {
1135
+ if (opts.json) console.log(JSON.stringify(task, null, 2));
1136
+ (opts.json ? console.error : console.log)(waitingHumanMessage(task));
1137
+ process.exit(EXIT_WAITING_HUMAN);
1138
+ return;
1139
+ }
1099
1140
  if (waited.timedOut) {
1100
1141
  if (opts.json) console.log(JSON.stringify(task, null, 2));
1101
1142
  (opts.json ? console.error : console.log)(stillRunningMessage(task, timeoutS));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optima-chat/browser-cli",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {