browser-debugger-cli 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/bdg/SKILL.md +268 -0
- package/README.md +15 -1
- package/dist/commands/dom/a11y.js +2 -1
- package/dist/commands/dom/formInteraction.js +56 -25
- package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
- package/dist/commands/dom/helpers/keyAttributes.js +54 -0
- package/dist/commands/dom/helpers/query.d.ts +1 -1
- package/dist/commands/dom/helpers/query.js +66 -19
- package/dist/commands/dom/helpers/runElementCommand.js +4 -3
- package/dist/commands/dom/helpers/screenshot.js +85 -12
- package/dist/commands/dom/index.d.ts +1 -0
- package/dist/commands/dom/index.js +8 -3
- package/dist/commands/dom/inspect.d.ts +15 -0
- package/dist/commands/dom/inspect.js +82 -0
- package/dist/commands/dom/layout.js +2 -2
- package/dist/commands/dom/listeners.js +2 -2
- package/dist/commands/dom/semanticUtils.d.ts +14 -1
- package/dist/commands/dom/semanticUtils.js +44 -3
- package/dist/commands/installSkill.d.ts +20 -0
- package/dist/commands/installSkill.js +87 -0
- package/dist/commands/network/list.js +13 -2
- package/dist/commands/optionBehaviors.js +48 -6
- package/dist/commands/page.d.ts +1 -1
- package/dist/commands/page.js +62 -3
- package/dist/commands/shared/commonOptions.d.ts +4 -0
- package/dist/commands/shared/commonOptions.js +9 -0
- package/dist/commands/shared/optionTypes.d.ts +21 -0
- package/dist/commands/shared/startHelpers.d.ts +66 -0
- package/dist/commands/shared/startHelpers.js +91 -10
- package/dist/commands/shared/validation.d.ts +11 -0
- package/dist/commands/shared/validation.js +16 -0
- package/dist/commands.js +3 -0
- package/dist/daemon/launcher.d.ts +8 -1
- package/dist/daemon/launcher.js +3 -1
- package/dist/daemon/session/Session.d.ts +7 -0
- package/dist/daemon/session/Session.js +23 -1
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +65 -9
- package/dist/daemon/session/interactions.d.ts +18 -5
- package/dist/daemon/session/interactions.js +22 -12
- package/dist/daemon.js +3565 -329
- package/dist/errors/messages.d.ts +85 -0
- package/dist/errors/messages.js +128 -1
- package/dist/index.js +2151 -960
- package/dist/ipc/client.d.ts +9 -0
- package/dist/ipc/client.js +13 -0
- package/dist/ipc/protocol/commands.d.ts +56 -1
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +35 -2
- package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
- package/dist/ipc/protocol/inspectTypes.js +10 -0
- package/dist/runtime/dom/actionEffects.d.ts +94 -15
- package/dist/runtime/dom/actionEffects.js +173 -27
- package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -14
- package/dist/runtime/dom/actionEffectsScripts.js +224 -32
- package/dist/runtime/dom/elementInfo.d.ts +26 -0
- package/dist/runtime/dom/elementInfo.js +65 -0
- package/dist/runtime/dom/eventListeners.js +14 -4
- package/dist/runtime/dom/formFillHelpers/fill.d.ts +3 -4
- package/dist/runtime/dom/formFillHelpers/fill.js +77 -28
- package/dist/runtime/dom/frameSelection.d.ts +11 -0
- package/dist/runtime/dom/frameSelection.js +20 -1
- package/dist/runtime/dom/frames.d.ts +38 -5
- package/dist/runtime/dom/frames.js +136 -21
- package/dist/runtime/dom/inspect.d.ts +28 -0
- package/dist/runtime/dom/inspect.js +557 -0
- package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
- package/dist/runtime/dom/inspectAllStyles.js +385 -0
- package/dist/runtime/dom/inspectCascade.d.ts +94 -0
- package/dist/runtime/dom/inspectCascade.js +371 -0
- package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
- package/dist/runtime/dom/inspectCascadeModel.js +232 -0
- package/dist/runtime/dom/inspectHints.d.ts +62 -0
- package/dist/runtime/dom/inspectHints.js +305 -0
- package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
- package/dist/runtime/dom/inspectLayoutModel.js +346 -0
- package/dist/runtime/dom/inspectModel.d.ts +74 -0
- package/dist/runtime/dom/inspectModel.js +184 -0
- package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
- package/dist/runtime/dom/inspectPaintModel.js +461 -0
- package/dist/runtime/dom/inspectRules.d.ts +37 -0
- package/dist/runtime/dom/inspectRules.js +101 -0
- package/dist/runtime/dom/inspectScripts.d.ts +132 -0
- package/dist/runtime/dom/inspectScripts.js +263 -0
- package/dist/runtime/dom/inspectTree.d.ts +40 -0
- package/dist/runtime/dom/inspectTree.js +134 -0
- package/dist/runtime/dom/inspectVariables.d.ts +33 -0
- package/dist/runtime/dom/inspectVariables.js +94 -0
- package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
- package/dist/runtime/dom/inspectWhyModel.js +134 -0
- package/dist/runtime/dom/layout.d.ts +5 -1
- package/dist/runtime/dom/layout.js +10 -3
- package/dist/runtime/dom/listenerPageScripts.d.ts +11 -5
- package/dist/runtime/dom/listenerPageScripts.js +95 -9
- package/dist/runtime/dom/listenerSummary.d.ts +4 -0
- package/dist/runtime/dom/listenerSummary.js +26 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +5 -0
- package/dist/runtime/dom/reactEventHelpers.js +12 -4
- package/dist/runtime/page/emulation.d.ts +20 -0
- package/dist/runtime/page/emulation.js +37 -0
- package/dist/telemetry/a11y.d.ts +10 -0
- package/dist/telemetry/a11y.js +78 -1
- package/dist/telemetry/console.d.ts +1 -0
- package/dist/telemetry/console.js +100 -5
- package/dist/telemetry/network.js +3 -1
- package/dist/types.d.ts +40 -0
- package/dist/ui/formatters/details.d.ts +8 -0
- package/dist/ui/formatters/details.js +59 -3
- package/dist/ui/formatters/dom.d.ts +2 -1
- package/dist/ui/formatters/dom.js +25 -9
- package/dist/ui/formatters/inspect.d.ts +39 -0
- package/dist/ui/formatters/inspect.js +596 -0
- package/dist/ui/formatters/installSkill.d.ts +11 -0
- package/dist/ui/formatters/installSkill.js +31 -0
- package/dist/ui/formatters/keyAttributes.d.ts +19 -0
- package/dist/ui/formatters/keyAttributes.js +84 -0
- package/dist/ui/formatters/layout.js +2 -2
- package/dist/ui/formatters/networkHeaders.d.ts +13 -0
- package/dist/ui/formatters/networkHeaders.js +23 -3
- package/dist/ui/formatters/networkList.d.ts +29 -1
- package/dist/ui/formatters/networkList.js +86 -20
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/formatting.d.ts +9 -0
- package/dist/ui/formatting.js +6 -3
- package/dist/ui/messages/commands.d.ts +123 -7
- package/dist/ui/messages/commands.js +181 -10
- package/dist/ui/messages/networkMessages.d.ts +14 -0
- package/dist/ui/messages/networkMessages.js +18 -0
- package/dist/ui/messages/session.d.ts +14 -0
- package/dist/ui/messages/session.js +20 -0
- package/dist/utils/async.d.ts +9 -0
- package/dist/utils/async.js +17 -0
- package/dist/utils/color.d.ts +84 -0
- package/dist/utils/color.js +376 -0
- package/dist/utils/cssValues.d.ts +109 -0
- package/dist/utils/cssValues.js +236 -0
- package/dist/utils/selectorFilters.d.ts +12 -0
- package/dist/utils/selectorFilters.js +29 -0
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -2197,6 +2197,7 @@ function truncateUrl(url, maxLength = 60) {
|
|
|
2197
2197
|
return `${host}/${rest.substring(0, start)}\u2026${rest.substring(rest.length - (room - start))}`;
|
|
2198
2198
|
}
|
|
2199
2199
|
function cutMiddle(text, maxLength) {
|
|
2200
|
+
if (text.length <= maxLength) return text;
|
|
2200
2201
|
const head = Math.ceil((maxLength - 1) / 2);
|
|
2201
2202
|
return `${text.substring(0, head)}\u2026${text.substring(text.length - (maxLength - 1 - head))}`;
|
|
2202
2203
|
}
|
|
@@ -2386,9 +2387,25 @@ function formReadinessMessage(summary) {
|
|
|
2386
2387
|
if (summary.requiredTotal > 0 || summary.emptyFieldLabels.length === 0) return "READY to submit";
|
|
2387
2388
|
return `READY to submit (no field is marked required; empty: ${fieldLabelList(summary.emptyFieldLabels)})`;
|
|
2388
2389
|
}
|
|
2389
|
-
function actionStatusLine(done,
|
|
2390
|
-
if (noEffect) return `\u26A0 ${done} (${NO_VISIBLE_EFFECT})`;
|
|
2391
|
-
|
|
2390
|
+
function actionStatusLine(done, state) {
|
|
2391
|
+
if (state.noEffect) return `\u26A0 ${done} (${NO_VISIBLE_EFFECT})`;
|
|
2392
|
+
const notes = [state.warned && "with warnings", state.stillChanging && "page still changing"];
|
|
2393
|
+
const shown = notes.filter((note) => typeof note === "string");
|
|
2394
|
+
return shown.length > 0 ? `\u26A0 ${done} (${shown.join("; ")})` : `\u2713 ${done}`;
|
|
2395
|
+
}
|
|
2396
|
+
function stillChangingNote(action, pending) {
|
|
2397
|
+
const parts = [
|
|
2398
|
+
pending.requests !== void 0 && `${pluralize(pending.requests, "request")} pending`,
|
|
2399
|
+
pending.navigation && "a new page still loading",
|
|
2400
|
+
pending.loading !== void 0 && `loading indicator ${pending.loading} shown`,
|
|
2401
|
+
pending.domChanging && "DOM still changing",
|
|
2402
|
+
pending.busy && "page busy running a script"
|
|
2403
|
+
].filter((part) => typeof part === "string");
|
|
2404
|
+
const wait = sessionCommand("bdg dom wait <selector>");
|
|
2405
|
+
return `The page was still changing when the ${action} returned (${parts.join(", ")}); wait for the result with ${wait}`;
|
|
2406
|
+
}
|
|
2407
|
+
function shownElementText(element) {
|
|
2408
|
+
return `${element.element} "${element.text}"`;
|
|
2392
2409
|
}
|
|
2393
2410
|
function pageNavigationText(navigation) {
|
|
2394
2411
|
if (navigation.sameDocument) return `URL changed to ${navigation.url} (same document)`;
|
|
@@ -2466,7 +2483,7 @@ function layoutPositionLabel(element, viewport) {
|
|
|
2466
2483
|
return note ? `${label} (${note})` : label;
|
|
2467
2484
|
}
|
|
2468
2485
|
function colorSchemeLabel(scheme, emulated) {
|
|
2469
|
-
const source = emulated ? "emulated
|
|
2486
|
+
const source = emulated ? "emulated" : "from the system setting";
|
|
2470
2487
|
return `prefers-color-scheme: ${scheme} (${source})`;
|
|
2471
2488
|
}
|
|
2472
2489
|
function sessionActiveLine(page) {
|
|
@@ -2485,6 +2502,32 @@ function layoutHeadline(count, listed, selector) {
|
|
|
2485
2502
|
function indexLayoutHeadline(target) {
|
|
2486
2503
|
return `Element at ${target} (page x,y and size in CSS px):`;
|
|
2487
2504
|
}
|
|
2505
|
+
function coverText(cover, transparent) {
|
|
2506
|
+
return transparent ? `under transparent ${cover} (clicks land on it)` : `covered by ${cover}`;
|
|
2507
|
+
}
|
|
2508
|
+
function inspectCascadeNote(reason) {
|
|
2509
|
+
return reason === "timeout" ? "CSS rules not read: the page's stylesheets took too long (hints wait 1 s; --rules and --why 5 s)" : "CSS rules not read: Chrome could not report the rules matching this element";
|
|
2510
|
+
}
|
|
2511
|
+
function inspectDarkThemeBadge() {
|
|
2512
|
+
return "[dark theme from system; --color-scheme light for light]";
|
|
2513
|
+
}
|
|
2514
|
+
function inspectVisibilityBadges(visibility) {
|
|
2515
|
+
const reason = (text) => text ? `: ${text}` : "";
|
|
2516
|
+
if (visibility.notRendered) {
|
|
2517
|
+
return [`[not rendered${reason(visibility.hidden?.replace(/^not rendered \((.*)\)$/, "$1"))}]`];
|
|
2518
|
+
}
|
|
2519
|
+
return [
|
|
2520
|
+
visibility.hidden && `[hidden: ${visibility.hidden}]`,
|
|
2521
|
+
visibility.offscreen && `[offscreen: ${visibility.offscreen}]`,
|
|
2522
|
+
visibility.coveredBy && `[${coverText(visibility.coveredBy, visibility.coverTransparent)}]`
|
|
2523
|
+
].filter((badge) => Boolean(badge));
|
|
2524
|
+
}
|
|
2525
|
+
function inspectedMatchAction(picked, index) {
|
|
2526
|
+
return picked === "first-visible" ? `inspected the first visible one ([${index}])` : "inspected the first";
|
|
2527
|
+
}
|
|
2528
|
+
function multipleMatchesWarning(count, action) {
|
|
2529
|
+
return `${count} elements match; ${action} (use --index or a more specific selector)`;
|
|
2530
|
+
}
|
|
2488
2531
|
function listenersHeadline(element, counts, context = {}) {
|
|
2489
2532
|
const index = context.index === void 0 ? "" : ` [${context.index}]`;
|
|
2490
2533
|
const frame = context.frame ? ` in ${context.frame}` : "";
|
|
@@ -2643,7 +2686,31 @@ function startCommandHelpMessage() {
|
|
|
2643
2686
|
""
|
|
2644
2687
|
);
|
|
2645
2688
|
}
|
|
2646
|
-
|
|
2689
|
+
function pageEmulateNothingError() {
|
|
2690
|
+
return {
|
|
2691
|
+
message: "Nothing to emulate",
|
|
2692
|
+
suggestion: "Give --viewport <WxH>, --color-scheme light|dark, or --reset, e.g. bdg page emulate --viewport 900x700"
|
|
2693
|
+
};
|
|
2694
|
+
}
|
|
2695
|
+
function pageEmulationLines(result) {
|
|
2696
|
+
const size = (v) => `${v.width}x${v.height}`;
|
|
2697
|
+
const { emulated } = result;
|
|
2698
|
+
return [
|
|
2699
|
+
["Viewport", emulated.viewport ? size(emulated.viewport) : "the browser window"],
|
|
2700
|
+
...result.viewport ? [["Layout", `${size(result.viewport)} (without scrollbars)`]] : [],
|
|
2701
|
+
[
|
|
2702
|
+
"Scheme",
|
|
2703
|
+
emulated.colorScheme ?? `system setting${result.colorScheme ? ` (${result.colorScheme})` : ""}`
|
|
2704
|
+
]
|
|
2705
|
+
];
|
|
2706
|
+
}
|
|
2707
|
+
function inspectAnimatingBadge(animating) {
|
|
2708
|
+
return `[animating: ${animating.join(", ")}; values are mid-way, inspect again]`;
|
|
2709
|
+
}
|
|
2710
|
+
function inspectMidTransitionNote() {
|
|
2711
|
+
return "(mid-transition: inspect again for the final value)";
|
|
2712
|
+
}
|
|
2713
|
+
var REQUIRED_FIELD_EMPTY_REASON, NO_VISIBLE_EFFECT, LAYOUT_REASONS, SCROLL_LOCKED_PREFIX, VIEWPORT_POSITION_HINTS, INSPECT_OUTPUT_LEGEND, COLLAPSED_LISTENERS_HEADING, NO_LISTENERS_HINT, NOTE_TYPES_SHOWN, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, PAGE_ACTION_DONE, PAGE_INFO_DESCRIPTION, PAGE_EMULATE_DESCRIPTION, PAGE_ACTION_DESCRIPTIONS, CLICK_RESULT_WAIT_HELP, WAIT_HELP_EXAMPLES, FRAME_URL_MAX_LENGTH;
|
|
2647
2714
|
var init_commands = __esm({
|
|
2648
2715
|
"src/ui/messages/commands.ts"() {
|
|
2649
2716
|
"use strict";
|
|
@@ -2674,6 +2741,24 @@ var init_commands = __esm({
|
|
|
2674
2741
|
right: "right of viewport",
|
|
2675
2742
|
hidden: "hidden"
|
|
2676
2743
|
};
|
|
2744
|
+
INSPECT_OUTPUT_LEGEND = `
|
|
2745
|
+
Output notation:
|
|
2746
|
+
WxH @x,y rendered border box size and page position (CSS px, no unit)
|
|
2747
|
+
m / p / b margin / padding / border widths, 1-4 values in CSS order (top right bottom left)
|
|
2748
|
+
in-parent distances to the parent's content edges (l t r b); sib: gaps to the sibling on each side
|
|
2749
|
+
scroll WxH the content (pseudo-elements too) is larger than the box
|
|
2750
|
+
16/24 font size / line height; 'webfont loaded' = drawn with a downloaded font;
|
|
2751
|
+
(rendered "X") = drawn with another font than declared (a fallback)
|
|
2752
|
+
contrast 4.47 WCAG ratio, rounded down, against the background behind the text
|
|
2753
|
+
(+N not rendered) children with display: none (or not in the layout)
|
|
2754
|
+
hints declarations on this element that have no effect, why, the fix and where they are
|
|
2755
|
+
('none': checked, nothing found)
|
|
2756
|
+
\u2190 sel (file:N) --rules: the declaration that sets the value (file:line, or file:line:column in
|
|
2757
|
+
minified files); 'over X': rules it beats; '= v': the value of a var() expression
|
|
2758
|
+
\u2713 / \u2717 --why: the winning declaration / ones it beats, highest precedence first;
|
|
2759
|
+
[0,2,0]: selector specificity (ids, classes, types); indented --name lines: where
|
|
2760
|
+
the winner's custom properties are set
|
|
2761
|
+
Sessions follow the system color scheme; start with --color-scheme light|dark to choose.`;
|
|
2677
2762
|
COLLAPSED_LISTENERS_HEADING = "Framework roots (one line per node; --all lists each listener):";
|
|
2678
2763
|
NO_LISTENERS_HINT = "Inline on\u2026 attributes and on\u2026 properties are included, and so are handlers frameworks delegate to ancestors (React, jQuery)";
|
|
2679
2764
|
NOTE_TYPES_SHOWN = 5;
|
|
@@ -2683,6 +2768,12 @@ var init_commands = __esm({
|
|
|
2683
2768
|
right: "Right-clicked",
|
|
2684
2769
|
hover: "Hovered"
|
|
2685
2770
|
};
|
|
2771
|
+
POINTER_ACTION_NOUN = {
|
|
2772
|
+
click: "click",
|
|
2773
|
+
double: "double-click",
|
|
2774
|
+
right: "right-click",
|
|
2775
|
+
hover: "hover"
|
|
2776
|
+
};
|
|
2686
2777
|
PAGE_ACTION_DONE = {
|
|
2687
2778
|
navigate: "Navigated",
|
|
2688
2779
|
reload: "Reloaded",
|
|
@@ -2690,6 +2781,7 @@ var init_commands = __esm({
|
|
|
2690
2781
|
forward: "Went forward"
|
|
2691
2782
|
};
|
|
2692
2783
|
PAGE_INFO_DESCRIPTION = "Show the URL and title of the session page";
|
|
2784
|
+
PAGE_EMULATE_DESCRIPTION = "Change the viewport or color scheme mid-session (like --viewport and --color-scheme at start), or --reset both";
|
|
2693
2785
|
PAGE_ACTION_DESCRIPTIONS = {
|
|
2694
2786
|
reload: "Reload the page",
|
|
2695
2787
|
back: "Go back one page (like the browser button)",
|
|
@@ -2698,7 +2790,8 @@ var init_commands = __esm({
|
|
|
2698
2790
|
CLICK_RESULT_WAIT_HELP = joinLines(
|
|
2699
2791
|
"",
|
|
2700
2792
|
"Waits only for the requests the action starts (150 ms idle, up to 2 s), not for",
|
|
2701
|
-
"results the page shows later (timers, spinners, animations)
|
|
2793
|
+
"results the page shows later (timers, spinners, animations); the result says",
|
|
2794
|
+
'"page still changing" when it saw such work pending. Wait for those with:',
|
|
2702
2795
|
" bdg dom wait '#result' --visible # or --text 'Saved', or '.spinner' --gone"
|
|
2703
2796
|
);
|
|
2704
2797
|
WAIT_HELP_EXAMPLES = joinLines(
|
|
@@ -3142,6 +3235,9 @@ function similarSelectorsLine(kind, names) {
|
|
|
3142
3235
|
const plural = names.length === 1 ? what : kind === "id" ? "ids" : "classes";
|
|
3143
3236
|
return `Did you mean ${names.map((name) => sigil + name).join(", ")}? (similar ${plural} on the page)`;
|
|
3144
3237
|
}
|
|
3238
|
+
function shadowBoundaryLine(host) {
|
|
3239
|
+
return `${host.compound} hosts a shadow root, which a selector cannot cross into; bdg searches open shadow roots itself: use "${host.rest}"`;
|
|
3240
|
+
}
|
|
3145
3241
|
function noNodesFoundError(selector, context = {}) {
|
|
3146
3242
|
const hiddenMatches = context.hidden ?? 0;
|
|
3147
3243
|
const hidden = hiddenMatches > 0 ? hiddenMatches === 1 ? "1 element matches without :visible but is hidden (check with bdg dom layout). " : `${hiddenMatches} elements match without :visible but are hidden (check with bdg dom layout). ` : "";
|
|
@@ -3151,6 +3247,7 @@ function noNodesFoundError(selector, context = {}) {
|
|
|
3151
3247
|
suggestion: withLoadingHint(
|
|
3152
3248
|
joinLines(
|
|
3153
3249
|
context.similar,
|
|
3250
|
+
context.shadowHost && shadowBoundaryLine(context.shadowHost),
|
|
3154
3251
|
`${hidden}Verify the CSS selector is correct.`,
|
|
3155
3252
|
note ? note : void 0
|
|
3156
3253
|
),
|
|
@@ -3261,6 +3358,18 @@ function formInIframeError(iframeUrl, crossOrigin) {
|
|
|
3261
3358
|
suggestion: crossOrigin ? "Cross-origin iframes cannot be read or controlled; open the iframe URL directly: bdg <iframe url>" : 'dom form lists forms of the main document only; its fields are reachable directly: bdg dom query "input, select, textarea", then bdg dom fill <index> <value>'
|
|
3262
3359
|
};
|
|
3263
3360
|
}
|
|
3361
|
+
function skillSourceMissingError(source) {
|
|
3362
|
+
return {
|
|
3363
|
+
message: `The bdg skill is missing from the installed package (${source})`,
|
|
3364
|
+
suggestion: "Reinstall bdg: npm install -g browser-debugger-cli"
|
|
3365
|
+
};
|
|
3366
|
+
}
|
|
3367
|
+
function skillWriteFailedError(path6, reason) {
|
|
3368
|
+
return {
|
|
3369
|
+
message: `Could not write ${path6}: ${reason}`,
|
|
3370
|
+
suggestion: "Check the permissions of that directory, or install for the other agent only (--claude or --agents)"
|
|
3371
|
+
};
|
|
3372
|
+
}
|
|
3264
3373
|
var LAUNCHED_CHROME_DESCRIPTION, A11Y_NAME_QUERY_EXAMPLE, SCOPED_FILTER_EXAMPLES, PLAYWRIGHT_ONLY_SYNTAX, MALFORMED_SELECTOR_DETAILS, SESSION_DIR_SUGGESTION, SCROLL_PROBLEMS, VIA_LABEL_SUFFIX, NAME_QUERY_PLACEHOLDER, LABEL_WITHOUT_CONTROL;
|
|
3265
3374
|
var init_messages = __esm({
|
|
3266
3375
|
"src/errors/messages.ts"() {
|
|
@@ -3853,6 +3962,9 @@ async function domScroll(params) {
|
|
|
3853
3962
|
async function pageNavigate(params) {
|
|
3854
3963
|
return sendCommand("page_navigate", params);
|
|
3855
3964
|
}
|
|
3965
|
+
async function pageEmulate(params) {
|
|
3966
|
+
return sendCommand("page_emulate", params);
|
|
3967
|
+
}
|
|
3856
3968
|
async function domFormDiscover() {
|
|
3857
3969
|
return sendCommand("dom_form_discover", {});
|
|
3858
3970
|
}
|
|
@@ -3862,6 +3974,9 @@ async function domListeners(params) {
|
|
|
3862
3974
|
async function domLayout(params) {
|
|
3863
3975
|
return sendCommand("dom_layout", params);
|
|
3864
3976
|
}
|
|
3977
|
+
async function domInspect(params) {
|
|
3978
|
+
return sendCommand("dom_inspect", params);
|
|
3979
|
+
}
|
|
3865
3980
|
async function domWait(params) {
|
|
3866
3981
|
return sendCommand(
|
|
3867
3982
|
"dom_wait",
|
|
@@ -3888,7 +4003,7 @@ function textPreview2(text, length = PREVIEW_LENGTH) {
|
|
|
3888
4003
|
const characters = Array.from(collapsed);
|
|
3889
4004
|
return characters.length > length ? characters.slice(0, length).join("") + "..." : collapsed;
|
|
3890
4005
|
}
|
|
3891
|
-
var PREVIEW_LENGTH, ELEMENT_TEXT_LENGTH, WITHOUT_DECORATIONS_JS, ELEMENT_TEXT_JS, ELEMENT_DESCRIPTION_JS, SIBLING_POSITION_JS, ELEMENT_IDENTITY_JS, ELEMENT_CONTEXT_JS;
|
|
4006
|
+
var PREVIEW_LENGTH, ELEMENT_TEXT_LENGTH, WITHOUT_DECORATIONS_JS, ELEMENT_TEXT_JS, MASKED_VALUE, SENSITIVE_FIELD_JS, ELEMENT_STATE_JS, ELEMENT_DESCRIPTION_JS, SIBLING_POSITION_JS, ELEMENT_IDENTITY_JS, ELEMENT_CONTEXT_JS;
|
|
3892
4007
|
var init_elementInfo = __esm({
|
|
3893
4008
|
"src/runtime/dom/elementInfo.ts"() {
|
|
3894
4009
|
"use strict";
|
|
@@ -3933,6 +4048,48 @@ var init_elementInfo = __esm({
|
|
|
3933
4048
|
if (!parent || rendered(parent)) start += walker.currentNode.data;
|
|
3934
4049
|
}
|
|
3935
4050
|
return withoutDecorations(el, start);
|
|
4051
|
+
}`;
|
|
4052
|
+
MASKED_VALUE = "\u2022\u2022\u2022\u2022";
|
|
4053
|
+
SENSITIVE_FIELD_JS = `(el) => {
|
|
4054
|
+
const autocomplete = el.getAttribute('autocomplete') || '';
|
|
4055
|
+
if (/(^|\\s)(cc-[a-z-]+|one-time-code|current-password|new-password)(\\s|$)/i.test(autocomplete)) return true;
|
|
4056
|
+
if (el.type === 'password' || /^password$/i.test(el.getAttribute('type') || '')) return true;
|
|
4057
|
+
const names = [el.getAttribute('name'), el.id, autocomplete].join(' ');
|
|
4058
|
+
if (/passw|passwd|pwd|passcode|(^|[^a-z])otp([^a-z]|$)|cvv|cvc/i.test(names)) return true;
|
|
4059
|
+
try {
|
|
4060
|
+
const security = el.ownerDocument.defaultView.getComputedStyle(el).getPropertyValue('-webkit-text-security');
|
|
4061
|
+
return Boolean(security) && security !== 'none';
|
|
4062
|
+
} catch (e) {
|
|
4063
|
+
return false;
|
|
4064
|
+
}
|
|
4065
|
+
}`;
|
|
4066
|
+
ELEMENT_STATE_JS = `(el) => {
|
|
4067
|
+
const isSensitive = ${SENSITIVE_FIELD_JS};
|
|
4068
|
+
const mask = (value) => (value ? '${MASKED_VALUE}' : '');
|
|
4069
|
+
const guarded = (state) => {
|
|
4070
|
+
if (!isSensitive(el)) return state;
|
|
4071
|
+
const result = { ...state, sensitive: true };
|
|
4072
|
+
if ('value' in result) result.value = mask(result.value);
|
|
4073
|
+
if ('selected' in result) result.selected = mask(result.selected);
|
|
4074
|
+
return result;
|
|
4075
|
+
};
|
|
4076
|
+
switch (el.localName) {
|
|
4077
|
+
case 'input':
|
|
4078
|
+
if (el.type === 'hidden') return { type: 'hidden' };
|
|
4079
|
+
return guarded(
|
|
4080
|
+
/^(checkbox|radio)$/.test(el.type)
|
|
4081
|
+
? { type: el.type, checked: el.checked, value: el.getAttribute('value') || '' }
|
|
4082
|
+
: { type: el.type, value: el.value }
|
|
4083
|
+
);
|
|
4084
|
+
case 'textarea':
|
|
4085
|
+
return guarded({ value: el.value });
|
|
4086
|
+
case 'select':
|
|
4087
|
+
return guarded({ selected: Array.from(el.selectedOptions || [], (option) => option.label).join(', ') });
|
|
4088
|
+
case 'button':
|
|
4089
|
+
return el.form ? { type: el.type } : {};
|
|
4090
|
+
default:
|
|
4091
|
+
return {};
|
|
4092
|
+
}
|
|
3936
4093
|
}`;
|
|
3937
4094
|
ELEMENT_DESCRIPTION_JS = `(node) => node.tagName.toLowerCase() +
|
|
3938
4095
|
(node.id ? '#' + node.id : '') +
|
|
@@ -4011,6 +4168,42 @@ var init_elementInfo = __esm({
|
|
|
4011
4168
|
}
|
|
4012
4169
|
});
|
|
4013
4170
|
|
|
4171
|
+
// src/commands/dom/helpers/keyAttributes.ts
|
|
4172
|
+
function keyAttributes(tag, attributes, state = {}) {
|
|
4173
|
+
const names = ATTRIBUTES_BY_TAG[tag];
|
|
4174
|
+
if (!names) return void 0;
|
|
4175
|
+
const result = {};
|
|
4176
|
+
const type = state.type ?? attributes["type"];
|
|
4177
|
+
if (type) result["type"] = type;
|
|
4178
|
+
for (const name of names) {
|
|
4179
|
+
const value = attributes[name];
|
|
4180
|
+
if (value) result[name] = value;
|
|
4181
|
+
}
|
|
4182
|
+
if (type === "hidden") return result;
|
|
4183
|
+
const secret = state.sensitive === true || type === "password";
|
|
4184
|
+
if (state.value) result["value"] = secret ? MASKED_VALUE : state.value;
|
|
4185
|
+
if (state.checked !== void 0) result["checked"] = state.checked;
|
|
4186
|
+
if (state.selected) result["selected"] = secret ? MASKED_VALUE : state.selected;
|
|
4187
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
4188
|
+
}
|
|
4189
|
+
var ATTRIBUTES_BY_TAG;
|
|
4190
|
+
var init_keyAttributes = __esm({
|
|
4191
|
+
"src/commands/dom/helpers/keyAttributes.ts"() {
|
|
4192
|
+
"use strict";
|
|
4193
|
+
init_elementInfo();
|
|
4194
|
+
ATTRIBUTES_BY_TAG = {
|
|
4195
|
+
img: ["src", "alt"],
|
|
4196
|
+
a: ["href"],
|
|
4197
|
+
input: ["name", "placeholder"],
|
|
4198
|
+
textarea: ["name", "placeholder"],
|
|
4199
|
+
button: ["name"],
|
|
4200
|
+
select: ["name"],
|
|
4201
|
+
iframe: ["src"],
|
|
4202
|
+
form: ["action", "method"]
|
|
4203
|
+
};
|
|
4204
|
+
}
|
|
4205
|
+
});
|
|
4206
|
+
|
|
4014
4207
|
// src/runtime/dom/elementGeometry.ts
|
|
4015
4208
|
function edgesOf(box) {
|
|
4016
4209
|
return { left: box.x, top: box.y, right: box.x + box.width, bottom: box.y + box.height };
|
|
@@ -4397,6 +4590,23 @@ function splitSelectorList(selector) {
|
|
|
4397
4590
|
parts.push(selector.slice(partStart));
|
|
4398
4591
|
return parts;
|
|
4399
4592
|
}
|
|
4593
|
+
function leadingCompounds(selector) {
|
|
4594
|
+
if (splitSelectorList(selector).length > 1) return [];
|
|
4595
|
+
const text = selector.trim();
|
|
4596
|
+
const found = [];
|
|
4597
|
+
let start = 0;
|
|
4598
|
+
scan(text, (index, depth) => {
|
|
4599
|
+
const char = text[index] ?? "";
|
|
4600
|
+
if (depth > 0 || !/[\s>+~]/.test(char)) return index;
|
|
4601
|
+
const compound = text.slice(start, index).trim();
|
|
4602
|
+
let next = index;
|
|
4603
|
+
while (next + 1 < text.length && /[\s>+~]/.test(text[next + 1] ?? "")) next++;
|
|
4604
|
+
if (compound) found.push({ compound, rest: text.slice(next + 1).trim() });
|
|
4605
|
+
start = next + 1;
|
|
4606
|
+
return next;
|
|
4607
|
+
});
|
|
4608
|
+
return found;
|
|
4609
|
+
}
|
|
4400
4610
|
function scan(text, visit) {
|
|
4401
4611
|
let depth = 0;
|
|
4402
4612
|
for (let i = 0; i < text.length; i++) {
|
|
@@ -4864,15 +5074,24 @@ async function withSelection(selector, use) {
|
|
|
4864
5074
|
async function noMatchesError(selector) {
|
|
4865
5075
|
return noNodesFoundError(selector, await noMatchContext(selector));
|
|
4866
5076
|
}
|
|
5077
|
+
function shadowHostJS(compounds) {
|
|
5078
|
+
const plain = compounds.slice(0, MAX_HOST_COMPOUNDS).map((entry) => [withoutFilters(entry.compound), withoutFilters(entry.rest)]);
|
|
5079
|
+
return `${JSON.stringify(plain)}.findIndex(([host, rest]) => { try { const deep = ${DEEP_QUERY_JS}; return deep(host, null).some((el) => el.shadowRoot) && deep(rest, null).length > 0; } catch (e) { return false; } })`;
|
|
5080
|
+
}
|
|
5081
|
+
function withoutFilters(selector) {
|
|
5082
|
+
return selector.replace(/:visible\b|:(has-text|text-is)\((?:"[^"]*"|'[^']*'|[^)"'])*\)/g, "").trim() || "*";
|
|
5083
|
+
}
|
|
4867
5084
|
async function noMatchContext(selector) {
|
|
4868
5085
|
const parts = parseSelectorFilters(selector);
|
|
4869
5086
|
const unfiltered = parts && withoutVisibleFilters(parts);
|
|
4870
5087
|
const single = parseSingleNameSelector(selector);
|
|
4871
5088
|
const hidden = unfiltered ? `(() => { try { return (${DEEP_QUERY_JS})(${JSON.stringify(selector)}, ${JSON.stringify(unfiltered)}).length; } catch (e) { return 0; } })()` : "0";
|
|
4872
5089
|
const names = single ? pageNamesJS(single.kind) : "[]";
|
|
5090
|
+
const compounds = leadingCompounds(selector);
|
|
5091
|
+
const shadowHost = compounds.length > 0 ? shadowHostJS(compounds) : "-1";
|
|
4873
5092
|
try {
|
|
4874
5093
|
const evaluated = await callCDP("Runtime.evaluate", {
|
|
4875
|
-
expression: `({ hidden: ${hidden}, readyState: document.readyState, unsearched: ${UNSEARCHED_CONTENT_JS}, names: ${names} })`,
|
|
5094
|
+
expression: `({ hidden: ${hidden}, readyState: document.readyState, unsearched: ${UNSEARCHED_CONTENT_JS}, names: ${names}, shadowHost: ${shadowHost} })`,
|
|
4876
5095
|
returnByValue: true
|
|
4877
5096
|
});
|
|
4878
5097
|
const { result } = evaluated.data?.result ?? {};
|
|
@@ -4893,7 +5112,8 @@ async function noMatchContext(selector) {
|
|
|
4893
5112
|
embeds: value.unsearched.embeds === true
|
|
4894
5113
|
}
|
|
4895
5114
|
},
|
|
4896
|
-
...similar && { similar }
|
|
5115
|
+
...similar && { similar },
|
|
5116
|
+
...typeof value.shadowHost === "number" && compounds[value.shadowHost] && { shadowHost: compounds[value.shadowHost] }
|
|
4897
5117
|
};
|
|
4898
5118
|
} catch (error) {
|
|
4899
5119
|
log15.debug(`Could not read the page after no match: ${getErrorMessage(error)}`);
|
|
@@ -4924,6 +5144,7 @@ async function elementsWithDetails(arrayObjectId) {
|
|
|
4924
5144
|
backendNodeId,
|
|
4925
5145
|
context: details?.context ?? "",
|
|
4926
5146
|
text: details?.text ?? "",
|
|
5147
|
+
state: details?.state ?? {},
|
|
4927
5148
|
...viewportHint(details?.geometry, viewport)
|
|
4928
5149
|
}
|
|
4929
5150
|
];
|
|
@@ -4994,17 +5215,20 @@ async function queryDOMElements(selector) {
|
|
|
4994
5215
|
log15.debug(`Querying ${elements.length} elements with selector: ${selector}`);
|
|
4995
5216
|
}
|
|
4996
5217
|
const nodes = await mapConcurrently(elements, async (element, index) => {
|
|
4997
|
-
const { backendNodeId, context, text, inViewport, clippedBy } = element;
|
|
5218
|
+
const { backendNodeId, context, text, state, inViewport, clippedBy } = element;
|
|
4998
5219
|
const desc = await describeNode({ backendNodeId });
|
|
4999
5220
|
if (!desc) return { index, nodeId: 0 };
|
|
5000
5221
|
const attributes = unpackAttributes(desc.attributes);
|
|
5001
5222
|
const classes = attributes["class"]?.split(/\s+/).filter(Boolean);
|
|
5002
5223
|
const preview = textPreview2(text);
|
|
5224
|
+
const tag = desc.nodeName.toLowerCase();
|
|
5225
|
+
const keys = keyAttributes(tag, attributes, state);
|
|
5003
5226
|
return {
|
|
5004
5227
|
index,
|
|
5005
5228
|
nodeId: desc.backendNodeId,
|
|
5006
|
-
tag
|
|
5229
|
+
tag,
|
|
5007
5230
|
...identifyingAttributes(attributes, desc.nodeName),
|
|
5231
|
+
...keys && { attributes: keys },
|
|
5008
5232
|
...classes && { classes },
|
|
5009
5233
|
...preview && { preview },
|
|
5010
5234
|
...context && { context },
|
|
@@ -5023,20 +5247,23 @@ function identifyingAttributes(attributes, nodeName) {
|
|
|
5023
5247
|
...value !== void 0 && { value }
|
|
5024
5248
|
};
|
|
5025
5249
|
}
|
|
5026
|
-
async function
|
|
5250
|
+
async function elementTextAndState(ref, full) {
|
|
5027
5251
|
const objectGroup = `bdg-text-${process.pid}-${++queryCount}`;
|
|
5028
5252
|
const resolved = await callCDP("DOM.resolveNode", { ...ref, objectGroup });
|
|
5029
5253
|
const objectId = resolved.data?.result?.object.objectId;
|
|
5030
|
-
if (!objectId) return "";
|
|
5254
|
+
if (!objectId) return { text: "", state: {} };
|
|
5031
5255
|
try {
|
|
5032
5256
|
const response = await callCDP("Runtime.callFunctionOn", {
|
|
5033
5257
|
objectId,
|
|
5034
|
-
functionDeclaration: `function (full) { return (${ELEMENT_TEXT_JS})(this, full); }`,
|
|
5258
|
+
functionDeclaration: `function (full) { return { text: (${ELEMENT_TEXT_JS})(this, full), state: (${ELEMENT_STATE_JS})(this) }; }`,
|
|
5035
5259
|
arguments: [{ value: full }],
|
|
5036
5260
|
returnByValue: true
|
|
5037
5261
|
});
|
|
5038
5262
|
const value = response.data?.result?.result?.value;
|
|
5039
|
-
return
|
|
5263
|
+
return {
|
|
5264
|
+
text: typeof value?.text === "string" ? value.text : "",
|
|
5265
|
+
state: value?.state ?? {}
|
|
5266
|
+
};
|
|
5040
5267
|
} finally {
|
|
5041
5268
|
await callCDP("Runtime.releaseObjectGroup", { objectGroup });
|
|
5042
5269
|
}
|
|
@@ -5049,13 +5276,18 @@ async function getDomContext(ref, options = {}) {
|
|
|
5049
5276
|
return null;
|
|
5050
5277
|
}
|
|
5051
5278
|
const full = options.full === true;
|
|
5052
|
-
const
|
|
5053
|
-
const
|
|
5279
|
+
const attributes = unpackAttributes(desc.attributes);
|
|
5280
|
+
const classes = attributes["class"]?.split(/\s+/).filter(Boolean);
|
|
5281
|
+
const { text, state } = await elementTextAndState(ref, full);
|
|
5054
5282
|
const preview = textPreview2(text);
|
|
5055
5283
|
const longer = textPreview2(text, full ? Number.POSITIVE_INFINITY : ELEMENT_TEXT_LENGTH);
|
|
5284
|
+
const tag = desc.nodeName.toLowerCase();
|
|
5285
|
+
const keys = keyAttributes(tag, attributes, state);
|
|
5056
5286
|
return {
|
|
5057
|
-
tag
|
|
5287
|
+
tag,
|
|
5058
5288
|
...classes && classes.length > 0 && { classes },
|
|
5289
|
+
...keys && { attributes: keys },
|
|
5290
|
+
...state.sensitive && { sensitive: true },
|
|
5059
5291
|
...preview && { preview },
|
|
5060
5292
|
...longer !== preview && { text: longer },
|
|
5061
5293
|
...!preview && await childElements(ref)
|
|
@@ -5182,10 +5414,11 @@ async function assertNodeAttached(backendNodeId, source) {
|
|
|
5182
5414
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
|
|
5183
5415
|
}
|
|
5184
5416
|
}
|
|
5185
|
-
var log15, CDP_CONCURRENCY_LIMIT, queryCount, VIEWPORT_HINT_LIMIT, ELEMENT_DETAILS_FUNCTION, CHILDREN_LISTED;
|
|
5417
|
+
var log15, CDP_CONCURRENCY_LIMIT, queryCount, MAX_HOST_COMPOUNDS, VIEWPORT_HINT_LIMIT, ELEMENT_DETAILS_FUNCTION, CHILDREN_LISTED;
|
|
5186
5418
|
var init_query = __esm({
|
|
5187
5419
|
"src/commands/dom/helpers/query.ts"() {
|
|
5188
5420
|
"use strict";
|
|
5421
|
+
init_keyAttributes();
|
|
5189
5422
|
init_errors3();
|
|
5190
5423
|
init_messages();
|
|
5191
5424
|
init_client();
|
|
@@ -5202,14 +5435,16 @@ var init_query = __esm({
|
|
|
5202
5435
|
log15 = createLogger("dom");
|
|
5203
5436
|
CDP_CONCURRENCY_LIMIT = 10;
|
|
5204
5437
|
queryCount = 0;
|
|
5438
|
+
MAX_HOST_COMPOUNDS = 4;
|
|
5205
5439
|
VIEWPORT_HINT_LIMIT = 100;
|
|
5206
5440
|
ELEMENT_DETAILS_FUNCTION = `function () {
|
|
5207
5441
|
const contextOf = ${ELEMENT_CONTEXT_JS};
|
|
5208
5442
|
const textOf = ${ELEMENT_TEXT_JS};
|
|
5209
5443
|
const geometryOf = ${ELEMENT_GEOMETRY_JS};
|
|
5444
|
+
const stateOf = ${ELEMENT_STATE_JS};
|
|
5210
5445
|
const read = (el, index) => {
|
|
5211
5446
|
try {
|
|
5212
|
-
return { context: contextOf(el), text: textOf(el), geometry: index < ${VIEWPORT_HINT_LIMIT} ? geometryOf(el) : null };
|
|
5447
|
+
return { context: contextOf(el), text: textOf(el), state: stateOf(el), geometry: index < ${VIEWPORT_HINT_LIMIT} ? geometryOf(el) : null };
|
|
5213
5448
|
} catch (e) {
|
|
5214
5449
|
return {};
|
|
5215
5450
|
}
|
|
@@ -5486,13 +5721,22 @@ async function restoreScrollPosition(position) {
|
|
|
5486
5721
|
returnByValue: true
|
|
5487
5722
|
});
|
|
5488
5723
|
}
|
|
5724
|
+
async function windowSize(viewport) {
|
|
5725
|
+
const response = await callCDP("Runtime.evaluate", {
|
|
5726
|
+
expression: "[window.innerWidth, window.innerHeight]",
|
|
5727
|
+
returnByValue: true
|
|
5728
|
+
});
|
|
5729
|
+
const value = response.data?.result?.result?.value;
|
|
5730
|
+
const [width, height] = Array.isArray(value) ? value : [];
|
|
5731
|
+
return {
|
|
5732
|
+
width: Math.round(width ?? viewport.clientWidth),
|
|
5733
|
+
height: Math.round(height ?? viewport.clientHeight)
|
|
5734
|
+
};
|
|
5735
|
+
}
|
|
5489
5736
|
async function useUnitPixelRatio(devicePixelRatio, viewport) {
|
|
5490
5737
|
if (devicePixelRatio === 1) return () => Promise.resolve();
|
|
5491
5738
|
const sessionViewport = readSessionMetadata()?.viewport;
|
|
5492
|
-
const size = sessionViewport ??
|
|
5493
|
-
width: Math.round(viewport.clientWidth),
|
|
5494
|
-
height: Math.round(viewport.clientHeight)
|
|
5495
|
-
};
|
|
5739
|
+
const size = sessionViewport ?? await windowSize(viewport);
|
|
5496
5740
|
await callCDP("Emulation.setDeviceMetricsOverride", viewportOverride(size, 1));
|
|
5497
5741
|
return async () => {
|
|
5498
5742
|
if (sessionViewport) {
|
|
@@ -5636,6 +5880,28 @@ async function capturePageScreenshot(outputPath, options = {}) {
|
|
|
5636
5880
|
}
|
|
5637
5881
|
return result;
|
|
5638
5882
|
}
|
|
5883
|
+
async function visibleViewport() {
|
|
5884
|
+
const metrics = (await callCDP("Page.getLayoutMetrics", {})).data?.result;
|
|
5885
|
+
const view = metrics?.cssVisualViewport ?? metrics?.visualViewport;
|
|
5886
|
+
return { width: view?.clientWidth ?? 0, height: view?.clientHeight ?? 0 };
|
|
5887
|
+
}
|
|
5888
|
+
function insideView(area, view) {
|
|
5889
|
+
return area.x >= 0 && area.y >= 0 && area.x + area.width <= view.width && area.y + area.height <= view.height;
|
|
5890
|
+
}
|
|
5891
|
+
async function measureInView(ref) {
|
|
5892
|
+
const view = await visibleViewport();
|
|
5893
|
+
let box = await getElementBounds(ref);
|
|
5894
|
+
let bounds = await captureArea(ref, box);
|
|
5895
|
+
const fits = bounds.width <= view.width && bounds.height <= view.height;
|
|
5896
|
+
if (fits && !insideView(bounds, view)) {
|
|
5897
|
+
const dx = bounds.x + bounds.width / 2 - view.width / 2;
|
|
5898
|
+
const dy = bounds.y + bounds.height / 2 - view.height / 2;
|
|
5899
|
+
await callCDP("Runtime.evaluate", { expression: `window.scrollBy(${dx}, ${dy})` });
|
|
5900
|
+
box = await getElementBounds(ref);
|
|
5901
|
+
bounds = await captureArea(ref, box);
|
|
5902
|
+
}
|
|
5903
|
+
return { box, bounds, inView: insideView(bounds, view) };
|
|
5904
|
+
}
|
|
5639
5905
|
async function captureArea(ref, bounds) {
|
|
5640
5906
|
const objectGroup = `bdg-shot-${process.pid}`;
|
|
5641
5907
|
try {
|
|
@@ -5667,8 +5933,6 @@ async function captureArea(ref, bounds) {
|
|
|
5667
5933
|
}
|
|
5668
5934
|
}
|
|
5669
5935
|
async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
5670
|
-
const box = await getElementBounds(ref);
|
|
5671
|
-
const bounds = await captureArea(ref, box);
|
|
5672
5936
|
const format = options.format ?? "png";
|
|
5673
5937
|
const quality = format === "jpeg" ? options.quality ?? 90 : void 0;
|
|
5674
5938
|
const noResize = options.noResize ?? false;
|
|
@@ -5677,6 +5941,20 @@ async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
5677
5941
|
returnByValue: true
|
|
5678
5942
|
});
|
|
5679
5943
|
const devicePixelRatio = dprResponse.data?.result?.result?.value ?? 1;
|
|
5944
|
+
const before = (await callCDP("Page.getLayoutMetrics", {})).data?.result;
|
|
5945
|
+
const restoreMetrics = await useUnitPixelRatio(
|
|
5946
|
+
devicePixelRatio,
|
|
5947
|
+
before?.visualViewport ?? { clientWidth: 800, clientHeight: 600 }
|
|
5948
|
+
);
|
|
5949
|
+
let box;
|
|
5950
|
+
let bounds;
|
|
5951
|
+
let inView;
|
|
5952
|
+
try {
|
|
5953
|
+
({ box, bounds, inView } = await measureInView(ref));
|
|
5954
|
+
} catch (error) {
|
|
5955
|
+
await restoreMetrics();
|
|
5956
|
+
throw error;
|
|
5957
|
+
}
|
|
5680
5958
|
const originalWidth = bounds.width;
|
|
5681
5959
|
const originalHeight = bounds.height;
|
|
5682
5960
|
const resized = shouldResize(originalWidth, originalHeight, noResize);
|
|
@@ -5685,7 +5963,6 @@ async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
5685
5963
|
const finalHeight = Math.round(originalHeight * scale);
|
|
5686
5964
|
const metricsResponse = await callCDP("Page.getLayoutMetrics", {});
|
|
5687
5965
|
const metricsResult = metricsResponse.data?.result;
|
|
5688
|
-
const viewport = metricsResult?.visualViewport ?? { clientWidth: 800, clientHeight: 600 };
|
|
5689
5966
|
const scroll = metricsResult?.cssLayoutViewport ?? { pageX: 0, pageY: 0 };
|
|
5690
5967
|
const onPage = (area) => ({
|
|
5691
5968
|
...area,
|
|
@@ -5693,14 +5970,13 @@ async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
|
5693
5970
|
y: area.y + scroll.pageY
|
|
5694
5971
|
});
|
|
5695
5972
|
const clip = onPage(bounds);
|
|
5696
|
-
const restoreMetrics = await useUnitPixelRatio(devicePixelRatio, viewport);
|
|
5697
5973
|
let screenshotResult;
|
|
5698
5974
|
try {
|
|
5699
5975
|
const screenshotResponse = await callCDP("Page.captureScreenshot", {
|
|
5700
5976
|
format,
|
|
5701
5977
|
...quality !== void 0 && { quality },
|
|
5702
5978
|
clip: { ...clip, scale },
|
|
5703
|
-
captureBeyondViewport:
|
|
5979
|
+
captureBeyondViewport: !inView
|
|
5704
5980
|
});
|
|
5705
5981
|
screenshotResult = screenshotResponse.data?.result;
|
|
5706
5982
|
} finally {
|
|
@@ -9193,6 +9469,8 @@ init_schema();
|
|
|
9193
9469
|
init_screenshotResize();
|
|
9194
9470
|
var TRIGGERED_REQUESTS_BEHAVIOR = "Requests (and WebSocket connections) that start after the action begins are returned as triggeredRequests (method, url, status, durationMs; pending when still running at return, loading when the response arrived but its body is still streaming; with resourceType; human output lists documents, XHR/fetch and WebSockets first (up to 10) and counts static assets on one line; absent when network telemetry is off). Attribution is by time: requests a page timer or poller starts meanwhile are listed too, whether or not the action caused them";
|
|
9195
9471
|
var ACTION_EFFECTS_BEHAVIOR = 'The result also says what changed on the page: a navigation (Page: navigated to <url> (status), or URL changed to <url> (same document); JSON navigation { url, sameDocument, status }), and messages that appeared or changed in alert/status/aria-live elements or flash/error/toast-like classes (New text: "\u2026" (element); JSON messages [{ text, element }], at most 3; after a navigation every message on the new page counts; texts of only digits and time units, such as clocks and counters, are left out, but other text that changes on its own, such as a rotating banner, can show up). Both are absent when nothing changed. Cost: one page script sent before the action without waiting for it and one read after it, a few ms; when the page does not answer (a pending navigation) bdg waits at most 200 ms for the snapshot and 250 ms per read, and the navigation is still reported from CDP events';
|
|
9472
|
+
var STILL_CHANGING_BEHAVIOR = 'When the page was still changing as the action returned, the status line says (page still changing), a note below it says what was pending and suggests bdg dom wait <selector>, and JSON has settled: false with pending { requests (document, fetch/XHR and script requests still running), navigation (a new page still loading), loading (a loading indicator that appeared, e.g. "div#loading"), domChanging (DOM changes kept coming in bursts over a second look 250 ms later; a single render, ticking text and style animations do not count), busy (the page did not answer within 250 ms: a long script) }; absent when the page looked settled (exit code stays 0). A result a timer renders later, with no DOM change, request or loading indicator before it, is not detected. Cost: nothing extra, except 250 ms plus one read when the DOM looked busy. Not checked with --no-wait';
|
|
9473
|
+
var SHOWN_BEHAVIOR = `Elements the action showed are listed (Shown: <element> "<text>"; JSON shown [{ text, element }], at most 3, outermost first): elements with visible text added inside the target's form, search box, dialog or combobox (else its grandparent, or its parent when that is the body), and popups and messages added anywhere (tooltip, menu, listbox, dialog, alert, status roles, popover, aria-live, message-like classes); widgets elsewhere on the page and re-rendered elements whose text was there before do not count`;
|
|
9196
9474
|
var NO_WAIT_TRIGGERED_REQUESTS = "Returns immediately without waiting for network; triggeredRequests lists only requests bdg saw start before returning (often none yet; check bdg network list later)";
|
|
9197
9475
|
var OPTION_BEHAVIORS = {
|
|
9198
9476
|
"screenshot:--selector": {
|
|
@@ -9252,7 +9530,7 @@ var OPTION_BEHAVIORS = {
|
|
|
9252
9530
|
"eval:--frame": {
|
|
9253
9531
|
default: "Evaluates in the page's main frame",
|
|
9254
9532
|
whenEnabled: "Evaluates in one iframe's main world (its own globals), including cross-origin (out-of-process) iframes; output gains a frame field (its URL)",
|
|
9255
|
-
automaticBehavior: "The value is matched as: a 0-based index (bdg dom frames order, main page not counted), else an exact name/id attribute, else a case-insensitive part of the name, id or URL. Several matches fail with 81 listing them; none fails with 83 listing all frames. Frames are looked up on every call (a reloaded iframe is found again)."
|
|
9533
|
+
automaticBehavior: "The value is matched as: a 0-based index (bdg dom frames order: document order of the <iframe> elements, nested ones depth-first, main page not counted), else an exact name/id attribute, else a case-insensitive part of the name, id or URL. Several matches fail with 81 listing them; none fails with 83 listing all frames. Frames are looked up on every call (a reloaded iframe is found again). An index that names another frame than in the last bdg dom frames listing (iframes added, removed or moved, or the page navigated) fails with 87 STALE_CACHE: re-run bdg dom frames or pick the frame by name."
|
|
9256
9534
|
},
|
|
9257
9535
|
"console:-H": {
|
|
9258
9536
|
default: "Shows messages from current page load only (most recent navigation)",
|
|
@@ -9294,7 +9572,7 @@ var OPTION_BEHAVIORS = {
|
|
|
9294
9572
|
"click:--no-wait": {
|
|
9295
9573
|
default: "Waits for network stability after click (150ms idle, up to 2s)",
|
|
9296
9574
|
whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
|
|
9297
|
-
automaticBehavior: `Network wait helps ensure AJAX requests triggered by click complete. ${TRIGGERED_REQUESTS_BEHAVIOR}. The click itself uses real mouse events in the visible part of the element (method "mouse"); if the element is covered or has no size it falls back to DOM events (method "dom", with a warning). Results the page shows later
|
|
9575
|
+
automaticBehavior: `Network wait helps ensure AJAX requests triggered by click complete. ${TRIGGERED_REQUESTS_BEHAVIOR}. The click itself uses real mouse events in the visible part of the element (method "mouse"); if the element is covered or has no size it falls back to DOM events (method "dom", with a warning; --strict refuses instead). Results the page shows later (timers, spinners, slow renders) are not waited for but reported as pending work: use bdg dom wait <selector> --visible. ${ACTION_EFFECTS_BEHAVIOR}. ${STILL_CHANGING_BEHAVIOR}. A click with no DOM change, no request and no navigation (checked again 300 ms later, which adds 300 ms plus at most 250 ms for the read) is reported as \u26A0 Element Clicked (no visible effect observed: no DOM change, requests or navigation within 300 ms) and effect: "none" in JSON (exit code stays 0); not claimed with --no-wait, for hover or right-click, after a copy or cut, or when the click hit a form control, label, media, iframe, popover button, a mailto:/tel:/javascript: or other non-http link, a link to another window or a custom element with a closed shadow root, or moved focus to an element that is not a button or link. Effects outside the DOM (CSS :hover/:focus-within styles, canvas, clipboard without a copy event) are not seen`,
|
|
9298
9576
|
tokenImpact: "A click that navigates lists the whole page load in JSON triggeredRequests"
|
|
9299
9577
|
},
|
|
9300
9578
|
"click:--double": {
|
|
@@ -9305,10 +9583,19 @@ var OPTION_BEHAVIORS = {
|
|
|
9305
9583
|
default: "Left click",
|
|
9306
9584
|
whenEnabled: "Right-click: the page gets contextmenu (custom context menus open); cannot be combined with --double"
|
|
9307
9585
|
},
|
|
9586
|
+
"click:--strict": {
|
|
9587
|
+
default: 'A covered, hidden or zero-size element is clicked with DOM events (method "dom", with a warning, exit 0)',
|
|
9588
|
+
whenEnabled: "Refuses with exit 90 (RESOURCE_CONFLICT: the page state blocks the request) when a real mouse cannot reach the element, naming what covers it and suggesting bdg dom layout <selector>; also when the mouse press never reached the element (it is released, no further presses for --double)",
|
|
9589
|
+
automaticBehavior: "Applies to --double and --right too. Nothing is dispatched when the element is unreachable, so the page is unchanged"
|
|
9590
|
+
},
|
|
9308
9591
|
"hover:--no-wait": {
|
|
9309
9592
|
default: "Waits for network stability after moving the mouse (menus may load content)",
|
|
9310
9593
|
whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
|
|
9311
|
-
automaticBehavior: `The mouse stays over the element afterwards, so hover menus stay open until the next mouse action. ${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}`
|
|
9594
|
+
automaticBehavior: `The mouse stays over the element afterwards, so hover menus stay open until the next mouse action. ${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}. ${SHOWN_BEHAVIOR}; for a hover also elements around it (its parent's subtree) and tooltips, menus, listboxes, dialogs and popovers anywhere that were hidden before, so captions shown by CSS :hover count (hidden elements noted by identity right before the mouse moves: up to 1500, within 8 ms). A hover never claims "no visible effect" and does not check whether the page was still changing`
|
|
9595
|
+
},
|
|
9596
|
+
"hover:--strict": {
|
|
9597
|
+
default: 'A covered, hidden or zero-size element gets synthetic mouseover/mouseenter events (method "dom", with a warning)',
|
|
9598
|
+
whenEnabled: "Refuses with exit 90 when a real mouse cannot reach the element, naming what covers it and suggesting bdg dom layout <selector>"
|
|
9312
9599
|
},
|
|
9313
9600
|
"navigate:--no-wait": {
|
|
9314
9601
|
default: "Waits until the new page has loaded and the network and DOM are idle (up to 15 s)",
|
|
@@ -9318,7 +9605,7 @@ var OPTION_BEHAVIORS = {
|
|
|
9318
9605
|
"pressKey:--no-wait": {
|
|
9319
9606
|
default: "Waits for network stability after key press (150ms idle, up to 2s)",
|
|
9320
9607
|
whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
|
|
9321
|
-
automaticBehavior: `${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}`
|
|
9608
|
+
automaticBehavior: `${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}. ${SHOWN_BEHAVIOR}, such as the item Enter added to a list. ${STILL_CHANGING_BEHAVIOR}. A key press never claims "no visible effect"`
|
|
9322
9609
|
},
|
|
9323
9610
|
"pressKey:--times": {
|
|
9324
9611
|
default: "Presses key once",
|
|
@@ -9363,7 +9650,7 @@ var OPTION_BEHAVIORS = {
|
|
|
9363
9650
|
"listeners:--type": {
|
|
9364
9651
|
default: "Lists listeners of every event type on the element, its ancestors (through open shadow roots), its document and window",
|
|
9365
9652
|
whenEnabled: "Lists only these event types (comma-separated or repeated, case-sensitive like addEventListener); when none match, typeSuggestions names close types (Click, onclick \u2192 click)",
|
|
9366
|
-
automaticBehavior: `Listeners are grouped by type, the element's own handlers first (types handled on the element before delegated ones; nearest first, no-ops last); JSON line/column numbers are 0-based (human output shows them 1-based like DevTools). React's on\u2026 props that run for the element (its own and its React parents', through portals; onFocus/onBlur as focusin/focusout; parents' props for non-bubbling events left out) are listed with source and location (framework: "React", reactProp: "onClick"; at most 50 of the requested types, reactHandlersSkipped counts the rest). jQuery handlers are shown instead of jQuery's dispatcher (framework: "jQuery", delegateSelector for delegates the element matches; a dispatcher with no handler for the element is omitted; at most 50 are resolved, jqueryHandlersSkipped counts the rest). Empty handlers (React's onclick placeholder) are marked noop and do not count as the element's own handler. The Debugger domain is not enabled`,
|
|
9653
|
+
automaticBehavior: `Listeners are grouped by type, the element's own handlers first (types handled on the element before delegated ones; nearest first, no-ops last); JSON line/column numbers are 0-based (human output shows them 1-based like DevTools). React's on\u2026 props that run for the element (its own and its React parents', through portals and from a nested root into the outer root; onFocus/onBlur as focusin/focusout; parents' props for non-bubbling events left out) are listed with source and location (framework: "React", reactProp: "onClick"; at most 50 of the requested types, reactHandlersSkipped counts the rest). jQuery handlers are shown instead of jQuery's dispatcher (framework: "jQuery", delegateSelector for delegates the element matches; a dispatcher with no handler for the element is omitted; at most 50 are resolved, jqueryHandlersSkipped counts the rest). Preact's event proxy is replaced by the handler Preact runs (framework: "Preact"). Empty handlers (React's onclick placeholder) are marked noop and do not count as the element's own handler. The Debugger domain is not enabled`,
|
|
9367
9654
|
tokenImpact: "Framework roots are already collapsed; --type keeps the output short on pages with many listeners"
|
|
9368
9655
|
},
|
|
9369
9656
|
"listeners:--all": {
|
|
@@ -9377,6 +9664,35 @@ var OPTION_BEHAVIORS = {
|
|
|
9377
9664
|
automaticBehavior: 'Coordinates are CSS px: bounds relative to the top-level page (iframe offsets and page scroll included), viewport relative to the visible area. Iframes and overflow containers (scroll lists, overflow: hidden) clip what counts as visible (clippedBy names the one cutting it off). scrollBy brings the whole element into view and is limited to how far the page can scroll: for an element out of view it centres it (aligns its start when it is larger than the viewport; human output says "to centre it"), for a partly visible one it is the smallest scroll that shows all of it (the part cut off at the top or bottom; the start of one larger than the viewport; "partly visible (87%); scroll up 5px to see all of it"). Elements a page script moves on scroll (floating menus) may move again after it; fixed and sticky elements (page scroll does not move them, or only until they stick) and ones beyond that range get offScreenReason instead, which says "page scrolling is locked (\u2026)" when the page cannot scroll because body/html is position: fixed or overflow: hidden, so in-flow content is not called fixed; a visible dialog (dialog[open], [aria-modal=true], [role=dialog|alertdialog]) is named as the likely cause ("likely by dialog div#consent"). page.viewport is the layout viewport without scrollbars, as dom scroll reports it; page.colorScheme is the prefers-color-scheme media feature the page sees (not the theme it renders). Content in a closed <details> or under content-visibility: hidden is hidden. coveredBy is the topmost element at the center of the largest visible box (none for pointer-events: none, nor for an element of the same click target: an overlay inside the link, button or label the element is in, a link to the same URL, or the textless absolutely positioned overlay link spanning the card that holds plain content); inert elements are flagged, not hidden',
|
|
9378
9665
|
tokenImpact: 'About one line per element; a cheap alternative to screenshots for "where is it?"'
|
|
9379
9666
|
},
|
|
9667
|
+
"inspect:--index": {
|
|
9668
|
+
default: "Inspects the first rendered match (the first when none is rendered) and notes how many matched; a numeric argument inspects that cached element (from dom query, dom form or dom a11y query)",
|
|
9669
|
+
whenEnabled: "Inspects the nth match (0-based); out of range exits 81",
|
|
9670
|
+
automaticBehavior: `Answers "what does it look like" without a screenshot, grouped like Figma Dev Mode: header (element, text, size and page position, [flex]/[grid], [not rendered]/[hidden]/[offscreen]/[covered by \u2026], prefers-color-scheme), box (margin, padding, border widths, box-sizing, overflow, scroll size), layout (display, position, flex/grid container and item settings), parent (its display and layout, distances to its content edges, gaps to the neighbouring siblings), text (first font family \u2192 the font Chrome rendered, (webfont) or local; weight size/line-height; color; WCAG contrast against the composited background; only for elements with text), fill, border (sides, radius, outline), fx (shadow, transform, filter, opacity, blend), state (cursor, pointer-events, user-select, appearance), pseudo (::before/::after with content, ::placeholder) and a child tree (depth 2, 20 rows, identical siblings grouped). Values that change nothing (0, none, transparent, normal) are left out; colors are hex (lab/oklch from Tailwind converted), lengths px without the unit, rounded to 0.1. Secrets are never shown. JSON uses Figma-aligned names (rect, box, layout.sizing hug/fill/fixed, text, fills, strokes, radius, effects, children). Also by default: hints, the element's own declarations that have no effect (justify-content on a block, width on an inline element, top on a static one, var() of an unset custom property) with the reason, the fix and the rule's file:line`,
|
|
9671
|
+
tokenImpact: "About 60\u201380 tokens for the styles and 30\u201370 more for the child tree (--tree 0 drops it), against about 1,500 for a screenshot or 3,000+ for raw computed styles"
|
|
9672
|
+
},
|
|
9673
|
+
"inspect:--all": {
|
|
9674
|
+
default: "Shows the curated groups (the properties that define the look)",
|
|
9675
|
+
whenEnabled: "Lists every computed property that differs from the default of the same element type, longhands collapsed into shorthands, noise (logical duplicates, currentColor echoes, custom properties) dropped",
|
|
9676
|
+
tokenImpact: "About 80 tokens instead of 60\u201380"
|
|
9677
|
+
},
|
|
9678
|
+
"inspect:--props": {
|
|
9679
|
+
default: "Shows the curated groups",
|
|
9680
|
+
whenEnabled: 'Shows only the named properties (custom properties like --brand included, "(not set)" when no rule sets one; --* lists every custom property the element has, --bs-btn-* those with a prefix), each computed and normalized; an unknown name exits 81 with a suggestion'
|
|
9681
|
+
},
|
|
9682
|
+
"inspect:--rules": {
|
|
9683
|
+
default: "Shows the values, not where they come from",
|
|
9684
|
+
whenEnabled: "Adds a rules group: for each shown property the page's CSS sets, the value as written (with the computed value when it uses var()), selector, file:line (column for minified files), @media/@container condition, cascade layer, how many ancestors up it is inherited from, and the rules it beats. Sides one declaration sets are one row; browser defaults are left out. With --props, only those properties",
|
|
9685
|
+
automaticBehavior: "The cascade is computed by bdg from CSS.getMatchedStylesForNode (origin, !important, style attribute, layers, specificity and order); reading it waits up to 5 s, then the output notes the cascade was not read",
|
|
9686
|
+
tokenImpact: "About 15\u201325 tokens per row, 5\u201320 rows"
|
|
9687
|
+
},
|
|
9688
|
+
"inspect:--why": {
|
|
9689
|
+
default: "Not shown",
|
|
9690
|
+
whenEnabled: "Adds why <property> = computed value, then every declaration of it on the element, highest precedence first: \u2713 the winner (or the inherited ancestor's), \u2717 the ones it beats, browser defaults included. Each rule shows its selector specificity [ids,classes,types]. var() values are shown substituted (or invalid: --x not set), with where the winner's custom properties are set, followed up to :root. Logical names map to physical ones (margin-inline-start \u2192 margin-left); a shorthand (padding, border) gives one answer when one declaration sets all its sides, else one per side"
|
|
9691
|
+
},
|
|
9692
|
+
"inspect:--no-hints": {
|
|
9693
|
+
default: "Hints at the element's own author declarations that have no effect (flex/grid properties without flex or grid, item properties without a flex or grid parent, offsets on static elements, sizes on inline ones, var() of an unset custom property, form controls in the browser's font), within a 1 s budget; hints none when nothing was found",
|
|
9694
|
+
whenEnabled: "Skips the hints and does not read the matched rules"
|
|
9695
|
+
},
|
|
9380
9696
|
"scroll:--down": {
|
|
9381
9697
|
whenEnabled: "Scrolls page down by specified pixel amount"
|
|
9382
9698
|
},
|
|
@@ -9458,7 +9774,7 @@ var OPTION_BEHAVIORS = {
|
|
|
9458
9774
|
"bdg:--viewport": {
|
|
9459
9775
|
default: "A launched Chrome opens a 1920x1080 window (the viewport is smaller by the scrollbar, and in a visible window by the browser UI); an attached Chrome keeps its window",
|
|
9460
9776
|
whenEnabled: "The page gets exactly that viewport (CSS px, e.g. 1280x800) for the whole session, through navigations and reloads (Emulation.setDeviceMetricsOverride at the display pixel ratio); a launched Chrome also opens its window at that size, so tabs the page opens get it too",
|
|
9461
|
-
automaticBehavior: "Works with --chrome-ws-url: the override belongs to the session, and Chrome drops it when the session ends, so the attached browser gets its own size back. bdg status shows the resulting layout viewport without the scrollbar (Viewport: 1265\xD7800 (
|
|
9777
|
+
automaticBehavior: "Works with --chrome-ws-url: the override belongs to the session, and Chrome drops it when the session ends, so the attached browser gets its own size back. bdg status shows the resulting layout viewport without the scrollbar (Viewport: 1265\xD7800 (emulated 1280x800)). Invalid sizes (not WxH, a side outside 1-10000) exit 81"
|
|
9462
9778
|
},
|
|
9463
9779
|
"bdg:--color-scheme": {
|
|
9464
9780
|
default: "The page sees the system setting for prefers-color-scheme (headless Chrome follows the OS, so a dark OS renders dark pages); bdg status and dom layout show which one",
|
|
@@ -10113,6 +10429,14 @@ init_logging();
|
|
|
10113
10429
|
function delay2(ms) {
|
|
10114
10430
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
10115
10431
|
}
|
|
10432
|
+
async function waitUntil(condition, timeoutMs, pollMs = 25) {
|
|
10433
|
+
const deadline = Date.now() + timeoutMs;
|
|
10434
|
+
while (!condition()) {
|
|
10435
|
+
if (Date.now() >= deadline) return false;
|
|
10436
|
+
await delay2(Math.min(pollMs, Math.max(0, deadline - Date.now())));
|
|
10437
|
+
}
|
|
10438
|
+
return true;
|
|
10439
|
+
}
|
|
10116
10440
|
|
|
10117
10441
|
// src/utils/file.ts
|
|
10118
10442
|
init_logging();
|
|
@@ -10892,6 +11216,12 @@ function showBothSectionsWhenBothRequested(options) {
|
|
|
10892
11216
|
options.console = false;
|
|
10893
11217
|
}
|
|
10894
11218
|
}
|
|
11219
|
+
var SELECTOR_SCOPE = "searches open shadow roots and same-origin iframes";
|
|
11220
|
+
var SELECTOR_OR_INDEX_ARGUMENT = `CSS selector (${SELECTOR_SCOPE}) or numeric index from query results (0-based)`;
|
|
11221
|
+
var SELECTOR_SCOPE_HELP = `
|
|
11222
|
+
Selectors search the page, open shadow roots and same-origin iframes (nested ones
|
|
11223
|
+
included). They cannot reach into closed shadow roots or cross-origin iframes:
|
|
11224
|
+
use bdg dom eval --frame <frame> for those (see bdg dom frames).`;
|
|
10895
11225
|
|
|
10896
11226
|
// src/commands/cdp.ts
|
|
10897
11227
|
init_errors3();
|
|
@@ -10919,9 +11249,11 @@ var COMMANDS = {
|
|
|
10919
11249
|
dom_press_key: defineCommand(),
|
|
10920
11250
|
dom_scroll: defineCommand(),
|
|
10921
11251
|
page_navigate: defineCommand(),
|
|
11252
|
+
page_emulate: defineCommand(),
|
|
10922
11253
|
dom_form_discover: defineCommand(),
|
|
10923
11254
|
dom_listeners: defineCommand(),
|
|
10924
11255
|
dom_layout: defineCommand(),
|
|
11256
|
+
dom_inspect: defineCommand(),
|
|
10925
11257
|
dom_wait: defineCommand()
|
|
10926
11258
|
};
|
|
10927
11259
|
|
|
@@ -11940,6 +12272,15 @@ function eventTypesOption(value, previous = []) {
|
|
|
11940
12272
|
if (types.length > 0) return [.../* @__PURE__ */ new Set([...previous, ...types])];
|
|
11941
12273
|
throw new InvalidArgumentError("Give at least one event type, e.g. click or click,keydown.");
|
|
11942
12274
|
}
|
|
12275
|
+
function cssPropertiesOption(value, previous = []) {
|
|
12276
|
+
const names = parseCommaSeparated(value).map(
|
|
12277
|
+
(name) => name.startsWith("--") ? name : name.toLowerCase()
|
|
12278
|
+
);
|
|
12279
|
+
if (names.length > 0) return [.../* @__PURE__ */ new Set([...previous, ...names])];
|
|
12280
|
+
throw new InvalidArgumentError(
|
|
12281
|
+
"Give at least one CSS property, e.g. color or padding-top,color."
|
|
12282
|
+
);
|
|
12283
|
+
}
|
|
11943
12284
|
function parseCommaSeparated(value) {
|
|
11944
12285
|
return value.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
11945
12286
|
}
|
|
@@ -12607,10 +12948,71 @@ function formatRequestStatus(request) {
|
|
|
12607
12948
|
return request.errorText && request.errorText !== BODY_ABORTED ? `${request.status} (${request.errorText})` : String(request.status);
|
|
12608
12949
|
}
|
|
12609
12950
|
|
|
12951
|
+
// src/ui/formatters/networkHeaders.ts
|
|
12952
|
+
init_formatting();
|
|
12953
|
+
|
|
12954
|
+
// src/ui/messages/networkMessages.ts
|
|
12955
|
+
function followingNetworkMessage() {
|
|
12956
|
+
return "Following network requests... (Ctrl+C to stop)";
|
|
12957
|
+
}
|
|
12958
|
+
function stoppedFollowingNetworkMessage() {
|
|
12959
|
+
return "\nStopped following network requests.";
|
|
12960
|
+
}
|
|
12961
|
+
function headerRepeatedNote(count) {
|
|
12962
|
+
return `(sent ${count} times)`;
|
|
12963
|
+
}
|
|
12964
|
+
function localProxyNote() {
|
|
12965
|
+
return "(loopback; likely a local proxy)";
|
|
12966
|
+
}
|
|
12967
|
+
|
|
12968
|
+
// src/ui/formatters/networkHeaders.ts
|
|
12969
|
+
function formatNetworkHeaders(data) {
|
|
12970
|
+
const fmt = new OutputFormatter();
|
|
12971
|
+
fmt.text("Network Request Headers").separator("\u2501", 60).blank();
|
|
12972
|
+
fmt.text("URL:").text(` ${data.url}`).blank();
|
|
12973
|
+
fmt.text(`Status: ${requestStatusLine(data)}`).blank();
|
|
12974
|
+
if (Object.keys(data.responseHeaders).length > 0) {
|
|
12975
|
+
fmt.text("Response Headers:");
|
|
12976
|
+
formatHeaderSection(fmt, data.responseHeaders);
|
|
12977
|
+
fmt.blank();
|
|
12978
|
+
}
|
|
12979
|
+
if (Object.keys(data.requestHeaders).length > 0) {
|
|
12980
|
+
fmt.text("Request Headers:");
|
|
12981
|
+
formatHeaderSection(fmt, data.requestHeaders);
|
|
12982
|
+
fmt.blank();
|
|
12983
|
+
}
|
|
12984
|
+
if (Object.keys(data.responseHeaders).length === 0 && Object.keys(data.requestHeaders).length === 0) {
|
|
12985
|
+
fmt.text("No headers found").blank();
|
|
12986
|
+
}
|
|
12987
|
+
fmt.text(`Request ID: ${data.requestId}`);
|
|
12988
|
+
return fmt.build();
|
|
12989
|
+
}
|
|
12990
|
+
function headerValueLines(name, value) {
|
|
12991
|
+
if (name.toLowerCase() === "set-cookie") return value.split("\n");
|
|
12992
|
+
const counts = /* @__PURE__ */ new Map();
|
|
12993
|
+
for (const line of value.split("\n")) counts.set(line, (counts.get(line) ?? 0) + 1);
|
|
12994
|
+
return [...counts].map(
|
|
12995
|
+
([line, count]) => count > 1 ? `${line} ${headerRepeatedNote(count)}` : line
|
|
12996
|
+
);
|
|
12997
|
+
}
|
|
12998
|
+
function formatHeaderSection(fmt, headers) {
|
|
12999
|
+
const entries = Object.entries(headers).sort(([a], [b]) => a.localeCompare(b));
|
|
13000
|
+
const keyWidth = Math.max(...entries.map(([k]) => k.length)) + 4;
|
|
13001
|
+
entries.forEach(([key, value]) => {
|
|
13002
|
+
headerValueLines(key, value).forEach((line) => fmt.keyValue(` ${key}`, line, keyWidth));
|
|
13003
|
+
});
|
|
13004
|
+
}
|
|
13005
|
+
function requestStatusLine(data) {
|
|
13006
|
+
const status = formatRequestStatus(data);
|
|
13007
|
+
const text = data.statusText && /^\d+$/.test(status) ? ` ${data.statusText}` : "";
|
|
13008
|
+
return [data.method, `${status}${text}`].filter(Boolean).join(" ");
|
|
13009
|
+
}
|
|
13010
|
+
|
|
12610
13011
|
// src/ui/formatters/details.ts
|
|
12611
13012
|
init_formatting();
|
|
12612
13013
|
init_sessionCommand();
|
|
12613
13014
|
init_strings();
|
|
13015
|
+
init_url();
|
|
12614
13016
|
var MESSAGE_PREVIEW_LENGTH = 200;
|
|
12615
13017
|
var BINARY_OPCODE = 2;
|
|
12616
13018
|
function formatWebSocketMessage(frame) {
|
|
@@ -12652,6 +13054,34 @@ function formatBytes(bytes) {
|
|
|
12652
13054
|
if (bytes < 1024) return `${bytes} B`;
|
|
12653
13055
|
return bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
12654
13056
|
}
|
|
13057
|
+
function isLoopback(ip) {
|
|
13058
|
+
const bare = ip.replace(/^\[|\]$/g, "").toLowerCase();
|
|
13059
|
+
return /^(::ffff:)?127\./.test(bare) || bare === "::1";
|
|
13060
|
+
}
|
|
13061
|
+
var DEFAULT_PORTS = {
|
|
13062
|
+
"http:": "80",
|
|
13063
|
+
"https:": "443",
|
|
13064
|
+
"ws:": "80",
|
|
13065
|
+
"wss:": "443"
|
|
13066
|
+
};
|
|
13067
|
+
function looksLikeLocalProxy(request) {
|
|
13068
|
+
const url = safeParseUrl(request.url);
|
|
13069
|
+
if (!url || request.serverPort === void 0 || !isLoopback(request.serverIPAddress ?? "")) {
|
|
13070
|
+
return false;
|
|
13071
|
+
}
|
|
13072
|
+
const hostname = url.hostname.toLowerCase();
|
|
13073
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost") || isLoopback(hostname)) {
|
|
13074
|
+
return false;
|
|
13075
|
+
}
|
|
13076
|
+
const urlPort = url.port || DEFAULT_PORTS[url.protocol];
|
|
13077
|
+
return urlPort !== void 0 && String(request.serverPort) !== urlPort;
|
|
13078
|
+
}
|
|
13079
|
+
function remoteAddress(request) {
|
|
13080
|
+
const ip = request.serverIPAddress ?? "";
|
|
13081
|
+
const host = ip.includes(":") && !ip.startsWith("[") ? `[${ip}]` : ip;
|
|
13082
|
+
const address = request.serverPort ? `${host}:${request.serverPort}` : host;
|
|
13083
|
+
return looksLikeLocalProxy(request) ? `${address} ${localProxyNote()}` : address;
|
|
13084
|
+
}
|
|
12655
13085
|
function requestSummaryRows(request) {
|
|
12656
13086
|
const rows = [
|
|
12657
13087
|
["Request ID", request.requestId],
|
|
@@ -12670,13 +13100,15 @@ function requestSummaryRows(request) {
|
|
|
12670
13100
|
rows.push(["Size", sizes.join(", ")]);
|
|
12671
13101
|
}
|
|
12672
13102
|
if (request.fromCache) rows.push(["From Cache", "yes"]);
|
|
12673
|
-
if (request.serverIPAddress) rows.push(["Remote Address", request
|
|
13103
|
+
if (request.serverIPAddress) rows.push(["Remote Address", remoteAddress(request)]);
|
|
12674
13104
|
if (request.blockedReason) rows.push(["Blocked", request.blockedReason]);
|
|
12675
13105
|
return rows;
|
|
12676
13106
|
}
|
|
12677
13107
|
function addHeaders(fmt, title, headers) {
|
|
12678
13108
|
fmt.text(title).separator("\u2501", 70);
|
|
12679
|
-
Object.entries(headers).forEach(
|
|
13109
|
+
Object.entries(headers).forEach(
|
|
13110
|
+
([key, value]) => headerValueLines(key, value).forEach((line) => fmt.text(` ${key}: ${line}`))
|
|
13111
|
+
);
|
|
12680
13112
|
fmt.blank();
|
|
12681
13113
|
}
|
|
12682
13114
|
function describeResponseBody(request) {
|
|
@@ -13065,7 +13497,12 @@ async function withNotFoundContext(failure, selector, searched) {
|
|
|
13065
13497
|
const context = await noMatchContext(selector);
|
|
13066
13498
|
const note = searched ? unreachableElementsNote(selector, context.unsearched) : "";
|
|
13067
13499
|
const suggestion = withLoadingHint(
|
|
13068
|
-
joinLines(
|
|
13500
|
+
joinLines(
|
|
13501
|
+
context.similar,
|
|
13502
|
+
context.shadowHost && shadowBoundaryLine(context.shadowHost),
|
|
13503
|
+
failure.errorContext?.suggestion,
|
|
13504
|
+
note ? note : void 0
|
|
13505
|
+
),
|
|
13069
13506
|
context.readyState,
|
|
13070
13507
|
selector
|
|
13071
13508
|
);
|
|
@@ -13211,6 +13648,7 @@ init_formatting();
|
|
|
13211
13648
|
init_commands();
|
|
13212
13649
|
init_sessionCommand();
|
|
13213
13650
|
init_exitCodes();
|
|
13651
|
+
var STRICT_OPTION_HELP = "Fail (exit 90) instead of using DOM events when a real mouse cannot reach the element (covered, hidden, zero-size)";
|
|
13214
13652
|
function modifiersOption(value) {
|
|
13215
13653
|
const [unknown] = findUnknownModifiers(value);
|
|
13216
13654
|
if (unknown !== void 0) {
|
|
@@ -13224,7 +13662,7 @@ function registerFormInteractionCommands(program2) {
|
|
|
13224
13662
|
const err = internalError("DOM command group not found");
|
|
13225
13663
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SOFTWARE_ERROR);
|
|
13226
13664
|
}
|
|
13227
|
-
domCommand.command("fill").description("Fill a form field with a value (React-compatible, waits for stability)").argument("<selectorOrIndex>",
|
|
13665
|
+
domCommand.command("fill").description("Fill a form field with a value (React-compatible, waits for stability)").argument("<selectorOrIndex>", SELECTOR_OR_INDEX_ARGUMENT).argument("<value>", 'Value to fill (file inputs: paths separated by commas, "" clears)').option("--index <n>", "Element index if selector matches multiple (0-based)", integerOption(0)).option("--no-blur", "Do not blur after filling (keeps focus on element)").option("--no-wait", "Skip waiting for network stability after fill").addOption(jsonOption()).action(async (selectorOrIndex, value, options) => {
|
|
13228
13666
|
await runCommand(
|
|
13229
13667
|
() => runElementCommand({
|
|
13230
13668
|
selectorOrIndex,
|
|
@@ -13245,14 +13683,14 @@ function registerFormInteractionCommands(program2) {
|
|
|
13245
13683
|
formatFillOutput
|
|
13246
13684
|
);
|
|
13247
13685
|
});
|
|
13248
|
-
domCommand.command("click").description("Click an element and wait for stability (accepts selector or index)").argument("<selectorOrIndex>",
|
|
13686
|
+
domCommand.command("click").description("Click an element and wait for stability (accepts selector or index)").argument("<selectorOrIndex>", SELECTOR_OR_INDEX_ARGUMENT).option("--index <n>", "Element index if selector matches multiple (0-based)", integerOption(0)).option("--double", "Double-click").option("--right", "Right-click (opens the context menu)").option("--strict", STRICT_OPTION_HELP).option("--no-wait", "Skip waiting for network stability after click").addOption(jsonOption()).addHelpText("after", CLICK_RESULT_WAIT_HELP).action(async (selectorOrIndex, options) => {
|
|
13249
13687
|
const action = options.double ? "double" : options.right ? "right" : "click";
|
|
13250
13688
|
await runPointerCommand(selectorOrIndex, options, action);
|
|
13251
13689
|
});
|
|
13252
|
-
domCommand.command("hover").description("Move the mouse over an element (shows hover menus and tooltips)").argument("<selectorOrIndex>",
|
|
13690
|
+
domCommand.command("hover").description("Move the mouse over an element (shows hover menus and tooltips)").argument("<selectorOrIndex>", SELECTOR_OR_INDEX_ARGUMENT).option("--index <n>", "Element index if selector matches multiple (0-based)", integerOption(0)).option("--strict", STRICT_OPTION_HELP).option("--no-wait", "Skip waiting for network stability after hovering").addOption(jsonOption()).action(async (selectorOrIndex, options) => {
|
|
13253
13691
|
await runPointerCommand(selectorOrIndex, options, "hover");
|
|
13254
13692
|
});
|
|
13255
|
-
domCommand.command("submit").description("Submit a form by clicking submit button and waiting for completion").argument("<selectorOrIndex>",
|
|
13693
|
+
domCommand.command("submit").description("Submit a form by clicking submit button and waiting for completion").argument("<selectorOrIndex>", SELECTOR_OR_INDEX_ARGUMENT).option("--index <n>", "Element index if selector matches multiple (0-based)", integerOption(0)).option("--wait-navigation", "Wait for page navigation after submit").option(
|
|
13256
13694
|
"--wait-network <ms>",
|
|
13257
13695
|
"Wait for network idle after submit (milliseconds)",
|
|
13258
13696
|
integerOption(0),
|
|
@@ -13279,7 +13717,7 @@ function registerFormInteractionCommands(program2) {
|
|
|
13279
13717
|
formatSubmitOutput
|
|
13280
13718
|
);
|
|
13281
13719
|
});
|
|
13282
|
-
domCommand.command("pressKey").description("Press a key on an element (for Enter-to-submit, keyboard navigation)").argument("<selectorOrIndex>",
|
|
13720
|
+
domCommand.command("pressKey").description("Press a key on an element (for Enter-to-submit, keyboard navigation)").argument("<selectorOrIndex>", SELECTOR_OR_INDEX_ARGUMENT).argument("<key>", "Key to press (Enter, Tab, Escape, Space, ArrowUp, etc.)").option("--index <n>", "Element index if selector matches multiple (0-based)", integerOption(0)).option("--times <n>", "Press key multiple times (default: 1)", integerOption(1, 1e3)).option(
|
|
13283
13721
|
"--modifiers <mods>",
|
|
13284
13722
|
"Modifier keys: shift,ctrl,alt,meta (comma-separated; aliases cmd, control, option)",
|
|
13285
13723
|
modifiersOption
|
|
@@ -13381,7 +13819,8 @@ async function runPointerCommand(selectorOrIndex, options, action) {
|
|
|
13381
13819
|
buildRequest: (target) => ({
|
|
13382
13820
|
...target,
|
|
13383
13821
|
wait: options.wait !== false,
|
|
13384
|
-
...action !== "click" && { action }
|
|
13822
|
+
...action !== "click" && { action },
|
|
13823
|
+
...options.strict && { strict: true }
|
|
13385
13824
|
}),
|
|
13386
13825
|
call: domClick,
|
|
13387
13826
|
command: action === "hover" ? "hover" : "click",
|
|
@@ -13392,18 +13831,26 @@ async function runPointerCommand(selectorOrIndex, options, action) {
|
|
|
13392
13831
|
formatClickOutput
|
|
13393
13832
|
);
|
|
13394
13833
|
}
|
|
13395
|
-
function formatActionOutput(done, details, result,
|
|
13834
|
+
function formatActionOutput(done, details, result, options = {}) {
|
|
13835
|
+
const keyWidth = options.keyWidth ?? 15;
|
|
13396
13836
|
const fmt = new OutputFormatter();
|
|
13397
|
-
|
|
13837
|
+
const stillChanging = result.settled === false && result.pending !== void 0;
|
|
13838
|
+
fmt.text(
|
|
13839
|
+
actionStatusLine(done, {
|
|
13840
|
+
warned: result.warning !== void 0,
|
|
13841
|
+
noEffect: result.effect === "none",
|
|
13842
|
+
stillChanging
|
|
13843
|
+
})
|
|
13844
|
+
);
|
|
13398
13845
|
if (result.warning) fmt.text(`\u26A0 Warning: ${result.warning}`);
|
|
13846
|
+
if (stillChanging && result.pending) {
|
|
13847
|
+
fmt.text(`\u26A0 ${stillChangingNote(options.action ?? "action", result.pending)}`);
|
|
13848
|
+
}
|
|
13399
13849
|
fmt.blank();
|
|
13400
13850
|
fmt.keyValueList(details, keyWidth);
|
|
13401
13851
|
if (result.navigation) fmt.keyValue("Page", pageNavigationText(result.navigation), keyWidth);
|
|
13402
|
-
(result.messages ?? []).
|
|
13403
|
-
|
|
13404
|
-
if (index === 0) fmt.keyValue("New text", text, keyWidth);
|
|
13405
|
-
else fmt.text(" ".repeat(keyWidth) + text);
|
|
13406
|
-
});
|
|
13852
|
+
listRows(fmt, "New text", (result.messages ?? []).map(newMessageText), keyWidth);
|
|
13853
|
+
listRows(fmt, "Shown", (result.shown ?? []).map(shownElementText), keyWidth);
|
|
13407
13854
|
const omitted = result.triggeredRequestsOmitted;
|
|
13408
13855
|
const requests = formatTriggeredRequestLines(result.triggeredRequests ?? [], omitted);
|
|
13409
13856
|
if (requests.length > 0) {
|
|
@@ -13415,6 +13862,12 @@ function formatActionOutput(done, details, result, keyWidth = 15) {
|
|
|
13415
13862
|
}
|
|
13416
13863
|
return fmt;
|
|
13417
13864
|
}
|
|
13865
|
+
function listRows(fmt, label, texts, keyWidth) {
|
|
13866
|
+
texts.forEach((text, index) => {
|
|
13867
|
+
if (index === 0) fmt.keyValue(label, text, keyWidth);
|
|
13868
|
+
else fmt.text(" ".repeat(keyWidth) + text);
|
|
13869
|
+
});
|
|
13870
|
+
}
|
|
13418
13871
|
function elementRow(result) {
|
|
13419
13872
|
const source = result.indexSource ? ` (${indexSourceText(result.indexSource)})` : "";
|
|
13420
13873
|
if (result.element === void 0) {
|
|
@@ -13445,7 +13898,8 @@ function formatClickOutput(result) {
|
|
|
13445
13898
|
elementRow(result),
|
|
13446
13899
|
["Method", result.method === "dom" ? "DOM events" : "mouse events"]
|
|
13447
13900
|
],
|
|
13448
|
-
result
|
|
13901
|
+
result,
|
|
13902
|
+
{ action: POINTER_ACTION_NOUN[result.action ?? "click"] }
|
|
13449
13903
|
).build();
|
|
13450
13904
|
}
|
|
13451
13905
|
function formatSubmitOutput(result) {
|
|
@@ -13459,7 +13913,10 @@ function formatSubmitOutput(result) {
|
|
|
13459
13913
|
if (result.navigationOccurred === false) details.push(["Navigation", "no"]);
|
|
13460
13914
|
if (result.navigationOccurred && !result.navigation) details.push(["Navigation", "yes"]);
|
|
13461
13915
|
if (result.waitTimeMs !== void 0) details.push(["Wait Time", `${result.waitTimeMs}ms`]);
|
|
13462
|
-
const fmt = formatActionOutput("Form Submitted", details, result,
|
|
13916
|
+
const fmt = formatActionOutput("Form Submitted", details, result, {
|
|
13917
|
+
keyWidth: 20,
|
|
13918
|
+
action: "submit"
|
|
13919
|
+
});
|
|
13463
13920
|
fmt.hints("Next steps:", [
|
|
13464
13921
|
`${sessionCommand("bdg network list --last 10").padEnd(32)} Check network requests`,
|
|
13465
13922
|
`${sessionCommand("bdg console --last 5").padEnd(32)} Check console messages`,
|
|
@@ -13475,7 +13932,7 @@ function formatPressKeyOutput(result) {
|
|
|
13475
13932
|
];
|
|
13476
13933
|
if (result.times && result.times > 1) details.push(["Times", result.times.toString()]);
|
|
13477
13934
|
if (result.modifiers?.length) details.push(["Modifiers", result.modifiers.join("+")]);
|
|
13478
|
-
return formatActionOutput("Key Pressed", details, result).build();
|
|
13935
|
+
return formatActionOutput("Key Pressed", details, result, { action: "key press" }).build();
|
|
13479
13936
|
}
|
|
13480
13937
|
function formatScrollOutput(result) {
|
|
13481
13938
|
const details = [["Scroll Type", result.scrollType]];
|
|
@@ -13494,36 +13951,245 @@ function formatScrollOutput(result) {
|
|
|
13494
13951
|
|
|
13495
13952
|
// src/commands/dom/a11y.ts
|
|
13496
13953
|
init_helpers();
|
|
13497
|
-
init_errors3();
|
|
13498
|
-
init_messages();
|
|
13499
13954
|
|
|
13500
|
-
// src/
|
|
13501
|
-
|
|
13502
|
-
init_messages();
|
|
13503
|
-
init_client();
|
|
13955
|
+
// src/commands/dom/semanticUtils.ts
|
|
13956
|
+
init_elementInfo();
|
|
13504
13957
|
|
|
13505
|
-
// src/
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
|
|
13510
|
-
|
|
13511
|
-
|
|
13958
|
+
// src/telemetry/roleInference.ts
|
|
13959
|
+
var IMPLICIT_ARIA_ROLES = {
|
|
13960
|
+
// Interactive elements
|
|
13961
|
+
a: "link",
|
|
13962
|
+
button: "button",
|
|
13963
|
+
input: "textbox",
|
|
13964
|
+
select: "combobox",
|
|
13965
|
+
textarea: "textbox",
|
|
13966
|
+
option: "option",
|
|
13967
|
+
// Headings
|
|
13968
|
+
h1: "heading",
|
|
13969
|
+
h2: "heading",
|
|
13970
|
+
h3: "heading",
|
|
13971
|
+
h4: "heading",
|
|
13972
|
+
h5: "heading",
|
|
13973
|
+
h6: "heading",
|
|
13974
|
+
// Landmarks
|
|
13975
|
+
nav: "navigation",
|
|
13976
|
+
main: "main",
|
|
13977
|
+
header: "banner",
|
|
13978
|
+
footer: "contentinfo",
|
|
13979
|
+
aside: "complementary",
|
|
13980
|
+
section: "region",
|
|
13981
|
+
article: "article",
|
|
13982
|
+
form: "form",
|
|
13983
|
+
search: "search",
|
|
13984
|
+
// Media
|
|
13985
|
+
img: "image",
|
|
13986
|
+
figure: "figure",
|
|
13987
|
+
figcaption: "caption",
|
|
13988
|
+
video: "video",
|
|
13989
|
+
audio: "audio",
|
|
13990
|
+
// Lists
|
|
13991
|
+
ul: "list",
|
|
13992
|
+
ol: "list",
|
|
13993
|
+
li: "listitem",
|
|
13994
|
+
dl: "list",
|
|
13995
|
+
dt: "term",
|
|
13996
|
+
dd: "definition",
|
|
13997
|
+
menu: "menu",
|
|
13998
|
+
menuitem: "menuitem",
|
|
13999
|
+
// Tables
|
|
14000
|
+
table: "table",
|
|
14001
|
+
thead: "rowgroup",
|
|
14002
|
+
tbody: "rowgroup",
|
|
14003
|
+
tfoot: "rowgroup",
|
|
14004
|
+
tr: "row",
|
|
14005
|
+
th: "columnheader",
|
|
14006
|
+
td: "cell",
|
|
14007
|
+
// Other semantic elements
|
|
14008
|
+
dialog: "dialog",
|
|
14009
|
+
details: "group",
|
|
14010
|
+
summary: "button",
|
|
14011
|
+
progress: "progressbar",
|
|
14012
|
+
meter: "meter",
|
|
14013
|
+
output: "status",
|
|
14014
|
+
address: "group",
|
|
14015
|
+
blockquote: "blockquote",
|
|
14016
|
+
code: "code",
|
|
14017
|
+
pre: "group",
|
|
14018
|
+
hr: "separator"
|
|
14019
|
+
};
|
|
14020
|
+
var DEFAULT_ROLE = "generic";
|
|
14021
|
+
var MAX_NAME_LENGTH = 100;
|
|
14022
|
+
function inferRoleFromTag(tag) {
|
|
14023
|
+
const normalizedTag = tag.toLowerCase();
|
|
14024
|
+
return IMPLICIT_ARIA_ROLES[normalizedTag] ?? DEFAULT_ROLE;
|
|
13512
14025
|
}
|
|
13513
|
-
|
|
13514
|
-
|
|
13515
|
-
|
|
13516
|
-
|
|
13517
|
-
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
14026
|
+
function extractHeadingLevel(tag) {
|
|
14027
|
+
const match = tag.toLowerCase().match(/^h([1-6])$/);
|
|
14028
|
+
const level = match?.[1];
|
|
14029
|
+
if (!level) {
|
|
14030
|
+
return null;
|
|
14031
|
+
}
|
|
14032
|
+
return parseInt(level, 10);
|
|
14033
|
+
}
|
|
14034
|
+
function synthesizeA11yNode(domContext, nodeId) {
|
|
14035
|
+
const role = inferRoleFromTag(domContext.tag);
|
|
14036
|
+
const node = {
|
|
14037
|
+
nodeId: String(nodeId),
|
|
14038
|
+
role,
|
|
14039
|
+
inferred: true,
|
|
14040
|
+
backendDOMNodeId: nodeId
|
|
14041
|
+
};
|
|
14042
|
+
if (domContext.preview) {
|
|
14043
|
+
const name = domContext.preview.length > MAX_NAME_LENGTH ? domContext.preview.slice(0, MAX_NAME_LENGTH - 3) + "..." : domContext.preview;
|
|
14044
|
+
node.name = name;
|
|
14045
|
+
}
|
|
14046
|
+
const headingLevel = extractHeadingLevel(domContext.tag);
|
|
14047
|
+
if (headingLevel !== null) {
|
|
14048
|
+
node.properties = { level: headingLevel };
|
|
14049
|
+
}
|
|
14050
|
+
return node;
|
|
14051
|
+
}
|
|
14052
|
+
|
|
14053
|
+
// src/ui/formatters/keyAttributes.ts
|
|
14054
|
+
init_formatting();
|
|
14055
|
+
init_url();
|
|
14056
|
+
var MAX_VALUE_LENGTH = 40;
|
|
14057
|
+
function fileName(src) {
|
|
14058
|
+
if (/^(data|blob):/i.test(src)) return src;
|
|
14059
|
+
const path6 = src.split(/[?#]/)[0] ?? src;
|
|
14060
|
+
const name = path6.substring(path6.lastIndexOf("/") + 1);
|
|
14061
|
+
return name && name !== path6 ? `\u2026/${name}` : src;
|
|
14062
|
+
}
|
|
14063
|
+
function hostOf(src) {
|
|
14064
|
+
const parsed = /^https?:/i.test(src) ? safeParseUrl(src) : null;
|
|
14065
|
+
if (!parsed) return src;
|
|
14066
|
+
const more = parsed.pathname.length > 1 || parsed.search ? "/\u2026" : "";
|
|
14067
|
+
return `//${parsed.host}${more}`;
|
|
14068
|
+
}
|
|
14069
|
+
function shortUrl(url) {
|
|
14070
|
+
return /^https?:/i.test(url) ? `//${truncateUrl(url, MAX_VALUE_LENGTH - 2)}` : url;
|
|
14071
|
+
}
|
|
14072
|
+
function shortValue(tag, name, value) {
|
|
14073
|
+
let short = value;
|
|
14074
|
+
if (name === "src") short = tag === "iframe" ? hostOf(value) : fileName(value);
|
|
14075
|
+
else if (name === "href" || name === "action") short = shortUrl(value);
|
|
14076
|
+
return cutMiddle(escapeControlChars(short.replace(/\s+/g, " ").trim()), MAX_VALUE_LENGTH);
|
|
14077
|
+
}
|
|
14078
|
+
function keyAttributeItems(tag, attributes, leaveOut = /* @__PURE__ */ new Set()) {
|
|
14079
|
+
return Object.entries(attributes ?? {}).flatMap(([name, value]) => {
|
|
14080
|
+
if (leaveOut.has(name) || value === false) return [];
|
|
14081
|
+
if (value === true) return [name];
|
|
14082
|
+
return [`${name}="${shortValue(tag, name, value)}"`];
|
|
14083
|
+
});
|
|
14084
|
+
}
|
|
14085
|
+
|
|
14086
|
+
// src/commands/dom/semanticUtils.ts
|
|
14087
|
+
init_formatting();
|
|
14088
|
+
init_commands();
|
|
14089
|
+
function capitalize(str) {
|
|
14090
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
14091
|
+
}
|
|
14092
|
+
function buildRoleText(node) {
|
|
14093
|
+
if (node.role.toLowerCase() === "heading" && node.properties?.["level"] !== void 0) {
|
|
14094
|
+
const level = node.properties["level"];
|
|
14095
|
+
const levelNum = typeof level === "number" ? level : Number(level);
|
|
14096
|
+
if (!isNaN(levelNum)) {
|
|
14097
|
+
return `[Heading L${levelNum}]`;
|
|
14098
|
+
}
|
|
14099
|
+
}
|
|
14100
|
+
return `[${capitalize(node.role)}]`;
|
|
14101
|
+
}
|
|
14102
|
+
function buildContextText(node, domContext) {
|
|
14103
|
+
if (node.name) {
|
|
14104
|
+
return ` "${node.name}"`;
|
|
14105
|
+
}
|
|
14106
|
+
if (domContext) {
|
|
14107
|
+
const tagPart = `<${domContext.tag}`;
|
|
14108
|
+
const classPart = domContext.classes && domContext.classes.length > 0 ? `.${domContext.classes.slice(0, 3).join(".")}` : "";
|
|
14109
|
+
const previewPart = domContext.preview && !domContext.text ? ` "${domContext.preview}"` : "";
|
|
14110
|
+
return ` ${tagPart}${classPart}>${previewPart}`;
|
|
14111
|
+
}
|
|
14112
|
+
return "";
|
|
14113
|
+
}
|
|
14114
|
+
function buildKeyAttributesText(node, domContext) {
|
|
14115
|
+
if (!domContext?.attributes) return "";
|
|
14116
|
+
const shown = Object.entries(domContext.attributes).filter(([, value]) => Boolean(node.name) && value === node.name).map(([name]) => name);
|
|
14117
|
+
if (node.value !== void 0 && node.value !== "") shown.push("value");
|
|
14118
|
+
const items = keyAttributeItems(domContext.tag, domContext.attributes, new Set(shown));
|
|
14119
|
+
return items.length > 0 ? ` ${items.join(" ")}` : "";
|
|
14120
|
+
}
|
|
14121
|
+
function buildPropertiesText(node) {
|
|
14122
|
+
const props = [];
|
|
14123
|
+
if (node.value !== void 0 && node.value !== "") props.push(`value: "${node.value}"`);
|
|
14124
|
+
const checked = node.properties?.["checked"];
|
|
14125
|
+
if (checked !== void 0) {
|
|
14126
|
+
props.push(checked === "mixed" ? "partly checked" : checked ? "checked" : "unchecked");
|
|
14127
|
+
}
|
|
14128
|
+
const expanded = node.properties?.["expanded"];
|
|
14129
|
+
if (expanded !== void 0) props.push(expanded ? "expanded" : "collapsed");
|
|
14130
|
+
if (node.focusable) props.push("focusable");
|
|
14131
|
+
if (node.focused) props.push("focused");
|
|
14132
|
+
if (node.disabled) props.push("disabled");
|
|
14133
|
+
if (node.required) props.push("required");
|
|
14134
|
+
return props.length > 0 ? ` (${props.join(", ")})` : "";
|
|
14135
|
+
}
|
|
14136
|
+
function formatSemanticNodeWithContext(data) {
|
|
14137
|
+
const { node, domContext } = data;
|
|
14138
|
+
const roleText = buildRoleText(node);
|
|
14139
|
+
const contextText = buildContextText(node, domContext);
|
|
14140
|
+
const keysText = buildKeyAttributesText(node, domContext);
|
|
14141
|
+
const propsText = buildPropertiesText(node);
|
|
14142
|
+
const inferredText = node.inferred ? " (inferred from DOM)" : "";
|
|
14143
|
+
const line = `${roleText}${contextText}${keysText}${propsText}${inferredText}`;
|
|
14144
|
+
if (domContext?.text) return joinLines(line, elementTextLine(domContext.text));
|
|
14145
|
+
if (domContext?.childCount !== void 0 && !node.name) {
|
|
14146
|
+
return joinLines(line, emptyElementLine(domContext.children ?? [], domContext.childCount));
|
|
14147
|
+
}
|
|
14148
|
+
return line;
|
|
14149
|
+
}
|
|
14150
|
+
function resolveNodeWithFallback(a11yNode, domContext, nodeId) {
|
|
14151
|
+
if (a11yNode) return withSecretMasked(a11yNode, domContext);
|
|
14152
|
+
if (domContext && nodeId) return synthesizeA11yNode(domContext, nodeId);
|
|
14153
|
+
return null;
|
|
14154
|
+
}
|
|
14155
|
+
function withSecretMasked(node, domContext) {
|
|
14156
|
+
if (!domContext?.sensitive || !node.value) return node;
|
|
14157
|
+
return { ...node, value: MASKED_VALUE };
|
|
14158
|
+
}
|
|
14159
|
+
|
|
14160
|
+
// src/commands/dom/a11y.ts
|
|
14161
|
+
init_errors3();
|
|
14162
|
+
init_messages();
|
|
14163
|
+
|
|
14164
|
+
// src/telemetry/a11y.ts
|
|
14165
|
+
init_errors3();
|
|
14166
|
+
init_messages();
|
|
14167
|
+
init_client();
|
|
14168
|
+
init_elementInfo();
|
|
14169
|
+
|
|
14170
|
+
// src/runtime/dom/frameLayout.ts
|
|
14171
|
+
init_frameScopedConnection();
|
|
14172
|
+
init_logging();
|
|
14173
|
+
init_errors();
|
|
14174
|
+
var log19 = createLogger("dom");
|
|
14175
|
+
function childFrameIds(tree) {
|
|
14176
|
+
return (tree?.childFrames ?? []).flatMap((child) => [child.frame.id, ...childFrameIds(child)]);
|
|
14177
|
+
}
|
|
14178
|
+
|
|
14179
|
+
// src/telemetry/a11y.ts
|
|
14180
|
+
init_concurrency();
|
|
14181
|
+
init_exitCodes();
|
|
14182
|
+
init_levenshtein();
|
|
14183
|
+
function buildTreeFromRawNodes(rawNodes) {
|
|
14184
|
+
const rawById = new Map(rawNodes.map((rawNode) => [rawNode.nodeId, rawNode]));
|
|
14185
|
+
const rootRaw = rawNodes.find((rawNode) => !rawNode.ignored);
|
|
14186
|
+
if (!rootRaw) {
|
|
14187
|
+
throw new CommandError(
|
|
14188
|
+
"No root node found in accessibility tree",
|
|
14189
|
+
{ suggestion: "The page may not be fully loaded. Wait and retry." },
|
|
14190
|
+
EXIT_CODES.SOFTWARE_ERROR
|
|
14191
|
+
);
|
|
14192
|
+
}
|
|
13527
14193
|
const nodes = /* @__PURE__ */ new Map();
|
|
13528
14194
|
const visit = (rawNode) => {
|
|
13529
14195
|
if (nodes.has(rawNode.nodeId)) return;
|
|
@@ -13562,11 +14228,60 @@ async function collectA11yTree() {
|
|
|
13562
14228
|
EXIT_CODES.SOFTWARE_ERROR
|
|
13563
14229
|
);
|
|
13564
14230
|
}
|
|
13565
|
-
|
|
14231
|
+
const tree = buildTreeFromRawNodes([
|
|
14232
|
+
...result.nodes,
|
|
14233
|
+
...await collectFrameNodes(result.nodes)
|
|
14234
|
+
]);
|
|
14235
|
+
return maskSecretValues(tree, await sensitiveNodeIds(tree));
|
|
13566
14236
|
} finally {
|
|
13567
14237
|
await callCDP("Accessibility.disable", {});
|
|
13568
14238
|
}
|
|
13569
14239
|
}
|
|
14240
|
+
var SENSITIVE_CHECK_CONCURRENCY = 10;
|
|
14241
|
+
async function sensitiveNodeIds(tree) {
|
|
14242
|
+
const candidates = [...tree.nodes.values()].filter(
|
|
14243
|
+
(node) => node.value !== void 0 && node.backendDOMNodeId !== void 0
|
|
14244
|
+
);
|
|
14245
|
+
const limiter = new ConcurrencyLimiter(SENSITIVE_CHECK_CONCURRENCY);
|
|
14246
|
+
const checked = await Promise.all(
|
|
14247
|
+
candidates.map(
|
|
14248
|
+
(node) => limiter.run(
|
|
14249
|
+
async () => await isSensitiveField(node.backendDOMNodeId ?? 0) ? node.nodeId : null
|
|
14250
|
+
)
|
|
14251
|
+
)
|
|
14252
|
+
);
|
|
14253
|
+
return new Set(checked.filter((id) => id !== null));
|
|
14254
|
+
}
|
|
14255
|
+
async function isSensitiveField(backendNodeId) {
|
|
14256
|
+
const objectGroup = "bdg-a11y-secret";
|
|
14257
|
+
const resolved = await callCDP("DOM.resolveNode", { backendNodeId, objectGroup });
|
|
14258
|
+
const objectId = resolved.data?.result?.object?.objectId;
|
|
14259
|
+
if (!objectId) return true;
|
|
14260
|
+
try {
|
|
14261
|
+
const response = await callCDP("Runtime.callFunctionOn", {
|
|
14262
|
+
objectId,
|
|
14263
|
+
functionDeclaration: `function () { return this.nodeType !== 1 || (${SENSITIVE_FIELD_JS})(this); }`,
|
|
14264
|
+
returnByValue: true
|
|
14265
|
+
});
|
|
14266
|
+
const value = response.data?.result?.result?.value;
|
|
14267
|
+
return value !== false;
|
|
14268
|
+
} finally {
|
|
14269
|
+
await callCDP("Runtime.releaseObjectGroup", { objectGroup });
|
|
14270
|
+
}
|
|
14271
|
+
}
|
|
14272
|
+
function maskSecretValues(tree, secretIds) {
|
|
14273
|
+
const hide = (nodeId, inside) => {
|
|
14274
|
+
const node = tree.nodes.get(nodeId);
|
|
14275
|
+
if (!node) return;
|
|
14276
|
+
const { name: _name, value: _value, ...rest } = node;
|
|
14277
|
+
const masked = inside ? rest : { ...node, ...node.value !== void 0 && { value: MASKED_VALUE } };
|
|
14278
|
+
tree.nodes.set(nodeId, masked);
|
|
14279
|
+
if (tree.root.nodeId === nodeId) tree.root = masked;
|
|
14280
|
+
node.childIds?.forEach((childId) => hide(childId, true));
|
|
14281
|
+
};
|
|
14282
|
+
secretIds.forEach((nodeId) => hide(nodeId, false));
|
|
14283
|
+
return tree;
|
|
14284
|
+
}
|
|
13570
14285
|
async function collectFrameNodes(pageNodes) {
|
|
13571
14286
|
const tree = (await callCDP("Page.getFrameTree", {})).data?.result;
|
|
13572
14287
|
const known = [...pageNodes];
|
|
@@ -13913,7 +14628,7 @@ function formatA11yNodeWithContext(data) {
|
|
|
13913
14628
|
}
|
|
13914
14629
|
function formatA11yNodeOneLine(node) {
|
|
13915
14630
|
const parts = [];
|
|
13916
|
-
parts.push(`[${
|
|
14631
|
+
parts.push(`[${capitalize2(node.role)}]`);
|
|
13917
14632
|
if (node.name) {
|
|
13918
14633
|
parts.push(`"${node.name}"`);
|
|
13919
14634
|
}
|
|
@@ -13942,7 +14657,7 @@ function formatA11yNodeOneLine(node) {
|
|
|
13942
14657
|
}
|
|
13943
14658
|
return parts.join(" ");
|
|
13944
14659
|
}
|
|
13945
|
-
function
|
|
14660
|
+
function capitalize2(str) {
|
|
13946
14661
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
13947
14662
|
}
|
|
13948
14663
|
function truncate(str, maxLen) {
|
|
@@ -14051,7 +14766,7 @@ async function handleA11yDescribe(selectorOrIndex, options) {
|
|
|
14051
14766
|
if (domNodeId) {
|
|
14052
14767
|
domContext = await getDomContext({ backendNodeId: domNodeId });
|
|
14053
14768
|
}
|
|
14054
|
-
return { node, domContext };
|
|
14769
|
+
return { node: withSecretMasked(node, domContext), domContext };
|
|
14055
14770
|
}
|
|
14056
14771
|
if (options.json) {
|
|
14057
14772
|
await runJsonCommand(fetchA11yNodeData);
|
|
@@ -14118,13 +14833,7 @@ function formatDomQuery(data) {
|
|
|
14118
14833
|
const { count, nodes, selector } = data;
|
|
14119
14834
|
const fmt = new OutputFormatter();
|
|
14120
14835
|
const nodeLines = nodes.slice(0, QUERY_DISPLAY_LIMIT).map((node) => {
|
|
14121
|
-
const attributes =
|
|
14122
|
-
node.id && ` id="${node.id}"`,
|
|
14123
|
-
node.name && ` name="${node.name}"`,
|
|
14124
|
-
node.type && ` type="${node.type}"`,
|
|
14125
|
-
node.value !== void 0 && ` value="${node.value}"`,
|
|
14126
|
-
node.classes?.length && ` class="${node.classes.join(" ")}"`
|
|
14127
|
-
].filter(Boolean).join("");
|
|
14836
|
+
const attributes = queryTagAttributes(node).map((item) => ` ${item}`).join("");
|
|
14128
14837
|
const context = node.context ? ` (in ${node.context})` : "";
|
|
14129
14838
|
const preview = node.preview ? ` ${node.preview}` : "";
|
|
14130
14839
|
const position = node.inViewport && viewportPositionHint(node.inViewport, node.clippedBy);
|
|
@@ -14134,6 +14843,18 @@ function formatDomQuery(data) {
|
|
|
14134
14843
|
const exampleIndex = nodes[0]?.index ?? 0;
|
|
14135
14844
|
return fmt.text(`Found ${count} node${count === 1 ? "" : "s"} matching "${selector}":`).list(nodeLines).list(count > QUERY_DISPLAY_LIMIT ? [moreMatchesNote(count - QUERY_DISPLAY_LIMIT)] : []).tip(queryNextSteps(exampleIndex)).build();
|
|
14136
14845
|
}
|
|
14846
|
+
function queryTagAttributes(node) {
|
|
14847
|
+
const identifying = {
|
|
14848
|
+
...node.name && { name: node.name },
|
|
14849
|
+
...node.type && { type: node.type },
|
|
14850
|
+
...node.value !== void 0 && { value: node.value }
|
|
14851
|
+
};
|
|
14852
|
+
return [
|
|
14853
|
+
...node.id ? [`id="${node.id}"`] : [],
|
|
14854
|
+
...keyAttributeItems(node.tag ?? "", { ...identifying, ...node.attributes }),
|
|
14855
|
+
...node.classes?.length ? [`class="${node.classes.join(" ")}"`] : []
|
|
14856
|
+
];
|
|
14857
|
+
}
|
|
14137
14858
|
function formatDomGet(data) {
|
|
14138
14859
|
const { nodes } = data;
|
|
14139
14860
|
if (nodes.length === 1) {
|
|
@@ -14820,192 +15541,33 @@ async function withReadyState(frames) {
|
|
|
14820
15541
|
|
|
14821
15542
|
// src/commands/dom/get.ts
|
|
14822
15543
|
init_helpers();
|
|
14823
|
-
|
|
14824
|
-
|
|
14825
|
-
|
|
14826
|
-
|
|
14827
|
-
|
|
14828
|
-
|
|
14829
|
-
|
|
14830
|
-
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
// Headings
|
|
14834
|
-
h1: "heading",
|
|
14835
|
-
h2: "heading",
|
|
14836
|
-
h3: "heading",
|
|
14837
|
-
h4: "heading",
|
|
14838
|
-
h5: "heading",
|
|
14839
|
-
h6: "heading",
|
|
14840
|
-
// Landmarks
|
|
14841
|
-
nav: "navigation",
|
|
14842
|
-
main: "main",
|
|
14843
|
-
header: "banner",
|
|
14844
|
-
footer: "contentinfo",
|
|
14845
|
-
aside: "complementary",
|
|
14846
|
-
section: "region",
|
|
14847
|
-
article: "article",
|
|
14848
|
-
form: "form",
|
|
14849
|
-
search: "search",
|
|
14850
|
-
// Media
|
|
14851
|
-
img: "image",
|
|
14852
|
-
figure: "figure",
|
|
14853
|
-
figcaption: "caption",
|
|
14854
|
-
video: "video",
|
|
14855
|
-
audio: "audio",
|
|
14856
|
-
// Lists
|
|
14857
|
-
ul: "list",
|
|
14858
|
-
ol: "list",
|
|
14859
|
-
li: "listitem",
|
|
14860
|
-
dl: "list",
|
|
14861
|
-
dt: "term",
|
|
14862
|
-
dd: "definition",
|
|
14863
|
-
menu: "menu",
|
|
14864
|
-
menuitem: "menuitem",
|
|
14865
|
-
// Tables
|
|
14866
|
-
table: "table",
|
|
14867
|
-
thead: "rowgroup",
|
|
14868
|
-
tbody: "rowgroup",
|
|
14869
|
-
tfoot: "rowgroup",
|
|
14870
|
-
tr: "row",
|
|
14871
|
-
th: "columnheader",
|
|
14872
|
-
td: "cell",
|
|
14873
|
-
// Other semantic elements
|
|
14874
|
-
dialog: "dialog",
|
|
14875
|
-
details: "group",
|
|
14876
|
-
summary: "button",
|
|
14877
|
-
progress: "progressbar",
|
|
14878
|
-
meter: "meter",
|
|
14879
|
-
output: "status",
|
|
14880
|
-
address: "group",
|
|
14881
|
-
blockquote: "blockquote",
|
|
14882
|
-
code: "code",
|
|
14883
|
-
pre: "group",
|
|
14884
|
-
hr: "separator"
|
|
14885
|
-
};
|
|
14886
|
-
var DEFAULT_ROLE = "generic";
|
|
14887
|
-
var MAX_NAME_LENGTH = 100;
|
|
14888
|
-
function inferRoleFromTag(tag) {
|
|
14889
|
-
const normalizedTag = tag.toLowerCase();
|
|
14890
|
-
return IMPLICIT_ARIA_ROLES[normalizedTag] ?? DEFAULT_ROLE;
|
|
15544
|
+
init_errors3();
|
|
15545
|
+
init_messages();
|
|
15546
|
+
init_exitCodes();
|
|
15547
|
+
var DOM_GET_DEFAULT_SELECTOR = "body";
|
|
15548
|
+
async function targetNodeId(selectorOrIndex, index) {
|
|
15549
|
+
const resolver = DomElementResolver.getInstance();
|
|
15550
|
+
if (resolver.isNumericIndex(selectorOrIndex)) {
|
|
15551
|
+
return (await resolver.getNodeIdForIndex(Number(selectorOrIndex))).nodeId;
|
|
15552
|
+
}
|
|
15553
|
+
return selectMatch(selectorOrIndex, index ?? 0);
|
|
14891
15554
|
}
|
|
14892
|
-
function
|
|
14893
|
-
const
|
|
14894
|
-
const
|
|
14895
|
-
|
|
14896
|
-
|
|
15555
|
+
async function semanticElement(backendNodeId, full) {
|
|
15556
|
+
const ref = { backendNodeId };
|
|
15557
|
+
const [a11yNode, domContext] = await Promise.all([
|
|
15558
|
+
resolveA11yNode(ref),
|
|
15559
|
+
getDomContext(ref, { full })
|
|
15560
|
+
]);
|
|
15561
|
+
const node = resolveNodeWithFallback(a11yNode, domContext, backendNodeId);
|
|
15562
|
+
if (!node) {
|
|
15563
|
+
const err = nodeIdNotFoundError(backendNodeId);
|
|
15564
|
+
throw new CommandError(
|
|
15565
|
+
err.message,
|
|
15566
|
+
{ suggestion: err.suggestion },
|
|
15567
|
+
EXIT_CODES.RESOURCE_NOT_FOUND
|
|
15568
|
+
);
|
|
14897
15569
|
}
|
|
14898
|
-
return
|
|
14899
|
-
}
|
|
14900
|
-
function synthesizeA11yNode(domContext, nodeId) {
|
|
14901
|
-
const role = inferRoleFromTag(domContext.tag);
|
|
14902
|
-
const node = {
|
|
14903
|
-
nodeId: String(nodeId),
|
|
14904
|
-
role,
|
|
14905
|
-
inferred: true,
|
|
14906
|
-
backendDOMNodeId: nodeId
|
|
14907
|
-
};
|
|
14908
|
-
if (domContext.preview) {
|
|
14909
|
-
const name = domContext.preview.length > MAX_NAME_LENGTH ? domContext.preview.slice(0, MAX_NAME_LENGTH - 3) + "..." : domContext.preview;
|
|
14910
|
-
node.name = name;
|
|
14911
|
-
}
|
|
14912
|
-
const headingLevel = extractHeadingLevel(domContext.tag);
|
|
14913
|
-
if (headingLevel !== null) {
|
|
14914
|
-
node.properties = { level: headingLevel };
|
|
14915
|
-
}
|
|
14916
|
-
return node;
|
|
14917
|
-
}
|
|
14918
|
-
|
|
14919
|
-
// src/commands/dom/semanticUtils.ts
|
|
14920
|
-
init_formatting();
|
|
14921
|
-
init_commands();
|
|
14922
|
-
function capitalize2(str) {
|
|
14923
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
14924
|
-
}
|
|
14925
|
-
function buildRoleText(node) {
|
|
14926
|
-
if (node.role.toLowerCase() === "heading" && node.properties?.["level"] !== void 0) {
|
|
14927
|
-
const level = node.properties["level"];
|
|
14928
|
-
const levelNum = typeof level === "number" ? level : Number(level);
|
|
14929
|
-
if (!isNaN(levelNum)) {
|
|
14930
|
-
return `[Heading L${levelNum}]`;
|
|
14931
|
-
}
|
|
14932
|
-
}
|
|
14933
|
-
return `[${capitalize2(node.role)}]`;
|
|
14934
|
-
}
|
|
14935
|
-
function buildContextText(node, domContext) {
|
|
14936
|
-
if (node.name) {
|
|
14937
|
-
return ` "${node.name}"`;
|
|
14938
|
-
}
|
|
14939
|
-
if (domContext) {
|
|
14940
|
-
const tagPart = `<${domContext.tag}`;
|
|
14941
|
-
const classPart = domContext.classes && domContext.classes.length > 0 ? `.${domContext.classes.slice(0, 3).join(".")}` : "";
|
|
14942
|
-
const previewPart = domContext.preview && !domContext.text ? ` "${domContext.preview}"` : "";
|
|
14943
|
-
return ` ${tagPart}${classPart}>${previewPart}`;
|
|
14944
|
-
}
|
|
14945
|
-
return "";
|
|
14946
|
-
}
|
|
14947
|
-
function buildPropertiesText(node) {
|
|
14948
|
-
const props = [];
|
|
14949
|
-
if (node.value !== void 0 && node.value !== "") props.push(`value: "${node.value}"`);
|
|
14950
|
-
const checked = node.properties?.["checked"];
|
|
14951
|
-
if (checked !== void 0) {
|
|
14952
|
-
props.push(checked === "mixed" ? "partly checked" : checked ? "checked" : "unchecked");
|
|
14953
|
-
}
|
|
14954
|
-
const expanded = node.properties?.["expanded"];
|
|
14955
|
-
if (expanded !== void 0) props.push(expanded ? "expanded" : "collapsed");
|
|
14956
|
-
if (node.focusable) props.push("focusable");
|
|
14957
|
-
if (node.focused) props.push("focused");
|
|
14958
|
-
if (node.disabled) props.push("disabled");
|
|
14959
|
-
if (node.required) props.push("required");
|
|
14960
|
-
return props.length > 0 ? ` (${props.join(", ")})` : "";
|
|
14961
|
-
}
|
|
14962
|
-
function formatSemanticNodeWithContext(data) {
|
|
14963
|
-
const { node, domContext } = data;
|
|
14964
|
-
const roleText = buildRoleText(node);
|
|
14965
|
-
const contextText = buildContextText(node, domContext);
|
|
14966
|
-
const propsText = buildPropertiesText(node);
|
|
14967
|
-
const inferredText = node.inferred ? " (inferred from DOM)" : "";
|
|
14968
|
-
const line = `${roleText}${contextText}${propsText}${inferredText}`;
|
|
14969
|
-
if (domContext?.text) return joinLines(line, elementTextLine(domContext.text));
|
|
14970
|
-
if (domContext?.childCount !== void 0 && !node.name) {
|
|
14971
|
-
return joinLines(line, emptyElementLine(domContext.children ?? [], domContext.childCount));
|
|
14972
|
-
}
|
|
14973
|
-
return line;
|
|
14974
|
-
}
|
|
14975
|
-
function resolveNodeWithFallback(a11yNode, domContext, nodeId) {
|
|
14976
|
-
if (a11yNode) return a11yNode;
|
|
14977
|
-
if (domContext && nodeId) return synthesizeA11yNode(domContext, nodeId);
|
|
14978
|
-
return null;
|
|
14979
|
-
}
|
|
14980
|
-
|
|
14981
|
-
// src/commands/dom/get.ts
|
|
14982
|
-
init_errors3();
|
|
14983
|
-
init_messages();
|
|
14984
|
-
init_exitCodes();
|
|
14985
|
-
var DOM_GET_DEFAULT_SELECTOR = "body";
|
|
14986
|
-
async function targetNodeId(selectorOrIndex, index) {
|
|
14987
|
-
const resolver = DomElementResolver.getInstance();
|
|
14988
|
-
if (resolver.isNumericIndex(selectorOrIndex)) {
|
|
14989
|
-
return (await resolver.getNodeIdForIndex(Number(selectorOrIndex))).nodeId;
|
|
14990
|
-
}
|
|
14991
|
-
return selectMatch(selectorOrIndex, index ?? 0);
|
|
14992
|
-
}
|
|
14993
|
-
async function semanticElement(backendNodeId, full) {
|
|
14994
|
-
const ref = { backendNodeId };
|
|
14995
|
-
const [a11yNode, domContext] = await Promise.all([
|
|
14996
|
-
resolveA11yNode(ref),
|
|
14997
|
-
getDomContext(ref, { full })
|
|
14998
|
-
]);
|
|
14999
|
-
const node = resolveNodeWithFallback(a11yNode, domContext, backendNodeId);
|
|
15000
|
-
if (!node) {
|
|
15001
|
-
const err = nodeIdNotFoundError(backendNodeId);
|
|
15002
|
-
throw new CommandError(
|
|
15003
|
-
err.message,
|
|
15004
|
-
{ suggestion: err.suggestion },
|
|
15005
|
-
EXIT_CODES.RESOURCE_NOT_FOUND
|
|
15006
|
-
);
|
|
15007
|
-
}
|
|
15008
|
-
return { node, domContext };
|
|
15570
|
+
return { node, domContext };
|
|
15009
15571
|
}
|
|
15010
15572
|
async function handleRawGet(selectorOrIndex, options) {
|
|
15011
15573
|
await runCommand(
|
|
@@ -15078,6 +15640,441 @@ async function handleDomGet(selectorOrIndex, options) {
|
|
|
15078
15640
|
else await handleSemanticGet(target, options);
|
|
15079
15641
|
}
|
|
15080
15642
|
|
|
15643
|
+
// src/commands/dom/inspect.ts
|
|
15644
|
+
init_client();
|
|
15645
|
+
|
|
15646
|
+
// src/utils/color.ts
|
|
15647
|
+
var D50_WHITE = [0.3457 / 0.3585, 1, (1 - 0.3457 - 0.3585) / 0.3585];
|
|
15648
|
+
|
|
15649
|
+
// src/utils/cssValues.ts
|
|
15650
|
+
function sidesShorthand(sides) {
|
|
15651
|
+
const [top, right, bottom, left] = sides.map(String);
|
|
15652
|
+
if (top === right && right === bottom && bottom === left) return `${top}`;
|
|
15653
|
+
if (top === bottom && right === left) return `${top} ${right}`;
|
|
15654
|
+
if (right === left) return `${top} ${right} ${bottom}`;
|
|
15655
|
+
return `${top} ${right} ${bottom} ${left}`;
|
|
15656
|
+
}
|
|
15657
|
+
function shadowText(layer, withSpread = true) {
|
|
15658
|
+
const lengths = [layer.x, layer.y, layer.blur, ...withSpread ? [layer.spread] : []];
|
|
15659
|
+
return `${layer.inset ? "inset " : ""}${layer.color} ${lengths.join(" ")}`;
|
|
15660
|
+
}
|
|
15661
|
+
|
|
15662
|
+
// src/runtime/dom/inspectLayoutModel.ts
|
|
15663
|
+
function containerKind(display) {
|
|
15664
|
+
if (display === void 0) return void 0;
|
|
15665
|
+
if (/(^|-)flex$/.test(display) || display === "-webkit-box") return "flex";
|
|
15666
|
+
return /(^|-)grid$/.test(display) ? "grid" : void 0;
|
|
15667
|
+
}
|
|
15668
|
+
|
|
15669
|
+
// src/runtime/dom/inspectTree.ts
|
|
15670
|
+
var DEFAULT_TREE_DEPTH = 2;
|
|
15671
|
+
var DEFAULT_TREE_LIMIT = 20;
|
|
15672
|
+
|
|
15673
|
+
// src/ui/formatters/inspect.ts
|
|
15674
|
+
init_formatting();
|
|
15675
|
+
init_commands();
|
|
15676
|
+
init_strings();
|
|
15677
|
+
var PARENT_LABEL_MAX = 40;
|
|
15678
|
+
var LABEL_WIDTH = 7;
|
|
15679
|
+
var ALL_LINE_WIDTH = 100;
|
|
15680
|
+
function groupLine(label, parts) {
|
|
15681
|
+
const shown = parts.filter((part) => Boolean(part));
|
|
15682
|
+
return shown.length > 0 ? `${label.padEnd(LABEL_WIDTH)}${shown.join(" \xB7 ")}` : void 0;
|
|
15683
|
+
}
|
|
15684
|
+
function inspectHeader(data) {
|
|
15685
|
+
const display = data.layout?.display ?? data.all?.["display"];
|
|
15686
|
+
const kind = containerKind(display);
|
|
15687
|
+
return [
|
|
15688
|
+
data.element,
|
|
15689
|
+
data.content !== void 0 && `"${data.content}"`,
|
|
15690
|
+
data.placeholder !== void 0 && `placeholder "${data.placeholder}"`,
|
|
15691
|
+
data.rect && `${data.rect.w}x${data.rect.h} @${data.rect.x},${data.rect.y}`,
|
|
15692
|
+
kind && `[${kind}]`,
|
|
15693
|
+
...inspectVisibilityBadges(data.visibility),
|
|
15694
|
+
data.context && `in ${data.context}`,
|
|
15695
|
+
data.theme === "dark" && inspectDarkThemeBadge(),
|
|
15696
|
+
data.animating && inspectAnimatingBadge(data.animating)
|
|
15697
|
+
].filter(Boolean).join(" ");
|
|
15698
|
+
}
|
|
15699
|
+
function zero(sides) {
|
|
15700
|
+
return sides.every((side) => side === 0);
|
|
15701
|
+
}
|
|
15702
|
+
function limitParts(kind, limits) {
|
|
15703
|
+
if (!limits) return [];
|
|
15704
|
+
return [
|
|
15705
|
+
limits.w !== void 0 ? `${kind}-w ${limits.w}` : "",
|
|
15706
|
+
limits.h !== void 0 ? `${kind}-h ${limits.h}` : ""
|
|
15707
|
+
].filter(Boolean);
|
|
15708
|
+
}
|
|
15709
|
+
function boxLine(box) {
|
|
15710
|
+
const padded = !zero(box.padding) || !zero(box.border);
|
|
15711
|
+
return groupLine("box", [
|
|
15712
|
+
!zero(box.margin) && `m ${sidesShorthand(box.margin)}`,
|
|
15713
|
+
!zero(box.padding) && `p ${sidesShorthand(box.padding)}`,
|
|
15714
|
+
!zero(box.border) && `b ${sidesShorthand(box.border)}`,
|
|
15715
|
+
padded && box.sizing === "content-box" && "sizing content-box",
|
|
15716
|
+
...limitParts("min", box.min),
|
|
15717
|
+
...limitParts("max", box.max),
|
|
15718
|
+
box.overflow && `overflow ${box.overflow}`,
|
|
15719
|
+
box.scroll && `scroll ${box.scroll.w}x${box.scroll.h}`
|
|
15720
|
+
]);
|
|
15721
|
+
}
|
|
15722
|
+
function containerParts(container) {
|
|
15723
|
+
const gap = Array.isArray(container.gap) ? container.gap.join(" ") : container.gap;
|
|
15724
|
+
return [
|
|
15725
|
+
container.wrap && `wrap ${container.wrap}`,
|
|
15726
|
+
container.columns && `cols ${container.columns}`,
|
|
15727
|
+
container.rows && `rows ${container.rows}`,
|
|
15728
|
+
container.justify && `justify ${container.justify}`,
|
|
15729
|
+
container.align && `align ${container.align}`,
|
|
15730
|
+
gap !== void 0 && `gap ${gap}`
|
|
15731
|
+
].filter((part) => Boolean(part));
|
|
15732
|
+
}
|
|
15733
|
+
function displayWords(display, direction) {
|
|
15734
|
+
return direction ? `${display} ${direction}` : display;
|
|
15735
|
+
}
|
|
15736
|
+
function layoutLine(layout) {
|
|
15737
|
+
return groupLine("layout", [
|
|
15738
|
+
displayWords(layout.display, layout.direction),
|
|
15739
|
+
layout.position && `position ${layout.position}`,
|
|
15740
|
+
layout.inset && `inset ${sidesShorthand(layout.inset)}`,
|
|
15741
|
+
layout.z !== void 0 && `z ${layout.z}`,
|
|
15742
|
+
...containerParts(layout),
|
|
15743
|
+
layout.flex && `flex ${layout.flex}`,
|
|
15744
|
+
layout.self && `self ${layout.self}`,
|
|
15745
|
+
layout.order !== void 0 && `order ${layout.order}`,
|
|
15746
|
+
layout.area && `area ${layout.area}`,
|
|
15747
|
+
layout.valign && `valign ${layout.valign}`,
|
|
15748
|
+
layout.float && `float ${layout.float}`
|
|
15749
|
+
]);
|
|
15750
|
+
}
|
|
15751
|
+
function parentLine(layout) {
|
|
15752
|
+
const parent = layout.parent;
|
|
15753
|
+
const offsets = layout.inParent;
|
|
15754
|
+
const gaps = Object.entries(layout.siblings ?? {}).map(([side, gap]) => `${side[0]}${gap}`);
|
|
15755
|
+
const parentWords = parent && [
|
|
15756
|
+
truncateByLength(parent.element, PARENT_LABEL_MAX),
|
|
15757
|
+
displayWords(parent.display, parent.direction),
|
|
15758
|
+
...containerParts(parent),
|
|
15759
|
+
parent.textAlign && `text-align ${parent.textAlign}`
|
|
15760
|
+
];
|
|
15761
|
+
return groupLine("parent", [
|
|
15762
|
+
parentWords?.filter(Boolean).join(" "),
|
|
15763
|
+
offsets && `in-parent l${offsets.left} t${offsets.top} r${offsets.right} b${offsets.bottom}`,
|
|
15764
|
+
gaps.length > 0 && `sib ${gaps.join(" ")}`
|
|
15765
|
+
]);
|
|
15766
|
+
}
|
|
15767
|
+
function fontParts(text) {
|
|
15768
|
+
const loaded = [
|
|
15769
|
+
text.rendered && `rendered "${text.rendered}"`,
|
|
15770
|
+
text.webfont && "webfont loaded"
|
|
15771
|
+
].filter(Boolean);
|
|
15772
|
+
const family = text.family && [text.family, loaded.length > 0 && `(${loaded.join(", ")})`];
|
|
15773
|
+
const size = text.size !== void 0 && (text.lineHeight !== void 0 ? `${text.size}/${text.lineHeight}` : `${text.size}`);
|
|
15774
|
+
if (family && text.weight !== void 0 && size) {
|
|
15775
|
+
return [
|
|
15776
|
+
[...family, text.weight, text.style, size].filter((part) => part !== void 0 && part !== false && part !== "").join(" ")
|
|
15777
|
+
];
|
|
15778
|
+
}
|
|
15779
|
+
return [
|
|
15780
|
+
family && `font ${family.filter(Boolean).join(" ")}`,
|
|
15781
|
+
text.weight !== void 0 && `weight ${text.weight}`,
|
|
15782
|
+
text.style && `style ${text.style}`,
|
|
15783
|
+
size && `size ${size}`,
|
|
15784
|
+
!size && text.lineHeight !== void 0 && `line-height ${text.lineHeight}`
|
|
15785
|
+
].filter((part) => Boolean(part));
|
|
15786
|
+
}
|
|
15787
|
+
function contrastText(contrast) {
|
|
15788
|
+
if (!contrast) return void 0;
|
|
15789
|
+
return [
|
|
15790
|
+
`contrast ${contrast.ratio} ${contrast.level}`,
|
|
15791
|
+
contrast.inherited && `on ${contrast.background}`,
|
|
15792
|
+
contrast.overImage && "(over image)",
|
|
15793
|
+
contrast.opacity !== void 0 && `(faded: opacity ${contrast.opacity})`
|
|
15794
|
+
].filter(Boolean).join(" ");
|
|
15795
|
+
}
|
|
15796
|
+
function textLine(text) {
|
|
15797
|
+
return groupLine("text", [
|
|
15798
|
+
...fontParts(text),
|
|
15799
|
+
text.color && `color ${text.color}`,
|
|
15800
|
+
contrastText(text.contrast),
|
|
15801
|
+
text.align && `align ${text.align}`,
|
|
15802
|
+
text.transform && `transform ${text.transform}`,
|
|
15803
|
+
text.tracking !== void 0 && `tracking ${text.tracking}`,
|
|
15804
|
+
text.decoration && `decoration ${text.decoration}`,
|
|
15805
|
+
text.whiteSpace && `ws ${text.whiteSpace}`,
|
|
15806
|
+
text.overflow && `text-overflow ${text.overflow}`,
|
|
15807
|
+
text.clamp && `clamp ${text.clamp}`,
|
|
15808
|
+
text.shadow && `shadow ${text.shadow}`,
|
|
15809
|
+
text.features && `features ${text.features}`
|
|
15810
|
+
]);
|
|
15811
|
+
}
|
|
15812
|
+
function fillText(fill) {
|
|
15813
|
+
if (fill.type === "solid") return `bg ${fill.color}`;
|
|
15814
|
+
if (fill.type === "gradient") return `bg-image ${fill.value}`;
|
|
15815
|
+
return `bg-image ${fill.value}${fill.size ? ` size ${fill.size}` : ""}`;
|
|
15816
|
+
}
|
|
15817
|
+
function fillLine(data) {
|
|
15818
|
+
return groupLine("fill", [
|
|
15819
|
+
...(data.fills ?? []).map(fillText),
|
|
15820
|
+
data.opacity !== void 0 && `opacity ${data.opacity}`,
|
|
15821
|
+
data.blend && `blend ${data.blend}`
|
|
15822
|
+
]);
|
|
15823
|
+
}
|
|
15824
|
+
function borderLine(data) {
|
|
15825
|
+
const outline = data.outline;
|
|
15826
|
+
return groupLine("border", [
|
|
15827
|
+
...(data.strokes ?? []).map(
|
|
15828
|
+
(s) => `${s.side === "all" ? "" : `${s.side} `}${s.width} ${s.style} ${s.color}`
|
|
15829
|
+
),
|
|
15830
|
+
data.radius && `radius ${sidesShorthand(data.radius)}`,
|
|
15831
|
+
outline && `outline ${outline.width} ${outline.style} ${outline.color}${outline.offset ? ` offset ${outline.offset}` : ""}`
|
|
15832
|
+
]);
|
|
15833
|
+
}
|
|
15834
|
+
function fxLine(data) {
|
|
15835
|
+
const shadows = (data.effects ?? []).map(
|
|
15836
|
+
(e) => shadowText({ ...e, inset: e.type === "inner-shadow" })
|
|
15837
|
+
);
|
|
15838
|
+
const fx = data.fx ?? {};
|
|
15839
|
+
return groupLine("fx", [
|
|
15840
|
+
shadows.length > 0 && `shadow ${shadows.join(", ")}`,
|
|
15841
|
+
fx.transform && `transform ${fx.transform}`,
|
|
15842
|
+
fx.filter && `filter ${fx.filter}`,
|
|
15843
|
+
fx.backdrop && `backdrop ${fx.backdrop}`,
|
|
15844
|
+
fx.clip && `clip ${fx.clip}`,
|
|
15845
|
+
fx.mask && `mask ${fx.mask}`,
|
|
15846
|
+
fx.animation && `animation ${fx.animation}`
|
|
15847
|
+
]);
|
|
15848
|
+
}
|
|
15849
|
+
function stateLine(data) {
|
|
15850
|
+
const state = data.state ?? {};
|
|
15851
|
+
return groupLine("state", [
|
|
15852
|
+
state.cursor && `cursor ${state.cursor}`,
|
|
15853
|
+
state.pointerEvents && `pointer-events ${state.pointerEvents}`,
|
|
15854
|
+
state.visibility && `visibility ${state.visibility}`,
|
|
15855
|
+
state.userSelect && `select ${state.userSelect}`,
|
|
15856
|
+
state.appearance && `appearance ${state.appearance}`
|
|
15857
|
+
]);
|
|
15858
|
+
}
|
|
15859
|
+
function pseudoText(pseudo) {
|
|
15860
|
+
const shadows = (pseudo.effects ?? []).map(
|
|
15861
|
+
(e) => shadowText({ ...e, inset: e.type === "inner-shadow" })
|
|
15862
|
+
);
|
|
15863
|
+
return [
|
|
15864
|
+
pseudo.type,
|
|
15865
|
+
pseudo.content !== void 0 && `content ${pseudo.content}`,
|
|
15866
|
+
pseudo.display,
|
|
15867
|
+
pseudo.position,
|
|
15868
|
+
pseudo.size && `${pseudo.size.w}x${pseudo.size.h}`,
|
|
15869
|
+
pseudo.color && `color ${pseudo.color}`,
|
|
15870
|
+
pseudo.fontStyle,
|
|
15871
|
+
pseudo.fontWeight !== void 0 && `weight ${pseudo.fontWeight}`,
|
|
15872
|
+
pseudo.contrast && `\xB7 ${contrastText(pseudo.contrast)}`,
|
|
15873
|
+
...(pseudo.fills ?? []).map(fillText),
|
|
15874
|
+
pseudo.radius && `radius ${pseudo.radius}`,
|
|
15875
|
+
shadows.length > 0 && `shadow ${shadows.join(", ")}`,
|
|
15876
|
+
pseudo.transform && `transform ${pseudo.transform}`,
|
|
15877
|
+
pseudo.opacity !== void 0 && `opacity ${pseudo.opacity}`
|
|
15878
|
+
].filter(Boolean).join(" ");
|
|
15879
|
+
}
|
|
15880
|
+
function pseudoLines(pseudo) {
|
|
15881
|
+
return (pseudo ?? []).map(
|
|
15882
|
+
(entry, i) => `${(i === 0 ? "pseudo" : "").padEnd(LABEL_WIDTH)}${pseudoText(entry)}`
|
|
15883
|
+
);
|
|
15884
|
+
}
|
|
15885
|
+
function treeRow(node) {
|
|
15886
|
+
return [
|
|
15887
|
+
node.element,
|
|
15888
|
+
node.count !== void 0 && `\xD7${node.count}`,
|
|
15889
|
+
`${node.w}x${node.h}`,
|
|
15890
|
+
node.layout && `[${node.layout}]`,
|
|
15891
|
+
node.text && `"${node.text}"`,
|
|
15892
|
+
node.childCount !== void 0 && `(${node.childCount})`
|
|
15893
|
+
].filter(Boolean).join(" ");
|
|
15894
|
+
}
|
|
15895
|
+
function treeLines2(nodes, hidden, depth) {
|
|
15896
|
+
const indent = " ".repeat(depth);
|
|
15897
|
+
const lines = nodes.flatMap((node) => [
|
|
15898
|
+
`${indent}${treeRow(node)}`,
|
|
15899
|
+
...treeLines2(node.children ?? [], node.hiddenChildren, depth + 1)
|
|
15900
|
+
]);
|
|
15901
|
+
return hidden ? [...lines, `${indent}(+${hidden} not rendered)`] : lines;
|
|
15902
|
+
}
|
|
15903
|
+
function labelledLines(label, lines) {
|
|
15904
|
+
return lines.map((line, i) => `${i === 0 ? label.padEnd(6) : "".padEnd(6)} ${line}`);
|
|
15905
|
+
}
|
|
15906
|
+
function ruleScope(entry) {
|
|
15907
|
+
return [entry.condition && ` @${entry.condition}`, entry.layer && ` layer ${entry.layer}`].filter(Boolean).join("");
|
|
15908
|
+
}
|
|
15909
|
+
function cascadeBlock(data) {
|
|
15910
|
+
const hints = (data.hints ?? []).map(
|
|
15911
|
+
(hint) => `${hint.property}: ${hint.value} ${hint.kind === "not-inherited" ? "is the browser's" : "has no effect"}: ${hint.reason} \u2192 ${hint.fix} \xB7 in ${hint.source}`
|
|
15912
|
+
);
|
|
15913
|
+
return [
|
|
15914
|
+
...labelledLines("hints", data.hints?.length === 0 ? ["none"] : hints),
|
|
15915
|
+
...labelledLines("rules", (data.rules ?? []).map(ruleLine)),
|
|
15916
|
+
...labelledLines(
|
|
15917
|
+
"why",
|
|
15918
|
+
(data.why ?? []).flatMap((why) => whyLines(why, data))
|
|
15919
|
+
),
|
|
15920
|
+
...data.cascade ? [inspectCascadeNote(data.cascade)] : []
|
|
15921
|
+
];
|
|
15922
|
+
}
|
|
15923
|
+
function ruleLine(rule) {
|
|
15924
|
+
return [
|
|
15925
|
+
`${rule.property} ${truncateByLength(rule.value, CASCADE_VALUE_WIDTH)}`,
|
|
15926
|
+
rule.computed !== void 0 && ` = ${truncateByLength(rule.computed, CASCADE_VALUE_WIDTH)}`,
|
|
15927
|
+
`${rule.important ? " !important" : ""} \u2190 ${rule.source}`,
|
|
15928
|
+
ruleScope(rule),
|
|
15929
|
+
rule.inherited !== void 0 && ` (inherited from ${levelsUp(rule.inherited)})`,
|
|
15930
|
+
rule.overrides && ` over ${rule.overrides.join(", ")}`
|
|
15931
|
+
].filter(Boolean).join("");
|
|
15932
|
+
}
|
|
15933
|
+
function whyLines(why, data) {
|
|
15934
|
+
const entries = why.chain.map((entry) => {
|
|
15935
|
+
const value = `${entry.via ? `${entry.via}: ` : ""}${entry.value}`;
|
|
15936
|
+
const resolved = entry.unset ? ` = invalid: ${entry.unset.join(", ")} not set` : entry.resolved !== void 0 ? ` = ${truncateByLength(entry.resolved, CASCADE_VALUE_WIDTH)}` : "";
|
|
15937
|
+
return [
|
|
15938
|
+
` ${entry.status === "overridden" ? "\u2717" : "\u2713"} `,
|
|
15939
|
+
truncateByLength(value, CASCADE_VALUE_WIDTH) + resolved,
|
|
15940
|
+
entry.important ? " !important" : "",
|
|
15941
|
+
` ${entry.source}${entry.specificity ? ` [${entry.specificity.join(",")}]` : ""}${ruleScope(entry)}`,
|
|
15942
|
+
entry.status === "inherited" ? " (inherited)" : ""
|
|
15943
|
+
].join("");
|
|
15944
|
+
});
|
|
15945
|
+
const variables = (why.variables ?? []).map(
|
|
15946
|
+
(variable) => ` ${variable.name}: ${truncateByLength(variable.value, CASCADE_VALUE_WIDTH)} ${variable.source}${variable.inherited !== void 0 ? ` (inherited from ${levelsUp(variable.inherited)})` : ""}`
|
|
15947
|
+
);
|
|
15948
|
+
return [
|
|
15949
|
+
`${why.property} = ${why.computed}${midTransition(why.property, data.animating) ? ` ${inspectMidTransitionNote()}` : ""}`,
|
|
15950
|
+
...entries.slice(0, 1),
|
|
15951
|
+
...variables,
|
|
15952
|
+
...entries.slice(1),
|
|
15953
|
+
...why.chain.length === 0 ? [" no author declaration: the default or inherited value"] : []
|
|
15954
|
+
];
|
|
15955
|
+
}
|
|
15956
|
+
function midTransition(property, animating) {
|
|
15957
|
+
return (animating ?? []).some(
|
|
15958
|
+
(name) => name === "all" || name === property || property.startsWith(`${name}-`) || name.startsWith(`${property}-`)
|
|
15959
|
+
);
|
|
15960
|
+
}
|
|
15961
|
+
function levelsUp(levels) {
|
|
15962
|
+
return levels === 1 ? "the parent" : `${levels} levels up`;
|
|
15963
|
+
}
|
|
15964
|
+
var CASCADE_VALUE_WIDTH = 60;
|
|
15965
|
+
function treeBlock(data) {
|
|
15966
|
+
if (!data.children && !data.hiddenChildren) return [];
|
|
15967
|
+
return [
|
|
15968
|
+
"tree",
|
|
15969
|
+
...treeLines2(data.children ?? [], data.hiddenChildren, 1),
|
|
15970
|
+
...data.moreRows ? [` \u2026 +${data.moreRows} more`] : []
|
|
15971
|
+
];
|
|
15972
|
+
}
|
|
15973
|
+
function allLines(all) {
|
|
15974
|
+
const lines = [];
|
|
15975
|
+
let current = [];
|
|
15976
|
+
for (const [name, value] of Object.entries(all)) {
|
|
15977
|
+
current.push(`${name} ${value}`);
|
|
15978
|
+
if (current.join(" \xB7 ").length > ALL_LINE_WIDTH) {
|
|
15979
|
+
lines.push(current.join(" \xB7 "));
|
|
15980
|
+
current = [];
|
|
15981
|
+
}
|
|
15982
|
+
}
|
|
15983
|
+
if (current.length > 0) lines.push(current.join(" \xB7 "));
|
|
15984
|
+
return lines.map((line, i) => `${(i === 0 ? "all" : "").padEnd(LABEL_WIDTH)}${line}`);
|
|
15985
|
+
}
|
|
15986
|
+
function propLines(props) {
|
|
15987
|
+
if (Object.keys(props).length === 0) return ["(no matching custom properties)"];
|
|
15988
|
+
return Object.entries(props).map(
|
|
15989
|
+
([name, prop]) => `${name}: ${prop.computed || (name.startsWith("--") ? "(not set)" : "(empty)")}${prop.value !== prop.computed ? ` = ${prop.value}` : ""}`
|
|
15990
|
+
);
|
|
15991
|
+
}
|
|
15992
|
+
function groupLines(data) {
|
|
15993
|
+
return [
|
|
15994
|
+
data.box && boxLine(data.box),
|
|
15995
|
+
data.layout && layoutLine(data.layout),
|
|
15996
|
+
data.layout && parentLine(data.layout),
|
|
15997
|
+
data.text && textLine(data.text),
|
|
15998
|
+
fillLine(data),
|
|
15999
|
+
borderLine(data),
|
|
16000
|
+
fxLine(data),
|
|
16001
|
+
stateLine(data),
|
|
16002
|
+
...pseudoLines(data.pseudo)
|
|
16003
|
+
];
|
|
16004
|
+
}
|
|
16005
|
+
function formatInspect(data) {
|
|
16006
|
+
const body = data.props ? propLines(data.props) : data.all ? allLines(data.all) : groupLines(data);
|
|
16007
|
+
const note = data.picked ? multipleMatchesWarning(data.count, inspectedMatchAction(data.picked, data.index)) : void 0;
|
|
16008
|
+
if (data.why) return joinLines(inspectHeader(data), ...cascadeBlock(data), note);
|
|
16009
|
+
return joinLines(inspectHeader(data), ...body, ...cascadeBlock(data), ...treeBlock(data), note);
|
|
16010
|
+
}
|
|
16011
|
+
|
|
16012
|
+
// src/commands/dom/inspect.ts
|
|
16013
|
+
init_commands();
|
|
16014
|
+
function registerInspectCommand(dom) {
|
|
16015
|
+
dom.command("inspect").description(
|
|
16016
|
+
"What one element looks like without a screenshot: box, layout, font (rendered, contrast), colors, borders, effects, pseudo-elements and child tree"
|
|
16017
|
+
).argument("<selectorOrIndex>", SELECTOR_OR_INDEX_ARGUMENT).option(
|
|
16018
|
+
"--index <n>",
|
|
16019
|
+
"Which match to inspect (0-based; default: the first rendered one)",
|
|
16020
|
+
integerOption(0)
|
|
16021
|
+
).option(
|
|
16022
|
+
"--tree <depth>",
|
|
16023
|
+
`Child tree depth (default ${DEFAULT_TREE_DEPTH}; 0 for none)`,
|
|
16024
|
+
integerOption(0, 10)
|
|
16025
|
+
).option(
|
|
16026
|
+
"--tree-limit <n>",
|
|
16027
|
+
`Child tree rows at most (default ${DEFAULT_TREE_LIMIT})`,
|
|
16028
|
+
integerOption(1, 500)
|
|
16029
|
+
).addOption(
|
|
16030
|
+
new Option(
|
|
16031
|
+
"--all",
|
|
16032
|
+
"Every computed property that is not its default, collapsed into shorthands, instead of the groups"
|
|
16033
|
+
).conflicts("props")
|
|
16034
|
+
).addHelpText("after", INSPECT_OUTPUT_LEGEND).option(
|
|
16035
|
+
"--props <names>",
|
|
16036
|
+
"Only these properties, computed and normalized (comma-separated, e.g. padding,color,--brand; '--*' or '--bs-btn-*' lists custom properties)",
|
|
16037
|
+
cssPropertiesOption
|
|
16038
|
+
).addOption(
|
|
16039
|
+
new Option(
|
|
16040
|
+
"--rules",
|
|
16041
|
+
"Also show which CSS rule sets each shown property (selector, file:line, what it overrides); with --props, those properties"
|
|
16042
|
+
).conflicts("all")
|
|
16043
|
+
).addOption(
|
|
16044
|
+
new Option(
|
|
16045
|
+
"--why <property>",
|
|
16046
|
+
"Every declaration of one property: the one that applies and those it overrides (e.g. --why color)"
|
|
16047
|
+
).conflicts("all").argParser(propertyOption)
|
|
16048
|
+
).option("--no-hints", "Skip the check for declarations that have no effect").addOption(jsonOption()).action(async (selectorOrIndex, options) => {
|
|
16049
|
+
await runCommand(() => inspectTarget(selectorOrIndex, options), options, formatInspect);
|
|
16050
|
+
});
|
|
16051
|
+
}
|
|
16052
|
+
function propertyOption(value) {
|
|
16053
|
+
return value.startsWith("--") ? value.trim() : value.trim().toLowerCase();
|
|
16054
|
+
}
|
|
16055
|
+
async function inspectTarget(selectorOrIndex, options) {
|
|
16056
|
+
return runElementCommand({
|
|
16057
|
+
selectorOrIndex,
|
|
16058
|
+
index: options.index,
|
|
16059
|
+
buildRequest: (target) => ({
|
|
16060
|
+
...target,
|
|
16061
|
+
...filterDefined({
|
|
16062
|
+
tree: options.tree,
|
|
16063
|
+
treeLimit: options.treeLimit,
|
|
16064
|
+
all: options.all,
|
|
16065
|
+
props: options.props,
|
|
16066
|
+
rules: options.rules,
|
|
16067
|
+
why: options.why,
|
|
16068
|
+
...options.hints === false && { hints: false }
|
|
16069
|
+
})
|
|
16070
|
+
}),
|
|
16071
|
+
call: domInspect,
|
|
16072
|
+
command: "inspect",
|
|
16073
|
+
action: "inspect the element",
|
|
16074
|
+
failureSuggestion: 'Verify the selector matches an element: bdg dom query "<selector>"'
|
|
16075
|
+
});
|
|
16076
|
+
}
|
|
16077
|
+
|
|
15081
16078
|
// src/commands/dom/layout.ts
|
|
15082
16079
|
init_client();
|
|
15083
16080
|
|
|
@@ -15086,13 +16083,13 @@ init_messages();
|
|
|
15086
16083
|
init_formatting();
|
|
15087
16084
|
init_commands();
|
|
15088
16085
|
var LAYOUT_DISPLAY_LIMIT = 20;
|
|
15089
|
-
function
|
|
16086
|
+
function layoutLine2(element, viewport) {
|
|
15090
16087
|
const { bounds } = element;
|
|
15091
16088
|
return [
|
|
15092
16089
|
`[${element.index}] ${element.element}${element.text ? ` "${element.text}"` : ""}`,
|
|
15093
16090
|
element.inViewport !== "hidden" && `${bounds.x},${bounds.y} ${bounds.width}\xD7${bounds.height}`,
|
|
15094
16091
|
layoutPositionLabel(element, viewport),
|
|
15095
|
-
element.coveredBy &&
|
|
16092
|
+
element.coveredBy && coverText(element.coveredBy, element.coverTransparent),
|
|
15096
16093
|
element.inert && "inert",
|
|
15097
16094
|
element.invisible,
|
|
15098
16095
|
element.context && element.context !== element.clippedBy && `in ${element.context}`
|
|
@@ -15103,14 +16100,14 @@ function formatLayout(data) {
|
|
|
15103
16100
|
const more = data.elements.length - shown.length + (data.omitted ?? 0);
|
|
15104
16101
|
return new OutputFormatter().text(pageLayoutLine(data.page)).text(
|
|
15105
16102
|
data.indexSource ? indexLayoutHeadline(indexSourceText(data.indexSource)) : layoutHeadline(data.count, data.elements.length + (data.omitted ?? 0), data.selector)
|
|
15106
|
-
).list(shown.map((element) =>
|
|
16103
|
+
).list(shown.map((element) => layoutLine2(element, data.page.viewport))).list(more > 0 ? [moreMatchesNote(more, data.omitted ? data.elements.length : void 0)] : []).build();
|
|
15107
16104
|
}
|
|
15108
16105
|
|
|
15109
16106
|
// src/commands/dom/layout.ts
|
|
15110
16107
|
function registerLayoutCommand(dom) {
|
|
15111
16108
|
dom.command("layout").description(
|
|
15112
16109
|
"Positions, sizes and visibility of elements (above/below the fold, hidden, covered) without a screenshot"
|
|
15113
|
-
).argument("<selectorOrIndex>",
|
|
16110
|
+
).argument("<selectorOrIndex>", SELECTOR_OR_INDEX_ARGUMENT).option("--index <n>", "Only this match of the selector (0-based)", integerOption(0)).addOption(jsonOption()).action(async (selectorOrIndex, options) => {
|
|
15114
16111
|
await runCommand(() => measureLayout(selectorOrIndex, options), options, formatLayout);
|
|
15115
16112
|
});
|
|
15116
16113
|
}
|
|
@@ -15295,7 +16292,7 @@ function appendListenerGroups(fmt, listeners) {
|
|
|
15295
16292
|
|
|
15296
16293
|
// src/commands/dom/listeners.ts
|
|
15297
16294
|
function registerListenersCommand(dom) {
|
|
15298
|
-
dom.command("listeners").description("List event listeners that run for an element (incl. delegated ones on ancestors)").argument("<selectorOrIndex>",
|
|
16295
|
+
dom.command("listeners").description("List event listeners that run for an element (incl. delegated ones on ancestors)").argument("<selectorOrIndex>", SELECTOR_OR_INDEX_ARGUMENT).option("--index <n>", "Element index if selector matches multiple (0-based)", integerOption(0)).option(
|
|
15299
16296
|
"--type <types>",
|
|
15300
16297
|
"Only these event types (comma-separated, e.g. click,keydown; repeatable)",
|
|
15301
16298
|
eventTypesOption
|
|
@@ -15638,25 +16635,28 @@ function registerDomCommands(program2) {
|
|
|
15638
16635
|
registerFormCommand(dom);
|
|
15639
16636
|
registerListenersCommand(dom);
|
|
15640
16637
|
registerLayoutCommand(dom);
|
|
16638
|
+
registerInspectCommand(dom);
|
|
15641
16639
|
registerWaitCommand(dom);
|
|
15642
|
-
dom.command("query").description("Find elements by CSS selector").argument("<selector>", 'CSS selector (e.g., ".error", "#app", "button")').option("-j, --json", "Output as JSON").action(async (selector, options) => {
|
|
16640
|
+
dom.command("query").description("Find elements by CSS selector").argument("<selector>", 'CSS selector (e.g., ".error", "#app", "button")').option("-j, --json", "Output as JSON").addHelpText("after", SELECTOR_SCOPE_HELP).action(async (selector, options) => {
|
|
15643
16641
|
await handleDomQuery(selector, options);
|
|
15644
16642
|
});
|
|
15645
16643
|
dom.command("eval").description("Evaluate JavaScript expression in the page context").argument("<script>", 'JavaScript to execute (e.g., "document.title", "window.location.href")').option(
|
|
15646
16644
|
"--frame <frame>",
|
|
15647
|
-
"Evaluate in an iframe, cross-origin ones included: index, name/id attribute, or part of the name, id or URL
|
|
16645
|
+
"Evaluate in an iframe, cross-origin ones included: index (from dom frames; 87 when stale), name/id attribute, or part of the name, id or URL"
|
|
15648
16646
|
).option("-j, --json", "Output as JSON").action(async (script, options) => {
|
|
15649
16647
|
await handleDomEval(script, options);
|
|
15650
16648
|
});
|
|
15651
16649
|
program2.command("eval", { hidden: true }).description("Shortcut for: bdg dom eval").argument("<script>", "JavaScript to execute").option("--frame <frame>", "Evaluate in an iframe (see dom frames)").option("-j, --json", "Output as JSON").action(async (script, options) => {
|
|
15652
16650
|
await handleDomEval(script, options);
|
|
15653
16651
|
});
|
|
15654
|
-
dom.command("frames").description(
|
|
16652
|
+
dom.command("frames").description(
|
|
16653
|
+
"List the page's iframes in document order (nested and cross-origin ones included) for eval --frame"
|
|
16654
|
+
).option("-j, --json", "Output as JSON").action(async (options) => {
|
|
15655
16655
|
await handleDomFrames(options);
|
|
15656
16656
|
});
|
|
15657
16657
|
dom.command("get").description("Get semantic accessibility structure (default) or raw HTML (--raw)").argument(
|
|
15658
16658
|
"[selectorOrIndex]",
|
|
15659
|
-
|
|
16659
|
+
`${SELECTOR_OR_INDEX_ARGUMENT} (e.g. ".error", "#app", 0); default: ${DOM_GET_DEFAULT_SELECTOR}`
|
|
15660
16660
|
).option("--raw", "Output raw HTML with all filtering options").option("--full", "Show all of the element text (default: the first 500 characters)").option("--all", "Get all matches (only with --raw)").option("--index <n>", "Element index if selector matches multiple (0-based)", integerOption(0)).addOption(new Option("--nth <n>", "Alias of --index").argParser(integerOption(0)).hideHelp()).option(
|
|
15661
16661
|
"--node-id <id>",
|
|
15662
16662
|
"Get the element with this node id (from dom query/get --raw or a11y describe; implies --raw)",
|
|
@@ -15682,6 +16682,87 @@ function registerDomCommands(program2) {
|
|
|
15682
16682
|
);
|
|
15683
16683
|
}
|
|
15684
16684
|
|
|
16685
|
+
// src/commands/installSkill.ts
|
|
16686
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
|
|
16687
|
+
import { homedir as homedir3 } from "os";
|
|
16688
|
+
import { dirname as dirname5, join as join6 } from "path";
|
|
16689
|
+
init_errors3();
|
|
16690
|
+
init_messages();
|
|
16691
|
+
|
|
16692
|
+
// src/ui/formatters/installSkill.ts
|
|
16693
|
+
init_formatting();
|
|
16694
|
+
import { homedir as homedir2 } from "os";
|
|
16695
|
+
function tildePath(path6) {
|
|
16696
|
+
const home = homedir2();
|
|
16697
|
+
return path6.startsWith(`${home}/`) ? `~${path6.slice(home.length)}` : path6;
|
|
16698
|
+
}
|
|
16699
|
+
function formatInstalledSkills(data) {
|
|
16700
|
+
const fmt = new OutputFormatter().text("bdg skill:");
|
|
16701
|
+
for (const skill of data.skills) {
|
|
16702
|
+
fmt.text(` ${skill.target.padEnd(6)} ${skill.status.padEnd(9)} ${tildePath(skill.path)}`);
|
|
16703
|
+
}
|
|
16704
|
+
return fmt.hints("Next:", [
|
|
16705
|
+
"Start a new agent session to load it (running sessions keep the old list)",
|
|
16706
|
+
"After upgrading bdg, run bdg install-skill again"
|
|
16707
|
+
]).build();
|
|
16708
|
+
}
|
|
16709
|
+
|
|
16710
|
+
// src/commands/installSkill.ts
|
|
16711
|
+
init_errors();
|
|
16712
|
+
init_exitCodes();
|
|
16713
|
+
var SKILL_SOURCE_PATH = join6(PACKAGE_ROOT, ".claude", "skills", "bdg", "SKILL.md");
|
|
16714
|
+
var SKILL_ROOTS = {
|
|
16715
|
+
claude: join6(".claude", "skills"),
|
|
16716
|
+
agents: join6(".agents", "skills")
|
|
16717
|
+
};
|
|
16718
|
+
function installSkill(targets, home = homedir3(), source = SKILL_SOURCE_PATH) {
|
|
16719
|
+
if (!existsSync10(source)) {
|
|
16720
|
+
const err = skillSourceMissingError(source);
|
|
16721
|
+
throw new CommandError(
|
|
16722
|
+
err.message,
|
|
16723
|
+
{ suggestion: err.suggestion },
|
|
16724
|
+
EXIT_CODES.RESOURCE_NOT_FOUND
|
|
16725
|
+
);
|
|
16726
|
+
}
|
|
16727
|
+
const content = readFileSync8(source, "utf-8");
|
|
16728
|
+
return targets.map(
|
|
16729
|
+
(target) => writeSkill(target, join6(home, SKILL_ROOTS[target], "bdg", "SKILL.md"), content)
|
|
16730
|
+
);
|
|
16731
|
+
}
|
|
16732
|
+
function writeSkill(target, path6, content) {
|
|
16733
|
+
const existing = existsSync10(path6) ? readFileSync8(path6, "utf-8") : void 0;
|
|
16734
|
+
if (existing === content) {
|
|
16735
|
+
return { target, path: path6, status: "unchanged" };
|
|
16736
|
+
}
|
|
16737
|
+
try {
|
|
16738
|
+
mkdirSync3(dirname5(path6), { recursive: true });
|
|
16739
|
+
writeFileSync2(path6, content);
|
|
16740
|
+
} catch (caught) {
|
|
16741
|
+
const err = skillWriteFailedError(path6, getErrorMessage(caught));
|
|
16742
|
+
throw new CommandError(
|
|
16743
|
+
err.message,
|
|
16744
|
+
{ suggestion: err.suggestion },
|
|
16745
|
+
EXIT_CODES.PERMISSION_DENIED
|
|
16746
|
+
);
|
|
16747
|
+
}
|
|
16748
|
+
return { target, path: path6, status: existing === void 0 ? "installed" : "updated" };
|
|
16749
|
+
}
|
|
16750
|
+
function selectedTargets(options) {
|
|
16751
|
+
const picked = Object.keys(SKILL_ROOTS).filter((target) => options[target]);
|
|
16752
|
+
return picked.length > 0 ? picked : Object.keys(SKILL_ROOTS);
|
|
16753
|
+
}
|
|
16754
|
+
function registerInstallSkillCommand(program2) {
|
|
16755
|
+
program2.command("install-skill").description(
|
|
16756
|
+
"Install the bdg agent skill for Claude Code (~/.claude/skills) and agents reading ~/.agents/skills (Codex, Gemini CLI, ...); re-run after upgrading bdg"
|
|
16757
|
+
).option("--claude", "Only ~/.claude/skills (Claude Code)").option("--agents", "Only ~/.agents/skills (Codex, Gemini CLI and other agents)").addOption(jsonOption()).action(async (options) => {
|
|
16758
|
+
await runCommand(
|
|
16759
|
+
(opts) => Promise.resolve({ success: true, data: { skills: installSkill(selectedTargets(opts)) } }),
|
|
16760
|
+
options,
|
|
16761
|
+
formatInstalledSkills
|
|
16762
|
+
);
|
|
16763
|
+
});
|
|
16764
|
+
}
|
|
16765
|
+
|
|
15685
16766
|
// src/commands/network/har.ts
|
|
15686
16767
|
import * as fs13 from "fs";
|
|
15687
16768
|
import * as path5 from "path";
|
|
@@ -16307,6 +17388,7 @@ init_commands();
|
|
|
16307
17388
|
// src/ui/messages/session.ts
|
|
16308
17389
|
init_formatting();
|
|
16309
17390
|
init_commands();
|
|
17391
|
+
init_sessionCommand();
|
|
16310
17392
|
function startNotices(options) {
|
|
16311
17393
|
const { documentStatus, loading, autoStopAt, session } = options;
|
|
16312
17394
|
return [
|
|
@@ -16353,6 +17435,13 @@ function lastSessionEndText(end) {
|
|
|
16353
17435
|
const at = new Date(end.endedAt).toLocaleTimeString();
|
|
16354
17436
|
return `The last session ended at ${at}: ${why[end.reason] ?? end.reason}`;
|
|
16355
17437
|
}
|
|
17438
|
+
function daemonStillExitingHint(pid, waitedMs) {
|
|
17439
|
+
const daemon = pid === void 0 ? "The daemon" : `The daemon (PID ${pid})`;
|
|
17440
|
+
return `${daemon} was still shutting down after ${waitedMs / 1e3}s`;
|
|
17441
|
+
}
|
|
17442
|
+
function daemonStillExitingSuggestion() {
|
|
17443
|
+
return `check with bdg sessions, or end it with ${sessionCommand("bdg cleanup --force")}`;
|
|
17444
|
+
}
|
|
16356
17445
|
|
|
16357
17446
|
// src/ui/formatters/status.ts
|
|
16358
17447
|
init_sessionCommand();
|
|
@@ -16438,7 +17527,7 @@ function appearanceLines(metadata, pageState) {
|
|
|
16438
17527
|
...viewport ? [
|
|
16439
17528
|
[
|
|
16440
17529
|
"Viewport",
|
|
16441
|
-
`${viewport.width}\xD7${viewport.height}${metadata.viewport ? ` (
|
|
17530
|
+
`${viewport.width}\xD7${viewport.height}${metadata.viewport ? ` (emulated ${metadata.viewport.width}x${metadata.viewport.height})` : ""}`
|
|
16442
17531
|
]
|
|
16443
17532
|
] : [],
|
|
16444
17533
|
...scheme ? [
|
|
@@ -16691,49 +17780,13 @@ function formatPreviewVerbose(output, options) {
|
|
|
16691
17780
|
return fmt.build();
|
|
16692
17781
|
}
|
|
16693
17782
|
|
|
16694
|
-
// src/
|
|
16695
|
-
|
|
16696
|
-
function
|
|
16697
|
-
const
|
|
16698
|
-
|
|
16699
|
-
|
|
16700
|
-
|
|
16701
|
-
if (Object.keys(data.responseHeaders).length > 0) {
|
|
16702
|
-
fmt.text("Response Headers:");
|
|
16703
|
-
formatHeaderSection(fmt, data.responseHeaders);
|
|
16704
|
-
fmt.blank();
|
|
16705
|
-
}
|
|
16706
|
-
if (Object.keys(data.requestHeaders).length > 0) {
|
|
16707
|
-
fmt.text("Request Headers:");
|
|
16708
|
-
formatHeaderSection(fmt, data.requestHeaders);
|
|
16709
|
-
fmt.blank();
|
|
16710
|
-
}
|
|
16711
|
-
if (Object.keys(data.responseHeaders).length === 0 && Object.keys(data.requestHeaders).length === 0) {
|
|
16712
|
-
fmt.text("No headers found").blank();
|
|
16713
|
-
}
|
|
16714
|
-
fmt.text(`Request ID: ${data.requestId}`);
|
|
16715
|
-
return fmt.build();
|
|
16716
|
-
}
|
|
16717
|
-
function formatHeaderSection(fmt, headers) {
|
|
16718
|
-
const entries = Object.entries(headers).sort(([a], [b]) => a.localeCompare(b));
|
|
16719
|
-
const keyWidth = Math.max(...entries.map(([k]) => k.length)) + 4;
|
|
16720
|
-
entries.forEach(([key, value]) => {
|
|
16721
|
-
value.split("\n").forEach((line) => fmt.keyValue(` ${key}`, line, keyWidth));
|
|
16722
|
-
});
|
|
16723
|
-
}
|
|
16724
|
-
function requestStatusLine(data) {
|
|
16725
|
-
const status = formatRequestStatus(data);
|
|
16726
|
-
const text = data.statusText && /^\d+$/.test(status) ? ` ${data.statusText}` : "";
|
|
16727
|
-
return [data.method, `${status}${text}`].filter(Boolean).join(" ");
|
|
16728
|
-
}
|
|
16729
|
-
|
|
16730
|
-
// src/commands/network/shared.ts
|
|
16731
|
-
init_exitCodes();
|
|
16732
|
-
async function fetchFromLiveSession() {
|
|
16733
|
-
const response = await getHARData();
|
|
16734
|
-
validateIPCResponse(response);
|
|
16735
|
-
if (!response.data?.requests) {
|
|
16736
|
-
throw new Error("No network data in response");
|
|
17783
|
+
// src/commands/network/shared.ts
|
|
17784
|
+
init_exitCodes();
|
|
17785
|
+
async function fetchFromLiveSession() {
|
|
17786
|
+
const response = await getHARData();
|
|
17787
|
+
validateIPCResponse(response);
|
|
17788
|
+
if (!response.data?.requests) {
|
|
17789
|
+
throw new Error("No network data in response");
|
|
16737
17790
|
}
|
|
16738
17791
|
return response.data.requests;
|
|
16739
17792
|
}
|
|
@@ -17013,19 +18066,56 @@ function formatDuration2(ms) {
|
|
|
17013
18066
|
if (ms === void 0) return "-";
|
|
17014
18067
|
return ms < 1e3 ? `${Math.round(ms)}ms` : `${(ms / 1e3).toFixed(1)}s`;
|
|
17015
18068
|
}
|
|
17016
|
-
function
|
|
17017
|
-
|
|
18069
|
+
function pageStartOf(requests) {
|
|
18070
|
+
const navigationIds = requests.flatMap(
|
|
18071
|
+
(r) => r.navigationId === void 0 ? [] : [r.navigationId]
|
|
18072
|
+
);
|
|
18073
|
+
const current = navigationIds.length > 0 ? Math.max(...navigationIds) : void 0;
|
|
18074
|
+
const page = requests.filter((r) => r.navigationId === current);
|
|
18075
|
+
const documents = page.filter((r) => r.resourceType === "Document");
|
|
18076
|
+
const first = (documents.length > 0 ? documents : page).reduce(
|
|
18077
|
+
(earliest, r) => earliest === void 0 || r.timestamp < earliest.timestamp ? r : earliest,
|
|
18078
|
+
void 0
|
|
18079
|
+
);
|
|
18080
|
+
return first && {
|
|
18081
|
+
timestamp: first.timestamp,
|
|
18082
|
+
...first.sentTime !== void 0 && { sentTime: first.sentTime }
|
|
18083
|
+
};
|
|
18084
|
+
}
|
|
18085
|
+
function startOffsetMs(request, pageStart) {
|
|
18086
|
+
if (request.sentTime !== void 0 && pageStart.sentTime !== void 0) {
|
|
18087
|
+
return (request.sentTime - pageStart.sentTime) * 1e3;
|
|
18088
|
+
}
|
|
18089
|
+
return request.timestamp - pageStart.timestamp;
|
|
18090
|
+
}
|
|
18091
|
+
function formatStartOffset(request, pageStart) {
|
|
18092
|
+
if (!pageStart) return "-";
|
|
18093
|
+
const ms = startOffsetMs(request, pageStart);
|
|
18094
|
+
const seconds = Math.abs(ms) / 1e3;
|
|
18095
|
+
const sign = ms < -50 ? "-" : "+";
|
|
18096
|
+
if (seconds < 99.95) return `${sign}${seconds.toFixed(1)}s`;
|
|
18097
|
+
if (seconds < 999.5) return `${sign}${Math.round(seconds)}s`;
|
|
18098
|
+
return `${sign}${Math.floor(seconds / 60)}m`;
|
|
17018
18099
|
}
|
|
17019
|
-
function
|
|
17020
|
-
|
|
18100
|
+
function formatColumnHeader(widths) {
|
|
18101
|
+
return `${"[ID]".padEnd(widths.id)} ${"START".padStart(6)} STS ${"METH".padEnd(widths.method)} TYP ${"SIZE".padStart(8)} ${"TIME".padStart(6)} URL`;
|
|
18102
|
+
}
|
|
18103
|
+
function formatRequestLine(request, options, widths) {
|
|
18104
|
+
const id = `[${request.requestId}]`.padEnd(widths.id);
|
|
18105
|
+
const start = formatStartOffset(request, options.pageStart).padStart(6);
|
|
17021
18106
|
const status = formatStatus(request).padEnd(3);
|
|
17022
|
-
const method = request.method.padEnd(
|
|
18107
|
+
const method = request.method.padEnd(widths.method);
|
|
17023
18108
|
const type = getResourceTypeAbbr(request.resourceType, request.mimeType).padEnd(3);
|
|
17024
18109
|
const size = formatSize(request.encodedDataLength).padStart(8);
|
|
17025
18110
|
const time = formatDuration2(request.duration).padStart(6);
|
|
17026
|
-
const
|
|
17027
|
-
|
|
17028
|
-
|
|
18111
|
+
const url = options.verbose ? request.url : truncateUrl(request.url, 50);
|
|
18112
|
+
return `${id} ${start} ${status} ${method} ${type} ${size} ${time} ${url}`;
|
|
18113
|
+
}
|
|
18114
|
+
function columnWidths(requests, minIdWidth) {
|
|
18115
|
+
return {
|
|
18116
|
+
id: Math.max(minIdWidth, ...requests.map((r) => r.requestId.length + 2)),
|
|
18117
|
+
method: Math.max("METH".length, ...requests.map((r) => r.method.length))
|
|
18118
|
+
};
|
|
17029
18119
|
}
|
|
17030
18120
|
function buildHeader(showingCount, filteredCount, totalCount) {
|
|
17031
18121
|
const shown = filteredCount > showingCount ? `last ${showingCount} of ` : "";
|
|
@@ -17044,40 +18134,30 @@ function formatNetworkListHuman(requests, options) {
|
|
|
17044
18134
|
fmt.text("No matching requests found.");
|
|
17045
18135
|
return fmt.build();
|
|
17046
18136
|
}
|
|
17047
|
-
const
|
|
17048
|
-
fmt.text(formatColumnHeader(
|
|
18137
|
+
const widths = columnWidths(requests, "[ID]".length);
|
|
18138
|
+
fmt.text(formatColumnHeader(widths));
|
|
17049
18139
|
fmt.separator("\u2500", SEPARATOR_WIDTH2);
|
|
17050
|
-
const verbose = options.verbose ?? false;
|
|
17051
18140
|
for (const request of requests) {
|
|
17052
|
-
fmt.text(formatRequestLine(request,
|
|
18141
|
+
fmt.text(formatRequestLine(request, options, widths));
|
|
17053
18142
|
}
|
|
17054
18143
|
return fmt.build();
|
|
17055
18144
|
}
|
|
17056
18145
|
var FOLLOW_ID_WIDTH = 14;
|
|
17057
18146
|
function formatNetworkFollowRows(requests, options = {}) {
|
|
17058
18147
|
const fmt = new OutputFormatter();
|
|
17059
|
-
const
|
|
18148
|
+
const widths = columnWidths(requests, FOLLOW_ID_WIDTH);
|
|
17060
18149
|
if (options.header) {
|
|
17061
18150
|
fmt.text("Streaming network requests... (Ctrl+C to stop)");
|
|
17062
|
-
fmt.text(formatColumnHeader(
|
|
18151
|
+
fmt.text(formatColumnHeader(widths));
|
|
17063
18152
|
fmt.separator("\u2500", SEPARATOR_WIDTH2);
|
|
17064
18153
|
}
|
|
17065
|
-
for (const request of requests)
|
|
17066
|
-
fmt.text(formatRequestLine(request, options.verbose ?? false, idWidth));
|
|
18154
|
+
for (const request of requests) fmt.text(formatRequestLine(request, options, widths));
|
|
17067
18155
|
return fmt.build();
|
|
17068
18156
|
}
|
|
17069
18157
|
function formatNetworkList(requests, options) {
|
|
17070
18158
|
return formatNetworkListHuman(requests, options);
|
|
17071
18159
|
}
|
|
17072
18160
|
|
|
17073
|
-
// src/ui/messages/networkMessages.ts
|
|
17074
|
-
function followingNetworkMessage() {
|
|
17075
|
-
return "Following network requests... (Ctrl+C to stop)";
|
|
17076
|
-
}
|
|
17077
|
-
function stoppedFollowingNetworkMessage() {
|
|
17078
|
-
return "\nStopped following network requests.";
|
|
17079
|
-
}
|
|
17080
|
-
|
|
17081
18161
|
// src/commands/network/list.ts
|
|
17082
18162
|
init_exitCodes();
|
|
17083
18163
|
var MIN_LAST2 = 0;
|
|
@@ -17132,7 +18212,8 @@ function buildFormatOptions2(options, result, lastLimit) {
|
|
|
17132
18212
|
verbose: options.verbose ?? false,
|
|
17133
18213
|
last: lastLimit,
|
|
17134
18214
|
totalCount: result.totalCount,
|
|
17135
|
-
filteredCount: result.filteredCount
|
|
18215
|
+
filteredCount: result.filteredCount,
|
|
18216
|
+
...result.pageStart && { pageStart: result.pageStart }
|
|
17136
18217
|
};
|
|
17137
18218
|
}
|
|
17138
18219
|
async function runFollowMode2(options, resourceTypes, lastN) {
|
|
@@ -17161,9 +18242,11 @@ async function runFollowMode2(options, resourceTypes, lastN) {
|
|
|
17161
18242
|
console.log(JSON.stringify(buildSuccessResponse(data), null, 2));
|
|
17162
18243
|
}
|
|
17163
18244
|
} else {
|
|
18245
|
+
const pageStart = pageStartOf(result.data);
|
|
17164
18246
|
const text = formatNetworkFollowRows(fresh, {
|
|
17165
18247
|
header: !started,
|
|
17166
|
-
verbose: options.verbose ?? false
|
|
18248
|
+
verbose: options.verbose ?? false,
|
|
18249
|
+
...pageStart && { pageStart }
|
|
17167
18250
|
});
|
|
17168
18251
|
if (text) console.log(text);
|
|
17169
18252
|
}
|
|
@@ -17176,6 +18259,11 @@ async function runFollowMode2(options, resourceTypes, lastN) {
|
|
|
17176
18259
|
intervalMs: FOLLOW_INTERVAL
|
|
17177
18260
|
});
|
|
17178
18261
|
}
|
|
18262
|
+
var COLUMNS_HELP = `Columns:
|
|
18263
|
+
START When the request started, from the start of the current page (its document
|
|
18264
|
+
request): +1.2s. Requests of earlier pages are negative. --json has the
|
|
18265
|
+
absolute time (timestamp, epoch ms) and data.pageStart.
|
|
18266
|
+
TIME How long it took (to its last byte or failure); - while pending`;
|
|
17179
18267
|
function formatPresetHelp() {
|
|
17180
18268
|
return Object.entries(FILTER_PRESETS).map(([name, preset]) => ` ${name.padEnd(12)} ${preset.description}`).join("\n");
|
|
17181
18269
|
}
|
|
@@ -17195,11 +18283,16 @@ function registerListCommand(networkCmd) {
|
|
|
17195
18283
|
"--type <types>",
|
|
17196
18284
|
"Filter by resource type (comma-separated: Document,XHR,Fetch,etc.)"
|
|
17197
18285
|
)
|
|
17198
|
-
).addOption(networkLastOption).addOption(new Option("-f, --follow", "Stream network requests in real-time").default(false)).addOption(new Option("-v, --verbose", "Show full URLs and additional details").default(false)).addHelpText(
|
|
18286
|
+
).addOption(networkLastOption).addOption(new Option("-f, --follow", "Stream network requests in real-time").default(false)).addOption(new Option("-v, --verbose", "Show full URLs and additional details").default(false)).addHelpText(
|
|
18287
|
+
"after",
|
|
18288
|
+
`
|
|
18289
|
+
${COLUMNS_HELP}
|
|
18290
|
+
|
|
17199
18291
|
${getFilterHelpText()}
|
|
17200
18292
|
|
|
17201
18293
|
Presets:
|
|
17202
|
-
${formatPresetHelp()}`
|
|
18294
|
+
${formatPresetHelp()}`
|
|
18295
|
+
).action(async (options) => {
|
|
17203
18296
|
let resourceTypes;
|
|
17204
18297
|
let lastN;
|
|
17205
18298
|
try {
|
|
@@ -17229,12 +18322,14 @@ ${formatPresetHelp()}`).action(async (options) => {
|
|
|
17229
18322
|
return createErrorResult(result.error, result.exitCode, result.suggestion);
|
|
17230
18323
|
}
|
|
17231
18324
|
const filtered = filterRequests(result.data, options, resourceTypes);
|
|
18325
|
+
const pageStart = pageStartOf(result.data);
|
|
17232
18326
|
return {
|
|
17233
18327
|
success: true,
|
|
17234
18328
|
data: {
|
|
17235
18329
|
requests: lastN === 0 ? filtered : filtered.slice(-lastN),
|
|
17236
18330
|
totalCount: result.data.length,
|
|
17237
|
-
filteredCount: filtered.length
|
|
18331
|
+
filteredCount: filtered.length,
|
|
18332
|
+
...pageStart && { pageStart }
|
|
17238
18333
|
}
|
|
17239
18334
|
};
|
|
17240
18335
|
},
|
|
@@ -17254,635 +18349,241 @@ function registerNetworkCommands(program2) {
|
|
|
17254
18349
|
registerDocumentCommand(networkCmd);
|
|
17255
18350
|
}
|
|
17256
18351
|
|
|
17257
|
-
// src/commands/
|
|
18352
|
+
// src/commands/start.ts
|
|
18353
|
+
import fs15 from "node:fs";
|
|
18354
|
+
import os3 from "node:os";
|
|
18355
|
+
|
|
18356
|
+
// src/daemon/errors.ts
|
|
18357
|
+
init_exitCodes();
|
|
18358
|
+
var DaemonError = class _DaemonError extends Error {
|
|
18359
|
+
exitCode;
|
|
18360
|
+
code;
|
|
18361
|
+
constructor(message, code, exitCode = EXIT_CODES.SOFTWARE_ERROR) {
|
|
18362
|
+
super(message);
|
|
18363
|
+
this.name = "DaemonError";
|
|
18364
|
+
this.exitCode = exitCode;
|
|
18365
|
+
if (code !== void 0) {
|
|
18366
|
+
this.code = code;
|
|
18367
|
+
}
|
|
18368
|
+
if (Error.captureStackTrace) {
|
|
18369
|
+
Error.captureStackTrace(this, _DaemonError);
|
|
18370
|
+
}
|
|
18371
|
+
}
|
|
18372
|
+
};
|
|
18373
|
+
var DaemonStartupError = class extends DaemonError {
|
|
18374
|
+
name = "DaemonStartupError";
|
|
18375
|
+
constructor(message, code) {
|
|
18376
|
+
super(message, code, EXIT_CODES.SOFTWARE_ERROR);
|
|
18377
|
+
}
|
|
18378
|
+
};
|
|
18379
|
+
var SessionDirError = class extends DaemonError {
|
|
18380
|
+
/**
|
|
18381
|
+
* @param message - What is wrong with the directory
|
|
18382
|
+
* @param suggestion - How to fix it
|
|
18383
|
+
* @param exitCode - 82 for permissions, 81 for a too-long path, 103 otherwise
|
|
18384
|
+
*/
|
|
18385
|
+
constructor(message, suggestion, exitCode = EXIT_CODES.SESSION_FILE_ERROR) {
|
|
18386
|
+
super(message, "SESSION_DIR_UNUSABLE", exitCode);
|
|
18387
|
+
this.suggestion = suggestion;
|
|
18388
|
+
}
|
|
18389
|
+
suggestion;
|
|
18390
|
+
name = "SessionDirError";
|
|
18391
|
+
};
|
|
18392
|
+
|
|
18393
|
+
// src/daemon/launcher.ts
|
|
18394
|
+
import { spawn } from "child_process";
|
|
18395
|
+
import fs14 from "fs";
|
|
18396
|
+
import { join as join8 } from "path";
|
|
17258
18397
|
init_messages();
|
|
17259
|
-
|
|
17260
|
-
|
|
17261
|
-
|
|
18398
|
+
init_paths();
|
|
18399
|
+
init_logging();
|
|
18400
|
+
init_errors();
|
|
17262
18401
|
init_exitCodes();
|
|
17263
|
-
|
|
17264
|
-
|
|
17265
|
-
|
|
17266
|
-
|
|
17267
|
-
|
|
17268
|
-
|
|
17269
|
-
|
|
17270
|
-
|
|
17271
|
-
|
|
17272
|
-
|
|
17273
|
-
|
|
17274
|
-
|
|
17275
|
-
|
|
17276
|
-
}
|
|
17277
|
-
|
|
17278
|
-
const
|
|
17279
|
-
|
|
17280
|
-
|
|
17281
|
-
|
|
17282
|
-
|
|
17283
|
-
|
|
17284
|
-
|
|
17285
|
-
const err = javascriptNavigationError();
|
|
17286
|
-
return {
|
|
17287
|
-
success: false,
|
|
17288
|
-
error: err.message,
|
|
17289
|
-
exitCode: EXIT_CODES.INVALID_URL,
|
|
17290
|
-
errorContext: { suggestion: err.suggestion }
|
|
17291
|
-
};
|
|
17292
|
-
}
|
|
17293
|
-
if (url !== void 0) {
|
|
17294
|
-
const check = validateUrl(url);
|
|
17295
|
-
if (!check.valid) {
|
|
17296
|
-
return {
|
|
17297
|
-
success: false,
|
|
17298
|
-
error: check.error,
|
|
17299
|
-
exitCode: EXIT_CODES.INVALID_URL,
|
|
17300
|
-
...check.suggestion && { errorContext: { suggestion: check.suggestion } }
|
|
17301
|
-
};
|
|
17302
|
-
}
|
|
17303
|
-
}
|
|
17304
|
-
const response = await pageNavigate({
|
|
17305
|
-
action,
|
|
17306
|
-
...url !== void 0 && { url },
|
|
17307
|
-
wait: options.wait !== false
|
|
17308
|
-
});
|
|
17309
|
-
if (response.status === "error" || !response.data) {
|
|
17310
|
-
return {
|
|
17311
|
-
success: false,
|
|
17312
|
-
error: response.error ?? `Failed to ${action}`,
|
|
17313
|
-
exitCode: response.exitCode ?? EXIT_CODES.SOFTWARE_ERROR,
|
|
17314
|
-
...response.suggestion && { errorContext: { suggestion: response.suggestion } }
|
|
17315
|
-
};
|
|
17316
|
-
}
|
|
17317
|
-
return { success: true, data: response.data };
|
|
17318
|
-
},
|
|
17319
|
-
options,
|
|
17320
|
-
formatPageResult
|
|
17321
|
-
);
|
|
17322
|
-
}
|
|
17323
|
-
async function showPageInfo(options) {
|
|
17324
|
-
await runCommand(
|
|
17325
|
-
async () => {
|
|
17326
|
-
const response = await getStatus();
|
|
17327
|
-
if (response.status === "error") {
|
|
17328
|
-
return {
|
|
17329
|
-
success: false,
|
|
17330
|
-
error: response.error ?? "Failed to read the page",
|
|
17331
|
-
exitCode: EXIT_CODES.SOFTWARE_ERROR
|
|
17332
|
-
};
|
|
17333
|
-
}
|
|
17334
|
-
const page = response.data?.sessionPid ? response.data.pageState : void 0;
|
|
17335
|
-
if (!page) throw noActiveSessionError();
|
|
17336
|
-
return { success: true, data: { url: page.url, title: page.title } };
|
|
17337
|
-
},
|
|
17338
|
-
options,
|
|
17339
|
-
(page) => new OutputFormatter().keyValueList(
|
|
17340
|
-
[
|
|
17341
|
-
["URL", page.url],
|
|
17342
|
-
["Title", page.title]
|
|
17343
|
-
],
|
|
17344
|
-
8
|
|
17345
|
-
).build()
|
|
17346
|
-
);
|
|
17347
|
-
}
|
|
17348
|
-
function registerPageCommands(program2) {
|
|
17349
|
-
const page = program2.command("page").description("The session page: info (URL and title), navigate <url>, reload, back, forward");
|
|
17350
|
-
page.command("info").description(PAGE_INFO_DESCRIPTION).addOption(jsonOption()).action(async (options) => {
|
|
17351
|
-
await showPageInfo(options);
|
|
18402
|
+
var log24 = createLogger("launcher");
|
|
18403
|
+
var DAEMON_READY_TIMEOUT_MS = 5e3;
|
|
18404
|
+
var DAEMON_READY_POLL_MS = 20;
|
|
18405
|
+
async function launchDaemon() {
|
|
18406
|
+
if (await isDaemonAlive()) {
|
|
18407
|
+
log24.debug("Daemon already running");
|
|
18408
|
+
return void 0;
|
|
18409
|
+
}
|
|
18410
|
+
if (!fs14.existsSync(DAEMON_SCRIPT_PATH)) {
|
|
18411
|
+
throw new DaemonStartupError(
|
|
18412
|
+
`Daemon script not found at ${DAEMON_SCRIPT_PATH}. Did you run 'npm run build'?`,
|
|
18413
|
+
"DAEMON_SCRIPT_NOT_FOUND"
|
|
18414
|
+
);
|
|
18415
|
+
}
|
|
18416
|
+
assertUsableSessionDir();
|
|
18417
|
+
const logPath = join8(getSessionDir(), "daemon.log");
|
|
18418
|
+
rotateLog(logPath);
|
|
18419
|
+
const logFd = fs14.openSync(logPath, "a");
|
|
18420
|
+
log24.debug(`Starting daemon: ${DAEMON_SCRIPT_PATH}`);
|
|
18421
|
+
const daemon = spawn(process.execPath, [DAEMON_SCRIPT_PATH], {
|
|
18422
|
+
detached: true,
|
|
18423
|
+
stdio: ["ignore", logFd, logFd]
|
|
17352
18424
|
});
|
|
17353
|
-
|
|
17354
|
-
|
|
17355
|
-
|
|
17356
|
-
|
|
17357
|
-
await runPageAction("navigate", options, url);
|
|
18425
|
+
fs14.closeSync(logFd);
|
|
18426
|
+
let exited = false;
|
|
18427
|
+
daemon.once("exit", () => {
|
|
18428
|
+
exited = true;
|
|
17358
18429
|
});
|
|
17359
|
-
|
|
17360
|
-
|
|
17361
|
-
|
|
17362
|
-
|
|
17363
|
-
|
|
18430
|
+
daemon.unref();
|
|
18431
|
+
await waitForDaemonReady(() => exited);
|
|
18432
|
+
return { pid: daemon.pid, hasExited: () => exited };
|
|
18433
|
+
}
|
|
18434
|
+
function assertUsableSessionDir() {
|
|
18435
|
+
const dir = getSessionDir();
|
|
18436
|
+
const fail = (err, exitCode) => {
|
|
18437
|
+
throw new SessionDirError(err.message, err.suggestion, exitCode);
|
|
18438
|
+
};
|
|
18439
|
+
if (fs14.existsSync(dir) && !fs14.statSync(dir).isDirectory()) fail(sessionDirIsFileError(dir));
|
|
18440
|
+
const socketPath = getSessionFilePath("DAEMON_SOCKET");
|
|
18441
|
+
if (Buffer.byteLength(socketPath) > MAX_DAEMON_SOCKET_PATH_BYTES) {
|
|
18442
|
+
fail(
|
|
18443
|
+
socketPathTooLongError(socketPath, MAX_DAEMON_SOCKET_PATH_BYTES),
|
|
18444
|
+
EXIT_CODES.INVALID_ARGUMENTS
|
|
18445
|
+
);
|
|
18446
|
+
}
|
|
18447
|
+
try {
|
|
18448
|
+
ensureSessionDir();
|
|
18449
|
+
fs14.accessSync(dir, fs14.constants.W_OK);
|
|
18450
|
+
} catch (error) {
|
|
18451
|
+
const code = error.code;
|
|
18452
|
+
const denied = code === "EACCES" || code === "EPERM" || code === "EROFS";
|
|
18453
|
+
fail(
|
|
18454
|
+
sessionDirNotWritableError(dir, code ?? getErrorMessage(error)),
|
|
18455
|
+
denied ? EXIT_CODES.PERMISSION_DENIED : EXIT_CODES.SESSION_FILE_ERROR
|
|
17364
18456
|
);
|
|
17365
18457
|
}
|
|
17366
18458
|
}
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17370
|
-
|
|
17371
|
-
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
|
|
17375
|
-
allowZeroForAll: true
|
|
17376
|
-
}).validate(options.last);
|
|
17377
|
-
const resourceTypes = resourceTypeRule().validate(options.type);
|
|
17378
|
-
return { lastN, resourceTypes };
|
|
17379
|
-
}
|
|
17380
|
-
function peekSection(options) {
|
|
17381
|
-
if (options.network) return "network";
|
|
17382
|
-
return options.console ? "console" : void 0;
|
|
17383
|
-
}
|
|
17384
|
-
async function fetchAndFilterPreview(lastN, resourceTypes, only) {
|
|
17385
|
-
const result = await fetchPreviewOutput({
|
|
17386
|
-
lastN: resourceTypes.length > 0 ? 0 : lastN,
|
|
17387
|
-
...only && { only }
|
|
17388
|
-
});
|
|
17389
|
-
if (!result.success) return result;
|
|
17390
|
-
const output = result.data;
|
|
17391
|
-
const unfilteredNetworkCount = output.totals?.network ?? output.data.network?.length ?? 0;
|
|
17392
|
-
if (resourceTypes.length === 0 || !output.data.network) {
|
|
17393
|
-
return { success: true, data: { output, unfilteredNetworkCount } };
|
|
17394
|
-
}
|
|
17395
|
-
const network = filterByResourceType(output.data.network, resourceTypes);
|
|
17396
|
-
const filteredOutput = {
|
|
17397
|
-
...output,
|
|
17398
|
-
data: { ...output.data, network },
|
|
17399
|
-
...output.totals && { totals: { ...output.totals, network: network.length } }
|
|
17400
|
-
};
|
|
17401
|
-
return { success: true, data: { output: filteredOutput, unfilteredNetworkCount } };
|
|
17402
|
-
}
|
|
17403
|
-
function createPreviewOptions(base, resourceTypes, unfilteredCount) {
|
|
17404
|
-
const options = { ...base };
|
|
17405
|
-
if (base.follow) options.viewedAt = /* @__PURE__ */ new Date();
|
|
17406
|
-
if (resourceTypes.length > 0) {
|
|
17407
|
-
options.filteredTypes = resourceTypes;
|
|
17408
|
-
options.unfilteredNetworkCount = unfilteredCount;
|
|
18459
|
+
var MAX_LOG_BYTES = 5 * 1024 * 1024;
|
|
18460
|
+
function rotateLog(logPath) {
|
|
18461
|
+
try {
|
|
18462
|
+
if (fs14.statSync(logPath).size > MAX_LOG_BYTES) {
|
|
18463
|
+
fs14.renameSync(logPath, `${logPath}.1`);
|
|
18464
|
+
}
|
|
18465
|
+
} catch (error) {
|
|
18466
|
+
log24.debug(`Daemon log not rotated: ${getErrorMessage(error)}`);
|
|
17409
18467
|
}
|
|
17410
|
-
return options;
|
|
17411
18468
|
}
|
|
17412
|
-
async function
|
|
17413
|
-
const
|
|
17414
|
-
|
|
17415
|
-
if (
|
|
17416
|
-
|
|
18469
|
+
async function waitForDaemonReady(hasExited) {
|
|
18470
|
+
const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
|
|
18471
|
+
while (Date.now() < deadline) {
|
|
18472
|
+
if (await isDaemonAlive()) {
|
|
18473
|
+
log24.debug("Daemon is ready");
|
|
18474
|
+
return;
|
|
17417
18475
|
}
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
resourceTypes,
|
|
17423
|
-
result.data.unfilteredNetworkCount
|
|
17424
|
-
);
|
|
17425
|
-
console.log(formatPreview(result.data.output, previewOptions));
|
|
17426
|
-
return void 0;
|
|
17427
|
-
};
|
|
17428
|
-
await setupFollowMode(showPreview, {
|
|
17429
|
-
startMessage: followingPreviewMessage,
|
|
17430
|
-
stopMessage: stoppedFollowingPreviewMessage,
|
|
17431
|
-
intervalMs: 1e3
|
|
17432
|
-
});
|
|
17433
|
-
}
|
|
17434
|
-
function registerPeekCommand(program2) {
|
|
17435
|
-
program2.command("peek").description("Preview collected data without stopping the session").addOption(jsonOption()).option("-v, --verbose", "Use verbose output with full URLs and formatting", false).option("-n, --network", "Show only network requests", false).option("-c, --console", "Show only console messages", false).option("-f, --follow", "Watch for updates (like tail -f)", false).option("--last <count>", "Show last N items, 0 for all", "10").option(
|
|
17436
|
-
"--type <types>",
|
|
17437
|
-
"Filter network requests by resource type (comma-separated: Document,XHR,Fetch,etc.)"
|
|
17438
|
-
).action(async (options) => {
|
|
17439
|
-
showBothSectionsWhenBothRequested(options);
|
|
17440
|
-
if (options.network && !options.json) {
|
|
17441
|
-
console.error(
|
|
17442
|
-
'Note: "bdg peek --network" is deprecated. Use "bdg network list" for enhanced filtering.'
|
|
18476
|
+
if (hasExited() && !await isDaemonAlive()) {
|
|
18477
|
+
throw new DaemonStartupError(
|
|
18478
|
+
`Daemon exited during startup. See ${join8(getSessionDir(), "daemon.log")}`,
|
|
18479
|
+
"DAEMON_EXITED"
|
|
17443
18480
|
);
|
|
17444
18481
|
}
|
|
17445
|
-
|
|
17446
|
-
let resourceTypes;
|
|
17447
|
-
try {
|
|
17448
|
-
const parsed = parseOptions(options);
|
|
17449
|
-
lastN = parsed.lastN;
|
|
17450
|
-
resourceTypes = parsed.resourceTypes;
|
|
17451
|
-
} catch (error) {
|
|
17452
|
-
handleValidationError(error, options.json ?? false);
|
|
17453
|
-
}
|
|
17454
|
-
const baseOptions = {
|
|
17455
|
-
json: options.json,
|
|
17456
|
-
network: options.network,
|
|
17457
|
-
console: options.console,
|
|
17458
|
-
last: lastN,
|
|
17459
|
-
verbose: options.verbose,
|
|
17460
|
-
follow: options.follow
|
|
17461
|
-
};
|
|
17462
|
-
if (options.follow) {
|
|
17463
|
-
await runFollowMode3(options, lastN, resourceTypes, baseOptions);
|
|
17464
|
-
return;
|
|
17465
|
-
}
|
|
17466
|
-
await runCommand(
|
|
17467
|
-
async () => {
|
|
17468
|
-
const result = await fetchAndFilterPreview(lastN, resourceTypes, peekSection(options));
|
|
17469
|
-
if (!result.success) {
|
|
17470
|
-
return createErrorResult(result.error, result.exitCode, result.suggestion);
|
|
17471
|
-
}
|
|
17472
|
-
const { output } = result.data;
|
|
17473
|
-
return {
|
|
17474
|
-
success: true,
|
|
17475
|
-
data: options.json ? buildPreviewJsonData(output, baseOptions) : output
|
|
17476
|
-
};
|
|
17477
|
-
},
|
|
17478
|
-
options,
|
|
17479
|
-
(data) => {
|
|
17480
|
-
const output = data;
|
|
17481
|
-
const previewOptions = createPreviewOptions(
|
|
17482
|
-
baseOptions,
|
|
17483
|
-
resourceTypes,
|
|
17484
|
-
output.data.network?.length ?? 0
|
|
17485
|
-
);
|
|
17486
|
-
return formatPreview(output, previewOptions);
|
|
17487
|
-
}
|
|
17488
|
-
);
|
|
17489
|
-
});
|
|
17490
|
-
}
|
|
17491
|
-
|
|
17492
|
-
// src/session/sessionList.ts
|
|
17493
|
-
init_client();
|
|
17494
|
-
import * as fs15 from "fs";
|
|
17495
|
-
init_paths();
|
|
17496
|
-
|
|
17497
|
-
// src/session/portClaims.ts
|
|
17498
|
-
import * as fs14 from "fs";
|
|
17499
|
-
init_paths();
|
|
17500
|
-
init_logging();
|
|
17501
|
-
var log24 = createLogger("session");
|
|
17502
|
-
function readPortFile(portPath) {
|
|
17503
|
-
try {
|
|
17504
|
-
const port = parseInt(fs14.readFileSync(portPath, "utf-8").trim(), 10);
|
|
17505
|
-
return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : null;
|
|
17506
|
-
} catch (error) {
|
|
17507
|
-
logDebugError(log24, `read ${portPath}`, error);
|
|
17508
|
-
return null;
|
|
18482
|
+
await delay2(DAEMON_READY_POLL_MS);
|
|
17509
18483
|
}
|
|
18484
|
+
throw new DaemonStartupError(
|
|
18485
|
+
`Daemon failed to start within ${DAEMON_READY_TIMEOUT_MS / 1e3} seconds`,
|
|
18486
|
+
"DAEMON_START_TIMEOUT"
|
|
18487
|
+
);
|
|
17510
18488
|
}
|
|
17511
18489
|
|
|
17512
|
-
// src/
|
|
17513
|
-
init_errors3();
|
|
18490
|
+
// src/commands/shared/startHelpers.ts
|
|
17514
18491
|
init_messages();
|
|
18492
|
+
init_client();
|
|
18493
|
+
init_transport();
|
|
18494
|
+
init_errors2();
|
|
17515
18495
|
init_paths();
|
|
18496
|
+
init_formatting();
|
|
18497
|
+
init_logging();
|
|
18498
|
+
init_sessionCommand();
|
|
18499
|
+
init_errors();
|
|
17516
18500
|
init_exitCodes();
|
|
17517
|
-
var
|
|
17518
|
-
var
|
|
17519
|
-
|
|
17520
|
-
|
|
17521
|
-
|
|
17522
|
-
|
|
17523
|
-
}
|
|
17524
|
-
function
|
|
17525
|
-
|
|
17526
|
-
|
|
17527
|
-
|
|
17528
|
-
|
|
17529
|
-
|
|
17530
|
-
|
|
17531
|
-
const
|
|
17532
|
-
|
|
17533
|
-
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
|
|
17537
|
-
|
|
18501
|
+
var log25 = createLogger("bdg");
|
|
18502
|
+
var SPAWNED_DAEMON_EXIT_WAIT_MS = 3e3;
|
|
18503
|
+
var DEFAULT_START_DEPS = {
|
|
18504
|
+
launch: launchDaemon,
|
|
18505
|
+
send: startSession,
|
|
18506
|
+
exitWaitMs: SPAWNED_DAEMON_EXIT_WAIT_MS
|
|
18507
|
+
};
|
|
18508
|
+
async function startSessionViaDaemon(url, options, telemetry) {
|
|
18509
|
+
process.once("SIGINT", () => reportStartOutcome(interruptedOutcome("SIGINT"), options));
|
|
18510
|
+
process.once("SIGTERM", () => reportStartOutcome(interruptedOutcome("SIGTERM"), options));
|
|
18511
|
+
reportStartOutcome(await attemptStart(url, options, telemetry), options);
|
|
18512
|
+
}
|
|
18513
|
+
async function attemptStart(url, options, telemetry, deps = DEFAULT_START_DEPS) {
|
|
18514
|
+
const spawned = [];
|
|
18515
|
+
const attempt = async () => {
|
|
18516
|
+
const outcome2 = await requestSession(url, options, telemetry, deps);
|
|
18517
|
+
if (!outcome2.ok && outcome2.spawned) spawned.push(outcome2.spawned);
|
|
18518
|
+
return outcome2;
|
|
18519
|
+
};
|
|
18520
|
+
let outcome = await attempt();
|
|
18521
|
+
const deadline = Date.now() + SHUTDOWN_WAIT_MS;
|
|
18522
|
+
while (isShuttingDown(outcome) && Date.now() < deadline) {
|
|
18523
|
+
await delay2(SHUTDOWN_POLL_MS);
|
|
18524
|
+
outcome = await attempt();
|
|
17538
18525
|
}
|
|
18526
|
+
return afterSpawnedDaemonExit(outcome, spawned, deps.exitWaitMs);
|
|
17539
18527
|
}
|
|
17540
|
-
function
|
|
17541
|
-
|
|
17542
|
-
}
|
|
17543
|
-
|
|
17544
|
-
|
|
17545
|
-
|
|
17546
|
-
function selectSession(optionValue) {
|
|
17547
|
-
const name = optionValue ?? process.env[SESSION_NAME_ENV]?.trim();
|
|
17548
|
-
if (name === void 0 || optionValue === void 0 && name === "") {
|
|
17549
|
-
delete process.env[SESSION_NAME_ENV];
|
|
17550
|
-
return;
|
|
18528
|
+
async function afterSpawnedDaemonExit(outcome, spawned, waitMs) {
|
|
18529
|
+
if (outcome.ok) return outcome;
|
|
18530
|
+
const { spawned: _spawned, daemonExiting: _exiting, ...failure } = outcome;
|
|
18531
|
+
if (spawned.length === 0) return failure;
|
|
18532
|
+
if (await waitUntil(() => spawned.every((daemon) => daemon.hasExited()), waitMs)) {
|
|
18533
|
+
return failure;
|
|
17551
18534
|
}
|
|
17552
|
-
|
|
17553
|
-
|
|
17554
|
-
|
|
17555
|
-
|
|
17556
|
-
// src/session/sessionList.ts
|
|
17557
|
-
init_logging();
|
|
17558
|
-
init_sessionCommand();
|
|
17559
|
-
var log25 = createLogger("session");
|
|
17560
|
-
function toRunningSession(name, data) {
|
|
17561
|
-
const meta = data.sessionMetadata;
|
|
17562
|
-
const state = meta ? "active" : data.ending ? "ending" : "starting";
|
|
17563
|
-
const url = data.pageState?.url ?? data.starting?.url;
|
|
18535
|
+
const running = spawned.filter((daemon) => !daemon.hasExited()).pop();
|
|
18536
|
+
const suggestion = daemonStillExitingSuggestion();
|
|
18537
|
+
const previous = failure.details?.["suggestion"];
|
|
17564
18538
|
return {
|
|
17565
|
-
|
|
17566
|
-
|
|
17567
|
-
|
|
17568
|
-
|
|
17569
|
-
|
|
17570
|
-
|
|
18539
|
+
...failure,
|
|
18540
|
+
human: joinLines(
|
|
18541
|
+
failure.human,
|
|
18542
|
+
`${daemonStillExitingHint(running?.pid, waitMs)}; ${suggestion}`
|
|
18543
|
+
),
|
|
18544
|
+
details: {
|
|
18545
|
+
...failure.details,
|
|
18546
|
+
daemonStillRunning: true,
|
|
18547
|
+
...running?.pid !== void 0 && { daemonPid: running.pid },
|
|
18548
|
+
suggestion: typeof previous === "string" && previous ? `${previous}; ${suggestion}` : suggestion
|
|
18549
|
+
}
|
|
17571
18550
|
};
|
|
17572
18551
|
}
|
|
17573
|
-
|
|
17574
|
-
const {
|
|
17575
|
-
const
|
|
17576
|
-
|
|
17577
|
-
if (probe !== "alive") return describeWithoutSocket(entry, probe);
|
|
17578
|
-
try {
|
|
17579
|
-
const response = await getStatus(socketPath);
|
|
17580
|
-
if (response.status === "ok" && response.data) return toRunningSession(name, response.data);
|
|
17581
|
-
} catch (error) {
|
|
17582
|
-
logDebugError(log25, `get status of ${dir}`, error);
|
|
17583
|
-
}
|
|
17584
|
-
return { name, state: "unresponsive" };
|
|
17585
|
-
}
|
|
17586
|
-
function describeWithoutSocket(entry, probe) {
|
|
17587
|
-
const daemonPid = readLiveDaemonPid(entry.dir);
|
|
17588
|
-
if (daemonPid !== null) return { name: entry.name, state: "starting", daemonPid };
|
|
17589
|
-
return describeLeftovers(entry, probe);
|
|
18552
|
+
function interruptedOutcome(signal) {
|
|
18553
|
+
const message = `Start cancelled (${signal === "SIGINT" ? "interrupted" : "terminated"})`;
|
|
18554
|
+
const exitCode = signal === "SIGINT" ? EXIT_CODES.INTERRUPTED : EXIT_CODES.TERMINATED;
|
|
18555
|
+
return { ok: false, error: message, human: message, exitCode };
|
|
17590
18556
|
}
|
|
17591
|
-
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
|
|
17595
|
-
if (!orphan && !leftover) return null;
|
|
17596
|
-
const port = leftoverPort(dir);
|
|
17597
|
-
return {
|
|
17598
|
-
name,
|
|
17599
|
-
state: orphan ? "crashed" : "stale",
|
|
17600
|
-
...port !== null && { port },
|
|
17601
|
-
...orphan && { chromePid },
|
|
17602
|
-
cleanup: sessionCommand("bdg cleanup", name)
|
|
17603
|
-
};
|
|
18557
|
+
var SHUTDOWN_WAIT_MS = 15e3;
|
|
18558
|
+
var SHUTDOWN_POLL_MS = 200;
|
|
18559
|
+
function isShuttingDown(outcome) {
|
|
18560
|
+
return !outcome.ok && (outcome.retryable === true || outcome.errorCode === "SESSION_SHUTTING_DOWN" /* SESSION_SHUTTING_DOWN */);
|
|
17604
18561
|
}
|
|
17605
|
-
function
|
|
18562
|
+
async function requestSession(url, options, telemetry, deps) {
|
|
18563
|
+
let spawned;
|
|
17606
18564
|
try {
|
|
17607
|
-
|
|
17608
|
-
if (typeof meta.port === "number") return meta.port;
|
|
18565
|
+
spawned = await deps.launch();
|
|
17609
18566
|
} catch (error) {
|
|
17610
|
-
|
|
18567
|
+
if (error instanceof SessionDirError) {
|
|
18568
|
+
return {
|
|
18569
|
+
ok: false,
|
|
18570
|
+
error: error.message,
|
|
18571
|
+
human: joinLines(genericError(error.message), error.suggestion),
|
|
18572
|
+
exitCode: error.exitCode,
|
|
18573
|
+
details: { suggestion: error.suggestion }
|
|
18574
|
+
};
|
|
18575
|
+
}
|
|
18576
|
+
const message = `Failed to start daemon: ${getErrorMessage(error)}`;
|
|
18577
|
+
const exitCode = error instanceof DaemonError ? error.exitCode : EXIT_CODES.SOFTWARE_ERROR;
|
|
18578
|
+
return { ok: false, error: message, human: genericError(message), exitCode };
|
|
17611
18579
|
}
|
|
17612
|
-
|
|
18580
|
+
const outcome = await sendStart(url, options, telemetry, deps.send);
|
|
18581
|
+
return !outcome.ok && outcome.daemonExiting && spawned ? { ...outcome, spawned } : outcome;
|
|
17613
18582
|
}
|
|
17614
|
-
function
|
|
17615
|
-
return name === null || isValidSessionName(name) && name === normalizeSessionName(name);
|
|
17616
|
-
}
|
|
17617
|
-
function isSameDir(a, b) {
|
|
17618
|
-
try {
|
|
17619
|
-
const first = fs15.statSync(a);
|
|
17620
|
-
const second = fs15.statSync(b);
|
|
17621
|
-
return first.ino === second.ino && first.dev === second.dev;
|
|
17622
|
-
} catch (error) {
|
|
17623
|
-
logDebugError(log25, `compare ${a} with ${b}`, error);
|
|
17624
|
-
return false;
|
|
17625
|
-
}
|
|
17626
|
-
}
|
|
17627
|
-
function selectableEntry(entry) {
|
|
17628
|
-
if (isSelectable(entry) || entry.name === null) return entry;
|
|
17629
|
-
const lower = normalizeSessionName(entry.name);
|
|
17630
|
-
if (!isValidSessionName(lower)) return null;
|
|
17631
|
-
return isSameDir(entry.dir, getNamedSessionDir(lower)) ? { name: lower, dir: entry.dir } : null;
|
|
17632
|
-
}
|
|
17633
|
-
async function describeUnselectable(entry) {
|
|
17634
|
-
const socketPath = sessionFilePathIn(entry.dir, "DAEMON_SOCKET");
|
|
17635
|
-
if (await probeDaemonSocket(socketPath) === "alive") return describeSession(entry);
|
|
17636
|
-
return { name: entry.name, state: "stale", cleanup: removeDirCommand(entry.dir) };
|
|
17637
|
-
}
|
|
17638
|
-
async function listRunningSessions() {
|
|
17639
|
-
const listed = /* @__PURE__ */ new Set();
|
|
17640
|
-
const sessions = await Promise.all(
|
|
17641
|
-
listSessionDirs().map((entry) => {
|
|
17642
|
-
const selectable = selectableEntry(entry);
|
|
17643
|
-
if (selectable === null) return describeUnselectable(entry);
|
|
17644
|
-
if (listed.has(selectable.name)) return Promise.resolve(null);
|
|
17645
|
-
listed.add(selectable.name);
|
|
17646
|
-
return describeSession(selectable);
|
|
17647
|
-
})
|
|
17648
|
-
);
|
|
17649
|
-
return sessions.filter((session) => session !== null);
|
|
17650
|
-
}
|
|
17651
|
-
|
|
17652
|
-
// src/ui/formatters/sessions.ts
|
|
17653
|
-
init_formatting();
|
|
17654
|
-
var DEFAULT_SESSION_LABEL = "(default)";
|
|
17655
|
-
function formatSessionList(data) {
|
|
17656
|
-
const fmt = new OutputFormatter();
|
|
17657
|
-
if (data.sessions.length === 0) {
|
|
17658
|
-
return fmt.text("No running sessions").hints("Suggestions:", ["Start one: bdg <url> [--session <name>]"]).build();
|
|
17659
|
-
}
|
|
17660
|
-
const rows = data.sessions.map((session) => [
|
|
17661
|
-
session.name ?? DEFAULT_SESSION_LABEL,
|
|
17662
|
-
session.state,
|
|
17663
|
-
session.port?.toString() ?? "-",
|
|
17664
|
-
session.daemonPid?.toString() ?? "-",
|
|
17665
|
-
session.url ?? "-"
|
|
17666
|
-
]);
|
|
17667
|
-
const header = ["SESSION", "STATE", "PORT", "PID", "URL"];
|
|
17668
|
-
const widths = header.map(
|
|
17669
|
-
(title, column) => Math.max(title.length, ...rows.map((row) => row[column]?.length ?? 0))
|
|
17670
|
-
);
|
|
17671
|
-
for (const row of [header, ...rows]) {
|
|
17672
|
-
fmt.text(
|
|
17673
|
-
row.map((cell, column) => cell.padEnd(widths[column] ?? 0)).join(" ").trimEnd()
|
|
17674
|
-
);
|
|
17675
|
-
}
|
|
17676
|
-
const cleanups = data.sessions.flatMap((session) => session.cleanup ? [session.cleanup] : []);
|
|
17677
|
-
if (cleanups.length > 0) {
|
|
17678
|
-
fmt.hints("Crashed or stale sessions; clean up with:", cleanups);
|
|
17679
|
-
}
|
|
17680
|
-
return fmt.build();
|
|
17681
|
-
}
|
|
17682
|
-
|
|
17683
|
-
// src/commands/sessions.ts
|
|
17684
|
-
function registerSessionsCommand(program2) {
|
|
17685
|
-
program2.command("sessions").description(
|
|
17686
|
-
"List sessions (default and named) with their state, URL, port and PID, including crashed ones to clean up"
|
|
17687
|
-
).addOption(jsonOption()).action(async (options) => {
|
|
17688
|
-
await runCommand(
|
|
17689
|
-
async () => ({ success: true, data: { sessions: await listRunningSessions() } }),
|
|
17690
|
-
options,
|
|
17691
|
-
formatSessionList
|
|
17692
|
-
);
|
|
17693
|
-
});
|
|
17694
|
-
}
|
|
17695
|
-
|
|
17696
|
-
// src/commands/start.ts
|
|
17697
|
-
import fs17 from "node:fs";
|
|
17698
|
-
import os3 from "node:os";
|
|
17699
|
-
|
|
17700
|
-
// src/daemon/errors.ts
|
|
17701
|
-
init_exitCodes();
|
|
17702
|
-
var DaemonError = class _DaemonError extends Error {
|
|
17703
|
-
exitCode;
|
|
17704
|
-
code;
|
|
17705
|
-
constructor(message, code, exitCode = EXIT_CODES.SOFTWARE_ERROR) {
|
|
17706
|
-
super(message);
|
|
17707
|
-
this.name = "DaemonError";
|
|
17708
|
-
this.exitCode = exitCode;
|
|
17709
|
-
if (code !== void 0) {
|
|
17710
|
-
this.code = code;
|
|
17711
|
-
}
|
|
17712
|
-
if (Error.captureStackTrace) {
|
|
17713
|
-
Error.captureStackTrace(this, _DaemonError);
|
|
17714
|
-
}
|
|
17715
|
-
}
|
|
17716
|
-
};
|
|
17717
|
-
var DaemonStartupError = class extends DaemonError {
|
|
17718
|
-
name = "DaemonStartupError";
|
|
17719
|
-
constructor(message, code) {
|
|
17720
|
-
super(message, code, EXIT_CODES.SOFTWARE_ERROR);
|
|
17721
|
-
}
|
|
17722
|
-
};
|
|
17723
|
-
var SessionDirError = class extends DaemonError {
|
|
17724
|
-
/**
|
|
17725
|
-
* @param message - What is wrong with the directory
|
|
17726
|
-
* @param suggestion - How to fix it
|
|
17727
|
-
* @param exitCode - 82 for permissions, 81 for a too-long path, 103 otherwise
|
|
17728
|
-
*/
|
|
17729
|
-
constructor(message, suggestion, exitCode = EXIT_CODES.SESSION_FILE_ERROR) {
|
|
17730
|
-
super(message, "SESSION_DIR_UNUSABLE", exitCode);
|
|
17731
|
-
this.suggestion = suggestion;
|
|
17732
|
-
}
|
|
17733
|
-
suggestion;
|
|
17734
|
-
name = "SessionDirError";
|
|
17735
|
-
};
|
|
17736
|
-
|
|
17737
|
-
// src/daemon/launcher.ts
|
|
17738
|
-
import { spawn } from "child_process";
|
|
17739
|
-
import fs16 from "fs";
|
|
17740
|
-
import { join as join7 } from "path";
|
|
17741
|
-
init_messages();
|
|
17742
|
-
init_paths();
|
|
17743
|
-
init_logging();
|
|
17744
|
-
init_errors();
|
|
17745
|
-
init_exitCodes();
|
|
17746
|
-
var log26 = createLogger("launcher");
|
|
17747
|
-
var DAEMON_READY_TIMEOUT_MS = 5e3;
|
|
17748
|
-
var DAEMON_READY_POLL_MS = 20;
|
|
17749
|
-
async function launchDaemon() {
|
|
17750
|
-
if (await isDaemonAlive()) {
|
|
17751
|
-
log26.debug("Daemon already running");
|
|
17752
|
-
return;
|
|
17753
|
-
}
|
|
17754
|
-
if (!fs16.existsSync(DAEMON_SCRIPT_PATH)) {
|
|
17755
|
-
throw new DaemonStartupError(
|
|
17756
|
-
`Daemon script not found at ${DAEMON_SCRIPT_PATH}. Did you run 'npm run build'?`,
|
|
17757
|
-
"DAEMON_SCRIPT_NOT_FOUND"
|
|
17758
|
-
);
|
|
17759
|
-
}
|
|
17760
|
-
assertUsableSessionDir();
|
|
17761
|
-
const logPath = join7(getSessionDir(), "daemon.log");
|
|
17762
|
-
rotateLog(logPath);
|
|
17763
|
-
const logFd = fs16.openSync(logPath, "a");
|
|
17764
|
-
log26.debug(`Starting daemon: ${DAEMON_SCRIPT_PATH}`);
|
|
17765
|
-
const daemon = spawn(process.execPath, [DAEMON_SCRIPT_PATH], {
|
|
17766
|
-
detached: true,
|
|
17767
|
-
stdio: ["ignore", logFd, logFd]
|
|
17768
|
-
});
|
|
17769
|
-
fs16.closeSync(logFd);
|
|
17770
|
-
let exited = false;
|
|
17771
|
-
daemon.once("exit", () => {
|
|
17772
|
-
exited = true;
|
|
17773
|
-
});
|
|
17774
|
-
daemon.unref();
|
|
17775
|
-
await waitForDaemonReady(() => exited);
|
|
17776
|
-
}
|
|
17777
|
-
function assertUsableSessionDir() {
|
|
17778
|
-
const dir = getSessionDir();
|
|
17779
|
-
const fail = (err, exitCode) => {
|
|
17780
|
-
throw new SessionDirError(err.message, err.suggestion, exitCode);
|
|
17781
|
-
};
|
|
17782
|
-
if (fs16.existsSync(dir) && !fs16.statSync(dir).isDirectory()) fail(sessionDirIsFileError(dir));
|
|
17783
|
-
const socketPath = getSessionFilePath("DAEMON_SOCKET");
|
|
17784
|
-
if (Buffer.byteLength(socketPath) > MAX_DAEMON_SOCKET_PATH_BYTES) {
|
|
17785
|
-
fail(
|
|
17786
|
-
socketPathTooLongError(socketPath, MAX_DAEMON_SOCKET_PATH_BYTES),
|
|
17787
|
-
EXIT_CODES.INVALID_ARGUMENTS
|
|
17788
|
-
);
|
|
17789
|
-
}
|
|
17790
|
-
try {
|
|
17791
|
-
ensureSessionDir();
|
|
17792
|
-
fs16.accessSync(dir, fs16.constants.W_OK);
|
|
17793
|
-
} catch (error) {
|
|
17794
|
-
const code = error.code;
|
|
17795
|
-
const denied = code === "EACCES" || code === "EPERM" || code === "EROFS";
|
|
17796
|
-
fail(
|
|
17797
|
-
sessionDirNotWritableError(dir, code ?? getErrorMessage(error)),
|
|
17798
|
-
denied ? EXIT_CODES.PERMISSION_DENIED : EXIT_CODES.SESSION_FILE_ERROR
|
|
17799
|
-
);
|
|
17800
|
-
}
|
|
17801
|
-
}
|
|
17802
|
-
var MAX_LOG_BYTES = 5 * 1024 * 1024;
|
|
17803
|
-
function rotateLog(logPath) {
|
|
17804
|
-
try {
|
|
17805
|
-
if (fs16.statSync(logPath).size > MAX_LOG_BYTES) {
|
|
17806
|
-
fs16.renameSync(logPath, `${logPath}.1`);
|
|
17807
|
-
}
|
|
17808
|
-
} catch (error) {
|
|
17809
|
-
log26.debug(`Daemon log not rotated: ${getErrorMessage(error)}`);
|
|
17810
|
-
}
|
|
17811
|
-
}
|
|
17812
|
-
async function waitForDaemonReady(hasExited) {
|
|
17813
|
-
const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
|
|
17814
|
-
while (Date.now() < deadline) {
|
|
17815
|
-
if (await isDaemonAlive()) {
|
|
17816
|
-
log26.debug("Daemon is ready");
|
|
17817
|
-
return;
|
|
17818
|
-
}
|
|
17819
|
-
if (hasExited() && !await isDaemonAlive()) {
|
|
17820
|
-
throw new DaemonStartupError(
|
|
17821
|
-
`Daemon exited during startup. See ${join7(getSessionDir(), "daemon.log")}`,
|
|
17822
|
-
"DAEMON_EXITED"
|
|
17823
|
-
);
|
|
17824
|
-
}
|
|
17825
|
-
await delay2(DAEMON_READY_POLL_MS);
|
|
17826
|
-
}
|
|
17827
|
-
throw new DaemonStartupError(
|
|
17828
|
-
`Daemon failed to start within ${DAEMON_READY_TIMEOUT_MS / 1e3} seconds`,
|
|
17829
|
-
"DAEMON_START_TIMEOUT"
|
|
17830
|
-
);
|
|
17831
|
-
}
|
|
17832
|
-
|
|
17833
|
-
// src/commands/shared/startHelpers.ts
|
|
17834
|
-
init_messages();
|
|
17835
|
-
init_client();
|
|
17836
|
-
init_transport();
|
|
17837
|
-
init_errors2();
|
|
17838
|
-
init_paths();
|
|
17839
|
-
init_formatting();
|
|
17840
|
-
init_logging();
|
|
17841
|
-
init_sessionCommand();
|
|
17842
|
-
init_errors();
|
|
17843
|
-
init_exitCodes();
|
|
17844
|
-
var log27 = createLogger("bdg");
|
|
17845
|
-
async function startSessionViaDaemon(url, options, telemetry) {
|
|
17846
|
-
process.once("SIGINT", () => reportStartOutcome(interruptedOutcome("SIGINT"), options));
|
|
17847
|
-
process.once("SIGTERM", () => reportStartOutcome(interruptedOutcome("SIGTERM"), options));
|
|
17848
|
-
let outcome = await requestSession(url, options, telemetry);
|
|
17849
|
-
const deadline = Date.now() + SHUTDOWN_WAIT_MS;
|
|
17850
|
-
while (isShuttingDown(outcome) && Date.now() < deadline) {
|
|
17851
|
-
await delay2(SHUTDOWN_POLL_MS);
|
|
17852
|
-
outcome = await requestSession(url, options, telemetry);
|
|
17853
|
-
}
|
|
17854
|
-
reportStartOutcome(outcome, options);
|
|
17855
|
-
}
|
|
17856
|
-
function interruptedOutcome(signal) {
|
|
17857
|
-
const message = `Start cancelled (${signal === "SIGINT" ? "interrupted" : "terminated"})`;
|
|
17858
|
-
const exitCode = signal === "SIGINT" ? EXIT_CODES.INTERRUPTED : EXIT_CODES.TERMINATED;
|
|
17859
|
-
return { ok: false, error: message, human: message, exitCode };
|
|
17860
|
-
}
|
|
17861
|
-
var SHUTDOWN_WAIT_MS = 15e3;
|
|
17862
|
-
var SHUTDOWN_POLL_MS = 200;
|
|
17863
|
-
function isShuttingDown(outcome) {
|
|
17864
|
-
return !outcome.ok && (outcome.retryable === true || outcome.errorCode === "SESSION_SHUTTING_DOWN" /* SESSION_SHUTTING_DOWN */);
|
|
17865
|
-
}
|
|
17866
|
-
async function requestSession(url, options, telemetry) {
|
|
17867
|
-
try {
|
|
17868
|
-
await launchDaemon();
|
|
17869
|
-
} catch (error) {
|
|
17870
|
-
if (error instanceof SessionDirError) {
|
|
17871
|
-
return {
|
|
17872
|
-
ok: false,
|
|
17873
|
-
error: error.message,
|
|
17874
|
-
human: joinLines(genericError(error.message), error.suggestion),
|
|
17875
|
-
exitCode: error.exitCode,
|
|
17876
|
-
details: { suggestion: error.suggestion }
|
|
17877
|
-
};
|
|
17878
|
-
}
|
|
17879
|
-
const message = `Failed to start daemon: ${getErrorMessage(error)}`;
|
|
17880
|
-
const exitCode = error instanceof DaemonError ? error.exitCode : EXIT_CODES.SOFTWARE_ERROR;
|
|
17881
|
-
return { ok: false, error: message, human: genericError(message), exitCode };
|
|
17882
|
-
}
|
|
18583
|
+
async function sendStart(url, options, telemetry, send) {
|
|
17883
18584
|
try {
|
|
17884
|
-
|
|
17885
|
-
const response = await
|
|
18585
|
+
log25.debug("Connecting to daemon...");
|
|
18586
|
+
const response = await send(
|
|
17886
18587
|
url,
|
|
17887
18588
|
filterDefined({
|
|
17888
18589
|
port: options.port,
|
|
@@ -17898,7 +18599,9 @@ async function requestSession(url, options, telemetry) {
|
|
|
17898
18599
|
colorScheme: options.colorScheme
|
|
17899
18600
|
})
|
|
17900
18601
|
);
|
|
17901
|
-
if (response.status === "error")
|
|
18602
|
+
if (response.status === "error") {
|
|
18603
|
+
return { ...describeStartFailure(response, options), daemonExiting: true };
|
|
18604
|
+
}
|
|
17902
18605
|
if (!response.data) {
|
|
17903
18606
|
const message = "Invalid response from daemon: missing data";
|
|
17904
18607
|
return {
|
|
@@ -17926,7 +18629,8 @@ async function requestSession(url, options, telemetry) {
|
|
|
17926
18629
|
error: `${noActiveSessionMessage()} (daemon not running)`,
|
|
17927
18630
|
human: daemonNotRunningError({ suggestStatus: true, suggestRetry: true }),
|
|
17928
18631
|
exitCode: EXIT_CODES.RESOURCE_NOT_FOUND,
|
|
17929
|
-
retryable: true
|
|
18632
|
+
retryable: true,
|
|
18633
|
+
daemonExiting: true
|
|
17930
18634
|
};
|
|
17931
18635
|
}
|
|
17932
18636
|
const message = getErrorMessage(error);
|
|
@@ -18270,7 +18974,7 @@ function assertUserDataDir(value) {
|
|
|
18270
18974
|
let reason;
|
|
18271
18975
|
if (!value.trim()) reason = "the path is empty";
|
|
18272
18976
|
else if (value.startsWith("-")) reason = "it looks like an option; -u needs a directory path";
|
|
18273
|
-
else if (
|
|
18977
|
+
else if (fs15.existsSync(resolved) && !fs15.statSync(resolved).isDirectory()) reason = "it is a file";
|
|
18274
18978
|
if (reason === void 0) return;
|
|
18275
18979
|
const err = invalidUserDataDirError(value, reason);
|
|
18276
18980
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
@@ -18325,6 +19029,491 @@ function assertValidChromeWsUrl(url) {
|
|
|
18325
19029
|
);
|
|
18326
19030
|
}
|
|
18327
19031
|
|
|
19032
|
+
// src/commands/page.ts
|
|
19033
|
+
init_errors3();
|
|
19034
|
+
init_messages();
|
|
19035
|
+
init_client();
|
|
19036
|
+
init_formatting();
|
|
19037
|
+
init_commands();
|
|
19038
|
+
init_exitCodes();
|
|
19039
|
+
init_url();
|
|
19040
|
+
function formatPageResult(result) {
|
|
19041
|
+
const fmt = new OutputFormatter().text(`\u2713 ${PAGE_ACTION_DONE[result.action]}`).keyValueList(
|
|
19042
|
+
[
|
|
19043
|
+
["URL", result.url],
|
|
19044
|
+
["Title", result.title],
|
|
19045
|
+
...result.status !== void 0 ? [["Status", String(result.status)]] : []
|
|
19046
|
+
],
|
|
19047
|
+
8
|
|
19048
|
+
);
|
|
19049
|
+
if (result.warning) fmt.text(`\u26A0 ${result.warning}`);
|
|
19050
|
+
if (result.loading) fmt.text(`\u26A0 ${pageLoadingWarning(result.loading)}`);
|
|
19051
|
+
return fmt.build();
|
|
19052
|
+
}
|
|
19053
|
+
function isScriptUrl(url) {
|
|
19054
|
+
const [scheme = ""] = url.trim().split(":", 1);
|
|
19055
|
+
return url.includes(":") && scheme.toLowerCase() === "javascript";
|
|
19056
|
+
}
|
|
19057
|
+
async function runPageAction(action, options, url) {
|
|
19058
|
+
await runCommand(
|
|
19059
|
+
async () => {
|
|
19060
|
+
if (url !== void 0 && isScriptUrl(url)) {
|
|
19061
|
+
const err = javascriptNavigationError();
|
|
19062
|
+
return {
|
|
19063
|
+
success: false,
|
|
19064
|
+
error: err.message,
|
|
19065
|
+
exitCode: EXIT_CODES.INVALID_URL,
|
|
19066
|
+
errorContext: { suggestion: err.suggestion }
|
|
19067
|
+
};
|
|
19068
|
+
}
|
|
19069
|
+
if (url !== void 0) {
|
|
19070
|
+
const check = validateUrl(url);
|
|
19071
|
+
if (!check.valid) {
|
|
19072
|
+
return {
|
|
19073
|
+
success: false,
|
|
19074
|
+
error: check.error,
|
|
19075
|
+
exitCode: EXIT_CODES.INVALID_URL,
|
|
19076
|
+
...check.suggestion && { errorContext: { suggestion: check.suggestion } }
|
|
19077
|
+
};
|
|
19078
|
+
}
|
|
19079
|
+
}
|
|
19080
|
+
const response = await pageNavigate({
|
|
19081
|
+
action,
|
|
19082
|
+
...url !== void 0 && { url },
|
|
19083
|
+
wait: options.wait !== false
|
|
19084
|
+
});
|
|
19085
|
+
if (response.status === "error" || !response.data) {
|
|
19086
|
+
return {
|
|
19087
|
+
success: false,
|
|
19088
|
+
error: response.error ?? `Failed to ${action}`,
|
|
19089
|
+
exitCode: response.exitCode ?? EXIT_CODES.SOFTWARE_ERROR,
|
|
19090
|
+
...response.suggestion && { errorContext: { suggestion: response.suggestion } }
|
|
19091
|
+
};
|
|
19092
|
+
}
|
|
19093
|
+
return { success: true, data: response.data };
|
|
19094
|
+
},
|
|
19095
|
+
options,
|
|
19096
|
+
formatPageResult
|
|
19097
|
+
);
|
|
19098
|
+
}
|
|
19099
|
+
async function showPageInfo(options) {
|
|
19100
|
+
await runCommand(
|
|
19101
|
+
async () => {
|
|
19102
|
+
const response = await getStatus();
|
|
19103
|
+
if (response.status === "error") {
|
|
19104
|
+
return {
|
|
19105
|
+
success: false,
|
|
19106
|
+
error: response.error ?? "Failed to read the page",
|
|
19107
|
+
exitCode: EXIT_CODES.SOFTWARE_ERROR
|
|
19108
|
+
};
|
|
19109
|
+
}
|
|
19110
|
+
const page = response.data?.sessionPid ? response.data.pageState : void 0;
|
|
19111
|
+
if (!page) throw noActiveSessionError();
|
|
19112
|
+
return { success: true, data: { url: page.url, title: page.title } };
|
|
19113
|
+
},
|
|
19114
|
+
options,
|
|
19115
|
+
(page) => new OutputFormatter().keyValueList(
|
|
19116
|
+
[
|
|
19117
|
+
["URL", page.url],
|
|
19118
|
+
["Title", page.title]
|
|
19119
|
+
],
|
|
19120
|
+
8
|
|
19121
|
+
).build()
|
|
19122
|
+
);
|
|
19123
|
+
}
|
|
19124
|
+
function emulationRequest(options) {
|
|
19125
|
+
if (options.reset) return { reset: true };
|
|
19126
|
+
if (options.viewport === void 0 && options.colorScheme === void 0) {
|
|
19127
|
+
const err = pageEmulateNothingError();
|
|
19128
|
+
throw new CommandError(
|
|
19129
|
+
err.message,
|
|
19130
|
+
{ suggestion: err.suggestion },
|
|
19131
|
+
EXIT_CODES.INVALID_ARGUMENTS
|
|
19132
|
+
);
|
|
19133
|
+
}
|
|
19134
|
+
return {
|
|
19135
|
+
...options.viewport !== void 0 && { viewport: parseViewport(options.viewport) },
|
|
19136
|
+
...options.colorScheme !== void 0 && {
|
|
19137
|
+
colorScheme: parseColorScheme(options.colorScheme)
|
|
19138
|
+
}
|
|
19139
|
+
};
|
|
19140
|
+
}
|
|
19141
|
+
async function emulate(options) {
|
|
19142
|
+
await runCommand(
|
|
19143
|
+
async () => {
|
|
19144
|
+
const response = await pageEmulate(emulationRequest(options));
|
|
19145
|
+
if (response.status === "error" || !response.data) {
|
|
19146
|
+
return {
|
|
19147
|
+
success: false,
|
|
19148
|
+
error: response.error ?? "Failed to change the emulation",
|
|
19149
|
+
exitCode: response.exitCode ?? EXIT_CODES.SOFTWARE_ERROR,
|
|
19150
|
+
...response.suggestion && { errorContext: { suggestion: response.suggestion } }
|
|
19151
|
+
};
|
|
19152
|
+
}
|
|
19153
|
+
return { success: true, data: response.data };
|
|
19154
|
+
},
|
|
19155
|
+
options,
|
|
19156
|
+
(result) => new OutputFormatter().text("\u2713 Page emulation changed").keyValueList(pageEmulationLines(result), 10).build()
|
|
19157
|
+
);
|
|
19158
|
+
}
|
|
19159
|
+
function registerPageCommands(program2) {
|
|
19160
|
+
const page = program2.command("page").description(
|
|
19161
|
+
"The session page: info (URL and title), navigate <url>, reload, back, forward, emulate (viewport, color scheme)"
|
|
19162
|
+
);
|
|
19163
|
+
page.command("info").description(PAGE_INFO_DESCRIPTION).addOption(jsonOption()).action(async (options) => {
|
|
19164
|
+
await showPageInfo(options);
|
|
19165
|
+
});
|
|
19166
|
+
const withCommon = (command) => command.option("--no-wait", "Return without waiting for the page to load").addOption(jsonOption());
|
|
19167
|
+
withCommon(
|
|
19168
|
+
page.command("navigate").description("Load a URL in the session page").argument("<url>", "URL to load")
|
|
19169
|
+
).action(async (url, options) => {
|
|
19170
|
+
await runPageAction("navigate", options, url);
|
|
19171
|
+
});
|
|
19172
|
+
page.command("emulate").description(PAGE_EMULATE_DESCRIPTION).option("--viewport <WxH>", "Viewport size in CSS px, e.g. 900x700").option("--color-scheme <scheme>", "Emulate prefers-color-scheme: light or dark").addOption(
|
|
19173
|
+
new Option("--reset", "Back to the browser window size and the system setting").conflicts([
|
|
19174
|
+
"viewport",
|
|
19175
|
+
"colorScheme"
|
|
19176
|
+
])
|
|
19177
|
+
).addOption(jsonOption()).action(async (options) => {
|
|
19178
|
+
await emulate(options);
|
|
19179
|
+
});
|
|
19180
|
+
for (const action of ["reload", "back", "forward"]) {
|
|
19181
|
+
withCommon(page.command(action).description(PAGE_ACTION_DESCRIPTIONS[action])).action(
|
|
19182
|
+
async (options) => {
|
|
19183
|
+
await runPageAction(action, options);
|
|
19184
|
+
}
|
|
19185
|
+
);
|
|
19186
|
+
}
|
|
19187
|
+
}
|
|
19188
|
+
|
|
19189
|
+
// src/commands/peek.ts
|
|
19190
|
+
function parseOptions(options) {
|
|
19191
|
+
const lastN = positiveIntRule({
|
|
19192
|
+
name: "--last",
|
|
19193
|
+
min: 1,
|
|
19194
|
+
max: MAX_LAST_ITEMS,
|
|
19195
|
+
default: 10,
|
|
19196
|
+
allowZeroForAll: true
|
|
19197
|
+
}).validate(options.last);
|
|
19198
|
+
const resourceTypes = resourceTypeRule().validate(options.type);
|
|
19199
|
+
return { lastN, resourceTypes };
|
|
19200
|
+
}
|
|
19201
|
+
function peekSection(options) {
|
|
19202
|
+
if (options.network) return "network";
|
|
19203
|
+
return options.console ? "console" : void 0;
|
|
19204
|
+
}
|
|
19205
|
+
async function fetchAndFilterPreview(lastN, resourceTypes, only) {
|
|
19206
|
+
const result = await fetchPreviewOutput({
|
|
19207
|
+
lastN: resourceTypes.length > 0 ? 0 : lastN,
|
|
19208
|
+
...only && { only }
|
|
19209
|
+
});
|
|
19210
|
+
if (!result.success) return result;
|
|
19211
|
+
const output = result.data;
|
|
19212
|
+
const unfilteredNetworkCount = output.totals?.network ?? output.data.network?.length ?? 0;
|
|
19213
|
+
if (resourceTypes.length === 0 || !output.data.network) {
|
|
19214
|
+
return { success: true, data: { output, unfilteredNetworkCount } };
|
|
19215
|
+
}
|
|
19216
|
+
const network = filterByResourceType(output.data.network, resourceTypes);
|
|
19217
|
+
const filteredOutput = {
|
|
19218
|
+
...output,
|
|
19219
|
+
data: { ...output.data, network },
|
|
19220
|
+
...output.totals && { totals: { ...output.totals, network: network.length } }
|
|
19221
|
+
};
|
|
19222
|
+
return { success: true, data: { output: filteredOutput, unfilteredNetworkCount } };
|
|
19223
|
+
}
|
|
19224
|
+
function createPreviewOptions(base, resourceTypes, unfilteredCount) {
|
|
19225
|
+
const options = { ...base };
|
|
19226
|
+
if (base.follow) options.viewedAt = /* @__PURE__ */ new Date();
|
|
19227
|
+
if (resourceTypes.length > 0) {
|
|
19228
|
+
options.filteredTypes = resourceTypes;
|
|
19229
|
+
options.unfilteredNetworkCount = unfilteredCount;
|
|
19230
|
+
}
|
|
19231
|
+
return options;
|
|
19232
|
+
}
|
|
19233
|
+
async function runFollowMode3(options, lastN, resourceTypes, baseOptions) {
|
|
19234
|
+
const showPreview = async () => {
|
|
19235
|
+
const result = await fetchAndFilterPreview(lastN, resourceTypes, peekSection(options));
|
|
19236
|
+
if (!result.success) {
|
|
19237
|
+
return followFetchFailure(result, { json: options.json, retryIntervalMs: 1e3 });
|
|
19238
|
+
}
|
|
19239
|
+
noteFollowConnected();
|
|
19240
|
+
if (!options.json) console.clear();
|
|
19241
|
+
const previewOptions = createPreviewOptions(
|
|
19242
|
+
baseOptions,
|
|
19243
|
+
resourceTypes,
|
|
19244
|
+
result.data.unfilteredNetworkCount
|
|
19245
|
+
);
|
|
19246
|
+
console.log(formatPreview(result.data.output, previewOptions));
|
|
19247
|
+
return void 0;
|
|
19248
|
+
};
|
|
19249
|
+
await setupFollowMode(showPreview, {
|
|
19250
|
+
startMessage: followingPreviewMessage,
|
|
19251
|
+
stopMessage: stoppedFollowingPreviewMessage,
|
|
19252
|
+
intervalMs: 1e3
|
|
19253
|
+
});
|
|
19254
|
+
}
|
|
19255
|
+
function registerPeekCommand(program2) {
|
|
19256
|
+
program2.command("peek").description("Preview collected data without stopping the session").addOption(jsonOption()).option("-v, --verbose", "Use verbose output with full URLs and formatting", false).option("-n, --network", "Show only network requests", false).option("-c, --console", "Show only console messages", false).option("-f, --follow", "Watch for updates (like tail -f)", false).option("--last <count>", "Show last N items, 0 for all", "10").option(
|
|
19257
|
+
"--type <types>",
|
|
19258
|
+
"Filter network requests by resource type (comma-separated: Document,XHR,Fetch,etc.)"
|
|
19259
|
+
).action(async (options) => {
|
|
19260
|
+
showBothSectionsWhenBothRequested(options);
|
|
19261
|
+
if (options.network && !options.json) {
|
|
19262
|
+
console.error(
|
|
19263
|
+
'Note: "bdg peek --network" is deprecated. Use "bdg network list" for enhanced filtering.'
|
|
19264
|
+
);
|
|
19265
|
+
}
|
|
19266
|
+
let lastN;
|
|
19267
|
+
let resourceTypes;
|
|
19268
|
+
try {
|
|
19269
|
+
const parsed = parseOptions(options);
|
|
19270
|
+
lastN = parsed.lastN;
|
|
19271
|
+
resourceTypes = parsed.resourceTypes;
|
|
19272
|
+
} catch (error) {
|
|
19273
|
+
handleValidationError(error, options.json ?? false);
|
|
19274
|
+
}
|
|
19275
|
+
const baseOptions = {
|
|
19276
|
+
json: options.json,
|
|
19277
|
+
network: options.network,
|
|
19278
|
+
console: options.console,
|
|
19279
|
+
last: lastN,
|
|
19280
|
+
verbose: options.verbose,
|
|
19281
|
+
follow: options.follow
|
|
19282
|
+
};
|
|
19283
|
+
if (options.follow) {
|
|
19284
|
+
await runFollowMode3(options, lastN, resourceTypes, baseOptions);
|
|
19285
|
+
return;
|
|
19286
|
+
}
|
|
19287
|
+
await runCommand(
|
|
19288
|
+
async () => {
|
|
19289
|
+
const result = await fetchAndFilterPreview(lastN, resourceTypes, peekSection(options));
|
|
19290
|
+
if (!result.success) {
|
|
19291
|
+
return createErrorResult(result.error, result.exitCode, result.suggestion);
|
|
19292
|
+
}
|
|
19293
|
+
const { output } = result.data;
|
|
19294
|
+
return {
|
|
19295
|
+
success: true,
|
|
19296
|
+
data: options.json ? buildPreviewJsonData(output, baseOptions) : output
|
|
19297
|
+
};
|
|
19298
|
+
},
|
|
19299
|
+
options,
|
|
19300
|
+
(data) => {
|
|
19301
|
+
const output = data;
|
|
19302
|
+
const previewOptions = createPreviewOptions(
|
|
19303
|
+
baseOptions,
|
|
19304
|
+
resourceTypes,
|
|
19305
|
+
output.data.network?.length ?? 0
|
|
19306
|
+
);
|
|
19307
|
+
return formatPreview(output, previewOptions);
|
|
19308
|
+
}
|
|
19309
|
+
);
|
|
19310
|
+
});
|
|
19311
|
+
}
|
|
19312
|
+
|
|
19313
|
+
// src/session/sessionList.ts
|
|
19314
|
+
init_client();
|
|
19315
|
+
import * as fs17 from "fs";
|
|
19316
|
+
init_paths();
|
|
19317
|
+
|
|
19318
|
+
// src/session/portClaims.ts
|
|
19319
|
+
import * as fs16 from "fs";
|
|
19320
|
+
init_paths();
|
|
19321
|
+
init_logging();
|
|
19322
|
+
var log26 = createLogger("session");
|
|
19323
|
+
function readPortFile(portPath) {
|
|
19324
|
+
try {
|
|
19325
|
+
const port = parseInt(fs16.readFileSync(portPath, "utf-8").trim(), 10);
|
|
19326
|
+
return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : null;
|
|
19327
|
+
} catch (error) {
|
|
19328
|
+
logDebugError(log26, `read ${portPath}`, error);
|
|
19329
|
+
return null;
|
|
19330
|
+
}
|
|
19331
|
+
}
|
|
19332
|
+
|
|
19333
|
+
// src/session/sessionName.ts
|
|
19334
|
+
init_errors3();
|
|
19335
|
+
init_messages();
|
|
19336
|
+
init_paths();
|
|
19337
|
+
init_exitCodes();
|
|
19338
|
+
var MAX_SESSION_NAME_LENGTH = 40;
|
|
19339
|
+
var SESSION_NAME_PATTERN = new RegExp(
|
|
19340
|
+
`^[A-Za-z0-9][A-Za-z0-9_-]{0,${MAX_SESSION_NAME_LENGTH - 1}}$`
|
|
19341
|
+
);
|
|
19342
|
+
function isValidSessionName(name) {
|
|
19343
|
+
return SESSION_NAME_PATTERN.test(name);
|
|
19344
|
+
}
|
|
19345
|
+
function normalizeSessionName(name) {
|
|
19346
|
+
return name.toLowerCase();
|
|
19347
|
+
}
|
|
19348
|
+
function validateSessionName(name) {
|
|
19349
|
+
if (!isValidSessionName(name)) {
|
|
19350
|
+
throwInvalid(invalidSessionNameError(name, MAX_SESSION_NAME_LENGTH));
|
|
19351
|
+
}
|
|
19352
|
+
const shortest = namedSocketPath("a");
|
|
19353
|
+
if (Buffer.byteLength(shortest) > MAX_DAEMON_SOCKET_PATH_BYTES) {
|
|
19354
|
+
throwInvalid(socketPathTooLongError(shortest, MAX_DAEMON_SOCKET_PATH_BYTES));
|
|
19355
|
+
}
|
|
19356
|
+
const socketPath = namedSocketPath(name);
|
|
19357
|
+
if (Buffer.byteLength(socketPath) > MAX_DAEMON_SOCKET_PATH_BYTES) {
|
|
19358
|
+
throwInvalid(sessionNameSocketTooLongError(name, socketPath, MAX_DAEMON_SOCKET_PATH_BYTES));
|
|
19359
|
+
}
|
|
19360
|
+
}
|
|
19361
|
+
function namedSocketPath(name) {
|
|
19362
|
+
return sessionFilePathIn(getNamedSessionDir(name), "DAEMON_SOCKET");
|
|
19363
|
+
}
|
|
19364
|
+
function throwInvalid(err) {
|
|
19365
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
19366
|
+
}
|
|
19367
|
+
function selectSession(optionValue) {
|
|
19368
|
+
const name = optionValue ?? process.env[SESSION_NAME_ENV]?.trim();
|
|
19369
|
+
if (name === void 0 || optionValue === void 0 && name === "") {
|
|
19370
|
+
delete process.env[SESSION_NAME_ENV];
|
|
19371
|
+
return;
|
|
19372
|
+
}
|
|
19373
|
+
validateSessionName(name);
|
|
19374
|
+
process.env[SESSION_NAME_ENV] = normalizeSessionName(name);
|
|
19375
|
+
}
|
|
19376
|
+
|
|
19377
|
+
// src/session/sessionList.ts
|
|
19378
|
+
init_logging();
|
|
19379
|
+
init_sessionCommand();
|
|
19380
|
+
var log27 = createLogger("session");
|
|
19381
|
+
function toRunningSession(name, data) {
|
|
19382
|
+
const meta = data.sessionMetadata;
|
|
19383
|
+
const state = meta ? "active" : data.ending ? "ending" : "starting";
|
|
19384
|
+
const url = data.pageState?.url ?? data.starting?.url;
|
|
19385
|
+
return {
|
|
19386
|
+
name,
|
|
19387
|
+
state,
|
|
19388
|
+
...url !== void 0 && { url },
|
|
19389
|
+
...meta && { port: meta.port },
|
|
19390
|
+
daemonPid: data.daemonPid,
|
|
19391
|
+
...meta?.chromePid && { chromePid: meta.chromePid }
|
|
19392
|
+
};
|
|
19393
|
+
}
|
|
19394
|
+
async function describeSession(entry) {
|
|
19395
|
+
const { name, dir } = entry;
|
|
19396
|
+
const socketPath = sessionFilePathIn(dir, "DAEMON_SOCKET");
|
|
19397
|
+
const probe = await probeDaemonSocket(socketPath);
|
|
19398
|
+
if (probe !== "alive") return describeWithoutSocket(entry, probe);
|
|
19399
|
+
try {
|
|
19400
|
+
const response = await getStatus(socketPath);
|
|
19401
|
+
if (response.status === "ok" && response.data) return toRunningSession(name, response.data);
|
|
19402
|
+
} catch (error) {
|
|
19403
|
+
logDebugError(log27, `get status of ${dir}`, error);
|
|
19404
|
+
}
|
|
19405
|
+
return { name, state: "unresponsive" };
|
|
19406
|
+
}
|
|
19407
|
+
function describeWithoutSocket(entry, probe) {
|
|
19408
|
+
const daemonPid = readLiveDaemonPid(entry.dir);
|
|
19409
|
+
if (daemonPid !== null) return { name: entry.name, state: "starting", daemonPid };
|
|
19410
|
+
return describeLeftovers(entry, probe);
|
|
19411
|
+
}
|
|
19412
|
+
function describeLeftovers({ name, dir }, probe) {
|
|
19413
|
+
const chromePid = readPidFromFile(sessionFilePathIn(dir, "CHROME_PID"));
|
|
19414
|
+
const orphan = chromePid !== null && isProcessAlive(chromePid) && isSessionChrome(chromePid, dir);
|
|
19415
|
+
const leftover = probe === "stale" || SESSION_STATE_FILES.some((type) => fs17.existsSync(sessionFilePathIn(dir, type)));
|
|
19416
|
+
if (!orphan && !leftover) return null;
|
|
19417
|
+
const port = leftoverPort(dir);
|
|
19418
|
+
return {
|
|
19419
|
+
name,
|
|
19420
|
+
state: orphan ? "crashed" : "stale",
|
|
19421
|
+
...port !== null && { port },
|
|
19422
|
+
...orphan && { chromePid },
|
|
19423
|
+
cleanup: sessionCommand("bdg cleanup", name)
|
|
19424
|
+
};
|
|
19425
|
+
}
|
|
19426
|
+
function leftoverPort(dir) {
|
|
19427
|
+
try {
|
|
19428
|
+
const meta = JSON.parse(fs17.readFileSync(sessionFilePathIn(dir, "METADATA"), "utf8"));
|
|
19429
|
+
if (typeof meta.port === "number") return meta.port;
|
|
19430
|
+
} catch (error) {
|
|
19431
|
+
logDebugError(log27, `read the metadata in ${dir}`, error);
|
|
19432
|
+
}
|
|
19433
|
+
return readPortFile(sessionFilePathIn(dir, "PORT"));
|
|
19434
|
+
}
|
|
19435
|
+
function isSelectable({ name }) {
|
|
19436
|
+
return name === null || isValidSessionName(name) && name === normalizeSessionName(name);
|
|
19437
|
+
}
|
|
19438
|
+
function isSameDir(a, b) {
|
|
19439
|
+
try {
|
|
19440
|
+
const first = fs17.statSync(a);
|
|
19441
|
+
const second = fs17.statSync(b);
|
|
19442
|
+
return first.ino === second.ino && first.dev === second.dev;
|
|
19443
|
+
} catch (error) {
|
|
19444
|
+
logDebugError(log27, `compare ${a} with ${b}`, error);
|
|
19445
|
+
return false;
|
|
19446
|
+
}
|
|
19447
|
+
}
|
|
19448
|
+
function selectableEntry(entry) {
|
|
19449
|
+
if (isSelectable(entry) || entry.name === null) return entry;
|
|
19450
|
+
const lower = normalizeSessionName(entry.name);
|
|
19451
|
+
if (!isValidSessionName(lower)) return null;
|
|
19452
|
+
return isSameDir(entry.dir, getNamedSessionDir(lower)) ? { name: lower, dir: entry.dir } : null;
|
|
19453
|
+
}
|
|
19454
|
+
async function describeUnselectable(entry) {
|
|
19455
|
+
const socketPath = sessionFilePathIn(entry.dir, "DAEMON_SOCKET");
|
|
19456
|
+
if (await probeDaemonSocket(socketPath) === "alive") return describeSession(entry);
|
|
19457
|
+
return { name: entry.name, state: "stale", cleanup: removeDirCommand(entry.dir) };
|
|
19458
|
+
}
|
|
19459
|
+
async function listRunningSessions() {
|
|
19460
|
+
const listed = /* @__PURE__ */ new Set();
|
|
19461
|
+
const sessions = await Promise.all(
|
|
19462
|
+
listSessionDirs().map((entry) => {
|
|
19463
|
+
const selectable = selectableEntry(entry);
|
|
19464
|
+
if (selectable === null) return describeUnselectable(entry);
|
|
19465
|
+
if (listed.has(selectable.name)) return Promise.resolve(null);
|
|
19466
|
+
listed.add(selectable.name);
|
|
19467
|
+
return describeSession(selectable);
|
|
19468
|
+
})
|
|
19469
|
+
);
|
|
19470
|
+
return sessions.filter((session) => session !== null);
|
|
19471
|
+
}
|
|
19472
|
+
|
|
19473
|
+
// src/ui/formatters/sessions.ts
|
|
19474
|
+
init_formatting();
|
|
19475
|
+
var DEFAULT_SESSION_LABEL = "(default)";
|
|
19476
|
+
function formatSessionList(data) {
|
|
19477
|
+
const fmt = new OutputFormatter();
|
|
19478
|
+
if (data.sessions.length === 0) {
|
|
19479
|
+
return fmt.text("No running sessions").hints("Suggestions:", ["Start one: bdg <url> [--session <name>]"]).build();
|
|
19480
|
+
}
|
|
19481
|
+
const rows = data.sessions.map((session) => [
|
|
19482
|
+
session.name ?? DEFAULT_SESSION_LABEL,
|
|
19483
|
+
session.state,
|
|
19484
|
+
session.port?.toString() ?? "-",
|
|
19485
|
+
session.daemonPid?.toString() ?? "-",
|
|
19486
|
+
session.url ?? "-"
|
|
19487
|
+
]);
|
|
19488
|
+
const header = ["SESSION", "STATE", "PORT", "PID", "URL"];
|
|
19489
|
+
const widths = header.map(
|
|
19490
|
+
(title, column) => Math.max(title.length, ...rows.map((row) => row[column]?.length ?? 0))
|
|
19491
|
+
);
|
|
19492
|
+
for (const row of [header, ...rows]) {
|
|
19493
|
+
fmt.text(
|
|
19494
|
+
row.map((cell, column) => cell.padEnd(widths[column] ?? 0)).join(" ").trimEnd()
|
|
19495
|
+
);
|
|
19496
|
+
}
|
|
19497
|
+
const cleanups = data.sessions.flatMap((session) => session.cleanup ? [session.cleanup] : []);
|
|
19498
|
+
if (cleanups.length > 0) {
|
|
19499
|
+
fmt.hints("Crashed or stale sessions; clean up with:", cleanups);
|
|
19500
|
+
}
|
|
19501
|
+
return fmt.build();
|
|
19502
|
+
}
|
|
19503
|
+
|
|
19504
|
+
// src/commands/sessions.ts
|
|
19505
|
+
function registerSessionsCommand(program2) {
|
|
19506
|
+
program2.command("sessions").description(
|
|
19507
|
+
"List sessions (default and named) with their state, URL, port and PID, including crashed ones to clean up"
|
|
19508
|
+
).addOption(jsonOption()).action(async (options) => {
|
|
19509
|
+
await runCommand(
|
|
19510
|
+
async () => ({ success: true, data: { sessions: await listRunningSessions() } }),
|
|
19511
|
+
options,
|
|
19512
|
+
formatSessionList
|
|
19513
|
+
);
|
|
19514
|
+
});
|
|
19515
|
+
}
|
|
19516
|
+
|
|
18328
19517
|
// src/commands/status.ts
|
|
18329
19518
|
init_errors3();
|
|
18330
19519
|
init_messages();
|
|
@@ -18645,7 +19834,9 @@ var commandRegistry = [
|
|
|
18645
19834
|
addCommandGroup("Network Commands:"),
|
|
18646
19835
|
registerNetworkCommands,
|
|
18647
19836
|
addCommandGroup("Console Commands:"),
|
|
18648
|
-
registerConsoleCommand
|
|
19837
|
+
registerConsoleCommand,
|
|
19838
|
+
addCommandGroup("Agent Setup:"),
|
|
19839
|
+
registerInstallSkillCommand
|
|
18649
19840
|
];
|
|
18650
19841
|
|
|
18651
19842
|
// src/index.ts
|