@optima-chat/browser-cli 0.15.0 → 0.15.2
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 +75 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -436,9 +436,13 @@ function clearSessionId(onlyIfEquals) {
|
|
|
436
436
|
}
|
|
437
437
|
|
|
438
438
|
// src/formatter.ts
|
|
439
|
+
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.";
|
|
439
440
|
function formatPageState(state) {
|
|
440
441
|
const lines = [];
|
|
441
442
|
lines.push(`Page: ${state.title} (${state.url})`);
|
|
443
|
+
if (state.url_stale) {
|
|
444
|
+
lines.push(URL_STALE_WARNING);
|
|
445
|
+
}
|
|
442
446
|
lines.push("");
|
|
443
447
|
lines.push("DOM:");
|
|
444
448
|
lines.push(state.dom);
|
|
@@ -478,6 +482,9 @@ function formatTabs(tabList) {
|
|
|
478
482
|
for (const tab of tabList.tabs) {
|
|
479
483
|
lines.push(` [${tab.tab_id}] ${tab.title} (${tab.url})`);
|
|
480
484
|
}
|
|
485
|
+
if (tabList.url_stale) {
|
|
486
|
+
lines.push(URL_STALE_WARNING);
|
|
487
|
+
}
|
|
481
488
|
return lines.join("\n");
|
|
482
489
|
}
|
|
483
490
|
function formatSearch(result) {
|
|
@@ -764,8 +771,9 @@ function registerTabCommands(program2) {
|
|
|
764
771
|
}
|
|
765
772
|
|
|
766
773
|
// src/commands/script.ts
|
|
767
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
768
|
-
import {
|
|
774
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
775
|
+
import { tmpdir } from "os";
|
|
776
|
+
import { basename, dirname, join as join3, resolve } from "path";
|
|
769
777
|
var POLL_INTERVAL_MS = 2e3;
|
|
770
778
|
var POLL_TIMEOUT_MS = 18e4;
|
|
771
779
|
var REQUIRED_ARG_FLAG = {
|
|
@@ -778,16 +786,50 @@ function formatRequiredArgs(s) {
|
|
|
778
786
|
const flags = s.required_args.map((a) => REQUIRED_ARG_FLAG[a] ?? `--param ${a}=<value>`).join(" ");
|
|
779
787
|
return ` requires: ${flags}`;
|
|
780
788
|
}
|
|
789
|
+
var ArtifactLostError = class extends Error {
|
|
790
|
+
};
|
|
791
|
+
var errMsg = (err) => err instanceof Error ? err.message : String(err);
|
|
792
|
+
function resolveArtifactTarget(out, name) {
|
|
793
|
+
if (!out) return resolve(`./${name}`);
|
|
794
|
+
const wantsDir = /[\\/]$/.test(out) || existsSync3(out) && statSync(out).isDirectory();
|
|
795
|
+
return wantsDir ? resolve(out, name) : resolve(out);
|
|
796
|
+
}
|
|
781
797
|
async function deliverArtifact(task, out) {
|
|
782
798
|
const output = task.output;
|
|
783
799
|
if (!output) return null;
|
|
784
|
-
if (output.path) return output.path;
|
|
800
|
+
if (output.path) return { path: output.path, size: output.size };
|
|
785
801
|
if (!output.artifact_path) return null;
|
|
786
|
-
const
|
|
787
|
-
const
|
|
802
|
+
const name = basename(output.filename ?? "") || "artifact";
|
|
803
|
+
const target = resolveArtifactTarget(out, name);
|
|
788
804
|
mkdirSync2(dirname(target), { recursive: true });
|
|
789
|
-
|
|
790
|
-
|
|
805
|
+
const content = await api.fetchArtifact(output.artifact_path);
|
|
806
|
+
try {
|
|
807
|
+
writeFileSync3(target, content);
|
|
808
|
+
} catch (err) {
|
|
809
|
+
const fallback = join3(tmpdir(), name);
|
|
810
|
+
try {
|
|
811
|
+
writeFileSync3(fallback, content);
|
|
812
|
+
} catch (err2) {
|
|
813
|
+
throw new ArtifactLostError(
|
|
814
|
+
`Could not write ${target} (${errMsg(err)}) nor ${fallback} (${errMsg(err2)}); the artifact was already fetched and is no longer on the server`
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
console.error(`Could not write ${target} (${errMsg(err)}); saved to ${fallback} instead`);
|
|
818
|
+
return { path: fallback, size: content.length };
|
|
819
|
+
}
|
|
820
|
+
return { path: target, size: content.length };
|
|
821
|
+
}
|
|
822
|
+
function artifactUrl(artifactPath) {
|
|
823
|
+
return `${BROWSER_API_URL}${artifactPath}`;
|
|
824
|
+
}
|
|
825
|
+
function hasStagedArtifact(task) {
|
|
826
|
+
const output = task.output;
|
|
827
|
+
return !!output?.artifact_path;
|
|
828
|
+
}
|
|
829
|
+
function downloadHint(task) {
|
|
830
|
+
const filename = task.output?.filename;
|
|
831
|
+
const dest = filename ? ` ./${basename(filename)}` : "";
|
|
832
|
+
return `Download with: browser-cli script-task ${task.id} --download${dest}`;
|
|
791
833
|
}
|
|
792
834
|
function formatScriptList(scripts) {
|
|
793
835
|
if (scripts.length === 0) return "No scripts registered.";
|
|
@@ -810,7 +852,7 @@ function formatScriptTask(task) {
|
|
|
810
852
|
lines.push("\u2713 Script completed");
|
|
811
853
|
if (task.output) {
|
|
812
854
|
for (const [k, v] of Object.entries(task.output)) {
|
|
813
|
-
lines.push(`${k}: ${v}`);
|
|
855
|
+
lines.push(k === "artifact_path" ? `${k}: ${artifactUrl(String(v))}` : `${k}: ${v}`);
|
|
814
856
|
}
|
|
815
857
|
}
|
|
816
858
|
} else if (task.status === "needs_fallback") {
|
|
@@ -918,15 +960,21 @@ function registerScriptCommands(program2) {
|
|
|
918
960
|
saveSessionId(result.session_id);
|
|
919
961
|
}
|
|
920
962
|
console.log(formatScriptTask(result));
|
|
963
|
+
if (result.status !== "completed" && hasStagedArtifact(result)) {
|
|
964
|
+
console.log(downloadHint(result));
|
|
965
|
+
}
|
|
921
966
|
if (result.status === "completed") {
|
|
922
967
|
try {
|
|
923
968
|
const saved = await deliverArtifact(result, opts.out);
|
|
924
|
-
if (saved) console.log(`Saved -> ${saved}`);
|
|
969
|
+
if (saved) console.log(`Saved -> ${saved.path}`);
|
|
925
970
|
} catch (err) {
|
|
926
971
|
const staged = result.output?.artifact_path;
|
|
927
972
|
console.error(`${formatError(err)}
|
|
928
973
|
Script completed; only saving the file failed.`);
|
|
929
|
-
if (staged
|
|
974
|
+
if (staged && !(err instanceof ArtifactLostError)) {
|
|
975
|
+
console.error(`The artifact is still at ${artifactUrl(staged)}.
|
|
976
|
+
${downloadHint(result)}`);
|
|
977
|
+
}
|
|
930
978
|
process.exit(1);
|
|
931
979
|
}
|
|
932
980
|
}
|
|
@@ -935,12 +983,26 @@ Script completed; only saving the file failed.`);
|
|
|
935
983
|
process.exit(1);
|
|
936
984
|
}
|
|
937
985
|
});
|
|
938
|
-
program2.command("script-task").argument("<task-id>", "Task ID").description("Check script task status").action(async (taskId) => {
|
|
986
|
+
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>)").action(async (taskId, opts) => {
|
|
939
987
|
try {
|
|
940
988
|
const task = await api.getScriptTask(taskId);
|
|
941
|
-
|
|
989
|
+
if (opts.download === void 0) {
|
|
990
|
+
console.log(formatScriptTask(task));
|
|
991
|
+
if (hasStagedArtifact(task)) console.log(downloadHint(task));
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (task.status === "running") {
|
|
995
|
+
throw new Error(`Task ${taskId} is ${task.status} \u2014 nothing to download yet`);
|
|
996
|
+
}
|
|
997
|
+
const saved = await deliverArtifact(task, typeof opts.download === "string" ? opts.download : void 0);
|
|
998
|
+
if (!saved) throw new Error(`Task ${taskId} produced no file`);
|
|
999
|
+
console.log(`Saved -> ${saved.path}${saved.size === void 0 ? "" : ` (${saved.size} bytes)`}`);
|
|
942
1000
|
} catch (err) {
|
|
943
|
-
|
|
1001
|
+
const gone = err.errorCode === "artifact_gone";
|
|
1002
|
+
console.error(
|
|
1003
|
+
gone ? `${formatError(err)}
|
|
1004
|
+
Each artifact can be fetched once; if runscript printed "Saved ->", the file is already local.` : formatError(err)
|
|
1005
|
+
);
|
|
944
1006
|
process.exit(1);
|
|
945
1007
|
}
|
|
946
1008
|
});
|