@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.
- package/dist/index.js +250 -5
- 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://
|
|
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 =
|
|
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();
|