@xbrowser/cli 1.5.2 → 1.5.4
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/{chunk-MDAPTB7C.js → chunk-ZJHQPMCY.js} +19 -2
- package/dist/cli.js +98 -14
- 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 +59 -8
- package/dist/{human-interaction-UKAS5ZXV.js → human-interaction-2DZK4MW7.js} +1 -1
- package/dist/index.js +81 -14
- 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");
|
|
@@ -289,11 +289,28 @@ function saveConfig(config) {
|
|
|
289
289
|
coreSaveConfig(getConfigSource(), config);
|
|
290
290
|
}
|
|
291
291
|
function getConfigValue(key) {
|
|
292
|
-
|
|
292
|
+
const parts = key.split(".");
|
|
293
|
+
let obj = loadConfig();
|
|
294
|
+
for (const part of parts) {
|
|
295
|
+
if (obj && typeof obj === "object") {
|
|
296
|
+
obj = obj[part];
|
|
297
|
+
} else {
|
|
298
|
+
return void 0;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return obj;
|
|
293
302
|
}
|
|
294
303
|
function setConfigValue(key, value) {
|
|
295
304
|
const config = loadConfig();
|
|
296
|
-
|
|
305
|
+
const parts = key.split(".");
|
|
306
|
+
let obj = config;
|
|
307
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
308
|
+
if (!obj[parts[i]] || typeof obj[parts[i]] !== "object") {
|
|
309
|
+
obj[parts[i]] = {};
|
|
310
|
+
}
|
|
311
|
+
obj = obj[parts[i]];
|
|
312
|
+
}
|
|
313
|
+
obj[parts[parts.length - 1]] = value;
|
|
297
314
|
saveConfig(config);
|
|
298
315
|
}
|
|
299
316
|
var DEFAULT_MARKETPLACE_URL = "https://marketplace.xbrowser.dev";
|
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,8 +5401,8 @@ 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
|
}
|
|
@@ -5412,8 +5412,8 @@ function parseCommandChain(input, options) {
|
|
|
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,47 @@ 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
|
+
let hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(" or ")}?` : "";
|
|
7092
|
+
const knownPlugins = [
|
|
7093
|
+
"douyin",
|
|
7094
|
+
"xiaohongshu",
|
|
7095
|
+
"zhihu",
|
|
7096
|
+
"chatgpt",
|
|
7097
|
+
"deepseek",
|
|
7098
|
+
"baidu",
|
|
7099
|
+
"bilibili",
|
|
7100
|
+
"github",
|
|
7101
|
+
"medium",
|
|
7102
|
+
"juejin",
|
|
7103
|
+
"devto",
|
|
7104
|
+
"twitter",
|
|
7105
|
+
"reddit",
|
|
7106
|
+
"steam",
|
|
7107
|
+
"doubao",
|
|
7108
|
+
"qianwen",
|
|
7109
|
+
"yuanbao",
|
|
7110
|
+
"claude",
|
|
7111
|
+
"gemini",
|
|
7112
|
+
"suno",
|
|
7113
|
+
"mureka",
|
|
7114
|
+
"wanx",
|
|
7115
|
+
"taobao",
|
|
7116
|
+
"google",
|
|
7117
|
+
"wordpress",
|
|
7118
|
+
"csdn",
|
|
7119
|
+
"quora",
|
|
7120
|
+
"producthunt",
|
|
7121
|
+
"hashnode",
|
|
7122
|
+
"blogger",
|
|
7123
|
+
"facebook",
|
|
7124
|
+
"instagram"
|
|
7125
|
+
];
|
|
7126
|
+
if (knownPlugins.includes(commandName)) {
|
|
7127
|
+
hint = ` Plugin "${commandName}" may need to be installed. Try: xbrowser plugin install @xbrowser/${commandName}`;
|
|
7128
|
+
}
|
|
7078
7129
|
return errorResult(
|
|
7079
|
-
`Unknown command: ${commandName}
|
|
7130
|
+
`Unknown command: ${commandName}.${hint} Available: ${available.join(", ")}`
|
|
7080
7131
|
);
|
|
7081
7132
|
}
|
|
7082
7133
|
const _target = params._target;
|
|
@@ -7103,7 +7154,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7103
7154
|
params = result.data;
|
|
7104
7155
|
}
|
|
7105
7156
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
7106
|
-
const { forwardExec } = await import("./daemon-client-
|
|
7157
|
+
const { forwardExec } = await import("./daemon-client-S3EUTRC6.js");
|
|
7107
7158
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
7108
7159
|
if (result) return result;
|
|
7109
7160
|
}
|
|
@@ -7681,11 +7732,28 @@ function saveConfig(config) {
|
|
|
7681
7732
|
coreSaveConfig(getConfigSource(), config);
|
|
7682
7733
|
}
|
|
7683
7734
|
function getConfigValue(key) {
|
|
7684
|
-
|
|
7735
|
+
const parts = key.split(".");
|
|
7736
|
+
let obj = loadConfig();
|
|
7737
|
+
for (const part of parts) {
|
|
7738
|
+
if (obj && typeof obj === "object") {
|
|
7739
|
+
obj = obj[part];
|
|
7740
|
+
} else {
|
|
7741
|
+
return void 0;
|
|
7742
|
+
}
|
|
7743
|
+
}
|
|
7744
|
+
return obj;
|
|
7685
7745
|
}
|
|
7686
7746
|
function setConfigValue(key, value) {
|
|
7687
7747
|
const config = loadConfig();
|
|
7688
|
-
|
|
7748
|
+
const parts = key.split(".");
|
|
7749
|
+
let obj = config;
|
|
7750
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
7751
|
+
if (!obj[parts[i]] || typeof obj[parts[i]] !== "object") {
|
|
7752
|
+
obj[parts[i]] = {};
|
|
7753
|
+
}
|
|
7754
|
+
obj = obj[parts[i]];
|
|
7755
|
+
}
|
|
7756
|
+
obj[parts[parts.length - 1]] = value;
|
|
7689
7757
|
saveConfig(config);
|
|
7690
7758
|
}
|
|
7691
7759
|
var DEFAULT_MARKETPLACE_URL = "https://marketplace.xbrowser.dev";
|
|
@@ -10441,7 +10509,7 @@ async function handlePlugin(args, options, mode) {
|
|
|
10441
10509
|
} catch {
|
|
10442
10510
|
}
|
|
10443
10511
|
try {
|
|
10444
|
-
const { daemonPing } = await import("./daemon-client-
|
|
10512
|
+
const { daemonPing } = await import("./daemon-client-S3EUTRC6.js");
|
|
10445
10513
|
if (await daemonPing()) {
|
|
10446
10514
|
await fetch("http://localhost:9224/rpc", {
|
|
10447
10515
|
method: "POST",
|
|
@@ -12520,7 +12588,7 @@ async function routeCommand(argvIn, stdinCommands) {
|
|
|
12520
12588
|
const mode = options.json ? "json" : options.yaml ? "yaml" : "text";
|
|
12521
12589
|
const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
|
|
12522
12590
|
const cdpEndpoint = options.cdp || process.env.XBROWSER_CDP;
|
|
12523
|
-
if (options.version) {
|
|
12591
|
+
if (options.version || options.v && positional.length === 0) {
|
|
12524
12592
|
console.log(`xbrowser v${version}`);
|
|
12525
12593
|
return;
|
|
12526
12594
|
}
|
|
@@ -12711,6 +12779,22 @@ async function routeCommand(argvIn, stdinCommands) {
|
|
|
12711
12779
|
const fullInput = cleanParts.join(" ");
|
|
12712
12780
|
if (isChainInput(fullInput)) {
|
|
12713
12781
|
const chainResult = await executeChain(fullInput, { cdpEndpoint, sessionName });
|
|
12782
|
+
if (mode === "json" || mode === "yaml") {
|
|
12783
|
+
const output = {
|
|
12784
|
+
success: chainResult.success,
|
|
12785
|
+
steps: chainResult.steps.map((s) => ({
|
|
12786
|
+
command: s.raw,
|
|
12787
|
+
success: s.success,
|
|
12788
|
+
data: s.data,
|
|
12789
|
+
duration: s.duration
|
|
12790
|
+
})),
|
|
12791
|
+
totalDuration: chainResult.totalDuration,
|
|
12792
|
+
...chainResult.stoppedReason ? { stoppedReason: chainResult.stoppedReason } : {}
|
|
12793
|
+
};
|
|
12794
|
+
outputResult(output, mode);
|
|
12795
|
+
if (!chainResult.success) throw new Error("Command failed");
|
|
12796
|
+
return;
|
|
12797
|
+
}
|
|
12714
12798
|
for (const step of chainResult.steps) {
|
|
12715
12799
|
if (step.success) {
|
|
12716
12800
|
console.log(`[OK] ${step.raw}`);
|
|
@@ -12820,7 +12904,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
12820
12904
|
}
|
|
12821
12905
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
12822
12906
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
12823
|
-
const { forwardExec } = await import("./daemon-client-
|
|
12907
|
+
const { forwardExec } = await import("./daemon-client-S3EUTRC6.js");
|
|
12824
12908
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
12825
12909
|
const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
|
|
12826
12910
|
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,8 +5362,8 @@ 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
|
}
|
|
@@ -5373,8 +5373,8 @@ function parseCommandChain(input, options) {
|
|
|
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,47 @@ 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
|
+
let hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(" or ")}?` : "";
|
|
6623
|
+
const knownPlugins = [
|
|
6624
|
+
"douyin",
|
|
6625
|
+
"xiaohongshu",
|
|
6626
|
+
"zhihu",
|
|
6627
|
+
"chatgpt",
|
|
6628
|
+
"deepseek",
|
|
6629
|
+
"baidu",
|
|
6630
|
+
"bilibili",
|
|
6631
|
+
"github",
|
|
6632
|
+
"medium",
|
|
6633
|
+
"juejin",
|
|
6634
|
+
"devto",
|
|
6635
|
+
"twitter",
|
|
6636
|
+
"reddit",
|
|
6637
|
+
"steam",
|
|
6638
|
+
"doubao",
|
|
6639
|
+
"qianwen",
|
|
6640
|
+
"yuanbao",
|
|
6641
|
+
"claude",
|
|
6642
|
+
"gemini",
|
|
6643
|
+
"suno",
|
|
6644
|
+
"mureka",
|
|
6645
|
+
"wanx",
|
|
6646
|
+
"taobao",
|
|
6647
|
+
"google",
|
|
6648
|
+
"wordpress",
|
|
6649
|
+
"csdn",
|
|
6650
|
+
"quora",
|
|
6651
|
+
"producthunt",
|
|
6652
|
+
"hashnode",
|
|
6653
|
+
"blogger",
|
|
6654
|
+
"facebook",
|
|
6655
|
+
"instagram"
|
|
6656
|
+
];
|
|
6657
|
+
if (knownPlugins.includes(commandName)) {
|
|
6658
|
+
hint = ` Plugin "${commandName}" may need to be installed. Try: xbrowser plugin install @xbrowser/${commandName}`;
|
|
6659
|
+
}
|
|
6609
6660
|
return errorResult(
|
|
6610
|
-
`Unknown command: ${commandName}
|
|
6661
|
+
`Unknown command: ${commandName}.${hint} Available: ${available.join(", ")}`
|
|
6611
6662
|
);
|
|
6612
6663
|
}
|
|
6613
6664
|
const _target = params._target;
|
|
@@ -6634,7 +6685,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6634
6685
|
params = result.data;
|
|
6635
6686
|
}
|
|
6636
6687
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
6637
|
-
const { forwardExec } = await import("./daemon-client-
|
|
6688
|
+
const { forwardExec } = await import("./daemon-client-WT7PTGYQ.js");
|
|
6638
6689
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
6639
6690
|
if (result) return result;
|
|
6640
6691
|
}
|
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,
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
loadConfig,
|
|
40
40
|
resolveNpmPackageWithFallback,
|
|
41
41
|
setConfigValue
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-ZJHQPMCY.js";
|
|
43
43
|
import {
|
|
44
44
|
generateBashScript,
|
|
45
45
|
generateJSScript,
|
|
@@ -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,8 +5718,8 @@ 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
|
}
|
|
@@ -5729,8 +5729,8 @@ function parseCommandChain(input, options) {
|
|
|
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,47 @@ 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
|
+
let hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(" or ")}?` : "";
|
|
7412
|
+
const knownPlugins = [
|
|
7413
|
+
"douyin",
|
|
7414
|
+
"xiaohongshu",
|
|
7415
|
+
"zhihu",
|
|
7416
|
+
"chatgpt",
|
|
7417
|
+
"deepseek",
|
|
7418
|
+
"baidu",
|
|
7419
|
+
"bilibili",
|
|
7420
|
+
"github",
|
|
7421
|
+
"medium",
|
|
7422
|
+
"juejin",
|
|
7423
|
+
"devto",
|
|
7424
|
+
"twitter",
|
|
7425
|
+
"reddit",
|
|
7426
|
+
"steam",
|
|
7427
|
+
"doubao",
|
|
7428
|
+
"qianwen",
|
|
7429
|
+
"yuanbao",
|
|
7430
|
+
"claude",
|
|
7431
|
+
"gemini",
|
|
7432
|
+
"suno",
|
|
7433
|
+
"mureka",
|
|
7434
|
+
"wanx",
|
|
7435
|
+
"taobao",
|
|
7436
|
+
"google",
|
|
7437
|
+
"wordpress",
|
|
7438
|
+
"csdn",
|
|
7439
|
+
"quora",
|
|
7440
|
+
"producthunt",
|
|
7441
|
+
"hashnode",
|
|
7442
|
+
"blogger",
|
|
7443
|
+
"facebook",
|
|
7444
|
+
"instagram"
|
|
7445
|
+
];
|
|
7446
|
+
if (knownPlugins.includes(commandName)) {
|
|
7447
|
+
hint = ` Plugin "${commandName}" may need to be installed. Try: xbrowser plugin install @xbrowser/${commandName}`;
|
|
7448
|
+
}
|
|
7398
7449
|
return errorResult(
|
|
7399
|
-
`Unknown command: ${commandName}
|
|
7450
|
+
`Unknown command: ${commandName}.${hint} Available: ${available.join(", ")}`
|
|
7400
7451
|
);
|
|
7401
7452
|
}
|
|
7402
7453
|
const _target = params._target;
|
|
@@ -7423,7 +7474,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7423
7474
|
params = result.data;
|
|
7424
7475
|
}
|
|
7425
7476
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
7426
|
-
const { forwardExec } = await import("./daemon-client-
|
|
7477
|
+
const { forwardExec } = await import("./daemon-client-S3EUTRC6.js");
|
|
7427
7478
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
7428
7479
|
if (result) return result;
|
|
7429
7480
|
}
|
|
@@ -7903,7 +7954,7 @@ function attachWaitForHuman(ctx, getOrCreateWSServer) {
|
|
|
7903
7954
|
if (!ctx.page) {
|
|
7904
7955
|
throw new Error("waitForHuman requires an active page");
|
|
7905
7956
|
}
|
|
7906
|
-
const { HumanInteractionManager: HumanInteractionManager2 } = await import("./human-interaction-
|
|
7957
|
+
const { HumanInteractionManager: HumanInteractionManager2 } = await import("./human-interaction-2DZK4MW7.js");
|
|
7907
7958
|
const wsServer2 = await getOrCreateWSServer(ctx.browserContext);
|
|
7908
7959
|
const manager = new HumanInteractionManager2(wsServer2, ctx.page);
|
|
7909
7960
|
return manager.waitForHuman(options);
|
|
@@ -10781,7 +10832,7 @@ async function handlePlugin(args, options, mode) {
|
|
|
10781
10832
|
} catch {
|
|
10782
10833
|
}
|
|
10783
10834
|
try {
|
|
10784
|
-
const { daemonPing } = await import("./daemon-client-
|
|
10835
|
+
const { daemonPing } = await import("./daemon-client-S3EUTRC6.js");
|
|
10785
10836
|
if (await daemonPing()) {
|
|
10786
10837
|
await fetch("http://localhost:9224/rpc", {
|
|
10787
10838
|
method: "POST",
|
|
@@ -12860,7 +12911,7 @@ async function routeCommand(argvIn, stdinCommands) {
|
|
|
12860
12911
|
const mode = options.json ? "json" : options.yaml ? "yaml" : "text";
|
|
12861
12912
|
const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
|
|
12862
12913
|
const cdpEndpoint = options.cdp || process.env.XBROWSER_CDP;
|
|
12863
|
-
if (options.version) {
|
|
12914
|
+
if (options.version || options.v && positional.length === 0) {
|
|
12864
12915
|
console.log(`xbrowser v${version}`);
|
|
12865
12916
|
return;
|
|
12866
12917
|
}
|
|
@@ -13051,6 +13102,22 @@ async function routeCommand(argvIn, stdinCommands) {
|
|
|
13051
13102
|
const fullInput = cleanParts.join(" ");
|
|
13052
13103
|
if (isChainInput(fullInput)) {
|
|
13053
13104
|
const chainResult = await executeChain(fullInput, { cdpEndpoint, sessionName });
|
|
13105
|
+
if (mode === "json" || mode === "yaml") {
|
|
13106
|
+
const output = {
|
|
13107
|
+
success: chainResult.success,
|
|
13108
|
+
steps: chainResult.steps.map((s) => ({
|
|
13109
|
+
command: s.raw,
|
|
13110
|
+
success: s.success,
|
|
13111
|
+
data: s.data,
|
|
13112
|
+
duration: s.duration
|
|
13113
|
+
})),
|
|
13114
|
+
totalDuration: chainResult.totalDuration,
|
|
13115
|
+
...chainResult.stoppedReason ? { stoppedReason: chainResult.stoppedReason } : {}
|
|
13116
|
+
};
|
|
13117
|
+
outputResult(output, mode);
|
|
13118
|
+
if (!chainResult.success) throw new Error("Command failed");
|
|
13119
|
+
return;
|
|
13120
|
+
}
|
|
13054
13121
|
for (const step of chainResult.steps) {
|
|
13055
13122
|
if (step.success) {
|
|
13056
13123
|
console.log(`[OK] ${step.raw}`);
|
|
@@ -13160,7 +13227,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13160
13227
|
}
|
|
13161
13228
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
13162
13229
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
13163
|
-
const { forwardExec } = await import("./daemon-client-
|
|
13230
|
+
const { forwardExec } = await import("./daemon-client-S3EUTRC6.js");
|
|
13164
13231
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
13165
13232
|
const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
|
|
13166
13233
|
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.4",
|
|
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": {
|