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

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 +127 -73
  2. package/dist/cli.js +127 -73
  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.3";
2710
+ var VERSION = "0.1.0-beta.4";
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)`
@@ -3385,30 +3385,42 @@ var createRefHelper = (getActivePage2) => {
3385
3385
  };
3386
3386
  const getSource = async (refId) => {
3387
3387
  const element = await getElement(refId);
3388
- const currentPage2 = getActivePage2();
3389
- return currentPage2.evaluate((el) => {
3390
- const g2 = globalThis;
3391
- if (!g2.__REACT_GRAB__) return null;
3392
- return g2.__REACT_GRAB__.getSource(el);
3393
- }, element);
3388
+ try {
3389
+ const currentPage2 = getActivePage2();
3390
+ return await currentPage2.evaluate((el) => {
3391
+ const g2 = globalThis;
3392
+ if (!g2.__REACT_GRAB__) return null;
3393
+ return g2.__REACT_GRAB__.getSource(el);
3394
+ }, element);
3395
+ } finally {
3396
+ await element.dispose();
3397
+ }
3394
3398
  };
3395
3399
  const getProps = async (refId) => {
3396
3400
  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);
3401
+ try {
3402
+ const currentPage2 = getActivePage2();
3403
+ return await currentPage2.evaluate((el) => {
3404
+ const g2 = globalThis;
3405
+ if (!g2.__REACT_GRAB_GET_PROPS__) return null;
3406
+ return g2.__REACT_GRAB_GET_PROPS__(el);
3407
+ }, element);
3408
+ } finally {
3409
+ await element.dispose();
3410
+ }
3403
3411
  };
3404
3412
  const getState = async (refId) => {
3405
3413
  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);
3414
+ try {
3415
+ const currentPage2 = getActivePage2();
3416
+ return await currentPage2.evaluate((el) => {
3417
+ const g2 = globalThis;
3418
+ if (!g2.__REACT_GRAB_GET_STATE__) return null;
3419
+ return g2.__REACT_GRAB_GET_STATE__(el);
3420
+ }, element);
3421
+ } finally {
3422
+ await element.dispose();
3423
+ }
3412
3424
  };
3413
3425
  return (refId) => {
3414
3426
  return new Proxy(
@@ -3428,13 +3440,23 @@ var createRefHelper = (getActivePage2) => {
3428
3440
  return () => getState(refId);
3429
3441
  }
3430
3442
  if (prop === "screenshot") {
3431
- return (options2) => getElement(refId).then(
3432
- (el) => el.screenshot({ scale: "css", ...options2 })
3433
- );
3443
+ return async (options2) => {
3444
+ const el = await getElement(refId);
3445
+ try {
3446
+ return await el.screenshot({ scale: "css", ...options2 });
3447
+ } finally {
3448
+ await el.dispose();
3449
+ }
3450
+ };
3434
3451
  }
3435
- return (...args) => getElement(refId).then(
3436
- (el) => el[prop](...args)
3437
- );
3452
+ return async (...args) => {
3453
+ const el = await getElement(refId);
3454
+ try {
3455
+ return await el[prop](...args);
3456
+ } finally {
3457
+ await el.dispose();
3458
+ }
3459
+ };
3438
3460
  }
3439
3461
  }
3440
3462
  );
@@ -3461,8 +3483,8 @@ var createComponentHelper = (getActivePage2) => {
3461
3483
  },
3462
3484
  { name: componentName, nth }
3463
3485
  );
3464
- const value = await elementHandles.jsonValue().catch(() => null);
3465
- if (value === null) {
3486
+ const isNull = await currentPage2.evaluate((value) => value === null, elementHandles);
3487
+ if (isNull) {
3466
3488
  await elementHandles.dispose();
3467
3489
  return null;
3468
3490
  }
@@ -3604,11 +3626,15 @@ var createGrabHelper = (ref, getActivePage2) => {
3604
3626
  copyElement: async (refId) => {
3605
3627
  const element = await ref(refId);
3606
3628
  if (!element) return false;
3607
- const currentPage2 = getActivePage2();
3608
- return currentPage2.evaluate((el) => {
3609
- const g2 = globalThis;
3610
- return g2.__REACT_GRAB__?.copyElement([el]) ?? false;
3611
- }, element);
3629
+ try {
3630
+ const currentPage2 = getActivePage2();
3631
+ return await currentPage2.evaluate((el) => {
3632
+ const g2 = globalThis;
3633
+ return g2.__REACT_GRAB__?.copyElement([el]) ?? false;
3634
+ }, element);
3635
+ } finally {
3636
+ await element.dispose();
3637
+ }
3612
3638
  }
3613
3639
  };
3614
3640
  };
@@ -17455,7 +17481,6 @@ USE VIEWPORT screenshot=true ONLY FOR:
17455
17481
 
17456
17482
  PERFORMANCE:
17457
17483
  - interactableOnly:true = much smaller output (recommended)
17458
- - format:'compact' = minimal ref:role:name@Component output
17459
17484
  - maxDepth = limit tree depth
17460
17485
 
17461
17486
  After getting refs, use browser_execute with: ref('e1').click()`,
@@ -17463,7 +17488,6 @@ After getting refs, use browser_execute with: ref('e1').click()`,
17463
17488
  page: external_exports.string().optional().default("default").describe("Named page context"),
17464
17489
  maxDepth: external_exports.number().optional().describe("Limit tree depth"),
17465
17490
  interactableOnly: external_exports.boolean().optional().describe("Only clickable/input elements (recommended)"),
17466
- format: external_exports.enum(["yaml", "compact"]).optional().default("yaml").describe("'yaml' or 'compact'"),
17467
17491
  screenshot: external_exports.boolean().optional().default(false).describe(
17468
17492
  "Viewport screenshot. For element screenshots (PREFERRED), use browser_execute: ref('eX').screenshot()"
17469
17493
  )
@@ -17473,7 +17497,6 @@ After getting refs, use browser_execute with: ref('e1').click()`,
17473
17497
  page: pageName,
17474
17498
  maxDepth,
17475
17499
  interactableOnly,
17476
- format,
17477
17500
  screenshot
17478
17501
  }) => {
17479
17502
  let browser2 = null;
@@ -17483,7 +17506,7 @@ After getting refs, use browser_execute with: ref('e1').click()`,
17483
17506
  const activePage = connection.page;
17484
17507
  const getActivePage2 = () => activePage;
17485
17508
  const snapshot = createSnapshotHelper(getActivePage2);
17486
- const snapshotResult = await snapshot({ maxDepth, interactableOnly, format });
17509
+ const snapshotResult = await snapshot({ maxDepth, interactableOnly });
17487
17510
  if (screenshot) {
17488
17511
  const screenshotBuffer = await activePage.screenshot({
17489
17512
  fullPage: false,
@@ -17519,7 +17542,7 @@ IMPORTANT: Always call snapshot() first to get element refs from the a11y tree (
17519
17542
 
17520
17543
  AVAILABLE HELPERS:
17521
17544
  - page: Playwright Page object (https://playwright.dev/docs/api/class-page)
17522
- - snapshot(opts?): Get ARIA tree with React component info. opts: {maxDepth, interactableOnly, format}
17545
+ - snapshot(opts?): Get ARIA tree with React component info. opts: {maxDepth, interactableOnly}
17523
17546
  - ref(id): Get element by ref ID, chainable with all ElementHandle methods
17524
17547
  - ref(id).source(): Get React component source {filePath, lineNumber, componentName}
17525
17548
  - ref(id).props(): Get React component props (serialized)
@@ -17722,7 +17745,7 @@ For interacting with elements, use browser_snapshot to get refs first.`,
17722
17745
  };
17723
17746
 
17724
17747
  // src/commands/browser.ts
17725
- var VERSION2 = "0.1.0-beta.3";
17748
+ var VERSION2 = "0.1.0-beta.4";
17726
17749
  var printHeader = () => {
17727
17750
  console.log(
17728
17751
  `${pc__default.default.magenta("\u273F")} ${pc__default.default.bold("React Grab")} ${pc__default.default.gray(VERSION2)}`
@@ -17745,6 +17768,10 @@ var rebuildNativeModuleAndRestart = async (browserPkgDir) => {
17745
17768
  stdio: "inherit",
17746
17769
  detached: false
17747
17770
  });
17771
+ child.on("error", (error48) => {
17772
+ console.error(`Failed to restart: ${error48.message}`);
17773
+ process.exit(1);
17774
+ });
17748
17775
  child.on("exit", (code) => process.exit(code ?? 0));
17749
17776
  };
17750
17777
  var isSupportedBrowser = (value) => {
@@ -17843,8 +17870,10 @@ var start = new commander.Command().name("start").description("start browser ser
17843
17870
  const playwrightCookies = browser$1.toPlaywrightCookies(cookies);
17844
17871
  const browser2 = await playwrightCore.chromium.connectOverCDP(browserServer.wsEndpoint);
17845
17872
  const contexts = browser2.contexts();
17846
- if (contexts.length > 0 && playwrightCookies.length > 0) {
17847
- await contexts[0].addCookies(playwrightCookies);
17873
+ if (contexts.length > 0) {
17874
+ if (playwrightCookies.length > 0) {
17875
+ await contexts[0].addCookies(playwrightCookies);
17876
+ }
17848
17877
  await browser$1.applyStealthScripts(contexts[0]);
17849
17878
  }
17850
17879
  await browser2.close();
@@ -17976,6 +18005,7 @@ var execute = new commander.Command().name("execute").description("run Playwrigh
17976
18005
  const dispatch = createDispatchHelper(getActivePage2);
17977
18006
  const grab = createGrabHelper(ref, getActivePage2);
17978
18007
  const waitFor = createWaitForHelper(getActivePage2);
18008
+ const component = createComponentHelper(getActivePage2);
17979
18009
  const executeFunction = new Function(
17980
18010
  "page",
17981
18011
  "getActivePage",
@@ -17986,9 +18016,10 @@ var execute = new commander.Command().name("execute").description("run Playwrigh
17986
18016
  "dispatch",
17987
18017
  "grab",
17988
18018
  "waitFor",
18019
+ "component",
17989
18020
  `return (async () => { ${code} })();`
17990
18021
  );
17991
- const result = await executeFunction(getActivePage2(), getActivePage2, snapshot, ref, fill, drag, dispatch, grab, waitFor);
18022
+ const result = await executeFunction(getActivePage2(), getActivePage2, snapshot, ref, fill, drag, dispatch, grab, waitFor, component);
17992
18023
  console.log(JSON.stringify(await buildOutput(true, result)));
17993
18024
  } catch (error48) {
17994
18025
  console.log(JSON.stringify(await buildOutput(false, void 0, error48 instanceof Error ? error48.message : "Failed")));
@@ -18046,8 +18077,7 @@ PERFORMANCE TIPS
18046
18077
  1. Batch multiple actions in a single execute call to minimize round-trips.
18047
18078
  Each execute spawns a new connection, so combining actions is 3-5x faster.
18048
18079
 
18049
- 2. Use compact format or limit depth for smaller snapshots (faster, fewer tokens).
18050
- - snapshot({format: 'compact'}) -> minimal ref:role:name output
18080
+ 2. Use interactableOnly or limit depth for smaller snapshots (faster, fewer tokens).
18051
18081
  - snapshot({interactableOnly: true}) -> only clickable/input elements
18052
18082
  - snapshot({maxDepth: 5}) -> limit tree depth
18053
18083
 
@@ -18056,22 +18086,28 @@ PERFORMANCE TIPS
18056
18086
  execute "await ref('e1').click()"
18057
18087
  execute "return await snapshot()"
18058
18088
 
18059
- # FAST: 1 round-trip, compact output
18089
+ # FAST: 1 round-trip, interactable only
18060
18090
  execute "
18061
18091
  await page.goto('https://example.com');
18062
18092
  await ref('e1').click();
18063
- return await snapshot({format: 'compact'});
18093
+ return await snapshot({interactableOnly: true});
18064
18094
  "
18065
18095
 
18066
18096
  HELPERS
18067
18097
  page - Playwright Page object
18068
18098
  snapshot(opts?) - Get ARIA accessibility tree with refs
18069
18099
  opts.maxDepth: limit tree depth (e.g., 5)
18070
- opts.interactableOnly: only show elements with refs
18071
- opts.format: "yaml" (default) or "compact"
18100
+ opts.interactableOnly: only clickable/input elements
18072
18101
  ref(id) - Get element by ref ID (chainable - supports all ElementHandle methods)
18073
18102
  Example: await ref('e1').click()
18074
18103
  Example: await ref('e1').getAttribute('data-foo')
18104
+ ref(id).source() - Get React component source file info for element
18105
+ Returns { filePath, lineNumber, componentName } or null
18106
+ ref(id).props() - Get React component props (serialized)
18107
+ ref(id).state() - Get React component state/hooks (serialized)
18108
+ component(name, opts?) - Find elements by React component name
18109
+ opts.nth: get the nth matching element (0-indexed)
18110
+ Example: await component('Button', {nth: 0})
18075
18111
  fill(id, text) - Clear and fill input (works with rich text editors)
18076
18112
  drag(opts) - Drag with custom MIME types
18077
18113
  opts.from: source selector or ref ID (e.g., "e1" or "text=src")
@@ -18087,21 +18123,16 @@ HELPERS
18087
18123
  await waitFor('.btn') - wait for selector
18088
18124
  await waitFor('networkidle') - wait for network idle
18089
18125
  await waitFor('load') - wait for page load
18090
- ref(id).source() - Get React component source file info for element
18091
- Returns { filePath, lineNumber, componentName } or null
18092
18126
  grab - React Grab client API (activate, copyElement, etc)
18093
18127
 
18094
- SNAPSHOT FORMATS
18128
+ SNAPSHOT OPTIONS
18095
18129
  # Full YAML tree (default, can be large)
18096
18130
  execute "return await snapshot()"
18097
18131
 
18098
18132
  # Interactable only (recommended - much smaller!)
18099
18133
  execute "return await snapshot({interactableOnly: true})"
18100
18134
 
18101
- # Compact format (minimal output: ref:role:name|ref:role:name)
18102
- execute "return await snapshot({format: 'compact'})"
18103
-
18104
- # Combined options
18135
+ # With depth limit
18105
18136
  execute "return await snapshot({interactableOnly: true, maxDepth: 6})"
18106
18137
 
18107
18138
  SCREENSHOTS - PREFER ELEMENT OVER FULL PAGE
@@ -18145,15 +18176,28 @@ COMMON PATTERNS
18145
18176
  dataTransfer: { 'application/x-custom': 'data' }
18146
18177
  })"
18147
18178
 
18148
- # Get React component source file
18149
- execute "return await ref('e1').source()"
18150
-
18151
18179
  # Get page info
18152
18180
  execute "return {url: page.url(), title: await page.title()}"
18153
18181
 
18154
18182
  # CSS selector fallback (refs are now in DOM as aria-ref)
18155
18183
  execute "await page.click('[aria-ref="e1"]')"
18156
18184
 
18185
+ REACT-SPECIFIC PATTERNS
18186
+ # Get React component source file
18187
+ execute "return await ref('e1').source()"
18188
+
18189
+ # Get component props
18190
+ execute "return await ref('e1').props()"
18191
+
18192
+ # Get component state
18193
+ execute "return await ref('e1').state()"
18194
+
18195
+ # Find elements by React component name
18196
+ execute "const buttons = await component('Button'); return buttons.length"
18197
+
18198
+ # Get the first Button component and click it
18199
+ execute "const btn = await component('Button', {nth: 0}); await btn.click()"
18200
+
18157
18201
  MULTI-PAGE SESSIONS
18158
18202
  execute "await page.goto('https://github.com')" --page github
18159
18203
  execute "return await snapshot({interactableOnly: true})" --page github
@@ -18210,7 +18254,7 @@ browser.addCommand(status);
18210
18254
  browser.addCommand(execute);
18211
18255
  browser.addCommand(pages);
18212
18256
  browser.addCommand(mcp);
18213
- var VERSION3 = "0.1.0-beta.3";
18257
+ var VERSION3 = "0.1.0-beta.4";
18214
18258
  var isMac = process.platform === "darwin";
18215
18259
  var META_LABEL = isMac ? "Cmd" : "Win";
18216
18260
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -18697,7 +18741,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
18697
18741
  handleError(error48);
18698
18742
  }
18699
18743
  };
18700
- var VERSION4 = "0.1.0-beta.3";
18744
+ var VERSION4 = "0.1.0-beta.4";
18701
18745
  var REPORT_URL = "https://react-grab.com/api/report-cli";
18702
18746
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
18703
18747
  var promptAgentIntegration = async (cwd, customPkg) => {
@@ -18741,19 +18785,29 @@ var promptAgentIntegration = async (cwd, customPkg) => {
18741
18785
  }
18742
18786
  }
18743
18787
  if (integrationType === "skill" || integrationType === "both") {
18744
- logger.break();
18745
- const skillSpinner = spinner("Installing browser automation skill").start();
18746
- try {
18747
- child_process.execSync(`npx -y openskills install aidenybai/react-grab -y`, {
18748
- stdio: "inherit",
18749
- cwd
18750
- });
18751
- logger.break();
18752
- skillSpinner.succeed("Skill installed to .claude/skills/");
18753
- } catch {
18788
+ const { skillTarget } = await prompts3__default.default({
18789
+ type: "select",
18790
+ name: "skillTarget",
18791
+ message: `Which ${highlighter.info("agent")} would you like to install the skill for?`,
18792
+ choices: SUPPORTED_TARGETS.map((target) => ({
18793
+ title: target,
18794
+ value: target
18795
+ }))
18796
+ });
18797
+ if (skillTarget) {
18754
18798
  logger.break();
18755
- skillSpinner.fail("Failed to install skill");
18756
- logger.dim("Try manually: npx -y openskills install aidenybai/react-grab");
18799
+ const skillSpinner = spinner("Installing browser automation skill").start();
18800
+ try {
18801
+ const skill = await fetchSkillFile();
18802
+ const skillDir = path.join(cwd, AGENT_TARGETS[skillTarget]);
18803
+ fs.rmSync(skillDir, { recursive: true, force: true });
18804
+ fs.mkdirSync(skillDir, { recursive: true });
18805
+ fs.writeFileSync(path.join(skillDir, "SKILL.md"), skill);
18806
+ skillSpinner.succeed(`Skill installed to ${AGENT_TARGETS[skillTarget]}/`);
18807
+ } catch {
18808
+ skillSpinner.fail("Failed to install skill");
18809
+ logger.dim("Try manually: npx -y openskills install aidenybai/react-grab");
18810
+ }
18757
18811
  }
18758
18812
  }
18759
18813
  logger.break();
@@ -19389,7 +19443,7 @@ var init = new commander.Command().name("init").description("initialize React Gr
19389
19443
  await reportToCli("error", void 0, error48);
19390
19444
  }
19391
19445
  });
19392
- var VERSION5 = "0.1.0-beta.3";
19446
+ var VERSION5 = "0.1.0-beta.4";
19393
19447
  var remove = new commander.Command().name("remove").description("remove an agent integration").argument(
19394
19448
  "[agent]",
19395
19449
  "agent to remove (claude-code, cursor, opencode, codex, gemini, amp, ami, visual-edit)"
@@ -19568,7 +19622,7 @@ var remove = new commander.Command().name("remove").description("remove an agent
19568
19622
  });
19569
19623
 
19570
19624
  // src/cli.ts
19571
- var VERSION6 = "0.1.0-beta.3";
19625
+ var VERSION6 = "0.1.0-beta.4";
19572
19626
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
19573
19627
  process.on("SIGINT", () => process.exit(0));
19574
19628
  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.3";
2700
+ var VERSION = "0.1.0-beta.4";
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)`
@@ -3375,30 +3375,42 @@ var createRefHelper = (getActivePage2) => {
3375
3375
  };
3376
3376
  const getSource = async (refId) => {
3377
3377
  const element = await getElement(refId);
3378
- const currentPage2 = getActivePage2();
3379
- return currentPage2.evaluate((el) => {
3380
- const g2 = globalThis;
3381
- if (!g2.__REACT_GRAB__) return null;
3382
- return g2.__REACT_GRAB__.getSource(el);
3383
- }, element);
3378
+ try {
3379
+ const currentPage2 = getActivePage2();
3380
+ return await currentPage2.evaluate((el) => {
3381
+ const g2 = globalThis;
3382
+ if (!g2.__REACT_GRAB__) return null;
3383
+ return g2.__REACT_GRAB__.getSource(el);
3384
+ }, element);
3385
+ } finally {
3386
+ await element.dispose();
3387
+ }
3384
3388
  };
3385
3389
  const getProps = async (refId) => {
3386
3390
  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);
3391
+ try {
3392
+ const currentPage2 = getActivePage2();
3393
+ return await currentPage2.evaluate((el) => {
3394
+ const g2 = globalThis;
3395
+ if (!g2.__REACT_GRAB_GET_PROPS__) return null;
3396
+ return g2.__REACT_GRAB_GET_PROPS__(el);
3397
+ }, element);
3398
+ } finally {
3399
+ await element.dispose();
3400
+ }
3393
3401
  };
3394
3402
  const getState = async (refId) => {
3395
3403
  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);
3404
+ try {
3405
+ const currentPage2 = getActivePage2();
3406
+ return await currentPage2.evaluate((el) => {
3407
+ const g2 = globalThis;
3408
+ if (!g2.__REACT_GRAB_GET_STATE__) return null;
3409
+ return g2.__REACT_GRAB_GET_STATE__(el);
3410
+ }, element);
3411
+ } finally {
3412
+ await element.dispose();
3413
+ }
3402
3414
  };
3403
3415
  return (refId) => {
3404
3416
  return new Proxy(
@@ -3418,13 +3430,23 @@ var createRefHelper = (getActivePage2) => {
3418
3430
  return () => getState(refId);
3419
3431
  }
3420
3432
  if (prop === "screenshot") {
3421
- return (options2) => getElement(refId).then(
3422
- (el) => el.screenshot({ scale: "css", ...options2 })
3423
- );
3433
+ return async (options2) => {
3434
+ const el = await getElement(refId);
3435
+ try {
3436
+ return await el.screenshot({ scale: "css", ...options2 });
3437
+ } finally {
3438
+ await el.dispose();
3439
+ }
3440
+ };
3424
3441
  }
3425
- return (...args) => getElement(refId).then(
3426
- (el) => el[prop](...args)
3427
- );
3442
+ return async (...args) => {
3443
+ const el = await getElement(refId);
3444
+ try {
3445
+ return await el[prop](...args);
3446
+ } finally {
3447
+ await el.dispose();
3448
+ }
3449
+ };
3428
3450
  }
3429
3451
  }
3430
3452
  );
@@ -3451,8 +3473,8 @@ var createComponentHelper = (getActivePage2) => {
3451
3473
  },
3452
3474
  { name: componentName, nth }
3453
3475
  );
3454
- const value = await elementHandles.jsonValue().catch(() => null);
3455
- if (value === null) {
3476
+ const isNull = await currentPage2.evaluate((value) => value === null, elementHandles);
3477
+ if (isNull) {
3456
3478
  await elementHandles.dispose();
3457
3479
  return null;
3458
3480
  }
@@ -3594,11 +3616,15 @@ var createGrabHelper = (ref, getActivePage2) => {
3594
3616
  copyElement: async (refId) => {
3595
3617
  const element = await ref(refId);
3596
3618
  if (!element) return false;
3597
- const currentPage2 = getActivePage2();
3598
- return currentPage2.evaluate((el) => {
3599
- const g2 = globalThis;
3600
- return g2.__REACT_GRAB__?.copyElement([el]) ?? false;
3601
- }, element);
3619
+ try {
3620
+ const currentPage2 = getActivePage2();
3621
+ return await currentPage2.evaluate((el) => {
3622
+ const g2 = globalThis;
3623
+ return g2.__REACT_GRAB__?.copyElement([el]) ?? false;
3624
+ }, element);
3625
+ } finally {
3626
+ await element.dispose();
3627
+ }
3602
3628
  }
3603
3629
  };
3604
3630
  };
@@ -17445,7 +17471,6 @@ USE VIEWPORT screenshot=true ONLY FOR:
17445
17471
 
17446
17472
  PERFORMANCE:
17447
17473
  - interactableOnly:true = much smaller output (recommended)
17448
- - format:'compact' = minimal ref:role:name@Component output
17449
17474
  - maxDepth = limit tree depth
17450
17475
 
17451
17476
  After getting refs, use browser_execute with: ref('e1').click()`,
@@ -17453,7 +17478,6 @@ After getting refs, use browser_execute with: ref('e1').click()`,
17453
17478
  page: external_exports.string().optional().default("default").describe("Named page context"),
17454
17479
  maxDepth: external_exports.number().optional().describe("Limit tree depth"),
17455
17480
  interactableOnly: external_exports.boolean().optional().describe("Only clickable/input elements (recommended)"),
17456
- format: external_exports.enum(["yaml", "compact"]).optional().default("yaml").describe("'yaml' or 'compact'"),
17457
17481
  screenshot: external_exports.boolean().optional().default(false).describe(
17458
17482
  "Viewport screenshot. For element screenshots (PREFERRED), use browser_execute: ref('eX').screenshot()"
17459
17483
  )
@@ -17463,7 +17487,6 @@ After getting refs, use browser_execute with: ref('e1').click()`,
17463
17487
  page: pageName,
17464
17488
  maxDepth,
17465
17489
  interactableOnly,
17466
- format,
17467
17490
  screenshot
17468
17491
  }) => {
17469
17492
  let browser2 = null;
@@ -17473,7 +17496,7 @@ After getting refs, use browser_execute with: ref('e1').click()`,
17473
17496
  const activePage = connection.page;
17474
17497
  const getActivePage2 = () => activePage;
17475
17498
  const snapshot = createSnapshotHelper(getActivePage2);
17476
- const snapshotResult = await snapshot({ maxDepth, interactableOnly, format });
17499
+ const snapshotResult = await snapshot({ maxDepth, interactableOnly });
17477
17500
  if (screenshot) {
17478
17501
  const screenshotBuffer = await activePage.screenshot({
17479
17502
  fullPage: false,
@@ -17509,7 +17532,7 @@ IMPORTANT: Always call snapshot() first to get element refs from the a11y tree (
17509
17532
 
17510
17533
  AVAILABLE HELPERS:
17511
17534
  - page: Playwright Page object (https://playwright.dev/docs/api/class-page)
17512
- - snapshot(opts?): Get ARIA tree with React component info. opts: {maxDepth, interactableOnly, format}
17535
+ - snapshot(opts?): Get ARIA tree with React component info. opts: {maxDepth, interactableOnly}
17513
17536
  - ref(id): Get element by ref ID, chainable with all ElementHandle methods
17514
17537
  - ref(id).source(): Get React component source {filePath, lineNumber, componentName}
17515
17538
  - ref(id).props(): Get React component props (serialized)
@@ -17712,7 +17735,7 @@ For interacting with elements, use browser_snapshot to get refs first.`,
17712
17735
  };
17713
17736
 
17714
17737
  // src/commands/browser.ts
17715
- var VERSION2 = "0.1.0-beta.3";
17738
+ var VERSION2 = "0.1.0-beta.4";
17716
17739
  var printHeader = () => {
17717
17740
  console.log(
17718
17741
  `${pc.magenta("\u273F")} ${pc.bold("React Grab")} ${pc.gray(VERSION2)}`
@@ -17735,6 +17758,10 @@ var rebuildNativeModuleAndRestart = async (browserPkgDir) => {
17735
17758
  stdio: "inherit",
17736
17759
  detached: false
17737
17760
  });
17761
+ child.on("error", (error48) => {
17762
+ console.error(`Failed to restart: ${error48.message}`);
17763
+ process.exit(1);
17764
+ });
17738
17765
  child.on("exit", (code) => process.exit(code ?? 0));
17739
17766
  };
17740
17767
  var isSupportedBrowser = (value) => {
@@ -17833,8 +17860,10 @@ var start = new Command().name("start").description("start browser server manual
17833
17860
  const playwrightCookies = toPlaywrightCookies(cookies);
17834
17861
  const browser2 = await chromium.connectOverCDP(browserServer.wsEndpoint);
17835
17862
  const contexts = browser2.contexts();
17836
- if (contexts.length > 0 && playwrightCookies.length > 0) {
17837
- await contexts[0].addCookies(playwrightCookies);
17863
+ if (contexts.length > 0) {
17864
+ if (playwrightCookies.length > 0) {
17865
+ await contexts[0].addCookies(playwrightCookies);
17866
+ }
17838
17867
  await applyStealthScripts(contexts[0]);
17839
17868
  }
17840
17869
  await browser2.close();
@@ -17966,6 +17995,7 @@ var execute = new Command().name("execute").description("run Playwright code wit
17966
17995
  const dispatch = createDispatchHelper(getActivePage2);
17967
17996
  const grab = createGrabHelper(ref, getActivePage2);
17968
17997
  const waitFor = createWaitForHelper(getActivePage2);
17998
+ const component = createComponentHelper(getActivePage2);
17969
17999
  const executeFunction = new Function(
17970
18000
  "page",
17971
18001
  "getActivePage",
@@ -17976,9 +18006,10 @@ var execute = new Command().name("execute").description("run Playwright code wit
17976
18006
  "dispatch",
17977
18007
  "grab",
17978
18008
  "waitFor",
18009
+ "component",
17979
18010
  `return (async () => { ${code} })();`
17980
18011
  );
17981
- const result = await executeFunction(getActivePage2(), getActivePage2, snapshot, ref, fill, drag, dispatch, grab, waitFor);
18012
+ const result = await executeFunction(getActivePage2(), getActivePage2, snapshot, ref, fill, drag, dispatch, grab, waitFor, component);
17982
18013
  console.log(JSON.stringify(await buildOutput(true, result)));
17983
18014
  } catch (error48) {
17984
18015
  console.log(JSON.stringify(await buildOutput(false, void 0, error48 instanceof Error ? error48.message : "Failed")));
@@ -18036,8 +18067,7 @@ PERFORMANCE TIPS
18036
18067
  1. Batch multiple actions in a single execute call to minimize round-trips.
18037
18068
  Each execute spawns a new connection, so combining actions is 3-5x faster.
18038
18069
 
18039
- 2. Use compact format or limit depth for smaller snapshots (faster, fewer tokens).
18040
- - snapshot({format: 'compact'}) -> minimal ref:role:name output
18070
+ 2. Use interactableOnly or limit depth for smaller snapshots (faster, fewer tokens).
18041
18071
  - snapshot({interactableOnly: true}) -> only clickable/input elements
18042
18072
  - snapshot({maxDepth: 5}) -> limit tree depth
18043
18073
 
@@ -18046,22 +18076,28 @@ PERFORMANCE TIPS
18046
18076
  execute "await ref('e1').click()"
18047
18077
  execute "return await snapshot()"
18048
18078
 
18049
- # FAST: 1 round-trip, compact output
18079
+ # FAST: 1 round-trip, interactable only
18050
18080
  execute "
18051
18081
  await page.goto('https://example.com');
18052
18082
  await ref('e1').click();
18053
- return await snapshot({format: 'compact'});
18083
+ return await snapshot({interactableOnly: true});
18054
18084
  "
18055
18085
 
18056
18086
  HELPERS
18057
18087
  page - Playwright Page object
18058
18088
  snapshot(opts?) - Get ARIA accessibility tree with refs
18059
18089
  opts.maxDepth: limit tree depth (e.g., 5)
18060
- opts.interactableOnly: only show elements with refs
18061
- opts.format: "yaml" (default) or "compact"
18090
+ opts.interactableOnly: only clickable/input elements
18062
18091
  ref(id) - Get element by ref ID (chainable - supports all ElementHandle methods)
18063
18092
  Example: await ref('e1').click()
18064
18093
  Example: await ref('e1').getAttribute('data-foo')
18094
+ ref(id).source() - Get React component source file info for element
18095
+ Returns { filePath, lineNumber, componentName } or null
18096
+ ref(id).props() - Get React component props (serialized)
18097
+ ref(id).state() - Get React component state/hooks (serialized)
18098
+ component(name, opts?) - Find elements by React component name
18099
+ opts.nth: get the nth matching element (0-indexed)
18100
+ Example: await component('Button', {nth: 0})
18065
18101
  fill(id, text) - Clear and fill input (works with rich text editors)
18066
18102
  drag(opts) - Drag with custom MIME types
18067
18103
  opts.from: source selector or ref ID (e.g., "e1" or "text=src")
@@ -18077,21 +18113,16 @@ HELPERS
18077
18113
  await waitFor('.btn') - wait for selector
18078
18114
  await waitFor('networkidle') - wait for network idle
18079
18115
  await waitFor('load') - wait for page load
18080
- ref(id).source() - Get React component source file info for element
18081
- Returns { filePath, lineNumber, componentName } or null
18082
18116
  grab - React Grab client API (activate, copyElement, etc)
18083
18117
 
18084
- SNAPSHOT FORMATS
18118
+ SNAPSHOT OPTIONS
18085
18119
  # Full YAML tree (default, can be large)
18086
18120
  execute "return await snapshot()"
18087
18121
 
18088
18122
  # Interactable only (recommended - much smaller!)
18089
18123
  execute "return await snapshot({interactableOnly: true})"
18090
18124
 
18091
- # Compact format (minimal output: ref:role:name|ref:role:name)
18092
- execute "return await snapshot({format: 'compact'})"
18093
-
18094
- # Combined options
18125
+ # With depth limit
18095
18126
  execute "return await snapshot({interactableOnly: true, maxDepth: 6})"
18096
18127
 
18097
18128
  SCREENSHOTS - PREFER ELEMENT OVER FULL PAGE
@@ -18135,15 +18166,28 @@ COMMON PATTERNS
18135
18166
  dataTransfer: { 'application/x-custom': 'data' }
18136
18167
  })"
18137
18168
 
18138
- # Get React component source file
18139
- execute "return await ref('e1').source()"
18140
-
18141
18169
  # Get page info
18142
18170
  execute "return {url: page.url(), title: await page.title()}"
18143
18171
 
18144
18172
  # CSS selector fallback (refs are now in DOM as aria-ref)
18145
18173
  execute "await page.click('[aria-ref="e1"]')"
18146
18174
 
18175
+ REACT-SPECIFIC PATTERNS
18176
+ # Get React component source file
18177
+ execute "return await ref('e1').source()"
18178
+
18179
+ # Get component props
18180
+ execute "return await ref('e1').props()"
18181
+
18182
+ # Get component state
18183
+ execute "return await ref('e1').state()"
18184
+
18185
+ # Find elements by React component name
18186
+ execute "const buttons = await component('Button'); return buttons.length"
18187
+
18188
+ # Get the first Button component and click it
18189
+ execute "const btn = await component('Button', {nth: 0}); await btn.click()"
18190
+
18147
18191
  MULTI-PAGE SESSIONS
18148
18192
  execute "await page.goto('https://github.com')" --page github
18149
18193
  execute "return await snapshot({interactableOnly: true})" --page github
@@ -18200,7 +18244,7 @@ browser.addCommand(status);
18200
18244
  browser.addCommand(execute);
18201
18245
  browser.addCommand(pages);
18202
18246
  browser.addCommand(mcp);
18203
- var VERSION3 = "0.1.0-beta.3";
18247
+ var VERSION3 = "0.1.0-beta.4";
18204
18248
  var isMac = process.platform === "darwin";
18205
18249
  var META_LABEL = isMac ? "Cmd" : "Win";
18206
18250
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -18687,7 +18731,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
18687
18731
  handleError(error48);
18688
18732
  }
18689
18733
  };
18690
- var VERSION4 = "0.1.0-beta.3";
18734
+ var VERSION4 = "0.1.0-beta.4";
18691
18735
  var REPORT_URL = "https://react-grab.com/api/report-cli";
18692
18736
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
18693
18737
  var promptAgentIntegration = async (cwd, customPkg) => {
@@ -18731,19 +18775,29 @@ var promptAgentIntegration = async (cwd, customPkg) => {
18731
18775
  }
18732
18776
  }
18733
18777
  if (integrationType === "skill" || integrationType === "both") {
18734
- logger.break();
18735
- const skillSpinner = spinner("Installing browser automation skill").start();
18736
- try {
18737
- execSync(`npx -y openskills install aidenybai/react-grab -y`, {
18738
- stdio: "inherit",
18739
- cwd
18740
- });
18741
- logger.break();
18742
- skillSpinner.succeed("Skill installed to .claude/skills/");
18743
- } catch {
18778
+ const { skillTarget } = await prompts3({
18779
+ type: "select",
18780
+ name: "skillTarget",
18781
+ message: `Which ${highlighter.info("agent")} would you like to install the skill for?`,
18782
+ choices: SUPPORTED_TARGETS.map((target) => ({
18783
+ title: target,
18784
+ value: target
18785
+ }))
18786
+ });
18787
+ if (skillTarget) {
18744
18788
  logger.break();
18745
- skillSpinner.fail("Failed to install skill");
18746
- logger.dim("Try manually: npx -y openskills install aidenybai/react-grab");
18789
+ const skillSpinner = spinner("Installing browser automation skill").start();
18790
+ try {
18791
+ const skill = await fetchSkillFile();
18792
+ const skillDir = join(cwd, AGENT_TARGETS[skillTarget]);
18793
+ rmSync(skillDir, { recursive: true, force: true });
18794
+ mkdirSync(skillDir, { recursive: true });
18795
+ writeFileSync(join(skillDir, "SKILL.md"), skill);
18796
+ skillSpinner.succeed(`Skill installed to ${AGENT_TARGETS[skillTarget]}/`);
18797
+ } catch {
18798
+ skillSpinner.fail("Failed to install skill");
18799
+ logger.dim("Try manually: npx -y openskills install aidenybai/react-grab");
18800
+ }
18747
18801
  }
18748
18802
  }
18749
18803
  logger.break();
@@ -19379,7 +19433,7 @@ var init = new Command().name("init").description("initialize React Grab in your
19379
19433
  await reportToCli("error", void 0, error48);
19380
19434
  }
19381
19435
  });
19382
- var VERSION5 = "0.1.0-beta.3";
19436
+ var VERSION5 = "0.1.0-beta.4";
19383
19437
  var remove = new Command().name("remove").description("remove an agent integration").argument(
19384
19438
  "[agent]",
19385
19439
  "agent to remove (claude-code, cursor, opencode, codex, gemini, amp, ami, visual-edit)"
@@ -19558,7 +19612,7 @@ var remove = new Command().name("remove").description("remove an agent integrati
19558
19612
  });
19559
19613
 
19560
19614
  // src/cli.ts
19561
- var VERSION6 = "0.1.0-beta.3";
19615
+ var VERSION6 = "0.1.0-beta.4";
19562
19616
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
19563
19617
  process.on("SIGINT", () => process.exit(0));
19564
19618
  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.4",
3
+ "version": "0.1.0-beta.5",
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.4"
35
+ "@react-grab/browser": "0.1.0-beta.5"
36
36
  },
37
37
  "scripts": {
38
38
  "dev": "tsup --watch",