@react-grab/cli 0.1.0-beta.2 → 0.1.0-beta.4

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 (3) hide show
  1. package/dist/cli.cjs +276 -84
  2. package/dist/cli.js +276 -84
  3. package/package.json +2 -2
package/dist/cli.cjs CHANGED
@@ -2707,7 +2707,7 @@ var previewPackageJsonAgentRemoval = (projectRoot, agent) => {
2707
2707
  };
2708
2708
 
2709
2709
  // src/commands/add.ts
2710
- var VERSION = "0.1.0-beta.2";
2710
+ var VERSION = "0.1.0-beta.3";
2711
2711
  var add = new commander.Command().name("add").alias("install").description("add an agent integration or MCP server").argument(
2712
2712
  "[agent]",
2713
2713
  `agent to add (${AGENTS.join(", ")}, mcp, skill)`
@@ -3236,6 +3236,15 @@ var add = new commander.Command().name("add").alias("install").description("add
3236
3236
  handleError(error48);
3237
3237
  }
3238
3238
  });
3239
+
3240
+ // src/utils/constants.ts
3241
+ var MAX_SUGGESTIONS_COUNT = 30;
3242
+ var MAX_KEY_HOLD_DURATION_MS = 2e3;
3243
+ var MAX_CONTEXT_LINES = 50;
3244
+ var COMPONENT_STACK_MAX_DEPTH = 10;
3245
+
3246
+ // src/utils/browser-automation.ts
3247
+ var LOAD_STATES = /* @__PURE__ */ new Set(["load", "domcontentloaded", "networkidle"]);
3239
3248
  var ensureHealthyServer = async (options2 = {}) => {
3240
3249
  const cliPath = process.argv[1];
3241
3250
  const serverRunning = await browser$1.isServerRunning();
@@ -3266,16 +3275,65 @@ var getOrCreatePage = async (serverUrl, name) => {
3266
3275
  }
3267
3276
  return response.json();
3268
3277
  };
3278
+ var getReactContextForActiveElement = async (page) => {
3279
+ try {
3280
+ return page.evaluate(async (maxDepth) => {
3281
+ const activeElement = document.activeElement;
3282
+ if (!activeElement || activeElement === document.body) return null;
3283
+ const reactGrab = globalThis.__REACT_GRAB__;
3284
+ if (!reactGrab?.getSource) return null;
3285
+ const source = await reactGrab.getSource(activeElement);
3286
+ if (!source) return null;
3287
+ const componentStack = [];
3288
+ if (source.componentName) {
3289
+ componentStack.push(source.componentName);
3290
+ }
3291
+ const fiberKey = Object.keys(activeElement).find(
3292
+ (key) => key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")
3293
+ );
3294
+ if (fiberKey) {
3295
+ let fiber = activeElement[fiberKey];
3296
+ let depth = 0;
3297
+ while (fiber?.return && depth < maxDepth) {
3298
+ fiber = fiber.return;
3299
+ if (fiber.tag === 0 || fiber.tag === 1 || fiber.tag === 11) {
3300
+ const name = typeof fiber.type === "object" ? fiber.type?.displayName || fiber.type?.name : null;
3301
+ if (name && !name.startsWith("_") && !componentStack.includes(name)) {
3302
+ componentStack.push(name);
3303
+ }
3304
+ }
3305
+ depth++;
3306
+ }
3307
+ }
3308
+ return {
3309
+ element: activeElement.tagName.toLowerCase(),
3310
+ component: source.componentName || void 0,
3311
+ source: source.filePath ? `${source.filePath}${source.lineNumber ? `:${source.lineNumber}` : ""}` : void 0,
3312
+ componentStack: componentStack.length > 0 ? componentStack : void 0
3313
+ };
3314
+ }, COMPONENT_STACK_MAX_DEPTH);
3315
+ } catch {
3316
+ return null;
3317
+ }
3318
+ };
3269
3319
  var createOutputJson = (getPage, pageName) => {
3270
3320
  return async (ok, result, error48) => {
3271
3321
  const page = getPage();
3322
+ let reactContext;
3323
+ if (page && ok) {
3324
+ const context = await getReactContextForActiveElement(page);
3325
+ if (context) {
3326
+ reactContext = context;
3327
+ }
3328
+ }
3272
3329
  return {
3273
3330
  ok,
3274
3331
  url: page?.url() ?? "",
3275
3332
  title: page ? await page.title().catch(() => "") : "",
3276
3333
  page: pageName,
3277
3334
  ...result !== void 0 && { result },
3278
- ...error48 && { error: error48 }
3335
+ ...error48 && { error: error48 },
3336
+ ...reactContext && { reactContext }
3279
3337
  };
3280
3338
  };
3281
3339
  };
@@ -3334,6 +3392,24 @@ var createRefHelper = (getActivePage2) => {
3334
3392
  return g2.__REACT_GRAB__.getSource(el);
3335
3393
  }, element);
3336
3394
  };
3395
+ const getProps = async (refId) => {
3396
+ const element = await getElement(refId);
3397
+ const currentPage2 = getActivePage2();
3398
+ return currentPage2.evaluate((el) => {
3399
+ const g2 = globalThis;
3400
+ if (!g2.__REACT_GRAB_GET_PROPS__) return null;
3401
+ return g2.__REACT_GRAB_GET_PROPS__(el);
3402
+ }, element);
3403
+ };
3404
+ const getState = async (refId) => {
3405
+ const element = await getElement(refId);
3406
+ const currentPage2 = getActivePage2();
3407
+ return currentPage2.evaluate((el) => {
3408
+ const g2 = globalThis;
3409
+ if (!g2.__REACT_GRAB_GET_STATE__) return null;
3410
+ return g2.__REACT_GRAB_GET_STATE__(el);
3411
+ }, element);
3412
+ };
3337
3413
  return (refId) => {
3338
3414
  return new Proxy(
3339
3415
  {},
@@ -3345,6 +3421,12 @@ var createRefHelper = (getActivePage2) => {
3345
3421
  if (prop === "source") {
3346
3422
  return () => getSource(refId);
3347
3423
  }
3424
+ if (prop === "props") {
3425
+ return () => getProps(refId);
3426
+ }
3427
+ if (prop === "state") {
3428
+ return () => getState(refId);
3429
+ }
3348
3430
  if (prop === "screenshot") {
3349
3431
  return (options2) => getElement(refId).then(
3350
3432
  (el) => el.screenshot({ scale: "css", ...options2 })
@@ -3358,6 +3440,50 @@ var createRefHelper = (getActivePage2) => {
3358
3440
  );
3359
3441
  };
3360
3442
  };
3443
+ var createComponentHelper = (getActivePage2) => {
3444
+ return async (componentName, options2) => {
3445
+ const currentPage2 = getActivePage2();
3446
+ const nth = options2?.nth;
3447
+ const elementHandles = await currentPage2.evaluateHandle(
3448
+ async (args) => {
3449
+ const g2 = globalThis;
3450
+ if (!g2.__REACT_GRAB_FIND_BY_COMPONENT__) {
3451
+ throw new Error("React introspection not available. Make sure react-grab is installed.");
3452
+ }
3453
+ const result = await g2.__REACT_GRAB_FIND_BY_COMPONENT__(args.name, args.nth !== void 0 ? { nth: args.nth } : void 0);
3454
+ if (!result) return null;
3455
+ if (args.nth !== void 0) {
3456
+ const single = result;
3457
+ return single?.element || null;
3458
+ }
3459
+ const arr = result;
3460
+ return arr.map((m2) => m2.element);
3461
+ },
3462
+ { name: componentName, nth }
3463
+ );
3464
+ const value = await elementHandles.jsonValue().catch(() => null);
3465
+ if (value === null) {
3466
+ await elementHandles.dispose();
3467
+ return null;
3468
+ }
3469
+ if (nth !== void 0) {
3470
+ const element = elementHandles.asElement();
3471
+ if (!element) {
3472
+ await elementHandles.dispose();
3473
+ return null;
3474
+ }
3475
+ return element;
3476
+ }
3477
+ const jsHandles = await elementHandles.getProperties();
3478
+ const handles = [];
3479
+ for (const [, handle] of jsHandles) {
3480
+ const element = handle.asElement();
3481
+ if (element) handles.push(element);
3482
+ }
3483
+ await elementHandles.dispose();
3484
+ return handles;
3485
+ };
3486
+ };
3361
3487
  var createFillHelper = (ref, getActivePage2) => {
3362
3488
  return async (refId, text) => {
3363
3489
  const element = await ref(refId);
@@ -3459,35 +3585,22 @@ var createDispatchHelper = (getActivePage2) => {
3459
3585
  };
3460
3586
  };
3461
3587
  var createGrabHelper = (ref, getActivePage2) => {
3588
+ const evaluateGrabMethod = async (methodName, defaultValue) => {
3589
+ const currentPage2 = getActivePage2();
3590
+ return currentPage2.evaluate(
3591
+ ({ method, fallback }) => {
3592
+ const grab = globalThis.__REACT_GRAB__;
3593
+ return grab?.[method]?.() ?? fallback;
3594
+ },
3595
+ { method: methodName, fallback: defaultValue }
3596
+ );
3597
+ };
3462
3598
  return {
3463
- activate: async () => {
3464
- const currentPage2 = getActivePage2();
3465
- await currentPage2.evaluate(() => {
3466
- const g2 = globalThis;
3467
- g2.__REACT_GRAB__?.activate();
3468
- });
3469
- },
3470
- deactivate: async () => {
3471
- const currentPage2 = getActivePage2();
3472
- await currentPage2.evaluate(() => {
3473
- const g2 = globalThis;
3474
- g2.__REACT_GRAB__?.deactivate();
3475
- });
3476
- },
3477
- toggle: async () => {
3478
- const currentPage2 = getActivePage2();
3479
- await currentPage2.evaluate(() => {
3480
- const g2 = globalThis;
3481
- g2.__REACT_GRAB__?.toggle();
3482
- });
3483
- },
3484
- isActive: async () => {
3485
- const currentPage2 = getActivePage2();
3486
- return currentPage2.evaluate(() => {
3487
- const g2 = globalThis;
3488
- return g2.__REACT_GRAB__?.isActive() ?? false;
3489
- });
3490
- },
3599
+ activate: () => evaluateGrabMethod("activate", void 0),
3600
+ deactivate: () => evaluateGrabMethod("deactivate", void 0),
3601
+ toggle: () => evaluateGrabMethod("toggle", void 0),
3602
+ isActive: () => evaluateGrabMethod("isActive", false),
3603
+ getState: () => evaluateGrabMethod("getState", null),
3491
3604
  copyElement: async (refId) => {
3492
3605
  const element = await ref(refId);
3493
3606
  if (!element) return false;
@@ -3496,13 +3609,6 @@ var createGrabHelper = (ref, getActivePage2) => {
3496
3609
  const g2 = globalThis;
3497
3610
  return g2.__REACT_GRAB__?.copyElement([el]) ?? false;
3498
3611
  }, element);
3499
- },
3500
- getState: async () => {
3501
- const currentPage2 = getActivePage2();
3502
- return currentPage2.evaluate(() => {
3503
- const g2 = globalThis;
3504
- return g2.__REACT_GRAB__?.getState() ?? null;
3505
- });
3506
3612
  }
3507
3613
  };
3508
3614
  };
@@ -3518,7 +3624,7 @@ var createWaitForHelper = (getActivePage2) => {
3518
3624
  return async (selectorOrState, options2) => {
3519
3625
  const currentPage2 = getActivePage2();
3520
3626
  const timeout = options2?.timeout;
3521
- if (selectorOrState === "load" || selectorOrState === "domcontentloaded" || selectorOrState === "networkidle") {
3627
+ if (LOAD_STATES.has(selectorOrState)) {
3522
3628
  await currentPage2.waitForLoadState(selectorOrState, { timeout });
3523
3629
  return;
3524
3630
  }
@@ -3529,6 +3635,33 @@ var createWaitForHelper = (getActivePage2) => {
3529
3635
  await currentPage2.waitForSelector(selectorOrState, { timeout });
3530
3636
  };
3531
3637
  };
3638
+ var connectToBrowserPage = async (pageName) => {
3639
+ const { chromium: chromium2 } = await import('playwright-core');
3640
+ const { findPageByTargetId: findPageByTargetId2 } = await import('@react-grab/browser');
3641
+ const { serverUrl } = await ensureHealthyServer();
3642
+ const pageInfo = await getOrCreatePage(serverUrl, pageName);
3643
+ const browser2 = await chromium2.connectOverCDP(pageInfo.wsEndpoint);
3644
+ const page = await findPageByTargetId2(browser2, pageInfo.targetId);
3645
+ if (!page) {
3646
+ await browser2.close();
3647
+ throw new Error(`Page "${pageName}" not found`);
3648
+ }
3649
+ return { browser: browser2, page, serverUrl };
3650
+ };
3651
+ var createMcpErrorResponse = (error48) => {
3652
+ return {
3653
+ content: [
3654
+ {
3655
+ type: "text",
3656
+ text: JSON.stringify({
3657
+ ok: false,
3658
+ error: error48 instanceof Error ? error48.message : "Failed"
3659
+ })
3660
+ }
3661
+ ],
3662
+ isError: true
3663
+ };
3664
+ };
3532
3665
 
3533
3666
  // ../../node_modules/.pnpm/zod@4.3.5/node_modules/zod/v4/classic/external.js
3534
3667
  var external_exports = {};
@@ -17298,7 +17431,13 @@ var startMcpServer = async () => {
17298
17431
  server.registerTool(
17299
17432
  "browser_snapshot",
17300
17433
  {
17301
- description: `Get ARIA accessibility tree with element refs (e1, e2...).
17434
+ description: `Get ARIA accessibility tree with element refs (e1, e2...) and React component info.
17435
+
17436
+ OUTPUT INCLUDES:
17437
+ - ARIA roles and accessible names
17438
+ - Element refs (e1, e2...) for interaction
17439
+ - [component=ComponentName] for React components
17440
+ - [source=file.tsx:line] for source location
17302
17441
 
17303
17442
  SCREENSHOT STRATEGY - ALWAYS prefer element screenshots over full page:
17304
17443
  1. First: Get refs with snapshot (this tool)
@@ -17316,7 +17455,7 @@ USE VIEWPORT screenshot=true ONLY FOR:
17316
17455
 
17317
17456
  PERFORMANCE:
17318
17457
  - interactableOnly:true = much smaller output (recommended)
17319
- - format:'compact' = minimal ref:role:name output
17458
+ - format:'compact' = minimal ref:role:name@Component output
17320
17459
  - maxDepth = limit tree depth
17321
17460
 
17322
17461
  After getting refs, use browser_execute with: ref('e1').click()`,
@@ -17337,16 +17476,11 @@ After getting refs, use browser_execute with: ref('e1').click()`,
17337
17476
  format,
17338
17477
  screenshot
17339
17478
  }) => {
17340
- let activePage = null;
17341
17479
  let browser2 = null;
17342
17480
  try {
17343
- const { serverUrl } = await ensureHealthyServer();
17344
- const pageInfo = await getOrCreatePage(serverUrl, pageName);
17345
- browser2 = await playwrightCore.chromium.connectOverCDP(pageInfo.wsEndpoint);
17346
- activePage = await browser$1.findPageByTargetId(browser2, pageInfo.targetId);
17347
- if (!activePage) {
17348
- throw new Error(`Page "${pageName}" not found`);
17349
- }
17481
+ const connection = await connectToBrowserPage(pageName);
17482
+ browser2 = connection.browser;
17483
+ const activePage = connection.page;
17350
17484
  const getActivePage2 = () => activePage;
17351
17485
  const snapshot = createSnapshotHelper(getActivePage2);
17352
17486
  const snapshotResult = await snapshot({ maxDepth, interactableOnly, format });
@@ -17370,18 +17504,7 @@ After getting refs, use browser_execute with: ref('e1').click()`,
17370
17504
  content: [{ type: "text", text: snapshotResult }]
17371
17505
  };
17372
17506
  } catch (error48) {
17373
- return {
17374
- content: [
17375
- {
17376
- type: "text",
17377
- text: JSON.stringify({
17378
- ok: false,
17379
- error: error48 instanceof Error ? error48.message : "Failed"
17380
- })
17381
- }
17382
- ],
17383
- isError: true
17384
- };
17507
+ return createMcpErrorResponse(error48);
17385
17508
  } finally {
17386
17509
  await browser2?.close();
17387
17510
  }
@@ -17396,25 +17519,32 @@ IMPORTANT: Always call snapshot() first to get element refs from the a11y tree (
17396
17519
 
17397
17520
  AVAILABLE HELPERS:
17398
17521
  - page: Playwright Page object (https://playwright.dev/docs/api/class-page)
17399
- - snapshot(opts?): Get ARIA tree. opts: {maxDepth, interactableOnly, format}
17522
+ - snapshot(opts?): Get ARIA tree with React component info. opts: {maxDepth, interactableOnly, format}
17400
17523
  - ref(id): Get element by ref ID, chainable with all ElementHandle methods
17524
+ - ref(id).source(): Get React component source {filePath, lineNumber, componentName}
17525
+ - ref(id).props(): Get React component props (serialized)
17526
+ - ref(id).state(): Get React component state/hooks (serialized)
17527
+ - component(name, opts?): Find elements by React component name. opts: {nth: number}
17401
17528
  - fill(id, text): Clear and fill input (works with rich text editors)
17402
17529
  - drag({from, to, dataTransfer?}): Drag with custom MIME types
17403
17530
  - dispatch({target, event, dataTransfer?, detail?}): Dispatch custom events
17404
17531
  - waitFor(target): Wait for selector/ref/state. e.g. waitFor('e1'), waitFor('networkidle')
17405
17532
  - grab: React Grab client API (activate, deactivate, toggle, isActive, copyElement, getState)
17406
17533
 
17534
+ REACT-SPECIFIC PATTERNS:
17535
+ - Get React source: return await ref('e1').source()
17536
+ - Get component props: return await ref('e1').props()
17537
+ - Get component state: return await ref('e1').state()
17538
+ - Find by component: const btn = await component('Button', {nth: 0})
17539
+
17407
17540
  ELEMENT SCREENSHOTS (PREFERRED for visual issues):
17408
17541
  - return await ref('e1').screenshot()
17409
- - return await ref('e2').screenshot()
17410
17542
  Use for: wrong color, broken styling, visual bugs, "how does X look", UI verification
17411
- Returns image directly - no file path needed.
17412
17543
 
17413
17544
  COMMON PATTERNS:
17414
17545
  - Click: await ref('e1').click()
17415
17546
  - Fill input: await fill('e1', 'hello')
17416
17547
  - Get attribute: return await ref('e1').getAttribute('href')
17417
- - Get React source: return await ref('e1').source()
17418
17548
  - Navigate: await page.goto('https://example.com')
17419
17549
  - Full page screenshot (rare): return await page.screenshot()
17420
17550
 
@@ -17434,13 +17564,9 @@ PERFORMANCE: Batch multiple actions in one execute call to minimize round-trips.
17434
17564
  let pageOpenHandler = null;
17435
17565
  const outputJson = createOutputJson(() => activePage, pageName);
17436
17566
  try {
17437
- const { serverUrl } = await ensureHealthyServer();
17438
- const pageInfo = await getOrCreatePage(serverUrl, pageName);
17439
- browser2 = await playwrightCore.chromium.connectOverCDP(pageInfo.wsEndpoint);
17440
- activePage = await browser$1.findPageByTargetId(browser2, pageInfo.targetId);
17441
- if (!activePage) {
17442
- throw new Error(`Page "${pageName}" not found`);
17443
- }
17567
+ const connection = await connectToBrowserPage(pageName);
17568
+ browser2 = connection.browser;
17569
+ activePage = connection.page;
17444
17570
  if (url2) {
17445
17571
  await activePage.goto(url2, {
17446
17572
  waitUntil: "domcontentloaded",
@@ -17460,6 +17586,7 @@ PERFORMANCE: Batch multiple actions in one execute call to minimize round-trips.
17460
17586
  const dispatch = createDispatchHelper(getActivePage2);
17461
17587
  const grab = createGrabHelper(ref, getActivePage2);
17462
17588
  const waitFor = createWaitForHelper(getActivePage2);
17589
+ const component = createComponentHelper(getActivePage2);
17463
17590
  const executeFunction = new Function(
17464
17591
  "page",
17465
17592
  "getActivePage",
@@ -17470,6 +17597,7 @@ PERFORMANCE: Batch multiple actions in one execute call to minimize round-trips.
17470
17597
  "dispatch",
17471
17598
  "grab",
17472
17599
  "waitFor",
17600
+ "component",
17473
17601
  `return (async () => { ${code} })();`
17474
17602
  );
17475
17603
  const result = await executeFunction(
@@ -17481,7 +17609,8 @@ PERFORMANCE: Batch multiple actions in one execute call to minimize round-trips.
17481
17609
  drag,
17482
17610
  dispatch,
17483
17611
  grab,
17484
- waitFor
17612
+ waitFor,
17613
+ component
17485
17614
  );
17486
17615
  if (Buffer.isBuffer(result)) {
17487
17616
  const output2 = await outputJson(true, void 0);
@@ -17518,12 +17647,82 @@ PERFORMANCE: Batch multiple actions in one execute call to minimize round-trips.
17518
17647
  }
17519
17648
  }
17520
17649
  );
17650
+ server.registerTool(
17651
+ "browser_react_tree",
17652
+ {
17653
+ description: `Get React component tree hierarchy (separate from ARIA tree).
17654
+
17655
+ Shows the React component structure with:
17656
+ - Component names and nesting
17657
+ - Source file locations
17658
+ - Element refs where available
17659
+ - Optional props (serialized)
17660
+
17661
+ Use this when you need to understand React component architecture rather than accessibility tree.
17662
+ For interacting with elements, use browser_snapshot to get refs first.`,
17663
+ inputSchema: {
17664
+ page: external_exports.string().optional().default("default").describe("Named page context"),
17665
+ maxDepth: external_exports.number().optional().default(50).describe("Maximum tree depth"),
17666
+ includeProps: external_exports.boolean().optional().default(false).describe("Include component props (increases output size)")
17667
+ }
17668
+ },
17669
+ async ({ page: pageName, maxDepth, includeProps }) => {
17670
+ let browser2 = null;
17671
+ try {
17672
+ const connection = await connectToBrowserPage(pageName);
17673
+ browser2 = connection.browser;
17674
+ const activePage = connection.page;
17675
+ const componentTree = await activePage.evaluate(
17676
+ async (opts2) => {
17677
+ const g2 = globalThis;
17678
+ if (!g2.__REACT_GRAB_GET_COMPONENT_TREE__) {
17679
+ return [];
17680
+ }
17681
+ return g2.__REACT_GRAB_GET_COMPONENT_TREE__(opts2);
17682
+ },
17683
+ { maxDepth: maxDepth ?? 50, includeProps: includeProps ?? false }
17684
+ );
17685
+ const renderTree = (nodes) => {
17686
+ const lines = [];
17687
+ for (const node of nodes) {
17688
+ const indent = " ".repeat(node.depth);
17689
+ let line = `${indent}- ${node.name}`;
17690
+ if (node.ref) line += ` [ref=${node.ref}]`;
17691
+ if (node.source) line += ` [source=${node.source}]`;
17692
+ if (node.props && Object.keys(node.props).length > 0) {
17693
+ const propsStr = JSON.stringify(node.props);
17694
+ if (propsStr.length < 100) {
17695
+ line += ` [props=${propsStr}]`;
17696
+ } else {
17697
+ line += ` [props=...]`;
17698
+ }
17699
+ }
17700
+ lines.push(line);
17701
+ }
17702
+ return lines.join("\n");
17703
+ };
17704
+ const treeOutput = renderTree(componentTree);
17705
+ return {
17706
+ content: [
17707
+ {
17708
+ type: "text",
17709
+ text: treeOutput || "No React components found. Make sure react-grab is installed and the page uses React."
17710
+ }
17711
+ ]
17712
+ };
17713
+ } catch (error48) {
17714
+ return createMcpErrorResponse(error48);
17715
+ } finally {
17716
+ await browser2?.close();
17717
+ }
17718
+ }
17719
+ );
17521
17720
  const transport = new stdio_js.StdioServerTransport();
17522
17721
  await server.connect(transport);
17523
17722
  };
17524
17723
 
17525
17724
  // src/commands/browser.ts
17526
- var VERSION2 = "0.1.0-beta.2";
17725
+ var VERSION2 = "0.1.0-beta.3";
17527
17726
  var printHeader = () => {
17528
17727
  console.log(
17529
17728
  `${pc__default.default.magenta("\u273F")} ${pc__default.default.bold("React Grab")} ${pc__default.default.gray(VERSION2)}`
@@ -18011,14 +18210,7 @@ browser.addCommand(status);
18011
18210
  browser.addCommand(execute);
18012
18211
  browser.addCommand(pages);
18013
18212
  browser.addCommand(mcp);
18014
-
18015
- // src/utils/constants.ts
18016
- var MAX_SUGGESTIONS_COUNT = 30;
18017
- var MAX_KEY_HOLD_DURATION_MS = 2e3;
18018
- var MAX_CONTEXT_LINES = 50;
18019
-
18020
- // src/commands/configure.ts
18021
- var VERSION3 = "0.1.0-beta.2";
18213
+ var VERSION3 = "0.1.0-beta.3";
18022
18214
  var isMac = process.platform === "darwin";
18023
18215
  var META_LABEL = isMac ? "Cmd" : "Win";
18024
18216
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -18505,7 +18697,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
18505
18697
  handleError(error48);
18506
18698
  }
18507
18699
  };
18508
- var VERSION4 = "0.1.0-beta.2";
18700
+ var VERSION4 = "0.1.0-beta.3";
18509
18701
  var REPORT_URL = "https://react-grab.com/api/report-cli";
18510
18702
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
18511
18703
  var promptAgentIntegration = async (cwd, customPkg) => {
@@ -19197,7 +19389,7 @@ var init = new commander.Command().name("init").description("initialize React Gr
19197
19389
  await reportToCli("error", void 0, error48);
19198
19390
  }
19199
19391
  });
19200
- var VERSION5 = "0.1.0-beta.2";
19392
+ var VERSION5 = "0.1.0-beta.3";
19201
19393
  var remove = new commander.Command().name("remove").description("remove an agent integration").argument(
19202
19394
  "[agent]",
19203
19395
  "agent to remove (claude-code, cursor, opencode, codex, gemini, amp, ami, visual-edit)"
@@ -19376,7 +19568,7 @@ var remove = new commander.Command().name("remove").description("remove an agent
19376
19568
  });
19377
19569
 
19378
19570
  // src/cli.ts
19379
- var VERSION6 = "0.1.0-beta.2";
19571
+ var VERSION6 = "0.1.0-beta.3";
19380
19572
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
19381
19573
  process.on("SIGINT", () => process.exit(0));
19382
19574
  process.on("SIGTERM", () => process.exit(0));
package/dist/cli.js CHANGED
@@ -2697,7 +2697,7 @@ var previewPackageJsonAgentRemoval = (projectRoot, agent) => {
2697
2697
  };
2698
2698
 
2699
2699
  // src/commands/add.ts
2700
- var VERSION = "0.1.0-beta.2";
2700
+ var VERSION = "0.1.0-beta.3";
2701
2701
  var add = new Command().name("add").alias("install").description("add an agent integration or MCP server").argument(
2702
2702
  "[agent]",
2703
2703
  `agent to add (${AGENTS.join(", ")}, mcp, skill)`
@@ -3226,6 +3226,15 @@ var add = new Command().name("add").alias("install").description("add an agent i
3226
3226
  handleError(error48);
3227
3227
  }
3228
3228
  });
3229
+
3230
+ // src/utils/constants.ts
3231
+ var MAX_SUGGESTIONS_COUNT = 30;
3232
+ var MAX_KEY_HOLD_DURATION_MS = 2e3;
3233
+ var MAX_CONTEXT_LINES = 50;
3234
+ var COMPONENT_STACK_MAX_DEPTH = 10;
3235
+
3236
+ // src/utils/browser-automation.ts
3237
+ var LOAD_STATES = /* @__PURE__ */ new Set(["load", "domcontentloaded", "networkidle"]);
3229
3238
  var ensureHealthyServer = async (options2 = {}) => {
3230
3239
  const cliPath = process.argv[1];
3231
3240
  const serverRunning = await isServerRunning();
@@ -3256,16 +3265,65 @@ var getOrCreatePage = async (serverUrl, name) => {
3256
3265
  }
3257
3266
  return response.json();
3258
3267
  };
3268
+ var getReactContextForActiveElement = async (page) => {
3269
+ try {
3270
+ return page.evaluate(async (maxDepth) => {
3271
+ const activeElement = document.activeElement;
3272
+ if (!activeElement || activeElement === document.body) return null;
3273
+ const reactGrab = globalThis.__REACT_GRAB__;
3274
+ if (!reactGrab?.getSource) return null;
3275
+ const source = await reactGrab.getSource(activeElement);
3276
+ if (!source) return null;
3277
+ const componentStack = [];
3278
+ if (source.componentName) {
3279
+ componentStack.push(source.componentName);
3280
+ }
3281
+ const fiberKey = Object.keys(activeElement).find(
3282
+ (key) => key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")
3283
+ );
3284
+ if (fiberKey) {
3285
+ let fiber = activeElement[fiberKey];
3286
+ let depth = 0;
3287
+ while (fiber?.return && depth < maxDepth) {
3288
+ fiber = fiber.return;
3289
+ if (fiber.tag === 0 || fiber.tag === 1 || fiber.tag === 11) {
3290
+ const name = typeof fiber.type === "object" ? fiber.type?.displayName || fiber.type?.name : null;
3291
+ if (name && !name.startsWith("_") && !componentStack.includes(name)) {
3292
+ componentStack.push(name);
3293
+ }
3294
+ }
3295
+ depth++;
3296
+ }
3297
+ }
3298
+ return {
3299
+ element: activeElement.tagName.toLowerCase(),
3300
+ component: source.componentName || void 0,
3301
+ source: source.filePath ? `${source.filePath}${source.lineNumber ? `:${source.lineNumber}` : ""}` : void 0,
3302
+ componentStack: componentStack.length > 0 ? componentStack : void 0
3303
+ };
3304
+ }, COMPONENT_STACK_MAX_DEPTH);
3305
+ } catch {
3306
+ return null;
3307
+ }
3308
+ };
3259
3309
  var createOutputJson = (getPage, pageName) => {
3260
3310
  return async (ok, result, error48) => {
3261
3311
  const page = getPage();
3312
+ let reactContext;
3313
+ if (page && ok) {
3314
+ const context = await getReactContextForActiveElement(page);
3315
+ if (context) {
3316
+ reactContext = context;
3317
+ }
3318
+ }
3262
3319
  return {
3263
3320
  ok,
3264
3321
  url: page?.url() ?? "",
3265
3322
  title: page ? await page.title().catch(() => "") : "",
3266
3323
  page: pageName,
3267
3324
  ...result !== void 0 && { result },
3268
- ...error48 && { error: error48 }
3325
+ ...error48 && { error: error48 },
3326
+ ...reactContext && { reactContext }
3269
3327
  };
3270
3328
  };
3271
3329
  };
@@ -3324,6 +3382,24 @@ var createRefHelper = (getActivePage2) => {
3324
3382
  return g2.__REACT_GRAB__.getSource(el);
3325
3383
  }, element);
3326
3384
  };
3385
+ const getProps = async (refId) => {
3386
+ const element = await getElement(refId);
3387
+ const currentPage2 = getActivePage2();
3388
+ return currentPage2.evaluate((el) => {
3389
+ const g2 = globalThis;
3390
+ if (!g2.__REACT_GRAB_GET_PROPS__) return null;
3391
+ return g2.__REACT_GRAB_GET_PROPS__(el);
3392
+ }, element);
3393
+ };
3394
+ const getState = async (refId) => {
3395
+ const element = await getElement(refId);
3396
+ const currentPage2 = getActivePage2();
3397
+ return currentPage2.evaluate((el) => {
3398
+ const g2 = globalThis;
3399
+ if (!g2.__REACT_GRAB_GET_STATE__) return null;
3400
+ return g2.__REACT_GRAB_GET_STATE__(el);
3401
+ }, element);
3402
+ };
3327
3403
  return (refId) => {
3328
3404
  return new Proxy(
3329
3405
  {},
@@ -3335,6 +3411,12 @@ var createRefHelper = (getActivePage2) => {
3335
3411
  if (prop === "source") {
3336
3412
  return () => getSource(refId);
3337
3413
  }
3414
+ if (prop === "props") {
3415
+ return () => getProps(refId);
3416
+ }
3417
+ if (prop === "state") {
3418
+ return () => getState(refId);
3419
+ }
3338
3420
  if (prop === "screenshot") {
3339
3421
  return (options2) => getElement(refId).then(
3340
3422
  (el) => el.screenshot({ scale: "css", ...options2 })
@@ -3348,6 +3430,50 @@ var createRefHelper = (getActivePage2) => {
3348
3430
  );
3349
3431
  };
3350
3432
  };
3433
+ var createComponentHelper = (getActivePage2) => {
3434
+ return async (componentName, options2) => {
3435
+ const currentPage2 = getActivePage2();
3436
+ const nth = options2?.nth;
3437
+ const elementHandles = await currentPage2.evaluateHandle(
3438
+ async (args) => {
3439
+ const g2 = globalThis;
3440
+ if (!g2.__REACT_GRAB_FIND_BY_COMPONENT__) {
3441
+ throw new Error("React introspection not available. Make sure react-grab is installed.");
3442
+ }
3443
+ const result = await g2.__REACT_GRAB_FIND_BY_COMPONENT__(args.name, args.nth !== void 0 ? { nth: args.nth } : void 0);
3444
+ if (!result) return null;
3445
+ if (args.nth !== void 0) {
3446
+ const single = result;
3447
+ return single?.element || null;
3448
+ }
3449
+ const arr = result;
3450
+ return arr.map((m2) => m2.element);
3451
+ },
3452
+ { name: componentName, nth }
3453
+ );
3454
+ const value = await elementHandles.jsonValue().catch(() => null);
3455
+ if (value === null) {
3456
+ await elementHandles.dispose();
3457
+ return null;
3458
+ }
3459
+ if (nth !== void 0) {
3460
+ const element = elementHandles.asElement();
3461
+ if (!element) {
3462
+ await elementHandles.dispose();
3463
+ return null;
3464
+ }
3465
+ return element;
3466
+ }
3467
+ const jsHandles = await elementHandles.getProperties();
3468
+ const handles = [];
3469
+ for (const [, handle] of jsHandles) {
3470
+ const element = handle.asElement();
3471
+ if (element) handles.push(element);
3472
+ }
3473
+ await elementHandles.dispose();
3474
+ return handles;
3475
+ };
3476
+ };
3351
3477
  var createFillHelper = (ref, getActivePage2) => {
3352
3478
  return async (refId, text) => {
3353
3479
  const element = await ref(refId);
@@ -3449,35 +3575,22 @@ var createDispatchHelper = (getActivePage2) => {
3449
3575
  };
3450
3576
  };
3451
3577
  var createGrabHelper = (ref, getActivePage2) => {
3578
+ const evaluateGrabMethod = async (methodName, defaultValue) => {
3579
+ const currentPage2 = getActivePage2();
3580
+ return currentPage2.evaluate(
3581
+ ({ method, fallback }) => {
3582
+ const grab = globalThis.__REACT_GRAB__;
3583
+ return grab?.[method]?.() ?? fallback;
3584
+ },
3585
+ { method: methodName, fallback: defaultValue }
3586
+ );
3587
+ };
3452
3588
  return {
3453
- activate: async () => {
3454
- const currentPage2 = getActivePage2();
3455
- await currentPage2.evaluate(() => {
3456
- const g2 = globalThis;
3457
- g2.__REACT_GRAB__?.activate();
3458
- });
3459
- },
3460
- deactivate: async () => {
3461
- const currentPage2 = getActivePage2();
3462
- await currentPage2.evaluate(() => {
3463
- const g2 = globalThis;
3464
- g2.__REACT_GRAB__?.deactivate();
3465
- });
3466
- },
3467
- toggle: async () => {
3468
- const currentPage2 = getActivePage2();
3469
- await currentPage2.evaluate(() => {
3470
- const g2 = globalThis;
3471
- g2.__REACT_GRAB__?.toggle();
3472
- });
3473
- },
3474
- isActive: async () => {
3475
- const currentPage2 = getActivePage2();
3476
- return currentPage2.evaluate(() => {
3477
- const g2 = globalThis;
3478
- return g2.__REACT_GRAB__?.isActive() ?? false;
3479
- });
3480
- },
3589
+ activate: () => evaluateGrabMethod("activate", void 0),
3590
+ deactivate: () => evaluateGrabMethod("deactivate", void 0),
3591
+ toggle: () => evaluateGrabMethod("toggle", void 0),
3592
+ isActive: () => evaluateGrabMethod("isActive", false),
3593
+ getState: () => evaluateGrabMethod("getState", null),
3481
3594
  copyElement: async (refId) => {
3482
3595
  const element = await ref(refId);
3483
3596
  if (!element) return false;
@@ -3486,13 +3599,6 @@ var createGrabHelper = (ref, getActivePage2) => {
3486
3599
  const g2 = globalThis;
3487
3600
  return g2.__REACT_GRAB__?.copyElement([el]) ?? false;
3488
3601
  }, element);
3489
- },
3490
- getState: async () => {
3491
- const currentPage2 = getActivePage2();
3492
- return currentPage2.evaluate(() => {
3493
- const g2 = globalThis;
3494
- return g2.__REACT_GRAB__?.getState() ?? null;
3495
- });
3496
3602
  }
3497
3603
  };
3498
3604
  };
@@ -3508,7 +3614,7 @@ var createWaitForHelper = (getActivePage2) => {
3508
3614
  return async (selectorOrState, options2) => {
3509
3615
  const currentPage2 = getActivePage2();
3510
3616
  const timeout = options2?.timeout;
3511
- if (selectorOrState === "load" || selectorOrState === "domcontentloaded" || selectorOrState === "networkidle") {
3617
+ if (LOAD_STATES.has(selectorOrState)) {
3512
3618
  await currentPage2.waitForLoadState(selectorOrState, { timeout });
3513
3619
  return;
3514
3620
  }
@@ -3519,6 +3625,33 @@ var createWaitForHelper = (getActivePage2) => {
3519
3625
  await currentPage2.waitForSelector(selectorOrState, { timeout });
3520
3626
  };
3521
3627
  };
3628
+ var connectToBrowserPage = async (pageName) => {
3629
+ const { chromium: chromium2 } = await import('playwright-core');
3630
+ const { findPageByTargetId: findPageByTargetId2 } = await import('@react-grab/browser');
3631
+ const { serverUrl } = await ensureHealthyServer();
3632
+ const pageInfo = await getOrCreatePage(serverUrl, pageName);
3633
+ const browser2 = await chromium2.connectOverCDP(pageInfo.wsEndpoint);
3634
+ const page = await findPageByTargetId2(browser2, pageInfo.targetId);
3635
+ if (!page) {
3636
+ await browser2.close();
3637
+ throw new Error(`Page "${pageName}" not found`);
3638
+ }
3639
+ return { browser: browser2, page, serverUrl };
3640
+ };
3641
+ var createMcpErrorResponse = (error48) => {
3642
+ return {
3643
+ content: [
3644
+ {
3645
+ type: "text",
3646
+ text: JSON.stringify({
3647
+ ok: false,
3648
+ error: error48 instanceof Error ? error48.message : "Failed"
3649
+ })
3650
+ }
3651
+ ],
3652
+ isError: true
3653
+ };
3654
+ };
3522
3655
 
3523
3656
  // ../../node_modules/.pnpm/zod@4.3.5/node_modules/zod/v4/classic/external.js
3524
3657
  var external_exports = {};
@@ -17288,7 +17421,13 @@ var startMcpServer = async () => {
17288
17421
  server.registerTool(
17289
17422
  "browser_snapshot",
17290
17423
  {
17291
- description: `Get ARIA accessibility tree with element refs (e1, e2...).
17424
+ description: `Get ARIA accessibility tree with element refs (e1, e2...) and React component info.
17425
+
17426
+ OUTPUT INCLUDES:
17427
+ - ARIA roles and accessible names
17428
+ - Element refs (e1, e2...) for interaction
17429
+ - [component=ComponentName] for React components
17430
+ - [source=file.tsx:line] for source location
17292
17431
 
17293
17432
  SCREENSHOT STRATEGY - ALWAYS prefer element screenshots over full page:
17294
17433
  1. First: Get refs with snapshot (this tool)
@@ -17306,7 +17445,7 @@ USE VIEWPORT screenshot=true ONLY FOR:
17306
17445
 
17307
17446
  PERFORMANCE:
17308
17447
  - interactableOnly:true = much smaller output (recommended)
17309
- - format:'compact' = minimal ref:role:name output
17448
+ - format:'compact' = minimal ref:role:name@Component output
17310
17449
  - maxDepth = limit tree depth
17311
17450
 
17312
17451
  After getting refs, use browser_execute with: ref('e1').click()`,
@@ -17327,16 +17466,11 @@ After getting refs, use browser_execute with: ref('e1').click()`,
17327
17466
  format,
17328
17467
  screenshot
17329
17468
  }) => {
17330
- let activePage = null;
17331
17469
  let browser2 = null;
17332
17470
  try {
17333
- const { serverUrl } = await ensureHealthyServer();
17334
- const pageInfo = await getOrCreatePage(serverUrl, pageName);
17335
- browser2 = await chromium.connectOverCDP(pageInfo.wsEndpoint);
17336
- activePage = await findPageByTargetId(browser2, pageInfo.targetId);
17337
- if (!activePage) {
17338
- throw new Error(`Page "${pageName}" not found`);
17339
- }
17471
+ const connection = await connectToBrowserPage(pageName);
17472
+ browser2 = connection.browser;
17473
+ const activePage = connection.page;
17340
17474
  const getActivePage2 = () => activePage;
17341
17475
  const snapshot = createSnapshotHelper(getActivePage2);
17342
17476
  const snapshotResult = await snapshot({ maxDepth, interactableOnly, format });
@@ -17360,18 +17494,7 @@ After getting refs, use browser_execute with: ref('e1').click()`,
17360
17494
  content: [{ type: "text", text: snapshotResult }]
17361
17495
  };
17362
17496
  } catch (error48) {
17363
- return {
17364
- content: [
17365
- {
17366
- type: "text",
17367
- text: JSON.stringify({
17368
- ok: false,
17369
- error: error48 instanceof Error ? error48.message : "Failed"
17370
- })
17371
- }
17372
- ],
17373
- isError: true
17374
- };
17497
+ return createMcpErrorResponse(error48);
17375
17498
  } finally {
17376
17499
  await browser2?.close();
17377
17500
  }
@@ -17386,25 +17509,32 @@ IMPORTANT: Always call snapshot() first to get element refs from the a11y tree (
17386
17509
 
17387
17510
  AVAILABLE HELPERS:
17388
17511
  - page: Playwright Page object (https://playwright.dev/docs/api/class-page)
17389
- - snapshot(opts?): Get ARIA tree. opts: {maxDepth, interactableOnly, format}
17512
+ - snapshot(opts?): Get ARIA tree with React component info. opts: {maxDepth, interactableOnly, format}
17390
17513
  - ref(id): Get element by ref ID, chainable with all ElementHandle methods
17514
+ - ref(id).source(): Get React component source {filePath, lineNumber, componentName}
17515
+ - ref(id).props(): Get React component props (serialized)
17516
+ - ref(id).state(): Get React component state/hooks (serialized)
17517
+ - component(name, opts?): Find elements by React component name. opts: {nth: number}
17391
17518
  - fill(id, text): Clear and fill input (works with rich text editors)
17392
17519
  - drag({from, to, dataTransfer?}): Drag with custom MIME types
17393
17520
  - dispatch({target, event, dataTransfer?, detail?}): Dispatch custom events
17394
17521
  - waitFor(target): Wait for selector/ref/state. e.g. waitFor('e1'), waitFor('networkidle')
17395
17522
  - grab: React Grab client API (activate, deactivate, toggle, isActive, copyElement, getState)
17396
17523
 
17524
+ REACT-SPECIFIC PATTERNS:
17525
+ - Get React source: return await ref('e1').source()
17526
+ - Get component props: return await ref('e1').props()
17527
+ - Get component state: return await ref('e1').state()
17528
+ - Find by component: const btn = await component('Button', {nth: 0})
17529
+
17397
17530
  ELEMENT SCREENSHOTS (PREFERRED for visual issues):
17398
17531
  - return await ref('e1').screenshot()
17399
- - return await ref('e2').screenshot()
17400
17532
  Use for: wrong color, broken styling, visual bugs, "how does X look", UI verification
17401
- Returns image directly - no file path needed.
17402
17533
 
17403
17534
  COMMON PATTERNS:
17404
17535
  - Click: await ref('e1').click()
17405
17536
  - Fill input: await fill('e1', 'hello')
17406
17537
  - Get attribute: return await ref('e1').getAttribute('href')
17407
- - Get React source: return await ref('e1').source()
17408
17538
  - Navigate: await page.goto('https://example.com')
17409
17539
  - Full page screenshot (rare): return await page.screenshot()
17410
17540
 
@@ -17424,13 +17554,9 @@ PERFORMANCE: Batch multiple actions in one execute call to minimize round-trips.
17424
17554
  let pageOpenHandler = null;
17425
17555
  const outputJson = createOutputJson(() => activePage, pageName);
17426
17556
  try {
17427
- const { serverUrl } = await ensureHealthyServer();
17428
- const pageInfo = await getOrCreatePage(serverUrl, pageName);
17429
- browser2 = await chromium.connectOverCDP(pageInfo.wsEndpoint);
17430
- activePage = await findPageByTargetId(browser2, pageInfo.targetId);
17431
- if (!activePage) {
17432
- throw new Error(`Page "${pageName}" not found`);
17433
- }
17557
+ const connection = await connectToBrowserPage(pageName);
17558
+ browser2 = connection.browser;
17559
+ activePage = connection.page;
17434
17560
  if (url2) {
17435
17561
  await activePage.goto(url2, {
17436
17562
  waitUntil: "domcontentloaded",
@@ -17450,6 +17576,7 @@ PERFORMANCE: Batch multiple actions in one execute call to minimize round-trips.
17450
17576
  const dispatch = createDispatchHelper(getActivePage2);
17451
17577
  const grab = createGrabHelper(ref, getActivePage2);
17452
17578
  const waitFor = createWaitForHelper(getActivePage2);
17579
+ const component = createComponentHelper(getActivePage2);
17453
17580
  const executeFunction = new Function(
17454
17581
  "page",
17455
17582
  "getActivePage",
@@ -17460,6 +17587,7 @@ PERFORMANCE: Batch multiple actions in one execute call to minimize round-trips.
17460
17587
  "dispatch",
17461
17588
  "grab",
17462
17589
  "waitFor",
17590
+ "component",
17463
17591
  `return (async () => { ${code} })();`
17464
17592
  );
17465
17593
  const result = await executeFunction(
@@ -17471,7 +17599,8 @@ PERFORMANCE: Batch multiple actions in one execute call to minimize round-trips.
17471
17599
  drag,
17472
17600
  dispatch,
17473
17601
  grab,
17474
- waitFor
17602
+ waitFor,
17603
+ component
17475
17604
  );
17476
17605
  if (Buffer.isBuffer(result)) {
17477
17606
  const output2 = await outputJson(true, void 0);
@@ -17508,12 +17637,82 @@ PERFORMANCE: Batch multiple actions in one execute call to minimize round-trips.
17508
17637
  }
17509
17638
  }
17510
17639
  );
17640
+ server.registerTool(
17641
+ "browser_react_tree",
17642
+ {
17643
+ description: `Get React component tree hierarchy (separate from ARIA tree).
17644
+
17645
+ Shows the React component structure with:
17646
+ - Component names and nesting
17647
+ - Source file locations
17648
+ - Element refs where available
17649
+ - Optional props (serialized)
17650
+
17651
+ Use this when you need to understand React component architecture rather than accessibility tree.
17652
+ For interacting with elements, use browser_snapshot to get refs first.`,
17653
+ inputSchema: {
17654
+ page: external_exports.string().optional().default("default").describe("Named page context"),
17655
+ maxDepth: external_exports.number().optional().default(50).describe("Maximum tree depth"),
17656
+ includeProps: external_exports.boolean().optional().default(false).describe("Include component props (increases output size)")
17657
+ }
17658
+ },
17659
+ async ({ page: pageName, maxDepth, includeProps }) => {
17660
+ let browser2 = null;
17661
+ try {
17662
+ const connection = await connectToBrowserPage(pageName);
17663
+ browser2 = connection.browser;
17664
+ const activePage = connection.page;
17665
+ const componentTree = await activePage.evaluate(
17666
+ async (opts2) => {
17667
+ const g2 = globalThis;
17668
+ if (!g2.__REACT_GRAB_GET_COMPONENT_TREE__) {
17669
+ return [];
17670
+ }
17671
+ return g2.__REACT_GRAB_GET_COMPONENT_TREE__(opts2);
17672
+ },
17673
+ { maxDepth: maxDepth ?? 50, includeProps: includeProps ?? false }
17674
+ );
17675
+ const renderTree = (nodes) => {
17676
+ const lines = [];
17677
+ for (const node of nodes) {
17678
+ const indent = " ".repeat(node.depth);
17679
+ let line = `${indent}- ${node.name}`;
17680
+ if (node.ref) line += ` [ref=${node.ref}]`;
17681
+ if (node.source) line += ` [source=${node.source}]`;
17682
+ if (node.props && Object.keys(node.props).length > 0) {
17683
+ const propsStr = JSON.stringify(node.props);
17684
+ if (propsStr.length < 100) {
17685
+ line += ` [props=${propsStr}]`;
17686
+ } else {
17687
+ line += ` [props=...]`;
17688
+ }
17689
+ }
17690
+ lines.push(line);
17691
+ }
17692
+ return lines.join("\n");
17693
+ };
17694
+ const treeOutput = renderTree(componentTree);
17695
+ return {
17696
+ content: [
17697
+ {
17698
+ type: "text",
17699
+ text: treeOutput || "No React components found. Make sure react-grab is installed and the page uses React."
17700
+ }
17701
+ ]
17702
+ };
17703
+ } catch (error48) {
17704
+ return createMcpErrorResponse(error48);
17705
+ } finally {
17706
+ await browser2?.close();
17707
+ }
17708
+ }
17709
+ );
17511
17710
  const transport = new StdioServerTransport();
17512
17711
  await server.connect(transport);
17513
17712
  };
17514
17713
 
17515
17714
  // src/commands/browser.ts
17516
- var VERSION2 = "0.1.0-beta.2";
17715
+ var VERSION2 = "0.1.0-beta.3";
17517
17716
  var printHeader = () => {
17518
17717
  console.log(
17519
17718
  `${pc.magenta("\u273F")} ${pc.bold("React Grab")} ${pc.gray(VERSION2)}`
@@ -18001,14 +18200,7 @@ browser.addCommand(status);
18001
18200
  browser.addCommand(execute);
18002
18201
  browser.addCommand(pages);
18003
18202
  browser.addCommand(mcp);
18004
-
18005
- // src/utils/constants.ts
18006
- var MAX_SUGGESTIONS_COUNT = 30;
18007
- var MAX_KEY_HOLD_DURATION_MS = 2e3;
18008
- var MAX_CONTEXT_LINES = 50;
18009
-
18010
- // src/commands/configure.ts
18011
- var VERSION3 = "0.1.0-beta.2";
18203
+ var VERSION3 = "0.1.0-beta.3";
18012
18204
  var isMac = process.platform === "darwin";
18013
18205
  var META_LABEL = isMac ? "Cmd" : "Win";
18014
18206
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -18495,7 +18687,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
18495
18687
  handleError(error48);
18496
18688
  }
18497
18689
  };
18498
- var VERSION4 = "0.1.0-beta.2";
18690
+ var VERSION4 = "0.1.0-beta.3";
18499
18691
  var REPORT_URL = "https://react-grab.com/api/report-cli";
18500
18692
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
18501
18693
  var promptAgentIntegration = async (cwd, customPkg) => {
@@ -19187,7 +19379,7 @@ var init = new Command().name("init").description("initialize React Grab in your
19187
19379
  await reportToCli("error", void 0, error48);
19188
19380
  }
19189
19381
  });
19190
- var VERSION5 = "0.1.0-beta.2";
19382
+ var VERSION5 = "0.1.0-beta.3";
19191
19383
  var remove = new Command().name("remove").description("remove an agent integration").argument(
19192
19384
  "[agent]",
19193
19385
  "agent to remove (claude-code, cursor, opencode, codex, gemini, amp, ami, visual-edit)"
@@ -19366,7 +19558,7 @@ var remove = new Command().name("remove").description("remove an agent integrati
19366
19558
  });
19367
19559
 
19368
19560
  // src/cli.ts
19369
- var VERSION6 = "0.1.0-beta.2";
19561
+ var VERSION6 = "0.1.0-beta.3";
19370
19562
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
19371
19563
  process.on("SIGINT", () => process.exit(0));
19372
19564
  process.on("SIGTERM", () => process.exit(0));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-grab/cli",
3
- "version": "0.1.0-beta.2",
3
+ "version": "0.1.0-beta.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "react-grab": "./dist/cli.js"
@@ -32,7 +32,7 @@
32
32
  "playwright-core": "^1.50.0",
33
33
  "prompts": "^2.4.2",
34
34
  "zod": "^4.3.5",
35
- "@react-grab/browser": "0.1.0-beta.2"
35
+ "@react-grab/browser": "0.1.0-beta.4"
36
36
  },
37
37
  "scripts": {
38
38
  "dev": "tsup --watch",