@optima-chat/browser-cli 0.2.0 → 0.4.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 +250 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ var ENV_CONFIG = {
16
16
  apiUrl: "https://browser-api.stage.optima.onl"
17
17
  },
18
18
  development: {
19
- apiUrl: "http://localhost:7292"
19
+ apiUrl: "http://127.0.0.1:7292"
20
20
  }
21
21
  };
22
22
  var OPTIMA_TOKEN_FILE = join(homedir(), ".optima", "token.json");
@@ -155,13 +155,23 @@ async function requestBinary(path) {
155
155
  return Buffer.from(arrayBuffer);
156
156
  }
157
157
  var BrowserAPI = class {
158
- async createSession(url) {
159
- const body = url ? { url } : {};
158
+ async createSession(url, profileId) {
159
+ const body = {};
160
+ if (url) body.url = url;
161
+ if (profileId) body.profile_id = profileId;
160
162
  return request("/api/sessions", {
161
163
  method: "POST",
162
164
  body: JSON.stringify(body)
163
165
  });
164
166
  }
167
+ async listProfiles() {
168
+ return request("/api/profiles");
169
+ }
170
+ async deleteProfile(profileId) {
171
+ return request("/api/profiles/" + encodeURIComponent(profileId), {
172
+ method: "DELETE"
173
+ });
174
+ }
165
175
  async closeSession(sessionId) {
166
176
  await request(`/api/sessions/${sessionId}`, { method: "DELETE" });
167
177
  }
@@ -249,6 +259,12 @@ var BrowserAPI = class {
249
259
  { method: "DELETE" }
250
260
  );
251
261
  }
262
+ async uploadFiles(sessionId, index, filePaths) {
263
+ return request(`/api/sessions/${sessionId}/upload`, {
264
+ method: "POST",
265
+ body: JSON.stringify({ index, file_paths: filePaths })
266
+ });
267
+ }
252
268
  // Workflow methods
253
269
  async saveWorkflow(sessionId, name, description) {
254
270
  return request("/api/workflows", {
@@ -293,6 +309,37 @@ var BrowserAPI = class {
293
309
  body: JSON.stringify({})
294
310
  });
295
311
  }
312
+ // Script methods
313
+ async submitScript(name, code) {
314
+ return request(`/api/scripts`, {
315
+ method: "POST",
316
+ body: JSON.stringify({ name, code })
317
+ });
318
+ }
319
+ async updateScript(name, code) {
320
+ return request(`/api/scripts/${encodeURIComponent(name)}`, {
321
+ method: "PUT",
322
+ body: JSON.stringify({ code })
323
+ });
324
+ }
325
+ async listScripts() {
326
+ return request("/api/scripts");
327
+ }
328
+ async getScript(name) {
329
+ return request(`/api/scripts/${encodeURIComponent(name)}`);
330
+ }
331
+ async deleteScript(name) {
332
+ await request(`/api/scripts/${encodeURIComponent(name)}`, { method: "DELETE" });
333
+ }
334
+ async runScript(scriptName, params = {}) {
335
+ return request(`/api/scripts/${encodeURIComponent(scriptName)}/run`, {
336
+ method: "POST",
337
+ body: JSON.stringify({ params })
338
+ });
339
+ }
340
+ async getScriptTask(taskId) {
341
+ return request(`/api/script-tasks/${taskId}`);
342
+ }
296
343
  };
297
344
  var api = new BrowserAPI();
298
345
 
@@ -456,10 +503,24 @@ function formatWorkflowTask(task) {
456
503
  function formatWorkflowDeleted(name) {
457
504
  return `\u2713 Workflow '${name}' deleted`;
458
505
  }
506
+ function formatProfiles(result) {
507
+ if (result.profiles.length === 0) {
508
+ return "No browser profiles found.";
509
+ }
510
+ const lines = result.profiles.map((p) => {
511
+ const sizeMb = (p.size_bytes / (1024 * 1024)).toFixed(1);
512
+ return ` ${p.profile_id} (${sizeMb} MB)`;
513
+ });
514
+ return `Browser profiles:
515
+ ${lines.join("\n")}`;
516
+ }
517
+ function formatProfileDelete(profileId) {
518
+ return `Profile '${profileId}' deleted.`;
519
+ }
459
520
 
460
521
  // src/commands/lifecycle.ts
461
522
  function registerLifecycleCommands(program2) {
462
- program2.command("launch").description("Launch a browser session").option("--url <url>", "Navigate to URL after launch").option("--force", "Close existing session before launching").action(async (opts) => {
523
+ 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").action(async (opts) => {
463
524
  try {
464
525
  if (opts.force) {
465
526
  try {
@@ -470,7 +531,7 @@ function registerLifecycleCommands(program2) {
470
531
  clearSessionId();
471
532
  }
472
533
  }
473
- const state = await api.createSession(opts.url);
534
+ const state = await api.createSession(opts.url, opts.profile);
474
535
  saveSessionId(state.session_id);
475
536
  console.log(formatLaunch(state));
476
537
  } catch (err) {
@@ -594,6 +655,16 @@ function registerInteractionCommands(program2) {
594
655
  process.exit(1);
595
656
  }
596
657
  });
658
+ program2.command("upload").description("Upload file(s) to a file input element").argument("<index>", "File input element index from DOM").argument("<files...>", "File path(s) to upload").action(async (index, files) => {
659
+ try {
660
+ const sessionId = loadSessionId();
661
+ const state = await api.uploadFiles(sessionId, parseInt(index, 10), files);
662
+ console.log(formatPageState(state));
663
+ } catch (err) {
664
+ console.error(formatError(err));
665
+ process.exit(1);
666
+ }
667
+ });
597
668
  }
598
669
 
599
670
  // src/commands/info.ts
@@ -961,6 +1032,178 @@ function cleanConsecutiveClick(steps) {
961
1032
  return result;
962
1033
  }
963
1034
 
1035
+ // src/commands/script.ts
1036
+ import { readFileSync as readFileSync3 } from "fs";
1037
+ var POLL_INTERVAL_MS = 2e3;
1038
+ var POLL_TIMEOUT_MS = 18e4;
1039
+ function formatScriptList(scripts) {
1040
+ if (scripts.length === 0) return "No scripts registered.";
1041
+ const lines = [];
1042
+ for (const s of scripts) {
1043
+ const tag = s.source ? ` [${s.source}]` : "";
1044
+ lines.push(`${s.name} \u2014 ${s.description}${tag}`);
1045
+ for (let i = 0; i < s.steps.length; i++) {
1046
+ lines.push(` [${i + 1}] ${s.steps[i].id} \u2014 ${s.steps[i].description}`);
1047
+ }
1048
+ lines.push("");
1049
+ }
1050
+ return lines.join("\n").trimEnd();
1051
+ }
1052
+ function formatScriptTask(task) {
1053
+ const lines = [];
1054
+ if (task.status === "completed") {
1055
+ lines.push("\u2713 Script completed");
1056
+ if (task.output) {
1057
+ for (const [k, v] of Object.entries(task.output)) {
1058
+ lines.push(`${k}: ${v}`);
1059
+ }
1060
+ }
1061
+ } else if (task.status === "needs_fallback") {
1062
+ lines.push("\u26A0 Script needs fallback");
1063
+ lines.push("");
1064
+ const output = task.output ?? {};
1065
+ const completed = output.completed_steps ?? [];
1066
+ const remaining = output.remaining_steps ?? [];
1067
+ const error = output.error;
1068
+ if (completed.length > 0) {
1069
+ lines.push("\u2713 Completed:");
1070
+ for (let i = 0; i < completed.length; i++) {
1071
+ lines.push(` [${i + 1}] ${completed[i].id} \u2014 ${completed[i].description}`);
1072
+ }
1073
+ lines.push("");
1074
+ }
1075
+ if (error) {
1076
+ const failedStep = completed.length + 1;
1077
+ const failedId = remaining.length > 0 ? remaining[0].id : "unknown";
1078
+ lines.push(`\u2717 Failed at step [${failedStep}] ${failedId}: ${error}`);
1079
+ lines.push("");
1080
+ }
1081
+ if (remaining.length > 0) {
1082
+ lines.push("\u2192 Remaining steps:");
1083
+ for (let i = 0; i < remaining.length; i++) {
1084
+ const stepNum = completed.length + i + 1;
1085
+ lines.push(` [${stepNum}] ${remaining[i].id} \u2014 ${remaining[i].description}`);
1086
+ }
1087
+ lines.push("");
1088
+ }
1089
+ lines.push("Session is still open \u2014 use browser-cli dom/click/input to continue");
1090
+ } else if (task.status === "failed") {
1091
+ lines.push(`\u2717 Script failed: ${task.error ?? "unknown error"}`);
1092
+ } else {
1093
+ lines.push(`Script status: ${task.status} (step ${task.current_step}/${task.total_steps})`);
1094
+ }
1095
+ return lines.join("\n");
1096
+ }
1097
+ function registerScriptCommands(program2) {
1098
+ program2.command("submit-script").description("Submit a script (write to EFS)").requiredOption("--name <name>", "Script name").requiredOption("--code-file <path>", "Path to Python script file").action(async (opts) => {
1099
+ try {
1100
+ const code = readFileSync3(opts.codeFile, "utf-8");
1101
+ const result = await api.submitScript(opts.name, code);
1102
+ console.log(`\u2713 Script ${result.name} ${result.created ? "created" : "updated"}`);
1103
+ } catch (err) {
1104
+ console.error(formatError(err));
1105
+ process.exit(1);
1106
+ }
1107
+ });
1108
+ program2.command("script-code").argument("<name>", "Script name").description("Show script source code").action(async (name) => {
1109
+ try {
1110
+ const detail = await api.getScript(name);
1111
+ console.log(detail.code);
1112
+ } catch (err) {
1113
+ console.error(formatError(err));
1114
+ process.exit(1);
1115
+ }
1116
+ });
1117
+ program2.command("delete-script").argument("<name>", "Script name").description("Delete a script").action(async (name) => {
1118
+ try {
1119
+ await api.deleteScript(name);
1120
+ console.log(`\u2713 Script ${name} deleted`);
1121
+ } catch (err) {
1122
+ console.error(formatError(err));
1123
+ process.exit(1);
1124
+ }
1125
+ });
1126
+ program2.command("scripts").description("List scripts").action(async () => {
1127
+ try {
1128
+ const resp = await api.listScripts();
1129
+ console.log(formatScriptList(resp.scripts));
1130
+ } catch (err) {
1131
+ console.error(formatError(err));
1132
+ process.exit(1);
1133
+ }
1134
+ });
1135
+ program2.command("runscript").argument("<name>", "Script name").description("Run a script (polls until complete)").option("--param <params...>", "Parameters as key=value").option("--no-poll", "Return immediately without polling").action(async (name, opts) => {
1136
+ try {
1137
+ const params = {};
1138
+ if (opts.param) {
1139
+ for (const p of opts.param) {
1140
+ const [k, ...rest] = p.split("=");
1141
+ params[k] = rest.join("=");
1142
+ }
1143
+ }
1144
+ clearSessionId();
1145
+ const task = await api.runScript(name, params);
1146
+ console.log(`Script started (task: ${task.id})`);
1147
+ if (!opts.poll) {
1148
+ console.log(`Poll with: browser-cli script-task ${task.id}`);
1149
+ return;
1150
+ }
1151
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
1152
+ let result = task;
1153
+ while (result.status === "running" && Date.now() < deadline) {
1154
+ await sleep(POLL_INTERVAL_MS);
1155
+ result = await api.getScriptTask(task.id);
1156
+ }
1157
+ if (result.status === "running") {
1158
+ console.log(`Polling timed out. Task ${task.id} still running.`);
1159
+ console.log(`Poll with: browser-cli script-task ${task.id}`);
1160
+ return;
1161
+ }
1162
+ if (result.status === "needs_fallback" && result.session_id) {
1163
+ saveSessionId(result.session_id);
1164
+ }
1165
+ console.log(formatScriptTask(result));
1166
+ } catch (err) {
1167
+ console.error(formatError(err));
1168
+ process.exit(1);
1169
+ }
1170
+ });
1171
+ program2.command("script-task").argument("<task-id>", "Task ID").description("Check script task status").action(async (taskId) => {
1172
+ try {
1173
+ const task = await api.getScriptTask(taskId);
1174
+ console.log(formatScriptTask(task));
1175
+ } catch (err) {
1176
+ console.error(formatError(err));
1177
+ process.exit(1);
1178
+ }
1179
+ });
1180
+ }
1181
+ function sleep(ms) {
1182
+ return new Promise((resolve) => setTimeout(resolve, ms));
1183
+ }
1184
+
1185
+ // src/commands/profile.ts
1186
+ function registerProfileCommands(program2) {
1187
+ program2.command("profiles").description("List browser profiles").action(async () => {
1188
+ try {
1189
+ const result = await api.listProfiles();
1190
+ console.log(formatProfiles(result));
1191
+ } catch (err) {
1192
+ console.error(formatError(err));
1193
+ process.exit(1);
1194
+ }
1195
+ });
1196
+ program2.command("delete-profile").description("Delete a named browser profile").argument("<profile-id>", "Profile ID to delete").action(async (profileId) => {
1197
+ try {
1198
+ const result = await api.deleteProfile(profileId);
1199
+ console.log(formatProfileDelete(result.profile_id));
1200
+ } catch (err) {
1201
+ console.error(formatError(err));
1202
+ process.exit(1);
1203
+ }
1204
+ });
1205
+ }
1206
+
964
1207
  // src/index.ts
965
1208
  var require2 = createRequire(import.meta.url);
966
1209
  var { version } = require2("../package.json");
@@ -978,4 +1221,6 @@ registerInteractionCommands(program);
978
1221
  registerInfoCommands(program);
979
1222
  registerTabCommands(program);
980
1223
  registerWorkflowCommands(program);
1224
+ registerScriptCommands(program);
1225
+ registerProfileCommands(program);
981
1226
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optima-chat/browser-cli",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {