feat(mcp): read element context from the OS clipboard#311
feat(mcp): read element context from the OS clipboard#311
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
There was a problem hiding this comment.
5 issues found across 33 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/mcp/src/utils/read-clipboard-windows.ts">
<violation number="1" location="packages/mcp/src/utils/read-clipboard-windows.ts:11">
P1: Reading the clipboard by `application/x-react-grab` will miss Chromium's custom clipboard format on Windows, so browser-copied element context is likely always unavailable there.</violation>
<violation number="2" location="packages/mcp/src/utils/read-clipboard-windows.ts:18">
P1: The fallback `$data.ToString()` in the `else` branch will silently produce `"System.IO.MemoryStream"` instead of the actual payload content. For custom clipboard formats, `[System.Windows.Forms.Clipboard]::GetData()` typically returns a `System.IO.MemoryStream`, not a `byte[]` or a string. Add a `System.IO.Stream` branch that reads the stream content as UTF-8:
```powershell
} elseif ($data -is [System.IO.Stream]) {
$reader = [System.IO.StreamReader]::new($data, [System.Text.Encoding]::UTF8)
[Console]::Out.Write($reader.ReadToEnd())
$reader.Close()
```</violation>
</file>
<file name="packages/mcp/src/utils/read-clipboard-wsl.ts">
<violation number="1" location="packages/mcp/src/utils/read-clipboard-wsl.ts:15">
P2: Preserve the Linux fallback hint instead of always overwriting it with the WSL interop message.</violation>
</file>
<file name="packages/mcp/src/utils/read-clipboard-linux.ts">
<violation number="1" location="packages/mcp/src/utils/read-clipboard-linux.ts:45">
P2: A non-ENOENT `wl-paste` failure returns early instead of falling back to `xclip`, so clipboard reads can fail on Wayland/XWayland setups where the X11 reader would still work.</violation>
</file>
<file name="packages/mcp/src/server.ts">
<violation number="1" location="packages/mcp/src/server.ts:22">
P2: `payload.content` can already include the user prompt, so this formats prompted grabs with the prompt duplicated in both `Prompt:` and `Elements:`.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/mcp/src/server.ts">
<violation number="1" location="packages/mcp/src/server.ts:30">
P2: Using `entry.content` to rebuild the elements body loses the canonical formatting in `payload.content`. Multi-element copies will drop their `[1]`/`[2]` labels, and any `transformCopyContent` output is discarded.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/mcp/src/server.ts">
<violation number="1" location="packages/mcp/src/server.ts:27">
P2: The trimmed prompt fallback can truncate real element content that happens to start with the prompt text.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
12 issues found across 66 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/cli/src/commands/watch.ts">
<violation number="1" location="packages/cli/src/commands/watch.ts:74">
P1: Let the process exit naturally after writing the payload; `process.exit(0)` here can truncate stdout.</violation>
</file>
<file name="skills/react-grab/SKILL.md">
<violation number="1" location="skills/react-grab/SKILL.md:19">
P2: Don't route pasted React Grab output through `watch`; it waits for a newer clipboard timestamp and will hang on already-pasted context.</violation>
</file>
<file name="packages/cli/src/utils/install-skill.ts">
<violation number="1" location="packages/cli/src/utils/install-skill.ts:137">
P2: Unsupported agents are exposed as valid install/remove targets, so commands can accept VS Code or Zed and then no-op as if the selection were valid.</violation>
</file>
<file name="packages/mcp/src/cli.ts">
<violation number="1" location="packages/mcp/src/cli.ts:15">
P3: Avoid `process.exit(1)` here; it can cut off the deprecation notice before stderr flushes.</violation>
</file>
<file name="packages/cli/src/utils/wait-for-next-grab.ts">
<violation number="1" location="packages/cli/src/utils/wait-for-next-grab.ts:53">
P2: The polling loop does not enforce the requested timeout while `read()` is in flight, so short `watch --timeout` values can still block for the clipboard reader's full 3s timeout.</violation>
</file>
<file name="packages/cli/src/commands/init.ts">
<violation number="1" location="packages/cli/src/commands/init.ts:350">
P1: Project-scoped skill installs use the original cwd, so selecting a subproject can write the skill into the wrong repo directory.</violation>
</file>
<file name="packages/cli/test/watch-cli.test.ts">
<violation number="1" location="packages/cli/test/watch-cli.test.ts:6">
P1: Use an ESM-safe path source here; `__dirname` is undefined in this package.</violation>
</file>
<file name="packages/cli/README.md">
<violation number="1" location="packages/cli/README.md:43">
P3: `grab add` is not an alias of `install-skill`; it is a separate wrapper command with different behavior. Reword this to avoid implying the commands are interchangeable.</violation>
</file>
<file name="packages/cli/src/utils/last-selected-agents.ts">
<violation number="1" location="packages/cli/src/utils/last-selected-agents.ts:12">
P2: Ignore relative `XDG_STATE_HOME` values before building the state path.</violation>
</file>
<file name="package.json">
<violation number="1" location="package.json:9">
P1: Keep `@react-grab/mcp` in the root build filter so its `dist` binary is generated before publishing.</violation>
</file>
<file name="packages/cli/src/commands/install-skill.ts">
<violation number="1" location="packages/cli/src/commands/install-skill.ts:85">
P2: Check `installSkills()` results before printing the restart/success message. This command currently reports success even when every requested install was skipped or failed.</violation>
</file>
<file name="packages/cli/src/utils/format-payload.ts">
<violation number="1" location="packages/cli/src/utils/format-payload.ts:27">
P2: Prompt-mode payloads created through `getContent` lose their `Prompt:` section because this formatter only reads prompts from `entries.commentText`.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
…aller
Reduces @react-grab/mcp to a 0.2.0 deprecation stub and folds the clipboard
reader into @react-grab/cli as a `react-grab watch` subcommand that polls
the system clipboard's `application/x-react-grab` payload and exits when a
fresh grab arrives (default 10-min timeout, `--timeout 0` blocks forever).
SSH/WSL "unrecoverable" envs now fast-exit with code 2 instead of polling
out the timeout.
Adds `react-grab install-skill` that writes SKILL.md to known agent skill
directories. Universal agents (Cursor, Codex, OpenCode, Amp, Cline, Gemini
CLI, GitHub Copilot, Warp) collapse to a single canonical write at
.agents/skills/ (project) or ~/.agents/skills/ (global), matching the
vercel-labs/skills convention. Non-universal agents (Claude Code,
Windsurf, Droid) get their own paths.
Auto-detects installed agents via existsSync against telltale dirs and
remembers the last-selected agents under
${XDG_STATE_HOME ?? ~/.local/state}/react-grab/. Honors CLAUDE_CONFIG_DIR,
CODEX_HOME, XDG_CONFIG_HOME. Telemetry pings now skip under
DISABLE_TELEMETRY / DO_NOT_TRACK and in common CI environments.
Skill template ships `allowed-tools: [Bash]` frontmatter per the Agent
Skills Specification.
Adds parse-timeout-seconds, wait-for-next-grab, format-payload,
is-telemetry-enabled, last-selected-agents, skill-template utilities plus
229 unit tests (clipboard readers, watch outcomes, install/remove dedup,
SSH fast-exit, deprecation stub).
- watch: don't `process.exit(0)` after writing the payload — return so Node drains stdout naturally (avoids truncation when piped). - init: pass `projectInfo.projectRoot` (not the original `cwd`) to the skill installer so subprojects in monorepos get the skill in the right dir. - root: re-add `--filter=@react-grab/mcp` to the build script so the deprecation stub `dist` artifact is generated before publish. - install-skill / remove: reject `--agent` arguments that name unsupported clients (VS Code, Zed) instead of silently no-opping. - skill template: clarify the trigger — agents should NOT run `watch` if the user has already pasted React Grab toolbar output (it would block waiting for a fresh clipboard timestamp that is not coming). - last-selected-agents: ignore relative `$XDG_STATE_HOME` values per the XDG Base Directory spec; fall back to `~/.local/state`. - subprocess tests (watch-cli, deprecation-stub): use `fileURLToPath(import.meta.url)` instead of `__dirname` so they're ESM-pure.
- install-skill: filter unsupported clients out of `--yes` fallback, multiselect prompt, and only print "Restart your agent(s)..." when at least one install actually succeeded. Mirror the result-checking in the single-detected and explicit-agent branches too. - clipboard readers: when the OS helper binary is missing (osascript / xclip+wl-paste / powershell), set `recoverable: false` so `watch` fast-exits with code 2 and the install hint, instead of polling out the timeout. - mcp deprecation stub: replace `process.exit(1)` with `process.exitCode = 1` so the deprecation notice fully flushes before exit. - cli README: clarify that `grab add` is a wrapper around `install-skill`, not a strict alias. Skipping two pre-existing/edge-case items: format-payload prompt-mode (producer always uses entries[].commentText, schema has no top-level prompt) and wait-for-next-grab read-blocks-timeout (refactor cost > 0.5% overshoot benefit at the default 600s timeout).
…uccess - WSL reader: stay recoverable as long as either the Windows host or the WSLg Linux channel can still produce. Previously, when one channel returned ENOENT the combined outcome was marked unrecoverable even when the other channel could still serve a valid grab once one appeared. - install-skill: only call writeLastSelectedAgents AFTER confirming at least one install succeeded. Previously the --agent and --yes branches persisted the selection unconditionally, so a failed run biased future interactive multiselect pre-checks. New WSL test confirms the recoverability composition: both-channels-failed => unrecoverable; one-channel-failed-but-other-empty => still recoverable.
…stall Same fix as init.ts (subprojects in monorepos). The non-interactive `installDetectedOrAllSkills` and the interactive `promptSkillInstall` paths both now anchor on the resolved project root rather than the launch cwd, so agents find the installed skill in the dir they're scanning.
bugbot flagged that fail() writes to stderr then immediately calls process.exit, which can truncate output on piped consumers (every agent tool harness pipes stderr). The match path already returned naturally; fix the same pattern for unrecoverable/timeout/aborted/default by: - Using process.stderr.write(msg, callback) and putting process.exit inside the callback - the callback only fires once the kernel buffer has accepted the write. - Throwing an ExitSignal sentinel to halt synchronous execution; the action's outer try/catch swallows it so the user sees only the message we just wrote.
bugbot: when init() runs in a fresh project, an optional skill install write failure was calling process.exit(0) before the main React Grab install/transform happened. Treat skill install as decoration: warn on failure and continue to install React Grab itself.
- Linux Wayland reader: only fall through to xclip when wl-paste binary is missing (ENOENT). Previously any non-zero exit fell through, which on Wayland-only systems with the common 'no data of that mime' case surfaced a misleading 'install xclip' hint. Update the test to assert the new behavior: runtime wl-paste failure returns empty payload, not an xclip retry. - init.ts already-installed branch: when the user opts into a skill install and it fails, surface the failure and exit 1 (was silent exit 0). Mirrors the warn-and-continue pattern in the fresh-install branch but with non-zero exit since skill install was the only action on this path.
…ally finds the payload
Chromium and WebKit on macOS do NOT expose web-custom-format MIME types
(anything the page wrote via clipboardData.setData(type, data) for a
non-standard MIME) under the raw type name on NSPasteboard. They bundle
all such entries into a single pasteboard type:
org.chromium.web-custom-data
org.webkit.web-custom-data
containing a base::Pickle with [count, then for each: mime length in
UTF-16 code units, mime UTF-16 LE bytes, padded to 4 bytes, value
length in UTF-16 code units, value UTF-16 LE bytes, padded].
Our previous JXA called dataForType('application/x-react-grab')
directly and got nil for browser-written grabs, so watch polled forever
without ever seeing the payload. Confirmed live: a Cursor session's
clipboard exposed `org.chromium.web-custom-data` (with vscode-editor-data
inside) and our raw lookup returned nil.
Fix:
- New util `decode-chromium-web-custom-data.ts` parses the pickle
format with proper 4-byte alignment.
- macos JXA now tries direct first (covers Safari/Firefox direct
exposure and any future browser change), then falls back to
org.chromium.web-custom-data, then org.webkit.web-custom-data,
emitting a sentinel-prefixed base64 dump that the Node side decodes.
Tests:
- 6 unit tests for the pickle decoder (single entry, scan past
unrelated, alignment padding, truncation handling, missing target).
- macos reader tests now assert the JXA script references all three
pasteboard types and decodes the sentinel-prefixed pickle correctly.
Verified end-to-end live: wrote a fake pickle to NSPasteboard, ran
`watch --timeout 8 --json`, swapped in a fresh-timestamp pickle during
polling, watch exited 0 with the correct JSON on stdout.
- Move PICKLE_SENTINEL + PICKLE_ALIGNMENT + MAX_ENTRIES to constants.ts
(per AGENTS.md rule on shared constants).
- Harden the JXA->Node sentinel to '\u0001CHROMIUM_PICKLE_B64\u0002' so
it can never collide with a direct-path payload (valid JSON cannot
start with control bytes; parseReactGrabPayload validates JSON shape
downstream regardless).
- Cap entryCount at 1024 to keep a malicious / corrupt pickle from
looping for a long time. Pre-existing buffer-bound checks already
bounded total work, but the explicit cap is defensive.
- Decoder + macOS reader headers now mention WebKit and document that
the WebKit pickle layout was not verified empirically; the parser
reuses the Chromium logic on a best-effort basis and returns null
cleanly if the format differs.
- Tests:
- Import sentinel + alignment from constants.ts (was duplicated as
string literals in two test files).
- Add 'declared payload size larger than buffer' test.
- Add 'entry count above MAX' test.
- Add 'entry count exactly at MAX' test.
- remove command refuses to delete a shared canonical file when other un-targeted universal agents still use it. `grab remove --agent Cursor` used to wipe `.agents/skills/react-grab/SKILL.md` and break the skill for every other universal agent (Codex, OpenCode, Amp, Cline, Gemini CLI, GitHub Copilot, Warp). Now it's preserved with a clear "kept: still used by ..." message and a hint to pass --agent for every sharer if the user really wants to remove. - install-skill --yes wholesale fallback (no detected agents) no longer persists the resulting "every supported agent" list as last-selected. Persistence is now restricted to explicit signals: detected agents, --agent flag, single-detected auto-install, interactive multiselect. Future interactive runs no longer get pre-checked with ~12 agents the user never deliberately chose. 3 new tests: - removeSkills: full canonical wipe when ALL universal agents targeted - removeSkills: refuses + reports sharedWith when subset targeted - removeSkills: non-universal agent removed without touching shared file
Per bugbot finding: `grab remove` invoked from a subdirectory in a monorepo silently no-ops while the actual SKILL.md sits at `<projectRoot>/.agents/skills/react-grab/`. Same blind spot affects `grab install-skill` (writes a stray `.agents/skills` under the subdir that agents won't pick up) and `grab add` (detectProject returns the input cwd as projectRoot without walking up). - Add `findNearestProjectRoot(start)` in detect.ts that walks up looking for the nearest `package.json`, capped at 64 levels, falling back to `start` if nothing is found before the filesystem root. - `install-skill`, `remove`, `add` all run cwd through it before passing to skill installer / remover / detectProject. - New tests: 4 cases for the walker (self, walk up, deepest workspace package wins, fallback when no package.json). - Smoke verified end-to-end: install from `$TMP/packages/web/src/components` → writes to `$TMP/.claude/skills/react-grab/SKILL.md`; remove from the same subdir → finds and deletes that exact file.
- monorepo workspace root: findNearestProjectRoot now prefers the outermost ancestor that's a workspace root (pnpm-workspace.yaml, lerna.json, or package.json with non-empty `workspaces` field) over the deepest plain package.json. Without this, install-skill / remove / add invoked from `<repo>/packages/web/src` resolved to `<repo>/packages/web` and the canonical `.agents/skills/...` ended up under the workspace package, not the repo root where editor agents actually look. - init -y now installs the React Grab skill (default to project scope) to match the pre-CLI MCP behavior. Without this fix, scripted `npx grab init -y` pipelines silently lose agent integration after upgrading. - installSkills spinner reports failure when every selected client was skipped (e.g. only unsupported agents like Zed/VS Code). The previous green "Installed to 0 agents." check contradicted the per-client "skipped" lines and the eventual non-zero exit. - readClipboardWsl falls through to WSLg when the Windows host returns a non-JSON-shaped payload. Previously a single garbage host read would cause the watch loop to idle until timeout even when WSLg held a valid grab. Tests: 5 new walker tests (workspace markers, npm/yarn workspaces, lerna, plain repo fallback, no-project fallback). 2 new WSL tests (garbage-host fallthrough, surface garbage when no channel has JSON).
…emplate - New `grab check-installed` (also `grab is-installed`) command. Exits 0 if react-grab is in the project's package.json dependencies, exits 1 otherwise. Supports `--json` for scripted usage. Useful for CI / automation pipelines that want to gate further setup on whether react-grab is already installed. - Tighten the SKILL.md description so agents pick the skill up on shorter, more natural references like "this thing" / "that component" / "/react-grab", while still keeping the explicit "don't run watch if content is already pasted" guidance.
promptSkillInstall returned a single boolean for both "user cancelled"
and "every install failed", and add.ts collapsed both into exit 0. A
genuine install failure (permission errors writing to an agent skill
dir, etc.) terminated with a success exit code while skipping the
"Success!" message - silently swallowed by wrapper scripts and CI.
Change return type to a discriminated SkillInstallOutcome union
("cancelled" | "succeeded" | "failed") and have add.ts exit 1 on
failed and 0 on cancelled, matching install-skill's exit semantics.
`grab check-installed` resolved cwd directly and called detectReactGrab against that single directory, only reading `<cwd>/package.json`. Sibling commands (add, install-skill, remove) walk up via findNearestProjectRoot for exactly this reason. The skill template instructs the agent to run `check-installed` as the preflight before `watch`, so when the agent's working directory is any subdirectory of the project (common in monorepos and even in single- package projects when the agent opens a deeper folder), the preflight falsely reported react-grab as not installed and the skill prompted the user to run `grab init` despite it already being set up. New test: invoke from `<workDir>/packages/ui/src` and verify the JSON output reports the project root and `installed: true`.
…ME accuracy - copy.ts: when callers use the getContent override (no per-element snippets), entries was left undefined and copy-content.ts built a default entry with commentText: undefined. Downstream formatters then dropped the "Prompt:" section even though extraPrompt had been prepended to payload.content. Pass extraPrompt as the top-level commentText so the default entry surfaces the prompt. - cli/README: clarify that `grab add` is a separate higher-level command (preflights React Grab installation, simpler scope choice) rather than a wrapper/alias of install-skill.
`detectReactGrab` only inspected the project root, which broke two common cases: 1. Working inside the react-grab source monorepo itself - the root package.json has no react-grab dep because react-grab IS the package defined under packages/react-grab. The skill preflight then falsely prompted users to run `grab init` on the source repo. 2. Consumer monorepos where only one or two app packages depend on react-grab - the root package.json carries no react-grab dep, so the preflight misfired anywhere outside that one app. Now also: (a) treat a package whose own `name` is `react-grab` as installed, and (b) walk every workspace package and re-check via the single-package detector before giving up.
- Linux ENOENT detection (Medium): isBinaryMissing now relies solely on error.code === 'ENOENT'. The previous /not found/i regex matched wl-paste's runtime "No data found of type X" stderr (the common "MIME isn't on the clipboard right now" case), which incorrectly routed to the X11 fallback on Wayland and surfaced the misleading "install xclip" hint as unrecoverable. - watch fail() reachability (Medium): all fail() call sites now use `return fail(...)` instead of expression-statements so TS control- flow analysis treats every branch as terminating, even if fail is ever refactored away from throwing synchronously. - removeSkills false sharedWith (Low): existence check before deciding between "removed", "shared with another agent", and "nothing here". `grab remove --agent Cursor` against a project that never installed the skill no longer prints "kept: still used by Codex, OpenCode, ..." for a file that doesn't exist; falls through to "Nothing to remove." - hasWorkspacesField alignment (Low): mirror detectMonorepo's permissive "any truthy `workspaces` counts" rule so findNearestProjectRoot can never disagree with detectMonorepo on the same package.json. - install-skill --yes single-detected persistence (Low): don't persist the auto-routed agent as "last selected" when the user didn't make an active choice. Prevents future interactive runs from being silently restricted to a single agent. - install-skill cancellation exit code (Low): exit 1 on user-cancelled multiselect to match the scope-prompt cancellation branch and `add`'s exit semantics, so wrapper scripts can distinguish a cancelled install from a successful one. NOT fixed (bugbot wrong on JXA): - read-clipboard-macos selector convention: bugbot claimed JXA needs underscore-per-colon (initWithData_encoding_, base64EncodedStringWithOptions_), but empirically those raise "is not a function" on current macOS. Reverted to camelCase forms (verified live: writes a fresh pickle to org.chromium.web-custom-data, watch reads + decodes + exits 0 with the parsed JSON payload). Added comment documenting the empirical behavior so it doesn't get "fixed" again. 5 new tests; total 263/263 passing.
…nts; rename JSON field
- check-installed: use the write-callback flush pattern from watch.ts so
stdout/stderr drain before process.exit (avoids truncation when piped
through agent tool harnesses).
- check-installed: wrap action body in try/catch + handleError to match
every other command's error reporting.
- check-installed: rename JSON output field cwd -> projectRoot and add
requestedCwd so consumers can see both the input and the resolved
walk-up target unambiguously.
- detect.ts: add unit tests for the two new detectReactGrab branches
(name === "react-grab" short-circuit, monorepo workspace walk).
- detect.ts, copy.ts, tests: trim multi-line comments per AGENTS.md
("default to no comments; only when the why is non-obvious").
- init -y now installs the React Grab skill on re-runs against an already-installed project (Medium). The previous fix only added skill install to the fresh-install non-interactive branch, so CI scripts that re-ran `npx grab init -y` after a successful first install silently lost agent integration. - watch no longer returns a stale grab when the initial clipboard read transiently fails to parse (Medium). Threaded a new `rawPayloadPresent` signal through readClipboardPayload -> waitForNextGrab so the polling loop can distinguish "clipboard was genuinely empty at start" (first non-null = match) from "parse failed on a real but corrupt/partial read" (first non-null = new baseline, wait for the next change). 2 new tests cover both arms of the discriminated logic. - promptSkillInstall single-detected auto-route no longer persists the chosen agent as last-selected (Medium). Symmetrical to the install-skill fix from the previous round - the user didn't make an active choice when we auto-routed, so persisting would silently bias every future interactive multiselect to that single agent and suppress the auto-route on subsequent runs. 268/268 tests passing.
- add cancellation now exits 1 (Low). Brings `grab add`'s cancellation exit code in line with `grab install-skill` and the scope-prompt branch. Wrapper scripts can now reliably distinguish a user-aborted multiselect from a successful install across both commands. - WSL combined hint now retains the host-side guidance (Low). Previously combineHints(WSL_INTEROP_HINT, wslgOutcome.hint) silently dropped hostOutcome.hint, hiding the most actionable error when the real failure was e.g. PowerShell missing rather than disabled interop. Now host hint -> WSL_INTEROP_HINT -> WSLg hint, in that order. Test updated to assert the host-specific marker is present.
Move the canonical skill content out of `skill-template.ts`'s string literal into a real `skill-template.md` next to it. The bundler inlines its contents at build time via a `define` substitution, and the repo-root `skills/react-grab/SKILL.md` is now a symlink to the same file - so the GitHub-visible copy and the bundled copy can never drift apart.
`grab log` emits every React Grab payload as NDJSON
(`{prompt?, content}` per line) and mirrors each line to
`.react-grab/logs` (auto-gitignored via `.react-grab/.gitignore`).
TTY users get continuous streaming; piped mode (`log | head -n 1`)
exits cleanly after the first match so agent skills don't wait
on a still-running pipeline.
Drops the 10-minute idle timeout - log no longer voluntarily
exits except on a fundamental clipboard-read error (SSH, missing
helper). Skill template, README, mcp migration table, and docs
updated to match.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…root resolve - `grab remove` no longer wipes the user's global skill by default. Without an explicit `--scope`, only project scope is touched. The interactive multiselect only asks WHICH agents - never which scope - so a user cleaning up a single project would silently lose their global install across every other project on the machine. Pass `--scope global` to remove the per-user copy. - `findNearestProjectRoot` returns the resolved absolute path on the no-package.json fallback branch. Every other branch already returned an absolute path; the relative-`--cwd` leak only fired here. 282 tests passing (+1).
Re-verified each agent's actual support for the Agent Skills `.agents/skills/` convention (the open standard at agentskills.io) and updated the install map accordingly: - Windsurf: now universal. Cascade scans `.agents/skills/` and `~/.agents/skills/` per the official Windsurf docs, so installs dedup against the canonical location instead of writing a duplicate file under `.windsurf/skills/`. - Pi (`badlogic/pi-mono`): added as universal. Detected via `~/.pi/`, scans `.agents/skills/` and `~/.agents/skills/` per the pi-mono docs. - Amp: split paths. Project scope is canonical (`.agents/skills/`) so it dedups with other universal agents, but global scope writes to `~/.config/agents/skills/` because that's what Amp actually reads - previously the global install silently went to a directory Amp doesn't scan. - Cline: demoted to unsupportedClient. Cline only reads from `.cline/skills/`, never `.agents/skills/`, so installing was a no-op. Surfaces a migration message instead of silently writing to the wrong path. `--agent Cline` still gets a clear "do not support skills yet" error rather than the misleading "unknown agent" path. Also fixes a stale-state bug found during review: - `readKnownLastSelectedAgents` filters the persisted last-selected list against the current client roster before consumption. Without this, a `["Cline"]` entry from a previous CLI version would skew the `lastSelected.length === 0` short-circuits used by the install flow and keep the multiselect's "user has a saved choice" branch active even when none of the saved choices map to a real agent anymore. 282 tests passing (+8).
Bugbot Low-severity DRY: `SKILL_SCOPES` and `isSkillScope` were defined identically in both `commands/install-skill.ts` and `commands/remove.ts`. Both files already import `SkillScope` from `utils/install-skill.ts`, so co-locate the scope list and guard there to remove the maintenance risk of updating one copy but not the other. Pure refactor, no behavior change. 282 tests still passing.
Summary
application/x-react-grabMIME type directly off the OS clipboard./context+/healthchannel with per-OS clipboard readers:osascript -l JavaScript(macOS),wl-paste/xclip(Linux), PowerShell-Sta+ WinForms (Windows), and a WSL bridge that tries the Windows host viapowershell.exethen falls back to WSLg via the Linux reader./skills/react-grabskill that tells agents to call theget_element_contextMCP tool whenever the user references a grabbed element.@react-grab/mcp/clientexport and the browser IIFE bundle. Drops thereact-grabandfkillworkspace deps from@react-grab/mcp. CLI is now always stdio.@react-grab/clino longer adds the now-pointless--stdioflag to generated MCP configs (existing configs keep working since the flag is silently ignored).Why permissionless
localhostrequests from the browser side, so no CORS, no mixed-content warnings, no Chrome local-network-access prompts.text/plain,text/html,application/x-react-grabJSON) — we just read the one that's already there.Failure-mode handling
osascript -l JavaScriptJXAosascript(preinstalled). Check$PATH."xclip -selection clipboard -t application/x-react-grab -oapt install xclip(X11) orapt install wl-clipboard(Wayland)."wl-paste -t application/x-react-grab -n, falls back to xclippowershell.exe -Sta+System.Windows.Forms.Clipboard::GetDataenabled = trueunder[interop]in/etc/wsl.conf)..."react-grab-mcpon the same machine as your browser."runExecFileattaches stdout/stderr to its rejection so timeouts, non-zero exits, andAdd-Typefailures all log the actual diagnostic via[react-grab-mcp] <bin> stderr: ....Test plan
pnpm --filter @react-grab/mcp test— 49/49 passingpnpm test:cli— 116/116 passing (the--stdioremoval is reflected ininstall-mcp.test.ts)pnpm lint— cleanpnpm typecheck— cleanpnpm format— appliedpnpm --filter @react-grab/mcp build— clean (no moreclient.*artifacts)initialize+tools/list+tools/call get_element_context), got the formatted prompt + element snippet backCONTEXT_TTL_MS), simulated SSH session viaSSH_CLIENTenvNotes / breaking changes
@react-grab/mcp/clientexport is removed. Any external consumer importing the empty plugin needs to drop the import (the plugin was a no-op after the rewrite)./contextand/healthendpoints and thePORTenv var are gone.react-grab-mcpis stdio-only now.major(or whichever your release cadence prefers) when releasing.Note
Medium Risk
Moderate risk because it replaces agent-integration flow (MCP config install) with new skill install/remove commands and adds cross-platform clipboard readers/streaming behavior that can vary by OS/shell environment.
Overview
Switches agent integration from MCP server configuration to installing a
react-grabagent skill (grab install-skill, updatedgrab addflow) and addsgrab removesupport for uninstalling skills with project/global scope handling and monorepo-aware root detection.Adds a new
grab logcommand that readsapplication/x-react-grabfrom the OS clipboard (macOS/Linux/Windows/WSL/SSH-aware), streams grabs as NDJSON (optionally exits after first match when piped), and mirrors output to.react-grab/logswith an auto-created.gitignore.Introduces
grab check-installed/is-installed, telemetry gating via CI/opt-out env vars, and replaces the old MCP installer code with a skill installer implementation (including persisted “last selected agents” state) plus extensive new unit tests and updated docs.Reviewed by Cursor Bugbot for commit 78f1537. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Replaces the MCP server with a clipboard‑driven agent skill and a streaming CLI.
react-grab logreadsapplication/x-react-grabfrom the OS clipboard, emits NDJSON, mirrors to.react-grab/logs, and@react-grab/mcpis now a deprecation stub.New Features
log: streams continuously; in piped mode exits after first match; writes.react-grab/logs(auto.gitignore); outputs lines as{"prompt"?,"content"}.install-skill/remove/add: standardizes on.agents/skills/for compatible agents; adds Windsurf and Pi as universal; Amp uses.agents/skills/(project) and~/.config/agents/skills/(global); refuses unsupported clients (e.g. Cline); remembers last-selected agents and filters them against currently supported clients; walks up from subdirs to the workspace/project root;removedefaults to project scope.check-installed(aliasis-installed): exits 0/1;--jsonincludesprojectRootandrequestedCwd; walks up to the nearest project root; treats the source repo and monorepo workspaces as installed when applicable.DISABLE_TELEMETRY,DO_NOT_TRACK, and common CI envs.@react-grab/cliand symlinked atskills/react-grab/SKILL.md.Bug Fixes
System.IO.Streamand avoids UTF‑8 BOM; Linux Wayland only falls back toxcliponENOENT; WSL bridges host PowerShell and WSLg and combines hints; SSH fast‑exit; emit helper stderr; fast‑exit when helper binary is missing.init -yinstalls (and re‑runs) the skill;addexits 1 on failed install and on cancel.extraPrompttocommentTextfor getContent payloads.Written for commit 7d36e4e. Summary will update on new commits. Review in cubic