@xbrowser/cli 1.5.1 → 1.5.3
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/{chunk-JPSFUFPG.js → chunk-L35D5DAY.js} +5 -1
- package/dist/cli.js +75 -22
- package/dist/{daemon-client-XXKMJZZ7.js → daemon-client-S3EUTRC6.js} +1 -1
- package/dist/{daemon-client-COJQESU2.js → daemon-client-WT7PTGYQ.js} +5 -1
- package/dist/daemon-main.js +24 -10
- package/dist/index.js +75 -22
- package/package.json +1 -1
|
@@ -140,6 +140,7 @@ async function ensureDaemonRunning() {
|
|
|
140
140
|
if (healthOk) return;
|
|
141
141
|
if (attempt < 2) await new Promise((r) => setTimeout(r, 500));
|
|
142
142
|
}
|
|
143
|
+
console.error("\u{1F504} Starting daemon...");
|
|
143
144
|
_ensurePromise = startDaemonProcess(DAEMON_PORT).then(() => {
|
|
144
145
|
});
|
|
145
146
|
_ensurePromise.catch(() => {
|
|
@@ -153,7 +154,10 @@ async function ensureDaemonRunning() {
|
|
|
153
154
|
}
|
|
154
155
|
for (let attempt = 0; attempt < 20; attempt++) {
|
|
155
156
|
const ready = await fetch(`${DAEMON_BASE}/health`, { signal: AbortSignal.timeout(1e3) }).then((r) => r.ok ? r.json() : null).then((d) => d?.status === "ok").catch(() => false);
|
|
156
|
-
if (ready)
|
|
157
|
+
if (ready) {
|
|
158
|
+
console.error("\u2705 Daemon ready");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
157
161
|
await new Promise((r) => setTimeout(r, 200));
|
|
158
162
|
}
|
|
159
163
|
throw new Error("Daemon HTTP server not ready after 4s");
|
package/dist/cli.js
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
killAllDaemonProcesses,
|
|
55
55
|
startDaemonProcess,
|
|
56
56
|
stopDaemonProcess
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-L35D5DAY.js";
|
|
58
58
|
import {
|
|
59
59
|
errMsg
|
|
60
60
|
} from "./chunk-GDKLH7ZY.js";
|
|
@@ -1126,7 +1126,7 @@ var screenshotCommand = registerCommand({
|
|
|
1126
1126
|
buffer = await ctx.page.screenshot(options);
|
|
1127
1127
|
}
|
|
1128
1128
|
if (p.output) {
|
|
1129
|
-
writeFileSync(p.output, buffer);
|
|
1129
|
+
writeFileSync(p.output, buffer, "binary");
|
|
1130
1130
|
return ok9({
|
|
1131
1131
|
output: p.output,
|
|
1132
1132
|
format,
|
|
@@ -1142,7 +1142,7 @@ var screenshotCommand = registerCommand({
|
|
|
1142
1142
|
}
|
|
1143
1143
|
ensureScreenshotsDir();
|
|
1144
1144
|
const screenshotPath = generateScreenshotPath(format);
|
|
1145
|
-
writeFileSync(screenshotPath, buffer);
|
|
1145
|
+
writeFileSync(screenshotPath, buffer, "binary");
|
|
1146
1146
|
return ok9({
|
|
1147
1147
|
output: screenshotPath,
|
|
1148
1148
|
format,
|
|
@@ -5401,19 +5401,19 @@ function parseCommandChain(input, options) {
|
|
|
5401
5401
|
continue;
|
|
5402
5402
|
}
|
|
5403
5403
|
if (char === "-" && input[i + 1] === ">" && isSpaceAround(input, i, 2)) {
|
|
5404
|
-
|
|
5405
|
-
|
|
5404
|
+
lastOperator = "sequence";
|
|
5405
|
+
flushPipeline();
|
|
5406
5406
|
i++;
|
|
5407
5407
|
continue;
|
|
5408
5408
|
}
|
|
5409
5409
|
if (char === "," && isSpaceAdjacent(input, i)) {
|
|
5410
|
-
|
|
5411
|
-
|
|
5410
|
+
lastOperator = "sequence";
|
|
5411
|
+
flushPipeline();
|
|
5412
5412
|
continue;
|
|
5413
5413
|
}
|
|
5414
5414
|
if (char === "+" && isSpaceAdjacent(input, i)) {
|
|
5415
|
-
|
|
5416
|
-
|
|
5415
|
+
lastOperator = "sequence";
|
|
5416
|
+
flushPipeline();
|
|
5417
5417
|
continue;
|
|
5418
5418
|
}
|
|
5419
5419
|
if (options?.fileMode && char === "|" && input[i + 1] !== "|") {
|
|
@@ -7015,6 +7015,18 @@ async function loadHooks() {
|
|
|
7015
7015
|
// src/executor.ts
|
|
7016
7016
|
import { homedir as homedir6 } from "os";
|
|
7017
7017
|
import { join as join6 } from "path";
|
|
7018
|
+
function levenshtein(a, b) {
|
|
7019
|
+
const m = a.length, n = b.length;
|
|
7020
|
+
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
7021
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
7022
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
7023
|
+
for (let i = 1; i <= m; i++) {
|
|
7024
|
+
for (let j = 1; j <= n; j++) {
|
|
7025
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
7026
|
+
}
|
|
7027
|
+
}
|
|
7028
|
+
return dp[m][n];
|
|
7029
|
+
}
|
|
7018
7030
|
var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
|
|
7019
7031
|
var snapshotHintShown = /* @__PURE__ */ new WeakSet();
|
|
7020
7032
|
var CONFIG_DIR2 = join6(homedir6(), ".xbrowser");
|
|
@@ -7075,8 +7087,10 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7075
7087
|
const command = getCommand(commandName);
|
|
7076
7088
|
if (!command) {
|
|
7077
7089
|
const available = getAllCommands().map((c) => c.name);
|
|
7090
|
+
const suggestions = available.map((name) => ({ name, dist: levenshtein(commandName, name) })).filter((s) => s.dist <= 3).sort((a, b) => a.dist - b.dist).slice(0, 3).map((s) => s.name);
|
|
7091
|
+
const hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(" or ")}?` : "";
|
|
7078
7092
|
return errorResult(
|
|
7079
|
-
`Unknown command: ${commandName}
|
|
7093
|
+
`Unknown command: ${commandName}.${hint} Available: ${available.join(", ")}`
|
|
7080
7094
|
);
|
|
7081
7095
|
}
|
|
7082
7096
|
const _target = params._target;
|
|
@@ -7103,7 +7117,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7103
7117
|
params = result.data;
|
|
7104
7118
|
}
|
|
7105
7119
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
7106
|
-
const { forwardExec } = await import("./daemon-client-
|
|
7120
|
+
const { forwardExec } = await import("./daemon-client-S3EUTRC6.js");
|
|
7107
7121
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
7108
7122
|
if (result) return result;
|
|
7109
7123
|
}
|
|
@@ -8920,7 +8934,7 @@ var pluginSearchBuiltin = {
|
|
|
8920
8934
|
const query = args[0] || "";
|
|
8921
8935
|
try {
|
|
8922
8936
|
const searchOptions = {
|
|
8923
|
-
query,
|
|
8937
|
+
query: query || (options["tag"] || ""),
|
|
8924
8938
|
tag: options["tag"],
|
|
8925
8939
|
site: options["site"],
|
|
8926
8940
|
limit: options["limit"] ? Number.parseInt(String(options["limit"])) : 20
|
|
@@ -10441,7 +10455,7 @@ async function handlePlugin(args, options, mode) {
|
|
|
10441
10455
|
} catch {
|
|
10442
10456
|
}
|
|
10443
10457
|
try {
|
|
10444
|
-
const { daemonPing } = await import("./daemon-client-
|
|
10458
|
+
const { daemonPing } = await import("./daemon-client-S3EUTRC6.js");
|
|
10445
10459
|
if (await daemonPing()) {
|
|
10446
10460
|
await fetch("http://localhost:9224/rpc", {
|
|
10447
10461
|
method: "POST",
|
|
@@ -10525,7 +10539,11 @@ Total: ${enrichedPlugins.length} plugins`);
|
|
|
10525
10539
|
case "reload": {
|
|
10526
10540
|
const name = subArgs[0];
|
|
10527
10541
|
if (!name) outputError("Usage: xbrowser plugin reload <name>");
|
|
10528
|
-
|
|
10542
|
+
try {
|
|
10543
|
+
await (await getPluginLoader()).reloadPlugin(name);
|
|
10544
|
+
} catch {
|
|
10545
|
+
outputError(`Plugin "${name}" not found. Use 'xbrowser plugin list' to see installed plugins.`);
|
|
10546
|
+
}
|
|
10529
10547
|
outputResult({ ok: true, name }, mode);
|
|
10530
10548
|
break;
|
|
10531
10549
|
}
|
|
@@ -12485,13 +12503,24 @@ async function routeCommand(argvIn, stdinCommands) {
|
|
|
12485
12503
|
await handleChainInput(argv[0], argv);
|
|
12486
12504
|
return;
|
|
12487
12505
|
}
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
|
|
12506
|
+
const globalFlags = /* @__PURE__ */ new Set(["--session", "--cdp", "--json", "--yaml", "--output", "--timeout", "--help", "-h", "--version"]);
|
|
12507
|
+
let chainArgIdx = -1;
|
|
12508
|
+
for (let i = 0; i < argv.length; i++) {
|
|
12509
|
+
if (globalFlags.has(argv[i])) continue;
|
|
12510
|
+
if (globalFlags.has(argv[i]) || i > 0 && globalFlags.has(argv[i - 1]) && !argv[i].startsWith("-")) continue;
|
|
12511
|
+
if (argv[i].includes(" ") && /^[a-zA-Z]/.test(argv[i])) {
|
|
12512
|
+
chainArgIdx = i;
|
|
12513
|
+
break;
|
|
12514
|
+
}
|
|
12515
|
+
}
|
|
12516
|
+
if (chainArgIdx >= 0) {
|
|
12517
|
+
const chainArg = argv[chainArgIdx];
|
|
12518
|
+
const spaceIdx = chainArg.indexOf(" ");
|
|
12519
|
+
const possibleCmd = chainArg.substring(0, spaceIdx);
|
|
12491
12520
|
if (/^[a-zA-Z][\w-]*$/.test(possibleCmd)) {
|
|
12492
|
-
const remainder =
|
|
12521
|
+
const remainder = chainArg.substring(spaceIdx + 1);
|
|
12493
12522
|
const remainderParts = remainder.split(/\s+/).filter(Boolean);
|
|
12494
|
-
argv = [possibleCmd, ...remainderParts, ...argv.slice(1)];
|
|
12523
|
+
argv = [...argv.slice(0, chainArgIdx), possibleCmd, ...remainderParts, ...argv.slice(chainArgIdx + 1)];
|
|
12495
12524
|
}
|
|
12496
12525
|
}
|
|
12497
12526
|
} catch (e) {
|
|
@@ -12505,7 +12534,7 @@ async function routeCommand(argvIn, stdinCommands) {
|
|
|
12505
12534
|
const mode = options.json ? "json" : options.yaml ? "yaml" : "text";
|
|
12506
12535
|
const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
|
|
12507
12536
|
const cdpEndpoint = options.cdp || process.env.XBROWSER_CDP;
|
|
12508
|
-
if (options.version) {
|
|
12537
|
+
if (options.version || options.v && positional.length === 0) {
|
|
12509
12538
|
console.log(`xbrowser v${version}`);
|
|
12510
12539
|
return;
|
|
12511
12540
|
}
|
|
@@ -12685,9 +12714,33 @@ async function routeCommand(argvIn, stdinCommands) {
|
|
|
12685
12714
|
const internalLoader = loader.getCore().loader;
|
|
12686
12715
|
const site = internalLoader.getSite(command);
|
|
12687
12716
|
if (!site) {
|
|
12688
|
-
const
|
|
12717
|
+
const globalFlagSet = /* @__PURE__ */ new Set(["--session", "--cdp", "--json", "--yaml", "--help", "-h", "--version", "--output", "-o"]);
|
|
12718
|
+
const cleanParts = [];
|
|
12719
|
+
for (let i = 0; i < argv.length; i++) {
|
|
12720
|
+
if (globalFlagSet.has(argv[i])) continue;
|
|
12721
|
+
if (i > 0 && globalFlagSet.has(argv[i - 1]) && !argv[i].startsWith("-")) continue;
|
|
12722
|
+
if (argv[i].startsWith("--session=") || argv[i].startsWith("--cdp=")) continue;
|
|
12723
|
+
cleanParts.push(argv[i]);
|
|
12724
|
+
}
|
|
12725
|
+
const fullInput = cleanParts.join(" ");
|
|
12689
12726
|
if (isChainInput(fullInput)) {
|
|
12690
12727
|
const chainResult = await executeChain(fullInput, { cdpEndpoint, sessionName });
|
|
12728
|
+
if (mode === "json" || mode === "yaml") {
|
|
12729
|
+
const output = {
|
|
12730
|
+
success: chainResult.success,
|
|
12731
|
+
steps: chainResult.steps.map((s) => ({
|
|
12732
|
+
command: s.raw,
|
|
12733
|
+
success: s.success,
|
|
12734
|
+
data: s.data,
|
|
12735
|
+
duration: s.duration
|
|
12736
|
+
})),
|
|
12737
|
+
totalDuration: chainResult.totalDuration,
|
|
12738
|
+
...chainResult.stoppedReason ? { stoppedReason: chainResult.stoppedReason } : {}
|
|
12739
|
+
};
|
|
12740
|
+
outputResult(output, mode);
|
|
12741
|
+
if (!chainResult.success) throw new Error("Command failed");
|
|
12742
|
+
return;
|
|
12743
|
+
}
|
|
12691
12744
|
for (const step of chainResult.steps) {
|
|
12692
12745
|
if (step.success) {
|
|
12693
12746
|
console.log(`[OK] ${step.raw}`);
|
|
@@ -12797,7 +12850,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
12797
12850
|
}
|
|
12798
12851
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
12799
12852
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
12800
|
-
const { forwardExec } = await import("./daemon-client-
|
|
12853
|
+
const { forwardExec } = await import("./daemon-client-S3EUTRC6.js");
|
|
12801
12854
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
12802
12855
|
const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
|
|
12803
12856
|
const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
|
|
@@ -23,6 +23,7 @@ async function ensureDaemonRunning() {
|
|
|
23
23
|
if (healthOk) return;
|
|
24
24
|
if (attempt < 2) await new Promise((r) => setTimeout(r, 500));
|
|
25
25
|
}
|
|
26
|
+
console.error("\u{1F504} Starting daemon...");
|
|
26
27
|
_ensurePromise = startDaemonProcess(DAEMON_PORT).then(() => {
|
|
27
28
|
});
|
|
28
29
|
_ensurePromise.catch(() => {
|
|
@@ -36,7 +37,10 @@ async function ensureDaemonRunning() {
|
|
|
36
37
|
}
|
|
37
38
|
for (let attempt = 0; attempt < 20; attempt++) {
|
|
38
39
|
const ready = await fetch(`${DAEMON_BASE}/health`, { signal: AbortSignal.timeout(1e3) }).then((r) => r.ok ? r.json() : null).then((d) => d?.status === "ok").catch(() => false);
|
|
39
|
-
if (ready)
|
|
40
|
+
if (ready) {
|
|
41
|
+
console.error("\u2705 Daemon ready");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
40
44
|
await new Promise((r) => setTimeout(r, 200));
|
|
41
45
|
}
|
|
42
46
|
throw new Error("Daemon HTTP server not ready after 4s");
|
package/dist/daemon-main.js
CHANGED
|
@@ -1087,7 +1087,7 @@ var screenshotCommand = registerCommand({
|
|
|
1087
1087
|
buffer = await ctx.page.screenshot(options);
|
|
1088
1088
|
}
|
|
1089
1089
|
if (p.output) {
|
|
1090
|
-
writeFileSync(p.output, buffer);
|
|
1090
|
+
writeFileSync(p.output, buffer, "binary");
|
|
1091
1091
|
return ok9({
|
|
1092
1092
|
output: p.output,
|
|
1093
1093
|
format,
|
|
@@ -1103,7 +1103,7 @@ var screenshotCommand = registerCommand({
|
|
|
1103
1103
|
}
|
|
1104
1104
|
ensureScreenshotsDir();
|
|
1105
1105
|
const screenshotPath = generateScreenshotPath(format);
|
|
1106
|
-
writeFileSync(screenshotPath, buffer);
|
|
1106
|
+
writeFileSync(screenshotPath, buffer, "binary");
|
|
1107
1107
|
return ok9({
|
|
1108
1108
|
output: screenshotPath,
|
|
1109
1109
|
format,
|
|
@@ -5362,19 +5362,19 @@ function parseCommandChain(input, options) {
|
|
|
5362
5362
|
continue;
|
|
5363
5363
|
}
|
|
5364
5364
|
if (char === "-" && input[i + 1] === ">" && isSpaceAround(input, i, 2)) {
|
|
5365
|
-
|
|
5366
|
-
|
|
5365
|
+
lastOperator = "sequence";
|
|
5366
|
+
flushPipeline();
|
|
5367
5367
|
i++;
|
|
5368
5368
|
continue;
|
|
5369
5369
|
}
|
|
5370
5370
|
if (char === "," && isSpaceAdjacent(input, i)) {
|
|
5371
|
-
|
|
5372
|
-
|
|
5371
|
+
lastOperator = "sequence";
|
|
5372
|
+
flushPipeline();
|
|
5373
5373
|
continue;
|
|
5374
5374
|
}
|
|
5375
5375
|
if (char === "+" && isSpaceAdjacent(input, i)) {
|
|
5376
|
-
|
|
5377
|
-
|
|
5376
|
+
lastOperator = "sequence";
|
|
5377
|
+
flushPipeline();
|
|
5378
5378
|
continue;
|
|
5379
5379
|
}
|
|
5380
5380
|
if (options?.fileMode && char === "|" && input[i + 1] !== "|") {
|
|
@@ -6546,6 +6546,18 @@ async function loadHooks() {
|
|
|
6546
6546
|
// src/executor.ts
|
|
6547
6547
|
import { homedir as homedir5 } from "os";
|
|
6548
6548
|
import { join as join5 } from "path";
|
|
6549
|
+
function levenshtein(a, b) {
|
|
6550
|
+
const m = a.length, n = b.length;
|
|
6551
|
+
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
6552
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
6553
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
6554
|
+
for (let i = 1; i <= m; i++) {
|
|
6555
|
+
for (let j = 1; j <= n; j++) {
|
|
6556
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
6557
|
+
}
|
|
6558
|
+
}
|
|
6559
|
+
return dp[m][n];
|
|
6560
|
+
}
|
|
6549
6561
|
var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
|
|
6550
6562
|
var snapshotHintShown = /* @__PURE__ */ new WeakSet();
|
|
6551
6563
|
var CONFIG_DIR2 = join5(homedir5(), ".xbrowser");
|
|
@@ -6606,8 +6618,10 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6606
6618
|
const command = getCommand(commandName);
|
|
6607
6619
|
if (!command) {
|
|
6608
6620
|
const available = getAllCommands().map((c) => c.name);
|
|
6621
|
+
const suggestions = available.map((name) => ({ name, dist: levenshtein(commandName, name) })).filter((s) => s.dist <= 3).sort((a, b) => a.dist - b.dist).slice(0, 3).map((s) => s.name);
|
|
6622
|
+
const hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(" or ")}?` : "";
|
|
6609
6623
|
return errorResult(
|
|
6610
|
-
`Unknown command: ${commandName}
|
|
6624
|
+
`Unknown command: ${commandName}.${hint} Available: ${available.join(", ")}`
|
|
6611
6625
|
);
|
|
6612
6626
|
}
|
|
6613
6627
|
const _target = params._target;
|
|
@@ -6634,7 +6648,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6634
6648
|
params = result.data;
|
|
6635
6649
|
}
|
|
6636
6650
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
6637
|
-
const { forwardExec } = await import("./daemon-client-
|
|
6651
|
+
const { forwardExec } = await import("./daemon-client-WT7PTGYQ.js");
|
|
6638
6652
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
6639
6653
|
if (result) return result;
|
|
6640
6654
|
}
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
killAllDaemonProcesses,
|
|
26
26
|
startDaemonProcess,
|
|
27
27
|
stopDaemonProcess
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-L35D5DAY.js";
|
|
29
29
|
import {
|
|
30
30
|
CaptchaDetector,
|
|
31
31
|
HumanInteractionManager,
|
|
@@ -1166,7 +1166,7 @@ var screenshotCommand = registerCommand({
|
|
|
1166
1166
|
buffer = await ctx.page.screenshot(options);
|
|
1167
1167
|
}
|
|
1168
1168
|
if (p.output) {
|
|
1169
|
-
writeFileSync(p.output, buffer);
|
|
1169
|
+
writeFileSync(p.output, buffer, "binary");
|
|
1170
1170
|
return ok9({
|
|
1171
1171
|
output: p.output,
|
|
1172
1172
|
format,
|
|
@@ -1182,7 +1182,7 @@ var screenshotCommand = registerCommand({
|
|
|
1182
1182
|
}
|
|
1183
1183
|
ensureScreenshotsDir();
|
|
1184
1184
|
const screenshotPath = generateScreenshotPath(format);
|
|
1185
|
-
writeFileSync(screenshotPath, buffer);
|
|
1185
|
+
writeFileSync(screenshotPath, buffer, "binary");
|
|
1186
1186
|
return ok9({
|
|
1187
1187
|
output: screenshotPath,
|
|
1188
1188
|
format,
|
|
@@ -5718,19 +5718,19 @@ function parseCommandChain(input, options) {
|
|
|
5718
5718
|
continue;
|
|
5719
5719
|
}
|
|
5720
5720
|
if (char === "-" && input[i + 1] === ">" && isSpaceAround(input, i, 2)) {
|
|
5721
|
-
|
|
5722
|
-
|
|
5721
|
+
lastOperator = "sequence";
|
|
5722
|
+
flushPipeline();
|
|
5723
5723
|
i++;
|
|
5724
5724
|
continue;
|
|
5725
5725
|
}
|
|
5726
5726
|
if (char === "," && isSpaceAdjacent(input, i)) {
|
|
5727
|
-
|
|
5728
|
-
|
|
5727
|
+
lastOperator = "sequence";
|
|
5728
|
+
flushPipeline();
|
|
5729
5729
|
continue;
|
|
5730
5730
|
}
|
|
5731
5731
|
if (char === "+" && isSpaceAdjacent(input, i)) {
|
|
5732
|
-
|
|
5733
|
-
|
|
5732
|
+
lastOperator = "sequence";
|
|
5733
|
+
flushPipeline();
|
|
5734
5734
|
continue;
|
|
5735
5735
|
}
|
|
5736
5736
|
if (options?.fileMode && char === "|" && input[i + 1] !== "|") {
|
|
@@ -7332,6 +7332,18 @@ async function loadHooks() {
|
|
|
7332
7332
|
// src/executor.ts
|
|
7333
7333
|
import { homedir as homedir6 } from "os";
|
|
7334
7334
|
import { join as join6 } from "path";
|
|
7335
|
+
function levenshtein(a, b) {
|
|
7336
|
+
const m = a.length, n = b.length;
|
|
7337
|
+
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
7338
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
7339
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
7340
|
+
for (let i = 1; i <= m; i++) {
|
|
7341
|
+
for (let j = 1; j <= n; j++) {
|
|
7342
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
7343
|
+
}
|
|
7344
|
+
}
|
|
7345
|
+
return dp[m][n];
|
|
7346
|
+
}
|
|
7335
7347
|
var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
|
|
7336
7348
|
var snapshotHintShown = /* @__PURE__ */ new WeakSet();
|
|
7337
7349
|
var CONFIG_DIR2 = join6(homedir6(), ".xbrowser");
|
|
@@ -7395,8 +7407,10 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7395
7407
|
const command = getCommand(commandName);
|
|
7396
7408
|
if (!command) {
|
|
7397
7409
|
const available = getAllCommands().map((c) => c.name);
|
|
7410
|
+
const suggestions = available.map((name) => ({ name, dist: levenshtein(commandName, name) })).filter((s) => s.dist <= 3).sort((a, b) => a.dist - b.dist).slice(0, 3).map((s) => s.name);
|
|
7411
|
+
const hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(" or ")}?` : "";
|
|
7398
7412
|
return errorResult(
|
|
7399
|
-
`Unknown command: ${commandName}
|
|
7413
|
+
`Unknown command: ${commandName}.${hint} Available: ${available.join(", ")}`
|
|
7400
7414
|
);
|
|
7401
7415
|
}
|
|
7402
7416
|
const _target = params._target;
|
|
@@ -7423,7 +7437,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7423
7437
|
params = result.data;
|
|
7424
7438
|
}
|
|
7425
7439
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
7426
|
-
const { forwardExec } = await import("./daemon-client-
|
|
7440
|
+
const { forwardExec } = await import("./daemon-client-S3EUTRC6.js");
|
|
7427
7441
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
7428
7442
|
if (result) return result;
|
|
7429
7443
|
}
|
|
@@ -9255,7 +9269,7 @@ var pluginSearchBuiltin = {
|
|
|
9255
9269
|
const query = args[0] || "";
|
|
9256
9270
|
try {
|
|
9257
9271
|
const searchOptions = {
|
|
9258
|
-
query,
|
|
9272
|
+
query: query || (options["tag"] || ""),
|
|
9259
9273
|
tag: options["tag"],
|
|
9260
9274
|
site: options["site"],
|
|
9261
9275
|
limit: options["limit"] ? Number.parseInt(String(options["limit"])) : 20
|
|
@@ -10781,7 +10795,7 @@ async function handlePlugin(args, options, mode) {
|
|
|
10781
10795
|
} catch {
|
|
10782
10796
|
}
|
|
10783
10797
|
try {
|
|
10784
|
-
const { daemonPing } = await import("./daemon-client-
|
|
10798
|
+
const { daemonPing } = await import("./daemon-client-S3EUTRC6.js");
|
|
10785
10799
|
if (await daemonPing()) {
|
|
10786
10800
|
await fetch("http://localhost:9224/rpc", {
|
|
10787
10801
|
method: "POST",
|
|
@@ -10865,7 +10879,11 @@ Total: ${enrichedPlugins.length} plugins`);
|
|
|
10865
10879
|
case "reload": {
|
|
10866
10880
|
const name = subArgs[0];
|
|
10867
10881
|
if (!name) outputError("Usage: xbrowser plugin reload <name>");
|
|
10868
|
-
|
|
10882
|
+
try {
|
|
10883
|
+
await (await getPluginLoader()).reloadPlugin(name);
|
|
10884
|
+
} catch {
|
|
10885
|
+
outputError(`Plugin "${name}" not found. Use 'xbrowser plugin list' to see installed plugins.`);
|
|
10886
|
+
}
|
|
10869
10887
|
outputResult({ ok: true, name }, mode);
|
|
10870
10888
|
break;
|
|
10871
10889
|
}
|
|
@@ -12825,13 +12843,24 @@ async function routeCommand(argvIn, stdinCommands) {
|
|
|
12825
12843
|
await handleChainInput(argv[0], argv);
|
|
12826
12844
|
return;
|
|
12827
12845
|
}
|
|
12828
|
-
|
|
12829
|
-
|
|
12830
|
-
|
|
12846
|
+
const globalFlags = /* @__PURE__ */ new Set(["--session", "--cdp", "--json", "--yaml", "--output", "--timeout", "--help", "-h", "--version"]);
|
|
12847
|
+
let chainArgIdx = -1;
|
|
12848
|
+
for (let i = 0; i < argv.length; i++) {
|
|
12849
|
+
if (globalFlags.has(argv[i])) continue;
|
|
12850
|
+
if (globalFlags.has(argv[i]) || i > 0 && globalFlags.has(argv[i - 1]) && !argv[i].startsWith("-")) continue;
|
|
12851
|
+
if (argv[i].includes(" ") && /^[a-zA-Z]/.test(argv[i])) {
|
|
12852
|
+
chainArgIdx = i;
|
|
12853
|
+
break;
|
|
12854
|
+
}
|
|
12855
|
+
}
|
|
12856
|
+
if (chainArgIdx >= 0) {
|
|
12857
|
+
const chainArg = argv[chainArgIdx];
|
|
12858
|
+
const spaceIdx = chainArg.indexOf(" ");
|
|
12859
|
+
const possibleCmd = chainArg.substring(0, spaceIdx);
|
|
12831
12860
|
if (/^[a-zA-Z][\w-]*$/.test(possibleCmd)) {
|
|
12832
|
-
const remainder =
|
|
12861
|
+
const remainder = chainArg.substring(spaceIdx + 1);
|
|
12833
12862
|
const remainderParts = remainder.split(/\s+/).filter(Boolean);
|
|
12834
|
-
argv = [possibleCmd, ...remainderParts, ...argv.slice(1)];
|
|
12863
|
+
argv = [...argv.slice(0, chainArgIdx), possibleCmd, ...remainderParts, ...argv.slice(chainArgIdx + 1)];
|
|
12835
12864
|
}
|
|
12836
12865
|
}
|
|
12837
12866
|
} catch (e) {
|
|
@@ -12845,7 +12874,7 @@ async function routeCommand(argvIn, stdinCommands) {
|
|
|
12845
12874
|
const mode = options.json ? "json" : options.yaml ? "yaml" : "text";
|
|
12846
12875
|
const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
|
|
12847
12876
|
const cdpEndpoint = options.cdp || process.env.XBROWSER_CDP;
|
|
12848
|
-
if (options.version) {
|
|
12877
|
+
if (options.version || options.v && positional.length === 0) {
|
|
12849
12878
|
console.log(`xbrowser v${version}`);
|
|
12850
12879
|
return;
|
|
12851
12880
|
}
|
|
@@ -13025,9 +13054,33 @@ async function routeCommand(argvIn, stdinCommands) {
|
|
|
13025
13054
|
const internalLoader = loader.getCore().loader;
|
|
13026
13055
|
const site = internalLoader.getSite(command);
|
|
13027
13056
|
if (!site) {
|
|
13028
|
-
const
|
|
13057
|
+
const globalFlagSet = /* @__PURE__ */ new Set(["--session", "--cdp", "--json", "--yaml", "--help", "-h", "--version", "--output", "-o"]);
|
|
13058
|
+
const cleanParts = [];
|
|
13059
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13060
|
+
if (globalFlagSet.has(argv[i])) continue;
|
|
13061
|
+
if (i > 0 && globalFlagSet.has(argv[i - 1]) && !argv[i].startsWith("-")) continue;
|
|
13062
|
+
if (argv[i].startsWith("--session=") || argv[i].startsWith("--cdp=")) continue;
|
|
13063
|
+
cleanParts.push(argv[i]);
|
|
13064
|
+
}
|
|
13065
|
+
const fullInput = cleanParts.join(" ");
|
|
13029
13066
|
if (isChainInput(fullInput)) {
|
|
13030
13067
|
const chainResult = await executeChain(fullInput, { cdpEndpoint, sessionName });
|
|
13068
|
+
if (mode === "json" || mode === "yaml") {
|
|
13069
|
+
const output = {
|
|
13070
|
+
success: chainResult.success,
|
|
13071
|
+
steps: chainResult.steps.map((s) => ({
|
|
13072
|
+
command: s.raw,
|
|
13073
|
+
success: s.success,
|
|
13074
|
+
data: s.data,
|
|
13075
|
+
duration: s.duration
|
|
13076
|
+
})),
|
|
13077
|
+
totalDuration: chainResult.totalDuration,
|
|
13078
|
+
...chainResult.stoppedReason ? { stoppedReason: chainResult.stoppedReason } : {}
|
|
13079
|
+
};
|
|
13080
|
+
outputResult(output, mode);
|
|
13081
|
+
if (!chainResult.success) throw new Error("Command failed");
|
|
13082
|
+
return;
|
|
13083
|
+
}
|
|
13031
13084
|
for (const step of chainResult.steps) {
|
|
13032
13085
|
if (step.success) {
|
|
13033
13086
|
console.log(`[OK] ${step.raw}`);
|
|
@@ -13137,7 +13190,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13137
13190
|
}
|
|
13138
13191
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
13139
13192
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
13140
|
-
const { forwardExec } = await import("./daemon-client-
|
|
13193
|
+
const { forwardExec } = await import("./daemon-client-S3EUTRC6.js");
|
|
13141
13194
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
13142
13195
|
const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
|
|
13143
13196
|
const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xbrowser/cli",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
4
4
|
"description": "Browser automation CLI for web scraping, headless browsing, SEO analysis, and AI agent workflows. A command-line alternative to Playwright, Puppeteer, and Selenium.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|