@xbrowser/cli 1.24.0 → 1.24.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.
@@ -20,8 +20,8 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-2Q3GQRUP.js";
24
- import "./chunk-7OP2ISEY.js";
23
+ } from "./chunk-PKXJST62.js";
24
+ import "./chunk-UR3R5LWF.js";
25
25
  import "./chunk-GYB5BOSP.js";
26
26
  import "./chunk-MYH6FIBZ.js";
27
27
  import "./chunk-GDKLH7ZY.js";
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-IFKJO2UR.js";
23
+ } from "./chunk-X735U7G7.js";
24
24
  import "./chunk-GYB5BOSP.js";
25
25
  import "./chunk-MYH6FIBZ.js";
26
26
  import "./chunk-GDKLH7ZY.js";
@@ -20,8 +20,8 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-T4WTKBWI.js";
24
- import "./chunk-4OU37CXP.js";
23
+ } from "./chunk-ROAXLDV7.js";
24
+ import "./chunk-BH3GIMWE.js";
25
25
  import "./chunk-A7NEA4PA.js";
26
26
  import "./chunk-GYB5BOSP.js";
27
27
  import "./chunk-MYH6FIBZ.js";
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-7OP2ISEY.js";
17
+ } from "./chunk-UR3R5LWF.js";
18
18
  import "./chunk-GYB5BOSP.js";
19
19
  import {
20
20
  connectToCDP,
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-4OU37CXP.js";
17
+ } from "./chunk-BH3GIMWE.js";
18
18
  import "./chunk-A7NEA4PA.js";
19
19
  import "./chunk-GYB5BOSP.js";
20
20
  import {
@@ -710,6 +710,61 @@ async function scrollIntoView(page, selector) {
710
710
  `);
711
711
  }
712
712
 
713
+ // src/cdp-driver/editor-profile.ts
714
+ function buildEditorProbe(selector) {
715
+ return `(function() {
716
+ const el = ${selector};
717
+ if (!el) return { react: false, richFamily: null, contenteditable: false, controlled: false };
718
+ const editable = el.closest('[contenteditable="true"]');
719
+ const host = editable || el;
720
+ // React fiber\uFF1A\u5143\u7D20\u81EA\u6709\u952E\u626B\u63CF\uFF08interaction.ts \u9875\u9762\u7EA7\u63A2\u6D4B\u7684\u5143\u7D20\u7EA7\u6536\u7A84\uFF09
721
+ let react = false;
722
+ try {
723
+ for (const k of Object.keys(host)) {
724
+ if (k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$')) { react = true; break; }
725
+ }
726
+ if (!react && host.parentElement) {
727
+ for (const k of Object.keys(host.parentElement)) {
728
+ if (k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$')) { react = true; break; }
729
+ }
730
+ }
731
+ } catch (e) { /* \u8DE8\u57DF/\u6743\u9650\u515C\u5E95 */ }
732
+ // \u5DF2\u77E5\u7F16\u8F91\u5668\u5BB6\u65CF\uFF08\u81EA\u7814 Draft \u7CFB\u6700\u5371\u9669\u2014\u2014EditorState \u5F3A\u53D7\u63A7\uFF09
733
+ const families = [
734
+ ['.public-DraftEditor-content', 'draft'],
735
+ ['.DraftEditor-editorContainer', 'draft'],
736
+ ['.ProseMirror', 'prosemirror'],
737
+ ['.ql-editor', 'quill'],
738
+ ['.CodeMirror', 'codemirror'],
739
+ ['.cm-editor', 'codemirror'],
740
+ ['[data-slate-editor]', 'slate'],
741
+ ['.jsx-lexical', 'lexical'],
742
+ ['.contenteditable-editor', 'generic-rich'],
743
+ ];
744
+ let richFamily = null;
745
+ for (const [sel, name] of families) {
746
+ if (host.closest(sel)) { richFamily = name; break; }
747
+ }
748
+ const contenteditable = !!editable && el.tagName !== 'INPUT' && el.tagName !== 'TEXTAREA';
749
+ return {
750
+ react,
751
+ richFamily,
752
+ contenteditable,
753
+ controlled: react || !!richFamily || contenteditable,
754
+ };
755
+ })()`;
756
+ }
757
+ async function probeEditor(evaluate, selector) {
758
+ const fallback = { react: false, richFamily: null, contenteditable: false, controlled: false };
759
+ try {
760
+ const r = await evaluate(buildEditorProbe(selector));
761
+ if (r && typeof r === "object") return r;
762
+ return fallback;
763
+ } catch {
764
+ return fallback;
765
+ }
766
+ }
767
+
713
768
  // src/cdp-driver/locator.ts
714
769
  var XBLocatorImpl = class _XBLocatorImpl {
715
770
  page;
@@ -783,19 +838,51 @@ var XBLocatorImpl = class _XBLocatorImpl {
783
838
  if ((el.value || el.textContent || '') !== '') document.execCommand('selectAll');
784
839
  })()
785
840
  `);
786
- if (process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type") {
841
+ const profile = await probeEditor(
842
+ (expr) => this.page.evaluate(expr),
843
+ this._q(this.selector)
844
+ );
845
+ const verifyMs = parseInt(process.env.XBROWSER_FILL_VERIFY_MS ?? "600", 10) || 600;
846
+ const readValue = () => this.page.evaluate(
847
+ `(function(){ const el = ${this._q(this.selector)}; if (!el) return ''; const v = el.value !== undefined ? el.value : el.textContent; return v || ''; })()`
848
+ );
849
+ const survived = async (expected) => {
850
+ await this.page.waitForTimeout(verifyMs);
851
+ const got = await readValue();
852
+ return !(got === "" || got.length < expected.length * 0.5);
853
+ };
854
+ const canFastPath = process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type" && !profile.controlled;
855
+ if (canFastPath) {
787
856
  try {
788
857
  const { pasteViaClipboard, syntheticPaste } = await import("./clipboard-XRP54ENE.js");
789
858
  await pasteViaClipboard(this.page, value);
790
859
  const got = await this.page.evaluate(
791
860
  `(function(){ const el = ${this._q(this.selector)}; return el ? (el.value || '') : ''; })()`
792
861
  );
793
- if (got === value) return;
794
- if (await syntheticPaste(this.page, this._q(this.selector), value)) return;
862
+ if (got === value && await survived(value)) return;
863
+ const syn = await syntheticPaste(this.page, this._q(this.selector), value);
864
+ if (syn && await survived(value)) return;
795
865
  } catch {
796
866
  }
797
867
  }
798
868
  await this.page.keyboard.type(value, { stealth: true });
869
+ if (profile.controlled && verifyMs > 0 && !await survived(value)) {
870
+ await this.click({ ...opts });
871
+ await this.page.evaluate(`
872
+ (function() {
873
+ const el = ${this._q(this.selector)};
874
+ if (!el) return;
875
+ if (document.activeElement !== el) el.focus();
876
+ document.execCommand('selectAll');
877
+ })()
878
+ `);
879
+ await this.page.keyboard.type(value, { stealth: true });
880
+ if (!await survived(value)) {
881
+ throw new Error(
882
+ `input not retained: \u53D7\u63A7\u7F16\u8F91\u5668\u5728\u952E\u76D8\u6D41\u540E\u4ECD\u6E05\u7A7A\u6CE8\u5165\u503C\uFF08\u5347\u7EA7\u4EBA\u5DE5\u6216\u68C0\u67E5\u7AD9\u70B9\u88C1\u51B3\u903B\u8F91\uFF1B\u53EF\u8C03 XBROWSER_FILL_VERIFY_MS\uFF09`
883
+ );
884
+ }
885
+ }
799
886
  return;
800
887
  await this.page.evaluate(`
801
888
  (function() {
@@ -611,6 +611,61 @@ async function scrollIntoView(page, selector) {
611
611
  `);
612
612
  }
613
613
 
614
+ // src/cdp-driver/editor-profile.ts
615
+ function buildEditorProbe(selector) {
616
+ return `(function() {
617
+ const el = ${selector};
618
+ if (!el) return { react: false, richFamily: null, contenteditable: false, controlled: false };
619
+ const editable = el.closest('[contenteditable="true"]');
620
+ const host = editable || el;
621
+ // React fiber\uFF1A\u5143\u7D20\u81EA\u6709\u952E\u626B\u63CF\uFF08interaction.ts \u9875\u9762\u7EA7\u63A2\u6D4B\u7684\u5143\u7D20\u7EA7\u6536\u7A84\uFF09
622
+ let react = false;
623
+ try {
624
+ for (const k of Object.keys(host)) {
625
+ if (k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$')) { react = true; break; }
626
+ }
627
+ if (!react && host.parentElement) {
628
+ for (const k of Object.keys(host.parentElement)) {
629
+ if (k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$')) { react = true; break; }
630
+ }
631
+ }
632
+ } catch (e) { /* \u8DE8\u57DF/\u6743\u9650\u515C\u5E95 */ }
633
+ // \u5DF2\u77E5\u7F16\u8F91\u5668\u5BB6\u65CF\uFF08\u81EA\u7814 Draft \u7CFB\u6700\u5371\u9669\u2014\u2014EditorState \u5F3A\u53D7\u63A7\uFF09
634
+ const families = [
635
+ ['.public-DraftEditor-content', 'draft'],
636
+ ['.DraftEditor-editorContainer', 'draft'],
637
+ ['.ProseMirror', 'prosemirror'],
638
+ ['.ql-editor', 'quill'],
639
+ ['.CodeMirror', 'codemirror'],
640
+ ['.cm-editor', 'codemirror'],
641
+ ['[data-slate-editor]', 'slate'],
642
+ ['.jsx-lexical', 'lexical'],
643
+ ['.contenteditable-editor', 'generic-rich'],
644
+ ];
645
+ let richFamily = null;
646
+ for (const [sel, name] of families) {
647
+ if (host.closest(sel)) { richFamily = name; break; }
648
+ }
649
+ const contenteditable = !!editable && el.tagName !== 'INPUT' && el.tagName !== 'TEXTAREA';
650
+ return {
651
+ react,
652
+ richFamily,
653
+ contenteditable,
654
+ controlled: react || !!richFamily || contenteditable,
655
+ };
656
+ })()`;
657
+ }
658
+ async function probeEditor(evaluate, selector) {
659
+ const fallback = { react: false, richFamily: null, contenteditable: false, controlled: false };
660
+ try {
661
+ const r = await evaluate(buildEditorProbe(selector));
662
+ if (r && typeof r === "object") return r;
663
+ return fallback;
664
+ } catch {
665
+ return fallback;
666
+ }
667
+ }
668
+
614
669
  // src/cdp-driver/locator.ts
615
670
  var XBLocatorImpl = class _XBLocatorImpl {
616
671
  page;
@@ -684,19 +739,51 @@ var XBLocatorImpl = class _XBLocatorImpl {
684
739
  if ((el.value || el.textContent || '') !== '') document.execCommand('selectAll');
685
740
  })()
686
741
  `);
687
- if (process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type") {
742
+ const profile = await probeEditor(
743
+ (expr) => this.page.evaluate(expr),
744
+ this._q(this.selector)
745
+ );
746
+ const verifyMs = parseInt(process.env.XBROWSER_FILL_VERIFY_MS ?? "600", 10) || 600;
747
+ const readValue = () => this.page.evaluate(
748
+ `(function(){ const el = ${this._q(this.selector)}; if (!el) return ''; const v = el.value !== undefined ? el.value : el.textContent; return v || ''; })()`
749
+ );
750
+ const survived = async (expected) => {
751
+ await this.page.waitForTimeout(verifyMs);
752
+ const got = await readValue();
753
+ return !(got === "" || got.length < expected.length * 0.5);
754
+ };
755
+ const canFastPath = process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type" && !profile.controlled;
756
+ if (canFastPath) {
688
757
  try {
689
758
  const { pasteViaClipboard, syntheticPaste } = await import("./clipboard-BOL2GP2E.js");
690
759
  await pasteViaClipboard(this.page, value);
691
760
  const got = await this.page.evaluate(
692
761
  `(function(){ const el = ${this._q(this.selector)}; return el ? (el.value || '') : ''; })()`
693
762
  );
694
- if (got === value) return;
695
- if (await syntheticPaste(this.page, this._q(this.selector), value)) return;
763
+ if (got === value && await survived(value)) return;
764
+ const syn = await syntheticPaste(this.page, this._q(this.selector), value);
765
+ if (syn && await survived(value)) return;
696
766
  } catch {
697
767
  }
698
768
  }
699
769
  await this.page.keyboard.type(value, { stealth: true });
770
+ if (profile.controlled && verifyMs > 0 && !await survived(value)) {
771
+ await this.click({ ...opts });
772
+ await this.page.evaluate(`
773
+ (function() {
774
+ const el = ${this._q(this.selector)};
775
+ if (!el) return;
776
+ if (document.activeElement !== el) el.focus();
777
+ document.execCommand('selectAll');
778
+ })()
779
+ `);
780
+ await this.page.keyboard.type(value, { stealth: true });
781
+ if (!await survived(value)) {
782
+ throw new Error(
783
+ `input not retained: \u53D7\u63A7\u7F16\u8F91\u5668\u5728\u952E\u76D8\u6D41\u540E\u4ECD\u6E05\u7A7A\u6CE8\u5165\u503C\uFF08\u5347\u7EA7\u4EBA\u5DE5\u6216\u68C0\u67E5\u7AD9\u70B9\u88C1\u51B3\u903B\u8F91\uFF1B\u53EF\u8C03 XBROWSER_FILL_VERIFY_MS\uFF09`
784
+ );
785
+ }
786
+ }
700
787
  return;
701
788
  await this.page.evaluate(`
702
789
  (function() {
@@ -9,7 +9,7 @@ import {
9
9
  resolveLaunchOpts,
10
10
  saveSessionDiskMeta,
11
11
  setActivePage
12
- } from "./chunk-2Q3GQRUP.js";
12
+ } from "./chunk-PKXJST62.js";
13
13
  import {
14
14
  detectAntiBot,
15
15
  formatDetectionMessage
@@ -7146,7 +7146,7 @@ function setWSServerCache(browserContext, server) {
7146
7146
  import {
7147
7147
  Core
7148
7148
  } from "@dyyz1993/xcli-core";
7149
- import { resolve as resolve2 } from "path";
7149
+ import { resolve as resolve2, basename, dirname as dirname3 } from "path";
7150
7150
  import { existsSync as existsSync6, readdirSync } from "fs";
7151
7151
  import { homedir as homedir4 } from "os";
7152
7152
 
@@ -7450,6 +7450,8 @@ var XBrowserPluginLoader = class {
7450
7450
  core;
7451
7451
  loader;
7452
7452
  options;
7453
+ /** 目录名 → 该插件注册的 site 名(site.name ≠ 目录名时的命令别名兜底) */
7454
+ dirSiteAliases = /* @__PURE__ */ new Map();
7453
7455
  constructor(options) {
7454
7456
  patchLoginRequired();
7455
7457
  this.options = options ?? {};
@@ -7487,15 +7489,39 @@ var XBrowserPluginLoader = class {
7487
7489
  getLoadedPlugins() {
7488
7490
  return this.loader.getLoadedPlugins();
7489
7491
  }
7492
+ /**
7493
+ * 目录名别名兜底(小白用户实测踩坑):`plugin list` 显示目录名,但命令路由
7494
+ * 按 site.name 匹配——两者不一致时(如目录 alibaba-1688、site 名 1688),
7495
+ * 用户按 list 显示的名字敲命令会 "Unknown command"。映射在 scanAndLoad/
7496
+ * loadPlugin 时通过加载前后 site 快照 diff 记录(instance.siteName 返回的是
7497
+ * 插件 id 而非 site 名,不可用)。
7498
+ */
7499
+ resolveSiteName(name) {
7500
+ if (this.core.loader.getSite(name)) return name;
7501
+ const aliased = this.dirSiteAliases.get(name);
7502
+ if (aliased && this.core.loader.getSite(aliased)) return aliased;
7503
+ return void 0;
7504
+ }
7505
+ /** 记录一次插件加载引入的 site:加载前后快照 diff(同插件可注册多个 site) */
7506
+ recordSiteAliases(pluginDirName, before) {
7507
+ for (const site of this.core.loader.getSites()) {
7508
+ if (!before.has(site.name)) this.dirSiteAliases.set(pluginDirName, site.name);
7509
+ }
7510
+ }
7490
7511
  getPluginContract(siteName, commandName) {
7491
- const site = this.core.loader.getSite(siteName);
7512
+ const resolved = this.resolveSiteName(siteName);
7513
+ if (!resolved) return void 0;
7514
+ const site = this.core.loader.getSite(resolved);
7492
7515
  if (!site) return void 0;
7493
7516
  const contract = buildPluginContract(site);
7494
7517
  if (!commandName) return contract;
7495
7518
  return contract.commands.find((command) => command.name === commandName);
7496
7519
  }
7497
7520
  async loadPlugin(pluginPath, id) {
7498
- return this.loader.loadPlugin(pluginPath, id);
7521
+ const before = new Set(this.core.loader.getSites().map((s) => s.name));
7522
+ const instance = await this.loader.loadPlugin(pluginPath, id);
7523
+ this.recordSiteAliases(basename(dirname3(pluginPath)), before);
7524
+ return instance;
7499
7525
  }
7500
7526
  async unloadPlugin(id) {
7501
7527
  return this.loader.unloadPlugin(id);
@@ -7563,6 +7589,7 @@ var FAIL_CODES = {
7563
7589
  selectorNotFound: "RETRYABLE: selector-not-found",
7564
7590
  pageNotReady: "RETRYABLE: page-not-ready",
7565
7591
  elementGone: "RETRYABLE: element-gone",
7592
+ inputNotRetained: "RETRYABLE: input-not-retained",
7566
7593
  captcha: "HUMAN: captcha",
7567
7594
  loginWall: "HUMAN: login-wall",
7568
7595
  permissionDenied: "FATAL: permission-denied",
@@ -7582,6 +7609,7 @@ function classifyFailure(message) {
7582
7609
  if (/selector|选择器|element not found|未找到元素|no such element|queryselector returned null/.test(msg))
7583
7610
  return pick(FAIL_CODES.selectorNotFound);
7584
7611
  if (/timeout|超时|timed out|waiting for/.test(msg)) return pick(FAIL_CODES.pageNotReady);
7612
+ if (/not retained|清空了注入值|input-not-retained/.test(msg)) return pick(FAIL_CODES.inputNotRetained);
7585
7613
  if (/net::err|404|navigation failed|页面不存在|target closed|element-gone/.test(msg))
7586
7614
  return pick(FAIL_CODES.targetGone);
7587
7615
  return pick(FAIL_CODES.unknown);
@@ -8536,7 +8564,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
8536
8564
  }
8537
8565
  let targetPageOverride = null;
8538
8566
  if (_target && extraOpts?.cdpEndpoint) {
8539
- const { findTargetPage } = await import("./browser-Q67BJTI2.js");
8567
+ const { findTargetPage } = await import("./browser-DFM2SQDM.js");
8540
8568
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
8541
8569
  if (!targetPageOverride) {
8542
8570
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -8837,7 +8865,8 @@ async function executeChain(input, options) {
8837
8865
  const cmdArgs = parts.slice(1);
8838
8866
  const loader = await getPluginLoader();
8839
8867
  const internalLoader = loader.getCore().loader;
8840
- const site = internalLoader.getSite(cmdName);
8868
+ const resolvedName = loader.resolveSiteName(cmdName);
8869
+ const site = resolvedName ? internalLoader.getSite(resolvedName) : void 0;
8841
8870
  if (site) {
8842
8871
  const subCommand = cmdArgs[0];
8843
8872
  if (!subCommand) {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  Core
4
4
  } from "@dyyz1993/xcli-core";
5
- import { resolve as resolve2 } from "path";
5
+ import { resolve as resolve2, basename, dirname } from "path";
6
6
  import { existsSync as existsSync3, readdirSync } from "fs";
7
7
  import { homedir } from "os";
8
8
 
@@ -306,6 +306,8 @@ var XBrowserPluginLoader = class {
306
306
  core;
307
307
  loader;
308
308
  options;
309
+ /** 目录名 → 该插件注册的 site 名(site.name ≠ 目录名时的命令别名兜底) */
310
+ dirSiteAliases = /* @__PURE__ */ new Map();
309
311
  constructor(options) {
310
312
  patchLoginRequired();
311
313
  this.options = options ?? {};
@@ -343,15 +345,39 @@ var XBrowserPluginLoader = class {
343
345
  getLoadedPlugins() {
344
346
  return this.loader.getLoadedPlugins();
345
347
  }
348
+ /**
349
+ * 目录名别名兜底(小白用户实测踩坑):`plugin list` 显示目录名,但命令路由
350
+ * 按 site.name 匹配——两者不一致时(如目录 alibaba-1688、site 名 1688),
351
+ * 用户按 list 显示的名字敲命令会 "Unknown command"。映射在 scanAndLoad/
352
+ * loadPlugin 时通过加载前后 site 快照 diff 记录(instance.siteName 返回的是
353
+ * 插件 id 而非 site 名,不可用)。
354
+ */
355
+ resolveSiteName(name) {
356
+ if (this.core.loader.getSite(name)) return name;
357
+ const aliased = this.dirSiteAliases.get(name);
358
+ if (aliased && this.core.loader.getSite(aliased)) return aliased;
359
+ return void 0;
360
+ }
361
+ /** 记录一次插件加载引入的 site:加载前后快照 diff(同插件可注册多个 site) */
362
+ recordSiteAliases(pluginDirName, before) {
363
+ for (const site of this.core.loader.getSites()) {
364
+ if (!before.has(site.name)) this.dirSiteAliases.set(pluginDirName, site.name);
365
+ }
366
+ }
346
367
  getPluginContract(siteName, commandName) {
347
- const site = this.core.loader.getSite(siteName);
368
+ const resolved = this.resolveSiteName(siteName);
369
+ if (!resolved) return void 0;
370
+ const site = this.core.loader.getSite(resolved);
348
371
  if (!site) return void 0;
349
372
  const contract = buildPluginContract(site);
350
373
  if (!commandName) return contract;
351
374
  return contract.commands.find((command) => command.name === commandName);
352
375
  }
353
376
  async loadPlugin(pluginPath, id) {
354
- return this.loader.loadPlugin(pluginPath, id);
377
+ const before = new Set(this.core.loader.getSites().map((s) => s.name));
378
+ const instance = await this.loader.loadPlugin(pluginPath, id);
379
+ this.recordSiteAliases(basename(dirname(pluginPath)), before);
380
+ return instance;
355
381
  }
356
382
  async unloadPlugin(id) {
357
383
  return this.loader.unloadPlugin(id);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-7OP2ISEY.js";
3
+ } from "./chunk-UR3R5LWF.js";
4
4
  import {
5
5
  errMsg
6
6
  } from "./chunk-GDKLH7ZY.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createRuleEngine,
3
3
  launch
4
- } from "./chunk-4OU37CXP.js";
4
+ } from "./chunk-BH3GIMWE.js";
5
5
  import {
6
6
  errMsg
7
7
  } from "./chunk-GDKLH7ZY.js";
@@ -20,7 +20,7 @@ import {
20
20
  resolveLaunchOpts,
21
21
  saveSessionDiskMeta,
22
22
  setActivePage
23
- } from "./chunk-IFKJO2UR.js";
23
+ } from "./chunk-X735U7G7.js";
24
24
  import {
25
25
  rand,
26
26
  sleep,
@@ -35,6 +35,7 @@ var FAIL_CODES = {
35
35
  selectorNotFound: "RETRYABLE: selector-not-found",
36
36
  pageNotReady: "RETRYABLE: page-not-ready",
37
37
  elementGone: "RETRYABLE: element-gone",
38
+ inputNotRetained: "RETRYABLE: input-not-retained",
38
39
  captcha: "HUMAN: captcha",
39
40
  loginWall: "HUMAN: login-wall",
40
41
  permissionDenied: "FATAL: permission-denied",
@@ -54,6 +55,7 @@ function classifyFailure(message) {
54
55
  if (/selector|选择器|element not found|未找到元素|no such element|queryselector returned null/.test(msg))
55
56
  return pick(FAIL_CODES.selectorNotFound);
56
57
  if (/timeout|超时|timed out|waiting for/.test(msg)) return pick(FAIL_CODES.pageNotReady);
58
+ if (/not retained|清空了注入值|input-not-retained/.test(msg)) return pick(FAIL_CODES.inputNotRetained);
57
59
  if (/net::err|404|navigation failed|页面不存在|target closed|element-gone/.test(msg))
58
60
  return pick(FAIL_CODES.targetGone);
59
61
  return pick(FAIL_CODES.unknown);
@@ -7045,7 +7047,7 @@ function attachDetectAntiBot(ctx) {
7045
7047
  import {
7046
7048
  Core
7047
7049
  } from "@dyyz1993/xcli-core";
7048
- import { resolve as resolve2 } from "path";
7050
+ import { resolve as resolve2, basename, dirname as dirname3 } from "path";
7049
7051
  import { existsSync as existsSync6, readdirSync } from "fs";
7050
7052
  import { homedir as homedir5 } from "os";
7051
7053
 
@@ -7349,6 +7351,8 @@ var XBrowserPluginLoader = class {
7349
7351
  core;
7350
7352
  loader;
7351
7353
  options;
7354
+ /** 目录名 → 该插件注册的 site 名(site.name ≠ 目录名时的命令别名兜底) */
7355
+ dirSiteAliases = /* @__PURE__ */ new Map();
7352
7356
  constructor(options) {
7353
7357
  patchLoginRequired();
7354
7358
  this.options = options ?? {};
@@ -7386,15 +7390,39 @@ var XBrowserPluginLoader = class {
7386
7390
  getLoadedPlugins() {
7387
7391
  return this.loader.getLoadedPlugins();
7388
7392
  }
7393
+ /**
7394
+ * 目录名别名兜底(小白用户实测踩坑):`plugin list` 显示目录名,但命令路由
7395
+ * 按 site.name 匹配——两者不一致时(如目录 alibaba-1688、site 名 1688),
7396
+ * 用户按 list 显示的名字敲命令会 "Unknown command"。映射在 scanAndLoad/
7397
+ * loadPlugin 时通过加载前后 site 快照 diff 记录(instance.siteName 返回的是
7398
+ * 插件 id 而非 site 名,不可用)。
7399
+ */
7400
+ resolveSiteName(name) {
7401
+ if (this.core.loader.getSite(name)) return name;
7402
+ const aliased = this.dirSiteAliases.get(name);
7403
+ if (aliased && this.core.loader.getSite(aliased)) return aliased;
7404
+ return void 0;
7405
+ }
7406
+ /** 记录一次插件加载引入的 site:加载前后快照 diff(同插件可注册多个 site) */
7407
+ recordSiteAliases(pluginDirName, before) {
7408
+ for (const site of this.core.loader.getSites()) {
7409
+ if (!before.has(site.name)) this.dirSiteAliases.set(pluginDirName, site.name);
7410
+ }
7411
+ }
7389
7412
  getPluginContract(siteName, commandName) {
7390
- const site = this.core.loader.getSite(siteName);
7413
+ const resolved = this.resolveSiteName(siteName);
7414
+ if (!resolved) return void 0;
7415
+ const site = this.core.loader.getSite(resolved);
7391
7416
  if (!site) return void 0;
7392
7417
  const contract = buildPluginContract(site);
7393
7418
  if (!commandName) return contract;
7394
7419
  return contract.commands.find((command) => command.name === commandName);
7395
7420
  }
7396
7421
  async loadPlugin(pluginPath, id) {
7397
- return this.loader.loadPlugin(pluginPath, id);
7422
+ const before = new Set(this.core.loader.getSites().map((s) => s.name));
7423
+ const instance = await this.loader.loadPlugin(pluginPath, id);
7424
+ this.recordSiteAliases(basename(dirname3(pluginPath)), before);
7425
+ return instance;
7398
7426
  }
7399
7427
  async unloadPlugin(id) {
7400
7428
  return this.loader.unloadPlugin(id);
@@ -8216,7 +8244,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
8216
8244
  }
8217
8245
  let targetPageOverride = null;
8218
8246
  if (_target && extraOpts?.cdpEndpoint) {
8219
- const { findTargetPage } = await import("./browser-BVRFSUXU.js");
8247
+ const { findTargetPage } = await import("./browser-I3EZY3FL.js");
8220
8248
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
8221
8249
  if (!targetPageOverride) {
8222
8250
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -8517,7 +8545,8 @@ async function executeChain(input, options) {
8517
8545
  const cmdArgs = parts.slice(1);
8518
8546
  const loader = await getPluginLoader();
8519
8547
  const internalLoader = loader.getCore().loader;
8520
- const site = internalLoader.getSite(cmdName);
8548
+ const resolvedName = loader.resolveSiteName(cmdName);
8549
+ const site = resolvedName ? internalLoader.getSite(resolvedName) : void 0;
8521
8550
  if (site) {
8522
8551
  const subCommand = cmdArgs[0];
8523
8552
  if (!subCommand) {
@@ -712,6 +712,61 @@ async function scrollIntoView(page, selector) {
712
712
  `);
713
713
  }
714
714
 
715
+ // src/cdp-driver/editor-profile.ts
716
+ function buildEditorProbe(selector) {
717
+ return `(function() {
718
+ const el = ${selector};
719
+ if (!el) return { react: false, richFamily: null, contenteditable: false, controlled: false };
720
+ const editable = el.closest('[contenteditable="true"]');
721
+ const host = editable || el;
722
+ // React fiber\uFF1A\u5143\u7D20\u81EA\u6709\u952E\u626B\u63CF\uFF08interaction.ts \u9875\u9762\u7EA7\u63A2\u6D4B\u7684\u5143\u7D20\u7EA7\u6536\u7A84\uFF09
723
+ let react = false;
724
+ try {
725
+ for (const k of Object.keys(host)) {
726
+ if (k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$')) { react = true; break; }
727
+ }
728
+ if (!react && host.parentElement) {
729
+ for (const k of Object.keys(host.parentElement)) {
730
+ if (k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$')) { react = true; break; }
731
+ }
732
+ }
733
+ } catch (e) { /* \u8DE8\u57DF/\u6743\u9650\u515C\u5E95 */ }
734
+ // \u5DF2\u77E5\u7F16\u8F91\u5668\u5BB6\u65CF\uFF08\u81EA\u7814 Draft \u7CFB\u6700\u5371\u9669\u2014\u2014EditorState \u5F3A\u53D7\u63A7\uFF09
735
+ const families = [
736
+ ['.public-DraftEditor-content', 'draft'],
737
+ ['.DraftEditor-editorContainer', 'draft'],
738
+ ['.ProseMirror', 'prosemirror'],
739
+ ['.ql-editor', 'quill'],
740
+ ['.CodeMirror', 'codemirror'],
741
+ ['.cm-editor', 'codemirror'],
742
+ ['[data-slate-editor]', 'slate'],
743
+ ['.jsx-lexical', 'lexical'],
744
+ ['.contenteditable-editor', 'generic-rich'],
745
+ ];
746
+ let richFamily = null;
747
+ for (const [sel, name] of families) {
748
+ if (host.closest(sel)) { richFamily = name; break; }
749
+ }
750
+ const contenteditable = !!editable && el.tagName !== 'INPUT' && el.tagName !== 'TEXTAREA';
751
+ return {
752
+ react,
753
+ richFamily,
754
+ contenteditable,
755
+ controlled: react || !!richFamily || contenteditable,
756
+ };
757
+ })()`;
758
+ }
759
+ async function probeEditor(evaluate, selector) {
760
+ const fallback = { react: false, richFamily: null, contenteditable: false, controlled: false };
761
+ try {
762
+ const r = await evaluate(buildEditorProbe(selector));
763
+ if (r && typeof r === "object") return r;
764
+ return fallback;
765
+ } catch {
766
+ return fallback;
767
+ }
768
+ }
769
+
715
770
  // src/cdp-driver/locator.ts
716
771
  var XBLocatorImpl = class _XBLocatorImpl {
717
772
  page;
@@ -785,19 +840,51 @@ var XBLocatorImpl = class _XBLocatorImpl {
785
840
  if ((el.value || el.textContent || '') !== '') document.execCommand('selectAll');
786
841
  })()
787
842
  `);
788
- if (process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type") {
843
+ const profile = await probeEditor(
844
+ (expr) => this.page.evaluate(expr),
845
+ this._q(this.selector)
846
+ );
847
+ const verifyMs = parseInt(process.env.XBROWSER_FILL_VERIFY_MS ?? "600", 10) || 600;
848
+ const readValue = () => this.page.evaluate(
849
+ `(function(){ const el = ${this._q(this.selector)}; if (!el) return ''; const v = el.value !== undefined ? el.value : el.textContent; return v || ''; })()`
850
+ );
851
+ const survived = async (expected) => {
852
+ await this.page.waitForTimeout(verifyMs);
853
+ const got = await readValue();
854
+ return !(got === "" || got.length < expected.length * 0.5);
855
+ };
856
+ const canFastPath = process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type" && !profile.controlled;
857
+ if (canFastPath) {
789
858
  try {
790
859
  const { pasteViaClipboard, syntheticPaste } = await import("./clipboard-BOL2GP2E.js");
791
860
  await pasteViaClipboard(this.page, value);
792
861
  const got = await this.page.evaluate(
793
862
  `(function(){ const el = ${this._q(this.selector)}; return el ? (el.value || '') : ''; })()`
794
863
  );
795
- if (got === value) return;
796
- if (await syntheticPaste(this.page, this._q(this.selector), value)) return;
864
+ if (got === value && await survived(value)) return;
865
+ const syn = await syntheticPaste(this.page, this._q(this.selector), value);
866
+ if (syn && await survived(value)) return;
797
867
  } catch {
798
868
  }
799
869
  }
800
870
  await this.page.keyboard.type(value, { stealth: true });
871
+ if (profile.controlled && verifyMs > 0 && !await survived(value)) {
872
+ await this.click({ ...opts });
873
+ await this.page.evaluate(`
874
+ (function() {
875
+ const el = ${this._q(this.selector)};
876
+ if (!el) return;
877
+ if (document.activeElement !== el) el.focus();
878
+ document.execCommand('selectAll');
879
+ })()
880
+ `);
881
+ await this.page.keyboard.type(value, { stealth: true });
882
+ if (!await survived(value)) {
883
+ throw new Error(
884
+ `input not retained: \u53D7\u63A7\u7F16\u8F91\u5668\u5728\u952E\u76D8\u6D41\u540E\u4ECD\u6E05\u7A7A\u6CE8\u5165\u503C\uFF08\u5347\u7EA7\u4EBA\u5DE5\u6216\u68C0\u67E5\u7AD9\u70B9\u88C1\u51B3\u903B\u8F91\uFF1B\u53EF\u8C03 XBROWSER_FILL_VERIFY_MS\uFF09`
885
+ );
886
+ }
887
+ }
801
888
  return;
802
889
  await this.page.evaluate(`
803
890
  (function() {
@@ -715,6 +715,61 @@ async function scrollIntoView(page, selector) {
715
715
  `);
716
716
  }
717
717
 
718
+ // src/cdp-driver/editor-profile.ts
719
+ function buildEditorProbe(selector) {
720
+ return `(function() {
721
+ const el = ${selector};
722
+ if (!el) return { react: false, richFamily: null, contenteditable: false, controlled: false };
723
+ const editable = el.closest('[contenteditable="true"]');
724
+ const host = editable || el;
725
+ // React fiber\uFF1A\u5143\u7D20\u81EA\u6709\u952E\u626B\u63CF\uFF08interaction.ts \u9875\u9762\u7EA7\u63A2\u6D4B\u7684\u5143\u7D20\u7EA7\u6536\u7A84\uFF09
726
+ let react = false;
727
+ try {
728
+ for (const k of Object.keys(host)) {
729
+ if (k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$')) { react = true; break; }
730
+ }
731
+ if (!react && host.parentElement) {
732
+ for (const k of Object.keys(host.parentElement)) {
733
+ if (k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$')) { react = true; break; }
734
+ }
735
+ }
736
+ } catch (e) { /* \u8DE8\u57DF/\u6743\u9650\u515C\u5E95 */ }
737
+ // \u5DF2\u77E5\u7F16\u8F91\u5668\u5BB6\u65CF\uFF08\u81EA\u7814 Draft \u7CFB\u6700\u5371\u9669\u2014\u2014EditorState \u5F3A\u53D7\u63A7\uFF09
738
+ const families = [
739
+ ['.public-DraftEditor-content', 'draft'],
740
+ ['.DraftEditor-editorContainer', 'draft'],
741
+ ['.ProseMirror', 'prosemirror'],
742
+ ['.ql-editor', 'quill'],
743
+ ['.CodeMirror', 'codemirror'],
744
+ ['.cm-editor', 'codemirror'],
745
+ ['[data-slate-editor]', 'slate'],
746
+ ['.jsx-lexical', 'lexical'],
747
+ ['.contenteditable-editor', 'generic-rich'],
748
+ ];
749
+ let richFamily = null;
750
+ for (const [sel, name] of families) {
751
+ if (host.closest(sel)) { richFamily = name; break; }
752
+ }
753
+ const contenteditable = !!editable && el.tagName !== 'INPUT' && el.tagName !== 'TEXTAREA';
754
+ return {
755
+ react,
756
+ richFamily,
757
+ contenteditable,
758
+ controlled: react || !!richFamily || contenteditable,
759
+ };
760
+ })()`;
761
+ }
762
+ async function probeEditor(evaluate, selector) {
763
+ const fallback = { react: false, richFamily: null, contenteditable: false, controlled: false };
764
+ try {
765
+ const r = await evaluate(buildEditorProbe(selector));
766
+ if (r && typeof r === "object") return r;
767
+ return fallback;
768
+ } catch {
769
+ return fallback;
770
+ }
771
+ }
772
+
718
773
  // src/cdp-driver/locator.ts
719
774
  var XBLocatorImpl = class _XBLocatorImpl {
720
775
  page;
@@ -788,19 +843,51 @@ var XBLocatorImpl = class _XBLocatorImpl {
788
843
  if ((el.value || el.textContent || '') !== '') document.execCommand('selectAll');
789
844
  })()
790
845
  `);
791
- if (process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type") {
846
+ const profile = await probeEditor(
847
+ (expr) => this.page.evaluate(expr),
848
+ this._q(this.selector)
849
+ );
850
+ const verifyMs = parseInt(process.env.XBROWSER_FILL_VERIFY_MS ?? "600", 10) || 600;
851
+ const readValue = () => this.page.evaluate(
852
+ `(function(){ const el = ${this._q(this.selector)}; if (!el) return ''; const v = el.value !== undefined ? el.value : el.textContent; return v || ''; })()`
853
+ );
854
+ const survived = async (expected) => {
855
+ await this.page.waitForTimeout(verifyMs);
856
+ const got = await readValue();
857
+ return !(got === "" || got.length < expected.length * 0.5);
858
+ };
859
+ const canFastPath = process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type" && !profile.controlled;
860
+ if (canFastPath) {
792
861
  try {
793
862
  const { pasteViaClipboard, syntheticPaste } = await import("./clipboard-BOL2GP2E.js");
794
863
  await pasteViaClipboard(this.page, value);
795
864
  const got = await this.page.evaluate(
796
865
  `(function(){ const el = ${this._q(this.selector)}; return el ? (el.value || '') : ''; })()`
797
866
  );
798
- if (got === value) return;
799
- if (await syntheticPaste(this.page, this._q(this.selector), value)) return;
867
+ if (got === value && await survived(value)) return;
868
+ const syn = await syntheticPaste(this.page, this._q(this.selector), value);
869
+ if (syn && await survived(value)) return;
800
870
  } catch {
801
871
  }
802
872
  }
803
873
  await this.page.keyboard.type(value, { stealth: true });
874
+ if (profile.controlled && verifyMs > 0 && !await survived(value)) {
875
+ await this.click({ ...opts });
876
+ await this.page.evaluate(`
877
+ (function() {
878
+ const el = ${this._q(this.selector)};
879
+ if (!el) return;
880
+ if (document.activeElement !== el) el.focus();
881
+ document.execCommand('selectAll');
882
+ })()
883
+ `);
884
+ await this.page.keyboard.type(value, { stealth: true });
885
+ if (!await survived(value)) {
886
+ throw new Error(
887
+ `input not retained: \u53D7\u63A7\u7F16\u8F91\u5668\u5728\u952E\u76D8\u6D41\u540E\u4ECD\u6E05\u7A7A\u6CE8\u5165\u503C\uFF08\u5347\u7EA7\u4EBA\u5DE5\u6216\u68C0\u67E5\u7AD9\u70B9\u88C1\u51B3\u903B\u8F91\uFF1B\u53EF\u8C03 XBROWSER_FILL_VERIFY_MS\uFF09`
888
+ );
889
+ }
890
+ }
804
891
  return;
805
892
  await this.page.evaluate(`
806
893
  (function() {
package/dist/cli.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  loadHooks,
17
17
  parsePluginParams,
18
18
  readJsonFile
19
- } from "./chunk-A7RZYSWM.js";
19
+ } from "./chunk-SUHS2EWV.js";
20
20
  import "./chunk-JKVUFP3G.js";
21
21
  import {
22
22
  forwardCommandLog,
@@ -80,7 +80,7 @@ import {
80
80
  findSession,
81
81
  getAllSessions,
82
82
  saveSessionDiskMeta
83
- } from "./chunk-IFKJO2UR.js";
83
+ } from "./chunk-X735U7G7.js";
84
84
  import "./chunk-GYB5BOSP.js";
85
85
  import "./chunk-MYH6FIBZ.js";
86
86
  import {
@@ -629,6 +629,9 @@ async function installFromMarketplace(pluginsDir, slug, options) {
629
629
  const realSlug = String(plugin.slug || slug);
630
630
  try {
631
631
  await downloadAndExtractMarketplaceTarball(baseUrl, realSlug, tmpDir, targetDir);
632
+ } catch (err) {
633
+ safeCleanup5(targetDir);
634
+ throw err;
632
635
  } finally {
633
636
  safeCleanup5(tmpDir);
634
637
  }
@@ -802,6 +805,7 @@ function ensureIndexFile(plugin, name, targetDir) {
802
805
  }
803
806
 
804
807
  // src/plugin/installer.ts
808
+ import { safeCleanup as safeCleanup6 } from "@dyyz1993/xcli-core";
805
809
  var PluginInstaller = class {
806
810
  pluginsDir;
807
811
  constructor(pluginsDir) {
@@ -827,31 +831,29 @@ var PluginInstaller = class {
827
831
  }
828
832
  mkdirSync4(targetDir, { recursive: true });
829
833
  const resolvedSource = type === "npm" ? await resolveNpmPackageWithFallback(source) : source;
834
+ const withCleanup = async (fn) => {
835
+ try {
836
+ const r = await fn();
837
+ await this.fixSharedDeps(targetDir);
838
+ ensurePluginDependencies(this.pluginsDir);
839
+ if (!existsSync6(resolve6(targetDir, "index.ts")) && !existsSync6(resolve6(targetDir, "index.js"))) {
840
+ throw new Error("installed package has no index.ts/js entry");
841
+ }
842
+ return r;
843
+ } catch (err) {
844
+ safeCleanup6(targetDir);
845
+ throw err;
846
+ }
847
+ };
830
848
  switch (type) {
831
849
  case "local":
832
- return await installFromLocal(source, name, targetDir).then(async (r) => {
833
- await this.fixSharedDeps(targetDir);
834
- ensurePluginDependencies(this.pluginsDir);
835
- return r;
836
- });
850
+ return await withCleanup(() => installFromLocal(source, name, targetDir));
837
851
  case "npm":
838
- return await installFromNpm(resolvedSource, name, targetDir).then(async (r) => {
839
- await this.fixSharedDeps(targetDir);
840
- ensurePluginDependencies(this.pluginsDir);
841
- return r;
842
- });
852
+ return await withCleanup(() => installFromNpm(resolvedSource, name, targetDir));
843
853
  case "git":
844
- return await installFromGit(source, name, targetDir).then(async (r) => {
845
- await this.fixSharedDeps(targetDir);
846
- ensurePluginDependencies(this.pluginsDir);
847
- return r;
848
- });
854
+ return await withCleanup(() => installFromGit(source, name, targetDir));
849
855
  case "url":
850
- return await installFromUrl(source, name, targetDir).then(async (r) => {
851
- await this.fixSharedDeps(targetDir);
852
- ensurePluginDependencies(this.pluginsDir);
853
- return r;
854
- });
856
+ return await withCleanup(() => installFromUrl(source, name, targetDir));
855
857
  }
856
858
  }
857
859
  /**
@@ -5816,7 +5818,14 @@ async function routeCommand(argvIn, stdinCommands) {
5816
5818
  default: {
5817
5819
  const loader = await getPluginLoader();
5818
5820
  const internalLoader = loader.getCore().loader;
5819
- const site = internalLoader.getSite(command);
5821
+ const resolvedSiteName = loader.resolveSiteName(command);
5822
+ const site = resolvedSiteName ? internalLoader.getSite(resolvedSiteName) : void 0;
5823
+ if (site && resolvedSiteName !== command) {
5824
+ if (mode !== "json" && mode !== "yaml") {
5825
+ process.stderr.write(`\u2139\uFE0F "${command}" \u7684\u547D\u4EE4\u540D\u662F "${resolvedSiteName}"\uFF08\u63D2\u4EF6\u76EE\u5F55\u540D\u4E0E\u7AD9\u70B9\u540D\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u6620\u5C04\uFF09
5826
+ `);
5827
+ }
5828
+ }
5820
5829
  if (!site) {
5821
5830
  const globalFlagSet = /* @__PURE__ */ new Set(["--session", "--cdp", "--json", "--yaml", "--help", "-h", "--version", "--output", "-o"]);
5822
5831
  const cleanParts = [];
@@ -6007,7 +6016,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
6007
6016
  const targetPage = pages[cmdTabIndex];
6008
6017
  await targetPage.bringToFront().catch(() => {
6009
6018
  });
6010
- const { setActivePage } = await import("./browser-BVRFSUXU.js");
6019
+ const { setActivePage } = await import("./browser-I3EZY3FL.js");
6011
6020
  setActivePage(session, targetPage);
6012
6021
  }
6013
6022
  }
@@ -6272,7 +6281,7 @@ async function handleRemote(args, options, mode) {
6272
6281
  }
6273
6282
  }
6274
6283
  async function startMcpServer() {
6275
- const { startMcpStdio } = await import("./server-YVMWMNHD.js");
6284
+ const { startMcpStdio } = await import("./server-A5R4OIGX.js");
6276
6285
  startMcpStdio();
6277
6286
  }
6278
6287
 
@@ -6291,7 +6300,7 @@ async function main() {
6291
6300
  const command = process.argv[2];
6292
6301
  const isLongRunning = command === "preview" || command === "serve" || command === "mcp";
6293
6302
  if (!isLongRunning) {
6294
- const { ensureProcessCanExit } = await import("./browser-BVRFSUXU.js");
6303
+ const { ensureProcessCanExit } = await import("./browser-I3EZY3FL.js");
6295
6304
  await ensureProcessCanExit().catch(() => {
6296
6305
  });
6297
6306
  process.exit(process.exitCode || exitCode);
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-JKVUFP3G.js";
5
5
  import {
6
6
  getPluginLoader
7
- } from "./chunk-A3MORKYR.js";
7
+ } from "./chunk-OSZNJT4U.js";
8
8
  import {
9
9
  version
10
10
  } from "./chunk-43TEMG4U.js";
@@ -38,10 +38,10 @@ import {
38
38
  resolveLaunchOpts,
39
39
  saveSessionDiskMeta,
40
40
  setActivePage
41
- } from "./chunk-T4WTKBWI.js";
41
+ } from "./chunk-ROAXLDV7.js";
42
42
  import {
43
43
  createRuleEngine
44
- } from "./chunk-4OU37CXP.js";
44
+ } from "./chunk-BH3GIMWE.js";
45
45
  import {
46
46
  queryJS
47
47
  } from "./chunk-A7NEA4PA.js";
@@ -74,6 +74,7 @@ var FAIL_CODES = {
74
74
  selectorNotFound: "RETRYABLE: selector-not-found",
75
75
  pageNotReady: "RETRYABLE: page-not-ready",
76
76
  elementGone: "RETRYABLE: element-gone",
77
+ inputNotRetained: "RETRYABLE: input-not-retained",
77
78
  captcha: "HUMAN: captcha",
78
79
  loginWall: "HUMAN: login-wall",
79
80
  permissionDenied: "FATAL: permission-denied",
@@ -93,6 +94,7 @@ function classifyFailure(message) {
93
94
  if (/selector|选择器|element not found|未找到元素|no such element|queryselector returned null/.test(msg))
94
95
  return pick(FAIL_CODES.selectorNotFound);
95
96
  if (/timeout|超时|timed out|waiting for/.test(msg)) return pick(FAIL_CODES.pageNotReady);
97
+ if (/not retained|清空了注入值|input-not-retained/.test(msg)) return pick(FAIL_CODES.inputNotRetained);
96
98
  if (/net::err|404|navigation failed|页面不存在|target closed|element-gone/.test(msg))
97
99
  return pick(FAIL_CODES.targetGone);
98
100
  return pick(FAIL_CODES.unknown);
@@ -7797,7 +7799,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7797
7799
  }
7798
7800
  let targetPageOverride = null;
7799
7801
  if (_target && extraOpts?.cdpEndpoint) {
7800
- const { findTargetPage } = await import("./browser-DSYUAE7H.js");
7802
+ const { findTargetPage } = await import("./browser-WCHMUN6H.js");
7801
7803
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7802
7804
  if (!targetPageOverride) {
7803
7805
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -8098,7 +8100,8 @@ async function executeChain(input, options) {
8098
8100
  const cmdArgs = parts.slice(1);
8099
8101
  const loader = await getPluginLoader();
8100
8102
  const internalLoader = loader.getCore().loader;
8101
- const site = internalLoader.getSite(cmdName);
8103
+ const resolvedName = loader.resolveSiteName(cmdName);
8104
+ const site = resolvedName ? internalLoader.getSite(resolvedName) : void 0;
8102
8105
  if (site) {
8103
8106
  const subCommand = cmdArgs[0];
8104
8107
  if (!subCommand) {
@@ -9238,9 +9241,9 @@ function createRPCHandler() {
9238
9241
  return result;
9239
9242
  }
9240
9243
  async function handlePluginsReload() {
9241
- const { resetPluginLoader } = await import("./plugin-singleton-GJO7EZXB.js");
9244
+ const { resetPluginLoader } = await import("./plugin-singleton-GGSBI3EG.js");
9242
9245
  resetPluginLoader();
9243
- const loader = await import("./plugin-singleton-GJO7EZXB.js").then((m) => m.getPluginLoader());
9246
+ const loader = await import("./plugin-singleton-GGSBI3EG.js").then((m) => m.getPluginLoader());
9244
9247
  const sites = loader.getCore().loader.getSites();
9245
9248
  return { ok: true, plugins: sites.length };
9246
9249
  }
@@ -9636,7 +9639,7 @@ function createRPCHandler() {
9636
9639
  if (isNewFormat) {
9637
9640
  try {
9638
9641
  const replayErrors = [];
9639
- const { SessionReplayer } = await import("./session-replayer-N665T3G5.js");
9642
+ const { SessionReplayer } = await import("./session-replayer-PSAPQBZA.js");
9640
9643
  const replayer = new SessionReplayer({
9641
9644
  page: session.page,
9642
9645
  stepDelay: slowMo * 500,
package/dist/index.d.ts CHANGED
@@ -1634,6 +1634,8 @@ declare class XBrowserPluginLoader {
1634
1634
  private core;
1635
1635
  private loader;
1636
1636
  private options;
1637
+ /** 目录名 → 该插件注册的 site 名(site.name ≠ 目录名时的命令别名兜底) */
1638
+ private dirSiteAliases;
1637
1639
  constructor(options?: PluginLoaderOptions);
1638
1640
  getAPI(): XCLIAPI;
1639
1641
  /**
@@ -1644,6 +1646,16 @@ declare class XBrowserPluginLoader {
1644
1646
  getPlugin(id: string): PluginInstance | undefined;
1645
1647
  getPluginStatus(id: string): PluginStatus;
1646
1648
  getLoadedPlugins(): PluginInstance[];
1649
+ /**
1650
+ * 目录名别名兜底(小白用户实测踩坑):`plugin list` 显示目录名,但命令路由
1651
+ * 按 site.name 匹配——两者不一致时(如目录 alibaba-1688、site 名 1688),
1652
+ * 用户按 list 显示的名字敲命令会 "Unknown command"。映射在 scanAndLoad/
1653
+ * loadPlugin 时通过加载前后 site 快照 diff 记录(instance.siteName 返回的是
1654
+ * 插件 id 而非 site 名,不可用)。
1655
+ */
1656
+ resolveSiteName(name: string): string | undefined;
1657
+ /** 记录一次插件加载引入的 site:加载前后快照 diff(同插件可注册多个 site) */
1658
+ private recordSiteAliases;
1647
1659
  getPluginContract(siteName: string, commandName?: string): PluginContract | PluginCommandContract | undefined;
1648
1660
  loadPlugin(pluginPath: string, id?: string): Promise<PluginInstance>;
1649
1661
  unloadPlugin(id: string): Promise<void>;
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ import {
48
48
  splitCommand,
49
49
  waitForAIResponse,
50
50
  waitForPage
51
- } from "./chunk-BROEUPL3.js";
51
+ } from "./chunk-LTLMDKWG.js";
52
52
  import {
53
53
  SessionRecorder
54
54
  } from "./chunk-IR6C24P7.js";
@@ -63,7 +63,7 @@ import {
63
63
  getBrowser,
64
64
  resetForTesting,
65
65
  saveSessionDiskMeta
66
- } from "./chunk-2Q3GQRUP.js";
66
+ } from "./chunk-PKXJST62.js";
67
67
  import "./chunk-JKVUFP3G.js";
68
68
  import {
69
69
  forwardCommandLog,
@@ -120,7 +120,7 @@ import {
120
120
  generateJSScript,
121
121
  generatePythonScript
122
122
  } from "./chunk-ETCO4SNK.js";
123
- import "./chunk-7OP2ISEY.js";
123
+ import "./chunk-UR3R5LWF.js";
124
124
  import "./chunk-GYB5BOSP.js";
125
125
  import "./chunk-MYH6FIBZ.js";
126
126
  import {
@@ -720,6 +720,9 @@ async function installFromMarketplace(pluginsDir, slug, options) {
720
720
  const realSlug = String(plugin.slug || slug);
721
721
  try {
722
722
  await downloadAndExtractMarketplaceTarball(baseUrl, realSlug, tmpDir, targetDir);
723
+ } catch (err) {
724
+ safeCleanup5(targetDir);
725
+ throw err;
723
726
  } finally {
724
727
  safeCleanup5(tmpDir);
725
728
  }
@@ -893,6 +896,7 @@ function ensureIndexFile(plugin, name, targetDir) {
893
896
  }
894
897
 
895
898
  // src/plugin/installer.ts
899
+ import { safeCleanup as safeCleanup6 } from "@dyyz1993/xcli-core";
896
900
  var PluginInstaller = class {
897
901
  pluginsDir;
898
902
  constructor(pluginsDir) {
@@ -918,31 +922,29 @@ var PluginInstaller = class {
918
922
  }
919
923
  mkdirSync4(targetDir, { recursive: true });
920
924
  const resolvedSource = type === "npm" ? await resolveNpmPackageWithFallback(source) : source;
925
+ const withCleanup = async (fn) => {
926
+ try {
927
+ const r = await fn();
928
+ await this.fixSharedDeps(targetDir);
929
+ ensurePluginDependencies(this.pluginsDir);
930
+ if (!existsSync6(resolve6(targetDir, "index.ts")) && !existsSync6(resolve6(targetDir, "index.js"))) {
931
+ throw new Error("installed package has no index.ts/js entry");
932
+ }
933
+ return r;
934
+ } catch (err) {
935
+ safeCleanup6(targetDir);
936
+ throw err;
937
+ }
938
+ };
921
939
  switch (type) {
922
940
  case "local":
923
- return await installFromLocal(source, name, targetDir).then(async (r) => {
924
- await this.fixSharedDeps(targetDir);
925
- ensurePluginDependencies(this.pluginsDir);
926
- return r;
927
- });
941
+ return await withCleanup(() => installFromLocal(source, name, targetDir));
928
942
  case "npm":
929
- return await installFromNpm(resolvedSource, name, targetDir).then(async (r) => {
930
- await this.fixSharedDeps(targetDir);
931
- ensurePluginDependencies(this.pluginsDir);
932
- return r;
933
- });
943
+ return await withCleanup(() => installFromNpm(resolvedSource, name, targetDir));
934
944
  case "git":
935
- return await installFromGit(source, name, targetDir).then(async (r) => {
936
- await this.fixSharedDeps(targetDir);
937
- ensurePluginDependencies(this.pluginsDir);
938
- return r;
939
- });
945
+ return await withCleanup(() => installFromGit(source, name, targetDir));
940
946
  case "url":
941
- return await installFromUrl(source, name, targetDir).then(async (r) => {
942
- await this.fixSharedDeps(targetDir);
943
- ensurePluginDependencies(this.pluginsDir);
944
- return r;
945
- });
947
+ return await withCleanup(() => installFromUrl(source, name, targetDir));
946
948
  }
947
949
  }
948
950
  /**
@@ -5912,7 +5914,14 @@ async function routeCommand(argvIn, stdinCommands) {
5912
5914
  default: {
5913
5915
  const loader = await getPluginLoader();
5914
5916
  const internalLoader = loader.getCore().loader;
5915
- const site = internalLoader.getSite(command);
5917
+ const resolvedSiteName = loader.resolveSiteName(command);
5918
+ const site = resolvedSiteName ? internalLoader.getSite(resolvedSiteName) : void 0;
5919
+ if (site && resolvedSiteName !== command) {
5920
+ if (mode !== "json" && mode !== "yaml") {
5921
+ process.stderr.write(`\u2139\uFE0F "${command}" \u7684\u547D\u4EE4\u540D\u662F "${resolvedSiteName}"\uFF08\u63D2\u4EF6\u76EE\u5F55\u540D\u4E0E\u7AD9\u70B9\u540D\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u6620\u5C04\uFF09
5922
+ `);
5923
+ }
5924
+ }
5916
5925
  if (!site) {
5917
5926
  const globalFlagSet = /* @__PURE__ */ new Set(["--session", "--cdp", "--json", "--yaml", "--help", "-h", "--version", "--output", "-o"]);
5918
5927
  const cleanParts = [];
@@ -6103,7 +6112,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
6103
6112
  const targetPage = pages[cmdTabIndex];
6104
6113
  await targetPage.bringToFront().catch(() => {
6105
6114
  });
6106
- const { setActivePage } = await import("./browser-Q67BJTI2.js");
6115
+ const { setActivePage } = await import("./browser-DFM2SQDM.js");
6107
6116
  setActivePage(session, targetPage);
6108
6117
  }
6109
6118
  }
@@ -6368,7 +6377,7 @@ async function handleRemote(args, options, mode) {
6368
6377
  }
6369
6378
  }
6370
6379
  async function startMcpServer() {
6371
- const { startMcpStdio } = await import("./server-NPINOVJS.js");
6380
+ const { startMcpStdio } = await import("./server-Y5VVPRC6.js");
6372
6381
  startMcpStdio();
6373
6382
  }
6374
6383
 
@@ -9283,7 +9292,7 @@ var DataCollector = class {
9283
9292
  return results;
9284
9293
  }
9285
9294
  async createBrowserContext() {
9286
- const { launch } = await import("./cdp-driver-WWO2UZDR.js");
9295
+ const { launch } = await import("./cdp-driver-57QV7C5O.js");
9287
9296
  const { browser } = await launch({
9288
9297
  headless: true,
9289
9298
  args: ["--no-sandbox", "--disable-setuid-sandbox"]
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getPluginLoader,
3
3
  resetPluginLoader
4
- } from "./chunk-A3MORKYR.js";
4
+ } from "./chunk-OSZNJT4U.js";
5
5
  import "./chunk-KFQGP6VL.js";
6
6
  export {
7
7
  getPluginLoader,
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  executeChain,
3
3
  executeCommand
4
- } from "./chunk-A7RZYSWM.js";
4
+ } from "./chunk-SUHS2EWV.js";
5
5
  import "./chunk-JKVUFP3G.js";
6
6
  import "./chunk-3OD76SUE.js";
7
7
  import "./chunk-ZTHE5RBZ.js";
8
8
  import "./chunk-Q2DFJQGS.js";
9
- import "./chunk-IFKJO2UR.js";
9
+ import "./chunk-X735U7G7.js";
10
10
  import "./chunk-GYB5BOSP.js";
11
11
  import "./chunk-MYH6FIBZ.js";
12
12
  import "./chunk-GDKLH7ZY.js";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  executeChain,
3
3
  executeCommand
4
- } from "./chunk-BROEUPL3.js";
5
- import "./chunk-2Q3GQRUP.js";
4
+ } from "./chunk-LTLMDKWG.js";
5
+ import "./chunk-PKXJST62.js";
6
6
  import "./chunk-JKVUFP3G.js";
7
7
  import "./chunk-3OD76SUE.js";
8
8
  import "./chunk-ZTHE5RBZ.js";
9
9
  import "./chunk-Q2DFJQGS.js";
10
- import "./chunk-7OP2ISEY.js";
10
+ import "./chunk-UR3R5LWF.js";
11
11
  import "./chunk-GYB5BOSP.js";
12
12
  import "./chunk-MYH6FIBZ.js";
13
13
  import "./chunk-GDKLH7ZY.js";
@@ -61,7 +61,7 @@ var SessionReplayer = class {
61
61
  if (this.opts.page) {
62
62
  this.page = this.opts.page;
63
63
  } else if (this.opts.cdpUrl) {
64
- const { launch } = await import("./cdp-driver-DC7EIUG5.js");
64
+ const { launch } = await import("./cdp-driver-BFGFAVAN.js");
65
65
  const { browser } = await launch({ cdpEndpoint: this.opts.cdpUrl });
66
66
  let contexts = browser.contexts();
67
67
  for (let i = 0; i < 10 && contexts.length === 0; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "1.24.0",
3
+ "version": "1.24.2",
4
4
  "description": "Browser automation CLI for web scraping, headless browsing, SEO analysis, and AI agent workflows. A command-line alternative to Playwright, Puppeteer, and Selenium.",
5
5
  "type": "module",
6
6
  "bin": {