@csmodding/gameface-devtools-mcp 1.0.1 → 1.1.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.
Files changed (2) hide show
  1. package/dist/server.mjs +267 -97
  2. package/package.json +1 -1
package/dist/server.mjs CHANGED
@@ -30319,7 +30319,7 @@ class StdioServerTransport {
30319
30319
  }
30320
30320
 
30321
30321
  // src/server.ts
30322
- var import_common_tags5 = __toESM(require_lib(), 1);
30322
+ var import_common_tags6 = __toESM(require_lib(), 1);
30323
30323
 
30324
30324
  // src/cdp.ts
30325
30325
  var import_common_tags = __toESM(require_lib(), 1);
@@ -30603,7 +30603,143 @@ function num(value, fallback) {
30603
30603
  }
30604
30604
 
30605
30605
  // src/console.ts
30606
+ var import_common_tags3 = __toESM(require_lib(), 1);
30607
+
30608
+ // src/selectors.ts
30606
30609
  var import_common_tags2 = __toESM(require_lib(), 1);
30610
+ var INVALID_SELECTOR_MARKER = "Invalid CSS selector";
30611
+ var SELECTOR_PREFIX = `${INVALID_SELECTOR_MARKER} (`;
30612
+ var SELECTOR_TAIL = /\) in \w+!/gu;
30613
+ var SUPPORTED_PSEUDOS = new Set([
30614
+ ":first-child",
30615
+ ":last-child",
30616
+ ":only-child",
30617
+ ":nth-child",
30618
+ ":root",
30619
+ ":hover",
30620
+ ":focus",
30621
+ ":active",
30622
+ ":before",
30623
+ ":after",
30624
+ "::before",
30625
+ "::after"
30626
+ ]);
30627
+ var SUPPORTED_SUMMARY = import_common_tags2.oneLine`
30628
+ type, class, id and attribute selectors, combinators, \`:first-child\`, \`:last-child\`,
30629
+ \`:only-child\`, \`:nth-child()\`, \`:root\`, \`:hover\`, \`:focus\`, \`:active\`, \`::before\`
30630
+ and \`::after\`
30631
+ `;
30632
+ var NTH_CHILD_ARGUMENT = /^\s*(?:[+-]?\d+|even|odd|[+-]?\d*n)\s*$/iu;
30633
+ var PSEUDO_TOKEN = /(?<colons>::?)(?<name>[a-z][a-z-]*)(?:\((?<argument>[^()]*)\))?/giu;
30634
+ var LITERAL_SPANS = [/\\./gu, /"[^"]*"|'[^']*'/gu, /\[[^\])]*\]?/gu];
30635
+ var MASK_FILLER = "#";
30636
+ var SCAN_PATTERN = scanPattern();
30637
+ var BRANCH_PER_QUERY = `run one query per branch of the list and merge the results.`;
30638
+ var DISABLED_HINT = `${scanPattern("`el.disabled`")}.`;
30639
+ var HINTS = new Map([
30640
+ [":not", `${SCAN_PATTERN}.`],
30641
+ [":has", `${scanPattern("what the element contains")}.`],
30642
+ [":is", BRANCH_PER_QUERY],
30643
+ [":where", BRANCH_PER_QUERY],
30644
+ [":first-of-type", ofTypeHint(":first-child")],
30645
+ [":last-of-type", ofTypeHint(":last-child")],
30646
+ [":only-of-type", ofTypeHint(":only-child")],
30647
+ [":nth-of-type", ofTypeHint(":nth-child()")],
30648
+ [
30649
+ ":nth-child",
30650
+ import_common_tags2.oneLine`
30651
+ the engine takes an integer, \`even\`, \`odd\`, or a bare \`an\` step here, and throws on an
30652
+ \`an+b\` offset.
30653
+ `
30654
+ ],
30655
+ [
30656
+ ":nth-last-child",
30657
+ `\`:nth-child()\` counts from the start only, so ${scanPattern("a position from the end")}.`
30658
+ ],
30659
+ [":empty", `${scanPattern("`el.children.length` and `el.textContent`")}.`],
30660
+ [":checked", `${scanPattern("`el.checked`")}.`],
30661
+ [":disabled", DISABLED_HINT],
30662
+ [":enabled", DISABLED_HINT]
30663
+ ]);
30664
+ var GENERIC_HINT = import_common_tags2.oneLine`
30665
+ express the condition in JS (${SCAN_PATTERN}) or drop it from the selector.
30666
+ `;
30667
+ var DIAGNOSIS_LEAD = `Cohtml's JS query APIs rejected this selector.`;
30668
+ var SYNTAX_SLIP = `a syntax slip, an unclosed bracket or quote`;
30669
+ var SYNTAX_SLIP_EXIT = `If that is not the fault, check the selector for ${SYNTAX_SLIP}.`;
30670
+ var GENERIC_DIAGNOSIS = import_common_tags2.oneLine`
30671
+ ${DIAGNOSIS_LEAD} It names no construct known to be unsupported, so check it for ${SYNTAX_SLIP}.
30672
+ Verified to work here: ${SUPPORTED_SUMMARY}; a pseudo-class outside that set may still throw.
30673
+ `;
30674
+ function isInvalidSelectorError(engineError) {
30675
+ return engineError.includes(INVALID_SELECTOR_MARKER);
30676
+ }
30677
+ function diagnoseSelectorError(engineError, selector) {
30678
+ if (!isInvalidSelectorError(engineError)) {
30679
+ return engineError;
30680
+ }
30681
+ const suspects = findSuspects(selector ?? selectorFromError(engineError));
30682
+ if (suspects.length == 0) {
30683
+ return `${engineError}
30684
+
30685
+ ${GENERIC_DIAGNOSIS}`;
30686
+ }
30687
+ const sentences = suspects.map(({ token, hint }) => `\`${token}\` falls outside the set this engine answers: ${hint}`);
30688
+ return `${engineError}
30689
+
30690
+ ${DIAGNOSIS_LEAD} ${sentences.join(" ")} ${SYNTAX_SLIP_EXIT}`;
30691
+ }
30692
+ function selectorFromError(engineError) {
30693
+ const start = engineError.indexOf(SELECTOR_PREFIX);
30694
+ if (start == -1) {
30695
+ return "";
30696
+ }
30697
+ const afterPrefix = engineError.slice(start + SELECTOR_PREFIX.length);
30698
+ const tails = [...afterPrefix.matchAll(SELECTOR_TAIL)];
30699
+ return tails.length == 1 ? afterPrefix.slice(0, tails[0]?.index) : "";
30700
+ }
30701
+ function findSuspects(selector) {
30702
+ const suspects = [];
30703
+ const seen = new Set;
30704
+ const scanner = new RegExp(PSEUDO_TOKEN.source, PSEUDO_TOKEN.flags);
30705
+ const scannable = maskLiterals(selector);
30706
+ let match = scanner.exec(scannable);
30707
+ while (match != null) {
30708
+ const { colons = "", name = "", argument } = match.groups ?? {};
30709
+ const key = `${colons}${name}`.toLowerCase();
30710
+ if (!seen.has(key) && !isSupported(key, argument)) {
30711
+ seen.add(key);
30712
+ suspects.push({
30713
+ token: selector.slice(match.index, match.index + match[0].length),
30714
+ hint: HINTS.get(key) ?? GENERIC_HINT
30715
+ });
30716
+ }
30717
+ scanner.lastIndex = match.index + colons.length + name.length;
30718
+ match = scanner.exec(scannable);
30719
+ }
30720
+ return suspects;
30721
+ }
30722
+ function maskLiterals(selector) {
30723
+ return LITERAL_SPANS.reduce((masked, span) => masked.replace(span, (literal3) => MASK_FILLER.repeat(literal3.length)), selector);
30724
+ }
30725
+ function isSupported(key, argument) {
30726
+ if (!SUPPORTED_PSEUDOS.has(key)) {
30727
+ return false;
30728
+ }
30729
+ return key != ":nth-child" || argument == null || NTH_CHILD_ARGUMENT.test(argument);
30730
+ }
30731
+ function scanPattern(condition) {
30732
+ return import_common_tags2.oneLine`
30733
+ filter \`querySelectorAll\` results in \`game_eval\`${condition ? ` on ${condition}` : ""},
30734
+ tag the element you want with a data attribute, then query that attribute
30735
+ `;
30736
+ }
30737
+ function ofTypeHint(equivalent) {
30738
+ return import_common_tags2.oneLine`
30739
+ use \`${equivalent}\` where the parent holds only that element type,
30740
+ otherwise ${SCAN_PATTERN}.
30741
+ `;
30742
+ }
30607
30743
 
30608
30744
  // src/shared.ts
30609
30745
  function text(value) {
@@ -30635,6 +30771,9 @@ function formatException(details) {
30635
30771
  const desc = exception?.description ?? exception?.value ?? details?.text ?? `unknown error`;
30636
30772
  return typeof desc == "string" ? desc : JSON.stringify(desc);
30637
30773
  }
30774
+ function explainException(details, selector) {
30775
+ return diagnoseSelectorError(formatException(details), selector);
30776
+ }
30638
30777
  function valToStr(value) {
30639
30778
  if (typeof value == "string") {
30640
30779
  return value;
@@ -30791,7 +30930,7 @@ async function gameConsole(client, buffer, options) {
30791
30930
  }
30792
30931
  const lines = buffer.read(options);
30793
30932
  if (lines.length == 0) {
30794
- return text(import_common_tags2.oneLine`
30933
+ return text(import_common_tags3.oneLine`
30795
30934
  No console entries captured yet.
30796
30935
  Capture begins once the server connects to the application;
30797
30936
  trigger some UI activity (or a game_eval console.log) and retry.
@@ -31113,7 +31252,7 @@ function unknownValue() {
31113
31252
  }
31114
31253
 
31115
31254
  // src/debugger.ts
31116
- var import_common_tags3 = __toESM(require_lib(), 1);
31255
+ var import_common_tags4 = __toESM(require_lib(), 1);
31117
31256
  import { setTimeout as sleep } from "node:timers/promises";
31118
31257
  var POLL_INTERVAL_MS = 50;
31119
31258
  var PAUSE_WAIT_MS = 3000;
@@ -31123,32 +31262,32 @@ var MAX_SCOPE_VARIABLES = 50;
31123
31262
  var MAX_SEARCH_MATCHES = 10;
31124
31263
  var SEARCH_SNIPPET_MARGIN = 40;
31125
31264
  var DEFAULT_SOURCE_MAX_CHARS = 4000;
31126
- var EMPTY_SCRIPT_MAP_NOTE = import_common_tags3.oneLine`
31265
+ var EMPTY_SCRIPT_MAP_NOTE = import_common_tags4.oneLine`
31127
31266
  No scripts are parsed on this connection: a view reload re-parses every one of them.
31128
31267
  Take the reload count from game_status, game_eval of location.reload(), then game_wait with
31129
31268
  reload true and that count as sinceReloads, then call this again.
31130
31269
  `;
31131
- var NO_MATCH_NOTE = import_common_tags3.oneLine`
31270
+ var NO_MATCH_NOTE = import_common_tags4.oneLine`
31132
31271
  Nothing matched. The search is case-sensitive and literal (no regex), and it only reaches scripts
31133
31272
  parsed on this connection: game_debug_scripts lists them.
31134
31273
  `;
31135
- var FILTER_MISS_NOTE = import_common_tags3.oneLine`
31274
+ var FILTER_MISS_NOTE = import_common_tags4.oneLine`
31136
31275
  No parsed script's url contains that urlContains, so the query never ran against anything.
31137
31276
  game_debug_scripts lists the urls; the url filter is case-insensitive, so case is not the cause.
31138
31277
  `;
31139
- var FILTER_MISS_LIST_NOTE = import_common_tags3.oneLine`
31278
+ var FILTER_MISS_LIST_NOTE = import_common_tags4.oneLine`
31140
31279
  No parsed script's url contains that filter, though others are parsed: call this without one to
31141
31280
  see every url. Matching is case-insensitive, so case is not the cause.
31142
31281
  `;
31143
- var UNREADABLE_NOTE = import_common_tags3.oneLine`
31282
+ var UNREADABLE_NOTE = import_common_tags4.oneLine`
31144
31283
  The engine has no source for any of the scripts matched, so the query never ran: their ids are
31145
31284
  stale, which a view reload clears by re-parsing everything under fresh ones.
31146
31285
  `;
31147
- var SEARCH_HINT = import_common_tags3.oneLine`
31286
+ var SEARCH_HINT = import_common_tags4.oneLine`
31148
31287
  Pass a match's line and column to game_debug_set_breakpoint to break there; both are 1-based, as
31149
31288
  everywhere in these tools, and its urlContains takes the match's url as printed.
31150
31289
  `;
31151
- var SINGLE_LINE_HINT = import_common_tags3.oneLine`
31290
+ var SINGLE_LINE_HINT = import_common_tags4.oneLine`
31152
31291
  That script is one line, as a minified bundle is, so the whole module sits on the line the
31153
31292
  resolved location names and the breakpoint bound to the first breakable position at or after the
31154
31293
  one you asked for: read the column above to see where it landed.
@@ -31274,7 +31413,7 @@ class DebuggerSession {
31274
31413
  const cap = 400;
31275
31414
  const from = lineStart && lineStart > first ? lineStart : first;
31276
31415
  if (from > last) {
31277
- return errorText(import_common_tags3.oneLine`
31416
+ return errorText(import_common_tags4.oneLine`
31278
31417
  Script ${scriptId} spans lines ${first}-${last}, so there is nothing at line ${from}.
31279
31418
  `);
31280
31419
  }
@@ -31292,7 +31431,7 @@ class DebuggerSession {
31292
31431
  notes.push(`showing lines ${from}-${shownTo} of ${last}`);
31293
31432
  }
31294
31433
  if (clipped) {
31295
- notes.push(import_common_tags3.oneLine`
31434
+ notes.push(import_common_tags4.oneLine`
31296
31435
  clipped to ${budget} of ${rendered.length} characters: raise maxChars for more, or reach
31297
31436
  a position inside a minified line with game_debug_search_source
31298
31437
  `);
@@ -31336,10 +31475,10 @@ class DebuggerSession {
31336
31475
  condition,
31337
31476
  resolvedLocations: resolved,
31338
31477
  pending: locations.length == 0,
31339
- note: locations.length == 0 ? import_common_tags3.oneLine`
31478
+ note: locations.length == 0 ? import_common_tags4.oneLine`
31340
31479
  Pending: no matching script/line loaded yet, or the line has no code.
31341
31480
  It will bind when the script loads.
31342
- ` : import_common_tags3.oneLine`
31481
+ ` : import_common_tags4.oneLine`
31343
31482
  Hitting this breakpoint FREEZES the UI until you resume
31344
31483
  (game_debug_step resume).
31345
31484
  `,
@@ -31402,7 +31541,7 @@ class DebuggerSession {
31402
31541
  if (this.paused) {
31403
31542
  const frame = this.paused.callFrames[frameIndex];
31404
31543
  if (!frame) {
31405
- return errorText(import_common_tags3.oneLine`
31544
+ return errorText(import_common_tags4.oneLine`
31406
31545
  No call frame at index ${frameIndex}
31407
31546
  (paused stack has ${this.paused.callFrames.length}).
31408
31547
  `);
@@ -31414,7 +31553,7 @@ class DebuggerSession {
31414
31553
  silent: true
31415
31554
  });
31416
31555
  if (res2.exceptionDetails) {
31417
- return errorText(`Eval threw: ${formatException(res2.exceptionDetails)}`);
31556
+ return errorText(`Eval threw: ${explainException(res2.exceptionDetails)}`);
31418
31557
  }
31419
31558
  const value2 = describeRemoteObject(res2.result);
31420
31559
  return text(typeof value2 == "string" ? value2 : valToStr(value2));
@@ -31424,7 +31563,7 @@ class DebuggerSession {
31424
31563
  returnByValue: true
31425
31564
  });
31426
31565
  if (res.exceptionDetails) {
31427
- return errorText(`Eval threw: ${formatException(res.exceptionDetails)}`);
31566
+ return errorText(`Eval threw: ${explainException(res.exceptionDetails)}`);
31428
31567
  }
31429
31568
  const value = describeRemoteObject(res.result);
31430
31569
  return text(typeof value == "string" ? value : valToStr(value));
@@ -31451,7 +31590,7 @@ class DebuggerSession {
31451
31590
  await this.client.call("Debugger.resume");
31452
31591
  await this.waitUntil(() => !this.paused, RESUME_WAIT_MS);
31453
31592
  if (this.paused) {
31454
- return errorText(import_common_tags3.oneLine`
31593
+ return errorText(import_common_tags4.oneLine`
31455
31594
  Resume sent, but the UI is paused again at ${this.topLocation()}
31456
31595
  (reason: ${this.paused.reason}), so it is still frozen.
31457
31596
  Whatever armed that pause sits on the resumed path, so clear it before resuming again:
@@ -31665,7 +31804,7 @@ function snippetAt(source, index, length) {
31665
31804
  }
31666
31805
 
31667
31806
  // src/tools.ts
31668
- var import_common_tags4 = __toESM(require_lib(), 1);
31807
+ var import_common_tags5 = __toESM(require_lib(), 1);
31669
31808
  import { setTimeout as sleep2 } from "node:timers/promises";
31670
31809
  var POLL_INTERVAL_MS2 = 150;
31671
31810
  var MAX_WAIT_MS = 60000;
@@ -31713,7 +31852,7 @@ async function gameStatus(client, reloads) {
31713
31852
  reachable: false,
31714
31853
  endpoint: `http://${host}:${port}`,
31715
31854
  error: error51 instanceof Error ? error51.message : String(error51),
31716
- hint: import_common_tags4.oneLine`
31855
+ hint: import_common_tags5.oneLine`
31717
31856
  Launch the Gameface application with its CDP debug port open, then retry.
31718
31857
  Override host/port via GAMEFACE_HOST / GAMEFACE_PORT.
31719
31858
  `
@@ -31728,7 +31867,7 @@ async function gameEval(client, expression, awaitPromise = false) {
31728
31867
  awaitPromise
31729
31868
  });
31730
31869
  if (res.exceptionDetails) {
31731
- return errorText(`Evaluation threw: ${formatException(res.exceptionDetails)}`);
31870
+ return errorText(`Evaluation threw: ${explainException(res.exceptionDetails)}`);
31732
31871
  }
31733
31872
  const value = describeRemoteObject(res.result);
31734
31873
  return text(typeof value == "string" ? value : JSON.stringify(value, null, 2));
@@ -31742,7 +31881,7 @@ async function gameScreenshot(client, debug, options = {}) {
31742
31881
  await client.connection();
31743
31882
  const { pause } = debug;
31744
31883
  if (pause) {
31745
- return errorText(import_common_tags4.oneLine`
31884
+ return errorText(import_common_tags5.oneLine`
31746
31885
  game_screenshot cannot capture while the JS debugger holds the UI paused
31747
31886
  (${pause.reason}, at ${pause.location}): the capture needs the frame loop the pause has
31748
31887
  frozen, so it would hang until the call times out.
@@ -31757,26 +31896,14 @@ async function gameScreenshot(client, debug, options = {}) {
31757
31896
  if (format == "jpeg") {
31758
31897
  params.quality = quality ?? DEFAULT_JPEG_QUALITY;
31759
31898
  }
31760
- let caption;
31899
+ let clipCaption;
31761
31900
  if (selector) {
31762
- const rectRes = await client.call("Runtime.evaluate", {
31763
- expression: callPageFn(rectFn, selector, index),
31764
- returnByValue: true
31765
- });
31766
- const rect = rectRes.result.value;
31767
- if (!rect?.found) {
31768
- return errorText(import_common_tags4.oneLine`
31769
- No element matched ${JSON.stringify(selector)} for game_screenshot at index ${index}
31770
- (matches found: ${rect?.count ?? 0}).
31771
- `);
31772
- }
31773
- if (!(rect.width > 0 && rect.height > 0)) {
31774
- return errorText(`Element ${JSON.stringify(selector)} has a zero-size box; nothing to capture.`);
31901
+ const clip = await resolveClip(client, selector, index);
31902
+ if (!clip.ok) {
31903
+ return errorText(clip.error);
31775
31904
  }
31776
- params.clip = { x: rect.x, y: rect.y, width: rect.width, height: rect.height, scale: 1 };
31777
- caption = import_common_tags4.oneLine`
31778
- Clipped to ${JSON.stringify(selector)} [index ${index}]. Matches: ${rect.count}.
31779
- `;
31905
+ params.clip = clip.rect;
31906
+ clipCaption = clip.caption;
31780
31907
  }
31781
31908
  const res = await client.call("Page.captureScreenshot", params);
31782
31909
  if (!res?.data) {
@@ -31787,11 +31914,46 @@ async function gameScreenshot(client, debug, options = {}) {
31787
31914
  data: res.data,
31788
31915
  mimeType: format == "jpeg" ? "image/jpeg" : "image/png"
31789
31916
  };
31790
- return { content: caption ? [{ type: "text", text: caption }, image] : [image] };
31917
+ return { content: clipCaption ? [{ type: "text", text: clipCaption }, image] : [image] };
31791
31918
  } catch (error51) {
31792
31919
  return toErrorResult(error51);
31793
31920
  }
31794
31921
  }
31922
+ async function resolveClip(client, selector, index) {
31923
+ const rectRes = await client.call("Runtime.evaluate", {
31924
+ expression: callPageFn(rectFn, selector, index),
31925
+ returnByValue: true
31926
+ });
31927
+ if (rectRes.exceptionDetails) {
31928
+ return {
31929
+ ok: false,
31930
+ error: `Screenshot clip failed: ${explainException(rectRes.exceptionDetails, selector)}`
31931
+ };
31932
+ }
31933
+ const rect = rectRes.result.value;
31934
+ if (!rect?.found) {
31935
+ return {
31936
+ ok: false,
31937
+ error: import_common_tags5.oneLine`
31938
+ No element matched ${JSON.stringify(selector)} for game_screenshot at index ${index}
31939
+ (matches found: ${rect?.count ?? 0}).
31940
+ `
31941
+ };
31942
+ }
31943
+ if (!(rect.width > 0 && rect.height > 0)) {
31944
+ return {
31945
+ ok: false,
31946
+ error: `Element ${JSON.stringify(selector)} has a zero-size box; nothing to capture.`
31947
+ };
31948
+ }
31949
+ return {
31950
+ ok: true,
31951
+ rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height, scale: 1 },
31952
+ caption: import_common_tags5.oneLine`
31953
+ Clipped to ${JSON.stringify(selector)} [index ${index}]. Matches: ${rect.count}.
31954
+ `
31955
+ };
31956
+ }
31795
31957
  async function gameDom(client, selector, all = false, maxHtml = 4000) {
31796
31958
  try {
31797
31959
  const res = await client.call("Runtime.evaluate", {
@@ -31799,7 +31961,7 @@ async function gameDom(client, selector, all = false, maxHtml = 4000) {
31799
31961
  returnByValue: true
31800
31962
  });
31801
31963
  if (res.exceptionDetails) {
31802
- return errorText(`DOM query failed: ${formatException(res.exceptionDetails)}`);
31964
+ return errorText(`DOM query failed: ${explainException(res.exceptionDetails, selector)}`);
31803
31965
  }
31804
31966
  const value = res.result.value;
31805
31967
  if (!value || value.count == 0) {
@@ -31834,7 +31996,7 @@ async function gameQuery(client, options) {
31834
31996
  returnByValue: true
31835
31997
  });
31836
31998
  if (res.exceptionDetails) {
31837
- return errorText(`Query failed: ${formatException(res.exceptionDetails)}`);
31999
+ return errorText(`Query failed: ${explainException(res.exceptionDetails, args.sel)}`);
31838
32000
  }
31839
32001
  const value = res.result.value;
31840
32002
  if (!value) {
@@ -31863,13 +32025,13 @@ function queryRejection(options, mode) {
31863
32025
  return `game_query: selector is blank; pass a real one.`;
31864
32026
  }
31865
32027
  if (mode == "equals" && needle != null && needle.trim() != needle) {
31866
- return import_common_tags4.oneLine`
32028
+ return import_common_tags5.oneLine`
31867
32029
  game_query: equals compares against trimmed text, so trim yours; where that empties it, pass
31868
32030
  '' with a selector to find elements carrying no text.
31869
32031
  `;
31870
32032
  }
31871
32033
  if (needle == "" && mode != "equals") {
31872
- return import_common_tags4.oneLine`
32034
+ return import_common_tags5.oneLine`
31873
32035
  game_query: empty text matches every element under ${mode}; pass match: 'equals' with a
31874
32036
  selector to find elements carrying no text.
31875
32037
  `;
@@ -31884,7 +32046,7 @@ function pausedNote(debug) {
31884
32046
  if (!pause) {
31885
32047
  return "";
31886
32048
  }
31887
- return import_common_tags4.oneLine`
32049
+ return import_common_tags5.oneLine`
31888
32050
  The UI is paused in the JS debugger (${pause.reason}, at ${pause.location}): nothing in it can
31889
32051
  change until you resume with game_debug_step action=resume, so resume and retry rather than
31890
32052
  reading this as an unreachable condition.
@@ -31897,16 +32059,16 @@ async function gameClick(client, selector, index = 0) {
31897
32059
  returnByValue: true
31898
32060
  });
31899
32061
  if (res.exceptionDetails) {
31900
- return errorText(`Click failed: ${formatException(res.exceptionDetails)}`);
32062
+ return errorText(`Click failed: ${explainException(res.exceptionDetails, selector)}`);
31901
32063
  }
31902
32064
  const info = res.result.value;
31903
32065
  if (!info?.found) {
31904
- return errorText(import_common_tags4.oneLine`
32066
+ return errorText(import_common_tags5.oneLine`
31905
32067
  No element to click for selector ${JSON.stringify(selector)} at index ${index}
31906
32068
  (matches found: ${info?.count ?? 0}).
31907
32069
  `);
31908
32070
  }
31909
- return text(import_common_tags4.oneLine`
32071
+ return text(import_common_tags5.oneLine`
31910
32072
  Clicked ${JSON.stringify(selector)} [index ${index}] at
31911
32073
  (${info.x.toFixed(0)}, ${info.y.toFixed(0)}).
31912
32074
  Dispatched: ${info.fired.join(", ")}. Matches: ${info.count}.
@@ -31957,7 +32119,7 @@ async function gameWait(client, reloads, debug, options) {
31957
32119
  const baseline = sinceReloads ?? reloads.count;
31958
32120
  while (reloads.count <= baseline) {
31959
32121
  if (Date.now() >= deadline) {
31960
- return timedOut(import_common_tags4.oneLine`
32122
+ return timedOut(import_common_tags5.oneLine`
31961
32123
  Timed out after ${budget}ms waiting for a view reload
31962
32124
  (reload count still ${reloads.count}, baseline ${baseline}).
31963
32125
  `);
@@ -31969,7 +32131,7 @@ async function gameWait(client, reloads, debug, options) {
31969
32131
  let quietSince = Date.now();
31970
32132
  while (Date.now() - quietSince < quiescentMs) {
31971
32133
  if (Date.now() >= deadline) {
31972
- return timedOut(import_common_tags4.oneLine`
32134
+ return timedOut(import_common_tags5.oneLine`
31973
32135
  Timed out after ${budget}ms: reload observed, but context swaps kept arriving within
31974
32136
  the ${quiescentMs}ms quiescence window.
31975
32137
  `);
@@ -31993,6 +32155,13 @@ async function gameWait(client, reloads, debug, options) {
31993
32155
  });
31994
32156
  if (res.exceptionDetails) {
31995
32157
  lastError = formatException(res.exceptionDetails);
32158
+ if (selector && isInvalidSelectorError(lastError)) {
32159
+ const preamble = import_common_tags5.oneLine`
32160
+ game_wait stopped after ${Date.now() - start}ms without waiting out the ${budget}ms
32161
+ budget:
32162
+ `;
32163
+ return errorText(`${preamble} ${diagnoseSelectorError(lastError, selector)}`);
32164
+ }
31996
32165
  } else if (res.result.value) {
31997
32166
  const what = selector ? `selector ${JSON.stringify(selector)}` : "predicate";
31998
32167
  const reloadNote = reload ? ` Reload count: ${reloads.count}.` : "";
@@ -32002,7 +32171,8 @@ async function gameWait(client, reloads, debug, options) {
32002
32171
  }
32003
32172
  if (Date.now() >= deadline) {
32004
32173
  const what = selector ? `selector ${JSON.stringify(selector)}` : "predicate";
32005
- const errorNote = lastError ? ` Last predicate error: ${lastError}` : "";
32174
+ const lastNote = lastError && diagnoseSelectorError(lastError);
32175
+ const errorNote = lastNote ? ` Last predicate error: ${lastNote}` : "";
32006
32176
  return timedOut(`Timed out after ${budget}ms waiting for ${what}.${errorNote}`);
32007
32177
  }
32008
32178
  await sleep2(POLL_INTERVAL_MS2);
@@ -32016,16 +32186,16 @@ async function gameFill(client, selector, value, index = 0) {
32016
32186
  returnByValue: true
32017
32187
  });
32018
32188
  if (res.exceptionDetails) {
32019
- return errorText(`Fill failed: ${formatException(res.exceptionDetails)}`);
32189
+ return errorText(`Fill failed: ${explainException(res.exceptionDetails, selector)}`);
32020
32190
  }
32021
32191
  const info = res.result.value;
32022
32192
  if (!info?.found) {
32023
- return errorText(import_common_tags4.oneLine`
32193
+ return errorText(import_common_tags5.oneLine`
32024
32194
  No element matched ${JSON.stringify(selector)} for game_fill at index ${index}
32025
32195
  (matches found: ${info?.count ?? 0}).
32026
32196
  `);
32027
32197
  }
32028
- return text(import_common_tags4.oneLine`
32198
+ return text(import_common_tags5.oneLine`
32029
32199
  Filled ${JSON.stringify(selector)} [index ${index}] (${info.mode}).
32030
32200
  Value is now ${JSON.stringify(info.value)}. Matches: ${info.count}.
32031
32201
  `);
@@ -32040,16 +32210,16 @@ async function gameType(client, selector, textToType, index = 0) {
32040
32210
  returnByValue: true
32041
32211
  });
32042
32212
  if (res.exceptionDetails) {
32043
- return errorText(`Type failed: ${formatException(res.exceptionDetails)}`);
32213
+ return errorText(`Type failed: ${explainException(res.exceptionDetails, selector)}`);
32044
32214
  }
32045
32215
  const info = res.result.value;
32046
32216
  if (!info?.found) {
32047
- return errorText(import_common_tags4.oneLine`
32217
+ return errorText(import_common_tags5.oneLine`
32048
32218
  No element matched ${JSON.stringify(selector)} for game_type at index ${index}
32049
32219
  (matches found: ${info?.count ?? 0}).
32050
32220
  `);
32051
32221
  }
32052
- return text(import_common_tags4.oneLine`
32222
+ return text(import_common_tags5.oneLine`
32053
32223
  Typed ${info.typed} char(s) into ${JSON.stringify(selector)} [index ${index}].
32054
32224
  Value is now ${JSON.stringify(info.value)}. Matches: ${info.count}.
32055
32225
  `);
@@ -32064,16 +32234,16 @@ async function gameHover(client, selector, index = 0) {
32064
32234
  returnByValue: true
32065
32235
  });
32066
32236
  if (res.exceptionDetails) {
32067
- return errorText(`Hover failed: ${formatException(res.exceptionDetails)}`);
32237
+ return errorText(`Hover failed: ${explainException(res.exceptionDetails, selector)}`);
32068
32238
  }
32069
32239
  const info = res.result.value;
32070
32240
  if (!info?.found) {
32071
- return errorText(import_common_tags4.oneLine`
32241
+ return errorText(import_common_tags5.oneLine`
32072
32242
  No element matched ${JSON.stringify(selector)} for game_hover at index ${index}
32073
32243
  (matches found: ${info?.count ?? 0}).
32074
32244
  `);
32075
32245
  }
32076
- return text(import_common_tags4.oneLine`
32246
+ return text(import_common_tags5.oneLine`
32077
32247
  Hovered ${JSON.stringify(selector)} [index ${index}] at
32078
32248
  (${info.x.toFixed(0)}, ${info.y.toFixed(0)}).
32079
32249
  Dispatched: ${info.fired.join(", ")}. Matches: ${info.count}.
@@ -32108,21 +32278,21 @@ async function gameKey(client, options) {
32108
32278
  returnByValue: true
32109
32279
  });
32110
32280
  if (res.exceptionDetails) {
32111
- return errorText(`Key press failed: ${formatException(res.exceptionDetails)}`);
32281
+ return errorText(`Key press failed: ${explainException(res.exceptionDetails, selector)}`);
32112
32282
  }
32113
32283
  const info = res.result.value;
32114
32284
  if (!info) {
32115
32285
  return errorText(`game_key returned no result.`);
32116
32286
  }
32117
32287
  if (!info.found) {
32118
- return errorText(import_common_tags4.oneLine`
32288
+ return errorText(import_common_tags5.oneLine`
32119
32289
  No element matched ${JSON.stringify(selector)} for game_key at index ${index}
32120
32290
  (matches found: ${info.count}).
32121
32291
  `);
32122
32292
  }
32123
32293
  const where = info.via == "selector" ? `${JSON.stringify(selector)} [index ${index}] ${info.target}` : info.via == "activeElement" ? `the focused element ${info.target}` : `document`;
32124
32294
  const matchNote = info.matches == null ? "" : ` Matches: ${info.matches}.`;
32125
- return text(import_common_tags4.oneLine`
32295
+ return text(import_common_tags5.oneLine`
32126
32296
  Pressed ${keyLabel(key, { ctrl, shift, alt, meta: meta3 })} ${info.presses}x on ${where}.${matchNote}
32127
32297
  Default prevented: ${info.defaultPrevented ? "yes" : "no"}.
32128
32298
  `);
@@ -32644,7 +32814,7 @@ async function main() {
32644
32814
  const server = new McpServer({ name: "gameface-devtools-mcp", version: VERSION });
32645
32815
  server.registerTool("game_status", {
32646
32816
  title: `Gameface UI status`,
32647
- description: import_common_tags5.oneLine`
32817
+ description: import_common_tags6.oneLine`
32648
32818
  Check whether the Gameface UI debug endpoint is reachable and report the live page target,
32649
32819
  engine info, and view-reload tracking.
32650
32820
  Run this first when other game_* tools fail.
@@ -32653,7 +32823,7 @@ async function main() {
32653
32823
  }, () => gameStatus(client, reloads));
32654
32824
  server.registerTool("game_eval", {
32655
32825
  title: `Evaluate JS in the Gameface UI`,
32656
- description: import_common_tags5.oneLine`
32826
+ description: import_common_tags6.oneLine`
32657
32827
  Evaluate a JavaScript expression in the running Gameface UI (CDP Runtime.evaluate,
32658
32828
  returnByValue) and return the resulting value as JSON.
32659
32829
  `,
@@ -32664,7 +32834,7 @@ async function main() {
32664
32834
  }, ({ expression, awaitPromise }) => gameEval(client, expression, awaitPromise));
32665
32835
  server.registerTool("game_screenshot", {
32666
32836
  title: `Screenshot the Gameface UI`,
32667
- description: import_common_tags5.oneLine`
32837
+ description: import_common_tags6.oneLine`
32668
32838
  Capture a screenshot of the Gameface viewport and return it as an inline image; a selector
32669
32839
  clips the capture to one element.
32670
32840
  Clipping is the only lever on what the image costs you in context: that cost tracks the
@@ -32674,7 +32844,7 @@ async function main() {
32674
32844
  `,
32675
32845
  inputSchema: {
32676
32846
  format: exports_external.enum(["png", "jpeg"]).optional().describe(`Image format (default: png)`),
32677
- quality: exports_external.number().min(1).max(100).optional().describe(import_common_tags5.oneLine`
32847
+ quality: exports_external.number().min(1).max(100).optional().describe(import_common_tags6.oneLine`
32678
32848
  JPEG quality 1-100 (only used when format is jpeg; default 80).
32679
32849
  Trades fidelity for transfer bytes, leaving the image's context cost unchanged.
32680
32850
  `),
@@ -32684,7 +32854,7 @@ async function main() {
32684
32854
  }, ({ format, quality, selector, index }) => gameScreenshot(client, debug, { format, quality, selector, index }));
32685
32855
  server.registerTool("game_wait", {
32686
32856
  title: `Wait for a condition in the Gameface UI`,
32687
- description: import_common_tags5.oneLine`
32857
+ description: import_common_tags6.oneLine`
32688
32858
  Wait until a CSS selector matches (optionally visible), a JS predicate becomes truthy,
32689
32859
  and/or a view reload happens. Provide at least one of reload / selector / predicate
32690
32860
  (selector and predicate are mutually exclusive).
@@ -32695,20 +32865,20 @@ async function main() {
32695
32865
  inputSchema: {
32696
32866
  selector: exports_external.string().optional().describe(`CSS selector to wait for`),
32697
32867
  predicate: exports_external.string().optional().describe(`JS expression evaluated in the page; waits until it is truthy`),
32698
- reload: exports_external.boolean().optional().describe(import_common_tags5.oneLine`
32868
+ reload: exports_external.boolean().optional().describe(import_common_tags6.oneLine`
32699
32869
  Wait for a view reload (context reset) before the selector/predicate phase.
32700
32870
  Without sinceReloads, waits for the next reload after the call starts.
32701
32871
  `),
32702
- sinceReloads: exports_external.number().int().min(0).optional().describe(import_common_tags5.oneLine`
32872
+ sinceReloads: exports_external.number().int().min(0).optional().describe(import_common_tags6.oneLine`
32703
32873
  Baseline reload count (from a prior game_status or game_wait).
32704
32874
  The reload phase is satisfied as soon as the count exceeds it, even if the reload
32705
32875
  already happened; use it to avoid racing a reload you triggered yourself.
32706
32876
  `),
32707
- quiescentMs: exports_external.number().int().min(0).optional().describe(import_common_tags5.oneLine`
32877
+ quiescentMs: exports_external.number().int().min(0).optional().describe(import_common_tags6.oneLine`
32708
32878
  After a reload is observed, hold until no further context swap for this long (default
32709
32879
  1000, 0 disables); absorbs engines that swap the context several times per reload.
32710
32880
  `),
32711
- timeoutMs: exports_external.number().int().min(0).optional().describe(import_common_tags5.oneLine`
32881
+ timeoutMs: exports_external.number().int().min(0).optional().describe(import_common_tags6.oneLine`
32712
32882
  Max time to wait in ms (default 8000, or 30000 when reload is set; capped at 60000)
32713
32883
  `),
32714
32884
  visible: exports_external.boolean().optional().describe(`For selector waits, also require a non-zero bounding box (default false)`)
@@ -32724,7 +32894,7 @@ async function main() {
32724
32894
  }));
32725
32895
  server.registerTool("game_fill", {
32726
32896
  title: `Set an input value in the Gameface UI`,
32727
- description: import_common_tags5.oneLine`
32897
+ description: import_common_tags6.oneLine`
32728
32898
  Set the value of an input, textarea, or contenteditable element and fire input/change so
32729
32899
  the UI framework reacts as if the user edited it.
32730
32900
  Best for setting a field in one shot; use game_type for keystrokes.
@@ -32737,7 +32907,7 @@ async function main() {
32737
32907
  }, ({ selector, value, index }) => gameFill(client, selector, value, index));
32738
32908
  server.registerTool("game_type", {
32739
32909
  title: `Type text into the Gameface UI`,
32740
- description: import_common_tags5.oneLine`
32910
+ description: import_common_tags6.oneLine`
32741
32911
  Type text into an element character by character, firing real KeyboardEvents plus keeping
32742
32912
  the value in sync.
32743
32913
  Use when handlers react to individual keystrokes; otherwise game_fill.
@@ -32750,7 +32920,7 @@ async function main() {
32750
32920
  }, ({ selector, text: text2, index }) => gameType(client, selector, text2, index));
32751
32921
  server.registerTool("game_hover", {
32752
32922
  title: `Hover an element in the Gameface UI`,
32753
- description: import_common_tags5.oneLine`
32923
+ description: import_common_tags6.oneLine`
32754
32924
  Hover an element by dispatching the pointer/mouse over/enter/move sequence in the page, so
32755
32925
  the UI's mouseenter / pointerover JS handlers (tooltips) fire.
32756
32926
  The CSS :hover state is NOT set (only real game-forwarded mouse input sets it); verify a
@@ -32763,7 +32933,7 @@ async function main() {
32763
32933
  }, ({ selector, index }) => gameHover(client, selector, index));
32764
32934
  server.registerTool("game_key", {
32765
32935
  title: `Press a key in the Gameface UI`,
32766
- description: import_common_tags5.oneLine`
32936
+ description: import_common_tags6.oneLine`
32767
32937
  Press a named key by dispatching a real bubbling keydown+keyup (no keypress) in the page
32768
32938
  (KeyboardEvent.key, e.g. Escape, Enter, ArrowDown, a, F5), optionally with
32769
32939
  ctrl/shift/alt/meta and a repeat count.
@@ -32788,7 +32958,7 @@ async function main() {
32788
32958
  }, ({ key, count, ctrl, shift, alt, meta: meta3, selector, index }) => gameKey(client, { key, count, ctrl, shift, alt, meta: meta3, selector, index }));
32789
32959
  server.registerTool("game_console", {
32790
32960
  title: `Read the Gameface UI console`,
32791
- description: import_common_tags5.oneLine`
32961
+ description: import_common_tags6.oneLine`
32792
32962
  Return recent console.* calls, log entries, and uncaught exceptions captured from the
32793
32963
  Gameface UI, each prefixed with local wall-clock time and its object arguments expanded to
32794
32964
  their real values (state {a: 1, b: {c: {…}}, arr: [1, 2, 3]}).
@@ -32800,13 +32970,13 @@ async function main() {
32800
32970
  inputSchema: {
32801
32971
  limit: exports_external.number().int().min(1).max(1000).optional().describe(`Max entries to return (default 50)`),
32802
32972
  level: exports_external.string().optional().describe(`Filter by level, e.g. error / warning / log / info`),
32803
- depth: exports_external.number().int().min(1).max(CAPTURE_DEPTH_CAP).optional().describe(import_common_tags5.oneLine`
32973
+ depth: exports_external.number().int().min(1).max(CAPTURE_DEPTH_CAP).optional().describe(import_common_tags6.oneLine`
32804
32974
  How many levels of an expanded object to render (default
32805
32975
  ${String(DEFAULT_RENDER_DEPTH)}).
32806
32976
  Entries are captured ${String(CAPTURE_DEPTH_CAP)} levels deep, so re-reading the same
32807
32977
  entries deeper works up to that cap.
32808
32978
  `),
32809
- clear: exports_external.boolean().optional().describe(import_common_tags5.oneLine`
32979
+ clear: exports_external.boolean().optional().describe(import_common_tags6.oneLine`
32810
32980
  Empty the buffer once this read has taken its entries (default false).
32811
32981
  It also drops captures still being expanded, so a line logged moments before the call
32812
32982
  is discarded rather than surfacing on the next read.
@@ -32815,7 +32985,7 @@ async function main() {
32815
32985
  }, ({ limit, level, depth, clear }) => gameConsole(client, consoleBuffer, { limit, level, depth, clear }));
32816
32986
  server.registerTool("game_dom", {
32817
32987
  title: `Inspect Gameface UI DOM`,
32818
- description: import_common_tags5.oneLine`
32988
+ description: import_common_tags6.oneLine`
32819
32989
  Return DOM details (tag, id, classes, attributes, bounding rect, outerHTML) for elements
32820
32990
  matching a CSS selector in the live Gameface UI.
32821
32991
  outerHTML dominates the response: to locate elements, get a targetable handle, or read
@@ -32829,7 +32999,7 @@ async function main() {
32829
32999
  }, ({ selector, all, maxHtml }) => gameDom(client, selector, all, maxHtml));
32830
33000
  server.registerTool("game_query", {
32831
33001
  title: `Find elements in the Gameface UI`,
32832
- description: import_common_tags5.oneLine`
33002
+ description: import_common_tags6.oneLine`
32833
33003
  Locate elements in the live Gameface UI by CSS selector, by trimmed textContent
32834
33004
  (equals/contains/regex, case-insensitive by default), or by both; one of the two is
32835
33005
  required, and match/caseSensitive are ignored without text.
@@ -32839,7 +33009,7 @@ async function main() {
32839
33009
  To read an element's markup, use game_dom.
32840
33010
  `,
32841
33011
  inputSchema: {
32842
- text: exports_external.string().optional().describe(import_common_tags5.oneLine`
33012
+ text: exports_external.string().optional().describe(import_common_tags6.oneLine`
32843
33013
  Text to match against each element's trimmed textContent, so pass it trimmed under
32844
33014
  match=equals; omit it to select on the selector alone, or pass '' with match=equals
32845
33015
  and a selector for elements carrying no text.
@@ -32847,11 +33017,11 @@ async function main() {
32847
33017
  match: exports_external.enum(["equals", "contains", "regex"]).optional().describe(`How to match the text: equals / contains / regex (default: contains)`),
32848
33018
  caseSensitive: exports_external.boolean().optional().describe(`Match case-sensitively (default: false)`),
32849
33019
  selector: exports_external.string().optional().describe(`CSS selector to match, alone or scoping the text scan (with text, defaults to *)`),
32850
- deepest: exports_external.boolean().optional().describe(import_common_tags5.oneLine`
33020
+ deepest: exports_external.boolean().optional().describe(import_common_tags6.oneLine`
32851
33021
  Keep only the innermost match, pruning ancestors that also matched (default: true
32852
33022
  when non-empty text drives the match, false otherwise).
32853
33023
  `),
32854
- tag: exports_external.boolean().optional().describe(import_common_tags5.oneLine`
33024
+ tag: exports_external.boolean().optional().describe(import_common_tags6.oneLine`
32855
33025
  Stamp matches with data-gf-tag handles and return them as selectors, clearing any
32856
33026
  prior handles first (default: false).
32857
33027
  `),
@@ -32861,7 +33031,7 @@ async function main() {
32861
33031
  }, ({ text: text2, match, caseSensitive, selector, deepest, tag, attributes, limit }) => gameQuery(client, { text: text2, match, caseSensitive, selector, deepest, tag, attributes, limit }));
32862
33032
  server.registerTool("game_click", {
32863
33033
  title: `Click an element in the Gameface UI`,
32864
- description: import_common_tags5.oneLine`
33034
+ description: import_common_tags6.oneLine`
32865
33035
  Click the element matching a CSS selector by dispatching a real bubbling pointer/mouse/click
32866
33036
  sequence in the page (NOT CDP Input, which Gameface ignores for the UI).
32867
33037
  `,
@@ -32872,7 +33042,7 @@ async function main() {
32872
33042
  }, ({ selector, index }) => gameClick(client, selector, index));
32873
33043
  server.registerTool("game_debug_status", {
32874
33044
  title: `JS debugger status`,
32875
- description: import_common_tags5.oneLine`
33045
+ description: import_common_tags6.oneLine`
32876
33046
  Report debugger state: whether paused (and where), pause-on-exceptions mode, breakpoints,
32877
33047
  and parsed script count.
32878
33048
  Enables the debugger on first use.
@@ -32883,7 +33053,7 @@ async function main() {
32883
33053
  }, ({ setPauseOnExceptions }) => debug.status(setPauseOnExceptions));
32884
33054
  server.registerTool("game_debug_scripts", {
32885
33055
  title: `List parsed UI scripts`,
32886
- description: import_common_tags5.oneLine`
33056
+ description: import_common_tags6.oneLine`
32887
33057
  List JavaScript scripts parsed in the Gameface UI (scriptId + url + line count).
32888
33058
  Only scripts parsed since the debugger attached appear, since Gameface does not replay
32889
33059
  scriptParsed; an empty list is what a late attach looks like, and the result says how to
@@ -32897,7 +33067,7 @@ async function main() {
32897
33067
  }, ({ filter }) => debug.listScripts(filter));
32898
33068
  server.registerTool("game_debug_source", {
32899
33069
  title: `Get UI script source`,
32900
- description: import_common_tags5.oneLine`
33070
+ description: import_common_tags6.oneLine`
32901
33071
  Return the source of a script (by scriptId from game_debug_scripts), with line numbers.
32902
33072
  The window is capped at 400 lines, range or not; the result says which lines it showed.
32903
33073
  It renders whole lines, so a low line count is no promise of a small answer: one minified
@@ -32906,12 +33076,12 @@ async function main() {
32906
33076
  `,
32907
33077
  inputSchema: {
32908
33078
  scriptId: exports_external.string().describe(`Script id from game_debug_scripts`),
32909
- lineStart: exports_external.number().int().min(1).optional().describe(import_common_tags5.oneLine`
33079
+ lineStart: exports_external.number().int().min(1).optional().describe(import_common_tags6.oneLine`
32910
33080
  First line, 1-based and numbered as the whole file or document is, so a script embedded
32911
33081
  in a page starts at its firstLine from game_debug_scripts rather than at 1.
32912
33082
  `),
32913
33083
  lineEnd: exports_external.number().int().min(1).optional().describe(`Last line, numbered as lineStart is`),
32914
- maxChars: exports_external.number().int().min(1).optional().describe(import_common_tags5.oneLine`
33084
+ maxChars: exports_external.number().int().min(1).optional().describe(import_common_tags6.oneLine`
32915
33085
  Characters of source to return before clipping (default
32916
33086
  ${String(DEFAULT_SOURCE_MAX_CHARS)}); the result says when it clipped and at what.
32917
33087
  `)
@@ -32919,7 +33089,7 @@ async function main() {
32919
33089
  }, ({ scriptId, lineStart, lineEnd, maxChars }) => debug.getSource(scriptId, lineStart, lineEnd, maxChars));
32920
33090
  server.registerTool("game_debug_search_source", {
32921
33091
  title: `Search UI script sources`,
32922
- description: import_common_tags5.oneLine`
33092
+ description: import_common_tags6.oneLine`
32923
33093
  Find a literal string across the parsed script sources and return each hit as
32924
33094
  url + line + column (1-based) with a snippet of surrounding source.
32925
33095
  The query is case-sensitive and literal, no regex; read the total/truncated fields for what
@@ -32935,14 +33105,14 @@ async function main() {
32935
33105
  }, ({ query, urlContains }) => debug.searchSource(query, urlContains));
32936
33106
  server.registerTool("game_debug_set_breakpoint", {
32937
33107
  title: `Set a breakpoint`,
32938
- description: import_common_tags5.oneLine`
33108
+ description: import_common_tags6.oneLine`
32939
33109
  Set a breakpoint by url substring + line, and optionally column; both are 1-based.
32940
33110
  A condition limits how often the UI freezes; a line alone can bind somewhere that never
32941
33111
  runs again, so check the resolved location the result reports back.
32942
33112
  Hitting it FREEZES the UI until you resume with game_debug_step.
32943
33113
  `,
32944
33114
  inputSchema: {
32945
- urlContains: exports_external.string().describe(import_common_tags5.oneLine`
33115
+ urlContains: exports_external.string().describe(import_common_tags6.oneLine`
32946
33116
  Substring of the script url to break in, matched case-sensitively; copy it from the url
32947
33117
  game_debug_scripts or game_debug_search_source printed, whose own filters are not.
32948
33118
  `),
@@ -32960,7 +33130,7 @@ async function main() {
32960
33130
  }, ({ breakpoint }) => debug.removeBreakpoint(breakpoint));
32961
33131
  server.registerTool("game_debug_pause_state", {
32962
33132
  title: `Inspect the paused stack`,
32963
- description: import_common_tags5.oneLine`
33133
+ description: import_common_tags6.oneLine`
32964
33134
  When paused, return the call stack (frames with function + location + scope types);
32965
33135
  'not paused' otherwise.
32966
33136
  `,
@@ -32970,7 +33140,7 @@ async function main() {
32970
33140
  }, ({ expandScopes }) => debug.pauseStateReport(expandScopes ?? false));
32971
33141
  server.registerTool("game_debug_evaluate", {
32972
33142
  title: `Evaluate while debugging`,
32973
- description: import_common_tags5.oneLine`
33143
+ description: import_common_tags6.oneLine`
32974
33144
  Evaluate a JS expression.
32975
33145
  When paused, it runs in the selected call frame's scope so you can read locals; otherwise
32976
33146
  it runs globally.
@@ -32983,7 +33153,7 @@ async function main() {
32983
33153
  }, ({ expression, frameIndex }) => debug.evaluate(expression, frameIndex));
32984
33154
  server.registerTool("game_debug_step", {
32985
33155
  title: `Step / resume / pause execution`,
32986
- description: import_common_tags5.oneLine`
33156
+ description: import_common_tags6.oneLine`
32987
33157
  Control paused execution: resume (unfreeze the UI), over/into/out (step), or pause (break at
32988
33158
  the next statement).
32989
33159
  Stepping reports the new location, and resume fails rather than claiming success when the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@csmodding/gameface-devtools-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "MCP server that drives a running Coherent Gameface (Cohtml) game UI over a direct Chrome DevTools Protocol connection: evaluate JS, screenshot, inspect and drive the DOM, capture the console, and set JS breakpoints.",
5
5
  "keywords": [
6
6
  "browser-automation",