@xbrowser/cli 1.0.0 → 1.0.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/README.md +17 -26
- package/dist/{browser-DSVV4GHS.js → browser-5CTOA2WS.js} +4 -3
- package/dist/{browser-53KUFEEM.js → browser-ITLZZDHJ.js} +5 -5
- package/dist/{browser-GURRY444.js → browser-IUJXXNBT.js} +6 -3
- package/dist/{cdp-driver-MNPR3HZH.js → cdp-driver-4X3DK6PS.js} +339 -59
- package/dist/{cdp-driver-SSXUGXP6.js → cdp-driver-D6WMSMWX.js} +4 -3
- package/dist/chunk-2SVQTI2O.js +2794 -0
- package/dist/{chunk-IDVD44ED.js → chunk-6WOSXSCQ.js} +23 -7
- package/dist/{chunk-ZZ2TFWIV.js → chunk-ABXMBNQ6.js} +1 -1
- package/dist/{chunk-2MFXKN32.js → chunk-ACFE6PKF.js} +1013 -119
- package/dist/chunk-AMI64BSD.js +268 -0
- package/dist/{chunk-E4O5ZU3H.js → chunk-DKWR54XQ.js} +412 -98
- package/dist/{chunk-DTJRVA76.js → chunk-ETCO4SNK.js} +2 -2
- package/dist/chunk-GDKLH7ZY.js +8 -0
- package/dist/chunk-KFQGP6VL.js +33 -0
- package/dist/{chunk-2BQZIT3S.js → chunk-LRBSUKUZ.js} +85 -2497
- package/dist/{chunk-ITKPSIP7.js → chunk-MDAPTB7C.js} +6 -25
- package/dist/{chunk-42RPMJ76.js → chunk-N2JFPWMI.js} +342 -60
- package/dist/chunk-OZKD3W4X.js +417 -0
- package/dist/{chunk-T4J4C2NZ.js → chunk-TNEN6VQ2.js} +17 -4
- package/dist/{chunk-YKOHDEFV.js → chunk-TWWOIJM7.js} +74 -38
- package/dist/chunk-WJRE55TN.js +83 -0
- package/dist/cli.js +1558 -1122
- package/dist/{convert-EGFYNICZ.js → convert-LB3GJTLR.js} +3 -3
- package/dist/{convert-EKQVHKB4.js → convert-R3XXYKC6.js} +2 -2
- package/dist/{daemon-client-YAVQ343A.js → daemon-client-3JOKX2L2.js} +3 -2
- package/dist/{daemon-client-3VM7VU7O.js → daemon-client-DIEHGP5B.js} +28 -74
- package/dist/daemon-main.js +2296 -1722
- package/dist/{extract-JUOQQX4V.js → extract-2ZFW2MX7.js} +1 -1
- package/dist/{extract-L2IW3IUB.js → extract-BSYBM4MR.js} +1 -1
- package/dist/{filter-HC4RA7JY.js → filter-KCFO4RSV.js} +1 -1
- package/dist/{filter-VID2GGZ7.js → filter-T7DSZ2X7.js} +1 -1
- package/dist/{human-interaction-W753RVJB.js → human-interaction-UKAS5ZXV.js} +2 -2
- package/dist/index.d.ts +166 -109
- package/dist/index.js +2668 -1742
- package/dist/launcher-L2JNDB2H.js +20 -0
- package/dist/{launcher-KA7J32K5.js → launcher-OZXJQPNG.js} +1 -1
- package/dist/{network-store-66A2RATI.js → network-store-XGZ25FFC.js} +1 -1
- package/dist/{network-store-BN6QEZ7R.js → network-store-YVDNUREI.js} +1 -1
- package/dist/{parse-action-dsl-T3DYC33D.js → parse-action-dsl-UM333TL2.js} +1 -1
- package/dist/{proxy-WKGUCH2C.js → proxy-C6CK3UH5.js} +2 -2
- package/dist/session-recorder-RTDGURIJ.js +8 -0
- package/dist/session-recorder-YI7YYM36.js +7 -0
- package/dist/session-replayer-MY27H4DX.js +276 -0
- package/dist/site-knowledge-SYC6VCDB.js +23 -0
- package/package.json +5 -4
- package/dist/screenshot-CWAWMXVA.js +0 -28
- package/dist/session-recorder-MA75PKTQ.js +0 -7
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
SessionRecorder
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ACFE6PKF.js";
|
|
5
|
+
import {
|
|
6
|
+
addKnownIssue,
|
|
7
|
+
getKnowledgePath,
|
|
8
|
+
init_site_knowledge,
|
|
9
|
+
listSiteKnowledge,
|
|
10
|
+
readSiteKnowledge,
|
|
11
|
+
readSiteKnowledgeMarkdown
|
|
12
|
+
} from "./chunk-OZKD3W4X.js";
|
|
5
13
|
import {
|
|
6
14
|
closeAllSessions,
|
|
7
15
|
closeEphemeralContext,
|
|
@@ -17,8 +25,8 @@ import {
|
|
|
17
25
|
resolveLaunchOpts,
|
|
18
26
|
saveSessionDiskMeta,
|
|
19
27
|
setActivePage
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import "./chunk-
|
|
28
|
+
} from "./chunk-DKWR54XQ.js";
|
|
29
|
+
import "./chunk-TNEN6VQ2.js";
|
|
22
30
|
import {
|
|
23
31
|
forwardCommandLog,
|
|
24
32
|
forwardNetworkAnalyze,
|
|
@@ -39,7 +47,6 @@ import {
|
|
|
39
47
|
forwardRecordSummary,
|
|
40
48
|
forwardReplay,
|
|
41
49
|
forwardSessionClose,
|
|
42
|
-
forwardSessionCreate,
|
|
43
50
|
forwardSessionList,
|
|
44
51
|
forwardViewerCheckSelector,
|
|
45
52
|
getDaemonConfig,
|
|
@@ -47,11 +54,16 @@ import {
|
|
|
47
54
|
killAllDaemonProcesses,
|
|
48
55
|
startDaemonProcess,
|
|
49
56
|
stopDaemonProcess
|
|
50
|
-
} from "./chunk-
|
|
51
|
-
import
|
|
57
|
+
} from "./chunk-6WOSXSCQ.js";
|
|
58
|
+
import {
|
|
59
|
+
errMsg
|
|
60
|
+
} from "./chunk-GDKLH7ZY.js";
|
|
61
|
+
import {
|
|
62
|
+
__require
|
|
63
|
+
} from "./chunk-KFQGP6VL.js";
|
|
52
64
|
|
|
53
65
|
// src/router.ts
|
|
54
|
-
import { parseArgs, outputFormatter as outputFormatter2, isCommandResult as isCommandResult2, helpGenerator as helpGenerator2 } from "@dyyz1993/xcli-core";
|
|
66
|
+
import { parseArgs, outputFormatter as outputFormatter2, isCommandResult as isCommandResult2, helpGenerator as helpGenerator2, TipCollector as TipCollector3, normalizeTips as normalizeTips7, tip as makeTip } from "@dyyz1993/xcli-core";
|
|
55
67
|
|
|
56
68
|
// src/utils/positional-params.ts
|
|
57
69
|
import { unquote } from "@dyyz1993/xcli-core";
|
|
@@ -163,6 +175,11 @@ function parsePluginParams(args, schema, base = {}) {
|
|
|
163
175
|
return result;
|
|
164
176
|
}
|
|
165
177
|
|
|
178
|
+
// src/utils/zod-internal.ts
|
|
179
|
+
function asZodSchema(value) {
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
|
|
166
183
|
// src/version.ts
|
|
167
184
|
import { createRequire } from "module";
|
|
168
185
|
var require2 = createRequire(import.meta.url);
|
|
@@ -171,15 +188,79 @@ var version = pkg.version;
|
|
|
171
188
|
|
|
172
189
|
// src/executor.ts
|
|
173
190
|
import {
|
|
174
|
-
ok as
|
|
191
|
+
ok as ok25,
|
|
175
192
|
fail as fail7,
|
|
176
193
|
isCommandResult,
|
|
194
|
+
CompositeStorage as CompositeStorage2,
|
|
195
|
+
TipCollector as TipCollector2,
|
|
196
|
+
normalizeTips as normalizeTips6,
|
|
177
197
|
configureArchiveStore,
|
|
178
198
|
appendCommandToArchive,
|
|
179
199
|
checkGuard,
|
|
180
|
-
|
|
200
|
+
unquote as unquote2
|
|
181
201
|
} from "@dyyz1993/xcli-core";
|
|
182
202
|
|
|
203
|
+
// src/utils/stub-context.ts
|
|
204
|
+
import { TipCollector, CompositeStorage } from "@dyyz1993/xcli-core";
|
|
205
|
+
var CONFIG_DIR = __require("path").join(__require("os").homedir(), ".xbrowser");
|
|
206
|
+
var NoopSiteInstance = class {
|
|
207
|
+
name = "stub";
|
|
208
|
+
url = "";
|
|
209
|
+
config = { name: "stub" };
|
|
210
|
+
command() {
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
group() {
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
login() {
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
logout() {
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
async isLoggedIn() {
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
async requireLogin() {
|
|
226
|
+
}
|
|
227
|
+
getStorage() {
|
|
228
|
+
return new CompositeStorage("stub", CONFIG_DIR, "xbrowser");
|
|
229
|
+
}
|
|
230
|
+
getAllCommands() {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
getCommand() {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
getOriginalHandler() {
|
|
237
|
+
return void 0;
|
|
238
|
+
}
|
|
239
|
+
async executeLogin() {
|
|
240
|
+
}
|
|
241
|
+
async executeLogout() {
|
|
242
|
+
}
|
|
243
|
+
async restoreLogin() {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
function createStubContext(pluginName) {
|
|
248
|
+
return {
|
|
249
|
+
args: [],
|
|
250
|
+
options: {},
|
|
251
|
+
cwd: process.cwd(),
|
|
252
|
+
storage: new CompositeStorage(pluginName, CONFIG_DIR, "xbrowser"),
|
|
253
|
+
output: { mode: "text", showTips: false, color: false, emoji: false },
|
|
254
|
+
error: (msg) => {
|
|
255
|
+
throw new Error(msg);
|
|
256
|
+
},
|
|
257
|
+
config: {},
|
|
258
|
+
site: new NoopSiteInstance(),
|
|
259
|
+
cliName: "xbrowser",
|
|
260
|
+
tips: new TipCollector()
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
183
264
|
// src/commands/navigation.ts
|
|
184
265
|
import { z } from "zod";
|
|
185
266
|
import { ok } from "@dyyz1993/xcli-core";
|
|
@@ -227,7 +308,7 @@ async function detectSsr(page) {
|
|
|
227
308
|
try {
|
|
228
309
|
const result = await page.evaluate((vars) => {
|
|
229
310
|
for (const varName of vars) {
|
|
230
|
-
const value = window
|
|
311
|
+
const value = Reflect.get(window, varName);
|
|
231
312
|
if (value != null && typeof value === "object") {
|
|
232
313
|
const keys = Object.keys(value).slice(0, 10);
|
|
233
314
|
return { variable: varName, keys };
|
|
@@ -324,10 +405,18 @@ var urlCommand = registerCommand({
|
|
|
324
405
|
return ok({ url: ctx.page.url() });
|
|
325
406
|
}
|
|
326
407
|
});
|
|
408
|
+
registerCommand({
|
|
409
|
+
name: "open",
|
|
410
|
+
description: "Navigate to URL (alias for goto)",
|
|
411
|
+
scope: "page",
|
|
412
|
+
parameters: gotoCommand.parameters,
|
|
413
|
+
result: gotoCommand.result,
|
|
414
|
+
handler: gotoCommand.handler
|
|
415
|
+
});
|
|
327
416
|
|
|
328
417
|
// src/commands/interaction.ts
|
|
329
418
|
import { z as z2 } from "zod";
|
|
330
|
-
import { ok as ok2 } from "@dyyz1993/xcli-core";
|
|
419
|
+
import { ok as ok2, normalizeTips } from "@dyyz1993/xcli-core";
|
|
331
420
|
|
|
332
421
|
// src/lib/captcha.ts
|
|
333
422
|
var CAPTCHA_SELECTORS = {
|
|
@@ -395,15 +484,15 @@ var clickCommand = registerCommand({
|
|
|
395
484
|
let detectedNewPage;
|
|
396
485
|
let cleanup;
|
|
397
486
|
if (ctx.browserContext?.on) {
|
|
398
|
-
const pagePromise = new Promise((
|
|
487
|
+
const pagePromise = new Promise((resolve10) => {
|
|
399
488
|
const timer = setTimeout(() => {
|
|
400
489
|
ctx.browserContext.off("page", handler);
|
|
401
|
-
|
|
490
|
+
resolve10(void 0);
|
|
402
491
|
}, 3e3);
|
|
403
492
|
const handler = (page2) => {
|
|
404
493
|
clearTimeout(timer);
|
|
405
494
|
ctx.browserContext.off("page", handler);
|
|
406
|
-
|
|
495
|
+
resolve10(page2);
|
|
407
496
|
};
|
|
408
497
|
ctx.browserContext.on("page", handler);
|
|
409
498
|
});
|
|
@@ -438,7 +527,7 @@ var clickCommand = registerCommand({
|
|
|
438
527
|
selector: p.selector,
|
|
439
528
|
newTab: { url: newUrl, title: newTitle }
|
|
440
529
|
});
|
|
441
|
-
result.tips = [`\u65B0 Tab \u5DF2\u6253\u5F00: ${newTitle ? newTitle + " \u2014 " : ""}${newUrl}`];
|
|
530
|
+
result.tips = normalizeTips([`\u65B0 Tab \u5DF2\u6253\u5F00: ${newTitle ? newTitle + " \u2014 " : ""}${newUrl}`]);
|
|
442
531
|
return result;
|
|
443
532
|
}
|
|
444
533
|
const captchaInfo = await detectCaptcha(page);
|
|
@@ -452,7 +541,7 @@ var clickCommand = registerCommand({
|
|
|
452
541
|
tips.push(solved ? "\u2705 CAPTCHA solved!" : "\u274C CAPTCHA timeout");
|
|
453
542
|
}
|
|
454
543
|
const result = ok2({ selector: p.selector, captcha: captchaInfo });
|
|
455
|
-
result.tips = tips;
|
|
544
|
+
result.tips = normalizeTips(tips);
|
|
456
545
|
return result;
|
|
457
546
|
}
|
|
458
547
|
return ok2({ selector: p.selector });
|
|
@@ -787,7 +876,7 @@ var mouseCommand = registerCommand({
|
|
|
787
876
|
|
|
788
877
|
// src/commands/evaluate.ts
|
|
789
878
|
import { z as z7 } from "zod";
|
|
790
|
-
import { ok as ok7 } from "@dyyz1993/xcli-core";
|
|
879
|
+
import { ok as ok7, normalizeTips as normalizeTips2 } from "@dyyz1993/xcli-core";
|
|
791
880
|
var evaluateCommand = registerCommand({
|
|
792
881
|
name: "eval",
|
|
793
882
|
description: "Evaluate JavaScript expression in the browser",
|
|
@@ -808,10 +897,10 @@ var evaluateCommand = registerCommand({
|
|
|
808
897
|
const result = await ctx.page.evaluate(p.expression);
|
|
809
898
|
const response = ok7({ result });
|
|
810
899
|
if (decision && decision.severity === "danger") {
|
|
811
|
-
response.tips = [
|
|
900
|
+
response.tips = normalizeTips2([
|
|
812
901
|
`\u26A0\uFE0F CDP Firewall: ${decision.reason}`,
|
|
813
902
|
`\u{1F4A1} Fix: ${decision.suggestion}`
|
|
814
|
-
];
|
|
903
|
+
]);
|
|
815
904
|
}
|
|
816
905
|
return response;
|
|
817
906
|
}
|
|
@@ -934,7 +1023,19 @@ var clearLocalStorageCommand = registerCommand({
|
|
|
934
1023
|
// src/commands/screenshot.ts
|
|
935
1024
|
import { z as z9 } from "zod";
|
|
936
1025
|
import { ok as ok9 } from "@dyyz1993/xcli-core";
|
|
937
|
-
import { writeFileSync } from "fs";
|
|
1026
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
1027
|
+
import { join } from "path";
|
|
1028
|
+
import { homedir } from "os";
|
|
1029
|
+
var SCREENSHOTS_DIR = join(homedir(), ".xbrowser", "screenshots");
|
|
1030
|
+
function ensureScreenshotsDir() {
|
|
1031
|
+
mkdirSync(SCREENSHOTS_DIR, { recursive: true });
|
|
1032
|
+
}
|
|
1033
|
+
function generateScreenshotPath(format) {
|
|
1034
|
+
const timestamp = Date.now();
|
|
1035
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
1036
|
+
const ext = format === "jpeg" ? "jpg" : "png";
|
|
1037
|
+
return join(SCREENSHOTS_DIR, `screenshot-${timestamp}-${random}.${ext}`);
|
|
1038
|
+
}
|
|
938
1039
|
var screenshotCommand = registerCommand({
|
|
939
1040
|
name: "screenshot",
|
|
940
1041
|
description: "Take a screenshot of the page or element",
|
|
@@ -944,7 +1045,8 @@ var screenshotCommand = registerCommand({
|
|
|
944
1045
|
selector: z9.string().optional(),
|
|
945
1046
|
type: z9.enum(["png", "jpeg"]).optional(),
|
|
946
1047
|
fullPage: z9.boolean().optional(),
|
|
947
|
-
output: z9.string().optional()
|
|
1048
|
+
output: z9.string().optional(),
|
|
1049
|
+
base64: z9.boolean().optional().describe("Return base64 data instead of file path")
|
|
948
1050
|
}),
|
|
949
1051
|
result: z9.union([
|
|
950
1052
|
z9.object({
|
|
@@ -959,8 +1061,9 @@ var screenshotCommand = registerCommand({
|
|
|
959
1061
|
})
|
|
960
1062
|
]),
|
|
961
1063
|
handler: async (p, ctx) => {
|
|
1064
|
+
const format = p.type || "png";
|
|
962
1065
|
const options = {
|
|
963
|
-
type:
|
|
1066
|
+
type: format,
|
|
964
1067
|
fullPage: p.fullPage || false
|
|
965
1068
|
};
|
|
966
1069
|
let buffer;
|
|
@@ -973,13 +1076,23 @@ var screenshotCommand = registerCommand({
|
|
|
973
1076
|
writeFileSync(p.output, buffer);
|
|
974
1077
|
return ok9({
|
|
975
1078
|
output: p.output,
|
|
976
|
-
format
|
|
1079
|
+
format,
|
|
1080
|
+
size: buffer.length
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
if (p.base64) {
|
|
1084
|
+
return ok9({
|
|
1085
|
+
data: buffer.toString("base64"),
|
|
1086
|
+
format,
|
|
977
1087
|
size: buffer.length
|
|
978
1088
|
});
|
|
979
1089
|
}
|
|
1090
|
+
ensureScreenshotsDir();
|
|
1091
|
+
const screenshotPath = generateScreenshotPath(format);
|
|
1092
|
+
writeFileSync(screenshotPath, buffer);
|
|
980
1093
|
return ok9({
|
|
981
|
-
|
|
982
|
-
format
|
|
1094
|
+
output: screenshotPath,
|
|
1095
|
+
format,
|
|
983
1096
|
size: buffer.length
|
|
984
1097
|
});
|
|
985
1098
|
}
|
|
@@ -1161,7 +1274,7 @@ var consoleCheckCommand = registerCommand({
|
|
|
1161
1274
|
await page.goto(p.url, { waitUntil: "domcontentloaded" });
|
|
1162
1275
|
}
|
|
1163
1276
|
const messages = await page.evaluate((args) => {
|
|
1164
|
-
return new Promise((
|
|
1277
|
+
return new Promise((resolve10) => {
|
|
1165
1278
|
const collected = [];
|
|
1166
1279
|
const originalConsole = {
|
|
1167
1280
|
log: console.log,
|
|
@@ -1228,7 +1341,7 @@ ${a.stack || ""}`;
|
|
|
1228
1341
|
console.warn = originalConsole.warn;
|
|
1229
1342
|
console.error = originalConsole.error;
|
|
1230
1343
|
console.info = originalConsole.info;
|
|
1231
|
-
|
|
1344
|
+
resolve10(collected);
|
|
1232
1345
|
}, args.duration);
|
|
1233
1346
|
});
|
|
1234
1347
|
}, { duration: p.duration });
|
|
@@ -1544,7 +1657,7 @@ var healthCheckCommand = registerCommand({
|
|
|
1544
1657
|
issues.push({
|
|
1545
1658
|
severity: "error",
|
|
1546
1659
|
category: "links",
|
|
1547
|
-
message: `Broken link (fetch error): ${href} \u2014 ${err
|
|
1660
|
+
message: `Broken link (fetch error): ${href} \u2014 ${errMsg(err) || "unknown"}`
|
|
1548
1661
|
});
|
|
1549
1662
|
}
|
|
1550
1663
|
}
|
|
@@ -1577,6 +1690,19 @@ var healthCheckCommand = registerCommand({
|
|
|
1577
1690
|
// src/commands/actions.ts
|
|
1578
1691
|
import { z as z14 } from "zod";
|
|
1579
1692
|
import { ok as ok14 } from "@dyyz1993/xcli-core";
|
|
1693
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1694
|
+
import { join as join2 } from "path";
|
|
1695
|
+
import { homedir as homedir2 } from "os";
|
|
1696
|
+
var SCREENSHOTS_DIR2 = join2(homedir2(), ".xbrowser", "screenshots");
|
|
1697
|
+
function ensureScreenshotsDir2() {
|
|
1698
|
+
mkdirSync2(SCREENSHOTS_DIR2, { recursive: true });
|
|
1699
|
+
}
|
|
1700
|
+
function generateScreenshotPath2(format) {
|
|
1701
|
+
const timestamp = Date.now();
|
|
1702
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
1703
|
+
const ext = format === "jpeg" ? "jpg" : "png";
|
|
1704
|
+
return join2(SCREENSHOTS_DIR2, `screenshot-${timestamp}-${random}.${ext}`);
|
|
1705
|
+
}
|
|
1580
1706
|
var waitActionSchema = z14.object({
|
|
1581
1707
|
type: z14.literal("wait"),
|
|
1582
1708
|
milliseconds: z14.number().positive().optional(),
|
|
@@ -1594,7 +1720,8 @@ var screenshotActionSchema = z14.object({
|
|
|
1594
1720
|
type: z14.literal("screenshot"),
|
|
1595
1721
|
fullPage: z14.boolean().optional(),
|
|
1596
1722
|
quality: z14.number().min(1).max(100).optional(),
|
|
1597
|
-
viewport: z14.object({ width: z14.number().int().positive(), height: z14.number().int().positive() }).optional()
|
|
1723
|
+
viewport: z14.object({ width: z14.number().int().positive(), height: z14.number().int().positive() }).optional(),
|
|
1724
|
+
base64: z14.boolean().optional().describe("Return base64 data instead of file path")
|
|
1598
1725
|
});
|
|
1599
1726
|
var writeActionSchema = z14.object({
|
|
1600
1727
|
type: z14.literal("write"),
|
|
@@ -1640,7 +1767,7 @@ async function executeAction(page, action) {
|
|
|
1640
1767
|
if (action.selector) {
|
|
1641
1768
|
await page.waitForSelector(action.selector, { timeout: 3e4 });
|
|
1642
1769
|
} else if (action.milliseconds) {
|
|
1643
|
-
await new Promise((
|
|
1770
|
+
await new Promise((resolve10) => setTimeout(resolve10, action.milliseconds));
|
|
1644
1771
|
} else {
|
|
1645
1772
|
throw new Error("wait action requires either milliseconds or selector");
|
|
1646
1773
|
}
|
|
@@ -1661,7 +1788,13 @@ async function executeAction(page, action) {
|
|
|
1661
1788
|
quality: action.quality ?? 80,
|
|
1662
1789
|
...action.viewport ? { clip: { x: 0, y: 0, ...action.viewport } } : {}
|
|
1663
1790
|
});
|
|
1664
|
-
|
|
1791
|
+
if (action.base64) {
|
|
1792
|
+
return { type: "screenshot", result: buf.toString("base64"), base64: true };
|
|
1793
|
+
}
|
|
1794
|
+
ensureScreenshotsDir2();
|
|
1795
|
+
const screenshotPath = generateScreenshotPath2("jpg");
|
|
1796
|
+
writeFileSync2(screenshotPath, buf);
|
|
1797
|
+
return { type: "screenshot", result: screenshotPath };
|
|
1665
1798
|
}
|
|
1666
1799
|
case "write":
|
|
1667
1800
|
await page.keyboard.type(action.text);
|
|
@@ -1723,8 +1856,8 @@ var actionsCommand = registerCommand({
|
|
|
1723
1856
|
results.push(result);
|
|
1724
1857
|
}
|
|
1725
1858
|
})();
|
|
1726
|
-
const timeoutPromise = new Promise((
|
|
1727
|
-
setTimeout(
|
|
1859
|
+
const timeoutPromise = new Promise((resolve10) => {
|
|
1860
|
+
setTimeout(resolve10, timeoutMs);
|
|
1728
1861
|
});
|
|
1729
1862
|
await Promise.race([executionPromise, timeoutPromise]);
|
|
1730
1863
|
const title = await ctx.page.title();
|
|
@@ -2398,11 +2531,11 @@ async function navigateForMap(page, url, timeout = 15e3) {
|
|
|
2398
2531
|
}
|
|
2399
2532
|
async function extractPageLinks(page, baseUrl) {
|
|
2400
2533
|
await navigateForMap(page, baseUrl);
|
|
2401
|
-
await new Promise((
|
|
2534
|
+
await new Promise((resolve10) => setTimeout(resolve10, 2e3));
|
|
2402
2535
|
await page.evaluate(() => {
|
|
2403
2536
|
window.scrollTo(0, document.body.scrollHeight);
|
|
2404
2537
|
});
|
|
2405
|
-
await new Promise((
|
|
2538
|
+
await new Promise((resolve10) => setTimeout(resolve10, 1e3));
|
|
2406
2539
|
const origin = new URL(baseUrl).origin;
|
|
2407
2540
|
const rawLinks = await page.evaluate((evalOrigin) => {
|
|
2408
2541
|
return Array.from(document.querySelectorAll("a[href]")).map((a) => {
|
|
@@ -3828,7 +3961,7 @@ var ENGINE_KEY_ENUM = z20.enum(ALL_ENGINE_KEYS);
|
|
|
3828
3961
|
|
|
3829
3962
|
// src/commands/snapshot.ts
|
|
3830
3963
|
import { z as z21 } from "zod";
|
|
3831
|
-
import { ok as ok20, fail as fail4 } from "@dyyz1993/xcli-core";
|
|
3964
|
+
import { ok as ok20, fail as fail4, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
|
|
3832
3965
|
|
|
3833
3966
|
// src/runtime/ref-store.ts
|
|
3834
3967
|
var sessions = /* @__PURE__ */ new Map();
|
|
@@ -4069,9 +4202,9 @@ async function resolveRefParams(page, params, selectorKeys, cache, sessionId) {
|
|
|
4069
4202
|
}
|
|
4070
4203
|
|
|
4071
4204
|
// src/utils/site-semantics.ts
|
|
4072
|
-
import { writeFileSync as
|
|
4073
|
-
import { join, dirname } from "path";
|
|
4074
|
-
import { homedir } from "os";
|
|
4205
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync, readFileSync } from "fs";
|
|
4206
|
+
import { join as join3, dirname } from "path";
|
|
4207
|
+
import { homedir as homedir3 } from "os";
|
|
4075
4208
|
import { stringify, parse } from "yaml";
|
|
4076
4209
|
import { execFile } from "child_process";
|
|
4077
4210
|
var INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
|
|
@@ -4148,10 +4281,10 @@ function inferAction(role, label) {
|
|
|
4148
4281
|
return {};
|
|
4149
4282
|
}
|
|
4150
4283
|
function getSemanticsDir() {
|
|
4151
|
-
return
|
|
4284
|
+
return join3(homedir3(), ".xbrowser", "site-semantics");
|
|
4152
4285
|
}
|
|
4153
4286
|
function getSemanticsPath(domain) {
|
|
4154
|
-
return
|
|
4287
|
+
return join3(getSemanticsDir(), `${domain}.yaml`);
|
|
4155
4288
|
}
|
|
4156
4289
|
function extractDomain2(url) {
|
|
4157
4290
|
try {
|
|
@@ -4188,9 +4321,9 @@ function saveSemantics(domain, pagePath, url, elements) {
|
|
|
4188
4321
|
}
|
|
4189
4322
|
site.updated_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4190
4323
|
if (!existsSync(dir)) {
|
|
4191
|
-
|
|
4324
|
+
mkdirSync3(dir, { recursive: true });
|
|
4192
4325
|
}
|
|
4193
|
-
|
|
4326
|
+
writeFileSync3(filePath, stringify(site, { lineWidth: 0 }));
|
|
4194
4327
|
}
|
|
4195
4328
|
function loadSemantics(domain) {
|
|
4196
4329
|
const filePath = getSemanticsPath(domain);
|
|
@@ -4260,25 +4393,25 @@ aria snapshot\uFF1A
|
|
|
4260
4393
|
async function analyzeWithLLM(ariaSnapshot) {
|
|
4261
4394
|
const piBin = process.env.PI_CLI_PATH || "pi";
|
|
4262
4395
|
const prompt = LLM_PROMPT.replace("{snapshot}", ariaSnapshot.slice(0, 4e3));
|
|
4263
|
-
return new Promise((
|
|
4396
|
+
return new Promise((resolve10) => {
|
|
4264
4397
|
execFile(
|
|
4265
4398
|
piBin,
|
|
4266
4399
|
["--provider", LLM_PROVIDER, "--model", LLM_MODEL, prompt],
|
|
4267
4400
|
{ timeout: LLM_TIMEOUT_MS, maxBuffer: 1024 * 1024 },
|
|
4268
4401
|
(err, stdout, _stderr) => {
|
|
4269
4402
|
if (err) {
|
|
4270
|
-
|
|
4403
|
+
resolve10(null);
|
|
4271
4404
|
return;
|
|
4272
4405
|
}
|
|
4273
4406
|
const output = (stdout || "").trim();
|
|
4274
4407
|
if (!output) {
|
|
4275
|
-
|
|
4408
|
+
resolve10(null);
|
|
4276
4409
|
return;
|
|
4277
4410
|
}
|
|
4278
4411
|
try {
|
|
4279
4412
|
const parsed = parse(output);
|
|
4280
4413
|
if (!parsed || typeof parsed !== "object") {
|
|
4281
|
-
|
|
4414
|
+
resolve10(null);
|
|
4282
4415
|
return;
|
|
4283
4416
|
}
|
|
4284
4417
|
const elements = {};
|
|
@@ -4293,9 +4426,9 @@ async function analyzeWithLLM(ariaSnapshot) {
|
|
|
4293
4426
|
};
|
|
4294
4427
|
}
|
|
4295
4428
|
}
|
|
4296
|
-
|
|
4429
|
+
resolve10(Object.keys(elements).length > 0 ? elements : null);
|
|
4297
4430
|
} catch {
|
|
4298
|
-
|
|
4431
|
+
resolve10(null);
|
|
4299
4432
|
}
|
|
4300
4433
|
}
|
|
4301
4434
|
);
|
|
@@ -4623,7 +4756,7 @@ async function actOnPage(page, sessionId, input) {
|
|
|
4623
4756
|
ref: normalizedRef,
|
|
4624
4757
|
success: false,
|
|
4625
4758
|
reason: "browser_error",
|
|
4626
|
-
message: error
|
|
4759
|
+
message: errMsg(error),
|
|
4627
4760
|
stale,
|
|
4628
4761
|
screenHash: hash,
|
|
4629
4762
|
target: refMatch?.target
|
|
@@ -4650,7 +4783,7 @@ async function pollUntil(timeout, pollInterval, predicate) {
|
|
|
4650
4783
|
const startedAt = Date.now();
|
|
4651
4784
|
while (Date.now() - startedAt <= timeout) {
|
|
4652
4785
|
if (await predicate()) return true;
|
|
4653
|
-
await new Promise((
|
|
4786
|
+
await new Promise((resolve10) => setTimeout(resolve10, pollInterval));
|
|
4654
4787
|
}
|
|
4655
4788
|
return false;
|
|
4656
4789
|
}
|
|
@@ -4707,7 +4840,7 @@ async function waitForPage(page, input) {
|
|
|
4707
4840
|
matched: input.selector ? "selector" : input.text ? "text" : input.url ? "url" : input.load ? "load" : input.fn ? "fn" : "screenHashChanged",
|
|
4708
4841
|
timeout,
|
|
4709
4842
|
elapsed: Date.now() - startedAt,
|
|
4710
|
-
message: error
|
|
4843
|
+
message: errMsg(error)
|
|
4711
4844
|
};
|
|
4712
4845
|
}
|
|
4713
4846
|
return {
|
|
@@ -4732,7 +4865,7 @@ var snapshotCommand = registerCommand({
|
|
|
4732
4865
|
interactive: z21.boolean().optional().default(false).describe("Return interactive agent refs only"),
|
|
4733
4866
|
interactiveOnly: z21.boolean().optional().default(false).describe("Alias for interactive"),
|
|
4734
4867
|
i: z21.boolean().optional().default(false).describe("Short alias for interactive"),
|
|
4735
|
-
compact: z21.boolean().optional().default(false).describe("Include compact
|
|
4868
|
+
compact: z21.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
|
|
4736
4869
|
c: z21.boolean().optional().default(false).describe("Short alias for compact"),
|
|
4737
4870
|
selectors: z21.boolean().optional().default(false).describe("Include ref to CSS selector map"),
|
|
4738
4871
|
all: z21.boolean().optional().default(false).describe("Include hidden interactive targets when using interactive snapshot")
|
|
@@ -4756,15 +4889,15 @@ var snapshotCommand = registerCommand({
|
|
|
4756
4889
|
if (p.compact || p.c || p.interactive || p.interactiveOnly || p.i) {
|
|
4757
4890
|
observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
4758
4891
|
}
|
|
4759
|
-
return ok20(observation, [
|
|
4892
|
+
return ok20(observation, normalizeTips3([
|
|
4760
4893
|
`refs refreshed for ${observation.targets.length} targets; use click @e1 or fill @e2 "text"`
|
|
4761
|
-
]);
|
|
4894
|
+
]));
|
|
4762
4895
|
}
|
|
4763
4896
|
if (p.type === "aria") {
|
|
4764
4897
|
const aria = await captureAriaSnapshot(page, p.selector, p.depth);
|
|
4765
4898
|
const tips = await buildRefTips(page, aria);
|
|
4766
4899
|
persistSemantics(url, aria);
|
|
4767
|
-
return ok20({ url, title, aria }, tips);
|
|
4900
|
+
return ok20({ url, title, aria }, normalizeTips3(tips));
|
|
4768
4901
|
}
|
|
4769
4902
|
if (p.type === "text") {
|
|
4770
4903
|
const text = await captureTextSnapshot(page, p.selector);
|
|
@@ -4782,7 +4915,7 @@ var snapshotCommand = registerCommand({
|
|
|
4782
4915
|
]);
|
|
4783
4916
|
const tips = await buildRefTips(page, aria);
|
|
4784
4917
|
persistSemantics(url, aria);
|
|
4785
|
-
return ok20({ url, title, aria, text, dom }, tips);
|
|
4918
|
+
return ok20({ url, title, aria, text, dom }, normalizeTips3(tips));
|
|
4786
4919
|
}
|
|
4787
4920
|
return fail4(`Unknown snapshot type: ${p.type}`);
|
|
4788
4921
|
}
|
|
@@ -4862,7 +4995,7 @@ async function captureDomSnapshot(page, selector, maxDepth) {
|
|
|
4862
4995
|
|
|
4863
4996
|
// src/commands/agent.ts
|
|
4864
4997
|
import { z as z22 } from "zod";
|
|
4865
|
-
import { ok as ok21 } from "@dyyz1993/xcli-core";
|
|
4998
|
+
import { ok as ok21, normalizeTips as normalizeTips4 } from "@dyyz1993/xcli-core";
|
|
4866
4999
|
var observeCommand = registerCommand({
|
|
4867
5000
|
name: "observe",
|
|
4868
5001
|
description: "Observe the current page as structured agent targets with session refs",
|
|
@@ -4870,9 +5003,14 @@ var observeCommand = registerCommand({
|
|
|
4870
5003
|
parameters: z22.object({
|
|
4871
5004
|
includeHidden: z22.boolean().optional().default(false).describe("Include hidden elements in the target list"),
|
|
4872
5005
|
limit: z22.number().int().positive().max(300).optional().default(80).describe("Maximum number of targets to return"),
|
|
4873
|
-
compact: z22.boolean().optional().default(false).describe("Include compact
|
|
5006
|
+
compact: z22.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
|
|
4874
5007
|
selectors: z22.boolean().optional().default(false).describe("Include ref to stable CSS selector map")
|
|
4875
5008
|
}),
|
|
5009
|
+
result: z22.object({
|
|
5010
|
+
targets: z22.array(z22.record(z22.unknown())),
|
|
5011
|
+
selectors: z22.record(z22.unknown()).optional(),
|
|
5012
|
+
compact: z22.string().optional()
|
|
5013
|
+
}).passthrough(),
|
|
4876
5014
|
handler: async (p, ctx) => {
|
|
4877
5015
|
const observation = await observePage(ctx.page, ctx.sessionId, {
|
|
4878
5016
|
includeHidden: p.includeHidden,
|
|
@@ -4880,9 +5018,9 @@ var observeCommand = registerCommand({
|
|
|
4880
5018
|
});
|
|
4881
5019
|
if (p.selectors) observation.selectors = buildSelectorMap(observation);
|
|
4882
5020
|
if (p.compact) observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
4883
|
-
return ok21(observation, [
|
|
5021
|
+
return ok21(observation, normalizeTips4([
|
|
4884
5022
|
`refs refreshed for ${observation.targets.length} targets; use act --ref @e1 --action click or click @e1`
|
|
4885
|
-
]);
|
|
5023
|
+
]));
|
|
4886
5024
|
}
|
|
4887
5025
|
});
|
|
4888
5026
|
var actCommand = registerCommand({
|
|
@@ -4908,10 +5046,10 @@ var actCommand = registerCommand({
|
|
|
4908
5046
|
success: false,
|
|
4909
5047
|
data: result,
|
|
4910
5048
|
message: result.message || result.reason || "Action failed",
|
|
4911
|
-
tips: result.stale ? ["run observe again to refresh refs"] : []
|
|
5049
|
+
tips: normalizeTips4(result.stale ? ["run observe again to refresh refs"] : [])
|
|
4912
5050
|
};
|
|
4913
5051
|
}
|
|
4914
|
-
return ok21(result, result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []);
|
|
5052
|
+
return ok21(result, normalizeTips4(result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []));
|
|
4915
5053
|
}
|
|
4916
5054
|
});
|
|
4917
5055
|
var waitForCommand = registerCommand({
|
|
@@ -5087,7 +5225,8 @@ import { ok as ok23 } from "@dyyz1993/xcli-core";
|
|
|
5087
5225
|
import { readFileSync as readFileSync2 } from "fs";
|
|
5088
5226
|
|
|
5089
5227
|
// src/chain-parser.ts
|
|
5090
|
-
import {
|
|
5228
|
+
import { splitCommand, parseCommandArgs } from "@dyyz1993/xcli-core";
|
|
5229
|
+
import { registerCommandDefinition } from "@dyyz1993/xcli-core";
|
|
5091
5230
|
function parseCommandChain(input, options) {
|
|
5092
5231
|
const result = [];
|
|
5093
5232
|
let currentPipeline = [];
|
|
@@ -5178,149 +5317,46 @@ function isSpaceAround(input, pos, tokenLen) {
|
|
|
5178
5317
|
const after = pos + tokenLen < input.length && input[pos + tokenLen] === " ";
|
|
5179
5318
|
return before && after;
|
|
5180
5319
|
}
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
if (v.startsWith("[") || v.startsWith("{")) {
|
|
5222
|
-
try {
|
|
5223
|
-
return JSON.parse(v);
|
|
5224
|
-
} catch {
|
|
5225
|
-
return v;
|
|
5226
|
-
}
|
|
5227
|
-
}
|
|
5228
|
-
return v;
|
|
5229
|
-
}
|
|
5230
|
-
function parseCommandArgs(name, args) {
|
|
5231
|
-
const definitions = getCommandDefinitions();
|
|
5232
|
-
const def = definitions[name];
|
|
5233
|
-
const positionalKeys = def ? def.positional : [];
|
|
5234
|
-
const params = {};
|
|
5235
|
-
let positionalIndex = 0;
|
|
5236
|
-
for (let i = 0; i < args.length; i++) {
|
|
5237
|
-
const raw = args[i];
|
|
5238
|
-
const arg = unquote2(raw);
|
|
5239
|
-
if (raw.startsWith("--")) {
|
|
5240
|
-
const key = raw.slice(2);
|
|
5241
|
-
const value = args[i + 1];
|
|
5242
|
-
if (value && !value.startsWith("-")) {
|
|
5243
|
-
params[key] = coerceValue2(value);
|
|
5244
|
-
i++;
|
|
5245
|
-
} else {
|
|
5246
|
-
params[key] = true;
|
|
5247
|
-
}
|
|
5248
|
-
} else if (raw.startsWith("-") && raw.length === 2) {
|
|
5249
|
-
const flag = raw[1];
|
|
5250
|
-
const mappedKey = SHORT_FLAG_MAP[flag];
|
|
5251
|
-
const value = args[i + 1];
|
|
5252
|
-
if (mappedKey && value && !value.startsWith("-")) {
|
|
5253
|
-
params[mappedKey] = coerceValue2(value);
|
|
5254
|
-
i++;
|
|
5255
|
-
} else if (value && !value.startsWith("-")) {
|
|
5256
|
-
params[flag] = coerceValue2(value);
|
|
5257
|
-
i++;
|
|
5258
|
-
} else {
|
|
5259
|
-
params[mappedKey || flag] = true;
|
|
5260
|
-
}
|
|
5261
|
-
} else {
|
|
5262
|
-
if (positionalIndex < positionalKeys.length) {
|
|
5263
|
-
const isLast = positionalIndex === positionalKeys.length - 1;
|
|
5264
|
-
if (isLast && (name === "eval" || name === "find")) {
|
|
5265
|
-
const remaining = args.slice(i).map(unquote2).join(" ");
|
|
5266
|
-
params[positionalKeys[positionalIndex]] = remaining;
|
|
5267
|
-
break;
|
|
5268
|
-
}
|
|
5269
|
-
params[positionalKeys[positionalIndex]] = arg;
|
|
5270
|
-
positionalIndex++;
|
|
5271
|
-
}
|
|
5272
|
-
}
|
|
5273
|
-
}
|
|
5274
|
-
return { command: name, params };
|
|
5275
|
-
}
|
|
5276
|
-
var commandDefCache = {
|
|
5277
|
-
goto: { positional: ["url"] },
|
|
5278
|
-
click: { positional: ["selector"] },
|
|
5279
|
-
fill: { positional: ["selector", "value"] },
|
|
5280
|
-
type: { positional: ["selector", "text"] },
|
|
5281
|
-
press: { positional: ["selector", "key"] },
|
|
5282
|
-
select: { positional: ["selector", "value"] },
|
|
5283
|
-
check: { positional: ["selector"] },
|
|
5284
|
-
uncheck: { positional: ["selector"] },
|
|
5285
|
-
hover: { positional: ["selector"] },
|
|
5286
|
-
dblclick: { positional: ["selector"] },
|
|
5287
|
-
wait: { positional: ["selector"] },
|
|
5288
|
-
screenshot: { positional: [] },
|
|
5289
|
-
eval: { positional: ["expression"] },
|
|
5290
|
-
scroll: { positional: ["direction"] },
|
|
5291
|
-
title: { positional: [] },
|
|
5292
|
-
url: { positional: [] },
|
|
5293
|
-
html: { positional: [] },
|
|
5294
|
-
text: { positional: [] },
|
|
5295
|
-
back: { positional: [] },
|
|
5296
|
-
forward: { positional: [] },
|
|
5297
|
-
refresh: { positional: [] },
|
|
5298
|
-
console: { positional: [] },
|
|
5299
|
-
network: { positional: [] },
|
|
5300
|
-
perf: { positional: [] },
|
|
5301
|
-
health: { positional: [] },
|
|
5302
|
-
scrape: { positional: ["url"] },
|
|
5303
|
-
structure: { positional: [] },
|
|
5304
|
-
"get-cookies": { positional: [] },
|
|
5305
|
-
"set-cookie": { positional: [] },
|
|
5306
|
-
"clear-cookies": { positional: [] },
|
|
5307
|
-
"get-local-storage": { positional: [] },
|
|
5308
|
-
"set-local-storage": { positional: [] },
|
|
5309
|
-
"clear-local-storage": { positional: [] },
|
|
5310
|
-
"set-viewport": { positional: [] },
|
|
5311
|
-
frames: { positional: [] },
|
|
5312
|
-
frame: { positional: ["selector"] },
|
|
5313
|
-
actions: { positional: ["url"] },
|
|
5314
|
-
find: { positional: ["strategy", "value", "operation"] },
|
|
5315
|
-
addinitscript: { positional: ["script"] },
|
|
5316
|
-
tab: { positional: ["subcommand"] }
|
|
5317
|
-
};
|
|
5318
|
-
function getCommandDefinitions() {
|
|
5319
|
-
return commandDefCache;
|
|
5320
|
-
}
|
|
5321
|
-
function registerCommandDefinition(name, positional) {
|
|
5322
|
-
commandDefCache[name] = { positional };
|
|
5323
|
-
}
|
|
5320
|
+
registerCommandDefinition("goto", ["url"]);
|
|
5321
|
+
registerCommandDefinition("click", ["selector"]);
|
|
5322
|
+
registerCommandDefinition("fill", ["selector", "value"]);
|
|
5323
|
+
registerCommandDefinition("type", ["selector", "text"]);
|
|
5324
|
+
registerCommandDefinition("press", ["selector", "key"]);
|
|
5325
|
+
registerCommandDefinition("select", ["selector", "value"]);
|
|
5326
|
+
registerCommandDefinition("check", ["selector"]);
|
|
5327
|
+
registerCommandDefinition("uncheck", ["selector"]);
|
|
5328
|
+
registerCommandDefinition("hover", ["selector"]);
|
|
5329
|
+
registerCommandDefinition("dblclick", ["selector"]);
|
|
5330
|
+
registerCommandDefinition("wait", ["selector"]);
|
|
5331
|
+
registerCommandDefinition("screenshot", []);
|
|
5332
|
+
registerCommandDefinition("eval", ["expression"]);
|
|
5333
|
+
registerCommandDefinition("scroll", ["direction"]);
|
|
5334
|
+
registerCommandDefinition("title", []);
|
|
5335
|
+
registerCommandDefinition("url", []);
|
|
5336
|
+
registerCommandDefinition("html", []);
|
|
5337
|
+
registerCommandDefinition("text", []);
|
|
5338
|
+
registerCommandDefinition("back", []);
|
|
5339
|
+
registerCommandDefinition("forward", []);
|
|
5340
|
+
registerCommandDefinition("refresh", []);
|
|
5341
|
+
registerCommandDefinition("console", []);
|
|
5342
|
+
registerCommandDefinition("network", []);
|
|
5343
|
+
registerCommandDefinition("perf", []);
|
|
5344
|
+
registerCommandDefinition("health", []);
|
|
5345
|
+
registerCommandDefinition("scrape", ["url"]);
|
|
5346
|
+
registerCommandDefinition("structure", []);
|
|
5347
|
+
registerCommandDefinition("get-cookies", []);
|
|
5348
|
+
registerCommandDefinition("set-cookie", []);
|
|
5349
|
+
registerCommandDefinition("clear-cookies", []);
|
|
5350
|
+
registerCommandDefinition("get-local-storage", []);
|
|
5351
|
+
registerCommandDefinition("set-local-storage", []);
|
|
5352
|
+
registerCommandDefinition("clear-local-storage", []);
|
|
5353
|
+
registerCommandDefinition("set-viewport", []);
|
|
5354
|
+
registerCommandDefinition("frames", []);
|
|
5355
|
+
registerCommandDefinition("frame", ["selector"]);
|
|
5356
|
+
registerCommandDefinition("actions", ["url"]);
|
|
5357
|
+
registerCommandDefinition("find", ["strategy", "value", "operation"]);
|
|
5358
|
+
registerCommandDefinition("addinitscript", ["script"]);
|
|
5359
|
+
registerCommandDefinition("tab", ["subcommand"]);
|
|
5324
5360
|
|
|
5325
5361
|
// src/commands/addinitscript.ts
|
|
5326
5362
|
var InitScriptParams = z24.object({
|
|
@@ -5350,12 +5386,12 @@ function resolveScriptContent(params) {
|
|
|
5350
5386
|
}
|
|
5351
5387
|
async function readStdin() {
|
|
5352
5388
|
const { createReadStream } = await import("fs");
|
|
5353
|
-
const { createInterface
|
|
5354
|
-
return new Promise((
|
|
5389
|
+
const { createInterface } = await import("readline");
|
|
5390
|
+
return new Promise((resolve10, reject) => {
|
|
5355
5391
|
const lines = [];
|
|
5356
|
-
const rl =
|
|
5392
|
+
const rl = createInterface({ input: createReadStream("/dev/stdin") });
|
|
5357
5393
|
rl.on("line", (line) => lines.push(line));
|
|
5358
|
-
rl.on("close", () =>
|
|
5394
|
+
rl.on("close", () => resolve10(lines.join("\n")));
|
|
5359
5395
|
rl.on("error", reject);
|
|
5360
5396
|
});
|
|
5361
5397
|
}
|
|
@@ -5364,6 +5400,15 @@ var addInitScriptCommand = registerCommand({
|
|
|
5364
5400
|
description: "Add an initialization script that runs on every page load",
|
|
5365
5401
|
scope: "page",
|
|
5366
5402
|
parameters: InitScriptParams,
|
|
5403
|
+
result: z24.object({
|
|
5404
|
+
scripts: z24.array(z24.object({ name: z24.string(), size: z24.number(), preview: z24.string() })).optional(),
|
|
5405
|
+
removed: z24.string().optional(),
|
|
5406
|
+
existed: z24.boolean().optional(),
|
|
5407
|
+
error: z24.string().optional(),
|
|
5408
|
+
registered: z24.string().optional(),
|
|
5409
|
+
hint: z24.string().optional(),
|
|
5410
|
+
executedImmediately: z24.boolean().optional()
|
|
5411
|
+
}).passthrough(),
|
|
5367
5412
|
handler: async (params, ctx) => {
|
|
5368
5413
|
if (params.list) {
|
|
5369
5414
|
const scripts = Array.from(registeredScripts.entries()).map(([n, content2]) => ({
|
|
@@ -5399,7 +5444,7 @@ registerCommandDefinition("addinitscript", ["script"]);
|
|
|
5399
5444
|
|
|
5400
5445
|
// src/commands/find.ts
|
|
5401
5446
|
import { z as z25 } from "zod";
|
|
5402
|
-
import { ok as ok24, fail as fail6 } from "@dyyz1993/xcli-core";
|
|
5447
|
+
import { ok as ok24, fail as fail6, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
|
|
5403
5448
|
var actionSchema2 = z25.enum(["click", "fill", "type", "select", "hover", "check"]);
|
|
5404
5449
|
var findCommand = registerCommand({
|
|
5405
5450
|
name: "find",
|
|
@@ -5475,7 +5520,7 @@ var findCommand = registerCommand({
|
|
|
5475
5520
|
});
|
|
5476
5521
|
function okWithTips(data, tips) {
|
|
5477
5522
|
const result = ok24(data);
|
|
5478
|
-
if (tips.length > 0) result.tips = tips;
|
|
5523
|
+
if (tips.length > 0) result.tips = normalizeTips5(tips);
|
|
5479
5524
|
return result;
|
|
5480
5525
|
}
|
|
5481
5526
|
function parseOperation(operation) {
|
|
@@ -5730,16 +5775,11 @@ async function detectWebdriverExposure(page) {
|
|
|
5730
5775
|
try {
|
|
5731
5776
|
const webdriver = await page.evaluate(() => {
|
|
5732
5777
|
return {
|
|
5733
|
-
|
|
5734
|
-
webdriver: window.navigator?.webdriver,
|
|
5735
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5778
|
+
webdriver: navigator.webdriver,
|
|
5736
5779
|
webdriverScriptFn: !!window.__webdriver_script_fn,
|
|
5737
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5738
5780
|
webdriverEvaluate: !!window.__webdriver_evaluate,
|
|
5739
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5740
5781
|
chrome: !!window.chrome,
|
|
5741
|
-
|
|
5742
|
-
permissions: window.navigator?.permissions
|
|
5782
|
+
permissions: navigator.permissions
|
|
5743
5783
|
};
|
|
5744
5784
|
}).catch(() => null);
|
|
5745
5785
|
if (!webdriver) {
|
|
@@ -5819,377 +5859,23 @@ function formatDetectionMessage(result) {
|
|
|
5819
5859
|
Action: ${action}`;
|
|
5820
5860
|
}
|
|
5821
5861
|
|
|
5822
|
-
// src/commands/promo.ts
|
|
5823
|
-
import { z as z26 } from "zod";
|
|
5824
|
-
import { ok as ok25 } from "@dyyz1993/xcli-core";
|
|
5825
|
-
import { existsSync as existsSync2, readFileSync as readFileSync8 } from "fs";
|
|
5826
|
-
import { resolve as resolve6 } from "path";
|
|
5827
|
-
|
|
5828
|
-
// src/promo/devto.ts
|
|
5829
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
5830
|
-
import { resolve } from "path";
|
|
5831
|
-
import { execSync } from "child_process";
|
|
5832
|
-
var DEVTO_NEW_URL = "https://dev.to/new";
|
|
5833
|
-
function ab(config) {
|
|
5834
|
-
const parts = ["agent-browser"];
|
|
5835
|
-
if (config.cdpEndpoint) parts.push("--cdp", config.cdpEndpoint);
|
|
5836
|
-
if (config.session) parts.push("--session", config.session);
|
|
5837
|
-
return parts.join(" ");
|
|
5838
|
-
}
|
|
5839
|
-
function extractTitleFromMarkdown(content) {
|
|
5840
|
-
const match = content.match(/^#\s+(.+)$/m);
|
|
5841
|
-
if (match) return match[1].trim();
|
|
5842
|
-
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
5843
|
-
return lines[0] ? lines[0].replace(/^#+\s*/, "").trim() : "Untitled";
|
|
5844
|
-
}
|
|
5845
|
-
function stripTitleFromMarkdown(content) {
|
|
5846
|
-
return content.replace(/^#\s+.+$\n?/m, "").trim();
|
|
5847
|
-
}
|
|
5848
|
-
async function publishToDevto(config) {
|
|
5849
|
-
const cli = ab(config);
|
|
5850
|
-
try {
|
|
5851
|
-
const filePath = resolve(config.file);
|
|
5852
|
-
const raw = readFileSync3(filePath, "utf-8");
|
|
5853
|
-
const title = config.title ?? extractTitleFromMarkdown(raw);
|
|
5854
|
-
const body = stripTitleFromMarkdown(raw);
|
|
5855
|
-
execSync(`${cli} open ${DEVTO_NEW_URL}`, { encoding: "utf-8", timeout: 3e4 });
|
|
5856
|
-
const snapshot = execSync(`${cli} snapshot -i -s body`, { encoding: "utf-8", timeout: 15e3 });
|
|
5857
|
-
if (snapshot.includes("Log in") && !snapshot.includes("Notifications")) {
|
|
5858
|
-
const viewer = execSync(`${cli} viewer --json`, { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
5859
|
-
return {
|
|
5860
|
-
success: false,
|
|
5861
|
-
error: `Not logged in to Dev.to. Please log in via viewer: ${viewer}`,
|
|
5862
|
-
platform: "devto"
|
|
5863
|
-
};
|
|
5864
|
-
}
|
|
5865
|
-
execSync(`${cli} fill @e_title ${JSON.stringify(title)}`, { encoding: "utf-8", timeout: 1e4 });
|
|
5866
|
-
const escapedBody = JSON.stringify(body);
|
|
5867
|
-
execSync(`${cli} fill @e_content ${escapedBody}`, { encoding: "utf-8", timeout: 15e3 });
|
|
5868
|
-
if (config.tags) {
|
|
5869
|
-
const tags = config.tags.split(",").map((t) => t.trim()).slice(0, 4).join(", ");
|
|
5870
|
-
execSync(`${cli} find text "tags" click`, { encoding: "utf-8", timeout: 1e4 });
|
|
5871
|
-
execSync(`${cli} type ${JSON.stringify(tags)}`, { encoding: "utf-8", timeout: 1e4 });
|
|
5872
|
-
}
|
|
5873
|
-
execSync(`${cli} find text "Publish" click`, { encoding: "utf-8", timeout: 15e3 });
|
|
5874
|
-
const postUrl = execSync(`${cli} get url`, { encoding: "utf-8", timeout: 15e3 }).trim();
|
|
5875
|
-
if (postUrl && postUrl !== DEVTO_NEW_URL && postUrl.includes("dev.to")) {
|
|
5876
|
-
return { success: true, url: postUrl, platform: "devto" };
|
|
5877
|
-
}
|
|
5878
|
-
return { success: true, url: postUrl || void 0, platform: "devto" };
|
|
5879
|
-
} catch (err) {
|
|
5880
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5881
|
-
return { success: false, error: message, platform: "devto" };
|
|
5882
|
-
}
|
|
5883
|
-
}
|
|
5884
|
-
|
|
5885
|
-
// src/promo/medium.ts
|
|
5886
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
5887
|
-
import { resolve as resolve2 } from "path";
|
|
5888
|
-
import { execSync as execSync2 } from "child_process";
|
|
5889
|
-
var MEDIUM_NEW_URL = "https://medium.com/new-story";
|
|
5890
|
-
function ab2(config) {
|
|
5891
|
-
const parts = ["agent-browser"];
|
|
5892
|
-
if (config.cdpEndpoint) parts.push("--cdp", config.cdpEndpoint);
|
|
5893
|
-
if (config.session) parts.push("--session", config.session);
|
|
5894
|
-
return parts.join(" ");
|
|
5895
|
-
}
|
|
5896
|
-
function extractTitleFromMarkdown2(content) {
|
|
5897
|
-
const match = content.match(/^#\s+(.+)$/m);
|
|
5898
|
-
if (match) return match[1].trim();
|
|
5899
|
-
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
5900
|
-
return lines[0] ? lines[0].replace(/^#+\s*/, "").trim() : "Untitled";
|
|
5901
|
-
}
|
|
5902
|
-
function markdownToPlainText(content) {
|
|
5903
|
-
return content.replace(/^#+\s+.+$/gm, "").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/`(.+?)`/g, "$1").replace(/\[(.+?)\]\(.+?\)/g, "$1").replace(/!\[.*?\]\(.+?\)/g, "").trim();
|
|
5904
|
-
}
|
|
5905
|
-
async function publishToMedium(config) {
|
|
5906
|
-
const cli = ab2(config);
|
|
5907
|
-
try {
|
|
5908
|
-
const filePath = resolve2(config.file);
|
|
5909
|
-
const raw = readFileSync4(filePath, "utf-8");
|
|
5910
|
-
const title = config.title ?? extractTitleFromMarkdown2(raw);
|
|
5911
|
-
const body = markdownToPlainText(raw);
|
|
5912
|
-
execSync2(`${cli} open ${MEDIUM_NEW_URL}`, { encoding: "utf-8", timeout: 3e4 });
|
|
5913
|
-
const snapshot = execSync2(`${cli} snapshot -i -s body`, { encoding: "utf-8", timeout: 15e3 });
|
|
5914
|
-
if (snapshot.includes("Sign in") && !snapshot.includes("Write")) {
|
|
5915
|
-
const viewer = execSync2(`${cli} viewer --json`, { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
5916
|
-
return {
|
|
5917
|
-
success: false,
|
|
5918
|
-
error: `Not logged in to Medium. Please log in via viewer: ${viewer}`,
|
|
5919
|
-
platform: "medium"
|
|
5920
|
-
};
|
|
5921
|
-
}
|
|
5922
|
-
execSync2(`${cli} click @e_title`, { encoding: "utf-8", timeout: 1e4 });
|
|
5923
|
-
execSync2(`${cli} type ${JSON.stringify(title)}`, { encoding: "utf-8", timeout: 1e4 });
|
|
5924
|
-
execSync2(`${cli} click @e_content`, { encoding: "utf-8", timeout: 1e4 });
|
|
5925
|
-
const lines = body.split("\n");
|
|
5926
|
-
for (const line of lines) {
|
|
5927
|
-
execSync2(`${cli} type ${JSON.stringify(line)}`, { encoding: "utf-8", timeout: 1e4 });
|
|
5928
|
-
execSync2(`${cli} keyboard Enter`, { encoding: "utf-8", timeout: 5e3 });
|
|
5929
|
-
}
|
|
5930
|
-
if (config.tags) {
|
|
5931
|
-
execSync2(`${cli} find text "Tags" click`, { encoding: "utf-8", timeout: 1e4 });
|
|
5932
|
-
const tags = config.tags.split(",").map((t) => t.trim()).join(", ");
|
|
5933
|
-
execSync2(`${cli} type ${JSON.stringify(tags)}`, { encoding: "utf-8", timeout: 1e4 });
|
|
5934
|
-
}
|
|
5935
|
-
execSync2(`${cli} find text "Publish" click`, { encoding: "utf-8", timeout: 15e3 });
|
|
5936
|
-
const postUrl = execSync2(`${cli} get url`, { encoding: "utf-8", timeout: 15e3 }).trim();
|
|
5937
|
-
return { success: true, url: postUrl || void 0, platform: "medium" };
|
|
5938
|
-
} catch (err) {
|
|
5939
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5940
|
-
return { success: false, error: message, platform: "medium" };
|
|
5941
|
-
}
|
|
5942
|
-
}
|
|
5943
|
-
|
|
5944
|
-
// src/promo/csdn.ts
|
|
5945
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
5946
|
-
import { resolve as resolve3 } from "path";
|
|
5947
|
-
import { execSync as execSync3 } from "child_process";
|
|
5948
|
-
var CSDN_EDITOR_URL = "https://mp.csdn.net/mp_blog/creation/editor";
|
|
5949
|
-
function ab3(config) {
|
|
5950
|
-
const parts = ["agent-browser"];
|
|
5951
|
-
if (config.cdpEndpoint) parts.push("--cdp", config.cdpEndpoint);
|
|
5952
|
-
if (config.session) parts.push("--session", config.session);
|
|
5953
|
-
return parts.join(" ");
|
|
5954
|
-
}
|
|
5955
|
-
function extractTitleFromMarkdown3(content) {
|
|
5956
|
-
const match = content.match(/^#\s+(.+)$/m);
|
|
5957
|
-
if (match) return match[1].trim();
|
|
5958
|
-
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
5959
|
-
return lines[0] ? lines[0].replace(/^#+\s*/, "").trim() : "Untitled";
|
|
5960
|
-
}
|
|
5961
|
-
async function publishToCsdn(config) {
|
|
5962
|
-
const cli = ab3(config);
|
|
5963
|
-
try {
|
|
5964
|
-
const filePath = resolve3(config.file);
|
|
5965
|
-
const raw = readFileSync5(filePath, "utf-8");
|
|
5966
|
-
const title = config.title ?? extractTitleFromMarkdown3(raw);
|
|
5967
|
-
const body = raw.replace(/^#\s+.+$\n?/m, "").trim();
|
|
5968
|
-
execSync3(`${cli} open ${CSDN_EDITOR_URL}`, { encoding: "utf-8", timeout: 3e4 });
|
|
5969
|
-
const snapshot = execSync3(`${cli} snapshot -i -s body`, { encoding: "utf-8", timeout: 15e3 });
|
|
5970
|
-
if (snapshot.includes("\u767B\u5F55") && !snapshot.includes("\u5199\u6587\u7AE0")) {
|
|
5971
|
-
const viewer = execSync3(`${cli} viewer --json`, { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
5972
|
-
return {
|
|
5973
|
-
success: false,
|
|
5974
|
-
error: `Not logged in to CSDN. Please log in via viewer: ${viewer}`,
|
|
5975
|
-
platform: "csdn"
|
|
5976
|
-
};
|
|
5977
|
-
}
|
|
5978
|
-
execSync3(`${cli} fill @e_title ${JSON.stringify(title)}`, { encoding: "utf-8", timeout: 1e4 });
|
|
5979
|
-
const escapedBody = JSON.stringify(body);
|
|
5980
|
-
execSync3(`${cli} fill @e_content ${escapedBody}`, { encoding: "utf-8", timeout: 15e3 });
|
|
5981
|
-
if (config.tags) {
|
|
5982
|
-
const tags = config.tags.split(",").map((t) => t.trim());
|
|
5983
|
-
for (const tag of tags) {
|
|
5984
|
-
execSync3(`${cli} find text "\u6DFB\u52A0\u6807\u7B7E" click`, { encoding: "utf-8", timeout: 1e4 });
|
|
5985
|
-
execSync3(`${cli} type ${JSON.stringify(tag)}`, { encoding: "utf-8", timeout: 1e4 });
|
|
5986
|
-
execSync3(`${cli} keyboard Enter`, { encoding: "utf-8", timeout: 5e3 });
|
|
5987
|
-
}
|
|
5988
|
-
}
|
|
5989
|
-
execSync3(`${cli} find text "\u53D1\u5E03" click`, { encoding: "utf-8", timeout: 15e3 });
|
|
5990
|
-
const postUrl = execSync3(`${cli} get url`, { encoding: "utf-8", timeout: 15e3 }).trim();
|
|
5991
|
-
return { success: true, url: postUrl || void 0, platform: "csdn" };
|
|
5992
|
-
} catch (err) {
|
|
5993
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5994
|
-
return { success: false, error: message, platform: "csdn" };
|
|
5995
|
-
}
|
|
5996
|
-
}
|
|
5997
|
-
|
|
5998
|
-
// src/promo/juejin.ts
|
|
5999
|
-
import { readFileSync as readFileSync6 } from "fs";
|
|
6000
|
-
import { resolve as resolve4 } from "path";
|
|
6001
|
-
import { execSync as execSync4 } from "child_process";
|
|
6002
|
-
var JUEJIN_EDITOR_URL = "https://juejin.cn/editor/draft/new";
|
|
6003
|
-
function ab4(config) {
|
|
6004
|
-
const parts = ["agent-browser"];
|
|
6005
|
-
if (config.cdpEndpoint) parts.push("--cdp", config.cdpEndpoint);
|
|
6006
|
-
if (config.session) parts.push("--session", config.session);
|
|
6007
|
-
return parts.join(" ");
|
|
6008
|
-
}
|
|
6009
|
-
function extractTitleFromMarkdown4(content) {
|
|
6010
|
-
const match = content.match(/^#\s+(.+)$/m);
|
|
6011
|
-
if (match) return match[1].trim();
|
|
6012
|
-
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
6013
|
-
return lines[0] ? lines[0].replace(/^#+\s*/, "").trim() : "Untitled";
|
|
6014
|
-
}
|
|
6015
|
-
async function publishToJuejin(config) {
|
|
6016
|
-
const cli = ab4(config);
|
|
6017
|
-
try {
|
|
6018
|
-
const filePath = resolve4(config.file);
|
|
6019
|
-
const raw = readFileSync6(filePath, "utf-8");
|
|
6020
|
-
const title = config.title ?? extractTitleFromMarkdown4(raw);
|
|
6021
|
-
const body = raw.replace(/^#\s+.+$\n?/m, "").trim();
|
|
6022
|
-
execSync4(`${cli} open ${JUEJIN_EDITOR_URL}`, { encoding: "utf-8", timeout: 3e4 });
|
|
6023
|
-
const snapshot = execSync4(`${cli} snapshot -i -s body`, { encoding: "utf-8", timeout: 15e3 });
|
|
6024
|
-
if (snapshot.includes("\u767B\u5F55") && snapshot.includes("\u6CE8\u518C") && !snapshot.includes("\u521B\u4F5C\u8005\u4E2D\u5FC3")) {
|
|
6025
|
-
const viewer = execSync4(`${cli} viewer --json`, { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
6026
|
-
return {
|
|
6027
|
-
success: false,
|
|
6028
|
-
error: `Not logged in to Juejin. Please log in via viewer: ${viewer}`,
|
|
6029
|
-
platform: "juejin"
|
|
6030
|
-
};
|
|
6031
|
-
}
|
|
6032
|
-
execSync4(`${cli} fill @e_title ${JSON.stringify(title)}`, { encoding: "utf-8", timeout: 1e4 });
|
|
6033
|
-
const escapedBody = JSON.stringify(body);
|
|
6034
|
-
execSync4(`${cli} fill @e_content ${escapedBody}`, { encoding: "utf-8", timeout: 15e3 });
|
|
6035
|
-
if (config.tags) {
|
|
6036
|
-
const tags = config.tags.split(",").map((t) => t.trim());
|
|
6037
|
-
for (const tag of tags) {
|
|
6038
|
-
execSync4(`${cli} find text "\u6DFB\u52A0\u6807\u7B7E" click`, { encoding: "utf-8", timeout: 1e4 });
|
|
6039
|
-
execSync4(`${cli} type ${JSON.stringify(tag)}`, { encoding: "utf-8", timeout: 1e4 });
|
|
6040
|
-
execSync4(`${cli} keyboard Enter`, { encoding: "utf-8", timeout: 5e3 });
|
|
6041
|
-
}
|
|
6042
|
-
}
|
|
6043
|
-
execSync4(`${cli} find text "\u53D1\u5E03" click`, { encoding: "utf-8", timeout: 15e3 });
|
|
6044
|
-
const postUrl = execSync4(`${cli} get url`, { encoding: "utf-8", timeout: 15e3 }).trim();
|
|
6045
|
-
return { success: true, url: postUrl || void 0, platform: "juejin" };
|
|
6046
|
-
} catch (err) {
|
|
6047
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
6048
|
-
return { success: false, error: message, platform: "juejin" };
|
|
6049
|
-
}
|
|
6050
|
-
}
|
|
6051
|
-
|
|
6052
|
-
// src/promo/quora.ts
|
|
6053
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
6054
|
-
import { resolve as resolve5 } from "path";
|
|
6055
|
-
import { execSync as execSync5 } from "child_process";
|
|
6056
|
-
function ab5(config) {
|
|
6057
|
-
const parts = ["agent-browser"];
|
|
6058
|
-
if (config.cdpEndpoint) parts.push("--cdp", config.cdpEndpoint);
|
|
6059
|
-
if (config.session) parts.push("--session", config.session);
|
|
6060
|
-
return parts.join(" ");
|
|
6061
|
-
}
|
|
6062
|
-
async function publishToQuora(config) {
|
|
6063
|
-
const cli = ab5(config);
|
|
6064
|
-
try {
|
|
6065
|
-
if (!config.search) {
|
|
6066
|
-
return {
|
|
6067
|
-
success: false,
|
|
6068
|
-
error: "Quora requires --search parameter to find relevant questions",
|
|
6069
|
-
platform: "quora"
|
|
6070
|
-
};
|
|
6071
|
-
}
|
|
6072
|
-
const filePath = resolve5(config.file);
|
|
6073
|
-
const raw = readFileSync7(filePath, "utf-8");
|
|
6074
|
-
const answer = raw.trim();
|
|
6075
|
-
const searchUrl = `https://www.quora.com/search?q=${encodeURIComponent(config.search)}`;
|
|
6076
|
-
execSync5(`${cli} open ${searchUrl}`, { encoding: "utf-8", timeout: 3e4 });
|
|
6077
|
-
const snapshot = execSync5(`${cli} snapshot -i -s body`, { encoding: "utf-8", timeout: 15e3 });
|
|
6078
|
-
if (!snapshot.includes("Add question")) {
|
|
6079
|
-
const viewer = execSync5(`${cli} viewer --json`, { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
6080
|
-
return {
|
|
6081
|
-
success: false,
|
|
6082
|
-
error: `Not logged in to Quora. Please log in via viewer: ${viewer}`,
|
|
6083
|
-
platform: "quora"
|
|
6084
|
-
};
|
|
6085
|
-
}
|
|
6086
|
-
const answerMatch = snapshot.match(/Answer\s*(?:button|link)/i);
|
|
6087
|
-
if (!answerMatch) {
|
|
6088
|
-
return {
|
|
6089
|
-
success: false,
|
|
6090
|
-
error: "No answerable questions found for the given search query. Try a different search term.",
|
|
6091
|
-
platform: "quora"
|
|
6092
|
-
};
|
|
6093
|
-
}
|
|
6094
|
-
execSync5(`${cli} find text "Answer" click`, { encoding: "utf-8", timeout: 1e4 });
|
|
6095
|
-
const escapedAnswer = JSON.stringify(answer);
|
|
6096
|
-
execSync5(`${cli} fill @e_answer ${escapedAnswer}`, { encoding: "utf-8", timeout: 15e3 });
|
|
6097
|
-
execSync5(`${cli} find text "Submit" click`, { encoding: "utf-8", timeout: 15e3 });
|
|
6098
|
-
const postUrl = execSync5(`${cli} get url`, { encoding: "utf-8", timeout: 15e3 }).trim();
|
|
6099
|
-
return { success: true, url: postUrl || void 0, platform: "quora" };
|
|
6100
|
-
} catch (err) {
|
|
6101
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
6102
|
-
return { success: false, error: message, platform: "quora" };
|
|
6103
|
-
}
|
|
6104
|
-
}
|
|
6105
|
-
|
|
6106
|
-
// src/promo/index.ts
|
|
6107
|
-
var PUBLISHERS = {
|
|
6108
|
-
devto: publishToDevto,
|
|
6109
|
-
medium: publishToMedium,
|
|
6110
|
-
csdn: publishToCsdn,
|
|
6111
|
-
juejin: publishToJuejin,
|
|
6112
|
-
quora: publishToQuora
|
|
6113
|
-
};
|
|
6114
|
-
async function dispatchPromo(config) {
|
|
6115
|
-
const publisher = PUBLISHERS[config.platform];
|
|
6116
|
-
if (!publisher) {
|
|
6117
|
-
return {
|
|
6118
|
-
success: false,
|
|
6119
|
-
error: `Unknown platform: ${config.platform}. Supported: ${Object.keys(PUBLISHERS).join(", ")}`,
|
|
6120
|
-
platform: config.platform
|
|
6121
|
-
};
|
|
6122
|
-
}
|
|
6123
|
-
return publisher(config);
|
|
6124
|
-
}
|
|
6125
|
-
|
|
6126
|
-
// src/commands/promo.ts
|
|
6127
|
-
var promoParams = z26.object({
|
|
6128
|
-
platform: z26.enum(["devto", "medium", "csdn", "juejin", "quora"]).describe("Target platform for promotion"),
|
|
6129
|
-
file: z26.string().describe("Path to Markdown file to publish"),
|
|
6130
|
-
tags: z26.string().optional().describe("Comma-separated tags"),
|
|
6131
|
-
title: z26.string().optional().describe("Custom title (default: extracted from file first heading)"),
|
|
6132
|
-
search: z26.string().optional().describe("Quora: search query to find questions"),
|
|
6133
|
-
cdpEndpoint: z26.string().optional().describe("CDP endpoint for agent-browser"),
|
|
6134
|
-
session: z26.string().optional().describe("agent-browser session name")
|
|
6135
|
-
}).refine(
|
|
6136
|
-
(data) => data.platform !== "quora" || !!data.search,
|
|
6137
|
-
{ message: "Quora platform requires --search parameter" }
|
|
6138
|
-
).refine(
|
|
6139
|
-
(data) => existsSync2(resolve6(data.file)),
|
|
6140
|
-
{ message: "File does not exist" }
|
|
6141
|
-
);
|
|
6142
|
-
var promoCommand = registerCommand({
|
|
6143
|
-
name: "promo",
|
|
6144
|
-
description: "Publish promotional articles to various platforms (devto, medium, csdn, juejin, quora)",
|
|
6145
|
-
scope: "project",
|
|
6146
|
-
parameters: promoParams,
|
|
6147
|
-
result: z26.object({
|
|
6148
|
-
success: z26.boolean(),
|
|
6149
|
-
url: z26.string().optional(),
|
|
6150
|
-
error: z26.string().optional(),
|
|
6151
|
-
platform: z26.string()
|
|
6152
|
-
}),
|
|
6153
|
-
handler: async (p, _ctx) => {
|
|
6154
|
-
const filePath = resolve6(p.file);
|
|
6155
|
-
const content = readFileSync8(filePath, "utf-8");
|
|
6156
|
-
if (content.trim().length === 0) {
|
|
6157
|
-
return ok25({
|
|
6158
|
-
success: false,
|
|
6159
|
-
error: `File is empty: ${filePath}`,
|
|
6160
|
-
platform: p.platform
|
|
6161
|
-
});
|
|
6162
|
-
}
|
|
6163
|
-
const result = await dispatchPromo({
|
|
6164
|
-
platform: p.platform,
|
|
6165
|
-
file: filePath,
|
|
6166
|
-
tags: p.tags,
|
|
6167
|
-
title: p.title,
|
|
6168
|
-
search: p.search,
|
|
6169
|
-
cdpEndpoint: p.cdpEndpoint ?? _ctx.cdpEndpoint,
|
|
6170
|
-
session: p.session ?? p.platform
|
|
6171
|
-
});
|
|
6172
|
-
return ok25(result);
|
|
6173
|
-
}
|
|
6174
|
-
});
|
|
6175
|
-
|
|
6176
5862
|
// src/plugin/loader.ts
|
|
6177
5863
|
import {
|
|
6178
5864
|
Core
|
|
6179
5865
|
} from "@dyyz1993/xcli-core";
|
|
6180
|
-
import { resolve as
|
|
6181
|
-
import { existsSync as
|
|
6182
|
-
import { homedir as
|
|
5866
|
+
import { resolve as resolve2 } from "path";
|
|
5867
|
+
import { existsSync as existsSync4, readdirSync } from "fs";
|
|
5868
|
+
import { homedir as homedir4 } from "os";
|
|
6183
5869
|
|
|
6184
5870
|
// src/plugin/metadata-parser.ts
|
|
6185
|
-
import { existsSync as
|
|
6186
|
-
import { resolve
|
|
5871
|
+
import { existsSync as existsSync2 } from "fs";
|
|
5872
|
+
import { resolve } from "path";
|
|
6187
5873
|
|
|
6188
5874
|
// src/utils/json-file.ts
|
|
6189
|
-
import { readFileSync as
|
|
5875
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
6190
5876
|
function readJsonFile(filePath, defaultValue) {
|
|
6191
5877
|
try {
|
|
6192
|
-
const content =
|
|
5878
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
6193
5879
|
return JSON.parse(content);
|
|
6194
5880
|
} catch {
|
|
6195
5881
|
return defaultValue;
|
|
@@ -6200,8 +5886,8 @@ function readJsonFile(filePath, defaultValue) {
|
|
|
6200
5886
|
var PluginMetadataParser = class {
|
|
6201
5887
|
static XBROWSER_KEYWORDS = ["xbrowser", "xbrowser-plugin"];
|
|
6202
5888
|
static parseFromPackageJson(pluginPath) {
|
|
6203
|
-
const packageJsonPath =
|
|
6204
|
-
if (!
|
|
5889
|
+
const packageJsonPath = resolve(pluginPath, "package.json");
|
|
5890
|
+
if (!existsSync2(packageJsonPath)) {
|
|
6205
5891
|
return null;
|
|
6206
5892
|
}
|
|
6207
5893
|
const packageJson = readJsonFile(packageJsonPath, null);
|
|
@@ -6265,20 +5951,20 @@ var PluginMetadataParser = class {
|
|
|
6265
5951
|
};
|
|
6266
5952
|
|
|
6267
5953
|
// src/plugin/ensure-deps.ts
|
|
6268
|
-
import { existsSync as
|
|
6269
|
-
import { join as
|
|
6270
|
-
import { execSync
|
|
5954
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
5955
|
+
import { join as join4 } from "path";
|
|
5956
|
+
import { execSync } from "child_process";
|
|
6271
5957
|
var SHARED_PLUGIN_DEPENDENCIES = {
|
|
6272
5958
|
"zod": "^3.24.0",
|
|
6273
5959
|
"@dyyz1993/xcli-core": "^0.12.1"
|
|
6274
5960
|
};
|
|
6275
5961
|
function ensurePluginDependencies(pluginsDir) {
|
|
6276
|
-
const zodPath =
|
|
6277
|
-
if (
|
|
6278
|
-
|
|
6279
|
-
const pkgPath =
|
|
5962
|
+
const zodPath = join4(pluginsDir, "node_modules", "zod");
|
|
5963
|
+
if (existsSync3(zodPath)) return;
|
|
5964
|
+
mkdirSync4(pluginsDir, { recursive: true });
|
|
5965
|
+
const pkgPath = join4(pluginsDir, "package.json");
|
|
6280
5966
|
let pkg2 = {};
|
|
6281
|
-
if (
|
|
5967
|
+
if (existsSync3(pkgPath)) {
|
|
6282
5968
|
try {
|
|
6283
5969
|
pkg2 = readJsonFile(pkgPath, {});
|
|
6284
5970
|
} catch {
|
|
@@ -6292,13 +5978,13 @@ function ensurePluginDependencies(pluginsDir) {
|
|
|
6292
5978
|
needsInstall = true;
|
|
6293
5979
|
}
|
|
6294
5980
|
}
|
|
6295
|
-
if (!needsInstall &&
|
|
5981
|
+
if (!needsInstall && existsSync3(join4(pluginsDir, "node_modules"))) return;
|
|
6296
5982
|
pkg2.dependencies = existingDeps;
|
|
6297
5983
|
pkg2.private = true;
|
|
6298
5984
|
pkg2.description = pkg2.description || "xbrowser plugins \u2014 shared dependencies";
|
|
6299
|
-
|
|
5985
|
+
writeFileSync5(pkgPath, JSON.stringify(pkg2, null, 2) + "\n", "utf-8");
|
|
6300
5986
|
try {
|
|
6301
|
-
|
|
5987
|
+
execSync("npm install --production --no-package-lock --no-fund --no-audit", {
|
|
6302
5988
|
cwd: pluginsDir,
|
|
6303
5989
|
stdio: "pipe",
|
|
6304
5990
|
timeout: 6e4,
|
|
@@ -6310,8 +5996,15 @@ function ensurePluginDependencies(pluginsDir) {
|
|
|
6310
5996
|
}
|
|
6311
5997
|
|
|
6312
5998
|
// src/plugin/contract.ts
|
|
5999
|
+
import {
|
|
6000
|
+
unwrapZod,
|
|
6001
|
+
fieldsFromZodObjectReflected,
|
|
6002
|
+
zodTypeToContractType
|
|
6003
|
+
} from "@dyyz1993/xcli-core";
|
|
6313
6004
|
function buildPluginContract(site) {
|
|
6314
|
-
const commands = site.getAllCommands().map((command) => buildCommandContract(site.getCommand?.(command.name) || command
|
|
6005
|
+
const commands = site.getAllCommands().map((command) => buildCommandContract(site.getCommand?.(command.name) || command, {
|
|
6006
|
+
siteRequiresLogin: site.config?.requiresLogin
|
|
6007
|
+
}));
|
|
6315
6008
|
return {
|
|
6316
6009
|
version: 2,
|
|
6317
6010
|
plugin: {
|
|
@@ -6323,18 +6016,19 @@ function buildPluginContract(site) {
|
|
|
6323
6016
|
commands
|
|
6324
6017
|
};
|
|
6325
6018
|
}
|
|
6326
|
-
function buildCommandContract(command) {
|
|
6019
|
+
function buildCommandContract(command, options = {}) {
|
|
6327
6020
|
const extension = command.xbrowser || {};
|
|
6328
6021
|
const inferredFields = fieldsFromZodObject(command.parameters);
|
|
6329
6022
|
const fields = mergeFields(inferredFields, extension.form?.fields || []);
|
|
6330
6023
|
const positional = extension.positional || fields.filter((field) => field.positional).map((field) => field.name);
|
|
6331
|
-
const
|
|
6024
|
+
const requiresLogin = command.requiresLogin === true || options.siteRequiresLogin === true && command.name !== "login" && command.name !== "logout";
|
|
6025
|
+
const capabilities = extension.capabilities || inferCapabilities(command.scope || "project", requiresLogin);
|
|
6332
6026
|
const outputSchema = command.result ? summarizeZod(command.result) : void 0;
|
|
6333
6027
|
return {
|
|
6334
6028
|
name: command.name,
|
|
6335
6029
|
description: command.description || "",
|
|
6336
6030
|
scope: command.scope || "project",
|
|
6337
|
-
requiresLogin
|
|
6031
|
+
requiresLogin,
|
|
6338
6032
|
category: extension.category,
|
|
6339
6033
|
capabilities,
|
|
6340
6034
|
positional,
|
|
@@ -6348,25 +6042,21 @@ function buildCommandContract(command) {
|
|
|
6348
6042
|
};
|
|
6349
6043
|
}
|
|
6350
6044
|
function fieldsFromZodObject(schema) {
|
|
6351
|
-
const
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
...unwrapped.defaultValue !== void 0 ? { default: unwrapped.defaultValue } : {},
|
|
6367
|
-
...enumValues ? { enum: enumValues } : {},
|
|
6368
|
-
...type === "array" ? { multiple: true } : {}
|
|
6369
|
-
};
|
|
6045
|
+
const reflected = fieldsFromZodObjectReflected(schema);
|
|
6046
|
+
return reflected.map((field) => {
|
|
6047
|
+
const widget = widgetFor(field.type, field.enum);
|
|
6048
|
+
return {
|
|
6049
|
+
name: field.name,
|
|
6050
|
+
label: toLabel(field.name),
|
|
6051
|
+
type: field.type,
|
|
6052
|
+
widget,
|
|
6053
|
+
required: field.required,
|
|
6054
|
+
...field.description ? { description: field.description } : {},
|
|
6055
|
+
...field.default !== void 0 ? { default: field.default } : {},
|
|
6056
|
+
...field.enum ? { enum: field.enum } : {},
|
|
6057
|
+
...field.type === "array" ? { multiple: true } : {}
|
|
6058
|
+
};
|
|
6059
|
+
});
|
|
6370
6060
|
}
|
|
6371
6061
|
function mergeFields(inferred, overrides) {
|
|
6372
6062
|
if (overrides.length === 0) return inferred;
|
|
@@ -6397,72 +6087,14 @@ function inferCapabilities(scope, requiresLogin) {
|
|
|
6397
6087
|
if (requiresLogin) caps.push("auth.login");
|
|
6398
6088
|
return caps;
|
|
6399
6089
|
}
|
|
6400
|
-
function
|
|
6401
|
-
const zod = schema;
|
|
6402
|
-
const shapeOrFn = zod?.shape ?? zod?._def?.shape;
|
|
6403
|
-
if (!shapeOrFn) return void 0;
|
|
6404
|
-
return typeof shapeOrFn === "function" ? shapeOrFn() : shapeOrFn;
|
|
6405
|
-
}
|
|
6406
|
-
function unwrapZod(schema) {
|
|
6407
|
-
let current = schema;
|
|
6408
|
-
let optional = typeof current?.isOptional === "function" ? current.isOptional() : false;
|
|
6409
|
-
let description = current?._def?.description;
|
|
6410
|
-
let defaultValue;
|
|
6411
|
-
for (let i = 0; i < 8; i++) {
|
|
6412
|
-
const def = current?._def;
|
|
6413
|
-
const typeName = def?.typeName || "unknown";
|
|
6414
|
-
if (def?.description) description = def.description;
|
|
6415
|
-
if (!def) return { schema: current, typeName, optional, description, defaultValue };
|
|
6416
|
-
if (typeName === "ZodDefault") {
|
|
6417
|
-
optional = true;
|
|
6418
|
-
defaultValue = typeof def.defaultValue === "function" ? def.defaultValue() : def.defaultValue;
|
|
6419
|
-
current = def.innerType || def.type;
|
|
6420
|
-
continue;
|
|
6421
|
-
}
|
|
6422
|
-
if (typeName === "ZodOptional" || typeName === "ZodNullable") {
|
|
6423
|
-
optional = true;
|
|
6424
|
-
current = def.innerType || def.type;
|
|
6425
|
-
continue;
|
|
6426
|
-
}
|
|
6427
|
-
return { schema: current, typeName, optional, description, defaultValue };
|
|
6428
|
-
}
|
|
6429
|
-
return { schema: current, typeName: current?._def?.typeName || "unknown", optional, description, defaultValue };
|
|
6430
|
-
}
|
|
6431
|
-
function zodTypeToContractType(typeName) {
|
|
6432
|
-
switch (typeName) {
|
|
6433
|
-
case "ZodString":
|
|
6434
|
-
return "string";
|
|
6435
|
-
case "ZodNumber":
|
|
6436
|
-
return "number";
|
|
6437
|
-
case "ZodBoolean":
|
|
6438
|
-
return "boolean";
|
|
6439
|
-
case "ZodEnum":
|
|
6440
|
-
case "ZodNativeEnum":
|
|
6441
|
-
return "enum";
|
|
6442
|
-
case "ZodArray":
|
|
6443
|
-
return "array";
|
|
6444
|
-
case "ZodObject":
|
|
6445
|
-
return "object";
|
|
6446
|
-
default:
|
|
6447
|
-
return typeName.replace(/^Zod/, "").toLowerCase() || "unknown";
|
|
6448
|
-
}
|
|
6449
|
-
}
|
|
6450
|
-
function widgetFor(type, enumValues, schema) {
|
|
6090
|
+
function widgetFor(type, enumValues) {
|
|
6451
6091
|
if (enumValues) return "select";
|
|
6452
6092
|
if (type === "boolean") return "checkbox";
|
|
6453
6093
|
if (type === "number") return "number";
|
|
6454
6094
|
if (type === "array") return "multi-select";
|
|
6455
6095
|
if (type === "object") return "json";
|
|
6456
|
-
const checks = schema?._def?.checks;
|
|
6457
|
-
if (checks?.some((check) => check.kind === "url")) return "url";
|
|
6458
6096
|
return "text";
|
|
6459
6097
|
}
|
|
6460
|
-
function extractEnumValues(schema) {
|
|
6461
|
-
const def = schema?._def;
|
|
6462
|
-
const values = def?.values;
|
|
6463
|
-
if (Array.isArray(values)) return values.map(String);
|
|
6464
|
-
return void 0;
|
|
6465
|
-
}
|
|
6466
6098
|
function summarizeZod(schema) {
|
|
6467
6099
|
const unwrapped = unwrapZod(schema);
|
|
6468
6100
|
if (unwrapped.typeName === "ZodArray") {
|
|
@@ -6472,7 +6104,7 @@ function summarizeZod(schema) {
|
|
|
6472
6104
|
items: summarizeZod(def?.type || def?.innerType)
|
|
6473
6105
|
};
|
|
6474
6106
|
}
|
|
6475
|
-
const shape =
|
|
6107
|
+
const shape = getObjectShape(schema);
|
|
6476
6108
|
if (!shape) {
|
|
6477
6109
|
return {
|
|
6478
6110
|
type: zodTypeToContractType(unwrapped.typeName),
|
|
@@ -6482,19 +6114,53 @@ function summarizeZod(schema) {
|
|
|
6482
6114
|
}
|
|
6483
6115
|
return Object.fromEntries(
|
|
6484
6116
|
Object.entries(shape).map(([name, field]) => {
|
|
6485
|
-
const
|
|
6117
|
+
const inner = unwrapZod(field);
|
|
6486
6118
|
return [name, {
|
|
6487
|
-
type: zodTypeToContractType(
|
|
6488
|
-
required: !
|
|
6489
|
-
...
|
|
6119
|
+
type: zodTypeToContractType(inner.typeName),
|
|
6120
|
+
required: !inner.optional,
|
|
6121
|
+
...inner.description ? { description: inner.description } : {}
|
|
6490
6122
|
}];
|
|
6491
6123
|
})
|
|
6492
6124
|
);
|
|
6493
6125
|
}
|
|
6126
|
+
function getObjectShape(schema) {
|
|
6127
|
+
const zod = schema;
|
|
6128
|
+
const shapeOrFn = zod?.shape ?? zod?._def?.shape;
|
|
6129
|
+
if (!shapeOrFn) return void 0;
|
|
6130
|
+
return typeof shapeOrFn === "function" ? shapeOrFn() : shapeOrFn;
|
|
6131
|
+
}
|
|
6494
6132
|
function toLabel(name) {
|
|
6495
6133
|
return name.replace(/([A-Z])/g, " $1").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().replace(/^./, (char) => char.toUpperCase());
|
|
6496
6134
|
}
|
|
6497
6135
|
|
|
6136
|
+
// src/plugin/login-required-patch.ts
|
|
6137
|
+
import { SiteInstanceImpl } from "@dyyz1993/xcli-core";
|
|
6138
|
+
var patched = false;
|
|
6139
|
+
function patchLoginRequired() {
|
|
6140
|
+
if (patched) return;
|
|
6141
|
+
patched = true;
|
|
6142
|
+
const target = SiteInstanceImpl.prototype;
|
|
6143
|
+
const originalCommand = target.command;
|
|
6144
|
+
const wrapped = function(...args) {
|
|
6145
|
+
const result = originalCommand.apply(this, args);
|
|
6146
|
+
const [name, cmd] = args;
|
|
6147
|
+
const loginRequired = cmd.loginRequired;
|
|
6148
|
+
if (loginRequired) {
|
|
6149
|
+
const commands = this.commands;
|
|
6150
|
+
const entry = commands?.get(name);
|
|
6151
|
+
if (entry) {
|
|
6152
|
+
entry.loginRequired = loginRequired;
|
|
6153
|
+
}
|
|
6154
|
+
}
|
|
6155
|
+
return result;
|
|
6156
|
+
};
|
|
6157
|
+
Object.defineProperty(target, "command", {
|
|
6158
|
+
value: wrapped,
|
|
6159
|
+
writable: true,
|
|
6160
|
+
configurable: true
|
|
6161
|
+
});
|
|
6162
|
+
}
|
|
6163
|
+
|
|
6498
6164
|
// src/plugin/loader.ts
|
|
6499
6165
|
var DEFAULT_PLUGIN_DIRS = [".xcli/plugins", "../.xcli/plugins"];
|
|
6500
6166
|
var XBrowserPluginLoader = class {
|
|
@@ -6502,6 +6168,7 @@ var XBrowserPluginLoader = class {
|
|
|
6502
6168
|
loader;
|
|
6503
6169
|
options;
|
|
6504
6170
|
constructor(options) {
|
|
6171
|
+
patchLoginRequired();
|
|
6505
6172
|
this.options = options ?? {};
|
|
6506
6173
|
const cwd = this.options.cwd || process.cwd();
|
|
6507
6174
|
const coreConfig = {
|
|
@@ -6512,7 +6179,7 @@ var XBrowserPluginLoader = class {
|
|
|
6512
6179
|
envPrefix: "XBROWSER",
|
|
6513
6180
|
pluginDirs: [
|
|
6514
6181
|
...DEFAULT_PLUGIN_DIRS,
|
|
6515
|
-
|
|
6182
|
+
resolve2(cwd, ".xcli/plugins")
|
|
6516
6183
|
]
|
|
6517
6184
|
};
|
|
6518
6185
|
this.core = new Core(coreConfig);
|
|
@@ -6558,31 +6225,31 @@ var XBrowserPluginLoader = class {
|
|
|
6558
6225
|
}
|
|
6559
6226
|
async scanAndLoad() {
|
|
6560
6227
|
const cwd = this.options.cwd || process.cwd();
|
|
6561
|
-
const globalDir = this.options.globalDir ||
|
|
6228
|
+
const globalDir = this.options.globalDir || resolve2(homedir4(), ".xbrowser/plugins");
|
|
6562
6229
|
ensurePluginDependencies(globalDir);
|
|
6563
6230
|
const dirs = [
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
this.options.userDir ||
|
|
6231
|
+
resolve2(cwd, ".xcli/plugins"),
|
|
6232
|
+
resolve2(cwd, "../.xcli/plugins"),
|
|
6233
|
+
this.options.userDir || resolve2(homedir4(), ".xcli/plugins"),
|
|
6567
6234
|
globalDir
|
|
6568
6235
|
];
|
|
6569
6236
|
const loaded = [];
|
|
6570
6237
|
const seen = /* @__PURE__ */ new Set();
|
|
6571
6238
|
for (const dir of dirs) {
|
|
6572
|
-
if (!
|
|
6239
|
+
if (!existsSync4(dir)) continue;
|
|
6573
6240
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
6574
6241
|
for (const entry of entries) {
|
|
6575
6242
|
if (!entry.isDirectory()) continue;
|
|
6576
6243
|
if (seen.has(entry.name)) continue;
|
|
6577
6244
|
seen.add(entry.name);
|
|
6578
|
-
const pluginDir =
|
|
6579
|
-
let indexPath =
|
|
6580
|
-
if (!
|
|
6581
|
-
indexPath =
|
|
6245
|
+
const pluginDir = resolve2(dir, entry.name);
|
|
6246
|
+
let indexPath = resolve2(pluginDir, "index.js");
|
|
6247
|
+
if (!existsSync4(indexPath)) {
|
|
6248
|
+
indexPath = resolve2(pluginDir, "index.ts");
|
|
6582
6249
|
}
|
|
6583
|
-
if (!
|
|
6250
|
+
if (!existsSync4(indexPath)) continue;
|
|
6584
6251
|
try {
|
|
6585
|
-
if (!
|
|
6252
|
+
if (!existsSync4(resolve2(pluginDir, "package.json"))) {
|
|
6586
6253
|
console.warn(`\u26A0\uFE0F Plugin "${entry.name}" has no package.json. Use "xbrowser create ${entry.name} --template static" for proper structure.`);
|
|
6587
6254
|
} else {
|
|
6588
6255
|
const metadata = PluginMetadataParser.parseFromPackageJson(pluginDir);
|
|
@@ -6620,28 +6287,132 @@ async function getPluginLoader() {
|
|
|
6620
6287
|
return pluginLoader;
|
|
6621
6288
|
}
|
|
6622
6289
|
|
|
6623
|
-
// src/
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6290
|
+
// src/utils/viewer-url.ts
|
|
6291
|
+
function buildViewerUrl(sessionName = "default") {
|
|
6292
|
+
try {
|
|
6293
|
+
const status = getDaemonProcessStatus();
|
|
6294
|
+
if (!status.running) return void 0;
|
|
6295
|
+
const port = status.port || getDaemonConfig().basePort;
|
|
6296
|
+
return `http://localhost:${port}/preview/${encodeURIComponent(sessionName)}`;
|
|
6297
|
+
} catch {
|
|
6298
|
+
return void 0;
|
|
6299
|
+
}
|
|
6300
|
+
}
|
|
6630
6301
|
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6302
|
+
// src/plugin/login-guard.ts
|
|
6303
|
+
async function checkPluginLoginRequired(options) {
|
|
6304
|
+
const { site, command, commandName, ctx, page, sessionName } = options;
|
|
6305
|
+
if (commandName === "login" || commandName === "logout") return { ok: true };
|
|
6306
|
+
const loginConfig = site.config?.loginConfig;
|
|
6307
|
+
const requiresLogin = command.requiresLogin === true || site.config?.requiresLogin === true || loginConfig?.requiresLogin === true;
|
|
6308
|
+
if (!requiresLogin) return { ok: true };
|
|
6309
|
+
const pluginName = site.name || "plugin";
|
|
6310
|
+
if (typeof site.isLoggedIn === "function") {
|
|
6311
|
+
try {
|
|
6312
|
+
const loggedIn = await site.isLoggedIn(ctx);
|
|
6313
|
+
if (loggedIn) return { ok: true };
|
|
6314
|
+
return buildLoginRequired({
|
|
6315
|
+
plugin: pluginName,
|
|
6316
|
+
command: commandName,
|
|
6317
|
+
reason: "plugin isLoggedIn returned false",
|
|
6318
|
+
sessionName,
|
|
6319
|
+
loginConfig
|
|
6320
|
+
});
|
|
6321
|
+
} catch {
|
|
6635
6322
|
}
|
|
6636
|
-
const cls = el.className && typeof el.className === 'string'
|
|
6637
|
-
? el.className.trim().split(/\\s+/).filter(c => c.length > 0 && !c.startsWith('__')).slice(0, 2).join('.')
|
|
6638
|
-
: '';
|
|
6639
|
-
if (cls) return el.tagName.toLowerCase() + '.' + cls;
|
|
6640
|
-
return el.tagName.toLowerCase();
|
|
6641
6323
|
}
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6324
|
+
if (page && loginConfig) {
|
|
6325
|
+
const generic = await detectLoginFromPage(page, loginConfig);
|
|
6326
|
+
if (generic === "logged-in") return { ok: true };
|
|
6327
|
+
if (generic === "logged-out") {
|
|
6328
|
+
return buildLoginRequired({
|
|
6329
|
+
plugin: pluginName,
|
|
6330
|
+
command: commandName,
|
|
6331
|
+
reason: "generic loginConfig detected logged-out page",
|
|
6332
|
+
sessionName,
|
|
6333
|
+
loginConfig
|
|
6334
|
+
});
|
|
6335
|
+
}
|
|
6336
|
+
}
|
|
6337
|
+
return { ok: true };
|
|
6338
|
+
}
|
|
6339
|
+
function buildLoginRequired(options) {
|
|
6340
|
+
const viewerUrl = buildViewerUrl(options.sessionName);
|
|
6341
|
+
const loginUrl = options.loginConfig?.loginUrl;
|
|
6342
|
+
const message = options.loginConfig?.loginPrompt || `Plugin "${options.plugin}" requires login before running "${options.command}".`;
|
|
6343
|
+
const tips = [
|
|
6344
|
+
message,
|
|
6345
|
+
...viewerUrl ? [`Open viewer to complete login: ${viewerUrl}`] : [],
|
|
6346
|
+
...loginUrl ? [`Login page: ${loginUrl}`] : [],
|
|
6347
|
+
`After login, retry: xbrowser ${options.plugin} ${options.command} --session ${options.sessionName}`
|
|
6348
|
+
];
|
|
6349
|
+
return {
|
|
6350
|
+
ok: false,
|
|
6351
|
+
data: {
|
|
6352
|
+
code: "LOGIN_REQUIRED",
|
|
6353
|
+
plugin: options.plugin,
|
|
6354
|
+
command: options.command,
|
|
6355
|
+
reason: options.reason,
|
|
6356
|
+
...viewerUrl ? { viewerUrl } : {},
|
|
6357
|
+
...loginUrl ? { loginUrl } : {}
|
|
6358
|
+
},
|
|
6359
|
+
message,
|
|
6360
|
+
tips
|
|
6361
|
+
};
|
|
6362
|
+
}
|
|
6363
|
+
async function detectLoginFromPage(page, config) {
|
|
6364
|
+
const url = page.url();
|
|
6365
|
+
if (config.loginUrls?.some((part) => url.includes(part))) return "logged-out";
|
|
6366
|
+
const result = await page.evaluate((cfg) => {
|
|
6367
|
+
const visible = (selector) => {
|
|
6368
|
+
try {
|
|
6369
|
+
const el = document.querySelector(selector);
|
|
6370
|
+
if (!el) return false;
|
|
6371
|
+
const rect = el.getBoundingClientRect();
|
|
6372
|
+
const style = window.getComputedStyle(el);
|
|
6373
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
6374
|
+
} catch {
|
|
6375
|
+
return false;
|
|
6376
|
+
}
|
|
6377
|
+
};
|
|
6378
|
+
if (cfg.loggedInSelectors?.some(visible)) return "logged-in";
|
|
6379
|
+
if (cfg.loginSelectors?.some(visible)) return "logged-out";
|
|
6380
|
+
const bodyText = document.body?.innerText || "";
|
|
6381
|
+
const keywords = cfg.loginKeywords || [];
|
|
6382
|
+
if (keywords.length > 0 && keywords.every((keyword) => bodyText.includes(keyword))) {
|
|
6383
|
+
return "logged-out";
|
|
6384
|
+
}
|
|
6385
|
+
return "unknown";
|
|
6386
|
+
}, {
|
|
6387
|
+
loginSelectors: config.loginSelectors || [],
|
|
6388
|
+
loginKeywords: config.loginKeywords || [],
|
|
6389
|
+
loggedInSelectors: config.loggedInSelectors || []
|
|
6390
|
+
});
|
|
6391
|
+
return result;
|
|
6392
|
+
}
|
|
6393
|
+
|
|
6394
|
+
// src/tips/dom-watcher.ts
|
|
6395
|
+
var DOM_WATCHER_SCRIPT = `
|
|
6396
|
+
(function() {
|
|
6397
|
+
if (window.__xbrowserDomWatcher) return;
|
|
6398
|
+
window.__xbrowserDomWatcher = true;
|
|
6399
|
+
|
|
6400
|
+
window.__xbrowserDetectedElements = [];
|
|
6401
|
+
|
|
6402
|
+
function getSelector(el) {
|
|
6403
|
+
if (el.id) return '#' + CSS.escape(el.id);
|
|
6404
|
+
if (el.getAttribute('aria-label')) {
|
|
6405
|
+
return el.tagName.toLowerCase() + '[aria-label="' + el.getAttribute('aria-label') + '"]';
|
|
6406
|
+
}
|
|
6407
|
+
const cls = el.className && typeof el.className === 'string'
|
|
6408
|
+
? el.className.trim().split(/\\s+/).filter(c => c.length > 0 && !c.startsWith('__')).slice(0, 2).join('.')
|
|
6409
|
+
: '';
|
|
6410
|
+
if (cls) return el.tagName.toLowerCase() + '.' + cls;
|
|
6411
|
+
return el.tagName.toLowerCase();
|
|
6412
|
+
}
|
|
6413
|
+
|
|
6414
|
+
function isPopupLike(el) {
|
|
6415
|
+
const tag = el.tagName?.toLowerCase();
|
|
6645
6416
|
if (tag === 'dialog') return true;
|
|
6646
6417
|
if (tag === 'body' || tag === 'html') return false;
|
|
6647
6418
|
|
|
@@ -6999,7 +6770,7 @@ var TipsManager = class {
|
|
|
6999
6770
|
}
|
|
7000
6771
|
}
|
|
7001
6772
|
debounce() {
|
|
7002
|
-
return new Promise((
|
|
6773
|
+
return new Promise((resolve10) => setTimeout(resolve10, DEBOUNCE_MS));
|
|
7003
6774
|
}
|
|
7004
6775
|
formatTips(tips) {
|
|
7005
6776
|
return tips.map((tip) => {
|
|
@@ -7027,33 +6798,104 @@ function getTipsManager() {
|
|
|
7027
6798
|
}
|
|
7028
6799
|
|
|
7029
6800
|
// src/hooks/loader.ts
|
|
7030
|
-
var
|
|
7031
|
-
|
|
6801
|
+
var FAIL_KEYWORDS = [
|
|
6802
|
+
"\u767B\u5F55",
|
|
6803
|
+
"login",
|
|
6804
|
+
"Login",
|
|
6805
|
+
"\u672A\u767B\u5F55",
|
|
6806
|
+
"not logged in",
|
|
6807
|
+
"cdp",
|
|
6808
|
+
"CDP",
|
|
6809
|
+
"\u9A8C\u8BC1\u7801",
|
|
6810
|
+
"\u9A8C\u8BC1",
|
|
6811
|
+
"captcha",
|
|
6812
|
+
"\u9700\u8981\u767B\u5F55",
|
|
6813
|
+
"requires login",
|
|
6814
|
+
"blocked",
|
|
6815
|
+
"403",
|
|
6816
|
+
"404"
|
|
6817
|
+
];
|
|
6818
|
+
var HOOK_REGISTRY = {
|
|
6819
|
+
viewer: {
|
|
6820
|
+
name: "viewer",
|
|
6821
|
+
onAfterCommand: async (ctx) => {
|
|
6822
|
+
const result = ctx.result;
|
|
6823
|
+
if (!result || result.success !== false) return void 0;
|
|
6824
|
+
const msg = [
|
|
6825
|
+
result.message,
|
|
6826
|
+
...result.tips || []
|
|
6827
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
6828
|
+
if (!FAIL_KEYWORDS.some((k) => msg.includes(k))) return void 0;
|
|
6829
|
+
const viewerUrl = buildViewerUrl();
|
|
6830
|
+
if (!viewerUrl) return void 0;
|
|
6831
|
+
const tips = result.tips || [];
|
|
6832
|
+
if (!tips.some((t) => t.includes("viewer") || t.includes("Viewer"))) {
|
|
6833
|
+
tips.push(`Open viewer: ${viewerUrl}`);
|
|
6834
|
+
}
|
|
6835
|
+
result.tips = tips;
|
|
6836
|
+
result.viewerUrl = viewerUrl;
|
|
6837
|
+
return void 0;
|
|
6838
|
+
}
|
|
6839
|
+
},
|
|
6840
|
+
screenshot: {
|
|
6841
|
+
name: "screenshot",
|
|
6842
|
+
onAfterCommand: async (ctx) => {
|
|
6843
|
+
try {
|
|
6844
|
+
const buf = await ctx.page.screenshot({ type: "jpeg", quality: 40 }).catch(() => null);
|
|
6845
|
+
if (!buf) return;
|
|
6846
|
+
return { screenshot: { url: `data:image/jpeg;base64,${buf.toString("base64").slice(0, 50)}...` } };
|
|
6847
|
+
} catch {
|
|
6848
|
+
return;
|
|
6849
|
+
}
|
|
6850
|
+
}
|
|
6851
|
+
},
|
|
6852
|
+
recorder: {
|
|
6853
|
+
name: "recorder",
|
|
6854
|
+
onAfterCommand: async (ctx) => {
|
|
6855
|
+
const logs = ("__commandLogs" in ctx ? ctx.__commandLogs : void 0) || [];
|
|
6856
|
+
logs.push({
|
|
6857
|
+
timestamp: Date.now(),
|
|
6858
|
+
command: ctx.command,
|
|
6859
|
+
params: JSON.parse(JSON.stringify(ctx.params)),
|
|
6860
|
+
duration: ctx.duration
|
|
6861
|
+
});
|
|
6862
|
+
Reflect.set(ctx, "__commandLogs", logs);
|
|
6863
|
+
return void 0;
|
|
6864
|
+
}
|
|
6865
|
+
}
|
|
7032
6866
|
};
|
|
6867
|
+
var customHooks = {};
|
|
7033
6868
|
async function loadHooks() {
|
|
7034
|
-
const
|
|
7035
|
-
if (!
|
|
6869
|
+
const env = process.env.XBROWSER_HOOKS;
|
|
6870
|
+
if (!env) return [];
|
|
6871
|
+
const names = env.split(",").map((n) => n.trim()).filter(Boolean);
|
|
6872
|
+
if (names.length === 0) return [];
|
|
7036
6873
|
const hooks = [];
|
|
7037
|
-
for (const name of names
|
|
7038
|
-
const
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
6874
|
+
for (const name of names) {
|
|
6875
|
+
const hook = HOOK_REGISTRY[name];
|
|
6876
|
+
if (hook) {
|
|
6877
|
+
hooks.push(hook);
|
|
6878
|
+
continue;
|
|
6879
|
+
}
|
|
6880
|
+
const customFactory = customHooks[name];
|
|
6881
|
+
if (customFactory) {
|
|
6882
|
+
const customHook = await customFactory();
|
|
6883
|
+
if (customHook) hooks.push(customHook);
|
|
7042
6884
|
}
|
|
7043
6885
|
}
|
|
7044
6886
|
return hooks;
|
|
7045
6887
|
}
|
|
7046
6888
|
|
|
7047
6889
|
// src/executor.ts
|
|
7048
|
-
import { homedir as
|
|
7049
|
-
import { join as
|
|
6890
|
+
import { homedir as homedir5 } from "os";
|
|
6891
|
+
import { join as join5 } from "path";
|
|
7050
6892
|
var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
|
|
7051
6893
|
var snapshotHintShown = /* @__PURE__ */ new WeakSet();
|
|
7052
|
-
var
|
|
6894
|
+
var CONFIG_DIR2 = join5(homedir5(), ".xbrowser");
|
|
7053
6895
|
var storageCache = /* @__PURE__ */ new Map();
|
|
7054
6896
|
function getPluginStorage(pluginName) {
|
|
7055
6897
|
if (!storageCache.has(pluginName)) {
|
|
7056
|
-
storageCache.set(pluginName, new
|
|
6898
|
+
storageCache.set(pluginName, new CompositeStorage2(pluginName, CONFIG_DIR2, "xbrowser"));
|
|
7057
6899
|
}
|
|
7058
6900
|
return storageCache.get(pluginName);
|
|
7059
6901
|
}
|
|
@@ -7061,7 +6903,7 @@ var archiveInitialized = false;
|
|
|
7061
6903
|
function ensureArchiveInit() {
|
|
7062
6904
|
if (!archiveInitialized) {
|
|
7063
6905
|
try {
|
|
7064
|
-
configureArchiveStore({ archiveDir:
|
|
6906
|
+
configureArchiveStore({ archiveDir: join5(homedir5(), ".xbrowser", "archives") });
|
|
7065
6907
|
} catch {
|
|
7066
6908
|
}
|
|
7067
6909
|
archiveInitialized = true;
|
|
@@ -7087,6 +6929,10 @@ async function guardCheck(commandName) {
|
|
|
7087
6929
|
function errorResult(message) {
|
|
7088
6930
|
return { ...fail7(message), duration: 0 };
|
|
7089
6931
|
}
|
|
6932
|
+
function tipsToMessages(tips) {
|
|
6933
|
+
if (!tips || tips.length === 0) return [];
|
|
6934
|
+
return tips.map((t) => typeof t === "string" ? t : t.message);
|
|
6935
|
+
}
|
|
7090
6936
|
var wsServer = null;
|
|
7091
6937
|
function streamCommandEvent(sessionId, message) {
|
|
7092
6938
|
if (!wsServer || !wsServer.getRunning()) return;
|
|
@@ -7114,7 +6960,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7114
6960
|
}
|
|
7115
6961
|
let targetPageOverride = null;
|
|
7116
6962
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
7117
|
-
const { findTargetPage } = await import("./browser-
|
|
6963
|
+
const { findTargetPage } = await import("./browser-5CTOA2WS.js");
|
|
7118
6964
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
7119
6965
|
if (!targetPageOverride) {
|
|
7120
6966
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -7131,7 +6977,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7131
6977
|
params = result.data;
|
|
7132
6978
|
}
|
|
7133
6979
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
7134
|
-
const { forwardExec } = await import("./daemon-client-
|
|
6980
|
+
const { forwardExec } = await import("./daemon-client-3JOKX2L2.js");
|
|
7135
6981
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
7136
6982
|
if (result) return result;
|
|
7137
6983
|
}
|
|
@@ -7157,18 +7003,10 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7157
7003
|
});
|
|
7158
7004
|
}
|
|
7159
7005
|
}
|
|
7160
|
-
} else if (
|
|
7006
|
+
} else if (command.scope !== "project") {
|
|
7161
7007
|
session = await createSession(sessionName, params.url, {
|
|
7162
7008
|
cdpEndpoint: extraOpts?.cdpEndpoint
|
|
7163
7009
|
});
|
|
7164
|
-
} else if (command.scope === "browser") {
|
|
7165
|
-
session = await createSession(sessionName, void 0, {
|
|
7166
|
-
cdpEndpoint: extraOpts?.cdpEndpoint
|
|
7167
|
-
});
|
|
7168
|
-
} else if (command.scope !== "project") {
|
|
7169
|
-
return errorResult(
|
|
7170
|
-
`Session '${sessionName}' not found. Run "xbrowser session open <url>" first.`
|
|
7171
|
-
);
|
|
7172
7010
|
}
|
|
7173
7011
|
const ctx = {
|
|
7174
7012
|
page: session?.page,
|
|
@@ -7190,8 +7028,9 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7190
7028
|
throw new Error(msg);
|
|
7191
7029
|
},
|
|
7192
7030
|
config: {},
|
|
7193
|
-
site:
|
|
7194
|
-
cliName: "xbrowser"
|
|
7031
|
+
site: new NoopSiteInstance(),
|
|
7032
|
+
cliName: "xbrowser",
|
|
7033
|
+
tips: new TipCollector2()
|
|
7195
7034
|
};
|
|
7196
7035
|
const start = Date.now();
|
|
7197
7036
|
if (session) {
|
|
@@ -7212,7 +7051,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7212
7051
|
const cache = /* @__PURE__ */ new Map();
|
|
7213
7052
|
const resolved = await resolveRefParams(session.page, params, command.selectorParams, cache, session.id);
|
|
7214
7053
|
if (resolved.tips.length > 0) {
|
|
7215
|
-
refTips = resolved.tips;
|
|
7054
|
+
refTips = normalizeTips6(resolved.tips);
|
|
7216
7055
|
params = resolved.params;
|
|
7217
7056
|
}
|
|
7218
7057
|
}
|
|
@@ -7268,36 +7107,43 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7268
7107
|
snapshotHintShown.add(session);
|
|
7269
7108
|
snapshotHint = "\u{1F4A1} \u4F7F\u7528 snapshot \u547D\u4EE4\u83B7\u53D6\u9875\u9762\u5FEB\u7167\u548C ref \u7F16\u53F7\uFF0C\u7136\u540E\u7528 ref \u5FEB\u901F\u5B9A\u4F4D\u5143\u7D20\uFF08\u5982 click --selector e1\uFF09";
|
|
7270
7109
|
}
|
|
7271
|
-
const merged = [
|
|
7110
|
+
const merged = [
|
|
7111
|
+
...raw.tips || [],
|
|
7112
|
+
...normalizeTips6(smartTips),
|
|
7113
|
+
...snapshotHint ? normalizeTips6([snapshotHint]) : [],
|
|
7114
|
+
...refTips
|
|
7115
|
+
];
|
|
7272
7116
|
const isSuccess = raw.success !== false;
|
|
7117
|
+
const mergedOrRaw = merged.length > 0 ? merged : raw.tips || [];
|
|
7273
7118
|
recordArchive(session?.id, sessionName, {
|
|
7274
7119
|
step: 0,
|
|
7275
7120
|
command: commandName,
|
|
7276
7121
|
params,
|
|
7277
|
-
result: { success: isSuccess, data: raw.data, message: raw.message, tips:
|
|
7122
|
+
result: { success: isSuccess, data: raw.data, message: raw.message, tips: tipsToMessages(mergedOrRaw) },
|
|
7278
7123
|
toolCalls: [],
|
|
7279
7124
|
duration,
|
|
7280
7125
|
timestamp: start
|
|
7281
7126
|
});
|
|
7282
7127
|
if (isSuccess) {
|
|
7283
|
-
return { ...
|
|
7128
|
+
return { ...ok25(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
7284
7129
|
}
|
|
7285
|
-
return { success: false, data: raw.data, message: raw.message, tips:
|
|
7130
|
+
return { success: false, data: raw.data, message: raw.message, tips: mergedOrRaw, duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
7286
7131
|
}
|
|
7132
|
+
const smartTipNormalized = normalizeTips6(smartTips);
|
|
7287
7133
|
recordArchive(session?.id, sessionName, {
|
|
7288
7134
|
step: 0,
|
|
7289
7135
|
command: commandName,
|
|
7290
7136
|
params,
|
|
7291
|
-
result: { success: true, data: raw, tips:
|
|
7137
|
+
result: { success: true, data: raw, tips: tipsToMessages(smartTipNormalized) },
|
|
7292
7138
|
toolCalls: [],
|
|
7293
7139
|
duration,
|
|
7294
7140
|
timestamp: start
|
|
7295
7141
|
});
|
|
7296
|
-
return { ...
|
|
7142
|
+
return { ...ok25(raw, smartTipNormalized), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
7297
7143
|
} catch (err) {
|
|
7298
7144
|
const end = Date.now();
|
|
7299
7145
|
const duration = end - start;
|
|
7300
|
-
const errorMessage = err
|
|
7146
|
+
const errorMessage = errMsg(err);
|
|
7301
7147
|
if (session) {
|
|
7302
7148
|
streamCommandEvent(session.id, {
|
|
7303
7149
|
sessionId: session.id,
|
|
@@ -7343,8 +7189,8 @@ async function executeChain(input, options) {
|
|
|
7343
7189
|
});
|
|
7344
7190
|
}
|
|
7345
7191
|
try {
|
|
7346
|
-
for (const
|
|
7347
|
-
const { type, pipeline: commands } =
|
|
7192
|
+
for (const pipeline of pipelines) {
|
|
7193
|
+
const { type, pipeline: commands } = pipeline;
|
|
7348
7194
|
for (const cmdStr of commands) {
|
|
7349
7195
|
const parts = splitCommand(cmdStr);
|
|
7350
7196
|
if (parts.length === 0) continue;
|
|
@@ -7409,10 +7255,51 @@ async function executeChain(input, options) {
|
|
|
7409
7255
|
},
|
|
7410
7256
|
config: {},
|
|
7411
7257
|
site,
|
|
7412
|
-
cliName: "xbrowser"
|
|
7258
|
+
cliName: "xbrowser",
|
|
7259
|
+
tips: new TipCollector2()
|
|
7413
7260
|
};
|
|
7414
7261
|
const start2 = Date.now();
|
|
7415
7262
|
try {
|
|
7263
|
+
const loginGuard = await checkPluginLoginRequired({
|
|
7264
|
+
site,
|
|
7265
|
+
command: cmdEntry,
|
|
7266
|
+
commandName: subCommand,
|
|
7267
|
+
ctx: pluginCtx,
|
|
7268
|
+
page: session?.page,
|
|
7269
|
+
sessionName
|
|
7270
|
+
});
|
|
7271
|
+
if (!loginGuard.ok) {
|
|
7272
|
+
const duration3 = Date.now() - start2;
|
|
7273
|
+
const data2 = loginGuard.data ?? null;
|
|
7274
|
+
recordArchive(session.id, sessionName, {
|
|
7275
|
+
step: results.length,
|
|
7276
|
+
command: `${cmdName} ${subCommand}`,
|
|
7277
|
+
params: pluginParams,
|
|
7278
|
+
result: { success: false, data: data2, message: loginGuard.message, tips: loginGuard.tips || [] },
|
|
7279
|
+
toolCalls: [],
|
|
7280
|
+
duration: duration3,
|
|
7281
|
+
timestamp: start2
|
|
7282
|
+
});
|
|
7283
|
+
results.push({
|
|
7284
|
+
command: `${cmdName} ${subCommand}`,
|
|
7285
|
+
raw: cmdStr,
|
|
7286
|
+
success: false,
|
|
7287
|
+
data: data2,
|
|
7288
|
+
message: loginGuard.message,
|
|
7289
|
+
tips: normalizeTips6(loginGuard.tips),
|
|
7290
|
+
duration: duration3
|
|
7291
|
+
});
|
|
7292
|
+
if (type === "and") {
|
|
7293
|
+
return {
|
|
7294
|
+
success: false,
|
|
7295
|
+
steps: results,
|
|
7296
|
+
totalDuration: Date.now() - totalStart,
|
|
7297
|
+
stoppedAt: results.length,
|
|
7298
|
+
stoppedReason: `Command '${cmdName} ${subCommand}' failed (&& chain): ${loginGuard.message}`
|
|
7299
|
+
};
|
|
7300
|
+
}
|
|
7301
|
+
continue;
|
|
7302
|
+
}
|
|
7416
7303
|
const hooks = await loadHooks();
|
|
7417
7304
|
if (hooks.length > 0) {
|
|
7418
7305
|
await Promise.all(hooks.map((h) => h.onBeforeCommand?.({ page: session.page, command: `${cmdName} ${subCommand}`, params: pluginParams })));
|
|
@@ -7433,7 +7320,7 @@ async function executeChain(input, options) {
|
|
|
7433
7320
|
step: results.length,
|
|
7434
7321
|
command: `${cmdName} ${subCommand}`,
|
|
7435
7322
|
params: pluginParams,
|
|
7436
|
-
result: { success: true, data, tips: raw?.tips
|
|
7323
|
+
result: { success: true, data, tips: tipsToMessages(raw?.tips) },
|
|
7437
7324
|
toolCalls: [],
|
|
7438
7325
|
duration: duration2,
|
|
7439
7326
|
timestamp: start2
|
|
@@ -7441,7 +7328,7 @@ async function executeChain(input, options) {
|
|
|
7441
7328
|
results.push({
|
|
7442
7329
|
command: `${cmdName} ${subCommand}`,
|
|
7443
7330
|
raw: cmdStr,
|
|
7444
|
-
...
|
|
7331
|
+
...ok25(data),
|
|
7445
7332
|
duration: duration2,
|
|
7446
7333
|
...hookOutputs ? { hookOutputs } : {}
|
|
7447
7334
|
});
|
|
@@ -7456,7 +7343,7 @@ async function executeChain(input, options) {
|
|
|
7456
7343
|
}
|
|
7457
7344
|
} catch (err) {
|
|
7458
7345
|
const duration2 = Date.now() - start2;
|
|
7459
|
-
const errorMessage = err
|
|
7346
|
+
const errorMessage = errMsg(err);
|
|
7460
7347
|
recordArchive(session.id, sessionName, {
|
|
7461
7348
|
step: results.length,
|
|
7462
7349
|
command: `${cmdName} ${subCommand}`,
|
|
@@ -7484,7 +7371,7 @@ async function executeChain(input, options) {
|
|
|
7484
7371
|
}
|
|
7485
7372
|
continue;
|
|
7486
7373
|
}
|
|
7487
|
-
const { params } = parseCommandArgs(cmdName, cmdArgs);
|
|
7374
|
+
const { params } = parseCommandArgs(cmdName, cmdArgs, unquote2);
|
|
7488
7375
|
if (cmdName === "goto" && params.url) {
|
|
7489
7376
|
const existing2 = await findOrRestoreSession(sessionName, options?.cdpEndpoint);
|
|
7490
7377
|
if (!existing2) {
|
|
@@ -7545,15 +7432,6 @@ function isChainInput(input) {
|
|
|
7545
7432
|
}
|
|
7546
7433
|
|
|
7547
7434
|
// src/session/session-client.ts
|
|
7548
|
-
function sessionToInfo(s) {
|
|
7549
|
-
return { id: s.id, name: s.name, url: s.page.url(), createdAt: s.createdAt, cdpEndpoint: s.cdpEndpoint };
|
|
7550
|
-
}
|
|
7551
|
-
async function openSession(name, url, options) {
|
|
7552
|
-
const session = await createSession(name, url, { cdpEndpoint: options?.cdpEndpoint });
|
|
7553
|
-
const info = sessionToInfo(session);
|
|
7554
|
-
saveSessionDiskMeta(name, info);
|
|
7555
|
-
return info;
|
|
7556
|
-
}
|
|
7557
7435
|
async function closeSession(name) {
|
|
7558
7436
|
await closeSessionByName(name);
|
|
7559
7437
|
}
|
|
@@ -7567,53 +7445,22 @@ function handleSessionHelp() {
|
|
|
7567
7445
|
"Usage: xbrowser session <command> [options]",
|
|
7568
7446
|
"",
|
|
7569
7447
|
"Commands:",
|
|
7570
|
-
" open <url> [--name <name>] Open browser and create session",
|
|
7571
7448
|
" close [--name <name>] Close session",
|
|
7572
7449
|
" list, ls List active sessions",
|
|
7573
7450
|
" kill [--name <name>] Kill session forcefully",
|
|
7451
|
+
" kill-all Kill all sessions and daemon",
|
|
7574
7452
|
"",
|
|
7575
7453
|
"Options:",
|
|
7576
7454
|
' --name <name> Session name (default: "default")',
|
|
7577
7455
|
"",
|
|
7456
|
+
"Note: Sessions are auto-created via --session global option.",
|
|
7457
|
+
"",
|
|
7578
7458
|
"Examples:",
|
|
7579
|
-
" xbrowser
|
|
7580
|
-
" xbrowser session open https://example.com --name mypage",
|
|
7459
|
+
" xbrowser goto https://example.com --session mypage",
|
|
7581
7460
|
" xbrowser session close --name mypage",
|
|
7582
7461
|
" xbrowser session list"
|
|
7583
7462
|
].join("\n");
|
|
7584
7463
|
}
|
|
7585
|
-
var sessionOpenBuiltin = {
|
|
7586
|
-
name: "session open",
|
|
7587
|
-
description: "Open browser and create session",
|
|
7588
|
-
help: {
|
|
7589
|
-
usage: "xbrowser session open <url> [--name <name>]",
|
|
7590
|
-
description: "Open URL and create a browser session",
|
|
7591
|
-
options: [{ name: "--name <name>", description: 'Session name (default: "default")' }],
|
|
7592
|
-
examples: [
|
|
7593
|
-
{ cmd: "xbrowser session open https://example.com", description: "Open example.com" },
|
|
7594
|
-
{
|
|
7595
|
-
cmd: "xbrowser session open https://example.com --name test",
|
|
7596
|
-
description: "Open with custom name"
|
|
7597
|
-
}
|
|
7598
|
-
]
|
|
7599
|
-
},
|
|
7600
|
-
execute: async (args, options) => {
|
|
7601
|
-
const [url] = args;
|
|
7602
|
-
const name = options["name"] || "default";
|
|
7603
|
-
if (!url) {
|
|
7604
|
-
console.log("Usage: xbrowser session open <url> [--name <name>]");
|
|
7605
|
-
process.exit(1);
|
|
7606
|
-
}
|
|
7607
|
-
try {
|
|
7608
|
-
const info = await openSession(name, url);
|
|
7609
|
-
console.log(`Session "${info.name}" opened: ${info.url}`);
|
|
7610
|
-
console.log(`ID: ${info.id}`);
|
|
7611
|
-
} catch (e) {
|
|
7612
|
-
console.error("Error:", e instanceof Error ? e.message : String(e));
|
|
7613
|
-
process.exit(1);
|
|
7614
|
-
}
|
|
7615
|
-
}
|
|
7616
|
-
};
|
|
7617
7464
|
var sessionCloseBuiltin = {
|
|
7618
7465
|
name: "session close",
|
|
7619
7466
|
description: "Close browser session",
|
|
@@ -7695,22 +7542,17 @@ var sessionKillBuiltin = {
|
|
|
7695
7542
|
};
|
|
7696
7543
|
|
|
7697
7544
|
// src/config.ts
|
|
7698
|
-
import {
|
|
7699
|
-
import { join as
|
|
7700
|
-
import {
|
|
7701
|
-
function
|
|
7702
|
-
return
|
|
7545
|
+
import { homedir as homedir6, tmpdir } from "os";
|
|
7546
|
+
import { join as join6 } from "path";
|
|
7547
|
+
import { loadConfig as coreLoadConfig, saveConfig as coreSaveConfig } from "@dyyz1993/xcli-core";
|
|
7548
|
+
function getConfigSource() {
|
|
7549
|
+
return { configDir: join6(homedir6() || tmpdir(), ".xbrowser") };
|
|
7703
7550
|
}
|
|
7704
7551
|
function loadConfig() {
|
|
7705
|
-
|
|
7706
|
-
if (!existsSync6(configFile)) return {};
|
|
7707
|
-
return readJsonFile(configFile, {});
|
|
7552
|
+
return coreLoadConfig(getConfigSource());
|
|
7708
7553
|
}
|
|
7709
7554
|
function saveConfig(config) {
|
|
7710
|
-
|
|
7711
|
-
const configFile = getConfigFile();
|
|
7712
|
-
if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
|
|
7713
|
-
writeFileSync5(configFile, JSON.stringify(config, null, 2), "utf-8");
|
|
7555
|
+
coreSaveConfig(getConfigSource(), config);
|
|
7714
7556
|
}
|
|
7715
7557
|
function getConfigValue(key) {
|
|
7716
7558
|
return loadConfig()[key];
|
|
@@ -7818,38 +7660,69 @@ var configBuiltin = {
|
|
|
7818
7660
|
|
|
7819
7661
|
// src/plugin/installer.ts
|
|
7820
7662
|
import {
|
|
7821
|
-
existsSync as
|
|
7822
|
-
readdirSync as
|
|
7663
|
+
existsSync as existsSync10,
|
|
7664
|
+
readdirSync as readdirSync2,
|
|
7823
7665
|
mkdirSync as mkdirSync8,
|
|
7824
|
-
rmSync as
|
|
7666
|
+
rmSync as rmSync6
|
|
7825
7667
|
} from "fs";
|
|
7826
|
-
import { resolve as
|
|
7827
|
-
import { homedir as
|
|
7668
|
+
import { resolve as resolve8, basename as basename2 } from "path";
|
|
7669
|
+
import { homedir as homedir7 } from "os";
|
|
7828
7670
|
|
|
7829
7671
|
// src/plugin/install-sources/local.ts
|
|
7830
|
-
import { existsSync as
|
|
7831
|
-
import { resolve as
|
|
7672
|
+
import { existsSync as existsSync5, cpSync, rmSync } from "fs";
|
|
7673
|
+
import { resolve as resolve3 } from "path";
|
|
7674
|
+
import { verifyPlugin, safeCleanup } from "@dyyz1993/xcli-core";
|
|
7675
|
+
async function installFromLocal(source, name, targetDir) {
|
|
7676
|
+
const srcPath = resolve3(source);
|
|
7677
|
+
if (!existsSync5(srcPath)) {
|
|
7678
|
+
throw new Error(`Local path does not exist: ${srcPath}`);
|
|
7679
|
+
}
|
|
7680
|
+
const tmpTarget = `${targetDir}-tmp-${Date.now()}`;
|
|
7681
|
+
let warnings = [];
|
|
7682
|
+
try {
|
|
7683
|
+
cpSync(srcPath, tmpTarget, { recursive: true });
|
|
7684
|
+
const verify = verifyPlugin(tmpTarget, { metadataField: "xbrowser" });
|
|
7685
|
+
warnings = verify.warnings ?? [];
|
|
7686
|
+
if (!verify.valid) {
|
|
7687
|
+
safeCleanup(tmpTarget);
|
|
7688
|
+
throw new Error(`Invalid plugin: ${verify.error}`);
|
|
7689
|
+
}
|
|
7690
|
+
if (existsSync5(targetDir)) {
|
|
7691
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
7692
|
+
}
|
|
7693
|
+
cpSync(tmpTarget, targetDir, { recursive: true, force: true });
|
|
7694
|
+
safeCleanup(tmpTarget);
|
|
7695
|
+
} catch (err) {
|
|
7696
|
+
safeCleanup(tmpTarget);
|
|
7697
|
+
throw err;
|
|
7698
|
+
}
|
|
7699
|
+
return {
|
|
7700
|
+
id: name,
|
|
7701
|
+
name,
|
|
7702
|
+
path: targetDir,
|
|
7703
|
+
source: "local",
|
|
7704
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7705
|
+
warnings
|
|
7706
|
+
};
|
|
7707
|
+
}
|
|
7832
7708
|
|
|
7833
|
-
// src/plugin/install-
|
|
7709
|
+
// src/plugin/install-sources/npm.ts
|
|
7710
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync6, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
|
|
7711
|
+
import { resolve as resolve4, join as join7 } from "path";
|
|
7712
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
7834
7713
|
import {
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
createWriteStream
|
|
7842
|
-
} from "fs";
|
|
7843
|
-
import { resolve as resolve9 } from "path";
|
|
7844
|
-
import { execSync as execSync7 } from "child_process";
|
|
7845
|
-
import { pipeline } from "stream/promises";
|
|
7846
|
-
import { Readable } from "stream";
|
|
7714
|
+
downloadToFile,
|
|
7715
|
+
extractTarGz,
|
|
7716
|
+
flattenPackageRoot,
|
|
7717
|
+
verifyPlugin as verifyPlugin2,
|
|
7718
|
+
safeCleanup as safeCleanup2
|
|
7719
|
+
} from "@dyyz1993/xcli-core";
|
|
7847
7720
|
|
|
7848
7721
|
// src/utils/proxy-fetch.ts
|
|
7849
|
-
var
|
|
7722
|
+
var patched2 = false;
|
|
7850
7723
|
async function ensureProxyFetch() {
|
|
7851
|
-
if (
|
|
7852
|
-
|
|
7724
|
+
if (patched2) return;
|
|
7725
|
+
patched2 = true;
|
|
7853
7726
|
if (process.env.https_proxy && !process.env.HTTPS_PROXY) {
|
|
7854
7727
|
process.env.HTTPS_PROXY = process.env.https_proxy;
|
|
7855
7728
|
}
|
|
@@ -7873,7 +7746,8 @@ async function ensureProxyFetch() {
|
|
|
7873
7746
|
const body = init?.body;
|
|
7874
7747
|
if (body instanceof globalThis.FormData && !(body instanceof UFormData)) {
|
|
7875
7748
|
const ufd = new UFormData();
|
|
7876
|
-
|
|
7749
|
+
const domFormData = body;
|
|
7750
|
+
domFormData.forEach((value, key) => {
|
|
7877
7751
|
if (value instanceof Blob) {
|
|
7878
7752
|
ufd.append(key, value, value.name || "file");
|
|
7879
7753
|
} else {
|
|
@@ -7889,113 +7763,7 @@ async function ensureProxyFetch() {
|
|
|
7889
7763
|
}
|
|
7890
7764
|
}
|
|
7891
7765
|
|
|
7892
|
-
// src/plugin/install-utils.ts
|
|
7893
|
-
async function downloadToFile(url, destPath) {
|
|
7894
|
-
await ensureProxyFetch();
|
|
7895
|
-
if (url.startsWith("file://")) {
|
|
7896
|
-
const filePath = decodeURIComponent(new URL(url).pathname);
|
|
7897
|
-
cpSync(filePath, destPath, { force: true });
|
|
7898
|
-
return;
|
|
7899
|
-
}
|
|
7900
|
-
const res = await fetch(url);
|
|
7901
|
-
if (!res.ok) {
|
|
7902
|
-
throw new Error(`Download failed: HTTP ${res.status} from ${url}`);
|
|
7903
|
-
}
|
|
7904
|
-
if (!res.body) {
|
|
7905
|
-
throw new Error(`No response body from ${url}`);
|
|
7906
|
-
}
|
|
7907
|
-
const nodeStream = Readable.fromWeb(res.body);
|
|
7908
|
-
await pipeline(nodeStream, createWriteStream(destPath));
|
|
7909
|
-
}
|
|
7910
|
-
function extractTarGz(tarballPath, targetDir) {
|
|
7911
|
-
mkdirSync4(targetDir, { recursive: true });
|
|
7912
|
-
execSync7(`tar -xzf "${tarballPath}" -C "${targetDir}"`, { stdio: "pipe" });
|
|
7913
|
-
}
|
|
7914
|
-
function flattenPackageRoot(targetDir) {
|
|
7915
|
-
const entries = readdirSync2(targetDir, { withFileTypes: true });
|
|
7916
|
-
const dirs = entries.filter((e) => e.isDirectory());
|
|
7917
|
-
const files = entries.filter((e) => !e.isDirectory());
|
|
7918
|
-
if (dirs.length === 1 && files.length === 0) {
|
|
7919
|
-
const pkgDir = resolve9(targetDir, dirs[0].name);
|
|
7920
|
-
const items = readdirSync2(pkgDir);
|
|
7921
|
-
for (const item of items) {
|
|
7922
|
-
const src = resolve9(pkgDir, item);
|
|
7923
|
-
const dst = resolve9(targetDir, item);
|
|
7924
|
-
cpSync(src, dst, { recursive: true, force: true });
|
|
7925
|
-
}
|
|
7926
|
-
rmSync(pkgDir, { recursive: true, force: true });
|
|
7927
|
-
}
|
|
7928
|
-
}
|
|
7929
|
-
async function verifyPlugin(dir) {
|
|
7930
|
-
const warnings = [];
|
|
7931
|
-
const indexPath = resolve9(dir, "index.ts");
|
|
7932
|
-
if (!existsSync7(indexPath)) {
|
|
7933
|
-
const indexJs = resolve9(dir, "index.js");
|
|
7934
|
-
if (!existsSync7(indexJs)) {
|
|
7935
|
-
return { valid: false, error: "No index.ts or index.js entry point found", warnings };
|
|
7936
|
-
}
|
|
7937
|
-
}
|
|
7938
|
-
const pkgPath = resolve9(dir, "package.json");
|
|
7939
|
-
if (!existsSync7(pkgPath)) {
|
|
7940
|
-
warnings.push("No package.json found");
|
|
7941
|
-
} else {
|
|
7942
|
-
try {
|
|
7943
|
-
const pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
7944
|
-
if (!pkg2.xbrowser) {
|
|
7945
|
-
warnings.push("No xbrowser metadata in package.json");
|
|
7946
|
-
}
|
|
7947
|
-
} catch {
|
|
7948
|
-
warnings.push("Invalid package.json");
|
|
7949
|
-
}
|
|
7950
|
-
}
|
|
7951
|
-
return { valid: true, warnings };
|
|
7952
|
-
}
|
|
7953
|
-
function safeCleanup(dir) {
|
|
7954
|
-
try {
|
|
7955
|
-
rmSync(dir, { recursive: true, force: true });
|
|
7956
|
-
} catch {
|
|
7957
|
-
}
|
|
7958
|
-
}
|
|
7959
|
-
|
|
7960
|
-
// src/plugin/install-sources/local.ts
|
|
7961
|
-
async function installFromLocal(source, name, targetDir) {
|
|
7962
|
-
const srcPath = resolve10(source);
|
|
7963
|
-
if (!existsSync8(srcPath)) {
|
|
7964
|
-
throw new Error(`Local path does not exist: ${srcPath}`);
|
|
7965
|
-
}
|
|
7966
|
-
const tmpTarget = `${targetDir}-tmp-${Date.now()}`;
|
|
7967
|
-
let warnings = [];
|
|
7968
|
-
try {
|
|
7969
|
-
cpSync2(srcPath, tmpTarget, { recursive: true });
|
|
7970
|
-
const verify = await verifyPlugin(tmpTarget);
|
|
7971
|
-
warnings = verify.warnings ?? [];
|
|
7972
|
-
if (!verify.valid) {
|
|
7973
|
-
safeCleanup(tmpTarget);
|
|
7974
|
-
throw new Error(`Invalid plugin: ${verify.error}`);
|
|
7975
|
-
}
|
|
7976
|
-
if (existsSync8(targetDir)) {
|
|
7977
|
-
rmSync2(targetDir, { recursive: true, force: true });
|
|
7978
|
-
}
|
|
7979
|
-
cpSync2(tmpTarget, targetDir, { recursive: true, force: true });
|
|
7980
|
-
safeCleanup(tmpTarget);
|
|
7981
|
-
} catch (err) {
|
|
7982
|
-
safeCleanup(tmpTarget);
|
|
7983
|
-
throw err;
|
|
7984
|
-
}
|
|
7985
|
-
return {
|
|
7986
|
-
id: name,
|
|
7987
|
-
name,
|
|
7988
|
-
path: targetDir,
|
|
7989
|
-
source: "local",
|
|
7990
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7991
|
-
warnings
|
|
7992
|
-
};
|
|
7993
|
-
}
|
|
7994
|
-
|
|
7995
7766
|
// src/plugin/install-sources/npm.ts
|
|
7996
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync6, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
|
|
7997
|
-
import { resolve as resolve11, join as join5 } from "path";
|
|
7998
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
7999
7767
|
async function installFromNpm(packageName, name, targetDir) {
|
|
8000
7768
|
await ensureProxyFetch();
|
|
8001
7769
|
const encodedName = encodeURIComponent(packageName);
|
|
@@ -8013,34 +7781,34 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
8013
7781
|
throw new Error(`No tarball URL for ${packageName}@${latestVersion}`);
|
|
8014
7782
|
}
|
|
8015
7783
|
const tarballUrl = versionMeta.dist.tarball;
|
|
8016
|
-
const tmpDir =
|
|
7784
|
+
const tmpDir = join7(tmpdir2(), `xbrowser-npm-${Date.now()}`);
|
|
8017
7785
|
mkdirSync5(tmpDir, { recursive: true });
|
|
8018
7786
|
let warnings = [];
|
|
8019
7787
|
try {
|
|
8020
|
-
const tarballPath =
|
|
7788
|
+
const tarballPath = join7(tmpDir, `${name}.tgz`);
|
|
8021
7789
|
await downloadToFile(tarballUrl, tarballPath);
|
|
8022
|
-
const extractDir =
|
|
7790
|
+
const extractDir = join7(tmpDir, "extracted");
|
|
8023
7791
|
extractTarGz(tarballPath, extractDir);
|
|
8024
7792
|
flattenPackageRoot(extractDir);
|
|
8025
|
-
const verify =
|
|
7793
|
+
const verify = verifyPlugin2(extractDir, { metadataField: "xbrowser" });
|
|
8026
7794
|
warnings = verify.warnings ?? [];
|
|
8027
7795
|
if (!verify.valid) {
|
|
8028
7796
|
throw new Error(`Invalid npm plugin: ${verify.error}`);
|
|
8029
7797
|
}
|
|
8030
|
-
if (
|
|
8031
|
-
|
|
7798
|
+
if (existsSync6(targetDir)) {
|
|
7799
|
+
rmSync2(targetDir, { recursive: true, force: true });
|
|
8032
7800
|
}
|
|
8033
|
-
|
|
8034
|
-
const pkgPath =
|
|
8035
|
-
if (
|
|
8036
|
-
const pkg2 = JSON.parse(
|
|
7801
|
+
cpSync2(extractDir, targetDir, { recursive: true, force: true });
|
|
7802
|
+
const pkgPath = resolve4(targetDir, "package.json");
|
|
7803
|
+
if (existsSync6(pkgPath)) {
|
|
7804
|
+
const pkg2 = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
8037
7805
|
if (!pkg2._npmSource) {
|
|
8038
7806
|
pkg2._npmSource = { name: packageName, version: latestVersion };
|
|
8039
7807
|
writeFileSync6(pkgPath, JSON.stringify(pkg2, null, 2));
|
|
8040
7808
|
}
|
|
8041
7809
|
}
|
|
8042
7810
|
} finally {
|
|
8043
|
-
|
|
7811
|
+
safeCleanup2(tmpDir);
|
|
8044
7812
|
}
|
|
8045
7813
|
return {
|
|
8046
7814
|
id: name,
|
|
@@ -8053,35 +7821,36 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
8053
7821
|
}
|
|
8054
7822
|
|
|
8055
7823
|
// src/plugin/install-sources/git.ts
|
|
8056
|
-
import { existsSync as
|
|
8057
|
-
import { resolve as
|
|
7824
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync7, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
|
|
7825
|
+
import { resolve as resolve5, join as join8 } from "path";
|
|
8058
7826
|
import { tmpdir as tmpdir3 } from "os";
|
|
8059
|
-
import { execSync as
|
|
7827
|
+
import { execSync as execSync2 } from "child_process";
|
|
7828
|
+
import { verifyPlugin as verifyPlugin3, safeCleanup as safeCleanup3 } from "@dyyz1993/xcli-core";
|
|
8060
7829
|
async function installFromGit(gitUrl, name, targetDir) {
|
|
8061
|
-
const tmpDir =
|
|
7830
|
+
const tmpDir = join8(tmpdir3(), `xbrowser-git-${Date.now()}`);
|
|
8062
7831
|
let warnings = [];
|
|
8063
7832
|
try {
|
|
8064
|
-
|
|
8065
|
-
const verify =
|
|
7833
|
+
execSync2(`git clone --depth 1 "${gitUrl}" "${tmpDir}"`, { stdio: "pipe" });
|
|
7834
|
+
const verify = verifyPlugin3(tmpDir, { metadataField: "xbrowser" });
|
|
8066
7835
|
warnings = verify.warnings ?? [];
|
|
8067
7836
|
if (!verify.valid) {
|
|
8068
7837
|
throw new Error(`Invalid git plugin: ${verify.error}`);
|
|
8069
7838
|
}
|
|
8070
|
-
if (
|
|
8071
|
-
|
|
7839
|
+
if (existsSync7(targetDir)) {
|
|
7840
|
+
rmSync3(targetDir, { recursive: true, force: true });
|
|
8072
7841
|
}
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
const pkgPath =
|
|
8076
|
-
if (
|
|
8077
|
-
const pkg2 = JSON.parse(
|
|
7842
|
+
cpSync3(tmpDir, targetDir, { recursive: true, force: true });
|
|
7843
|
+
rmSync3(resolve5(targetDir, ".git"), { recursive: true, force: true });
|
|
7844
|
+
const pkgPath = resolve5(targetDir, "package.json");
|
|
7845
|
+
if (existsSync7(pkgPath)) {
|
|
7846
|
+
const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
8078
7847
|
if (!pkg2._gitSource) {
|
|
8079
7848
|
pkg2._gitSource = { url: gitUrl };
|
|
8080
7849
|
writeFileSync7(pkgPath, JSON.stringify(pkg2, null, 2));
|
|
8081
7850
|
}
|
|
8082
7851
|
}
|
|
8083
7852
|
} finally {
|
|
8084
|
-
|
|
7853
|
+
safeCleanup3(tmpDir);
|
|
8085
7854
|
}
|
|
8086
7855
|
return {
|
|
8087
7856
|
id: name,
|
|
@@ -8094,39 +7863,46 @@ async function installFromGit(gitUrl, name, targetDir) {
|
|
|
8094
7863
|
}
|
|
8095
7864
|
|
|
8096
7865
|
// src/plugin/install-sources/url.ts
|
|
8097
|
-
import { existsSync as
|
|
8098
|
-
import { resolve as
|
|
7866
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync8, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
|
|
7867
|
+
import { resolve as resolve6, join as join9, basename } from "path";
|
|
8099
7868
|
import { tmpdir as tmpdir4 } from "os";
|
|
7869
|
+
import {
|
|
7870
|
+
downloadToFile as downloadToFile2,
|
|
7871
|
+
extractTarGz as extractTarGz2,
|
|
7872
|
+
flattenPackageRoot as flattenPackageRoot2,
|
|
7873
|
+
verifyPlugin as verifyPlugin4,
|
|
7874
|
+
safeCleanup as safeCleanup4
|
|
7875
|
+
} from "@dyyz1993/xcli-core";
|
|
8100
7876
|
async function installFromUrl(url, name, targetDir) {
|
|
8101
|
-
const tmpDir =
|
|
7877
|
+
const tmpDir = join9(tmpdir4(), `xbrowser-url-${Date.now()}`);
|
|
8102
7878
|
mkdirSync6(tmpDir, { recursive: true });
|
|
8103
7879
|
let warnings = [];
|
|
8104
7880
|
try {
|
|
8105
7881
|
const fileName = basename(new URL(url).pathname) || "plugin.tar.gz";
|
|
8106
|
-
const tarballPath =
|
|
8107
|
-
await
|
|
8108
|
-
const extractDir =
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
const verify =
|
|
7882
|
+
const tarballPath = join9(tmpDir, fileName);
|
|
7883
|
+
await downloadToFile2(url, tarballPath);
|
|
7884
|
+
const extractDir = join9(tmpDir, "extracted");
|
|
7885
|
+
extractTarGz2(tarballPath, extractDir);
|
|
7886
|
+
flattenPackageRoot2(extractDir);
|
|
7887
|
+
const verify = verifyPlugin4(extractDir, { metadataField: "xbrowser" });
|
|
8112
7888
|
warnings = verify.warnings ?? [];
|
|
8113
7889
|
if (!verify.valid) {
|
|
8114
7890
|
throw new Error(`Invalid plugin from URL: ${verify.error}`);
|
|
8115
7891
|
}
|
|
8116
|
-
if (
|
|
8117
|
-
|
|
7892
|
+
if (existsSync8(targetDir)) {
|
|
7893
|
+
rmSync4(targetDir, { recursive: true, force: true });
|
|
8118
7894
|
}
|
|
8119
|
-
|
|
8120
|
-
const pkgPath =
|
|
8121
|
-
if (
|
|
8122
|
-
const pkg2 = JSON.parse(
|
|
7895
|
+
cpSync4(extractDir, targetDir, { recursive: true, force: true });
|
|
7896
|
+
const pkgPath = resolve6(targetDir, "package.json");
|
|
7897
|
+
if (existsSync8(pkgPath)) {
|
|
7898
|
+
const pkg2 = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
8123
7899
|
if (!pkg2._urlSource) {
|
|
8124
7900
|
pkg2._urlSource = { url };
|
|
8125
7901
|
writeFileSync8(pkgPath, JSON.stringify(pkg2, null, 2));
|
|
8126
7902
|
}
|
|
8127
7903
|
}
|
|
8128
7904
|
} finally {
|
|
8129
|
-
|
|
7905
|
+
safeCleanup4(tmpDir);
|
|
8130
7906
|
}
|
|
8131
7907
|
return {
|
|
8132
7908
|
id: name,
|
|
@@ -8140,16 +7916,23 @@ async function installFromUrl(url, name, targetDir) {
|
|
|
8140
7916
|
|
|
8141
7917
|
// src/plugin/install-sources/marketplace.ts
|
|
8142
7918
|
import {
|
|
8143
|
-
existsSync as
|
|
7919
|
+
existsSync as existsSync9,
|
|
8144
7920
|
mkdirSync as mkdirSync7,
|
|
8145
7921
|
writeFileSync as writeFileSync9,
|
|
8146
|
-
readFileSync as
|
|
8147
|
-
rmSync as
|
|
8148
|
-
cpSync as
|
|
7922
|
+
readFileSync as readFileSync7,
|
|
7923
|
+
rmSync as rmSync5,
|
|
7924
|
+
cpSync as cpSync5
|
|
8149
7925
|
} from "fs";
|
|
8150
|
-
import { resolve as
|
|
7926
|
+
import { resolve as resolve7, join as join10, dirname as dirname2 } from "path";
|
|
8151
7927
|
import { tmpdir as tmpdir5 } from "os";
|
|
8152
7928
|
import { gunzipSync } from "zlib";
|
|
7929
|
+
import {
|
|
7930
|
+
downloadToFile as downloadToFile3,
|
|
7931
|
+
extractTarGz as extractTarGz3,
|
|
7932
|
+
flattenPackageRoot as flattenPackageRoot3,
|
|
7933
|
+
verifyPlugin as verifyPlugin5,
|
|
7934
|
+
safeCleanup as safeCleanup5
|
|
7935
|
+
} from "@dyyz1993/xcli-core";
|
|
8153
7936
|
async function installFromMarketplace(pluginsDir, slug, options) {
|
|
8154
7937
|
await ensureProxyFetch();
|
|
8155
7938
|
const baseUrl = getMarketplaceUrl();
|
|
@@ -8164,24 +7947,24 @@ async function installFromMarketplace(pluginsDir, slug, options) {
|
|
|
8164
7947
|
}
|
|
8165
7948
|
const plugin = detailData.data;
|
|
8166
7949
|
const name = options?.name || String(plugin.slug || slug);
|
|
8167
|
-
const targetDir =
|
|
8168
|
-
if (
|
|
7950
|
+
const targetDir = resolve7(pluginsDir, name);
|
|
7951
|
+
if (existsSync9(targetDir) && !options?.force) {
|
|
8169
7952
|
throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
|
|
8170
7953
|
}
|
|
8171
7954
|
mkdirSync7(targetDir, { recursive: true });
|
|
8172
|
-
const tmpDir =
|
|
7955
|
+
const tmpDir = join10(tmpdir5(), `xbrowser-marketplace-${Date.now()}`);
|
|
8173
7956
|
mkdirSync7(tmpDir, { recursive: true });
|
|
8174
7957
|
const realSlug = String(plugin.slug || slug);
|
|
8175
7958
|
try {
|
|
8176
7959
|
await downloadAndExtractMarketplaceTarball(baseUrl, realSlug, tmpDir, targetDir);
|
|
8177
7960
|
} finally {
|
|
8178
|
-
|
|
7961
|
+
safeCleanup5(tmpDir);
|
|
8179
7962
|
}
|
|
8180
7963
|
writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir);
|
|
8181
7964
|
ensureIndexFile(plugin, name, targetDir);
|
|
8182
|
-
const verify =
|
|
7965
|
+
const verify = verifyPlugin5(targetDir, { metadataField: "xbrowser" });
|
|
8183
7966
|
if (!verify.valid) {
|
|
8184
|
-
|
|
7967
|
+
safeCleanup5(targetDir);
|
|
8185
7968
|
throw new Error(`Invalid marketplace plugin: ${verify.error}`);
|
|
8186
7969
|
}
|
|
8187
7970
|
const trackUrl = `${baseUrl}/api/plugins/${realSlug}/install`;
|
|
@@ -8200,12 +7983,12 @@ function isManifestArray(data) {
|
|
|
8200
7983
|
return Array.isArray(data) && data.length > 0 && typeof data[0].path === "string" && typeof data[0].content === "string";
|
|
8201
7984
|
}
|
|
8202
7985
|
function extractManifestToDir(manifest, targetDir) {
|
|
8203
|
-
if (
|
|
8204
|
-
|
|
7986
|
+
if (existsSync9(targetDir)) {
|
|
7987
|
+
rmSync5(targetDir, { recursive: true, force: true });
|
|
8205
7988
|
}
|
|
8206
7989
|
mkdirSync7(targetDir, { recursive: true });
|
|
8207
7990
|
for (const file of manifest) {
|
|
8208
|
-
const filePath =
|
|
7991
|
+
const filePath = resolve7(targetDir, file.path);
|
|
8209
7992
|
mkdirSync7(dirname2(filePath), { recursive: true });
|
|
8210
7993
|
writeFileSync9(filePath, Buffer.from(file.content, "base64"));
|
|
8211
7994
|
}
|
|
@@ -8232,21 +8015,21 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
|
|
|
8232
8015
|
}
|
|
8233
8016
|
if (tarballRes.status === 302 || tarballRes.headers.get("location")) {
|
|
8234
8017
|
const redirectUrl = tarballRes.headers.get("location");
|
|
8235
|
-
const tarballPath =
|
|
8236
|
-
await
|
|
8237
|
-
const buffer =
|
|
8018
|
+
const tarballPath = join10(tmpDir, `${slug}.tar.gz`);
|
|
8019
|
+
await downloadToFile3(redirectUrl, tarballPath);
|
|
8020
|
+
const buffer = readFileSync7(tarballPath);
|
|
8238
8021
|
const manifest = tryParseAsGzippedManifest(buffer);
|
|
8239
8022
|
if (manifest) {
|
|
8240
8023
|
extractManifestToDir(manifest, targetDir);
|
|
8241
8024
|
return;
|
|
8242
8025
|
}
|
|
8243
|
-
const extractDir =
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
if (
|
|
8247
|
-
|
|
8026
|
+
const extractDir = join10(tmpDir, "extracted");
|
|
8027
|
+
extractTarGz3(tarballPath, extractDir);
|
|
8028
|
+
flattenPackageRoot3(extractDir);
|
|
8029
|
+
if (existsSync9(targetDir)) {
|
|
8030
|
+
rmSync5(targetDir, { recursive: true, force: true });
|
|
8248
8031
|
}
|
|
8249
|
-
|
|
8032
|
+
cpSync5(extractDir, targetDir, { recursive: true, force: true });
|
|
8250
8033
|
} else {
|
|
8251
8034
|
const buffer = Buffer.from(await tarballRes.arrayBuffer());
|
|
8252
8035
|
const manifest = tryParseAsGzippedManifest(buffer);
|
|
@@ -8254,16 +8037,16 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
|
|
|
8254
8037
|
extractManifestToDir(manifest, targetDir);
|
|
8255
8038
|
return;
|
|
8256
8039
|
}
|
|
8257
|
-
const tarballPath =
|
|
8040
|
+
const tarballPath = join10(tmpDir, `${slug}.tar.gz`);
|
|
8258
8041
|
writeFileSync9(tarballPath, buffer);
|
|
8259
8042
|
try {
|
|
8260
|
-
const extractDir =
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
if (
|
|
8264
|
-
|
|
8043
|
+
const extractDir = join10(tmpDir, "extracted");
|
|
8044
|
+
extractTarGz3(tarballPath, extractDir);
|
|
8045
|
+
flattenPackageRoot3(extractDir);
|
|
8046
|
+
if (existsSync9(targetDir)) {
|
|
8047
|
+
rmSync5(targetDir, { recursive: true, force: true });
|
|
8265
8048
|
}
|
|
8266
|
-
|
|
8049
|
+
cpSync5(extractDir, targetDir, { recursive: true, force: true });
|
|
8267
8050
|
} catch {
|
|
8268
8051
|
throw new Error(
|
|
8269
8052
|
`Downloaded tarball for "${slug}" is neither a gzipped JSON manifest nor a valid tar.gz archive.`
|
|
@@ -8295,12 +8078,12 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
|
|
|
8295
8078
|
url: baseUrl
|
|
8296
8079
|
}
|
|
8297
8080
|
};
|
|
8298
|
-
const pkgPath =
|
|
8299
|
-
if (!
|
|
8081
|
+
const pkgPath = resolve7(targetDir, "package.json");
|
|
8082
|
+
if (!existsSync9(pkgPath)) {
|
|
8300
8083
|
writeFileSync9(pkgPath, JSON.stringify(packageJson, null, 2));
|
|
8301
8084
|
} else {
|
|
8302
8085
|
try {
|
|
8303
|
-
const existing = JSON.parse(
|
|
8086
|
+
const existing = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
8304
8087
|
const merged = {
|
|
8305
8088
|
...existing,
|
|
8306
8089
|
xbrowser: { ...existing.xbrowser, ...packageJson.xbrowser },
|
|
@@ -8313,7 +8096,7 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
|
|
|
8313
8096
|
}
|
|
8314
8097
|
}
|
|
8315
8098
|
function ensureIndexFile(plugin, name, targetDir) {
|
|
8316
|
-
if (!
|
|
8099
|
+
if (!existsSync9(resolve7(targetDir, "index.ts")) && !existsSync9(resolve7(targetDir, "index.js"))) {
|
|
8317
8100
|
const commands = plugin.commands || [];
|
|
8318
8101
|
const commandHandlers = commands.length > 0 ? commands.map((cmd) => {
|
|
8319
8102
|
return [
|
|
@@ -8329,7 +8112,7 @@ function ensureIndexFile(plugin, name, targetDir) {
|
|
|
8329
8112
|
` });`
|
|
8330
8113
|
].join("\n");
|
|
8331
8114
|
writeFileSync9(
|
|
8332
|
-
|
|
8115
|
+
resolve7(targetDir, "index.ts"),
|
|
8333
8116
|
[
|
|
8334
8117
|
`import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
|
|
8335
8118
|
``,
|
|
@@ -8350,7 +8133,7 @@ function ensureIndexFile(plugin, name, targetDir) {
|
|
|
8350
8133
|
var PluginInstaller = class {
|
|
8351
8134
|
pluginsDir;
|
|
8352
8135
|
constructor(pluginsDir) {
|
|
8353
|
-
this.pluginsDir = pluginsDir ||
|
|
8136
|
+
this.pluginsDir = pluginsDir || resolve8(homedir7(), ".xbrowser/plugins");
|
|
8354
8137
|
}
|
|
8355
8138
|
getPluginsDir() {
|
|
8356
8139
|
return this.pluginsDir;
|
|
@@ -8366,8 +8149,8 @@ var PluginInstaller = class {
|
|
|
8366
8149
|
async install(source, options) {
|
|
8367
8150
|
const type = this.detectSourceType(source);
|
|
8368
8151
|
const name = options?.name || this.deriveName(source, type);
|
|
8369
|
-
const targetDir =
|
|
8370
|
-
if (
|
|
8152
|
+
const targetDir = resolve8(this.pluginsDir, name);
|
|
8153
|
+
if (existsSync10(targetDir) && !options?.force) {
|
|
8371
8154
|
throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
|
|
8372
8155
|
}
|
|
8373
8156
|
mkdirSync8(targetDir, { recursive: true });
|
|
@@ -8427,11 +8210,11 @@ var PluginInstaller = class {
|
|
|
8427
8210
|
* @throws If the plugin is not installed.
|
|
8428
8211
|
*/
|
|
8429
8212
|
async uninstall(name) {
|
|
8430
|
-
const targetDir =
|
|
8431
|
-
if (!
|
|
8213
|
+
const targetDir = resolve8(this.pluginsDir, name);
|
|
8214
|
+
if (!existsSync10(targetDir)) {
|
|
8432
8215
|
throw new Error(`Plugin "${name}" not found`);
|
|
8433
8216
|
}
|
|
8434
|
-
|
|
8217
|
+
rmSync6(targetDir, { recursive: true, force: true });
|
|
8435
8218
|
}
|
|
8436
8219
|
/**
|
|
8437
8220
|
* List all installed plugins with metadata.
|
|
@@ -8439,18 +8222,18 @@ var PluginInstaller = class {
|
|
|
8439
8222
|
* @returns Array of installed plugin information.
|
|
8440
8223
|
*/
|
|
8441
8224
|
async list(_options) {
|
|
8442
|
-
if (!
|
|
8443
|
-
const entries =
|
|
8225
|
+
if (!existsSync10(this.pluginsDir)) return [];
|
|
8226
|
+
const entries = readdirSync2(this.pluginsDir, { withFileTypes: true });
|
|
8444
8227
|
const plugins = [];
|
|
8445
8228
|
for (const entry of entries) {
|
|
8446
8229
|
if (!entry.isDirectory()) continue;
|
|
8447
|
-
const pluginPath =
|
|
8448
|
-
const indexPath =
|
|
8449
|
-
const indexJsPath =
|
|
8450
|
-
if (!
|
|
8230
|
+
const pluginPath = resolve8(this.pluginsDir, entry.name);
|
|
8231
|
+
const indexPath = resolve8(pluginPath, "index.ts");
|
|
8232
|
+
const indexJsPath = resolve8(pluginPath, "index.js");
|
|
8233
|
+
if (!existsSync10(indexPath) && !existsSync10(indexJsPath)) continue;
|
|
8451
8234
|
const metadata = PluginMetadataParser.parseFromPackageJson(pluginPath);
|
|
8452
8235
|
let source = "local";
|
|
8453
|
-
const pkg2 = readJsonFile(
|
|
8236
|
+
const pkg2 = readJsonFile(resolve8(pluginPath, "package.json"), {});
|
|
8454
8237
|
if (pkg2._marketplace) source = "marketplace";
|
|
8455
8238
|
else if (pkg2._npmSource) source = "npm";
|
|
8456
8239
|
else if (pkg2._gitSource) source = "git";
|
|
@@ -8474,10 +8257,10 @@ var PluginInstaller = class {
|
|
|
8474
8257
|
}
|
|
8475
8258
|
if (source.startsWith("file://")) {
|
|
8476
8259
|
const filePath = decodeURIComponent(new URL(source).pathname);
|
|
8477
|
-
if (
|
|
8260
|
+
if (existsSync10(filePath)) return "url";
|
|
8478
8261
|
}
|
|
8479
8262
|
if (source.endsWith(".git") || source.includes("github.com/")) return "git";
|
|
8480
|
-
if (
|
|
8263
|
+
if (existsSync10(resolve8(source))) return "local";
|
|
8481
8264
|
return "npm";
|
|
8482
8265
|
}
|
|
8483
8266
|
deriveName(source, type) {
|
|
@@ -8879,7 +8662,7 @@ async function searchFromMarketplacePlugin(options, loader) {
|
|
|
8879
8662
|
site: options.site,
|
|
8880
8663
|
limit: options.limit
|
|
8881
8664
|
},
|
|
8882
|
-
|
|
8665
|
+
createStubContext("marketplace")
|
|
8883
8666
|
);
|
|
8884
8667
|
const items = extractItems(result);
|
|
8885
8668
|
return items.map((item) => ({
|
|
@@ -9311,7 +9094,7 @@ var previewBuiltin = {
|
|
|
9311
9094
|
if (options.json) {
|
|
9312
9095
|
outputResult({ running: false }, "json");
|
|
9313
9096
|
} else {
|
|
9314
|
-
console.log("Daemon is not running.
|
|
9097
|
+
console.log("Daemon is not running. It will start automatically when needed.");
|
|
9315
9098
|
console.log("");
|
|
9316
9099
|
console.log("Preview is automatically available when the daemon is running.");
|
|
9317
9100
|
}
|
|
@@ -9335,9 +9118,199 @@ var previewBuiltin = {
|
|
|
9335
9118
|
}
|
|
9336
9119
|
};
|
|
9337
9120
|
|
|
9121
|
+
// src/builtins/knowledge.ts
|
|
9122
|
+
init_site_knowledge();
|
|
9123
|
+
import { existsSync as existsSync12 } from "fs";
|
|
9124
|
+
var knowledgeBuiltin = {
|
|
9125
|
+
name: "knowledge",
|
|
9126
|
+
description: "View LLM-readable site knowledge base (selectors, forms, APIs)",
|
|
9127
|
+
aliases: ["know"],
|
|
9128
|
+
help: {
|
|
9129
|
+
usage: "xbrowser knowledge <list|show|search|issue|path> [domain] [options]",
|
|
9130
|
+
description: "Manage auto-generated site knowledge from recordings. Knowledge is stored at ~/.xbrowser/knowledge/{domain}.md and is designed for LLM consumption.",
|
|
9131
|
+
options: [
|
|
9132
|
+
{ name: "list", description: "List all domains with knowledge bases" },
|
|
9133
|
+
{ name: "show <domain>", description: "Show full knowledge for a domain (markdown)" },
|
|
9134
|
+
{ name: "search <domain> <query>", description: "Search selectors/APIs by keyword" },
|
|
9135
|
+
{ name: "selectors <domain>", description: "List all selectors for a domain" },
|
|
9136
|
+
{ name: "api <domain>", description: "List all API endpoints for a domain" },
|
|
9137
|
+
{ name: "issue <domain> <text>", description: "Add a known issue to a domain" },
|
|
9138
|
+
{ name: "path <domain>", description: "Show file path for a domain's knowledge" }
|
|
9139
|
+
],
|
|
9140
|
+
examples: [
|
|
9141
|
+
{ cmd: "xbrowser knowledge list", description: "List all known sites" },
|
|
9142
|
+
{ cmd: "xbrowser knowledge show juejin.cn", description: "Show juejin.cn knowledge" },
|
|
9143
|
+
{ cmd: "xbrowser knowledge search juejin.cn publish", description: 'Find selectors related to "publish"' },
|
|
9144
|
+
{ cmd: "xbrowser knowledge selectors juejin.cn", description: "List all selectors" },
|
|
9145
|
+
{ cmd: "xbrowser knowledge api juejin.cn", description: "List API endpoints" },
|
|
9146
|
+
{ cmd: 'xbrowser knowledge issue juejin.cn "Title selector changed"', description: "Report an issue" }
|
|
9147
|
+
]
|
|
9148
|
+
},
|
|
9149
|
+
execute: async (args, _options, _ctx) => {
|
|
9150
|
+
const [subcommand, ...rest] = args;
|
|
9151
|
+
if (!subcommand || subcommand === "list") {
|
|
9152
|
+
const domains = listSiteKnowledge();
|
|
9153
|
+
if (domains.length === 0) {
|
|
9154
|
+
console.log("No site knowledge bases found.");
|
|
9155
|
+
console.log("Knowledge is auto-generated when you run `xbrowser record stop`.");
|
|
9156
|
+
return;
|
|
9157
|
+
}
|
|
9158
|
+
console.log("Site Knowledge Bases:");
|
|
9159
|
+
console.log("");
|
|
9160
|
+
for (const domain of domains) {
|
|
9161
|
+
const kb = readSiteKnowledge(domain);
|
|
9162
|
+
if (kb) {
|
|
9163
|
+
const pageCount = Object.keys(kb.pages).length;
|
|
9164
|
+
const selCount = Object.values(kb.pages).reduce((sum, p) => sum + p.selectors.length, 0);
|
|
9165
|
+
const apiCount = Object.keys(kb.apiEndpoints).length;
|
|
9166
|
+
console.log(
|
|
9167
|
+
` ${domain} \u2014 ${kb.recordingCount} recordings, ${pageCount} pages, ${selCount} selectors, ${apiCount} APIs`
|
|
9168
|
+
);
|
|
9169
|
+
}
|
|
9170
|
+
}
|
|
9171
|
+
return;
|
|
9172
|
+
}
|
|
9173
|
+
if (subcommand === "show") {
|
|
9174
|
+
const domain = rest[0];
|
|
9175
|
+
if (!domain) {
|
|
9176
|
+
console.error("Usage: xbrowser knowledge show <domain>");
|
|
9177
|
+
process.exit(1);
|
|
9178
|
+
}
|
|
9179
|
+
const md = readSiteKnowledgeMarkdown(domain);
|
|
9180
|
+
if (!md) {
|
|
9181
|
+
console.error(`No knowledge base found for ${domain}`);
|
|
9182
|
+
console.error("Run `xbrowser knowledge list` to see available domains.");
|
|
9183
|
+
process.exit(1);
|
|
9184
|
+
}
|
|
9185
|
+
console.log(md);
|
|
9186
|
+
return;
|
|
9187
|
+
}
|
|
9188
|
+
if (subcommand === "selectors") {
|
|
9189
|
+
const domain = rest[0];
|
|
9190
|
+
if (!domain) {
|
|
9191
|
+
console.error("Usage: xbrowser knowledge selectors <domain>");
|
|
9192
|
+
process.exit(1);
|
|
9193
|
+
}
|
|
9194
|
+
const kb = readSiteKnowledge(domain);
|
|
9195
|
+
if (!kb) {
|
|
9196
|
+
console.error(`No knowledge base found for ${domain}`);
|
|
9197
|
+
process.exit(1);
|
|
9198
|
+
}
|
|
9199
|
+
console.log(`Selectors for ${domain} (${kb.recordingCount} recordings):`);
|
|
9200
|
+
console.log("");
|
|
9201
|
+
for (const [pagePath, page] of Object.entries(kb.pages)) {
|
|
9202
|
+
if (page.selectors.length === 0) continue;
|
|
9203
|
+
console.log(` ${pagePath}:`);
|
|
9204
|
+
for (const sel of page.selectors) {
|
|
9205
|
+
const status = sel.status === "deprecated" ? " \u26A0\uFE0F" : "";
|
|
9206
|
+
console.log(
|
|
9207
|
+
` ${sel.selector.padEnd(30)} ${sel.tag.padEnd(8)} ${sel.actionType.padEnd(10)} ${sel.confidence.padEnd(6)} ${sel.timesSeen}x${status}`
|
|
9208
|
+
);
|
|
9209
|
+
if (sel.description) console.log(` \u2192 ${sel.description}`);
|
|
9210
|
+
}
|
|
9211
|
+
console.log("");
|
|
9212
|
+
}
|
|
9213
|
+
return;
|
|
9214
|
+
}
|
|
9215
|
+
if (subcommand === "api") {
|
|
9216
|
+
const domain = rest[0];
|
|
9217
|
+
if (!domain) {
|
|
9218
|
+
console.error("Usage: xbrowser knowledge api <domain>");
|
|
9219
|
+
process.exit(1);
|
|
9220
|
+
}
|
|
9221
|
+
const kb = readSiteKnowledge(domain);
|
|
9222
|
+
if (!kb) {
|
|
9223
|
+
console.error(`No knowledge base found for ${domain}`);
|
|
9224
|
+
process.exit(1);
|
|
9225
|
+
}
|
|
9226
|
+
const endpoints = Object.values(kb.apiEndpoints);
|
|
9227
|
+
if (endpoints.length === 0) {
|
|
9228
|
+
console.log(`No API endpoints recorded for ${domain}`);
|
|
9229
|
+
return;
|
|
9230
|
+
}
|
|
9231
|
+
console.log(`API Endpoints for ${domain}:`);
|
|
9232
|
+
console.log("");
|
|
9233
|
+
for (const ep of endpoints.sort((a, b) => b.timesSeen - a.timesSeen)) {
|
|
9234
|
+
const params = ep.params.length > 0 ? ep.params.join(", ") : "-";
|
|
9235
|
+
console.log(` ${ep.method} ${ep.path} (${ep.timesSeen}x)`);
|
|
9236
|
+
console.log(` Params: ${params}`);
|
|
9237
|
+
if (ep.responseFields.length > 0) {
|
|
9238
|
+
console.log(` Response: ${ep.responseFields.slice(0, 5).join(", ")}`);
|
|
9239
|
+
}
|
|
9240
|
+
console.log("");
|
|
9241
|
+
}
|
|
9242
|
+
return;
|
|
9243
|
+
}
|
|
9244
|
+
if (subcommand === "search") {
|
|
9245
|
+
const domain = rest[0];
|
|
9246
|
+
const query = rest.slice(1).join(" ").toLowerCase();
|
|
9247
|
+
if (!domain || !query) {
|
|
9248
|
+
console.error("Usage: xbrowser knowledge search <domain> <query>");
|
|
9249
|
+
process.exit(1);
|
|
9250
|
+
}
|
|
9251
|
+
const kb = readSiteKnowledge(domain);
|
|
9252
|
+
if (!kb) {
|
|
9253
|
+
console.error(`No knowledge base found for ${domain}`);
|
|
9254
|
+
process.exit(1);
|
|
9255
|
+
}
|
|
9256
|
+
console.log(`Search results for "${query}" in ${domain}:`);
|
|
9257
|
+
console.log("");
|
|
9258
|
+
let found = 0;
|
|
9259
|
+
for (const [pagePath, page] of Object.entries(kb.pages)) {
|
|
9260
|
+
const matches = page.selectors.filter(
|
|
9261
|
+
(s) => s.selector.toLowerCase().includes(query) || s.description.toLowerCase().includes(query) || (s.text || "").toLowerCase().includes(query)
|
|
9262
|
+
);
|
|
9263
|
+
for (const m of matches) {
|
|
9264
|
+
console.log(` [${pagePath}] ${m.selector} \u2192 ${m.description} (${m.actionType}, ${m.confidence}, ${m.timesSeen}x)`);
|
|
9265
|
+
found++;
|
|
9266
|
+
}
|
|
9267
|
+
}
|
|
9268
|
+
for (const ep of Object.values(kb.apiEndpoints)) {
|
|
9269
|
+
if (ep.path.toLowerCase().includes(query) || ep.params.some((p) => p.toLowerCase().includes(query))) {
|
|
9270
|
+
console.log(` [API] ${ep.method} ${ep.path} (${ep.timesSeen}x)`);
|
|
9271
|
+
found++;
|
|
9272
|
+
}
|
|
9273
|
+
}
|
|
9274
|
+
if (found === 0) {
|
|
9275
|
+
console.log(" No matches found.");
|
|
9276
|
+
}
|
|
9277
|
+
return;
|
|
9278
|
+
}
|
|
9279
|
+
if (subcommand === "issue") {
|
|
9280
|
+
const domain = rest[0];
|
|
9281
|
+
const text = rest.slice(1).join(" ");
|
|
9282
|
+
if (!domain || !text) {
|
|
9283
|
+
console.error("Usage: xbrowser knowledge issue <domain> <description>");
|
|
9284
|
+
process.exit(1);
|
|
9285
|
+
}
|
|
9286
|
+
if (!readSiteKnowledge(domain)) {
|
|
9287
|
+
console.error(`No knowledge base found for ${domain}`);
|
|
9288
|
+
process.exit(1);
|
|
9289
|
+
}
|
|
9290
|
+
addKnownIssue(domain, text);
|
|
9291
|
+
console.log(`Added issue to ${domain}: ${text}`);
|
|
9292
|
+
return;
|
|
9293
|
+
}
|
|
9294
|
+
if (subcommand === "path") {
|
|
9295
|
+
const domain = rest[0];
|
|
9296
|
+
if (!domain) {
|
|
9297
|
+
console.error("Usage: xbrowser knowledge path <domain>");
|
|
9298
|
+
process.exit(1);
|
|
9299
|
+
}
|
|
9300
|
+
const mdPath = getKnowledgePath(domain, "md");
|
|
9301
|
+
const jsonPath = getKnowledgePath(domain, "json");
|
|
9302
|
+
console.log(`Markdown: ${mdPath} (${existsSync12(mdPath) ? "exists" : "not found"})`);
|
|
9303
|
+
console.log(`JSON: ${jsonPath} (${existsSync12(jsonPath) ? "exists" : "not found"})`);
|
|
9304
|
+
return;
|
|
9305
|
+
}
|
|
9306
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
|
9307
|
+
console.error("Usage: xbrowser knowledge <list|show|selectors|api|search|issue|path>");
|
|
9308
|
+
process.exit(1);
|
|
9309
|
+
}
|
|
9310
|
+
};
|
|
9311
|
+
|
|
9338
9312
|
// src/builtins/index.ts
|
|
9339
9313
|
var allBuiltins = [
|
|
9340
|
-
sessionOpenBuiltin,
|
|
9341
9314
|
sessionCloseBuiltin,
|
|
9342
9315
|
sessionListBuiltin,
|
|
9343
9316
|
sessionKillBuiltin,
|
|
@@ -9348,7 +9321,8 @@ var allBuiltins = [
|
|
|
9348
9321
|
pluginListBuiltin,
|
|
9349
9322
|
pluginReloadBuiltin,
|
|
9350
9323
|
createBuiltin,
|
|
9351
|
-
previewBuiltin
|
|
9324
|
+
previewBuiltin,
|
|
9325
|
+
knowledgeBuiltin
|
|
9352
9326
|
];
|
|
9353
9327
|
|
|
9354
9328
|
// src/utils/selector.ts
|
|
@@ -9464,16 +9438,16 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9464
9438
|
if (cmdDef) {
|
|
9465
9439
|
if (mode === "json") {
|
|
9466
9440
|
const paramsList = [];
|
|
9467
|
-
const schema = cmdDef.parameters;
|
|
9441
|
+
const schema = asZodSchema(cmdDef.parameters);
|
|
9468
9442
|
const shape = schema?.shape ?? schema?._def?.shape;
|
|
9469
9443
|
if (shape) {
|
|
9470
9444
|
for (const [key, value] of Object.entries(shape)) {
|
|
9471
|
-
const fieldSchema = value;
|
|
9445
|
+
const fieldSchema = asZodSchema(value);
|
|
9472
9446
|
const fieldDef = fieldSchema._def;
|
|
9473
9447
|
const description = fieldSchema.description || fieldDef?.description || "";
|
|
9474
9448
|
const typeName = fieldDef?.typeName || "";
|
|
9475
9449
|
const isOptional = typeName === "ZodOptional" || typeof fieldSchema.isOptional === "function" && fieldSchema.isOptional();
|
|
9476
|
-
const innerType = fieldDef?.innerType;
|
|
9450
|
+
const innerType = asZodSchema(fieldDef?.innerType);
|
|
9477
9451
|
const innerTypeName = innerType?._def ? innerType._def.typeName : typeName;
|
|
9478
9452
|
let type = "unknown";
|
|
9479
9453
|
if (innerTypeName === "ZodString" || typeName === "ZodString") type = "string";
|
|
@@ -9571,7 +9545,8 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9571
9545
|
} else {
|
|
9572
9546
|
switch (command) {
|
|
9573
9547
|
case "goto":
|
|
9574
|
-
|
|
9548
|
+
case "open":
|
|
9549
|
+
if (!args[0]) outputError(`Usage: xbrowser ${command} <url>`);
|
|
9575
9550
|
cmdName = "goto";
|
|
9576
9551
|
params = {
|
|
9577
9552
|
url: /^https?:\/\//i.test(args[0]) || /^wss?:\/\//i.test(args[0]) ? args[0] : "https://" + args[0],
|
|
@@ -9583,7 +9558,9 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9583
9558
|
params = {
|
|
9584
9559
|
fullPage: !!(options["full-page"] || options.fullPage),
|
|
9585
9560
|
type: options.type,
|
|
9586
|
-
selector: options.selector || options.s
|
|
9561
|
+
selector: options.selector || options.s,
|
|
9562
|
+
base64: !!options.base64,
|
|
9563
|
+
output: options.output || options.o
|
|
9587
9564
|
};
|
|
9588
9565
|
break;
|
|
9589
9566
|
case "eval":
|
|
@@ -9660,7 +9637,7 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9660
9637
|
let parsedActions;
|
|
9661
9638
|
if (options.action) {
|
|
9662
9639
|
const actionList = Array.isArray(options.action) ? options.action : [options.action];
|
|
9663
|
-
const { parseActionDsl } = await import("./parse-action-dsl-
|
|
9640
|
+
const { parseActionDsl } = await import("./parse-action-dsl-UM333TL2.js");
|
|
9664
9641
|
parsedActions = actionList.map((a) => parseActionDsl(a));
|
|
9665
9642
|
} else if (options["actions-file"]) {
|
|
9666
9643
|
const fs3 = await import("fs");
|
|
@@ -9788,36 +9765,27 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9788
9765
|
}
|
|
9789
9766
|
|
|
9790
9767
|
// src/cli/session-routes.ts
|
|
9791
|
-
import { homedir as
|
|
9792
|
-
import { join as
|
|
9793
|
-
import { readdirSync as
|
|
9768
|
+
import { homedir as homedir8 } from "os";
|
|
9769
|
+
import { join as join12 } from "path";
|
|
9770
|
+
import { readdirSync as readdirSync3, rmSync as rmSync7 } from "fs";
|
|
9794
9771
|
function cleanSessionFiles() {
|
|
9795
|
-
const dir =
|
|
9772
|
+
const dir = join12(homedir8(), ".xbrowser", "sessions");
|
|
9796
9773
|
let count = 0;
|
|
9797
9774
|
try {
|
|
9798
|
-
for (const entry of
|
|
9799
|
-
const p =
|
|
9800
|
-
|
|
9775
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
9776
|
+
const p = join12(dir, entry.name);
|
|
9777
|
+
rmSync7(p, { recursive: true, force: true });
|
|
9801
9778
|
count++;
|
|
9802
9779
|
}
|
|
9803
9780
|
} catch {
|
|
9804
9781
|
}
|
|
9805
9782
|
return count;
|
|
9806
9783
|
}
|
|
9807
|
-
async function handleSession(args, options, mode,
|
|
9784
|
+
async function handleSession(args, options, mode, _cdpEndpoint) {
|
|
9808
9785
|
const sub = args[0];
|
|
9809
9786
|
switch (sub) {
|
|
9810
|
-
case "open": {
|
|
9811
|
-
const url = args[1];
|
|
9812
|
-
const name = options.name || process.env.XBROWSER_SESSION || "default";
|
|
9813
|
-
if (!url)
|
|
9814
|
-
outputError("Usage: xbrowser session open <url> [--name <name>] [--cdp <endpoint>]");
|
|
9815
|
-
const info = await forwardSessionCreate(name, url, cdpEndpoint);
|
|
9816
|
-
outputResult({ ok: true, ...info }, mode);
|
|
9817
|
-
break;
|
|
9818
|
-
}
|
|
9819
9787
|
case "close": {
|
|
9820
|
-
const name = options.name || process.env.XBROWSER_SESSION || "default";
|
|
9788
|
+
const name = options.session || options.name || process.env.XBROWSER_SESSION || "default";
|
|
9821
9789
|
try {
|
|
9822
9790
|
await forwardSessionClose(name);
|
|
9823
9791
|
} catch {
|
|
@@ -9838,7 +9806,7 @@ async function handleSession(args, options, mode, cdpEndpoint) {
|
|
|
9838
9806
|
break;
|
|
9839
9807
|
}
|
|
9840
9808
|
case "kill": {
|
|
9841
|
-
const name = options.name || process.env.XBROWSER_SESSION || "default";
|
|
9809
|
+
const name = options.session || options.name || process.env.XBROWSER_SESSION || "default";
|
|
9842
9810
|
try {
|
|
9843
9811
|
await forwardSessionClose(name);
|
|
9844
9812
|
} catch {
|
|
@@ -9889,8 +9857,7 @@ async function buildRuntimePluginInfo() {
|
|
|
9889
9857
|
const cmds = site.getAllCommands();
|
|
9890
9858
|
const commandNames = cmds.map((c) => c.name);
|
|
9891
9859
|
if (commandNames.length === 0) continue;
|
|
9892
|
-
const
|
|
9893
|
-
const hasLoginHandler = typeof anySite.hasLoginCommand === "function" && anySite.hasLoginCommand();
|
|
9860
|
+
const hasLoginHandler = "hasLoginCommand" in site && typeof site.hasLoginCommand === "function" && site.hasLoginCommand();
|
|
9894
9861
|
const configRequiresLogin = !!site.config.requiresLogin;
|
|
9895
9862
|
const hasLogin = hasLoginHandler || configRequiresLogin;
|
|
9896
9863
|
let loggedIn = null;
|
|
@@ -9940,7 +9907,7 @@ async function searchFromMarketplacePlugin2(options, loader) {
|
|
|
9940
9907
|
site: options.site,
|
|
9941
9908
|
limit: options.limit
|
|
9942
9909
|
},
|
|
9943
|
-
|
|
9910
|
+
createStubContext("marketplace")
|
|
9944
9911
|
);
|
|
9945
9912
|
const items = extractItems2(result);
|
|
9946
9913
|
return items.map((item) => ({ ...item, source: "marketplace" }));
|
|
@@ -9955,7 +9922,7 @@ async function infoFromMarketplacePlugin(slug, loader) {
|
|
|
9955
9922
|
const infoCmd = marketplaceSite.getCommand("info");
|
|
9956
9923
|
if (!infoCmd) return null;
|
|
9957
9924
|
try {
|
|
9958
|
-
const result = await infoCmd.handler({ slug },
|
|
9925
|
+
const result = await infoCmd.handler({ slug }, createStubContext("marketplace"));
|
|
9959
9926
|
if (!result || typeof result !== "object") return null;
|
|
9960
9927
|
const r = result;
|
|
9961
9928
|
let plugin = null;
|
|
@@ -10063,7 +10030,7 @@ async function handlePluginInfo(args, options, mode) {
|
|
|
10063
10030
|
}
|
|
10064
10031
|
console.error(`\u63D2\u4EF6 '${slug}' \u672A\u627E\u5230`);
|
|
10065
10032
|
} catch (err) {
|
|
10066
|
-
console.error("\u67E5\u8BE2\u5931\u8D25:", err
|
|
10033
|
+
console.error("\u67E5\u8BE2\u5931\u8D25:", errMsg(err));
|
|
10067
10034
|
}
|
|
10068
10035
|
}
|
|
10069
10036
|
async function handlePluginSchema(args, mode) {
|
|
@@ -10256,7 +10223,7 @@ function handleDaemon(args, options, mode) {
|
|
|
10256
10223
|
break;
|
|
10257
10224
|
}
|
|
10258
10225
|
default:
|
|
10259
|
-
console.log("
|
|
10226
|
+
console.log("Daemon starts automatically. No manual action needed.");
|
|
10260
10227
|
}
|
|
10261
10228
|
}
|
|
10262
10229
|
|
|
@@ -10267,7 +10234,8 @@ async function handleRecord(args, options, mode) {
|
|
|
10267
10234
|
case "start": {
|
|
10268
10235
|
const url = options.url;
|
|
10269
10236
|
const sessionName = options.session || "default";
|
|
10270
|
-
const
|
|
10237
|
+
const cdpEndpoint = options.cdp;
|
|
10238
|
+
const result = await forwardRecordStart(sessionName, url, cdpEndpoint);
|
|
10271
10239
|
if (!result.ok) {
|
|
10272
10240
|
outputError(String(result.error || "Failed to start recording"));
|
|
10273
10241
|
return;
|
|
@@ -10351,6 +10319,13 @@ async function handleRecord(args, options, mode) {
|
|
|
10351
10319
|
outputResult(result, mode);
|
|
10352
10320
|
break;
|
|
10353
10321
|
}
|
|
10322
|
+
case "generate-plugin": {
|
|
10323
|
+
const sessionName = options.session || args[1] || "default";
|
|
10324
|
+
const pluginName = options.name || "";
|
|
10325
|
+
const outputDir = options.output || "";
|
|
10326
|
+
await handleGeneratePlugin(sessionName, pluginName, outputDir);
|
|
10327
|
+
break;
|
|
10328
|
+
}
|
|
10354
10329
|
default:
|
|
10355
10330
|
console.log("Usage:");
|
|
10356
10331
|
console.log(" xbrowser record start [--url <url>] [--session <name>]");
|
|
@@ -10358,6 +10333,7 @@ async function handleRecord(args, options, mode) {
|
|
|
10358
10333
|
console.log(" xbrowser record status [--session <name>]");
|
|
10359
10334
|
console.log(" xbrowser record summary [--session <name>] [--json]");
|
|
10360
10335
|
console.log(' xbrowser record checkpoint --type <type> --hint "description" [--selector <sel>] [--session <name>]');
|
|
10336
|
+
console.log(" xbrowser record generate-plugin [--session <name>] [--name <plugin>] [--output <dir>]");
|
|
10361
10337
|
console.log("");
|
|
10362
10338
|
console.log("Checkpoint types: dialog, captcha, login, iframe, slider, custom");
|
|
10363
10339
|
}
|
|
@@ -10546,7 +10522,7 @@ async function handleConvert(args, _mode) {
|
|
|
10546
10522
|
const fs3 = await import("fs");
|
|
10547
10523
|
const path3 = await import("path");
|
|
10548
10524
|
const { default: yaml } = await import("yaml");
|
|
10549
|
-
const { generateJSScript, generatePythonScript, generateBashScript } = await import("./convert-
|
|
10525
|
+
const { generateJSScript, generatePythonScript, generateBashScript } = await import("./convert-LB3GJTLR.js");
|
|
10550
10526
|
const content = fs3.readFileSync(filePath, "utf-8");
|
|
10551
10527
|
const recording = yaml.parse(content);
|
|
10552
10528
|
const ext = path3.extname(outputPath).toLowerCase();
|
|
@@ -10571,7 +10547,7 @@ async function handleExtract(args, _mode) {
|
|
|
10571
10547
|
console.error("Usage: xbrowser extract <recording.yaml>");
|
|
10572
10548
|
process.exit(1);
|
|
10573
10549
|
}
|
|
10574
|
-
const { extractAndSave, printExtractSummary } = await import("./extract-
|
|
10550
|
+
const { extractAndSave, printExtractSummary } = await import("./extract-BSYBM4MR.js");
|
|
10575
10551
|
const { summary, outputPath } = extractAndSave(filePath);
|
|
10576
10552
|
printExtractSummary(summary);
|
|
10577
10553
|
console.log(`
|
|
@@ -10584,40 +10560,203 @@ async function handleFilter(args, _mode) {
|
|
|
10584
10560
|
console.error("Usage: xbrowser filter <input.yaml> <output.yaml> [--exclude-types=type1,type2]");
|
|
10585
10561
|
process.exit(1);
|
|
10586
10562
|
}
|
|
10587
|
-
const { filterRecording, parseExcludeTypes } = await import("./filter-
|
|
10563
|
+
const { filterRecording, parseExcludeTypes } = await import("./filter-KCFO4RSV.js");
|
|
10588
10564
|
const excludeTypes = parseExcludeTypes(args.slice(2));
|
|
10589
10565
|
const result = filterRecording(filePath, outputPath, excludeTypes);
|
|
10590
10566
|
console.log(`Filtered ${filePath} -> ${outputPath}`);
|
|
10591
10567
|
console.log(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)`);
|
|
10592
10568
|
}
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10569
|
+
async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
|
|
10570
|
+
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-RTDGURIJ.js");
|
|
10571
|
+
const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
|
|
10572
|
+
const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync11 } = await import("fs");
|
|
10573
|
+
const { join: join13 } = await import("path");
|
|
10574
|
+
const data = SessionRecorder2.readData(sessionName);
|
|
10575
|
+
if (!data) {
|
|
10576
|
+
outputError(`No recording found for session "${sessionName}". Run \`xbrowser record stop --session ${sessionName}\` first.`);
|
|
10577
|
+
return;
|
|
10578
|
+
}
|
|
10579
|
+
let domain = "unknown";
|
|
10580
|
+
try {
|
|
10581
|
+
domain = new URL(data.startUrl).hostname.replace(/^www\./, "");
|
|
10582
|
+
} catch {
|
|
10583
|
+
}
|
|
10584
|
+
const finalPluginName = pluginName || domain.split(".")[0] || "my-site";
|
|
10585
|
+
const finalOutputDir = outputDir || join13(process.cwd(), ".xcli", "plugins", finalPluginName);
|
|
10586
|
+
const knowledge = readSiteKnowledge2(domain);
|
|
10587
|
+
const knowledgeMd = knowledge ? toMarkdown(knowledge) : "";
|
|
10588
|
+
const pluginCode = generatePluginCode(finalPluginName, domain, data, knowledgeMd);
|
|
10589
|
+
mkdirSync10(join13(finalOutputDir), { recursive: true });
|
|
10590
|
+
writeFileSync11(join13(finalOutputDir, "index.ts"), pluginCode, "utf-8");
|
|
10591
|
+
if (knowledgeMd) {
|
|
10592
|
+
writeFileSync11(join13(finalOutputDir, "SITE_KNOWLEDGE.md"), knowledgeMd, "utf-8");
|
|
10593
|
+
}
|
|
10594
|
+
console.log("");
|
|
10595
|
+
console.log("=== Plugin Generated ===");
|
|
10596
|
+
console.log(` Plugin: ${finalPluginName}`);
|
|
10597
|
+
console.log(` Domain: ${domain}`);
|
|
10598
|
+
console.log(` Output: ${finalOutputDir}/index.ts`);
|
|
10599
|
+
if (knowledgeMd) {
|
|
10600
|
+
console.log(` Knowledge: ${finalOutputDir}/SITE_KNOWLEDGE.md`);
|
|
10601
|
+
}
|
|
10602
|
+
console.log(` Actions: ${data.actions.length}`);
|
|
10603
|
+
console.log(` APIs: ${data.network.filter((n) => n.contentType.includes("json") || n.url.includes("/api/")).length}`);
|
|
10604
|
+
console.log("");
|
|
10605
|
+
console.log("Next steps:");
|
|
10606
|
+
console.log(` 1. Review and edit: ${finalOutputDir}/index.ts`);
|
|
10607
|
+
console.log(` 2. Test: xbrowser ${finalPluginName} <command>`);
|
|
10608
|
+
console.log(` 3. Reference: ${finalOutputDir}/SITE_KNOWLEDGE.md (for LLM)`);
|
|
10609
|
+
}
|
|
10610
|
+
function generatePluginCode(pluginName, domain, data, _knowledgeMd) {
|
|
10611
|
+
const pagePaths = /* @__PURE__ */ new Set();
|
|
10612
|
+
for (const action of data.actions) {
|
|
10613
|
+
if (action.url) {
|
|
10614
|
+
try {
|
|
10615
|
+
pagePaths.add(new URL(action.url).pathname);
|
|
10616
|
+
} catch {
|
|
10617
|
+
}
|
|
10605
10618
|
}
|
|
10606
10619
|
}
|
|
10607
|
-
|
|
10620
|
+
const clickSelectors = [];
|
|
10621
|
+
const inputSelectors = [];
|
|
10622
|
+
for (const action of data.actions) {
|
|
10623
|
+
const el = action.element;
|
|
10624
|
+
if (!el) continue;
|
|
10625
|
+
const sel = el.selector;
|
|
10626
|
+
if (!sel) continue;
|
|
10627
|
+
if (action.type === "click" && !clickSelectors.includes(sel)) {
|
|
10628
|
+
clickSelectors.push(sel);
|
|
10629
|
+
}
|
|
10630
|
+
if (action.type === "input" && !inputSelectors.some((s) => s.selector === sel)) {
|
|
10631
|
+
inputSelectors.push({ selector: sel, placeholder: el.placeholder });
|
|
10632
|
+
}
|
|
10633
|
+
}
|
|
10634
|
+
const apis = data.network.filter(
|
|
10635
|
+
(n) => (n.contentType || "").includes("json") || n.url.includes("/api/")
|
|
10636
|
+
);
|
|
10637
|
+
const commands = [];
|
|
10638
|
+
commands.push(` site.command({
|
|
10639
|
+
name: 'open',
|
|
10640
|
+
description: 'Open ${domain}',
|
|
10641
|
+
scope: 'browser',
|
|
10642
|
+
handler: async (_p: Record<string, unknown>, ctx: CommandContext) => {
|
|
10643
|
+
const page = ensurePage(ctx);
|
|
10644
|
+
await page.goto('${data.startUrl}', { waitUntil: 'domcontentloaded' });
|
|
10645
|
+
return ok({ url: page.url() });
|
|
10646
|
+
},
|
|
10647
|
+
});`);
|
|
10648
|
+
if (inputSelectors.length > 0) {
|
|
10649
|
+
const params = inputSelectors.slice(0, 5).map(
|
|
10650
|
+
(s, i) => ` ${s.selector.replace(/[^a-zA-Z0-9]/g, "_").replace(/^_+/, "").toLowerCase() || `field${i}`}: z.string().describe('${s.placeholder || s.selector}'),`
|
|
10651
|
+
).join("\n");
|
|
10652
|
+
const fills = inputSelectors.slice(0, 5).map(
|
|
10653
|
+
(s) => ` await page.fill('${s.selector}', p.${s.selector.replace(/[^a-zA-Z0-9]/g, "_").replace(/^_+/, "").toLowerCase() || "field0"}');`
|
|
10654
|
+
).join("\n");
|
|
10655
|
+
commands.push(` site.command({
|
|
10656
|
+
name: 'fill',
|
|
10657
|
+
description: 'Fill form on ${domain}',
|
|
10658
|
+
scope: 'page',
|
|
10659
|
+
parameters: z.object({
|
|
10660
|
+
${params}
|
|
10661
|
+
}),
|
|
10662
|
+
handler: async (p: Record<string, unknown>, ctx: CommandContext) => {
|
|
10663
|
+
const page = ensurePage(ctx);
|
|
10664
|
+
${fills}
|
|
10665
|
+
return ok({ filled: true });
|
|
10666
|
+
},
|
|
10667
|
+
});`);
|
|
10668
|
+
}
|
|
10669
|
+
if (clickSelectors.length > 0) {
|
|
10670
|
+
commands.push(` site.command({
|
|
10671
|
+
name: 'click',
|
|
10672
|
+
description: 'Click element on ${domain}',
|
|
10673
|
+
scope: 'page',
|
|
10674
|
+
parameters: z.object({
|
|
10675
|
+
selector: z.string().describe('CSS selector of element to click'),
|
|
10676
|
+
}),
|
|
10677
|
+
handler: async (p: Record<string, unknown>, ctx: CommandContext) => {
|
|
10678
|
+
const page = ensurePage(ctx);
|
|
10679
|
+
await page.click(p.selector as string);
|
|
10680
|
+
return ok({ clicked: p.selector });
|
|
10681
|
+
},
|
|
10682
|
+
});`);
|
|
10683
|
+
}
|
|
10684
|
+
if (apis.length > 0) {
|
|
10685
|
+
commands.push(` site.command({
|
|
10686
|
+
name: 'scrape',
|
|
10687
|
+
description: 'Scrape data from ${domain}',
|
|
10688
|
+
scope: 'page',
|
|
10689
|
+
handler: async (_p: Record<string, unknown>, ctx: CommandContext) => {
|
|
10690
|
+
const page = ensurePage(ctx);
|
|
10691
|
+
const data = await page.evaluate(() => {
|
|
10692
|
+
return {
|
|
10693
|
+
title: document.title,
|
|
10694
|
+
url: location.href,
|
|
10695
|
+
content: document.body?.innerText?.substring(0, 5000) || '',
|
|
10696
|
+
};
|
|
10697
|
+
});
|
|
10698
|
+
return ok(data);
|
|
10699
|
+
},
|
|
10700
|
+
});`);
|
|
10701
|
+
}
|
|
10702
|
+
return `/**
|
|
10703
|
+
* ${pluginName} \u2014 Auto-generated plugin for ${domain}
|
|
10704
|
+
*
|
|
10705
|
+
* Generated from xbrowser recording session.
|
|
10706
|
+
* Review and customize before using in production.
|
|
10707
|
+
*
|
|
10708
|
+
* Site Knowledge: See SITE_KNOWLEDGE.md for LLM-readable selector/API reference.
|
|
10709
|
+
*/
|
|
10710
|
+
|
|
10711
|
+
import { z } from 'zod';
|
|
10712
|
+
import { ok } from '@dyyz1993/xcli-core';
|
|
10713
|
+
import { createSite, type CommandContext } from '@dyyz1993/xcli-core';
|
|
10714
|
+
|
|
10715
|
+
interface XBPage {
|
|
10716
|
+
url(): string;
|
|
10717
|
+
goto(url: string, opts?: Record<string, unknown>): Promise<unknown>;
|
|
10718
|
+
click(selector: string, opts?: Record<string, unknown>): Promise<unknown>;
|
|
10719
|
+
fill(selector: string, value: string, opts?: Record<string, unknown>): Promise<unknown>;
|
|
10720
|
+
evaluate<T>(fn: string | (() => T)): Promise<T>;
|
|
10608
10721
|
}
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10722
|
+
|
|
10723
|
+
function ensurePage(ctx: CommandContext): XBPage {
|
|
10724
|
+
const page = 'page' in ctx ? (ctx as Record<string, unknown>).page : undefined;
|
|
10725
|
+
if (!page) throw new Error('No active page. Start a session first.');
|
|
10726
|
+
return page as XBPage;
|
|
10612
10727
|
}
|
|
10613
10728
|
|
|
10729
|
+
export default createSite({
|
|
10730
|
+
name: '${pluginName}',
|
|
10731
|
+
domain: '${domain}',
|
|
10732
|
+
description: 'Auto-generated plugin for ${domain} from recording',
|
|
10733
|
+
|
|
10734
|
+
login: {
|
|
10735
|
+
url: '${data.startUrl}',
|
|
10736
|
+
detect: async (ctx: CommandContext) => {
|
|
10737
|
+
const page = ensurePage(ctx);
|
|
10738
|
+
// TODO: Add login detection logic
|
|
10739
|
+
return false;
|
|
10740
|
+
},
|
|
10741
|
+
},
|
|
10742
|
+
|
|
10743
|
+
setup(site) {
|
|
10744
|
+
${commands.join("\n\n")}
|
|
10745
|
+
},
|
|
10746
|
+
});
|
|
10747
|
+
`;
|
|
10748
|
+
}
|
|
10749
|
+
|
|
10750
|
+
// src/stdin.ts
|
|
10751
|
+
import { readStdin as readStdin2, readCommandFile, splitFileLine } from "@dyyz1993/xcli-core";
|
|
10752
|
+
|
|
10614
10753
|
// src/cli/run-routes.ts
|
|
10615
10754
|
async function handleRun(filePath, options) {
|
|
10616
10755
|
let commands;
|
|
10617
10756
|
try {
|
|
10618
10757
|
commands = readCommandFile(filePath);
|
|
10619
10758
|
} catch (e) {
|
|
10620
|
-
outputError(`Failed to read file '${filePath}': ${e
|
|
10759
|
+
outputError(`Failed to read file '${filePath}': ${errMsg(e)}`);
|
|
10621
10760
|
return;
|
|
10622
10761
|
}
|
|
10623
10762
|
if (commands.length === 0) {
|
|
@@ -10654,10 +10793,10 @@ async function handleRun(filePath, options) {
|
|
|
10654
10793
|
async function handleViewer(_args, options, mode, _cdpEndpoint) {
|
|
10655
10794
|
const name = options.name || process.env.XBROWSER_SESSION || "default";
|
|
10656
10795
|
const selector = options.selector;
|
|
10657
|
-
|
|
10796
|
+
let status = getDaemonProcessStatus();
|
|
10658
10797
|
if (!status.running) {
|
|
10659
|
-
|
|
10660
|
-
|
|
10798
|
+
await startDaemonProcess();
|
|
10799
|
+
status = getDaemonProcessStatus();
|
|
10661
10800
|
}
|
|
10662
10801
|
const port = status.port || getDaemonConfig().basePort;
|
|
10663
10802
|
let url = `http://localhost:${port}/preview/${name}`;
|
|
@@ -10973,7 +11112,236 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
10973
11112
|
outputError(`Unknown net sub-command: ${subCommand}. Use: list, clear, top, log, around, analyze, curl, replay, inspect, like, dislike, export`);
|
|
10974
11113
|
}
|
|
10975
11114
|
} catch (err) {
|
|
10976
|
-
outputError(err
|
|
11115
|
+
outputError(errMsg(err) || "Network command failed");
|
|
11116
|
+
}
|
|
11117
|
+
}
|
|
11118
|
+
|
|
11119
|
+
// src/cli/test-routes.ts
|
|
11120
|
+
import { execSync as execSync3 } from "child_process";
|
|
11121
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
11122
|
+
import { resolve as resolve9 } from "path";
|
|
11123
|
+
function findPluginPath(plugin) {
|
|
11124
|
+
const candidates = [
|
|
11125
|
+
resolve9(process.cwd(), ".xcli/plugins", plugin, "index.ts"),
|
|
11126
|
+
resolve9(process.cwd(), "node_modules/@xbrowser/cli/.xcli/plugins", plugin, "index.ts")
|
|
11127
|
+
];
|
|
11128
|
+
for (const p of candidates) {
|
|
11129
|
+
try {
|
|
11130
|
+
readFileSync8(p, "utf-8");
|
|
11131
|
+
return p;
|
|
11132
|
+
} catch {
|
|
11133
|
+
}
|
|
11134
|
+
}
|
|
11135
|
+
return resolve9(process.cwd(), ".xcli/plugins", plugin, "index.ts");
|
|
11136
|
+
}
|
|
11137
|
+
function extractSchema(plugin, command) {
|
|
11138
|
+
const pluginPath = findPluginPath(plugin);
|
|
11139
|
+
let src;
|
|
11140
|
+
try {
|
|
11141
|
+
src = readFileSync8(pluginPath, "utf-8");
|
|
11142
|
+
} catch {
|
|
11143
|
+
return null;
|
|
11144
|
+
}
|
|
11145
|
+
const cmdIdx = src.indexOf(`.command('${command}'`);
|
|
11146
|
+
if (cmdIdx < 0) return null;
|
|
11147
|
+
const after = src.slice(cmdIdx);
|
|
11148
|
+
const resultIdx = after.indexOf("result:");
|
|
11149
|
+
if (resultIdx < 0) return null;
|
|
11150
|
+
let block = after.slice(resultIdx + 7);
|
|
11151
|
+
let depth = 0;
|
|
11152
|
+
let schemaStr = "";
|
|
11153
|
+
for (const ch of block) {
|
|
11154
|
+
if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
11155
|
+
if (ch === "}" || ch === ")" || ch === "]") {
|
|
11156
|
+
depth--;
|
|
11157
|
+
if (depth < 0) break;
|
|
11158
|
+
}
|
|
11159
|
+
if (depth === 0 && /\w/.test(ch) && schemaStr.trim().endsWith(",")) {
|
|
11160
|
+
break;
|
|
11161
|
+
}
|
|
11162
|
+
schemaStr += ch;
|
|
11163
|
+
}
|
|
11164
|
+
const objStart = schemaStr.indexOf("z.object({");
|
|
11165
|
+
const objEnd = objStart >= 0 ? schemaStr.indexOf("})", objStart) : -1;
|
|
11166
|
+
const objStr = objStart >= 0 && objEnd > objStart ? schemaStr.slice(objStart + 10, objEnd) : schemaStr;
|
|
11167
|
+
const fields = [];
|
|
11168
|
+
const SKIP_NAMES = /* @__PURE__ */ new Set(["passthrough", "optional", "describe", "default"]);
|
|
11169
|
+
const fieldRegex = /(\w+)\s*:\s*z\.(\w+)/g;
|
|
11170
|
+
let match;
|
|
11171
|
+
while ((match = fieldRegex.exec(objStr)) !== null) {
|
|
11172
|
+
const name = match[1];
|
|
11173
|
+
const type = match[2];
|
|
11174
|
+
if (SKIP_NAMES.has(name) || type === "union" || type === "enum") continue;
|
|
11175
|
+
const afterField = objStr.slice(match.index + match[0].length);
|
|
11176
|
+
const isOptional = afterField.trimStart().startsWith(".optional()");
|
|
11177
|
+
fields.push({
|
|
11178
|
+
name,
|
|
11179
|
+
type: type === "string" ? "string" : type === "number" ? "number" : type === "boolean" ? "boolean" : type === "array" ? "array" : type,
|
|
11180
|
+
optional: isOptional || name === "index"
|
|
11181
|
+
});
|
|
11182
|
+
}
|
|
11183
|
+
return fields.length > 0 ? fields : null;
|
|
11184
|
+
}
|
|
11185
|
+
async function runTest(plugin, command, cmdArgs, options) {
|
|
11186
|
+
const cdp = options.cdp || options.cdpEndpoint || "http://localhost:9221";
|
|
11187
|
+
const argsStr = cmdArgs.filter((a) => !a.startsWith("--cdp")).join(" ");
|
|
11188
|
+
const schema = extractSchema(plugin, command);
|
|
11189
|
+
const fullCmd = `npx xbrowser ${plugin} ${command} ${argsStr} --cdp ${cdp} --json --timeout 60000`;
|
|
11190
|
+
let stdout = "";
|
|
11191
|
+
try {
|
|
11192
|
+
stdout = execSync3(fullCmd, {
|
|
11193
|
+
timeout: 65e3,
|
|
11194
|
+
encoding: "utf-8",
|
|
11195
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
11196
|
+
env: { ...process.env, FORCE_COLOR: "0" }
|
|
11197
|
+
});
|
|
11198
|
+
} catch (e) {
|
|
11199
|
+
const err = e;
|
|
11200
|
+
stdout = err.stdout?.toString() || "";
|
|
11201
|
+
const jsonLine = stdout.split("\n").find((l) => {
|
|
11202
|
+
try {
|
|
11203
|
+
JSON.parse(l);
|
|
11204
|
+
return true;
|
|
11205
|
+
} catch {
|
|
11206
|
+
return false;
|
|
11207
|
+
}
|
|
11208
|
+
});
|
|
11209
|
+
if (jsonLine) {
|
|
11210
|
+
try {
|
|
11211
|
+
const parsed2 = JSON.parse(jsonLine);
|
|
11212
|
+
const code = parsed2?.data?.code || "";
|
|
11213
|
+
if (code === "LOGIN_REQUIRED") {
|
|
11214
|
+
return { status: "LOGIN_REQUIRED", message: parsed2.message || "\u9700\u8981\u767B\u5F55", viewerUrl: "http://localhost:9224/preview/default" };
|
|
11215
|
+
}
|
|
11216
|
+
} catch {
|
|
11217
|
+
}
|
|
11218
|
+
}
|
|
11219
|
+
const stderr = err.stderr?.toString() || "";
|
|
11220
|
+
if (stdout.includes("captcha") || stderr.includes("captcha") || stdout.includes("CAPTCHA")) {
|
|
11221
|
+
return { status: "CAPTCHA", message: "\u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801", viewerUrl: "http://localhost:9224/preview/default" };
|
|
11222
|
+
}
|
|
11223
|
+
return { status: "EXEC_ERROR", message: (err.message || "").slice(0, 200) || "\u6267\u884C\u5931\u8D25" };
|
|
11224
|
+
}
|
|
11225
|
+
const allLines = stdout.split("\n");
|
|
11226
|
+
const jsonStart = allLines.findIndex((l) => l.trim().startsWith("{"));
|
|
11227
|
+
const jsonStr = jsonStart >= 0 ? allLines.slice(jsonStart).join("\n") : "";
|
|
11228
|
+
let parsed = {};
|
|
11229
|
+
try {
|
|
11230
|
+
parsed = JSON.parse(jsonStr);
|
|
11231
|
+
} catch {
|
|
11232
|
+
return { status: "EXEC_ERROR", message: "\u65E0\u6CD5\u89E3\u6790 CLI \u8F93\u51FA" };
|
|
11233
|
+
}
|
|
11234
|
+
const rawData = parsed.data;
|
|
11235
|
+
const rawTips = parsed.tips || [];
|
|
11236
|
+
if (parsed.success === false) {
|
|
11237
|
+
const code = rawData?.code || "";
|
|
11238
|
+
if (code === "LOGIN_REQUIRED") {
|
|
11239
|
+
return { status: "LOGIN_REQUIRED", message: parsed.message || "\u9700\u8981\u767B\u5F55", viewerUrl: "http://localhost:9224/preview/default" };
|
|
11240
|
+
}
|
|
11241
|
+
if (rawTips.join(" ").includes("captcha") || rawTips.join(" ").includes("CAPTCHA")) {
|
|
11242
|
+
return { status: "CAPTCHA", message: parsed.message || "\u9A8C\u8BC1\u7801", viewerUrl: "http://localhost:9224/preview/default" };
|
|
11243
|
+
}
|
|
11244
|
+
}
|
|
11245
|
+
const data = rawData;
|
|
11246
|
+
if (data === null || data === void 0) {
|
|
11247
|
+
const msg = parsed.message || "";
|
|
11248
|
+
const tips = rawTips.join(" ");
|
|
11249
|
+
const viewerUrl = parsed.viewerUrl || rawData?.viewerUrl || "";
|
|
11250
|
+
let status = "NO_DATA";
|
|
11251
|
+
if (msg.includes("block") || msg.includes("anti-bot") || msg.includes("captcha") || tips.includes("viewer")) {
|
|
11252
|
+
status = "BLOCKED";
|
|
11253
|
+
} else if (msg.includes("\u767B\u5F55") || msg.includes("login")) {
|
|
11254
|
+
status = "LOGIN_REQUIRED";
|
|
11255
|
+
}
|
|
11256
|
+
const ret = { status, message: msg || "\u6682\u65E0\u6570\u636E" };
|
|
11257
|
+
if (viewerUrl) ret.viewerUrl = viewerUrl;
|
|
11258
|
+
return ret;
|
|
11259
|
+
}
|
|
11260
|
+
if (!schema) {
|
|
11261
|
+
return { status: "OK", note: "\u65E0 result schema", data: JSON.stringify(data).slice(0, 200) };
|
|
11262
|
+
}
|
|
11263
|
+
const errors = [];
|
|
11264
|
+
const items = Array.isArray(data) ? data.slice(0, 3) : [data];
|
|
11265
|
+
for (const item of items) {
|
|
11266
|
+
if (typeof item !== "object" || item === null) {
|
|
11267
|
+
errors.push("\u6570\u636E\u9879\u4E0D\u662F\u5BF9\u8C61");
|
|
11268
|
+
continue;
|
|
11269
|
+
}
|
|
11270
|
+
for (const field of schema) {
|
|
11271
|
+
const val = item[field.name];
|
|
11272
|
+
if (val === void 0) {
|
|
11273
|
+
if (!field.optional) errors.push(`\u7F3A\u5C11: ${field.name}`);
|
|
11274
|
+
continue;
|
|
11275
|
+
}
|
|
11276
|
+
if (field.type === "array") {
|
|
11277
|
+
if (!Array.isArray(val)) errors.push(`${field.name}: \u671F\u671B array`);
|
|
11278
|
+
} else if (typeof val !== field.type) {
|
|
11279
|
+
errors.push(`${field.name}: \u671F\u671B ${field.type}, \u5B9E\u9645 ${typeof val}`);
|
|
11280
|
+
}
|
|
11281
|
+
}
|
|
11282
|
+
}
|
|
11283
|
+
if (errors.length > 0) {
|
|
11284
|
+
return { status: "SCHEMA_ERROR", errors, data: JSON.stringify(data).slice(0, 200) };
|
|
11285
|
+
}
|
|
11286
|
+
const count = Array.isArray(data) ? data.length : 1;
|
|
11287
|
+
return { status: "OK", count, data: JSON.stringify(data).slice(0, 200) };
|
|
11288
|
+
}
|
|
11289
|
+
async function handleTest(cmdArgs, options, mode, cdpEndpoint) {
|
|
11290
|
+
const plugin = cmdArgs[0];
|
|
11291
|
+
const command = cmdArgs[1];
|
|
11292
|
+
if (!plugin || !command) {
|
|
11293
|
+
console.error("\u7528\u6CD5: xbrowser test <plugin> <command> [\u53C2\u6570...]");
|
|
11294
|
+
console.error("\u793A\u4F8B: xbrowser test doubao list --cdp 9221");
|
|
11295
|
+
return;
|
|
11296
|
+
}
|
|
11297
|
+
const loader = await getPluginLoader();
|
|
11298
|
+
const internalLoader = loader.getCore().loader;
|
|
11299
|
+
const site = internalLoader.getSite(plugin);
|
|
11300
|
+
if (!site) {
|
|
11301
|
+
console.error(`\u63D2\u4EF6 "${plugin}" \u4E0D\u5B58\u5728`);
|
|
11302
|
+
return;
|
|
11303
|
+
}
|
|
11304
|
+
const cmdEntry = site.getCommand(command);
|
|
11305
|
+
if (!cmdEntry) {
|
|
11306
|
+
console.error(`\u6307\u4EE4 "${command}" \u4E0D\u5B58\u5728`);
|
|
11307
|
+
return;
|
|
11308
|
+
}
|
|
11309
|
+
const testArgs = cmdArgs.slice(2);
|
|
11310
|
+
const mergedOptions = { ...options, cdp: cdpEndpoint || options.cdp };
|
|
11311
|
+
const result = await runTest(plugin, command, testArgs, mergedOptions);
|
|
11312
|
+
if (mode === "json") {
|
|
11313
|
+
console.log(JSON.stringify(result, null, 2));
|
|
11314
|
+
return;
|
|
11315
|
+
}
|
|
11316
|
+
const r = result;
|
|
11317
|
+
const icons = {
|
|
11318
|
+
OK: "\u2705",
|
|
11319
|
+
LOGIN_REQUIRED: "\u{1F511}",
|
|
11320
|
+
CAPTCHA: "\u{1F6A8}",
|
|
11321
|
+
SCHEMA_ERROR: "\u274C",
|
|
11322
|
+
BLOCKED: "\u{1F6A7}",
|
|
11323
|
+
NO_DATA: "\u{1F4ED}",
|
|
11324
|
+
EXEC_ERROR: "\u{1F4A5}"
|
|
11325
|
+
};
|
|
11326
|
+
const status = String(r.status);
|
|
11327
|
+
const icon = icons[status] || "\u2753";
|
|
11328
|
+
console.log(`
|
|
11329
|
+
${icon} ${plugin}.${command}`);
|
|
11330
|
+
console.log(` \u72B6\u6001: ${status}`);
|
|
11331
|
+
if (status === "OK") {
|
|
11332
|
+
if (r.count) console.log(` \u6570\u636E: ${r.count} \u9879`);
|
|
11333
|
+
if (r.data) console.log(` \u9884\u89C8: ${String(r.data).slice(0, 150)}`);
|
|
11334
|
+
} else if (status === "LOGIN_REQUIRED" || status === "CAPTCHA") {
|
|
11335
|
+
console.log(` \u4FE1\u606F: ${String(r.message)}`);
|
|
11336
|
+
console.log(` Viewer: ${String(r.viewerUrl)}`);
|
|
11337
|
+
} else if (status === "SCHEMA_ERROR") {
|
|
11338
|
+
const errs = r.errors;
|
|
11339
|
+
if (errs) console.log(` \u9519\u8BEF: ${errs.join("; ")}`);
|
|
11340
|
+
} else if (["NO_DATA", "BLOCKED"].includes(status)) {
|
|
11341
|
+
console.log(` \u4FE1\u606F: ${String(r.message)}`);
|
|
11342
|
+
if (r.viewerUrl) console.log(` Viewer: ${String(r.viewerUrl)}`);
|
|
11343
|
+
} else {
|
|
11344
|
+
console.log(` \u4FE1\u606F: ${String(r.message)}`);
|
|
10977
11345
|
}
|
|
10978
11346
|
}
|
|
10979
11347
|
|
|
@@ -10990,11 +11358,11 @@ Usage:
|
|
|
10990
11358
|
xbrowser -e cmd1 -e cmd2 Execute multiple -e commands
|
|
10991
11359
|
|
|
10992
11360
|
Commands:
|
|
10993
|
-
session
|
|
10994
|
-
session close [--name <n>] Close session
|
|
11361
|
+
session close [--session <name>] Close session
|
|
10995
11362
|
session list List sessions
|
|
10996
|
-
session kill [--
|
|
11363
|
+
session kill [--session <name>] Kill session
|
|
10997
11364
|
goto <url> Navigate to URL
|
|
11365
|
+
open <url> Navigate to URL (alias for goto)
|
|
10998
11366
|
click <selector> Click element (-s <sel>)
|
|
10999
11367
|
fill <selector> <value> Fill input (-s <sel> -v <val>)
|
|
11000
11368
|
type <selector> <text> Type text (-s <sel> -v <text>)
|
|
@@ -11004,7 +11372,7 @@ Commands:
|
|
|
11004
11372
|
dblclick <selector> Double click (-s <sel>)
|
|
11005
11373
|
check <selector> Check checkbox (-s <sel>)
|
|
11006
11374
|
uncheck <selector> Uncheck checkbox (-s <sel>)
|
|
11007
|
-
screenshot [--full-page]
|
|
11375
|
+
screenshot [--full-page] [--base64] Take screenshot (saves to ~/.xbrowser/screenshots/; use --base64 for inline data)
|
|
11008
11376
|
eval <expression> Evaluate JS
|
|
11009
11377
|
wait <selector> [--timeout <ms>] Wait for element (-s <sel>)
|
|
11010
11378
|
scroll <direction> [--distance N] Scroll page
|
|
@@ -11026,9 +11394,6 @@ Commands:
|
|
|
11026
11394
|
plugin list List plugins
|
|
11027
11395
|
plugin reload <name> Reload plugin
|
|
11028
11396
|
create <name> --template <type> Create plugin
|
|
11029
|
-
daemon start [--port <port>] Start daemon
|
|
11030
|
-
daemon stop Stop daemon
|
|
11031
|
-
daemon status Check status
|
|
11032
11397
|
serve [--port <port>] [--token <t>] Start HTTP server for remote access
|
|
11033
11398
|
remote <url> [command] [--token <t>] Execute command on remote server
|
|
11034
11399
|
record start --url <url> Start recording
|
|
@@ -11272,7 +11637,7 @@ async function createSessionHandler(req) {
|
|
|
11272
11637
|
createdAt: session.createdAt
|
|
11273
11638
|
});
|
|
11274
11639
|
} catch (err) {
|
|
11275
|
-
return errorResponse(500, "INTERNAL_ERROR", err
|
|
11640
|
+
return errorResponse(500, "INTERNAL_ERROR", errMsg(err));
|
|
11276
11641
|
}
|
|
11277
11642
|
}
|
|
11278
11643
|
async function closeSession2(req) {
|
|
@@ -11367,7 +11732,7 @@ async function route(method, url, headers, body) {
|
|
|
11367
11732
|
try {
|
|
11368
11733
|
return await match.route.handler(req);
|
|
11369
11734
|
} catch (err) {
|
|
11370
|
-
return errorResponse(500, "INTERNAL_ERROR", err
|
|
11735
|
+
return errorResponse(500, "INTERNAL_ERROR", errMsg(err));
|
|
11371
11736
|
}
|
|
11372
11737
|
}
|
|
11373
11738
|
async function handleRequest(req, res, validateAuthFn) {
|
|
@@ -11401,19 +11766,19 @@ function headersToObject(headers) {
|
|
|
11401
11766
|
return result;
|
|
11402
11767
|
}
|
|
11403
11768
|
function readBody(req) {
|
|
11404
|
-
return new Promise((
|
|
11769
|
+
return new Promise((resolve10, reject) => {
|
|
11405
11770
|
const chunks = [];
|
|
11406
11771
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
11407
11772
|
req.on("end", () => {
|
|
11408
11773
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
11409
11774
|
if (!raw) {
|
|
11410
|
-
|
|
11775
|
+
resolve10(null);
|
|
11411
11776
|
return;
|
|
11412
11777
|
}
|
|
11413
11778
|
try {
|
|
11414
|
-
|
|
11779
|
+
resolve10(JSON.parse(raw));
|
|
11415
11780
|
} catch {
|
|
11416
|
-
|
|
11781
|
+
resolve10(null);
|
|
11417
11782
|
}
|
|
11418
11783
|
});
|
|
11419
11784
|
req.on("error", reject);
|
|
@@ -11458,7 +11823,7 @@ var HTTPServer = class {
|
|
|
11458
11823
|
res.end(JSON.stringify({ error: "INTERNAL_ERROR", message, statusCode: 500 }));
|
|
11459
11824
|
});
|
|
11460
11825
|
});
|
|
11461
|
-
return new Promise((
|
|
11826
|
+
return new Promise((resolve10, reject) => {
|
|
11462
11827
|
const server = this.server;
|
|
11463
11828
|
server.on("error", (err) => {
|
|
11464
11829
|
this.server = null;
|
|
@@ -11470,7 +11835,7 @@ var HTTPServer = class {
|
|
|
11470
11835
|
this.port = addr.port;
|
|
11471
11836
|
}
|
|
11472
11837
|
console.log(`HTTP server listening on http://${this.host}:${this.port}`);
|
|
11473
|
-
|
|
11838
|
+
resolve10({ port: this.port, host: this.host });
|
|
11474
11839
|
});
|
|
11475
11840
|
});
|
|
11476
11841
|
}
|
|
@@ -11481,13 +11846,13 @@ var HTTPServer = class {
|
|
|
11481
11846
|
*/
|
|
11482
11847
|
async stop() {
|
|
11483
11848
|
if (!this.server) return;
|
|
11484
|
-
return new Promise((
|
|
11849
|
+
return new Promise((resolve10, reject) => {
|
|
11485
11850
|
this.server.close((err) => {
|
|
11486
11851
|
if (err) {
|
|
11487
11852
|
reject(err);
|
|
11488
11853
|
} else {
|
|
11489
11854
|
this.server = null;
|
|
11490
|
-
|
|
11855
|
+
resolve10();
|
|
11491
11856
|
}
|
|
11492
11857
|
});
|
|
11493
11858
|
});
|
|
@@ -11525,8 +11890,9 @@ function showCommandHelp(siteName, cmd, siteConfig, mode) {
|
|
|
11525
11890
|
if (mode === "json") {
|
|
11526
11891
|
const paramsList = [];
|
|
11527
11892
|
if (c.parameters) {
|
|
11528
|
-
const def = c.parameters._def;
|
|
11529
|
-
const
|
|
11893
|
+
const def = asZodSchema(c.parameters)._def;
|
|
11894
|
+
const rawShape = def?.shape;
|
|
11895
|
+
const shape = typeof rawShape === "function" ? rawShape() : rawShape;
|
|
11530
11896
|
if (shape) {
|
|
11531
11897
|
for (const [key, value] of Object.entries(shape)) {
|
|
11532
11898
|
const info = extractZodFieldInfo(value);
|
|
@@ -11546,6 +11912,7 @@ function showCommandHelp(siteName, cmd, siteConfig, mode) {
|
|
|
11546
11912
|
command: c.name,
|
|
11547
11913
|
description: c.description,
|
|
11548
11914
|
scope: c.scope,
|
|
11915
|
+
...c.loginRequired ? { loginRequired: c.loginRequired } : {},
|
|
11549
11916
|
parameters: paramsList
|
|
11550
11917
|
}, mode);
|
|
11551
11918
|
} else {
|
|
@@ -11558,9 +11925,25 @@ function showCommandHelp(siteName, cmd, siteConfig, mode) {
|
|
|
11558
11925
|
examples: c.examples
|
|
11559
11926
|
}, { color: false, emoji: false });
|
|
11560
11927
|
console.log(text);
|
|
11928
|
+
if (c.loginRequired) {
|
|
11929
|
+
console.log(` Login: ${c.loginRequired}`);
|
|
11930
|
+
}
|
|
11561
11931
|
console.log("");
|
|
11562
11932
|
}
|
|
11563
11933
|
}
|
|
11934
|
+
function outputLoginRequired(result, mode) {
|
|
11935
|
+
if (mode === "json" || mode === "yaml") {
|
|
11936
|
+
console.log(outputFormatter2.format(result, { mode, color: false, emoji: false }));
|
|
11937
|
+
return;
|
|
11938
|
+
}
|
|
11939
|
+
const message = result.message || "Login required";
|
|
11940
|
+
console.error(message);
|
|
11941
|
+
for (const tip of result.tips || []) {
|
|
11942
|
+
const text = typeof tip === "string" ? tip : tip.message;
|
|
11943
|
+
if (text !== message) console.error(` \u{1F4A1} ${text}`);
|
|
11944
|
+
}
|
|
11945
|
+
process.exit(1);
|
|
11946
|
+
}
|
|
11564
11947
|
function extractZodFieldInfo(value) {
|
|
11565
11948
|
const field = value;
|
|
11566
11949
|
const fieldDef = field._def;
|
|
@@ -11624,7 +12007,7 @@ function extractCdpFromArgv(argv) {
|
|
|
11624
12007
|
if (argv[i] === "--cdp" && argv[i + 1]) return argv[i + 1];
|
|
11625
12008
|
if (typeof argv[i] === "string" && argv[i].startsWith("--cdp=")) return argv[i].slice(6);
|
|
11626
12009
|
}
|
|
11627
|
-
return
|
|
12010
|
+
return process.env.XBROWSER_CDP;
|
|
11628
12011
|
}
|
|
11629
12012
|
async function handleStdinMode(stdinCommands, argv) {
|
|
11630
12013
|
const chain = stdinCommands.join(" && ");
|
|
@@ -11687,7 +12070,7 @@ async function routeCommand(argv, stdinCommands) {
|
|
|
11687
12070
|
const cmdArgs = positional.slice(1);
|
|
11688
12071
|
const mode = options.json ? "json" : options.yaml ? "yaml" : "text";
|
|
11689
12072
|
const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
|
|
11690
|
-
const cdpEndpoint = options.cdp;
|
|
12073
|
+
const cdpEndpoint = options.cdp || process.env.XBROWSER_CDP;
|
|
11691
12074
|
if (options.version || options.v) {
|
|
11692
12075
|
console.log(`xbrowser v${version}`);
|
|
11693
12076
|
return;
|
|
@@ -11736,16 +12119,16 @@ async function routeCommand(argv, stdinCommands) {
|
|
|
11736
12119
|
if (builtinCmd) {
|
|
11737
12120
|
if (mode === "json") {
|
|
11738
12121
|
const paramsList = [];
|
|
11739
|
-
const schema = builtinCmd.parameters;
|
|
12122
|
+
const schema = asZodSchema(builtinCmd.parameters);
|
|
11740
12123
|
const shape = schema?.shape ?? schema?._def?.shape;
|
|
11741
12124
|
if (shape) {
|
|
11742
12125
|
for (const [key, value] of Object.entries(shape)) {
|
|
11743
|
-
const fieldSchema = value;
|
|
12126
|
+
const fieldSchema = asZodSchema(value);
|
|
11744
12127
|
const fieldDef = fieldSchema._def;
|
|
11745
12128
|
const description = fieldSchema.description || fieldDef?.description || "";
|
|
11746
12129
|
const typeName = fieldDef?.typeName || "";
|
|
11747
12130
|
const isOptional = typeName === "ZodOptional" || typeof fieldSchema.isOptional === "function" && fieldSchema.isOptional();
|
|
11748
|
-
const innerType = fieldDef?.innerType;
|
|
12131
|
+
const innerType = asZodSchema(fieldDef?.innerType);
|
|
11749
12132
|
const innerTypeName = innerType?._def ? innerType._def.typeName : typeName;
|
|
11750
12133
|
let type = "unknown";
|
|
11751
12134
|
if (innerTypeName === "ZodString" || typeName === "ZodString") type = "string";
|
|
@@ -11820,12 +12203,21 @@ async function routeCommand(argv, stdinCommands) {
|
|
|
11820
12203
|
if (builtin) await builtin.execute(cmdArgs, options, { cwd: process.cwd() });
|
|
11821
12204
|
break;
|
|
11822
12205
|
}
|
|
12206
|
+
case "knowledge":
|
|
12207
|
+
case "know": {
|
|
12208
|
+
const builtin = allBuiltins.find((b) => b.name === "knowledge");
|
|
12209
|
+
if (builtin) await builtin.execute(cmdArgs, options, { cwd: process.cwd() });
|
|
12210
|
+
break;
|
|
12211
|
+
}
|
|
11823
12212
|
case "viewer":
|
|
11824
12213
|
await handleViewer(cmdArgs, options, mode, cdpEndpoint);
|
|
11825
12214
|
break;
|
|
11826
12215
|
case "help":
|
|
11827
12216
|
showMainHelp();
|
|
11828
12217
|
break;
|
|
12218
|
+
case "test":
|
|
12219
|
+
await handleTest(cmdArgs, options, mode, cdpEndpoint);
|
|
12220
|
+
break;
|
|
11829
12221
|
case "net":
|
|
11830
12222
|
await handleNetCommand(cmdArgs, options, mode, sessionName);
|
|
11831
12223
|
break;
|
|
@@ -11911,7 +12303,7 @@ Run "xbrowser ${command} --help" to see available commands.`
|
|
|
11911
12303
|
const rawPluginArgs = subCmdIdx >= 0 ? argv.slice(subCmdIdx + 1) : [];
|
|
11912
12304
|
const params = parsePluginParams(rawPluginArgs, cmdEntry.parameters);
|
|
11913
12305
|
if (cmdEntry.parameters) {
|
|
11914
|
-
const schemaAny = cmdEntry.parameters;
|
|
12306
|
+
const schemaAny = asZodSchema(cmdEntry.parameters);
|
|
11915
12307
|
const def = schemaAny._def;
|
|
11916
12308
|
const shapeOrFn = def?.shape ?? schemaAny.shape;
|
|
11917
12309
|
const shapeObj = typeof shapeOrFn === "function" ? shapeOrFn() : shapeOrFn;
|
|
@@ -11935,9 +12327,14 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
11935
12327
|
}
|
|
11936
12328
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
11937
12329
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
11938
|
-
const { forwardExec } = await import("./daemon-client-
|
|
12330
|
+
const { forwardExec } = await import("./daemon-client-3JOKX2L2.js");
|
|
11939
12331
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
11940
12332
|
const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
|
|
12333
|
+
const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
|
|
12334
|
+
if (result && result.success === false && resultData?.code === "LOGIN_REQUIRED") {
|
|
12335
|
+
outputLoginRequired(result, mode);
|
|
12336
|
+
return;
|
|
12337
|
+
}
|
|
11941
12338
|
if (result && result.success !== false) {
|
|
11942
12339
|
if (isCommandResult2(result)) {
|
|
11943
12340
|
if (mode === "json" || mode === "yaml") {
|
|
@@ -11981,10 +12378,33 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
11981
12378
|
cliName: "xbrowser",
|
|
11982
12379
|
waitForHuman: async (_opts) => {
|
|
11983
12380
|
return { solved: false, timedOut: true };
|
|
11984
|
-
}
|
|
12381
|
+
},
|
|
12382
|
+
tips: new TipCollector3()
|
|
11985
12383
|
};
|
|
11986
12384
|
try {
|
|
11987
12385
|
const cmdStart = Date.now();
|
|
12386
|
+
const loginGuard = await checkPluginLoginRequired({
|
|
12387
|
+
site,
|
|
12388
|
+
command: cmdEntry,
|
|
12389
|
+
commandName: subCommand,
|
|
12390
|
+
ctx,
|
|
12391
|
+
page: needsBrowser ? session?.page : null,
|
|
12392
|
+
sessionName
|
|
12393
|
+
});
|
|
12394
|
+
if (!loginGuard.ok) {
|
|
12395
|
+
const result2 = {
|
|
12396
|
+
success: false,
|
|
12397
|
+
data: loginGuard.data ?? null,
|
|
12398
|
+
message: loginGuard.message,
|
|
12399
|
+
tips: normalizeTips7(loginGuard.tips)
|
|
12400
|
+
};
|
|
12401
|
+
if (mode === "json" || mode === "yaml") {
|
|
12402
|
+
outputLoginRequired(result2, mode);
|
|
12403
|
+
} else {
|
|
12404
|
+
outputLoginRequired(result2, mode);
|
|
12405
|
+
}
|
|
12406
|
+
return;
|
|
12407
|
+
}
|
|
11988
12408
|
const cmdHooks = await loadHooks();
|
|
11989
12409
|
if (cmdHooks.length > 0 && session?.page) {
|
|
11990
12410
|
await Promise.all(cmdHooks.map((h) => h.onBeforeCommand?.({ page: session.page, command: `${command} ${subCommand}`, params })));
|
|
@@ -12003,23 +12423,39 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
12003
12423
|
saveSessionDiskMeta(sessionName, { conversationUrl: convUrl, cdpEndpoint });
|
|
12004
12424
|
}
|
|
12005
12425
|
}
|
|
12426
|
+
let injectedViewerUrl;
|
|
12427
|
+
const LOGIN_FAIL_KEYWORDS = ["\u767B\u5F55", "login", "Login", "\u672A\u767B\u5F55", "not logged in", "cdp", "CDP", "\u9A8C\u8BC1\u7801", "\u9A8C\u8BC1", "captcha", "\u9700\u8981\u767B\u5F55", "requires login"];
|
|
12428
|
+
const tipTexts = (result.tips || []).map((t) => typeof t === "string" ? t : t.message);
|
|
12429
|
+
const isLoginFail = isCommandResult2(result) && result.success === false && [result.message, ...tipTexts].filter(Boolean).join(" ").toLowerCase().match(new RegExp(LOGIN_FAIL_KEYWORDS.join("|"), "i"));
|
|
12430
|
+
if (isLoginFail) {
|
|
12431
|
+
injectedViewerUrl = buildViewerUrl(sessionName);
|
|
12432
|
+
if (injectedViewerUrl) {
|
|
12433
|
+
result.tips = [...result.tips || [], makeTip.info(`Open viewer to complete login: ${injectedViewerUrl}`)];
|
|
12434
|
+
}
|
|
12435
|
+
}
|
|
12006
12436
|
const outputData = isCommandResult2(result) ? result.data : result && typeof result === "object" ? result.data ?? result : result;
|
|
12007
12437
|
const tips = isCommandResult2(result) ? result.tips : result && typeof result === "object" ? result.tips : void 0;
|
|
12008
12438
|
if (mode === "json" || mode === "yaml") {
|
|
12009
12439
|
const finalOutput = {
|
|
12010
12440
|
data: outputData
|
|
12011
12441
|
};
|
|
12442
|
+
if (injectedViewerUrl) {
|
|
12443
|
+
finalOutput.viewerUrl = injectedViewerUrl;
|
|
12444
|
+
}
|
|
12445
|
+
if (tips?.length) {
|
|
12446
|
+
finalOutput.tips = tips;
|
|
12447
|
+
}
|
|
12012
12448
|
if (hookOutputs.length > 0) {
|
|
12013
12449
|
finalOutput.hooks = hookOutputs;
|
|
12014
12450
|
}
|
|
12015
12451
|
console.log(outputFormatter2.format(finalOutput, { mode, color: false, emoji: false }));
|
|
12016
12452
|
if (tips?.length) {
|
|
12017
|
-
for (const tip of tips) console.error(`\u{1F4A1} ${tip}`);
|
|
12453
|
+
for (const tip of tips) console.error(`\u{1F4A1} ${typeof tip === "string" ? tip : tip.message}`);
|
|
12018
12454
|
}
|
|
12019
12455
|
} else {
|
|
12020
12456
|
console.log(outputFormatter2.format(outputData, { mode: "text", color: true, emoji: true }));
|
|
12021
12457
|
if (tips?.length) {
|
|
12022
|
-
for (const tip of tips) console.log(` \u{1F4A1} ${tip}`);
|
|
12458
|
+
for (const tip of tips) console.log(` \u{1F4A1} ${typeof tip === "string" ? tip : tip.message}`);
|
|
12023
12459
|
}
|
|
12024
12460
|
if (hookOutputs.length > 0) {
|
|
12025
12461
|
for (const ho of hookOutputs) {
|
|
@@ -12197,7 +12633,7 @@ async function main() {
|
|
|
12197
12633
|
const command = process.argv[2];
|
|
12198
12634
|
const isLongRunning = command === "preview" || command === "serve";
|
|
12199
12635
|
if (!isLongRunning) {
|
|
12200
|
-
const { ensureProcessCanExit } = await import("./browser-
|
|
12636
|
+
const { ensureProcessCanExit } = await import("./browser-5CTOA2WS.js");
|
|
12201
12637
|
await ensureProcessCanExit().catch(() => {
|
|
12202
12638
|
});
|
|
12203
12639
|
process.exit(exitCode);
|