@xbrowser/cli 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +17 -26
  2. package/dist/{browser-GURRY444.js → browser-GITRHHFO.js} +4 -3
  3. package/dist/{browser-DSVV4GHS.js → browser-R56O3CW6.js} +3 -3
  4. package/dist/{browser-53KUFEEM.js → browser-ZJOZB5CR.js} +4 -4
  5. package/dist/{cdp-driver-MNPR3HZH.js → cdp-driver-BE3FOMRN.js} +324 -58
  6. package/dist/{cdp-driver-SSXUGXP6.js → cdp-driver-TOPYJIFL.js} +3 -3
  7. package/dist/chunk-2SVQTI2O.js +2794 -0
  8. package/dist/{chunk-2MFXKN32.js → chunk-ACFE6PKF.js} +1013 -119
  9. package/dist/chunk-BBMRDUYQ.js +260 -0
  10. package/dist/{chunk-E4O5ZU3H.js → chunk-CAFNSGYM.js} +393 -95
  11. package/dist/{chunk-DTJRVA76.js → chunk-ETCO4SNK.js} +2 -2
  12. package/dist/{chunk-YKOHDEFV.js → chunk-JPA2ZT2R.js} +69 -36
  13. package/dist/{chunk-T4J4C2NZ.js → chunk-JPHCY4TC.js} +12 -2
  14. package/dist/chunk-KFQGP6VL.js +33 -0
  15. package/dist/{chunk-ITKPSIP7.js → chunk-MDAPTB7C.js} +6 -25
  16. package/dist/chunk-OZKD3W4X.js +417 -0
  17. package/dist/{chunk-42RPMJ76.js → chunk-PPG4D2EW.js} +325 -59
  18. package/dist/{chunk-IDVD44ED.js → chunk-Q4IGYTKR.js} +19 -7
  19. package/dist/{chunk-2BQZIT3S.js → chunk-QIK2I3VQ.js} +86 -2501
  20. package/dist/chunk-WJRE55TN.js +83 -0
  21. package/dist/cli.js +1435 -1077
  22. package/dist/{convert-EGFYNICZ.js → convert-LB3GJTLR.js} +3 -3
  23. package/dist/{convert-EKQVHKB4.js → convert-R3XXYKC6.js} +2 -2
  24. package/dist/{daemon-client-3VM7VU7O.js → daemon-client-DRCUMNHK.js} +25 -74
  25. package/dist/{daemon-client-YAVQ343A.js → daemon-client-UZZEHHIV.js} +2 -2
  26. package/dist/daemon-main.js +2200 -1691
  27. package/dist/{extract-JUOQQX4V.js → extract-2ZFW2MX7.js} +1 -1
  28. package/dist/{extract-L2IW3IUB.js → extract-BSYBM4MR.js} +1 -1
  29. package/dist/{filter-HC4RA7JY.js → filter-KCFO4RSV.js} +1 -1
  30. package/dist/{filter-VID2GGZ7.js → filter-T7DSZ2X7.js} +1 -1
  31. package/dist/{human-interaction-W753RVJB.js → human-interaction-UKAS5ZXV.js} +2 -2
  32. package/dist/index.d.ts +165 -108
  33. package/dist/index.js +2531 -1680
  34. package/dist/launcher-QUJ4M2VS.js +19 -0
  35. package/dist/{launcher-KA7J32K5.js → launcher-YARP45UY.js} +1 -1
  36. package/dist/{network-store-66A2RATI.js → network-store-XGZ25FFC.js} +1 -1
  37. package/dist/{network-store-BN6QEZ7R.js → network-store-YVDNUREI.js} +1 -1
  38. package/dist/{parse-action-dsl-T3DYC33D.js → parse-action-dsl-UM333TL2.js} +1 -1
  39. package/dist/{proxy-WKGUCH2C.js → proxy-LV4BJ5RC.js} +1 -1
  40. package/dist/session-recorder-RTDGURIJ.js +8 -0
  41. package/dist/session-recorder-YI7YYM36.js +7 -0
  42. package/dist/session-replayer-GLTUICSD.js +276 -0
  43. package/dist/site-knowledge-SYC6VCDB.js +23 -0
  44. package/package.json +5 -4
  45. package/dist/screenshot-CWAWMXVA.js +0 -28
  46. 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-2MFXKN32.js";
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-2BQZIT3S.js";
21
- import "./chunk-T4J4C2NZ.js";
28
+ } from "./chunk-CAFNSGYM.js";
29
+ import "./chunk-BBMRDUYQ.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,11 @@ import {
47
54
  killAllDaemonProcesses,
48
55
  startDaemonProcess,
49
56
  stopDaemonProcess
50
- } from "./chunk-IDVD44ED.js";
51
- import "./chunk-3RG5ZIWI.js";
57
+ } from "./chunk-Q4IGYTKR.js";
58
+ import "./chunk-KFQGP6VL.js";
52
59
 
53
60
  // src/router.ts
54
- import { parseArgs, outputFormatter as outputFormatter2, isCommandResult as isCommandResult2, helpGenerator as helpGenerator2 } from "@dyyz1993/xcli-core";
61
+ import { parseArgs, outputFormatter as outputFormatter2, isCommandResult as isCommandResult2, helpGenerator as helpGenerator2, TipCollector as TipCollector2, normalizeTips as normalizeTips7, tip as makeTip } from "@dyyz1993/xcli-core";
55
62
 
56
63
  // src/utils/positional-params.ts
57
64
  import { unquote } from "@dyyz1993/xcli-core";
@@ -171,13 +178,16 @@ var version = pkg.version;
171
178
 
172
179
  // src/executor.ts
173
180
  import {
174
- ok as ok26,
181
+ ok as ok25,
175
182
  fail as fail7,
176
183
  isCommandResult,
184
+ CompositeStorage,
185
+ TipCollector,
186
+ normalizeTips as normalizeTips6,
177
187
  configureArchiveStore,
178
188
  appendCommandToArchive,
179
189
  checkGuard,
180
- PluginStorage
190
+ unquote as unquote2
181
191
  } from "@dyyz1993/xcli-core";
182
192
 
183
193
  // src/commands/navigation.ts
@@ -324,10 +334,18 @@ var urlCommand = registerCommand({
324
334
  return ok({ url: ctx.page.url() });
325
335
  }
326
336
  });
337
+ registerCommand({
338
+ name: "open",
339
+ description: "Navigate to URL (alias for goto)",
340
+ scope: "page",
341
+ parameters: gotoCommand.parameters,
342
+ result: gotoCommand.result,
343
+ handler: gotoCommand.handler
344
+ });
327
345
 
328
346
  // src/commands/interaction.ts
329
347
  import { z as z2 } from "zod";
330
- import { ok as ok2 } from "@dyyz1993/xcli-core";
348
+ import { ok as ok2, normalizeTips } from "@dyyz1993/xcli-core";
331
349
 
332
350
  // src/lib/captcha.ts
333
351
  var CAPTCHA_SELECTORS = {
@@ -395,15 +413,15 @@ var clickCommand = registerCommand({
395
413
  let detectedNewPage;
396
414
  let cleanup;
397
415
  if (ctx.browserContext?.on) {
398
- const pagePromise = new Promise((resolve16) => {
416
+ const pagePromise = new Promise((resolve10) => {
399
417
  const timer = setTimeout(() => {
400
418
  ctx.browserContext.off("page", handler);
401
- resolve16(void 0);
419
+ resolve10(void 0);
402
420
  }, 3e3);
403
421
  const handler = (page2) => {
404
422
  clearTimeout(timer);
405
423
  ctx.browserContext.off("page", handler);
406
- resolve16(page2);
424
+ resolve10(page2);
407
425
  };
408
426
  ctx.browserContext.on("page", handler);
409
427
  });
@@ -438,7 +456,7 @@ var clickCommand = registerCommand({
438
456
  selector: p.selector,
439
457
  newTab: { url: newUrl, title: newTitle }
440
458
  });
441
- result.tips = [`\u65B0 Tab \u5DF2\u6253\u5F00: ${newTitle ? newTitle + " \u2014 " : ""}${newUrl}`];
459
+ result.tips = normalizeTips([`\u65B0 Tab \u5DF2\u6253\u5F00: ${newTitle ? newTitle + " \u2014 " : ""}${newUrl}`]);
442
460
  return result;
443
461
  }
444
462
  const captchaInfo = await detectCaptcha(page);
@@ -452,7 +470,7 @@ var clickCommand = registerCommand({
452
470
  tips.push(solved ? "\u2705 CAPTCHA solved!" : "\u274C CAPTCHA timeout");
453
471
  }
454
472
  const result = ok2({ selector: p.selector, captcha: captchaInfo });
455
- result.tips = tips;
473
+ result.tips = normalizeTips(tips);
456
474
  return result;
457
475
  }
458
476
  return ok2({ selector: p.selector });
@@ -787,7 +805,7 @@ var mouseCommand = registerCommand({
787
805
 
788
806
  // src/commands/evaluate.ts
789
807
  import { z as z7 } from "zod";
790
- import { ok as ok7 } from "@dyyz1993/xcli-core";
808
+ import { ok as ok7, normalizeTips as normalizeTips2 } from "@dyyz1993/xcli-core";
791
809
  var evaluateCommand = registerCommand({
792
810
  name: "eval",
793
811
  description: "Evaluate JavaScript expression in the browser",
@@ -808,10 +826,10 @@ var evaluateCommand = registerCommand({
808
826
  const result = await ctx.page.evaluate(p.expression);
809
827
  const response = ok7({ result });
810
828
  if (decision && decision.severity === "danger") {
811
- response.tips = [
829
+ response.tips = normalizeTips2([
812
830
  `\u26A0\uFE0F CDP Firewall: ${decision.reason}`,
813
831
  `\u{1F4A1} Fix: ${decision.suggestion}`
814
- ];
832
+ ]);
815
833
  }
816
834
  return response;
817
835
  }
@@ -934,7 +952,19 @@ var clearLocalStorageCommand = registerCommand({
934
952
  // src/commands/screenshot.ts
935
953
  import { z as z9 } from "zod";
936
954
  import { ok as ok9 } from "@dyyz1993/xcli-core";
937
- import { writeFileSync } from "fs";
955
+ import { writeFileSync, mkdirSync } from "fs";
956
+ import { join } from "path";
957
+ import { homedir } from "os";
958
+ var SCREENSHOTS_DIR = join(homedir(), ".xbrowser", "screenshots");
959
+ function ensureScreenshotsDir() {
960
+ mkdirSync(SCREENSHOTS_DIR, { recursive: true });
961
+ }
962
+ function generateScreenshotPath(format) {
963
+ const timestamp = Date.now();
964
+ const random = Math.random().toString(36).slice(2, 8);
965
+ const ext = format === "jpeg" ? "jpg" : "png";
966
+ return join(SCREENSHOTS_DIR, `screenshot-${timestamp}-${random}.${ext}`);
967
+ }
938
968
  var screenshotCommand = registerCommand({
939
969
  name: "screenshot",
940
970
  description: "Take a screenshot of the page or element",
@@ -944,7 +974,8 @@ var screenshotCommand = registerCommand({
944
974
  selector: z9.string().optional(),
945
975
  type: z9.enum(["png", "jpeg"]).optional(),
946
976
  fullPage: z9.boolean().optional(),
947
- output: z9.string().optional()
977
+ output: z9.string().optional(),
978
+ base64: z9.boolean().optional().describe("Return base64 data instead of file path")
948
979
  }),
949
980
  result: z9.union([
950
981
  z9.object({
@@ -959,8 +990,9 @@ var screenshotCommand = registerCommand({
959
990
  })
960
991
  ]),
961
992
  handler: async (p, ctx) => {
993
+ const format = p.type || "png";
962
994
  const options = {
963
- type: p.type || "png",
995
+ type: format,
964
996
  fullPage: p.fullPage || false
965
997
  };
966
998
  let buffer;
@@ -973,13 +1005,23 @@ var screenshotCommand = registerCommand({
973
1005
  writeFileSync(p.output, buffer);
974
1006
  return ok9({
975
1007
  output: p.output,
976
- format: p.type || "png",
1008
+ format,
1009
+ size: buffer.length
1010
+ });
1011
+ }
1012
+ if (p.base64) {
1013
+ return ok9({
1014
+ data: buffer.toString("base64"),
1015
+ format,
977
1016
  size: buffer.length
978
1017
  });
979
1018
  }
1019
+ ensureScreenshotsDir();
1020
+ const screenshotPath = generateScreenshotPath(format);
1021
+ writeFileSync(screenshotPath, buffer);
980
1022
  return ok9({
981
- data: buffer.toString("base64"),
982
- format: p.type || "png",
1023
+ output: screenshotPath,
1024
+ format,
983
1025
  size: buffer.length
984
1026
  });
985
1027
  }
@@ -1161,7 +1203,7 @@ var consoleCheckCommand = registerCommand({
1161
1203
  await page.goto(p.url, { waitUntil: "domcontentloaded" });
1162
1204
  }
1163
1205
  const messages = await page.evaluate((args) => {
1164
- return new Promise((resolve16) => {
1206
+ return new Promise((resolve10) => {
1165
1207
  const collected = [];
1166
1208
  const originalConsole = {
1167
1209
  log: console.log,
@@ -1228,7 +1270,7 @@ ${a.stack || ""}`;
1228
1270
  console.warn = originalConsole.warn;
1229
1271
  console.error = originalConsole.error;
1230
1272
  console.info = originalConsole.info;
1231
- resolve16(collected);
1273
+ resolve10(collected);
1232
1274
  }, args.duration);
1233
1275
  });
1234
1276
  }, { duration: p.duration });
@@ -1577,6 +1619,19 @@ var healthCheckCommand = registerCommand({
1577
1619
  // src/commands/actions.ts
1578
1620
  import { z as z14 } from "zod";
1579
1621
  import { ok as ok14 } from "@dyyz1993/xcli-core";
1622
+ import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1623
+ import { join as join2 } from "path";
1624
+ import { homedir as homedir2 } from "os";
1625
+ var SCREENSHOTS_DIR2 = join2(homedir2(), ".xbrowser", "screenshots");
1626
+ function ensureScreenshotsDir2() {
1627
+ mkdirSync2(SCREENSHOTS_DIR2, { recursive: true });
1628
+ }
1629
+ function generateScreenshotPath2(format) {
1630
+ const timestamp = Date.now();
1631
+ const random = Math.random().toString(36).slice(2, 8);
1632
+ const ext = format === "jpeg" ? "jpg" : "png";
1633
+ return join2(SCREENSHOTS_DIR2, `screenshot-${timestamp}-${random}.${ext}`);
1634
+ }
1580
1635
  var waitActionSchema = z14.object({
1581
1636
  type: z14.literal("wait"),
1582
1637
  milliseconds: z14.number().positive().optional(),
@@ -1594,7 +1649,8 @@ var screenshotActionSchema = z14.object({
1594
1649
  type: z14.literal("screenshot"),
1595
1650
  fullPage: z14.boolean().optional(),
1596
1651
  quality: z14.number().min(1).max(100).optional(),
1597
- viewport: z14.object({ width: z14.number().int().positive(), height: z14.number().int().positive() }).optional()
1652
+ viewport: z14.object({ width: z14.number().int().positive(), height: z14.number().int().positive() }).optional(),
1653
+ base64: z14.boolean().optional().describe("Return base64 data instead of file path")
1598
1654
  });
1599
1655
  var writeActionSchema = z14.object({
1600
1656
  type: z14.literal("write"),
@@ -1640,7 +1696,7 @@ async function executeAction(page, action) {
1640
1696
  if (action.selector) {
1641
1697
  await page.waitForSelector(action.selector, { timeout: 3e4 });
1642
1698
  } else if (action.milliseconds) {
1643
- await new Promise((resolve16) => setTimeout(resolve16, action.milliseconds));
1699
+ await new Promise((resolve10) => setTimeout(resolve10, action.milliseconds));
1644
1700
  } else {
1645
1701
  throw new Error("wait action requires either milliseconds or selector");
1646
1702
  }
@@ -1661,7 +1717,13 @@ async function executeAction(page, action) {
1661
1717
  quality: action.quality ?? 80,
1662
1718
  ...action.viewport ? { clip: { x: 0, y: 0, ...action.viewport } } : {}
1663
1719
  });
1664
- return { type: "screenshot", result: buf.toString("base64") };
1720
+ if (action.base64) {
1721
+ return { type: "screenshot", result: buf.toString("base64"), base64: true };
1722
+ }
1723
+ ensureScreenshotsDir2();
1724
+ const screenshotPath = generateScreenshotPath2("jpg");
1725
+ writeFileSync2(screenshotPath, buf);
1726
+ return { type: "screenshot", result: screenshotPath };
1665
1727
  }
1666
1728
  case "write":
1667
1729
  await page.keyboard.type(action.text);
@@ -1723,8 +1785,8 @@ var actionsCommand = registerCommand({
1723
1785
  results.push(result);
1724
1786
  }
1725
1787
  })();
1726
- const timeoutPromise = new Promise((resolve16) => {
1727
- setTimeout(resolve16, timeoutMs);
1788
+ const timeoutPromise = new Promise((resolve10) => {
1789
+ setTimeout(resolve10, timeoutMs);
1728
1790
  });
1729
1791
  await Promise.race([executionPromise, timeoutPromise]);
1730
1792
  const title = await ctx.page.title();
@@ -2398,11 +2460,11 @@ async function navigateForMap(page, url, timeout = 15e3) {
2398
2460
  }
2399
2461
  async function extractPageLinks(page, baseUrl) {
2400
2462
  await navigateForMap(page, baseUrl);
2401
- await new Promise((resolve16) => setTimeout(resolve16, 2e3));
2463
+ await new Promise((resolve10) => setTimeout(resolve10, 2e3));
2402
2464
  await page.evaluate(() => {
2403
2465
  window.scrollTo(0, document.body.scrollHeight);
2404
2466
  });
2405
- await new Promise((resolve16) => setTimeout(resolve16, 1e3));
2467
+ await new Promise((resolve10) => setTimeout(resolve10, 1e3));
2406
2468
  const origin = new URL(baseUrl).origin;
2407
2469
  const rawLinks = await page.evaluate((evalOrigin) => {
2408
2470
  return Array.from(document.querySelectorAll("a[href]")).map((a) => {
@@ -3828,7 +3890,7 @@ var ENGINE_KEY_ENUM = z20.enum(ALL_ENGINE_KEYS);
3828
3890
 
3829
3891
  // src/commands/snapshot.ts
3830
3892
  import { z as z21 } from "zod";
3831
- import { ok as ok20, fail as fail4 } from "@dyyz1993/xcli-core";
3893
+ import { ok as ok20, fail as fail4, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
3832
3894
 
3833
3895
  // src/runtime/ref-store.ts
3834
3896
  var sessions = /* @__PURE__ */ new Map();
@@ -4069,9 +4131,9 @@ async function resolveRefParams(page, params, selectorKeys, cache, sessionId) {
4069
4131
  }
4070
4132
 
4071
4133
  // src/utils/site-semantics.ts
4072
- import { writeFileSync as writeFileSync2, mkdirSync, existsSync, readFileSync } from "fs";
4073
- import { join, dirname } from "path";
4074
- import { homedir } from "os";
4134
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync, readFileSync } from "fs";
4135
+ import { join as join3, dirname } from "path";
4136
+ import { homedir as homedir3 } from "os";
4075
4137
  import { stringify, parse } from "yaml";
4076
4138
  import { execFile } from "child_process";
4077
4139
  var INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
@@ -4148,10 +4210,10 @@ function inferAction(role, label) {
4148
4210
  return {};
4149
4211
  }
4150
4212
  function getSemanticsDir() {
4151
- return join(homedir(), ".xbrowser", "site-semantics");
4213
+ return join3(homedir3(), ".xbrowser", "site-semantics");
4152
4214
  }
4153
4215
  function getSemanticsPath(domain) {
4154
- return join(getSemanticsDir(), `${domain}.yaml`);
4216
+ return join3(getSemanticsDir(), `${domain}.yaml`);
4155
4217
  }
4156
4218
  function extractDomain2(url) {
4157
4219
  try {
@@ -4188,9 +4250,9 @@ function saveSemantics(domain, pagePath, url, elements) {
4188
4250
  }
4189
4251
  site.updated_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4190
4252
  if (!existsSync(dir)) {
4191
- mkdirSync(dir, { recursive: true });
4253
+ mkdirSync3(dir, { recursive: true });
4192
4254
  }
4193
- writeFileSync2(filePath, stringify(site, { lineWidth: 0 }));
4255
+ writeFileSync3(filePath, stringify(site, { lineWidth: 0 }));
4194
4256
  }
4195
4257
  function loadSemantics(domain) {
4196
4258
  const filePath = getSemanticsPath(domain);
@@ -4260,25 +4322,25 @@ aria snapshot\uFF1A
4260
4322
  async function analyzeWithLLM(ariaSnapshot) {
4261
4323
  const piBin = process.env.PI_CLI_PATH || "pi";
4262
4324
  const prompt = LLM_PROMPT.replace("{snapshot}", ariaSnapshot.slice(0, 4e3));
4263
- return new Promise((resolve16) => {
4325
+ return new Promise((resolve10) => {
4264
4326
  execFile(
4265
4327
  piBin,
4266
4328
  ["--provider", LLM_PROVIDER, "--model", LLM_MODEL, prompt],
4267
4329
  { timeout: LLM_TIMEOUT_MS, maxBuffer: 1024 * 1024 },
4268
4330
  (err, stdout, _stderr) => {
4269
4331
  if (err) {
4270
- resolve16(null);
4332
+ resolve10(null);
4271
4333
  return;
4272
4334
  }
4273
4335
  const output = (stdout || "").trim();
4274
4336
  if (!output) {
4275
- resolve16(null);
4337
+ resolve10(null);
4276
4338
  return;
4277
4339
  }
4278
4340
  try {
4279
4341
  const parsed = parse(output);
4280
4342
  if (!parsed || typeof parsed !== "object") {
4281
- resolve16(null);
4343
+ resolve10(null);
4282
4344
  return;
4283
4345
  }
4284
4346
  const elements = {};
@@ -4293,9 +4355,9 @@ async function analyzeWithLLM(ariaSnapshot) {
4293
4355
  };
4294
4356
  }
4295
4357
  }
4296
- resolve16(Object.keys(elements).length > 0 ? elements : null);
4358
+ resolve10(Object.keys(elements).length > 0 ? elements : null);
4297
4359
  } catch {
4298
- resolve16(null);
4360
+ resolve10(null);
4299
4361
  }
4300
4362
  }
4301
4363
  );
@@ -4650,7 +4712,7 @@ async function pollUntil(timeout, pollInterval, predicate) {
4650
4712
  const startedAt = Date.now();
4651
4713
  while (Date.now() - startedAt <= timeout) {
4652
4714
  if (await predicate()) return true;
4653
- await new Promise((resolve16) => setTimeout(resolve16, pollInterval));
4715
+ await new Promise((resolve10) => setTimeout(resolve10, pollInterval));
4654
4716
  }
4655
4717
  return false;
4656
4718
  }
@@ -4732,7 +4794,7 @@ var snapshotCommand = registerCommand({
4732
4794
  interactive: z21.boolean().optional().default(false).describe("Return interactive agent refs only"),
4733
4795
  interactiveOnly: z21.boolean().optional().default(false).describe("Alias for interactive"),
4734
4796
  i: z21.boolean().optional().default(false).describe("Short alias for interactive"),
4735
- compact: z21.boolean().optional().default(false).describe("Include compact agent-browser style snapshot text"),
4797
+ compact: z21.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
4736
4798
  c: z21.boolean().optional().default(false).describe("Short alias for compact"),
4737
4799
  selectors: z21.boolean().optional().default(false).describe("Include ref to CSS selector map"),
4738
4800
  all: z21.boolean().optional().default(false).describe("Include hidden interactive targets when using interactive snapshot")
@@ -4756,15 +4818,15 @@ var snapshotCommand = registerCommand({
4756
4818
  if (p.compact || p.c || p.interactive || p.interactiveOnly || p.i) {
4757
4819
  observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
4758
4820
  }
4759
- return ok20(observation, [
4821
+ return ok20(observation, normalizeTips3([
4760
4822
  `refs refreshed for ${observation.targets.length} targets; use click @e1 or fill @e2 "text"`
4761
- ]);
4823
+ ]));
4762
4824
  }
4763
4825
  if (p.type === "aria") {
4764
4826
  const aria = await captureAriaSnapshot(page, p.selector, p.depth);
4765
4827
  const tips = await buildRefTips(page, aria);
4766
4828
  persistSemantics(url, aria);
4767
- return ok20({ url, title, aria }, tips);
4829
+ return ok20({ url, title, aria }, normalizeTips3(tips));
4768
4830
  }
4769
4831
  if (p.type === "text") {
4770
4832
  const text = await captureTextSnapshot(page, p.selector);
@@ -4782,7 +4844,7 @@ var snapshotCommand = registerCommand({
4782
4844
  ]);
4783
4845
  const tips = await buildRefTips(page, aria);
4784
4846
  persistSemantics(url, aria);
4785
- return ok20({ url, title, aria, text, dom }, tips);
4847
+ return ok20({ url, title, aria, text, dom }, normalizeTips3(tips));
4786
4848
  }
4787
4849
  return fail4(`Unknown snapshot type: ${p.type}`);
4788
4850
  }
@@ -4862,7 +4924,7 @@ async function captureDomSnapshot(page, selector, maxDepth) {
4862
4924
 
4863
4925
  // src/commands/agent.ts
4864
4926
  import { z as z22 } from "zod";
4865
- import { ok as ok21 } from "@dyyz1993/xcli-core";
4927
+ import { ok as ok21, normalizeTips as normalizeTips4 } from "@dyyz1993/xcli-core";
4866
4928
  var observeCommand = registerCommand({
4867
4929
  name: "observe",
4868
4930
  description: "Observe the current page as structured agent targets with session refs",
@@ -4870,9 +4932,14 @@ var observeCommand = registerCommand({
4870
4932
  parameters: z22.object({
4871
4933
  includeHidden: z22.boolean().optional().default(false).describe("Include hidden elements in the target list"),
4872
4934
  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 agent-browser style snapshot text"),
4935
+ compact: z22.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
4874
4936
  selectors: z22.boolean().optional().default(false).describe("Include ref to stable CSS selector map")
4875
4937
  }),
4938
+ result: z22.object({
4939
+ targets: z22.array(z22.record(z22.unknown())),
4940
+ selectors: z22.record(z22.unknown()).optional(),
4941
+ compact: z22.string().optional()
4942
+ }).passthrough(),
4876
4943
  handler: async (p, ctx) => {
4877
4944
  const observation = await observePage(ctx.page, ctx.sessionId, {
4878
4945
  includeHidden: p.includeHidden,
@@ -4880,9 +4947,9 @@ var observeCommand = registerCommand({
4880
4947
  });
4881
4948
  if (p.selectors) observation.selectors = buildSelectorMap(observation);
4882
4949
  if (p.compact) observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
4883
- return ok21(observation, [
4950
+ return ok21(observation, normalizeTips4([
4884
4951
  `refs refreshed for ${observation.targets.length} targets; use act --ref @e1 --action click or click @e1`
4885
- ]);
4952
+ ]));
4886
4953
  }
4887
4954
  });
4888
4955
  var actCommand = registerCommand({
@@ -4908,10 +4975,10 @@ var actCommand = registerCommand({
4908
4975
  success: false,
4909
4976
  data: result,
4910
4977
  message: result.message || result.reason || "Action failed",
4911
- tips: result.stale ? ["run observe again to refresh refs"] : []
4978
+ tips: normalizeTips4(result.stale ? ["run observe again to refresh refs"] : [])
4912
4979
  };
4913
4980
  }
4914
- return ok21(result, result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []);
4981
+ return ok21(result, normalizeTips4(result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []));
4915
4982
  }
4916
4983
  });
4917
4984
  var waitForCommand = registerCommand({
@@ -5087,7 +5154,8 @@ import { ok as ok23 } from "@dyyz1993/xcli-core";
5087
5154
  import { readFileSync as readFileSync2 } from "fs";
5088
5155
 
5089
5156
  // src/chain-parser.ts
5090
- import { unquote as unquote2 } from "@dyyz1993/xcli-core";
5157
+ import { splitCommand, parseCommandArgs } from "@dyyz1993/xcli-core";
5158
+ import { registerCommandDefinition } from "@dyyz1993/xcli-core";
5091
5159
  function parseCommandChain(input, options) {
5092
5160
  const result = [];
5093
5161
  let currentPipeline = [];
@@ -5178,149 +5246,46 @@ function isSpaceAround(input, pos, tokenLen) {
5178
5246
  const after = pos + tokenLen < input.length && input[pos + tokenLen] === " ";
5179
5247
  return before && after;
5180
5248
  }
5181
- function splitCommand(cmdStr) {
5182
- const parts = [];
5183
- let current = "";
5184
- let inQuote = null;
5185
- for (let i = 0; i < cmdStr.length; i++) {
5186
- const char = cmdStr[i];
5187
- if (!inQuote && (char === "'" || char === '"')) {
5188
- inQuote = char;
5189
- current += char;
5190
- continue;
5191
- }
5192
- if (inQuote && char === inQuote) {
5193
- inQuote = null;
5194
- current += char;
5195
- continue;
5196
- }
5197
- if (!inQuote && /\s/.test(char)) {
5198
- if (current.trim()) {
5199
- parts.push(current.trim());
5200
- current = "";
5201
- }
5202
- continue;
5203
- }
5204
- current += char;
5205
- }
5206
- if (current.trim()) {
5207
- parts.push(current.trim());
5208
- }
5209
- return parts;
5210
- }
5211
- var SHORT_FLAG_MAP = {
5212
- s: "selector",
5213
- v: "value"
5214
- };
5215
- function coerceValue2(raw) {
5216
- const v = unquote2(raw);
5217
- if (v === "true") return true;
5218
- if (v === "false") return false;
5219
- if (/^\d+$/.test(v)) return parseInt(v, 10);
5220
- if (/^\d+\.\d+$/.test(v)) return parseFloat(v);
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
- }
5249
+ registerCommandDefinition("goto", ["url"]);
5250
+ registerCommandDefinition("click", ["selector"]);
5251
+ registerCommandDefinition("fill", ["selector", "value"]);
5252
+ registerCommandDefinition("type", ["selector", "text"]);
5253
+ registerCommandDefinition("press", ["selector", "key"]);
5254
+ registerCommandDefinition("select", ["selector", "value"]);
5255
+ registerCommandDefinition("check", ["selector"]);
5256
+ registerCommandDefinition("uncheck", ["selector"]);
5257
+ registerCommandDefinition("hover", ["selector"]);
5258
+ registerCommandDefinition("dblclick", ["selector"]);
5259
+ registerCommandDefinition("wait", ["selector"]);
5260
+ registerCommandDefinition("screenshot", []);
5261
+ registerCommandDefinition("eval", ["expression"]);
5262
+ registerCommandDefinition("scroll", ["direction"]);
5263
+ registerCommandDefinition("title", []);
5264
+ registerCommandDefinition("url", []);
5265
+ registerCommandDefinition("html", []);
5266
+ registerCommandDefinition("text", []);
5267
+ registerCommandDefinition("back", []);
5268
+ registerCommandDefinition("forward", []);
5269
+ registerCommandDefinition("refresh", []);
5270
+ registerCommandDefinition("console", []);
5271
+ registerCommandDefinition("network", []);
5272
+ registerCommandDefinition("perf", []);
5273
+ registerCommandDefinition("health", []);
5274
+ registerCommandDefinition("scrape", ["url"]);
5275
+ registerCommandDefinition("structure", []);
5276
+ registerCommandDefinition("get-cookies", []);
5277
+ registerCommandDefinition("set-cookie", []);
5278
+ registerCommandDefinition("clear-cookies", []);
5279
+ registerCommandDefinition("get-local-storage", []);
5280
+ registerCommandDefinition("set-local-storage", []);
5281
+ registerCommandDefinition("clear-local-storage", []);
5282
+ registerCommandDefinition("set-viewport", []);
5283
+ registerCommandDefinition("frames", []);
5284
+ registerCommandDefinition("frame", ["selector"]);
5285
+ registerCommandDefinition("actions", ["url"]);
5286
+ registerCommandDefinition("find", ["strategy", "value", "operation"]);
5287
+ registerCommandDefinition("addinitscript", ["script"]);
5288
+ registerCommandDefinition("tab", ["subcommand"]);
5324
5289
 
5325
5290
  // src/commands/addinitscript.ts
5326
5291
  var InitScriptParams = z24.object({
@@ -5350,12 +5315,12 @@ function resolveScriptContent(params) {
5350
5315
  }
5351
5316
  async function readStdin() {
5352
5317
  const { createReadStream } = await import("fs");
5353
- const { createInterface: createInterface2 } = await import("readline");
5354
- return new Promise((resolve16, reject) => {
5318
+ const { createInterface } = await import("readline");
5319
+ return new Promise((resolve10, reject) => {
5355
5320
  const lines = [];
5356
- const rl = createInterface2({ input: createReadStream("/dev/stdin") });
5321
+ const rl = createInterface({ input: createReadStream("/dev/stdin") });
5357
5322
  rl.on("line", (line) => lines.push(line));
5358
- rl.on("close", () => resolve16(lines.join("\n")));
5323
+ rl.on("close", () => resolve10(lines.join("\n")));
5359
5324
  rl.on("error", reject);
5360
5325
  });
5361
5326
  }
@@ -5364,6 +5329,15 @@ var addInitScriptCommand = registerCommand({
5364
5329
  description: "Add an initialization script that runs on every page load",
5365
5330
  scope: "page",
5366
5331
  parameters: InitScriptParams,
5332
+ result: z24.object({
5333
+ scripts: z24.array(z24.object({ name: z24.string(), size: z24.number(), preview: z24.string() })).optional(),
5334
+ removed: z24.string().optional(),
5335
+ existed: z24.boolean().optional(),
5336
+ error: z24.string().optional(),
5337
+ registered: z24.string().optional(),
5338
+ hint: z24.string().optional(),
5339
+ executedImmediately: z24.boolean().optional()
5340
+ }).passthrough(),
5367
5341
  handler: async (params, ctx) => {
5368
5342
  if (params.list) {
5369
5343
  const scripts = Array.from(registeredScripts.entries()).map(([n, content2]) => ({
@@ -5399,7 +5373,7 @@ registerCommandDefinition("addinitscript", ["script"]);
5399
5373
 
5400
5374
  // src/commands/find.ts
5401
5375
  import { z as z25 } from "zod";
5402
- import { ok as ok24, fail as fail6 } from "@dyyz1993/xcli-core";
5376
+ import { ok as ok24, fail as fail6, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
5403
5377
  var actionSchema2 = z25.enum(["click", "fill", "type", "select", "hover", "check"]);
5404
5378
  var findCommand = registerCommand({
5405
5379
  name: "find",
@@ -5475,7 +5449,7 @@ var findCommand = registerCommand({
5475
5449
  });
5476
5450
  function okWithTips(data, tips) {
5477
5451
  const result = ok24(data);
5478
- if (tips.length > 0) result.tips = tips;
5452
+ if (tips.length > 0) result.tips = normalizeTips5(tips);
5479
5453
  return result;
5480
5454
  }
5481
5455
  function parseOperation(operation) {
@@ -5730,16 +5704,11 @@ async function detectWebdriverExposure(page) {
5730
5704
  try {
5731
5705
  const webdriver = await page.evaluate(() => {
5732
5706
  return {
5733
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5734
- webdriver: window.navigator?.webdriver,
5735
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5707
+ webdriver: window.navigator && window.navigator instanceof Object ? window.navigator.webdriver : void 0,
5736
5708
  webdriverScriptFn: !!window.__webdriver_script_fn,
5737
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5738
5709
  webdriverEvaluate: !!window.__webdriver_evaluate,
5739
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5740
5710
  chrome: !!window.chrome,
5741
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5742
- permissions: window.navigator?.permissions
5711
+ permissions: window.navigator && window.navigator instanceof Object ? window.navigator.permissions : void 0
5743
5712
  };
5744
5713
  }).catch(() => null);
5745
5714
  if (!webdriver) {
@@ -5819,377 +5788,23 @@ function formatDetectionMessage(result) {
5819
5788
  Action: ${action}`;
5820
5789
  }
5821
5790
 
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
5791
  // src/plugin/loader.ts
6177
5792
  import {
6178
5793
  Core
6179
5794
  } from "@dyyz1993/xcli-core";
6180
- import { resolve as resolve8 } from "path";
6181
- import { existsSync as existsSync5, readdirSync } from "fs";
6182
- import { homedir as homedir2 } from "os";
5795
+ import { resolve as resolve2 } from "path";
5796
+ import { existsSync as existsSync4, readdirSync } from "fs";
5797
+ import { homedir as homedir4 } from "os";
6183
5798
 
6184
5799
  // src/plugin/metadata-parser.ts
6185
- import { existsSync as existsSync3 } from "fs";
6186
- import { resolve as resolve7 } from "path";
5800
+ import { existsSync as existsSync2 } from "fs";
5801
+ import { resolve } from "path";
6187
5802
 
6188
5803
  // src/utils/json-file.ts
6189
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
5804
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
6190
5805
  function readJsonFile(filePath, defaultValue) {
6191
5806
  try {
6192
- const content = readFileSync9(filePath, "utf-8");
5807
+ const content = readFileSync3(filePath, "utf-8");
6193
5808
  return JSON.parse(content);
6194
5809
  } catch {
6195
5810
  return defaultValue;
@@ -6200,8 +5815,8 @@ function readJsonFile(filePath, defaultValue) {
6200
5815
  var PluginMetadataParser = class {
6201
5816
  static XBROWSER_KEYWORDS = ["xbrowser", "xbrowser-plugin"];
6202
5817
  static parseFromPackageJson(pluginPath) {
6203
- const packageJsonPath = resolve7(pluginPath, "package.json");
6204
- if (!existsSync3(packageJsonPath)) {
5818
+ const packageJsonPath = resolve(pluginPath, "package.json");
5819
+ if (!existsSync2(packageJsonPath)) {
6205
5820
  return null;
6206
5821
  }
6207
5822
  const packageJson = readJsonFile(packageJsonPath, null);
@@ -6265,20 +5880,20 @@ var PluginMetadataParser = class {
6265
5880
  };
6266
5881
 
6267
5882
  // src/plugin/ensure-deps.ts
6268
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
6269
- import { join as join2 } from "path";
6270
- import { execSync as execSync6 } from "child_process";
5883
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
5884
+ import { join as join4 } from "path";
5885
+ import { execSync } from "child_process";
6271
5886
  var SHARED_PLUGIN_DEPENDENCIES = {
6272
5887
  "zod": "^3.24.0",
6273
5888
  "@dyyz1993/xcli-core": "^0.12.1"
6274
5889
  };
6275
5890
  function ensurePluginDependencies(pluginsDir) {
6276
- const zodPath = join2(pluginsDir, "node_modules", "zod");
6277
- if (existsSync4(zodPath)) return;
6278
- mkdirSync2(pluginsDir, { recursive: true });
6279
- const pkgPath = join2(pluginsDir, "package.json");
5891
+ const zodPath = join4(pluginsDir, "node_modules", "zod");
5892
+ if (existsSync3(zodPath)) return;
5893
+ mkdirSync4(pluginsDir, { recursive: true });
5894
+ const pkgPath = join4(pluginsDir, "package.json");
6280
5895
  let pkg2 = {};
6281
- if (existsSync4(pkgPath)) {
5896
+ if (existsSync3(pkgPath)) {
6282
5897
  try {
6283
5898
  pkg2 = readJsonFile(pkgPath, {});
6284
5899
  } catch {
@@ -6292,13 +5907,13 @@ function ensurePluginDependencies(pluginsDir) {
6292
5907
  needsInstall = true;
6293
5908
  }
6294
5909
  }
6295
- if (!needsInstall && existsSync4(join2(pluginsDir, "node_modules"))) return;
5910
+ if (!needsInstall && existsSync3(join4(pluginsDir, "node_modules"))) return;
6296
5911
  pkg2.dependencies = existingDeps;
6297
5912
  pkg2.private = true;
6298
5913
  pkg2.description = pkg2.description || "xbrowser plugins \u2014 shared dependencies";
6299
- writeFileSync4(pkgPath, JSON.stringify(pkg2, null, 2) + "\n", "utf-8");
5914
+ writeFileSync5(pkgPath, JSON.stringify(pkg2, null, 2) + "\n", "utf-8");
6300
5915
  try {
6301
- execSync6("npm install --production --no-package-lock --no-fund --no-audit", {
5916
+ execSync("npm install --production --no-package-lock --no-fund --no-audit", {
6302
5917
  cwd: pluginsDir,
6303
5918
  stdio: "pipe",
6304
5919
  timeout: 6e4,
@@ -6310,8 +5925,15 @@ function ensurePluginDependencies(pluginsDir) {
6310
5925
  }
6311
5926
 
6312
5927
  // src/plugin/contract.ts
5928
+ import {
5929
+ unwrapZod,
5930
+ fieldsFromZodObjectReflected,
5931
+ zodTypeToContractType
5932
+ } from "@dyyz1993/xcli-core";
6313
5933
  function buildPluginContract(site) {
6314
- const commands = site.getAllCommands().map((command) => buildCommandContract(site.getCommand?.(command.name) || command));
5934
+ const commands = site.getAllCommands().map((command) => buildCommandContract(site.getCommand?.(command.name) || command, {
5935
+ siteRequiresLogin: site.config?.requiresLogin
5936
+ }));
6315
5937
  return {
6316
5938
  version: 2,
6317
5939
  plugin: {
@@ -6323,18 +5945,19 @@ function buildPluginContract(site) {
6323
5945
  commands
6324
5946
  };
6325
5947
  }
6326
- function buildCommandContract(command) {
5948
+ function buildCommandContract(command, options = {}) {
6327
5949
  const extension = command.xbrowser || {};
6328
5950
  const inferredFields = fieldsFromZodObject(command.parameters);
6329
5951
  const fields = mergeFields(inferredFields, extension.form?.fields || []);
6330
5952
  const positional = extension.positional || fields.filter((field) => field.positional).map((field) => field.name);
6331
- const capabilities = extension.capabilities || inferCapabilities(command.scope || "project", command.requiresLogin);
5953
+ const requiresLogin = command.requiresLogin === true || options.siteRequiresLogin === true && command.name !== "login" && command.name !== "logout";
5954
+ const capabilities = extension.capabilities || inferCapabilities(command.scope || "project", requiresLogin);
6332
5955
  const outputSchema = command.result ? summarizeZod(command.result) : void 0;
6333
5956
  return {
6334
5957
  name: command.name,
6335
5958
  description: command.description || "",
6336
5959
  scope: command.scope || "project",
6337
- requiresLogin: command.requiresLogin === true,
5960
+ requiresLogin,
6338
5961
  category: extension.category,
6339
5962
  capabilities,
6340
5963
  positional,
@@ -6348,25 +5971,21 @@ function buildCommandContract(command) {
6348
5971
  };
6349
5972
  }
6350
5973
  function fieldsFromZodObject(schema) {
6351
- const shape = getShape(schema);
6352
- if (!shape) return [];
6353
- return Object.entries(shape).map(([name, field]) => fieldFromZod(name, field));
6354
- }
6355
- function fieldFromZod(name, schema) {
6356
- const unwrapped = unwrapZod(schema);
6357
- const type = zodTypeToContractType(unwrapped.typeName);
6358
- const enumValues = extractEnumValues(unwrapped.schema);
6359
- return {
6360
- name,
6361
- label: toLabel(name),
6362
- type,
6363
- widget: widgetFor(type, enumValues, unwrapped.schema),
6364
- required: !unwrapped.optional,
6365
- ...unwrapped.description ? { description: unwrapped.description } : {},
6366
- ...unwrapped.defaultValue !== void 0 ? { default: unwrapped.defaultValue } : {},
6367
- ...enumValues ? { enum: enumValues } : {},
6368
- ...type === "array" ? { multiple: true } : {}
6369
- };
5974
+ const reflected = fieldsFromZodObjectReflected(schema);
5975
+ return reflected.map((field) => {
5976
+ const widget = widgetFor(field.type, field.enum);
5977
+ return {
5978
+ name: field.name,
5979
+ label: toLabel(field.name),
5980
+ type: field.type,
5981
+ widget,
5982
+ required: field.required,
5983
+ ...field.description ? { description: field.description } : {},
5984
+ ...field.default !== void 0 ? { default: field.default } : {},
5985
+ ...field.enum ? { enum: field.enum } : {},
5986
+ ...field.type === "array" ? { multiple: true } : {}
5987
+ };
5988
+ });
6370
5989
  }
6371
5990
  function mergeFields(inferred, overrides) {
6372
5991
  if (overrides.length === 0) return inferred;
@@ -6397,72 +6016,14 @@ function inferCapabilities(scope, requiresLogin) {
6397
6016
  if (requiresLogin) caps.push("auth.login");
6398
6017
  return caps;
6399
6018
  }
6400
- function getShape(schema) {
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) {
6019
+ function widgetFor(type, enumValues) {
6451
6020
  if (enumValues) return "select";
6452
6021
  if (type === "boolean") return "checkbox";
6453
6022
  if (type === "number") return "number";
6454
6023
  if (type === "array") return "multi-select";
6455
6024
  if (type === "object") return "json";
6456
- const checks = schema?._def?.checks;
6457
- if (checks?.some((check) => check.kind === "url")) return "url";
6458
6025
  return "text";
6459
6026
  }
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
6027
  function summarizeZod(schema) {
6467
6028
  const unwrapped = unwrapZod(schema);
6468
6029
  if (unwrapped.typeName === "ZodArray") {
@@ -6472,7 +6033,7 @@ function summarizeZod(schema) {
6472
6033
  items: summarizeZod(def?.type || def?.innerType)
6473
6034
  };
6474
6035
  }
6475
- const shape = getShape(schema);
6036
+ const shape = getObjectShape(schema);
6476
6037
  if (!shape) {
6477
6038
  return {
6478
6039
  type: zodTypeToContractType(unwrapped.typeName),
@@ -6482,19 +6043,46 @@ function summarizeZod(schema) {
6482
6043
  }
6483
6044
  return Object.fromEntries(
6484
6045
  Object.entries(shape).map(([name, field]) => {
6485
- const unwrapped2 = unwrapZod(field);
6046
+ const inner = unwrapZod(field);
6486
6047
  return [name, {
6487
- type: zodTypeToContractType(unwrapped2.typeName),
6488
- required: !unwrapped2.optional,
6489
- ...unwrapped2.description ? { description: unwrapped2.description } : {}
6048
+ type: zodTypeToContractType(inner.typeName),
6049
+ required: !inner.optional,
6050
+ ...inner.description ? { description: inner.description } : {}
6490
6051
  }];
6491
6052
  })
6492
6053
  );
6493
6054
  }
6055
+ function getObjectShape(schema) {
6056
+ const zod = schema;
6057
+ const shapeOrFn = zod?.shape ?? zod?._def?.shape;
6058
+ if (!shapeOrFn) return void 0;
6059
+ return typeof shapeOrFn === "function" ? shapeOrFn() : shapeOrFn;
6060
+ }
6494
6061
  function toLabel(name) {
6495
6062
  return name.replace(/([A-Z])/g, " $1").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().replace(/^./, (char) => char.toUpperCase());
6496
6063
  }
6497
6064
 
6065
+ // src/plugin/login-required-patch.ts
6066
+ import { SiteInstanceImpl } from "@dyyz1993/xcli-core";
6067
+ var patched = false;
6068
+ function patchLoginRequired() {
6069
+ if (patched) return;
6070
+ patched = true;
6071
+ const proto = SiteInstanceImpl.prototype;
6072
+ const original = proto.command;
6073
+ proto.command = function(name, cmd) {
6074
+ const result = original.call(this, name, cmd);
6075
+ const loginRequired = cmd.loginRequired;
6076
+ if (loginRequired) {
6077
+ const entry = this.commands.get(name);
6078
+ if (entry) {
6079
+ entry.loginRequired = loginRequired;
6080
+ }
6081
+ }
6082
+ return result;
6083
+ };
6084
+ }
6085
+
6498
6086
  // src/plugin/loader.ts
6499
6087
  var DEFAULT_PLUGIN_DIRS = [".xcli/plugins", "../.xcli/plugins"];
6500
6088
  var XBrowserPluginLoader = class {
@@ -6502,6 +6090,7 @@ var XBrowserPluginLoader = class {
6502
6090
  loader;
6503
6091
  options;
6504
6092
  constructor(options) {
6093
+ patchLoginRequired();
6505
6094
  this.options = options ?? {};
6506
6095
  const cwd = this.options.cwd || process.cwd();
6507
6096
  const coreConfig = {
@@ -6512,7 +6101,7 @@ var XBrowserPluginLoader = class {
6512
6101
  envPrefix: "XBROWSER",
6513
6102
  pluginDirs: [
6514
6103
  ...DEFAULT_PLUGIN_DIRS,
6515
- resolve8(cwd, ".xcli/plugins")
6104
+ resolve2(cwd, ".xcli/plugins")
6516
6105
  ]
6517
6106
  };
6518
6107
  this.core = new Core(coreConfig);
@@ -6558,31 +6147,31 @@ var XBrowserPluginLoader = class {
6558
6147
  }
6559
6148
  async scanAndLoad() {
6560
6149
  const cwd = this.options.cwd || process.cwd();
6561
- const globalDir = this.options.globalDir || resolve8(homedir2(), ".xbrowser/plugins");
6150
+ const globalDir = this.options.globalDir || resolve2(homedir4(), ".xbrowser/plugins");
6562
6151
  ensurePluginDependencies(globalDir);
6563
6152
  const dirs = [
6564
- resolve8(cwd, ".xcli/plugins"),
6565
- resolve8(cwd, "../.xcli/plugins"),
6566
- this.options.userDir || resolve8(homedir2(), ".xcli/plugins"),
6153
+ resolve2(cwd, ".xcli/plugins"),
6154
+ resolve2(cwd, "../.xcli/plugins"),
6155
+ this.options.userDir || resolve2(homedir4(), ".xcli/plugins"),
6567
6156
  globalDir
6568
6157
  ];
6569
6158
  const loaded = [];
6570
6159
  const seen = /* @__PURE__ */ new Set();
6571
6160
  for (const dir of dirs) {
6572
- if (!existsSync5(dir)) continue;
6161
+ if (!existsSync4(dir)) continue;
6573
6162
  const entries = readdirSync(dir, { withFileTypes: true });
6574
6163
  for (const entry of entries) {
6575
6164
  if (!entry.isDirectory()) continue;
6576
6165
  if (seen.has(entry.name)) continue;
6577
6166
  seen.add(entry.name);
6578
- const pluginDir = resolve8(dir, entry.name);
6579
- let indexPath = resolve8(pluginDir, "index.js");
6580
- if (!existsSync5(indexPath)) {
6581
- indexPath = resolve8(pluginDir, "index.ts");
6167
+ const pluginDir = resolve2(dir, entry.name);
6168
+ let indexPath = resolve2(pluginDir, "index.js");
6169
+ if (!existsSync4(indexPath)) {
6170
+ indexPath = resolve2(pluginDir, "index.ts");
6582
6171
  }
6583
- if (!existsSync5(indexPath)) continue;
6172
+ if (!existsSync4(indexPath)) continue;
6584
6173
  try {
6585
- if (!existsSync5(resolve8(pluginDir, "package.json"))) {
6174
+ if (!existsSync4(resolve2(pluginDir, "package.json"))) {
6586
6175
  console.warn(`\u26A0\uFE0F Plugin "${entry.name}" has no package.json. Use "xbrowser create ${entry.name} --template static" for proper structure.`);
6587
6176
  } else {
6588
6177
  const metadata = PluginMetadataParser.parseFromPackageJson(pluginDir);
@@ -6620,6 +6209,110 @@ async function getPluginLoader() {
6620
6209
  return pluginLoader;
6621
6210
  }
6622
6211
 
6212
+ // src/utils/viewer-url.ts
6213
+ function buildViewerUrl(sessionName = "default") {
6214
+ try {
6215
+ const status = getDaemonProcessStatus();
6216
+ if (!status.running) return void 0;
6217
+ const port = status.port || getDaemonConfig().basePort;
6218
+ return `http://localhost:${port}/preview/${encodeURIComponent(sessionName)}`;
6219
+ } catch {
6220
+ return void 0;
6221
+ }
6222
+ }
6223
+
6224
+ // src/plugin/login-guard.ts
6225
+ async function checkPluginLoginRequired(options) {
6226
+ const { site, command, commandName, ctx, page, sessionName } = options;
6227
+ if (commandName === "login" || commandName === "logout") return { ok: true };
6228
+ const loginConfig = site.config?.loginConfig;
6229
+ const requiresLogin = command.requiresLogin === true || site.config?.requiresLogin === true || loginConfig?.requiresLogin === true;
6230
+ if (!requiresLogin) return { ok: true };
6231
+ const pluginName = site.name || "plugin";
6232
+ if (typeof site.isLoggedIn === "function") {
6233
+ try {
6234
+ const loggedIn = await site.isLoggedIn(ctx);
6235
+ if (loggedIn) return { ok: true };
6236
+ return buildLoginRequired({
6237
+ plugin: pluginName,
6238
+ command: commandName,
6239
+ reason: "plugin isLoggedIn returned false",
6240
+ sessionName,
6241
+ loginConfig
6242
+ });
6243
+ } catch {
6244
+ }
6245
+ }
6246
+ if (page && loginConfig) {
6247
+ const generic = await detectLoginFromPage(page, loginConfig);
6248
+ if (generic === "logged-in") return { ok: true };
6249
+ if (generic === "logged-out") {
6250
+ return buildLoginRequired({
6251
+ plugin: pluginName,
6252
+ command: commandName,
6253
+ reason: "generic loginConfig detected logged-out page",
6254
+ sessionName,
6255
+ loginConfig
6256
+ });
6257
+ }
6258
+ }
6259
+ return { ok: true };
6260
+ }
6261
+ function buildLoginRequired(options) {
6262
+ const viewerUrl = buildViewerUrl(options.sessionName);
6263
+ const loginUrl = options.loginConfig?.loginUrl;
6264
+ const message = options.loginConfig?.loginPrompt || `Plugin "${options.plugin}" requires login before running "${options.command}".`;
6265
+ const tips = [
6266
+ message,
6267
+ ...viewerUrl ? [`Open viewer to complete login: ${viewerUrl}`] : [],
6268
+ ...loginUrl ? [`Login page: ${loginUrl}`] : [],
6269
+ `After login, retry: xbrowser ${options.plugin} ${options.command} --session ${options.sessionName}`
6270
+ ];
6271
+ return {
6272
+ ok: false,
6273
+ data: {
6274
+ code: "LOGIN_REQUIRED",
6275
+ plugin: options.plugin,
6276
+ command: options.command,
6277
+ reason: options.reason,
6278
+ ...viewerUrl ? { viewerUrl } : {},
6279
+ ...loginUrl ? { loginUrl } : {}
6280
+ },
6281
+ message,
6282
+ tips
6283
+ };
6284
+ }
6285
+ async function detectLoginFromPage(page, config) {
6286
+ const url = page.url();
6287
+ if (config.loginUrls?.some((part) => url.includes(part))) return "logged-out";
6288
+ const result = await page.evaluate((cfg) => {
6289
+ const visible = (selector) => {
6290
+ try {
6291
+ const el = document.querySelector(selector);
6292
+ if (!el) return false;
6293
+ const rect = el.getBoundingClientRect();
6294
+ const style = window.getComputedStyle(el);
6295
+ return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
6296
+ } catch {
6297
+ return false;
6298
+ }
6299
+ };
6300
+ if (cfg.loggedInSelectors?.some(visible)) return "logged-in";
6301
+ if (cfg.loginSelectors?.some(visible)) return "logged-out";
6302
+ const bodyText = document.body?.innerText || "";
6303
+ const keywords = cfg.loginKeywords || [];
6304
+ if (keywords.length > 0 && keywords.every((keyword) => bodyText.includes(keyword))) {
6305
+ return "logged-out";
6306
+ }
6307
+ return "unknown";
6308
+ }, {
6309
+ loginSelectors: config.loginSelectors || [],
6310
+ loginKeywords: config.loginKeywords || [],
6311
+ loggedInSelectors: config.loggedInSelectors || []
6312
+ });
6313
+ return result;
6314
+ }
6315
+
6623
6316
  // src/tips/dom-watcher.ts
6624
6317
  var DOM_WATCHER_SCRIPT = `
6625
6318
  (function() {
@@ -6999,7 +6692,7 @@ var TipsManager = class {
6999
6692
  }
7000
6693
  }
7001
6694
  debounce() {
7002
- return new Promise((resolve16) => setTimeout(resolve16, DEBOUNCE_MS));
6695
+ return new Promise((resolve10) => setTimeout(resolve10, DEBOUNCE_MS));
7003
6696
  }
7004
6697
  formatTips(tips) {
7005
6698
  return tips.map((tip) => {
@@ -7027,33 +6720,105 @@ function getTipsManager() {
7027
6720
  }
7028
6721
 
7029
6722
  // src/hooks/loader.ts
7030
- var builtinHooks = {
7031
- screenshot: () => import("./screenshot-CWAWMXVA.js").then((m) => m.screenshotHook)
6723
+ var FAIL_KEYWORDS = [
6724
+ "\u767B\u5F55",
6725
+ "login",
6726
+ "Login",
6727
+ "\u672A\u767B\u5F55",
6728
+ "not logged in",
6729
+ "cdp",
6730
+ "CDP",
6731
+ "\u9A8C\u8BC1\u7801",
6732
+ "\u9A8C\u8BC1",
6733
+ "captcha",
6734
+ "\u9700\u8981\u767B\u5F55",
6735
+ "requires login",
6736
+ "blocked",
6737
+ "403",
6738
+ "404"
6739
+ ];
6740
+ var HOOK_REGISTRY = {
6741
+ viewer: {
6742
+ name: "viewer",
6743
+ onAfterCommand: async (ctx) => {
6744
+ const result = ctx.result;
6745
+ if (!result || result.success !== false) return void 0;
6746
+ const msg = [
6747
+ result.message,
6748
+ ...result.tips || []
6749
+ ].filter(Boolean).join(" ").toLowerCase();
6750
+ if (!FAIL_KEYWORDS.some((k) => msg.includes(k))) return void 0;
6751
+ const viewerUrl = buildViewerUrl();
6752
+ if (!viewerUrl) return void 0;
6753
+ const tips = result.tips || [];
6754
+ if (!tips.some((t) => t.includes("viewer") || t.includes("Viewer"))) {
6755
+ tips.push(`Open viewer: ${viewerUrl}`);
6756
+ }
6757
+ result.tips = tips;
6758
+ result.viewerUrl = viewerUrl;
6759
+ return void 0;
6760
+ }
6761
+ },
6762
+ screenshot: {
6763
+ name: "screenshot",
6764
+ onAfterCommand: async (ctx) => {
6765
+ try {
6766
+ const buf = await ctx.page.screenshot({ type: "jpeg", quality: 40 }).catch(() => null);
6767
+ if (!buf) return;
6768
+ return { screenshot: { url: `data:image/jpeg;base64,${buf.toString("base64").slice(0, 50)}...` } };
6769
+ } catch {
6770
+ return;
6771
+ }
6772
+ }
6773
+ },
6774
+ recorder: {
6775
+ name: "recorder",
6776
+ onAfterCommand: async (ctx) => {
6777
+ const ctxAny = ctx;
6778
+ const logs = ctxAny.__commandLogs || [];
6779
+ logs.push({
6780
+ timestamp: Date.now(),
6781
+ command: ctx.command,
6782
+ params: JSON.parse(JSON.stringify(ctx.params)),
6783
+ duration: ctx.duration
6784
+ });
6785
+ ctxAny.__commandLogs = logs;
6786
+ return void 0;
6787
+ }
6788
+ }
7032
6789
  };
6790
+ var customHooks = {};
7033
6791
  async function loadHooks() {
7034
- const names = process.env.XBROWSER_HOOKS;
7035
- if (!names) return [];
6792
+ const env = process.env.XBROWSER_HOOKS;
6793
+ if (!env) return [];
6794
+ const names = env.split(",").map((n) => n.trim()).filter(Boolean);
6795
+ if (names.length === 0) return [];
7036
6796
  const hooks = [];
7037
- for (const name of names.split(",")) {
7038
- const trimmed = name.trim();
7039
- const factory = builtinHooks[trimmed];
7040
- if (factory) {
7041
- hooks.push(await factory());
6797
+ for (const name of names) {
6798
+ const hook = HOOK_REGISTRY[name];
6799
+ if (hook) {
6800
+ hooks.push(hook);
6801
+ continue;
6802
+ }
6803
+ const customFactory = customHooks[name];
6804
+ if (customFactory) {
6805
+ const customHook = await customFactory();
6806
+ if (customHook) hooks.push(customHook);
7042
6807
  }
7043
6808
  }
7044
6809
  return hooks;
7045
6810
  }
7046
6811
 
7047
6812
  // src/executor.ts
7048
- import { homedir as homedir3 } from "os";
7049
- import { join as join3 } from "path";
6813
+ import { homedir as homedir5 } from "os";
6814
+ import { join as join5 } from "path";
7050
6815
  var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
7051
6816
  var snapshotHintShown = /* @__PURE__ */ new WeakSet();
7052
- var STORAGE_DIR = join3(homedir3(), ".xbrowser", "storage");
6817
+ var CONFIG_DIR = join5(homedir5(), ".xbrowser");
7053
6818
  var storageCache = /* @__PURE__ */ new Map();
7054
6819
  function getPluginStorage(pluginName) {
7055
6820
  if (!storageCache.has(pluginName)) {
7056
- storageCache.set(pluginName, new PluginStorage(pluginName, STORAGE_DIR));
6821
+ storageCache.set(pluginName, new CompositeStorage(pluginName, CONFIG_DIR, "xbrowser"));
7057
6822
  }
7058
6823
  return storageCache.get(pluginName);
7059
6824
  }
@@ -7061,7 +6826,7 @@ var archiveInitialized = false;
7061
6826
  function ensureArchiveInit() {
7062
6827
  if (!archiveInitialized) {
7063
6828
  try {
7064
- configureArchiveStore({ archiveDir: join3(homedir3(), ".xbrowser", "archives") });
6829
+ configureArchiveStore({ archiveDir: join5(homedir5(), ".xbrowser", "archives") });
7065
6830
  } catch {
7066
6831
  }
7067
6832
  archiveInitialized = true;
@@ -7087,6 +6852,10 @@ async function guardCheck(commandName) {
7087
6852
  function errorResult(message) {
7088
6853
  return { ...fail7(message), duration: 0 };
7089
6854
  }
6855
+ function tipsToMessages(tips) {
6856
+ if (!tips || tips.length === 0) return [];
6857
+ return tips.map((t) => typeof t === "string" ? t : t.message);
6858
+ }
7090
6859
  var wsServer = null;
7091
6860
  function streamCommandEvent(sessionId, message) {
7092
6861
  if (!wsServer || !wsServer.getRunning()) return;
@@ -7114,7 +6883,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7114
6883
  }
7115
6884
  let targetPageOverride = null;
7116
6885
  if (_target && extraOpts?.cdpEndpoint) {
7117
- const { findTargetPage } = await import("./browser-DSVV4GHS.js");
6886
+ const { findTargetPage } = await import("./browser-R56O3CW6.js");
7118
6887
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7119
6888
  if (!targetPageOverride) {
7120
6889
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -7131,7 +6900,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7131
6900
  params = result.data;
7132
6901
  }
7133
6902
  if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
7134
- const { forwardExec } = await import("./daemon-client-YAVQ343A.js");
6903
+ const { forwardExec } = await import("./daemon-client-UZZEHHIV.js");
7135
6904
  const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
7136
6905
  if (result) return result;
7137
6906
  }
@@ -7157,18 +6926,10 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7157
6926
  });
7158
6927
  }
7159
6928
  }
7160
- } else if ((command.scope === "page" || command.scope === "project") && params.url) {
6929
+ } else if (command.scope !== "project") {
7161
6930
  session = await createSession(sessionName, params.url, {
7162
6931
  cdpEndpoint: extraOpts?.cdpEndpoint
7163
6932
  });
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
6933
  }
7173
6934
  const ctx = {
7174
6935
  page: session?.page,
@@ -7191,7 +6952,8 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7191
6952
  },
7192
6953
  config: {},
7193
6954
  site: {},
7194
- cliName: "xbrowser"
6955
+ cliName: "xbrowser",
6956
+ tips: new TipCollector()
7195
6957
  };
7196
6958
  const start = Date.now();
7197
6959
  if (session) {
@@ -7212,7 +6974,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7212
6974
  const cache = /* @__PURE__ */ new Map();
7213
6975
  const resolved = await resolveRefParams(session.page, params, command.selectorParams, cache, session.id);
7214
6976
  if (resolved.tips.length > 0) {
7215
- refTips = resolved.tips;
6977
+ refTips = normalizeTips6(resolved.tips);
7216
6978
  params = resolved.params;
7217
6979
  }
7218
6980
  }
@@ -7268,32 +7030,39 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7268
7030
  snapshotHintShown.add(session);
7269
7031
  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
7032
  }
7271
- const merged = [...raw.tips || [], ...smartTips || [], ...snapshotHint ? [snapshotHint] : [], ...refTips];
7033
+ const merged = [
7034
+ ...raw.tips || [],
7035
+ ...normalizeTips6(smartTips),
7036
+ ...snapshotHint ? normalizeTips6([snapshotHint]) : [],
7037
+ ...refTips
7038
+ ];
7272
7039
  const isSuccess = raw.success !== false;
7040
+ const mergedOrRaw = merged.length > 0 ? merged : raw.tips || [];
7273
7041
  recordArchive(session?.id, sessionName, {
7274
7042
  step: 0,
7275
7043
  command: commandName,
7276
7044
  params,
7277
- result: { success: isSuccess, data: raw.data, message: raw.message, tips: merged.length > 0 ? merged : raw.tips || [] },
7045
+ result: { success: isSuccess, data: raw.data, message: raw.message, tips: tipsToMessages(mergedOrRaw) },
7278
7046
  toolCalls: [],
7279
7047
  duration,
7280
7048
  timestamp: start
7281
7049
  });
7282
7050
  if (isSuccess) {
7283
- return { ...ok26(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
7051
+ return { ...ok25(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
7284
7052
  }
7285
- return { success: false, data: raw.data, message: raw.message, tips: merged.length > 0 ? merged : raw.tips || [], duration, ...hookOutputs ? { hookOutputs } : {} };
7053
+ return { success: false, data: raw.data, message: raw.message, tips: mergedOrRaw, duration, ...hookOutputs ? { hookOutputs } : {} };
7286
7054
  }
7055
+ const smartTipNormalized = normalizeTips6(smartTips);
7287
7056
  recordArchive(session?.id, sessionName, {
7288
7057
  step: 0,
7289
7058
  command: commandName,
7290
7059
  params,
7291
- result: { success: true, data: raw, tips: smartTips || [] },
7060
+ result: { success: true, data: raw, tips: tipsToMessages(smartTipNormalized) },
7292
7061
  toolCalls: [],
7293
7062
  duration,
7294
7063
  timestamp: start
7295
7064
  });
7296
- return { ...ok26(raw, smartTips), duration, ...hookOutputs ? { hookOutputs } : {} };
7065
+ return { ...ok25(raw, smartTipNormalized), duration, ...hookOutputs ? { hookOutputs } : {} };
7297
7066
  } catch (err) {
7298
7067
  const end = Date.now();
7299
7068
  const duration = end - start;
@@ -7343,8 +7112,8 @@ async function executeChain(input, options) {
7343
7112
  });
7344
7113
  }
7345
7114
  try {
7346
- for (const pipeline2 of pipelines) {
7347
- const { type, pipeline: commands } = pipeline2;
7115
+ for (const pipeline of pipelines) {
7116
+ const { type, pipeline: commands } = pipeline;
7348
7117
  for (const cmdStr of commands) {
7349
7118
  const parts = splitCommand(cmdStr);
7350
7119
  if (parts.length === 0) continue;
@@ -7409,11 +7178,52 @@ async function executeChain(input, options) {
7409
7178
  },
7410
7179
  config: {},
7411
7180
  site,
7412
- cliName: "xbrowser"
7181
+ cliName: "xbrowser",
7182
+ tips: new TipCollector()
7413
7183
  };
7414
7184
  const start2 = Date.now();
7415
7185
  try {
7416
- const hooks = await loadHooks();
7186
+ const loginGuard = await checkPluginLoginRequired({
7187
+ site,
7188
+ command: cmdEntry,
7189
+ commandName: subCommand,
7190
+ ctx: pluginCtx,
7191
+ page: session?.page,
7192
+ sessionName
7193
+ });
7194
+ if (!loginGuard.ok) {
7195
+ const duration3 = Date.now() - start2;
7196
+ const data2 = loginGuard.data ?? null;
7197
+ recordArchive(session.id, sessionName, {
7198
+ step: results.length,
7199
+ command: `${cmdName} ${subCommand}`,
7200
+ params: pluginParams,
7201
+ result: { success: false, data: data2, message: loginGuard.message, tips: loginGuard.tips || [] },
7202
+ toolCalls: [],
7203
+ duration: duration3,
7204
+ timestamp: start2
7205
+ });
7206
+ results.push({
7207
+ command: `${cmdName} ${subCommand}`,
7208
+ raw: cmdStr,
7209
+ success: false,
7210
+ data: data2,
7211
+ message: loginGuard.message,
7212
+ tips: normalizeTips6(loginGuard.tips),
7213
+ duration: duration3
7214
+ });
7215
+ if (type === "and") {
7216
+ return {
7217
+ success: false,
7218
+ steps: results,
7219
+ totalDuration: Date.now() - totalStart,
7220
+ stoppedAt: results.length,
7221
+ stoppedReason: `Command '${cmdName} ${subCommand}' failed (&& chain): ${loginGuard.message}`
7222
+ };
7223
+ }
7224
+ continue;
7225
+ }
7226
+ const hooks = await loadHooks();
7417
7227
  if (hooks.length > 0) {
7418
7228
  await Promise.all(hooks.map((h) => h.onBeforeCommand?.({ page: session.page, command: `${cmdName} ${subCommand}`, params: pluginParams })));
7419
7229
  }
@@ -7433,7 +7243,7 @@ async function executeChain(input, options) {
7433
7243
  step: results.length,
7434
7244
  command: `${cmdName} ${subCommand}`,
7435
7245
  params: pluginParams,
7436
- result: { success: true, data, tips: raw?.tips || [] },
7246
+ result: { success: true, data, tips: tipsToMessages(raw?.tips) },
7437
7247
  toolCalls: [],
7438
7248
  duration: duration2,
7439
7249
  timestamp: start2
@@ -7441,7 +7251,7 @@ async function executeChain(input, options) {
7441
7251
  results.push({
7442
7252
  command: `${cmdName} ${subCommand}`,
7443
7253
  raw: cmdStr,
7444
- ...ok26(data),
7254
+ ...ok25(data),
7445
7255
  duration: duration2,
7446
7256
  ...hookOutputs ? { hookOutputs } : {}
7447
7257
  });
@@ -7484,7 +7294,7 @@ async function executeChain(input, options) {
7484
7294
  }
7485
7295
  continue;
7486
7296
  }
7487
- const { params } = parseCommandArgs(cmdName, cmdArgs);
7297
+ const { params } = parseCommandArgs(cmdName, cmdArgs, unquote2);
7488
7298
  if (cmdName === "goto" && params.url) {
7489
7299
  const existing2 = await findOrRestoreSession(sessionName, options?.cdpEndpoint);
7490
7300
  if (!existing2) {
@@ -7545,15 +7355,6 @@ function isChainInput(input) {
7545
7355
  }
7546
7356
 
7547
7357
  // 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
7358
  async function closeSession(name) {
7558
7359
  await closeSessionByName(name);
7559
7360
  }
@@ -7567,53 +7368,22 @@ function handleSessionHelp() {
7567
7368
  "Usage: xbrowser session <command> [options]",
7568
7369
  "",
7569
7370
  "Commands:",
7570
- " open <url> [--name <name>] Open browser and create session",
7571
7371
  " close [--name <name>] Close session",
7572
7372
  " list, ls List active sessions",
7573
7373
  " kill [--name <name>] Kill session forcefully",
7374
+ " kill-all Kill all sessions and daemon",
7574
7375
  "",
7575
7376
  "Options:",
7576
7377
  ' --name <name> Session name (default: "default")',
7577
7378
  "",
7379
+ "Note: Sessions are auto-created via --session global option.",
7380
+ "",
7578
7381
  "Examples:",
7579
- " xbrowser session open https://example.com",
7580
- " xbrowser session open https://example.com --name mypage",
7382
+ " xbrowser goto https://example.com --session mypage",
7581
7383
  " xbrowser session close --name mypage",
7582
7384
  " xbrowser session list"
7583
7385
  ].join("\n");
7584
7386
  }
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
7387
  var sessionCloseBuiltin = {
7618
7388
  name: "session close",
7619
7389
  description: "Close browser session",
@@ -7695,22 +7465,17 @@ var sessionKillBuiltin = {
7695
7465
  };
7696
7466
 
7697
7467
  // src/config.ts
7698
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
7699
- import { join as join4 } from "path";
7700
- import { homedir as homedir4, tmpdir } from "os";
7701
- function getConfigFile() {
7702
- return join4(homedir4() || tmpdir(), ".xbrowser", "config.json");
7468
+ import { homedir as homedir6, tmpdir } from "os";
7469
+ import { join as join6 } from "path";
7470
+ import { loadConfig as coreLoadConfig, saveConfig as coreSaveConfig } from "@dyyz1993/xcli-core";
7471
+ function getConfigSource() {
7472
+ return { configDir: join6(homedir6() || tmpdir(), ".xbrowser") };
7703
7473
  }
7704
7474
  function loadConfig() {
7705
- const configFile = getConfigFile();
7706
- if (!existsSync6(configFile)) return {};
7707
- return readJsonFile(configFile, {});
7475
+ return coreLoadConfig(getConfigSource());
7708
7476
  }
7709
7477
  function saveConfig(config) {
7710
- const dir = join4(homedir4() || tmpdir(), ".xbrowser");
7711
- const configFile = getConfigFile();
7712
- if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
7713
- writeFileSync5(configFile, JSON.stringify(config, null, 2), "utf-8");
7478
+ coreSaveConfig(getConfigSource(), config);
7714
7479
  }
7715
7480
  function getConfigValue(key) {
7716
7481
  return loadConfig()[key];
@@ -7818,38 +7583,69 @@ var configBuiltin = {
7818
7583
 
7819
7584
  // src/plugin/installer.ts
7820
7585
  import {
7821
- existsSync as existsSync13,
7822
- readdirSync as readdirSync3,
7586
+ existsSync as existsSync10,
7587
+ readdirSync as readdirSync2,
7823
7588
  mkdirSync as mkdirSync8,
7824
- rmSync as rmSync7
7589
+ rmSync as rmSync6
7825
7590
  } from "fs";
7826
- import { resolve as resolve15, basename as basename2 } from "path";
7827
- import { homedir as homedir5 } from "os";
7591
+ import { resolve as resolve8, basename as basename2 } from "path";
7592
+ import { homedir as homedir7 } from "os";
7828
7593
 
7829
7594
  // src/plugin/install-sources/local.ts
7830
- import { existsSync as existsSync8, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
7831
- import { resolve as resolve10 } from "path";
7595
+ import { existsSync as existsSync5, cpSync, rmSync } from "fs";
7596
+ import { resolve as resolve3 } from "path";
7597
+ import { verifyPlugin, safeCleanup } from "@dyyz1993/xcli-core";
7598
+ async function installFromLocal(source, name, targetDir) {
7599
+ const srcPath = resolve3(source);
7600
+ if (!existsSync5(srcPath)) {
7601
+ throw new Error(`Local path does not exist: ${srcPath}`);
7602
+ }
7603
+ const tmpTarget = `${targetDir}-tmp-${Date.now()}`;
7604
+ let warnings = [];
7605
+ try {
7606
+ cpSync(srcPath, tmpTarget, { recursive: true });
7607
+ const verify = verifyPlugin(tmpTarget, { metadataField: "xbrowser" });
7608
+ warnings = verify.warnings ?? [];
7609
+ if (!verify.valid) {
7610
+ safeCleanup(tmpTarget);
7611
+ throw new Error(`Invalid plugin: ${verify.error}`);
7612
+ }
7613
+ if (existsSync5(targetDir)) {
7614
+ rmSync(targetDir, { recursive: true, force: true });
7615
+ }
7616
+ cpSync(tmpTarget, targetDir, { recursive: true, force: true });
7617
+ safeCleanup(tmpTarget);
7618
+ } catch (err) {
7619
+ safeCleanup(tmpTarget);
7620
+ throw err;
7621
+ }
7622
+ return {
7623
+ id: name,
7624
+ name,
7625
+ path: targetDir,
7626
+ source: "local",
7627
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
7628
+ warnings
7629
+ };
7630
+ }
7832
7631
 
7833
- // src/plugin/install-utils.ts
7632
+ // src/plugin/install-sources/npm.ts
7633
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync6, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
7634
+ import { resolve as resolve4, join as join7 } from "path";
7635
+ import { tmpdir as tmpdir2 } from "os";
7834
7636
  import {
7835
- existsSync as existsSync7,
7836
- readdirSync as readdirSync2,
7837
- cpSync,
7838
- rmSync,
7839
- mkdirSync as mkdirSync4,
7840
- readFileSync as readFileSync10,
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";
7637
+ downloadToFile,
7638
+ extractTarGz,
7639
+ flattenPackageRoot,
7640
+ verifyPlugin as verifyPlugin2,
7641
+ safeCleanup as safeCleanup2
7642
+ } from "@dyyz1993/xcli-core";
7847
7643
 
7848
7644
  // src/utils/proxy-fetch.ts
7849
- var patched = false;
7645
+ var patched2 = false;
7850
7646
  async function ensureProxyFetch() {
7851
- if (patched) return;
7852
- patched = true;
7647
+ if (patched2) return;
7648
+ patched2 = true;
7853
7649
  if (process.env.https_proxy && !process.env.HTTPS_PROXY) {
7854
7650
  process.env.HTTPS_PROXY = process.env.https_proxy;
7855
7651
  }
@@ -7889,113 +7685,7 @@ async function ensureProxyFetch() {
7889
7685
  }
7890
7686
  }
7891
7687
 
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
7688
  // 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
7689
  async function installFromNpm(packageName, name, targetDir) {
8000
7690
  await ensureProxyFetch();
8001
7691
  const encodedName = encodeURIComponent(packageName);
@@ -8013,34 +7703,34 @@ async function installFromNpm(packageName, name, targetDir) {
8013
7703
  throw new Error(`No tarball URL for ${packageName}@${latestVersion}`);
8014
7704
  }
8015
7705
  const tarballUrl = versionMeta.dist.tarball;
8016
- const tmpDir = join5(tmpdir2(), `xbrowser-npm-${Date.now()}`);
7706
+ const tmpDir = join7(tmpdir2(), `xbrowser-npm-${Date.now()}`);
8017
7707
  mkdirSync5(tmpDir, { recursive: true });
8018
7708
  let warnings = [];
8019
7709
  try {
8020
- const tarballPath = join5(tmpDir, `${name}.tgz`);
7710
+ const tarballPath = join7(tmpDir, `${name}.tgz`);
8021
7711
  await downloadToFile(tarballUrl, tarballPath);
8022
- const extractDir = join5(tmpDir, "extracted");
7712
+ const extractDir = join7(tmpDir, "extracted");
8023
7713
  extractTarGz(tarballPath, extractDir);
8024
7714
  flattenPackageRoot(extractDir);
8025
- const verify = await verifyPlugin(extractDir);
7715
+ const verify = verifyPlugin2(extractDir, { metadataField: "xbrowser" });
8026
7716
  warnings = verify.warnings ?? [];
8027
7717
  if (!verify.valid) {
8028
7718
  throw new Error(`Invalid npm plugin: ${verify.error}`);
8029
7719
  }
8030
- if (existsSync9(targetDir)) {
8031
- rmSync3(targetDir, { recursive: true, force: true });
7720
+ if (existsSync6(targetDir)) {
7721
+ rmSync2(targetDir, { recursive: true, force: true });
8032
7722
  }
8033
- cpSync3(extractDir, targetDir, { recursive: true, force: true });
8034
- const pkgPath = resolve11(targetDir, "package.json");
8035
- if (existsSync9(pkgPath)) {
8036
- const pkg2 = JSON.parse(readFileSync11(pkgPath, "utf-8"));
7723
+ cpSync2(extractDir, targetDir, { recursive: true, force: true });
7724
+ const pkgPath = resolve4(targetDir, "package.json");
7725
+ if (existsSync6(pkgPath)) {
7726
+ const pkg2 = JSON.parse(readFileSync4(pkgPath, "utf-8"));
8037
7727
  if (!pkg2._npmSource) {
8038
7728
  pkg2._npmSource = { name: packageName, version: latestVersion };
8039
7729
  writeFileSync6(pkgPath, JSON.stringify(pkg2, null, 2));
8040
7730
  }
8041
7731
  }
8042
7732
  } finally {
8043
- safeCleanup(tmpDir);
7733
+ safeCleanup2(tmpDir);
8044
7734
  }
8045
7735
  return {
8046
7736
  id: name,
@@ -8053,35 +7743,36 @@ async function installFromNpm(packageName, name, targetDir) {
8053
7743
  }
8054
7744
 
8055
7745
  // src/plugin/install-sources/git.ts
8056
- import { existsSync as existsSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync7, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
8057
- import { resolve as resolve12, join as join6 } from "path";
7746
+ import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync7, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
7747
+ import { resolve as resolve5, join as join8 } from "path";
8058
7748
  import { tmpdir as tmpdir3 } from "os";
8059
- import { execSync as execSync8 } from "child_process";
7749
+ import { execSync as execSync2 } from "child_process";
7750
+ import { verifyPlugin as verifyPlugin3, safeCleanup as safeCleanup3 } from "@dyyz1993/xcli-core";
8060
7751
  async function installFromGit(gitUrl, name, targetDir) {
8061
- const tmpDir = join6(tmpdir3(), `xbrowser-git-${Date.now()}`);
7752
+ const tmpDir = join8(tmpdir3(), `xbrowser-git-${Date.now()}`);
8062
7753
  let warnings = [];
8063
7754
  try {
8064
- execSync8(`git clone --depth 1 "${gitUrl}" "${tmpDir}"`, { stdio: "pipe" });
8065
- const verify = await verifyPlugin(tmpDir);
7755
+ execSync2(`git clone --depth 1 "${gitUrl}" "${tmpDir}"`, { stdio: "pipe" });
7756
+ const verify = verifyPlugin3(tmpDir, { metadataField: "xbrowser" });
8066
7757
  warnings = verify.warnings ?? [];
8067
7758
  if (!verify.valid) {
8068
7759
  throw new Error(`Invalid git plugin: ${verify.error}`);
8069
7760
  }
8070
- if (existsSync10(targetDir)) {
8071
- rmSync4(targetDir, { recursive: true, force: true });
7761
+ if (existsSync7(targetDir)) {
7762
+ rmSync3(targetDir, { recursive: true, force: true });
8072
7763
  }
8073
- cpSync4(tmpDir, targetDir, { recursive: true, force: true });
8074
- rmSync4(resolve12(targetDir, ".git"), { recursive: true, force: true });
8075
- const pkgPath = resolve12(targetDir, "package.json");
8076
- if (existsSync10(pkgPath)) {
8077
- const pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
7764
+ cpSync3(tmpDir, targetDir, { recursive: true, force: true });
7765
+ rmSync3(resolve5(targetDir, ".git"), { recursive: true, force: true });
7766
+ const pkgPath = resolve5(targetDir, "package.json");
7767
+ if (existsSync7(pkgPath)) {
7768
+ const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
8078
7769
  if (!pkg2._gitSource) {
8079
7770
  pkg2._gitSource = { url: gitUrl };
8080
7771
  writeFileSync7(pkgPath, JSON.stringify(pkg2, null, 2));
8081
7772
  }
8082
7773
  }
8083
7774
  } finally {
8084
- safeCleanup(tmpDir);
7775
+ safeCleanup3(tmpDir);
8085
7776
  }
8086
7777
  return {
8087
7778
  id: name,
@@ -8094,39 +7785,46 @@ async function installFromGit(gitUrl, name, targetDir) {
8094
7785
  }
8095
7786
 
8096
7787
  // src/plugin/install-sources/url.ts
8097
- import { existsSync as existsSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, rmSync as rmSync5, cpSync as cpSync5 } from "fs";
8098
- import { resolve as resolve13, join as join7, basename } from "path";
7788
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync8, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
7789
+ import { resolve as resolve6, join as join9, basename } from "path";
8099
7790
  import { tmpdir as tmpdir4 } from "os";
7791
+ import {
7792
+ downloadToFile as downloadToFile2,
7793
+ extractTarGz as extractTarGz2,
7794
+ flattenPackageRoot as flattenPackageRoot2,
7795
+ verifyPlugin as verifyPlugin4,
7796
+ safeCleanup as safeCleanup4
7797
+ } from "@dyyz1993/xcli-core";
8100
7798
  async function installFromUrl(url, name, targetDir) {
8101
- const tmpDir = join7(tmpdir4(), `xbrowser-url-${Date.now()}`);
7799
+ const tmpDir = join9(tmpdir4(), `xbrowser-url-${Date.now()}`);
8102
7800
  mkdirSync6(tmpDir, { recursive: true });
8103
7801
  let warnings = [];
8104
7802
  try {
8105
7803
  const fileName = basename(new URL(url).pathname) || "plugin.tar.gz";
8106
- const tarballPath = join7(tmpDir, fileName);
8107
- await downloadToFile(url, tarballPath);
8108
- const extractDir = join7(tmpDir, "extracted");
8109
- extractTarGz(tarballPath, extractDir);
8110
- flattenPackageRoot(extractDir);
8111
- const verify = await verifyPlugin(extractDir);
7804
+ const tarballPath = join9(tmpDir, fileName);
7805
+ await downloadToFile2(url, tarballPath);
7806
+ const extractDir = join9(tmpDir, "extracted");
7807
+ extractTarGz2(tarballPath, extractDir);
7808
+ flattenPackageRoot2(extractDir);
7809
+ const verify = verifyPlugin4(extractDir, { metadataField: "xbrowser" });
8112
7810
  warnings = verify.warnings ?? [];
8113
7811
  if (!verify.valid) {
8114
7812
  throw new Error(`Invalid plugin from URL: ${verify.error}`);
8115
7813
  }
8116
- if (existsSync11(targetDir)) {
8117
- rmSync5(targetDir, { recursive: true, force: true });
7814
+ if (existsSync8(targetDir)) {
7815
+ rmSync4(targetDir, { recursive: true, force: true });
8118
7816
  }
8119
- cpSync5(extractDir, targetDir, { recursive: true, force: true });
8120
- const pkgPath = resolve13(targetDir, "package.json");
8121
- if (existsSync11(pkgPath)) {
8122
- const pkg2 = JSON.parse(readFileSync13(pkgPath, "utf-8"));
7817
+ cpSync4(extractDir, targetDir, { recursive: true, force: true });
7818
+ const pkgPath = resolve6(targetDir, "package.json");
7819
+ if (existsSync8(pkgPath)) {
7820
+ const pkg2 = JSON.parse(readFileSync6(pkgPath, "utf-8"));
8123
7821
  if (!pkg2._urlSource) {
8124
7822
  pkg2._urlSource = { url };
8125
7823
  writeFileSync8(pkgPath, JSON.stringify(pkg2, null, 2));
8126
7824
  }
8127
7825
  }
8128
7826
  } finally {
8129
- safeCleanup(tmpDir);
7827
+ safeCleanup4(tmpDir);
8130
7828
  }
8131
7829
  return {
8132
7830
  id: name,
@@ -8140,16 +7838,23 @@ async function installFromUrl(url, name, targetDir) {
8140
7838
 
8141
7839
  // src/plugin/install-sources/marketplace.ts
8142
7840
  import {
8143
- existsSync as existsSync12,
7841
+ existsSync as existsSync9,
8144
7842
  mkdirSync as mkdirSync7,
8145
7843
  writeFileSync as writeFileSync9,
8146
- readFileSync as readFileSync14,
8147
- rmSync as rmSync6,
8148
- cpSync as cpSync6
7844
+ readFileSync as readFileSync7,
7845
+ rmSync as rmSync5,
7846
+ cpSync as cpSync5
8149
7847
  } from "fs";
8150
- import { resolve as resolve14, join as join8, dirname as dirname2 } from "path";
7848
+ import { resolve as resolve7, join as join10, dirname as dirname2 } from "path";
8151
7849
  import { tmpdir as tmpdir5 } from "os";
8152
7850
  import { gunzipSync } from "zlib";
7851
+ import {
7852
+ downloadToFile as downloadToFile3,
7853
+ extractTarGz as extractTarGz3,
7854
+ flattenPackageRoot as flattenPackageRoot3,
7855
+ verifyPlugin as verifyPlugin5,
7856
+ safeCleanup as safeCleanup5
7857
+ } from "@dyyz1993/xcli-core";
8153
7858
  async function installFromMarketplace(pluginsDir, slug, options) {
8154
7859
  await ensureProxyFetch();
8155
7860
  const baseUrl = getMarketplaceUrl();
@@ -8164,24 +7869,24 @@ async function installFromMarketplace(pluginsDir, slug, options) {
8164
7869
  }
8165
7870
  const plugin = detailData.data;
8166
7871
  const name = options?.name || String(plugin.slug || slug);
8167
- const targetDir = resolve14(pluginsDir, name);
8168
- if (existsSync12(targetDir) && !options?.force) {
7872
+ const targetDir = resolve7(pluginsDir, name);
7873
+ if (existsSync9(targetDir) && !options?.force) {
8169
7874
  throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
8170
7875
  }
8171
7876
  mkdirSync7(targetDir, { recursive: true });
8172
- const tmpDir = join8(tmpdir5(), `xbrowser-marketplace-${Date.now()}`);
7877
+ const tmpDir = join10(tmpdir5(), `xbrowser-marketplace-${Date.now()}`);
8173
7878
  mkdirSync7(tmpDir, { recursive: true });
8174
7879
  const realSlug = String(plugin.slug || slug);
8175
7880
  try {
8176
7881
  await downloadAndExtractMarketplaceTarball(baseUrl, realSlug, tmpDir, targetDir);
8177
7882
  } finally {
8178
- safeCleanup(tmpDir);
7883
+ safeCleanup5(tmpDir);
8179
7884
  }
8180
7885
  writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir);
8181
7886
  ensureIndexFile(plugin, name, targetDir);
8182
- const verify = await verifyPlugin(targetDir);
7887
+ const verify = verifyPlugin5(targetDir, { metadataField: "xbrowser" });
8183
7888
  if (!verify.valid) {
8184
- safeCleanup(targetDir);
7889
+ safeCleanup5(targetDir);
8185
7890
  throw new Error(`Invalid marketplace plugin: ${verify.error}`);
8186
7891
  }
8187
7892
  const trackUrl = `${baseUrl}/api/plugins/${realSlug}/install`;
@@ -8200,12 +7905,12 @@ function isManifestArray(data) {
8200
7905
  return Array.isArray(data) && data.length > 0 && typeof data[0].path === "string" && typeof data[0].content === "string";
8201
7906
  }
8202
7907
  function extractManifestToDir(manifest, targetDir) {
8203
- if (existsSync12(targetDir)) {
8204
- rmSync6(targetDir, { recursive: true, force: true });
7908
+ if (existsSync9(targetDir)) {
7909
+ rmSync5(targetDir, { recursive: true, force: true });
8205
7910
  }
8206
7911
  mkdirSync7(targetDir, { recursive: true });
8207
7912
  for (const file of manifest) {
8208
- const filePath = resolve14(targetDir, file.path);
7913
+ const filePath = resolve7(targetDir, file.path);
8209
7914
  mkdirSync7(dirname2(filePath), { recursive: true });
8210
7915
  writeFileSync9(filePath, Buffer.from(file.content, "base64"));
8211
7916
  }
@@ -8232,21 +7937,21 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
8232
7937
  }
8233
7938
  if (tarballRes.status === 302 || tarballRes.headers.get("location")) {
8234
7939
  const redirectUrl = tarballRes.headers.get("location");
8235
- const tarballPath = join8(tmpDir, `${slug}.tar.gz`);
8236
- await downloadToFile(redirectUrl, tarballPath);
8237
- const buffer = readFileSync14(tarballPath);
7940
+ const tarballPath = join10(tmpDir, `${slug}.tar.gz`);
7941
+ await downloadToFile3(redirectUrl, tarballPath);
7942
+ const buffer = readFileSync7(tarballPath);
8238
7943
  const manifest = tryParseAsGzippedManifest(buffer);
8239
7944
  if (manifest) {
8240
7945
  extractManifestToDir(manifest, targetDir);
8241
7946
  return;
8242
7947
  }
8243
- const extractDir = join8(tmpDir, "extracted");
8244
- extractTarGz(tarballPath, extractDir);
8245
- flattenPackageRoot(extractDir);
8246
- if (existsSync12(targetDir)) {
8247
- rmSync6(targetDir, { recursive: true, force: true });
7948
+ const extractDir = join10(tmpDir, "extracted");
7949
+ extractTarGz3(tarballPath, extractDir);
7950
+ flattenPackageRoot3(extractDir);
7951
+ if (existsSync9(targetDir)) {
7952
+ rmSync5(targetDir, { recursive: true, force: true });
8248
7953
  }
8249
- cpSync6(extractDir, targetDir, { recursive: true, force: true });
7954
+ cpSync5(extractDir, targetDir, { recursive: true, force: true });
8250
7955
  } else {
8251
7956
  const buffer = Buffer.from(await tarballRes.arrayBuffer());
8252
7957
  const manifest = tryParseAsGzippedManifest(buffer);
@@ -8254,16 +7959,16 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
8254
7959
  extractManifestToDir(manifest, targetDir);
8255
7960
  return;
8256
7961
  }
8257
- const tarballPath = join8(tmpDir, `${slug}.tar.gz`);
7962
+ const tarballPath = join10(tmpDir, `${slug}.tar.gz`);
8258
7963
  writeFileSync9(tarballPath, buffer);
8259
7964
  try {
8260
- const extractDir = join8(tmpDir, "extracted");
8261
- extractTarGz(tarballPath, extractDir);
8262
- flattenPackageRoot(extractDir);
8263
- if (existsSync12(targetDir)) {
8264
- rmSync6(targetDir, { recursive: true, force: true });
7965
+ const extractDir = join10(tmpDir, "extracted");
7966
+ extractTarGz3(tarballPath, extractDir);
7967
+ flattenPackageRoot3(extractDir);
7968
+ if (existsSync9(targetDir)) {
7969
+ rmSync5(targetDir, { recursive: true, force: true });
8265
7970
  }
8266
- cpSync6(extractDir, targetDir, { recursive: true, force: true });
7971
+ cpSync5(extractDir, targetDir, { recursive: true, force: true });
8267
7972
  } catch {
8268
7973
  throw new Error(
8269
7974
  `Downloaded tarball for "${slug}" is neither a gzipped JSON manifest nor a valid tar.gz archive.`
@@ -8295,12 +8000,12 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
8295
8000
  url: baseUrl
8296
8001
  }
8297
8002
  };
8298
- const pkgPath = resolve14(targetDir, "package.json");
8299
- if (!existsSync12(pkgPath)) {
8003
+ const pkgPath = resolve7(targetDir, "package.json");
8004
+ if (!existsSync9(pkgPath)) {
8300
8005
  writeFileSync9(pkgPath, JSON.stringify(packageJson, null, 2));
8301
8006
  } else {
8302
8007
  try {
8303
- const existing = JSON.parse(readFileSync14(pkgPath, "utf-8"));
8008
+ const existing = JSON.parse(readFileSync7(pkgPath, "utf-8"));
8304
8009
  const merged = {
8305
8010
  ...existing,
8306
8011
  xbrowser: { ...existing.xbrowser, ...packageJson.xbrowser },
@@ -8313,7 +8018,7 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
8313
8018
  }
8314
8019
  }
8315
8020
  function ensureIndexFile(plugin, name, targetDir) {
8316
- if (!existsSync12(resolve14(targetDir, "index.ts")) && !existsSync12(resolve14(targetDir, "index.js"))) {
8021
+ if (!existsSync9(resolve7(targetDir, "index.ts")) && !existsSync9(resolve7(targetDir, "index.js"))) {
8317
8022
  const commands = plugin.commands || [];
8318
8023
  const commandHandlers = commands.length > 0 ? commands.map((cmd) => {
8319
8024
  return [
@@ -8329,7 +8034,7 @@ function ensureIndexFile(plugin, name, targetDir) {
8329
8034
  ` });`
8330
8035
  ].join("\n");
8331
8036
  writeFileSync9(
8332
- resolve14(targetDir, "index.ts"),
8037
+ resolve7(targetDir, "index.ts"),
8333
8038
  [
8334
8039
  `import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
8335
8040
  ``,
@@ -8350,7 +8055,7 @@ function ensureIndexFile(plugin, name, targetDir) {
8350
8055
  var PluginInstaller = class {
8351
8056
  pluginsDir;
8352
8057
  constructor(pluginsDir) {
8353
- this.pluginsDir = pluginsDir || resolve15(homedir5(), ".xbrowser/plugins");
8058
+ this.pluginsDir = pluginsDir || resolve8(homedir7(), ".xbrowser/plugins");
8354
8059
  }
8355
8060
  getPluginsDir() {
8356
8061
  return this.pluginsDir;
@@ -8366,8 +8071,8 @@ var PluginInstaller = class {
8366
8071
  async install(source, options) {
8367
8072
  const type = this.detectSourceType(source);
8368
8073
  const name = options?.name || this.deriveName(source, type);
8369
- const targetDir = resolve15(this.pluginsDir, name);
8370
- if (existsSync13(targetDir) && !options?.force) {
8074
+ const targetDir = resolve8(this.pluginsDir, name);
8075
+ if (existsSync10(targetDir) && !options?.force) {
8371
8076
  throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
8372
8077
  }
8373
8078
  mkdirSync8(targetDir, { recursive: true });
@@ -8427,11 +8132,11 @@ var PluginInstaller = class {
8427
8132
  * @throws If the plugin is not installed.
8428
8133
  */
8429
8134
  async uninstall(name) {
8430
- const targetDir = resolve15(this.pluginsDir, name);
8431
- if (!existsSync13(targetDir)) {
8135
+ const targetDir = resolve8(this.pluginsDir, name);
8136
+ if (!existsSync10(targetDir)) {
8432
8137
  throw new Error(`Plugin "${name}" not found`);
8433
8138
  }
8434
- rmSync7(targetDir, { recursive: true, force: true });
8139
+ rmSync6(targetDir, { recursive: true, force: true });
8435
8140
  }
8436
8141
  /**
8437
8142
  * List all installed plugins with metadata.
@@ -8439,18 +8144,18 @@ var PluginInstaller = class {
8439
8144
  * @returns Array of installed plugin information.
8440
8145
  */
8441
8146
  async list(_options) {
8442
- if (!existsSync13(this.pluginsDir)) return [];
8443
- const entries = readdirSync3(this.pluginsDir, { withFileTypes: true });
8147
+ if (!existsSync10(this.pluginsDir)) return [];
8148
+ const entries = readdirSync2(this.pluginsDir, { withFileTypes: true });
8444
8149
  const plugins = [];
8445
8150
  for (const entry of entries) {
8446
8151
  if (!entry.isDirectory()) continue;
8447
- const pluginPath = resolve15(this.pluginsDir, entry.name);
8448
- const indexPath = resolve15(pluginPath, "index.ts");
8449
- const indexJsPath = resolve15(pluginPath, "index.js");
8450
- if (!existsSync13(indexPath) && !existsSync13(indexJsPath)) continue;
8152
+ const pluginPath = resolve8(this.pluginsDir, entry.name);
8153
+ const indexPath = resolve8(pluginPath, "index.ts");
8154
+ const indexJsPath = resolve8(pluginPath, "index.js");
8155
+ if (!existsSync10(indexPath) && !existsSync10(indexJsPath)) continue;
8451
8156
  const metadata = PluginMetadataParser.parseFromPackageJson(pluginPath);
8452
8157
  let source = "local";
8453
- const pkg2 = readJsonFile(resolve15(pluginPath, "package.json"), {});
8158
+ const pkg2 = readJsonFile(resolve8(pluginPath, "package.json"), {});
8454
8159
  if (pkg2._marketplace) source = "marketplace";
8455
8160
  else if (pkg2._npmSource) source = "npm";
8456
8161
  else if (pkg2._gitSource) source = "git";
@@ -8474,10 +8179,10 @@ var PluginInstaller = class {
8474
8179
  }
8475
8180
  if (source.startsWith("file://")) {
8476
8181
  const filePath = decodeURIComponent(new URL(source).pathname);
8477
- if (existsSync13(filePath)) return "url";
8182
+ if (existsSync10(filePath)) return "url";
8478
8183
  }
8479
8184
  if (source.endsWith(".git") || source.includes("github.com/")) return "git";
8480
- if (existsSync13(resolve15(source))) return "local";
8185
+ if (existsSync10(resolve8(source))) return "local";
8481
8186
  return "npm";
8482
8187
  }
8483
8188
  deriveName(source, type) {
@@ -9311,7 +9016,7 @@ var previewBuiltin = {
9311
9016
  if (options.json) {
9312
9017
  outputResult({ running: false }, "json");
9313
9018
  } else {
9314
- console.log("Daemon is not running. Start with: xbrowser daemon start");
9019
+ console.log("Daemon is not running. It will start automatically when needed.");
9315
9020
  console.log("");
9316
9021
  console.log("Preview is automatically available when the daemon is running.");
9317
9022
  }
@@ -9335,9 +9040,199 @@ var previewBuiltin = {
9335
9040
  }
9336
9041
  };
9337
9042
 
9043
+ // src/builtins/knowledge.ts
9044
+ init_site_knowledge();
9045
+ import { existsSync as existsSync12 } from "fs";
9046
+ var knowledgeBuiltin = {
9047
+ name: "knowledge",
9048
+ description: "View LLM-readable site knowledge base (selectors, forms, APIs)",
9049
+ aliases: ["know"],
9050
+ help: {
9051
+ usage: "xbrowser knowledge <list|show|search|issue|path> [domain] [options]",
9052
+ description: "Manage auto-generated site knowledge from recordings. Knowledge is stored at ~/.xbrowser/knowledge/{domain}.md and is designed for LLM consumption.",
9053
+ options: [
9054
+ { name: "list", description: "List all domains with knowledge bases" },
9055
+ { name: "show <domain>", description: "Show full knowledge for a domain (markdown)" },
9056
+ { name: "search <domain> <query>", description: "Search selectors/APIs by keyword" },
9057
+ { name: "selectors <domain>", description: "List all selectors for a domain" },
9058
+ { name: "api <domain>", description: "List all API endpoints for a domain" },
9059
+ { name: "issue <domain> <text>", description: "Add a known issue to a domain" },
9060
+ { name: "path <domain>", description: "Show file path for a domain's knowledge" }
9061
+ ],
9062
+ examples: [
9063
+ { cmd: "xbrowser knowledge list", description: "List all known sites" },
9064
+ { cmd: "xbrowser knowledge show juejin.cn", description: "Show juejin.cn knowledge" },
9065
+ { cmd: "xbrowser knowledge search juejin.cn publish", description: 'Find selectors related to "publish"' },
9066
+ { cmd: "xbrowser knowledge selectors juejin.cn", description: "List all selectors" },
9067
+ { cmd: "xbrowser knowledge api juejin.cn", description: "List API endpoints" },
9068
+ { cmd: 'xbrowser knowledge issue juejin.cn "Title selector changed"', description: "Report an issue" }
9069
+ ]
9070
+ },
9071
+ execute: async (args, _options, _ctx) => {
9072
+ const [subcommand, ...rest] = args;
9073
+ if (!subcommand || subcommand === "list") {
9074
+ const domains = listSiteKnowledge();
9075
+ if (domains.length === 0) {
9076
+ console.log("No site knowledge bases found.");
9077
+ console.log("Knowledge is auto-generated when you run `xbrowser record stop`.");
9078
+ return;
9079
+ }
9080
+ console.log("Site Knowledge Bases:");
9081
+ console.log("");
9082
+ for (const domain of domains) {
9083
+ const kb = readSiteKnowledge(domain);
9084
+ if (kb) {
9085
+ const pageCount = Object.keys(kb.pages).length;
9086
+ const selCount = Object.values(kb.pages).reduce((sum, p) => sum + p.selectors.length, 0);
9087
+ const apiCount = Object.keys(kb.apiEndpoints).length;
9088
+ console.log(
9089
+ ` ${domain} \u2014 ${kb.recordingCount} recordings, ${pageCount} pages, ${selCount} selectors, ${apiCount} APIs`
9090
+ );
9091
+ }
9092
+ }
9093
+ return;
9094
+ }
9095
+ if (subcommand === "show") {
9096
+ const domain = rest[0];
9097
+ if (!domain) {
9098
+ console.error("Usage: xbrowser knowledge show <domain>");
9099
+ process.exit(1);
9100
+ }
9101
+ const md = readSiteKnowledgeMarkdown(domain);
9102
+ if (!md) {
9103
+ console.error(`No knowledge base found for ${domain}`);
9104
+ console.error("Run `xbrowser knowledge list` to see available domains.");
9105
+ process.exit(1);
9106
+ }
9107
+ console.log(md);
9108
+ return;
9109
+ }
9110
+ if (subcommand === "selectors") {
9111
+ const domain = rest[0];
9112
+ if (!domain) {
9113
+ console.error("Usage: xbrowser knowledge selectors <domain>");
9114
+ process.exit(1);
9115
+ }
9116
+ const kb = readSiteKnowledge(domain);
9117
+ if (!kb) {
9118
+ console.error(`No knowledge base found for ${domain}`);
9119
+ process.exit(1);
9120
+ }
9121
+ console.log(`Selectors for ${domain} (${kb.recordingCount} recordings):`);
9122
+ console.log("");
9123
+ for (const [pagePath, page] of Object.entries(kb.pages)) {
9124
+ if (page.selectors.length === 0) continue;
9125
+ console.log(` ${pagePath}:`);
9126
+ for (const sel of page.selectors) {
9127
+ const status = sel.status === "deprecated" ? " \u26A0\uFE0F" : "";
9128
+ console.log(
9129
+ ` ${sel.selector.padEnd(30)} ${sel.tag.padEnd(8)} ${sel.actionType.padEnd(10)} ${sel.confidence.padEnd(6)} ${sel.timesSeen}x${status}`
9130
+ );
9131
+ if (sel.description) console.log(` \u2192 ${sel.description}`);
9132
+ }
9133
+ console.log("");
9134
+ }
9135
+ return;
9136
+ }
9137
+ if (subcommand === "api") {
9138
+ const domain = rest[0];
9139
+ if (!domain) {
9140
+ console.error("Usage: xbrowser knowledge api <domain>");
9141
+ process.exit(1);
9142
+ }
9143
+ const kb = readSiteKnowledge(domain);
9144
+ if (!kb) {
9145
+ console.error(`No knowledge base found for ${domain}`);
9146
+ process.exit(1);
9147
+ }
9148
+ const endpoints = Object.values(kb.apiEndpoints);
9149
+ if (endpoints.length === 0) {
9150
+ console.log(`No API endpoints recorded for ${domain}`);
9151
+ return;
9152
+ }
9153
+ console.log(`API Endpoints for ${domain}:`);
9154
+ console.log("");
9155
+ for (const ep of endpoints.sort((a, b) => b.timesSeen - a.timesSeen)) {
9156
+ const params = ep.params.length > 0 ? ep.params.join(", ") : "-";
9157
+ console.log(` ${ep.method} ${ep.path} (${ep.timesSeen}x)`);
9158
+ console.log(` Params: ${params}`);
9159
+ if (ep.responseFields.length > 0) {
9160
+ console.log(` Response: ${ep.responseFields.slice(0, 5).join(", ")}`);
9161
+ }
9162
+ console.log("");
9163
+ }
9164
+ return;
9165
+ }
9166
+ if (subcommand === "search") {
9167
+ const domain = rest[0];
9168
+ const query = rest.slice(1).join(" ").toLowerCase();
9169
+ if (!domain || !query) {
9170
+ console.error("Usage: xbrowser knowledge search <domain> <query>");
9171
+ process.exit(1);
9172
+ }
9173
+ const kb = readSiteKnowledge(domain);
9174
+ if (!kb) {
9175
+ console.error(`No knowledge base found for ${domain}`);
9176
+ process.exit(1);
9177
+ }
9178
+ console.log(`Search results for "${query}" in ${domain}:`);
9179
+ console.log("");
9180
+ let found = 0;
9181
+ for (const [pagePath, page] of Object.entries(kb.pages)) {
9182
+ const matches = page.selectors.filter(
9183
+ (s) => s.selector.toLowerCase().includes(query) || s.description.toLowerCase().includes(query) || (s.text || "").toLowerCase().includes(query)
9184
+ );
9185
+ for (const m of matches) {
9186
+ console.log(` [${pagePath}] ${m.selector} \u2192 ${m.description} (${m.actionType}, ${m.confidence}, ${m.timesSeen}x)`);
9187
+ found++;
9188
+ }
9189
+ }
9190
+ for (const ep of Object.values(kb.apiEndpoints)) {
9191
+ if (ep.path.toLowerCase().includes(query) || ep.params.some((p) => p.toLowerCase().includes(query))) {
9192
+ console.log(` [API] ${ep.method} ${ep.path} (${ep.timesSeen}x)`);
9193
+ found++;
9194
+ }
9195
+ }
9196
+ if (found === 0) {
9197
+ console.log(" No matches found.");
9198
+ }
9199
+ return;
9200
+ }
9201
+ if (subcommand === "issue") {
9202
+ const domain = rest[0];
9203
+ const text = rest.slice(1).join(" ");
9204
+ if (!domain || !text) {
9205
+ console.error("Usage: xbrowser knowledge issue <domain> <description>");
9206
+ process.exit(1);
9207
+ }
9208
+ if (!readSiteKnowledge(domain)) {
9209
+ console.error(`No knowledge base found for ${domain}`);
9210
+ process.exit(1);
9211
+ }
9212
+ addKnownIssue(domain, text);
9213
+ console.log(`Added issue to ${domain}: ${text}`);
9214
+ return;
9215
+ }
9216
+ if (subcommand === "path") {
9217
+ const domain = rest[0];
9218
+ if (!domain) {
9219
+ console.error("Usage: xbrowser knowledge path <domain>");
9220
+ process.exit(1);
9221
+ }
9222
+ const mdPath = getKnowledgePath(domain, "md");
9223
+ const jsonPath = getKnowledgePath(domain, "json");
9224
+ console.log(`Markdown: ${mdPath} (${existsSync12(mdPath) ? "exists" : "not found"})`);
9225
+ console.log(`JSON: ${jsonPath} (${existsSync12(jsonPath) ? "exists" : "not found"})`);
9226
+ return;
9227
+ }
9228
+ console.error(`Unknown subcommand: ${subcommand}`);
9229
+ console.error("Usage: xbrowser knowledge <list|show|selectors|api|search|issue|path>");
9230
+ process.exit(1);
9231
+ }
9232
+ };
9233
+
9338
9234
  // src/builtins/index.ts
9339
9235
  var allBuiltins = [
9340
- sessionOpenBuiltin,
9341
9236
  sessionCloseBuiltin,
9342
9237
  sessionListBuiltin,
9343
9238
  sessionKillBuiltin,
@@ -9348,7 +9243,8 @@ var allBuiltins = [
9348
9243
  pluginListBuiltin,
9349
9244
  pluginReloadBuiltin,
9350
9245
  createBuiltin,
9351
- previewBuiltin
9246
+ previewBuiltin,
9247
+ knowledgeBuiltin
9352
9248
  ];
9353
9249
 
9354
9250
  // src/utils/selector.ts
@@ -9571,7 +9467,8 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
9571
9467
  } else {
9572
9468
  switch (command) {
9573
9469
  case "goto":
9574
- if (!args[0]) outputError(`Usage: xbrowser goto <url>`);
9470
+ case "open":
9471
+ if (!args[0]) outputError(`Usage: xbrowser ${command} <url>`);
9575
9472
  cmdName = "goto";
9576
9473
  params = {
9577
9474
  url: /^https?:\/\//i.test(args[0]) || /^wss?:\/\//i.test(args[0]) ? args[0] : "https://" + args[0],
@@ -9583,7 +9480,9 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
9583
9480
  params = {
9584
9481
  fullPage: !!(options["full-page"] || options.fullPage),
9585
9482
  type: options.type,
9586
- selector: options.selector || options.s
9483
+ selector: options.selector || options.s,
9484
+ base64: !!options.base64,
9485
+ output: options.output || options.o
9587
9486
  };
9588
9487
  break;
9589
9488
  case "eval":
@@ -9660,7 +9559,7 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
9660
9559
  let parsedActions;
9661
9560
  if (options.action) {
9662
9561
  const actionList = Array.isArray(options.action) ? options.action : [options.action];
9663
- const { parseActionDsl } = await import("./parse-action-dsl-T3DYC33D.js");
9562
+ const { parseActionDsl } = await import("./parse-action-dsl-UM333TL2.js");
9664
9563
  parsedActions = actionList.map((a) => parseActionDsl(a));
9665
9564
  } else if (options["actions-file"]) {
9666
9565
  const fs3 = await import("fs");
@@ -9788,36 +9687,27 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
9788
9687
  }
9789
9688
 
9790
9689
  // src/cli/session-routes.ts
9791
- import { homedir as homedir6 } from "os";
9792
- import { join as join10 } from "path";
9793
- import { readdirSync as readdirSync4, rmSync as rmSync8 } from "fs";
9690
+ import { homedir as homedir8 } from "os";
9691
+ import { join as join12 } from "path";
9692
+ import { readdirSync as readdirSync3, rmSync as rmSync7 } from "fs";
9794
9693
  function cleanSessionFiles() {
9795
- const dir = join10(homedir6(), ".xbrowser", "sessions");
9694
+ const dir = join12(homedir8(), ".xbrowser", "sessions");
9796
9695
  let count = 0;
9797
9696
  try {
9798
- for (const entry of readdirSync4(dir, { withFileTypes: true })) {
9799
- const p = join10(dir, entry.name);
9800
- rmSync8(p, { recursive: true, force: true });
9697
+ for (const entry of readdirSync3(dir, { withFileTypes: true })) {
9698
+ const p = join12(dir, entry.name);
9699
+ rmSync7(p, { recursive: true, force: true });
9801
9700
  count++;
9802
9701
  }
9803
9702
  } catch {
9804
9703
  }
9805
9704
  return count;
9806
9705
  }
9807
- async function handleSession(args, options, mode, cdpEndpoint) {
9706
+ async function handleSession(args, options, mode, _cdpEndpoint) {
9808
9707
  const sub = args[0];
9809
9708
  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
9709
  case "close": {
9820
- const name = options.name || process.env.XBROWSER_SESSION || "default";
9710
+ const name = options.session || options.name || process.env.XBROWSER_SESSION || "default";
9821
9711
  try {
9822
9712
  await forwardSessionClose(name);
9823
9713
  } catch {
@@ -9838,7 +9728,7 @@ async function handleSession(args, options, mode, cdpEndpoint) {
9838
9728
  break;
9839
9729
  }
9840
9730
  case "kill": {
9841
- const name = options.name || process.env.XBROWSER_SESSION || "default";
9731
+ const name = options.session || options.name || process.env.XBROWSER_SESSION || "default";
9842
9732
  try {
9843
9733
  await forwardSessionClose(name);
9844
9734
  } catch {
@@ -10256,7 +10146,7 @@ function handleDaemon(args, options, mode) {
10256
10146
  break;
10257
10147
  }
10258
10148
  default:
10259
- console.log("Usage: xbrowser daemon <start|stop|status> [--port <port>]");
10149
+ console.log("Daemon starts automatically. No manual action needed.");
10260
10150
  }
10261
10151
  }
10262
10152
 
@@ -10267,7 +10157,8 @@ async function handleRecord(args, options, mode) {
10267
10157
  case "start": {
10268
10158
  const url = options.url;
10269
10159
  const sessionName = options.session || "default";
10270
- const result = await forwardRecordStart(sessionName, url);
10160
+ const cdpEndpoint = options.cdp;
10161
+ const result = await forwardRecordStart(sessionName, url, cdpEndpoint);
10271
10162
  if (!result.ok) {
10272
10163
  outputError(String(result.error || "Failed to start recording"));
10273
10164
  return;
@@ -10351,6 +10242,13 @@ async function handleRecord(args, options, mode) {
10351
10242
  outputResult(result, mode);
10352
10243
  break;
10353
10244
  }
10245
+ case "generate-plugin": {
10246
+ const sessionName = options.session || args[1] || "default";
10247
+ const pluginName = options.name || "";
10248
+ const outputDir = options.output || "";
10249
+ await handleGeneratePlugin(sessionName, pluginName, outputDir);
10250
+ break;
10251
+ }
10354
10252
  default:
10355
10253
  console.log("Usage:");
10356
10254
  console.log(" xbrowser record start [--url <url>] [--session <name>]");
@@ -10358,6 +10256,7 @@ async function handleRecord(args, options, mode) {
10358
10256
  console.log(" xbrowser record status [--session <name>]");
10359
10257
  console.log(" xbrowser record summary [--session <name>] [--json]");
10360
10258
  console.log(' xbrowser record checkpoint --type <type> --hint "description" [--selector <sel>] [--session <name>]');
10259
+ console.log(" xbrowser record generate-plugin [--session <name>] [--name <plugin>] [--output <dir>]");
10361
10260
  console.log("");
10362
10261
  console.log("Checkpoint types: dialog, captcha, login, iframe, slider, custom");
10363
10262
  }
@@ -10546,7 +10445,7 @@ async function handleConvert(args, _mode) {
10546
10445
  const fs3 = await import("fs");
10547
10446
  const path3 = await import("path");
10548
10447
  const { default: yaml } = await import("yaml");
10549
- const { generateJSScript, generatePythonScript, generateBashScript } = await import("./convert-EGFYNICZ.js");
10448
+ const { generateJSScript, generatePythonScript, generateBashScript } = await import("./convert-LB3GJTLR.js");
10550
10449
  const content = fs3.readFileSync(filePath, "utf-8");
10551
10450
  const recording = yaml.parse(content);
10552
10451
  const ext = path3.extname(outputPath).toLowerCase();
@@ -10571,7 +10470,7 @@ async function handleExtract(args, _mode) {
10571
10470
  console.error("Usage: xbrowser extract <recording.yaml>");
10572
10471
  process.exit(1);
10573
10472
  }
10574
- const { extractAndSave, printExtractSummary } = await import("./extract-L2IW3IUB.js");
10473
+ const { extractAndSave, printExtractSummary } = await import("./extract-BSYBM4MR.js");
10575
10474
  const { summary, outputPath } = extractAndSave(filePath);
10576
10475
  printExtractSummary(summary);
10577
10476
  console.log(`
@@ -10584,33 +10483,196 @@ async function handleFilter(args, _mode) {
10584
10483
  console.error("Usage: xbrowser filter <input.yaml> <output.yaml> [--exclude-types=type1,type2]");
10585
10484
  process.exit(1);
10586
10485
  }
10587
- const { filterRecording, parseExcludeTypes } = await import("./filter-HC4RA7JY.js");
10486
+ const { filterRecording, parseExcludeTypes } = await import("./filter-KCFO4RSV.js");
10588
10487
  const excludeTypes = parseExcludeTypes(args.slice(2));
10589
10488
  const result = filterRecording(filePath, outputPath, excludeTypes);
10590
10489
  console.log(`Filtered ${filePath} -> ${outputPath}`);
10591
10490
  console.log(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)`);
10592
10491
  }
10593
-
10594
- // src/stdin.ts
10595
- import { createInterface } from "readline";
10596
- import { readFileSync as readFileSync15 } from "fs";
10597
- async function readStdin2() {
10598
- if (process.stdin.isTTY) return [];
10599
- const lines = [];
10600
- const rl = createInterface({ input: process.stdin });
10601
- for await (const line of rl) {
10602
- const trimmed = line.trim();
10603
- if (trimmed && !trimmed.startsWith("#")) {
10604
- lines.push(trimmed);
10492
+ async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
10493
+ const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-RTDGURIJ.js");
10494
+ const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
10495
+ const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync11 } = await import("fs");
10496
+ const { join: join13 } = await import("path");
10497
+ const data = SessionRecorder2.readData(sessionName);
10498
+ if (!data) {
10499
+ outputError(`No recording found for session "${sessionName}". Run \`xbrowser record stop --session ${sessionName}\` first.`);
10500
+ return;
10501
+ }
10502
+ let domain = "unknown";
10503
+ try {
10504
+ domain = new URL(data.startUrl).hostname.replace(/^www\./, "");
10505
+ } catch {
10506
+ }
10507
+ const finalPluginName = pluginName || domain.split(".")[0] || "my-site";
10508
+ const finalOutputDir = outputDir || join13(process.cwd(), ".xcli", "plugins", finalPluginName);
10509
+ const knowledge = readSiteKnowledge2(domain);
10510
+ const knowledgeMd = knowledge ? toMarkdown(knowledge) : "";
10511
+ const pluginCode = generatePluginCode(finalPluginName, domain, data, knowledgeMd);
10512
+ mkdirSync10(join13(finalOutputDir), { recursive: true });
10513
+ writeFileSync11(join13(finalOutputDir, "index.ts"), pluginCode, "utf-8");
10514
+ if (knowledgeMd) {
10515
+ writeFileSync11(join13(finalOutputDir, "SITE_KNOWLEDGE.md"), knowledgeMd, "utf-8");
10516
+ }
10517
+ console.log("");
10518
+ console.log("=== Plugin Generated ===");
10519
+ console.log(` Plugin: ${finalPluginName}`);
10520
+ console.log(` Domain: ${domain}`);
10521
+ console.log(` Output: ${finalOutputDir}/index.ts`);
10522
+ if (knowledgeMd) {
10523
+ console.log(` Knowledge: ${finalOutputDir}/SITE_KNOWLEDGE.md`);
10524
+ }
10525
+ console.log(` Actions: ${data.actions.length}`);
10526
+ console.log(` APIs: ${data.network.filter((n) => n.contentType.includes("json") || n.url.includes("/api/")).length}`);
10527
+ console.log("");
10528
+ console.log("Next steps:");
10529
+ console.log(` 1. Review and edit: ${finalOutputDir}/index.ts`);
10530
+ console.log(` 2. Test: xbrowser ${finalPluginName} <command>`);
10531
+ console.log(` 3. Reference: ${finalOutputDir}/SITE_KNOWLEDGE.md (for LLM)`);
10532
+ }
10533
+ function generatePluginCode(pluginName, domain, data, _knowledgeMd) {
10534
+ const pagePaths = /* @__PURE__ */ new Set();
10535
+ for (const action of data.actions) {
10536
+ if (action.url) {
10537
+ try {
10538
+ pagePaths.add(new URL(action.url).pathname);
10539
+ } catch {
10540
+ }
10541
+ }
10542
+ }
10543
+ const clickSelectors = [];
10544
+ const inputSelectors = [];
10545
+ for (const action of data.actions) {
10546
+ const el = action.element;
10547
+ if (!el) continue;
10548
+ const sel = el.selector;
10549
+ if (!sel) continue;
10550
+ if (action.type === "click" && !clickSelectors.includes(sel)) {
10551
+ clickSelectors.push(sel);
10552
+ }
10553
+ if (action.type === "input" && !inputSelectors.some((s) => s.selector === sel)) {
10554
+ inputSelectors.push({ selector: sel, placeholder: el.placeholder });
10605
10555
  }
10606
10556
  }
10607
- return lines;
10557
+ const apis = data.network.filter(
10558
+ (n) => (n.contentType || "").includes("json") || n.url.includes("/api/")
10559
+ );
10560
+ const commands = [];
10561
+ commands.push(` site.command({
10562
+ name: 'open',
10563
+ description: 'Open ${domain}',
10564
+ scope: 'browser',
10565
+ handler: async (_p: Record<string, unknown>, ctx: CommandContext) => {
10566
+ const page = ensurePage(ctx);
10567
+ await page.goto('${data.startUrl}', { waitUntil: 'domcontentloaded' });
10568
+ return ok({ url: page.url() });
10569
+ },
10570
+ });`);
10571
+ if (inputSelectors.length > 0) {
10572
+ const params = inputSelectors.slice(0, 5).map(
10573
+ (s, i) => ` ${s.selector.replace(/[^a-zA-Z0-9]/g, "_").replace(/^_+/, "").toLowerCase() || `field${i}`}: z.string().describe('${s.placeholder || s.selector}'),`
10574
+ ).join("\n");
10575
+ const fills = inputSelectors.slice(0, 5).map(
10576
+ (s) => ` await page.fill('${s.selector}', p.${s.selector.replace(/[^a-zA-Z0-9]/g, "_").replace(/^_+/, "").toLowerCase() || "field0"}');`
10577
+ ).join("\n");
10578
+ commands.push(` site.command({
10579
+ name: 'fill',
10580
+ description: 'Fill form on ${domain}',
10581
+ scope: 'page',
10582
+ parameters: z.object({
10583
+ ${params}
10584
+ }),
10585
+ handler: async (p: Record<string, unknown>, ctx: CommandContext) => {
10586
+ const page = ensurePage(ctx);
10587
+ ${fills}
10588
+ return ok({ filled: true });
10589
+ },
10590
+ });`);
10591
+ }
10592
+ if (clickSelectors.length > 0) {
10593
+ commands.push(` site.command({
10594
+ name: 'click',
10595
+ description: 'Click element on ${domain}',
10596
+ scope: 'page',
10597
+ parameters: z.object({
10598
+ selector: z.string().describe('CSS selector of element to click'),
10599
+ }),
10600
+ handler: async (p: Record<string, unknown>, ctx: CommandContext) => {
10601
+ const page = ensurePage(ctx);
10602
+ await page.click(p.selector as string);
10603
+ return ok({ clicked: p.selector });
10604
+ },
10605
+ });`);
10606
+ }
10607
+ if (apis.length > 0) {
10608
+ commands.push(` site.command({
10609
+ name: 'scrape',
10610
+ description: 'Scrape data from ${domain}',
10611
+ scope: 'page',
10612
+ handler: async (_p: Record<string, unknown>, ctx: CommandContext) => {
10613
+ const page = ensurePage(ctx);
10614
+ const data = await page.evaluate(() => {
10615
+ return {
10616
+ title: document.title,
10617
+ url: location.href,
10618
+ content: document.body?.innerText?.substring(0, 5000) || '',
10619
+ };
10620
+ });
10621
+ return ok(data);
10622
+ },
10623
+ });`);
10624
+ }
10625
+ return `/**
10626
+ * ${pluginName} \u2014 Auto-generated plugin for ${domain}
10627
+ *
10628
+ * Generated from xbrowser recording session.
10629
+ * Review and customize before using in production.
10630
+ *
10631
+ * Site Knowledge: See SITE_KNOWLEDGE.md for LLM-readable selector/API reference.
10632
+ */
10633
+
10634
+ import { z } from 'zod';
10635
+ import { ok } from '@dyyz1993/xcli-core';
10636
+ import { createSite, type CommandContext } from '@dyyz1993/xcli-core';
10637
+
10638
+ interface XBPage {
10639
+ url(): string;
10640
+ goto(url: string, opts?: Record<string, unknown>): Promise<unknown>;
10641
+ click(selector: string, opts?: Record<string, unknown>): Promise<unknown>;
10642
+ fill(selector: string, value: string, opts?: Record<string, unknown>): Promise<unknown>;
10643
+ evaluate<T>(fn: string | (() => T)): Promise<T>;
10644
+ }
10645
+
10646
+ function ensurePage(ctx: CommandContext): XBPage {
10647
+ const page = (ctx as Record<string, unknown>).page;
10648
+ if (!page) throw new Error('No active page. Start a session first.');
10649
+ return page as unknown as XBPage;
10608
10650
  }
10609
- function readCommandFile(filePath) {
10610
- const content = readFileSync15(filePath, "utf-8");
10611
- return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
10651
+
10652
+ export default createSite({
10653
+ name: '${pluginName}',
10654
+ domain: '${domain}',
10655
+ description: 'Auto-generated plugin for ${domain} from recording',
10656
+
10657
+ login: {
10658
+ url: '${data.startUrl}',
10659
+ detect: async (ctx: CommandContext) => {
10660
+ const page = ensurePage(ctx);
10661
+ // TODO: Add login detection logic
10662
+ return false;
10663
+ },
10664
+ },
10665
+
10666
+ setup(site) {
10667
+ ${commands.join("\n\n")}
10668
+ },
10669
+ });
10670
+ `;
10612
10671
  }
10613
10672
 
10673
+ // src/stdin.ts
10674
+ import { readStdin as readStdin2, readCommandFile, splitFileLine } from "@dyyz1993/xcli-core";
10675
+
10614
10676
  // src/cli/run-routes.ts
10615
10677
  async function handleRun(filePath, options) {
10616
10678
  let commands;
@@ -10654,10 +10716,10 @@ async function handleRun(filePath, options) {
10654
10716
  async function handleViewer(_args, options, mode, _cdpEndpoint) {
10655
10717
  const name = options.name || process.env.XBROWSER_SESSION || "default";
10656
10718
  const selector = options.selector;
10657
- const status = getDaemonProcessStatus();
10719
+ let status = getDaemonProcessStatus();
10658
10720
  if (!status.running) {
10659
- outputError("Daemon is not running. Start with: xbrowser daemon start");
10660
- return;
10721
+ await startDaemonProcess();
10722
+ status = getDaemonProcessStatus();
10661
10723
  }
10662
10724
  const port = status.port || getDaemonConfig().basePort;
10663
10725
  let url = `http://localhost:${port}/preview/${name}`;
@@ -10977,6 +11039,235 @@ async function handleNetCommand(args, options, mode, sessionName) {
10977
11039
  }
10978
11040
  }
10979
11041
 
11042
+ // src/cli/test-routes.ts
11043
+ import { execSync as execSync3 } from "child_process";
11044
+ import { readFileSync as readFileSync8 } from "fs";
11045
+ import { resolve as resolve9 } from "path";
11046
+ function findPluginPath(plugin) {
11047
+ const candidates = [
11048
+ resolve9(process.cwd(), ".xcli/plugins", plugin, "index.ts"),
11049
+ resolve9(process.cwd(), "node_modules/@xbrowser/cli/.xcli/plugins", plugin, "index.ts")
11050
+ ];
11051
+ for (const p of candidates) {
11052
+ try {
11053
+ readFileSync8(p, "utf-8");
11054
+ return p;
11055
+ } catch {
11056
+ }
11057
+ }
11058
+ return resolve9(process.cwd(), ".xcli/plugins", plugin, "index.ts");
11059
+ }
11060
+ function extractSchema(plugin, command) {
11061
+ const pluginPath = findPluginPath(plugin);
11062
+ let src;
11063
+ try {
11064
+ src = readFileSync8(pluginPath, "utf-8");
11065
+ } catch {
11066
+ return null;
11067
+ }
11068
+ const cmdIdx = src.indexOf(`.command('${command}'`);
11069
+ if (cmdIdx < 0) return null;
11070
+ const after = src.slice(cmdIdx);
11071
+ const resultIdx = after.indexOf("result:");
11072
+ if (resultIdx < 0) return null;
11073
+ let block = after.slice(resultIdx + 7);
11074
+ let depth = 0;
11075
+ let schemaStr = "";
11076
+ for (const ch of block) {
11077
+ if (ch === "{" || ch === "(" || ch === "[") depth++;
11078
+ if (ch === "}" || ch === ")" || ch === "]") {
11079
+ depth--;
11080
+ if (depth < 0) break;
11081
+ }
11082
+ if (depth === 0 && /\w/.test(ch) && schemaStr.trim().endsWith(",")) {
11083
+ break;
11084
+ }
11085
+ schemaStr += ch;
11086
+ }
11087
+ const objStart = schemaStr.indexOf("z.object({");
11088
+ const objEnd = objStart >= 0 ? schemaStr.indexOf("})", objStart) : -1;
11089
+ const objStr = objStart >= 0 && objEnd > objStart ? schemaStr.slice(objStart + 10, objEnd) : schemaStr;
11090
+ const fields = [];
11091
+ const SKIP_NAMES = /* @__PURE__ */ new Set(["passthrough", "optional", "describe", "default"]);
11092
+ const fieldRegex = /(\w+)\s*:\s*z\.(\w+)/g;
11093
+ let match;
11094
+ while ((match = fieldRegex.exec(objStr)) !== null) {
11095
+ const name = match[1];
11096
+ const type = match[2];
11097
+ if (SKIP_NAMES.has(name) || type === "union" || type === "enum") continue;
11098
+ const afterField = objStr.slice(match.index + match[0].length);
11099
+ const isOptional = afterField.trimStart().startsWith(".optional()");
11100
+ fields.push({
11101
+ name,
11102
+ type: type === "string" ? "string" : type === "number" ? "number" : type === "boolean" ? "boolean" : type === "array" ? "array" : type,
11103
+ optional: isOptional || name === "index"
11104
+ });
11105
+ }
11106
+ return fields.length > 0 ? fields : null;
11107
+ }
11108
+ async function runTest(plugin, command, cmdArgs, options) {
11109
+ const cdp = options.cdp || options.cdpEndpoint || "http://localhost:9221";
11110
+ const argsStr = cmdArgs.filter((a) => !a.startsWith("--cdp")).join(" ");
11111
+ const schema = extractSchema(plugin, command);
11112
+ const fullCmd = `npx xbrowser ${plugin} ${command} ${argsStr} --cdp ${cdp} --json --timeout 60000`;
11113
+ let stdout = "";
11114
+ try {
11115
+ stdout = execSync3(fullCmd, {
11116
+ timeout: 65e3,
11117
+ encoding: "utf-8",
11118
+ stdio: ["pipe", "pipe", "pipe"],
11119
+ env: { ...process.env, FORCE_COLOR: "0" }
11120
+ });
11121
+ } catch (e) {
11122
+ const err = e;
11123
+ stdout = err.stdout?.toString() || "";
11124
+ const jsonLine = stdout.split("\n").find((l) => {
11125
+ try {
11126
+ JSON.parse(l);
11127
+ return true;
11128
+ } catch {
11129
+ return false;
11130
+ }
11131
+ });
11132
+ if (jsonLine) {
11133
+ try {
11134
+ const parsed2 = JSON.parse(jsonLine);
11135
+ const code = parsed2?.data?.code || "";
11136
+ if (code === "LOGIN_REQUIRED") {
11137
+ return { status: "LOGIN_REQUIRED", message: parsed2.message || "\u9700\u8981\u767B\u5F55", viewerUrl: "http://localhost:9224/preview/default" };
11138
+ }
11139
+ } catch {
11140
+ }
11141
+ }
11142
+ const stderr = err.stderr?.toString() || "";
11143
+ if (stdout.includes("captcha") || stderr.includes("captcha") || stdout.includes("CAPTCHA")) {
11144
+ return { status: "CAPTCHA", message: "\u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801", viewerUrl: "http://localhost:9224/preview/default" };
11145
+ }
11146
+ return { status: "EXEC_ERROR", message: (err.message || "").slice(0, 200) || "\u6267\u884C\u5931\u8D25" };
11147
+ }
11148
+ const allLines = stdout.split("\n");
11149
+ const jsonStart = allLines.findIndex((l) => l.trim().startsWith("{"));
11150
+ const jsonStr = jsonStart >= 0 ? allLines.slice(jsonStart).join("\n") : "";
11151
+ let parsed = {};
11152
+ try {
11153
+ parsed = JSON.parse(jsonStr);
11154
+ } catch {
11155
+ return { status: "EXEC_ERROR", message: "\u65E0\u6CD5\u89E3\u6790 CLI \u8F93\u51FA" };
11156
+ }
11157
+ const rawData = parsed.data;
11158
+ const rawTips = parsed.tips || [];
11159
+ if (parsed.success === false) {
11160
+ const code = rawData?.code || "";
11161
+ if (code === "LOGIN_REQUIRED") {
11162
+ return { status: "LOGIN_REQUIRED", message: parsed.message || "\u9700\u8981\u767B\u5F55", viewerUrl: "http://localhost:9224/preview/default" };
11163
+ }
11164
+ if (rawTips.join(" ").includes("captcha") || rawTips.join(" ").includes("CAPTCHA")) {
11165
+ return { status: "CAPTCHA", message: parsed.message || "\u9A8C\u8BC1\u7801", viewerUrl: "http://localhost:9224/preview/default" };
11166
+ }
11167
+ }
11168
+ const data = rawData;
11169
+ if (data === null || data === void 0) {
11170
+ const msg = parsed.message || "";
11171
+ const tips = rawTips.join(" ");
11172
+ const viewerUrl = parsed.viewerUrl || rawData?.viewerUrl || "";
11173
+ let status = "NO_DATA";
11174
+ if (msg.includes("block") || msg.includes("anti-bot") || msg.includes("captcha") || tips.includes("viewer")) {
11175
+ status = "BLOCKED";
11176
+ } else if (msg.includes("\u767B\u5F55") || msg.includes("login")) {
11177
+ status = "LOGIN_REQUIRED";
11178
+ }
11179
+ const ret = { status, message: msg || "\u6682\u65E0\u6570\u636E" };
11180
+ if (viewerUrl) ret.viewerUrl = viewerUrl;
11181
+ return ret;
11182
+ }
11183
+ if (!schema) {
11184
+ return { status: "OK", note: "\u65E0 result schema", data: JSON.stringify(data).slice(0, 200) };
11185
+ }
11186
+ const errors = [];
11187
+ const items = Array.isArray(data) ? data.slice(0, 3) : [data];
11188
+ for (const item of items) {
11189
+ if (typeof item !== "object" || item === null) {
11190
+ errors.push("\u6570\u636E\u9879\u4E0D\u662F\u5BF9\u8C61");
11191
+ continue;
11192
+ }
11193
+ for (const field of schema) {
11194
+ const val = item[field.name];
11195
+ if (val === void 0) {
11196
+ if (!field.optional) errors.push(`\u7F3A\u5C11: ${field.name}`);
11197
+ continue;
11198
+ }
11199
+ if (field.type === "array") {
11200
+ if (!Array.isArray(val)) errors.push(`${field.name}: \u671F\u671B array`);
11201
+ } else if (typeof val !== field.type) {
11202
+ errors.push(`${field.name}: \u671F\u671B ${field.type}, \u5B9E\u9645 ${typeof val}`);
11203
+ }
11204
+ }
11205
+ }
11206
+ if (errors.length > 0) {
11207
+ return { status: "SCHEMA_ERROR", errors, data: JSON.stringify(data).slice(0, 200) };
11208
+ }
11209
+ const count = Array.isArray(data) ? data.length : 1;
11210
+ return { status: "OK", count, data: JSON.stringify(data).slice(0, 200) };
11211
+ }
11212
+ async function handleTest(cmdArgs, options, mode, cdpEndpoint) {
11213
+ const plugin = cmdArgs[0];
11214
+ const command = cmdArgs[1];
11215
+ if (!plugin || !command) {
11216
+ console.error("\u7528\u6CD5: xbrowser test <plugin> <command> [\u53C2\u6570...]");
11217
+ console.error("\u793A\u4F8B: xbrowser test doubao list --cdp 9221");
11218
+ return;
11219
+ }
11220
+ const loader = await getPluginLoader();
11221
+ const internalLoader = loader.getCore().loader;
11222
+ const site = internalLoader.getSite(plugin);
11223
+ if (!site) {
11224
+ console.error(`\u63D2\u4EF6 "${plugin}" \u4E0D\u5B58\u5728`);
11225
+ return;
11226
+ }
11227
+ const cmdEntry = site.getCommand(command);
11228
+ if (!cmdEntry) {
11229
+ console.error(`\u6307\u4EE4 "${command}" \u4E0D\u5B58\u5728`);
11230
+ return;
11231
+ }
11232
+ const testArgs = cmdArgs.slice(2);
11233
+ const mergedOptions = { ...options, cdp: cdpEndpoint || options.cdp };
11234
+ const result = await runTest(plugin, command, testArgs, mergedOptions);
11235
+ if (mode === "json") {
11236
+ console.log(JSON.stringify(result, null, 2));
11237
+ return;
11238
+ }
11239
+ const r = result;
11240
+ const icons = {
11241
+ OK: "\u2705",
11242
+ LOGIN_REQUIRED: "\u{1F511}",
11243
+ CAPTCHA: "\u{1F6A8}",
11244
+ SCHEMA_ERROR: "\u274C",
11245
+ BLOCKED: "\u{1F6A7}",
11246
+ NO_DATA: "\u{1F4ED}",
11247
+ EXEC_ERROR: "\u{1F4A5}"
11248
+ };
11249
+ const status = String(r.status);
11250
+ const icon = icons[status] || "\u2753";
11251
+ console.log(`
11252
+ ${icon} ${plugin}.${command}`);
11253
+ console.log(` \u72B6\u6001: ${status}`);
11254
+ if (status === "OK") {
11255
+ if (r.count) console.log(` \u6570\u636E: ${r.count} \u9879`);
11256
+ if (r.data) console.log(` \u9884\u89C8: ${String(r.data).slice(0, 150)}`);
11257
+ } else if (status === "LOGIN_REQUIRED" || status === "CAPTCHA") {
11258
+ console.log(` \u4FE1\u606F: ${String(r.message)}`);
11259
+ console.log(` Viewer: ${String(r.viewerUrl)}`);
11260
+ } else if (status === "SCHEMA_ERROR") {
11261
+ const errs = r.errors;
11262
+ if (errs) console.log(` \u9519\u8BEF: ${errs.join("; ")}`);
11263
+ } else if (["NO_DATA", "BLOCKED"].includes(status)) {
11264
+ console.log(` \u4FE1\u606F: ${String(r.message)}`);
11265
+ if (r.viewerUrl) console.log(` Viewer: ${String(r.viewerUrl)}`);
11266
+ } else {
11267
+ console.log(` \u4FE1\u606F: ${String(r.message)}`);
11268
+ }
11269
+ }
11270
+
10980
11271
  // src/cli/help.ts
10981
11272
  function showMainHelp() {
10982
11273
  console.log(`
@@ -10990,11 +11281,11 @@ Usage:
10990
11281
  xbrowser -e cmd1 -e cmd2 Execute multiple -e commands
10991
11282
 
10992
11283
  Commands:
10993
- session open <url> [--name <n>] Open browser session
10994
- session close [--name <n>] Close session
11284
+ session close [--session <name>] Close session
10995
11285
  session list List sessions
10996
- session kill [--name <n>] Kill session
11286
+ session kill [--session <name>] Kill session
10997
11287
  goto <url> Navigate to URL
11288
+ open <url> Navigate to URL (alias for goto)
10998
11289
  click <selector> Click element (-s <sel>)
10999
11290
  fill <selector> <value> Fill input (-s <sel> -v <val>)
11000
11291
  type <selector> <text> Type text (-s <sel> -v <text>)
@@ -11004,7 +11295,7 @@ Commands:
11004
11295
  dblclick <selector> Double click (-s <sel>)
11005
11296
  check <selector> Check checkbox (-s <sel>)
11006
11297
  uncheck <selector> Uncheck checkbox (-s <sel>)
11007
- screenshot [--full-page] Take screenshot
11298
+ screenshot [--full-page] [--base64] Take screenshot (saves to ~/.xbrowser/screenshots/; use --base64 for inline data)
11008
11299
  eval <expression> Evaluate JS
11009
11300
  wait <selector> [--timeout <ms>] Wait for element (-s <sel>)
11010
11301
  scroll <direction> [--distance N] Scroll page
@@ -11026,9 +11317,6 @@ Commands:
11026
11317
  plugin list List plugins
11027
11318
  plugin reload <name> Reload plugin
11028
11319
  create <name> --template <type> Create plugin
11029
- daemon start [--port <port>] Start daemon
11030
- daemon stop Stop daemon
11031
- daemon status Check status
11032
11320
  serve [--port <port>] [--token <t>] Start HTTP server for remote access
11033
11321
  remote <url> [command] [--token <t>] Execute command on remote server
11034
11322
  record start --url <url> Start recording
@@ -11401,19 +11689,19 @@ function headersToObject(headers) {
11401
11689
  return result;
11402
11690
  }
11403
11691
  function readBody(req) {
11404
- return new Promise((resolve16, reject) => {
11692
+ return new Promise((resolve10, reject) => {
11405
11693
  const chunks = [];
11406
11694
  req.on("data", (chunk) => chunks.push(chunk));
11407
11695
  req.on("end", () => {
11408
11696
  const raw = Buffer.concat(chunks).toString("utf-8");
11409
11697
  if (!raw) {
11410
- resolve16(null);
11698
+ resolve10(null);
11411
11699
  return;
11412
11700
  }
11413
11701
  try {
11414
- resolve16(JSON.parse(raw));
11702
+ resolve10(JSON.parse(raw));
11415
11703
  } catch {
11416
- resolve16(null);
11704
+ resolve10(null);
11417
11705
  }
11418
11706
  });
11419
11707
  req.on("error", reject);
@@ -11458,7 +11746,7 @@ var HTTPServer = class {
11458
11746
  res.end(JSON.stringify({ error: "INTERNAL_ERROR", message, statusCode: 500 }));
11459
11747
  });
11460
11748
  });
11461
- return new Promise((resolve16, reject) => {
11749
+ return new Promise((resolve10, reject) => {
11462
11750
  const server = this.server;
11463
11751
  server.on("error", (err) => {
11464
11752
  this.server = null;
@@ -11470,7 +11758,7 @@ var HTTPServer = class {
11470
11758
  this.port = addr.port;
11471
11759
  }
11472
11760
  console.log(`HTTP server listening on http://${this.host}:${this.port}`);
11473
- resolve16({ port: this.port, host: this.host });
11761
+ resolve10({ port: this.port, host: this.host });
11474
11762
  });
11475
11763
  });
11476
11764
  }
@@ -11481,13 +11769,13 @@ var HTTPServer = class {
11481
11769
  */
11482
11770
  async stop() {
11483
11771
  if (!this.server) return;
11484
- return new Promise((resolve16, reject) => {
11772
+ return new Promise((resolve10, reject) => {
11485
11773
  this.server.close((err) => {
11486
11774
  if (err) {
11487
11775
  reject(err);
11488
11776
  } else {
11489
11777
  this.server = null;
11490
- resolve16();
11778
+ resolve10();
11491
11779
  }
11492
11780
  });
11493
11781
  });
@@ -11546,6 +11834,7 @@ function showCommandHelp(siteName, cmd, siteConfig, mode) {
11546
11834
  command: c.name,
11547
11835
  description: c.description,
11548
11836
  scope: c.scope,
11837
+ ...c.loginRequired ? { loginRequired: c.loginRequired } : {},
11549
11838
  parameters: paramsList
11550
11839
  }, mode);
11551
11840
  } else {
@@ -11558,9 +11847,25 @@ function showCommandHelp(siteName, cmd, siteConfig, mode) {
11558
11847
  examples: c.examples
11559
11848
  }, { color: false, emoji: false });
11560
11849
  console.log(text);
11850
+ if (c.loginRequired) {
11851
+ console.log(` Login: ${c.loginRequired}`);
11852
+ }
11561
11853
  console.log("");
11562
11854
  }
11563
11855
  }
11856
+ function outputLoginRequired(result, mode) {
11857
+ if (mode === "json" || mode === "yaml") {
11858
+ console.log(outputFormatter2.format(result, { mode, color: false, emoji: false }));
11859
+ return;
11860
+ }
11861
+ const message = result.message || "Login required";
11862
+ console.error(message);
11863
+ for (const tip of result.tips || []) {
11864
+ const text = typeof tip === "string" ? tip : tip.message;
11865
+ if (text !== message) console.error(` \u{1F4A1} ${text}`);
11866
+ }
11867
+ process.exit(1);
11868
+ }
11564
11869
  function extractZodFieldInfo(value) {
11565
11870
  const field = value;
11566
11871
  const fieldDef = field._def;
@@ -11624,7 +11929,7 @@ function extractCdpFromArgv(argv) {
11624
11929
  if (argv[i] === "--cdp" && argv[i + 1]) return argv[i + 1];
11625
11930
  if (typeof argv[i] === "string" && argv[i].startsWith("--cdp=")) return argv[i].slice(6);
11626
11931
  }
11627
- return void 0;
11932
+ return process.env.XBROWSER_CDP;
11628
11933
  }
11629
11934
  async function handleStdinMode(stdinCommands, argv) {
11630
11935
  const chain = stdinCommands.join(" && ");
@@ -11687,7 +11992,7 @@ async function routeCommand(argv, stdinCommands) {
11687
11992
  const cmdArgs = positional.slice(1);
11688
11993
  const mode = options.json ? "json" : options.yaml ? "yaml" : "text";
11689
11994
  const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
11690
- const cdpEndpoint = options.cdp;
11995
+ const cdpEndpoint = options.cdp || process.env.XBROWSER_CDP;
11691
11996
  if (options.version || options.v) {
11692
11997
  console.log(`xbrowser v${version}`);
11693
11998
  return;
@@ -11820,12 +12125,21 @@ async function routeCommand(argv, stdinCommands) {
11820
12125
  if (builtin) await builtin.execute(cmdArgs, options, { cwd: process.cwd() });
11821
12126
  break;
11822
12127
  }
12128
+ case "knowledge":
12129
+ case "know": {
12130
+ const builtin = allBuiltins.find((b) => b.name === "knowledge");
12131
+ if (builtin) await builtin.execute(cmdArgs, options, { cwd: process.cwd() });
12132
+ break;
12133
+ }
11823
12134
  case "viewer":
11824
12135
  await handleViewer(cmdArgs, options, mode, cdpEndpoint);
11825
12136
  break;
11826
12137
  case "help":
11827
12138
  showMainHelp();
11828
12139
  break;
12140
+ case "test":
12141
+ await handleTest(cmdArgs, options, mode, cdpEndpoint);
12142
+ break;
11829
12143
  case "net":
11830
12144
  await handleNetCommand(cmdArgs, options, mode, sessionName);
11831
12145
  break;
@@ -11935,9 +12249,14 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
11935
12249
  }
11936
12250
  const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
11937
12251
  if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
11938
- const { forwardExec } = await import("./daemon-client-YAVQ343A.js");
12252
+ const { forwardExec } = await import("./daemon-client-UZZEHHIV.js");
11939
12253
  const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
11940
12254
  const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
12255
+ const resultData = result && typeof result === "object" ? result.data : void 0;
12256
+ if (result && result.success === false && resultData?.code === "LOGIN_REQUIRED") {
12257
+ outputLoginRequired(result, mode);
12258
+ return;
12259
+ }
11941
12260
  if (result && result.success !== false) {
11942
12261
  if (isCommandResult2(result)) {
11943
12262
  if (mode === "json" || mode === "yaml") {
@@ -11981,10 +12300,33 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
11981
12300
  cliName: "xbrowser",
11982
12301
  waitForHuman: async (_opts) => {
11983
12302
  return { solved: false, timedOut: true };
11984
- }
12303
+ },
12304
+ tips: new TipCollector2()
11985
12305
  };
11986
12306
  try {
11987
12307
  const cmdStart = Date.now();
12308
+ const loginGuard = await checkPluginLoginRequired({
12309
+ site,
12310
+ command: cmdEntry,
12311
+ commandName: subCommand,
12312
+ ctx,
12313
+ page: needsBrowser ? session?.page : null,
12314
+ sessionName
12315
+ });
12316
+ if (!loginGuard.ok) {
12317
+ const result2 = {
12318
+ success: false,
12319
+ data: loginGuard.data ?? null,
12320
+ message: loginGuard.message,
12321
+ tips: normalizeTips7(loginGuard.tips)
12322
+ };
12323
+ if (mode === "json" || mode === "yaml") {
12324
+ outputLoginRequired(result2, mode);
12325
+ } else {
12326
+ outputLoginRequired(result2, mode);
12327
+ }
12328
+ return;
12329
+ }
11988
12330
  const cmdHooks = await loadHooks();
11989
12331
  if (cmdHooks.length > 0 && session?.page) {
11990
12332
  await Promise.all(cmdHooks.map((h) => h.onBeforeCommand?.({ page: session.page, command: `${command} ${subCommand}`, params })));
@@ -12003,23 +12345,39 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
12003
12345
  saveSessionDiskMeta(sessionName, { conversationUrl: convUrl, cdpEndpoint });
12004
12346
  }
12005
12347
  }
12348
+ let injectedViewerUrl;
12349
+ 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"];
12350
+ const tipTexts = (result.tips || []).map((t) => typeof t === "string" ? t : t.message);
12351
+ const isLoginFail = isCommandResult2(result) && result.success === false && [result.message, ...tipTexts].filter(Boolean).join(" ").toLowerCase().match(new RegExp(LOGIN_FAIL_KEYWORDS.join("|"), "i"));
12352
+ if (isLoginFail) {
12353
+ injectedViewerUrl = buildViewerUrl(sessionName);
12354
+ if (injectedViewerUrl) {
12355
+ result.tips = [...result.tips || [], makeTip.info(`Open viewer to complete login: ${injectedViewerUrl}`)];
12356
+ }
12357
+ }
12006
12358
  const outputData = isCommandResult2(result) ? result.data : result && typeof result === "object" ? result.data ?? result : result;
12007
12359
  const tips = isCommandResult2(result) ? result.tips : result && typeof result === "object" ? result.tips : void 0;
12008
12360
  if (mode === "json" || mode === "yaml") {
12009
12361
  const finalOutput = {
12010
12362
  data: outputData
12011
12363
  };
12364
+ if (injectedViewerUrl) {
12365
+ finalOutput.viewerUrl = injectedViewerUrl;
12366
+ }
12367
+ if (tips?.length) {
12368
+ finalOutput.tips = tips;
12369
+ }
12012
12370
  if (hookOutputs.length > 0) {
12013
12371
  finalOutput.hooks = hookOutputs;
12014
12372
  }
12015
12373
  console.log(outputFormatter2.format(finalOutput, { mode, color: false, emoji: false }));
12016
12374
  if (tips?.length) {
12017
- for (const tip of tips) console.error(`\u{1F4A1} ${tip}`);
12375
+ for (const tip of tips) console.error(`\u{1F4A1} ${typeof tip === "string" ? tip : tip.message}`);
12018
12376
  }
12019
12377
  } else {
12020
12378
  console.log(outputFormatter2.format(outputData, { mode: "text", color: true, emoji: true }));
12021
12379
  if (tips?.length) {
12022
- for (const tip of tips) console.log(` \u{1F4A1} ${tip}`);
12380
+ for (const tip of tips) console.log(` \u{1F4A1} ${typeof tip === "string" ? tip : tip.message}`);
12023
12381
  }
12024
12382
  if (hookOutputs.length > 0) {
12025
12383
  for (const ho of hookOutputs) {
@@ -12197,7 +12555,7 @@ async function main() {
12197
12555
  const command = process.argv[2];
12198
12556
  const isLongRunning = command === "preview" || command === "serve";
12199
12557
  if (!isLongRunning) {
12200
- const { ensureProcessCanExit } = await import("./browser-DSVV4GHS.js");
12558
+ const { ensureProcessCanExit } = await import("./browser-R56O3CW6.js");
12201
12559
  await ensureProcessCanExit().catch(() => {
12202
12560
  });
12203
12561
  process.exit(exitCode);