{"id":50306218,"url":"https://github.com/moinsen-dev/agentic_rc_cli","last_synced_at":"2026-05-28T16:30:53.510Z","repository":{"id":359363495,"uuid":"1245562814","full_name":"moinsen-dev/agentic_rc_cli","owner":"moinsen-dev","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-21T14:53:18.000Z","size":442,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"develop","last_synced_at":"2026-05-21T23:00:50.951Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moinsen-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-21T10:39:34.000Z","updated_at":"2026-05-21T14:59:45.000Z","dependencies_parsed_at":"2026-05-21T23:01:00.299Z","dependency_job_id":null,"html_url":"https://github.com/moinsen-dev/agentic_rc_cli","commit_stats":null,"previous_names":["moinsen-dev/agentic_rc_cli"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/moinsen-dev/agentic_rc_cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fagentic_rc_cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fagentic_rc_cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fagentic_rc_cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fagentic_rc_cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moinsen-dev","download_url":"https://codeload.github.com/moinsen-dev/agentic_rc_cli/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fagentic_rc_cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33617718,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-28T16:30:52.419Z","updated_at":"2026-05-28T16:30:53.503Z","avatar_url":"https://github.com/moinsen-dev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# agentic-rc-mcp\n\n\u003e **An MCP server for non-invasive remote control + structured observability\n\u003e of long-running interactive local processes.** Spawn `flutter run`,\n\u003e `npm run dev`, REPLs, TUIs — drive them with keystrokes, read the rendered\n\u003e screen, wait for patterns, capture errors and logs as **structured events**.\n\u003e No human in the loop pressing `r` or copy-pasting log excerpts.\n\u003e No code changes required in the controlled program.\n\n[**14 MCP tools**](#tool-reference) · 25 unit tests · 3 live-driven demo scripts ·\nClaude Code skill bundled · v0.7.0 focused on its strengths.\n\n---\n\n## The strict scope of this tool\n\n`agentic-rc-mcp` is **not** an agentic UI testing framework. We tried in\nv0.6 — gestures, widget tree introspection, text input, screenshots —\nand concluded that **[Marionette MCP](https://pub.dev/packages/marionette_mcp)\ndoes that job better** because it runs INSIDE the Flutter app with a tiny\nbinding and gets the framework's real `GestureBinding`, hit-test pipeline,\ncustom-widget configuration, and multi-touch. We removed our gesture /\ninspector / text-input tools in v0.7 to stay focused on what's genuinely\nours.\n\n| If you need… | Use… |\n|---|---|\n| **Tap / scroll / text input / screenshots in a running Flutter app** | [Marionette MCP](https://pub.dev/packages/marionette_mcp) (requires `marionette_flutter` package + one binding line in `main.dart`) |\n| **Drive any interactive CLI process (start, send keys, read screen, wait, stop)** | This tool ✓ |\n| **Capture Flutter / Dart exceptions as structured events instead of grepping** | This tool ✓ |\n| **Auto-discover the Dart VM Service URL from `flutter run`** | This tool ✓ |\n| **Hot-reload Flutter and get a typed `{success, libraries_reloaded, duration_ms}` result** | This tool ✓ |\n| **Read-only Dart expression eval against the live app** | This tool ✓ |\n| **Pixel-level clicks in non-Flutter GUIs (Electron, native Cocoa, browser)** | [Peekaboo](https://github.com/steipete/Peekaboo) or [`chrome-devtools-mcp`](https://github.com/cnove/chrome-devtools-mcp) |\n\n## The problem we DO solve\n\nWhen you tell Claude Code \"run my app and watch for errors\", today\nwithout help it gets stuck:\n\n1. It spawns the process in the background. ✅\n2. It tails the log a few times. ✅\n3. The log stops scrolling. **It can't tell if the app is *ready* or\n   *deadlocked*.**\n4. To trigger a quit / hot-reload it has to press `q` / `r` in the\n   terminal. **It can't.**\n5. Something crashes. The full exception is somewhere in 5000 lines of\n   scroll. **It has to grep, guess where the error block ends, hope it\n   didn't miss anything.**\n6. The Dart VM Service URL is buried in the output. **It has to read it\n   manually and paste it.**\n\n`agentic-rc-mcp` removes every one of those blockers — for **any**\ninteractive program, without requiring any modification to that program.\n\n## What you get — two layers\n\n| Layer | What it does | Tools |\n|---|---|---|\n| **1. PTY remote control** | Spawn programs in a real pseudo-terminal. Send keys (`\u003cEnter\u003e`, `\u003cTab\u003e`, `\u003cC-c\u003e`, …). Read the rendered screen — including TUIs like Flutter, vim, top. Wait for patterns with timeout. Resize PTY. Clean shutdown via signals. | 8 |\n| **2. Flutter / Dart-VM observability** | Auto-detect the VM-service WebSocket URL from `flutter run`'s output. Open a programmatic connection. Subscribe to Stdout / Stderr / Logging / Extension / Debug streams — exceptions arrive as **structured events**. Trigger hot-reload with a typed result. Read-only eval Dart in the live app. | 6 |\n\nBoth layers are non-invasive: the controlled program doesn't have to do\nanything special to be driven. Spawn it the way you'd spawn it from a\nterminal, and the MCP server takes it from there.\n\n## Architecture\n\n```\n+------------------+   stdio    +───────── agentic-rc-mcp ──────────────+\n|  Claude Code     | \u003c-------\u003e |                                         |\n|  (MCP client)    |  JSON-RPC |   ┌─ SessionManager ─────────────────┐ |\n+------------------+           |   │   id → Session                   │ |\n                               |   └──────────┬──────────────────────┘ |\n                               |              │ owns                    |\n                               |   ┌─ Session ▼────────────────────────┐|\n                               |   │  ┌─── PTY layer ───┐               │|\n                               |   │  │ node-pty \u003c══\u003e   │ ──→ child     │|\n                               |   │  │ @xterm/headless │   process     │|\n                               |   │  │ + raw ring buf  │   (flutter,   │|\n                               |   │  └────────┬────────┘   vite, …)    │|\n                               |   │           │ feeds                   │|\n                               |   │  ┌── Endpoint sniffer ──────────┐  │|\n                               |   │  │ regex over PTY output →      │  │|\n                               |   │  │ ws / http / devtools URL     │  │|\n                               |   │  └──────────┬───────────────────┘  │|\n                               |   │             │ unblocks              │|\n                               |   │  ┌── VmServiceClient ── WS ──► Dart VM\n                               |   │  │  getVM, evaluate (read-only), │  │|\n                               |   │  │  streamListen(Stderr,         │  │|\n                               |   │  │  Extension, Debug, Logging)   │  │|\n                               |   │  └──────────┬────────────────────┘ │|\n                               |   │             │ wraps                  │|\n                               |   │  ┌── FlutterService ──────────────┐ │|\n                               |   │  │  error/log ring buffers,        │ │|\n                               |   │  │  hot-reload, eval, library      │ │|\n                               |   │  │  probe for eval scope          │ │|\n                               |   │  └────────────────────────────────┘ │|\n                               |   └────────────────────────────────────┘|\n                               +────────────────────────────────────────+\n```\n\n- **PTY:** real pseudo-terminal via `node-pty`, so the child program thinks\n  it's interactive (`isatty(0)==1`).\n- **Screen rendering:** `@xterm/headless` runs xterm.js without a DOM,\n  applying ANSI/curses sequences and exposing the rendered viewport — so\n  TUIs like Flutter, vim, top render correctly.\n- **Endpoint sniffer:** parses PTY output for the four URL forms Flutter\n  emits per device (Chrome / macOS / iOS / Android). When the WS URL isn't\n  printed explicitly it's synthesised from the DevTools URL's `?uri=`\n  query param or the HTTP URL.\n- **VM-service client:** JSON-RPC 2.0 over WebSocket. Read-only eval +\n  stream subscriptions only. For agentic UI interaction use Marionette\n  MCP instead.\n\n## Tool reference\n\n### 1. Generic PTY tools (any program)\n\n| Tool | Does |\n| --- | --- |\n| `rc_start`       | Spawn a command inside a real PTY. Returns `session_id`. |\n| `rc_send_keys`   | Write input. Supports `\u003cEnter\u003e`, `\u003cTab\u003e`, `\u003cEsc\u003e`, `\u003cC-c\u003e`, `\u003cC-d\u003e`, arrows, F-keys, `\u003cM-x\u003e`. Plain text passes through. |\n| `rc_read_screen` | Read the **rendered** viewport. Modes: `screen` / `scrollback` / `tail`. |\n| `rc_read_stream` | Read raw bytes since a cursor (for log-style apps). |\n| `rc_wait_for`    | Block (with timeout) until a pattern appears. Literal substring or `/regex/flags`. |\n| `rc_status`      | Status of one or all sessions: pid, state, exit_code, bytes I/O, Flutter endpoints once detected. |\n| `rc_stop`        | Terminate a session. SIGTERM → 2 s grace → SIGKILL. |\n| `rc_resize`      | Change cols/rows of a running PTY. |\n\n### 2. Flutter / Dart-VM observability tools\n\n| Tool | Does |\n| --- | --- |\n| `rc_flutter_endpoints`    | Returns sniffed WS / HTTP / DevTools URLs (auto-synthesised on macOS desktop / Flutter Web where the explicit WS line is absent). |\n| `rc_flutter_connect`      | Opens the VM-service WebSocket + subscribes to Stdout / Stderr / Logging / Extension / Debug. Idempotent. Probes for a library scope where `Element` resolves (handles the Flutter Web `web_entrypoint.dart` quirk). |\n| `rc_flutter_drain_errors` | Returns + clears **structured** exception events. Use this instead of grepping the console. |\n| `rc_flutter_drain_logs`   | Returns + clears structured log events. |\n| `rc_flutter_hot_reload`   | Sends `r` over PTY (Flutter's own pipeline), parses the report into `{success, libraries_reloaded, duration_ms}` or `{success:false, reason, console_excerpt}`. |\n| `rc_flutter_eval`         | Read-only Dart expression eval against the live app. Surfaces `eval_kind` + `eval_error` on failure so compile / runtime errors are diagnosable. For driving UI interactions, use Marionette MCP. |\n\n## Install\n\nRequires Node ≥ 20.\n\n```bash\ngit clone \u003cthis-repo\u003e\ncd agentic_rc_cli\nnpm install        # postinstall fixes node-pty's spawn-helper perms on macOS\nnpm run build\nnpm link            # makes `agentic-rc-mcp` available globally\n```\n\n\u003e **Heads-up:** npm 10 occasionally extracts `node-pty`'s `spawn-helper`\n\u003e prebuilt binary without the executable bit, which manifests at runtime as\n\u003e `posix_spawnp failed`. The included postinstall script\n\u003e ([`scripts/fix-node-pty-permissions.js`](scripts/fix-node-pty-permissions.js))\n\u003e chmods it back. If you ever see that error after a clean install,\n\u003e re-run `npm install`.\n\n## Wire it into Claude Code\n\nDrop `.mcp.json` next to the project you want the agent to drive (or merge\ninto an existing one):\n\n```json\n{\n  \"mcpServers\": {\n    \"agentic-rc\": {\n      \"command\": \"agentic-rc-mcp\"\n    }\n  }\n}\n```\n\nRestart Claude Code. The tools appear as `mcp__agentic-rc__rc_start`,\n`mcp__agentic-rc__rc_flutter_drain_errors`, etc. See\n[`.mcp.json.example`](.mcp.json.example) for variants (direct dist path, dev\nmode via `tsx`).\n\n## Install the bundled Claude Code skill\n\nThis repo ships a Claude Code skill at\n[`.claude/skills/agentic-rc/SKILL.md`](.claude/skills/agentic-rc/SKILL.md)\nthat teaches Claude **when** to reach for each tool and **when to redirect\nto Marionette MCP** for agentic UI testing.\n\n- **Project-local:** the skill is auto-loaded when you open Claude Code in\n  this repo's directory.\n- **Global:** copy it to your global skills dir so it's available in *every*\n  project:\n\n  ```bash\n  npm run install:skill\n  # → ~/.claude/skills/agentic-rc/SKILL.md\n  ```\n\n  Idempotent — re-run after each `git pull`.\n\n## Example: drive `flutter run` and catch its exceptions\n\n```jsonc\n// 1. Spawn the app — same as `flutter run` from the terminal.\nrc_start { command: \"flutter\", args: [\"run\", \"-d\", \"macos\"],\n           cwd: \"/path/to/my-flutter-app\" }\n// → { session_id: \"8fa45718\", pid: 79314 }\n\n// 2. Open the Dart VM Service — endpoint auto-sniffed from PTY output.\n//    No copy-paste of debug URLs.\nrc_flutter_connect { session_id: \"8fa45718\", wait_ms: 180000 }\n// → { connected: true,\n//     ws_url: \"ws://127.0.0.1:51658/hSQyXpnxQEo=/ws\",\n//     main_isolate_id: \"isolates/6257046507251003\" }\n\n// 3. Edit a Dart file (regular Edit / Write tool — not part of this MCP),\n//    then trigger hot reload + verify nothing broke.\nrc_flutter_hot_reload { session_id: \"8fa45718\" }\n// → { success: true, libraries_reloaded: 1, duration_ms: 135 }\n\nrc_flutter_drain_errors { session_id: \"8fa45718\" }\n// → { count: 1, errors: [\n//     { timestamp: \"2026-…\", stream: \"Extension\",\n//       message: \"EXCEPTION CAUGHT BY WIDGETS LIBRARY … main.dart:72:5 …\" } ] }\n\n// 4. (optional) Read-only inspection of live state via Dart eval.\nrc_flutter_eval { session_id: \"8fa45718\",\n                  expression: \"WidgetsBinding.instance.framesEnabled\" }\n// → { kind: \"Instance\", valueAsString: \"true\",\n//     eval_target_lib: \"\u003crootLib\u003e\" }\n\n// 5. Clean shutdown — send 'q' over PTY, or signal.\nrc_send_keys { session_id: \"8fa45718\", keys: \"q\" }\n//   …or:\nrc_stop { session_id: \"8fa45718\", wait_ms: 3000, remove: true }\n```\n\nFor **interacting** with the running UI (tap, scroll, text input), pivot to\nMarionette MCP — see [its quick-start](https://pub.dev/packages/marionette_mcp).\nBoth MCPs coexist happily in one `.mcp.json`.\n\n## Named-key cheat sheet (`rc_send_keys`)\n\n| Token                              | Bytes sent           |\n| ---------------------------------- | -------------------- |\n| `\u003cEnter\u003e` / `\u003cReturn\u003e`             | `\\r`                 |\n| `\u003cTab\u003e`                            | `\\t`                 |\n| `\u003cEsc\u003e` / `\u003cEscape\u003e`               | `\\x1b`               |\n| `\u003cSpace\u003e`                          | ` `                  |\n| `\u003cBackspace\u003e` / `\u003cBS\u003e`             | `\\x7f`               |\n| `\u003cDelete\u003e`                         | `\\x1b[3~`            |\n| `\u003cUp\u003e` `\u003cDown\u003e` `\u003cLeft\u003e` `\u003cRight\u003e` | `\\x1b[A..D`          |\n| `\u003cHome\u003e` / `\u003cEnd\u003e`                 | `\\x1b[H` / `\\x1b[F`  |\n| `\u003cPageUp\u003e` / `\u003cPageDown\u003e`          | `\\x1b[5~` / `\\x1b[6~` |\n| `\u003cF1\u003e`..`\u003cF12\u003e`                    | xterm sequences       |\n| `\u003cC-c\u003e` / `\u003cCtrl-c\u003e` (any letter)  | `\\x03`               |\n| `\u003cM-x\u003e` / `\u003cAlt-x\u003e` (any letter)   | `\\x1b` + `x`         |\n\nPlain characters pass through verbatim. Set `\"raw\": true` to skip the parser\nand send literal `\u003c` / `\u003e`.\n\n## When to use which read tool\n\n- **`rc_read_screen` with `mode: \"screen\"`** — for any TUI that redraws\n  (Flutter, vim, top, `npm run dev` with spinners). You get what the user\n  would *see* on the terminal right now.\n- **`rc_read_screen` with `mode: \"scrollback\"` or `\"tail\"`** — for the\n  history of what was rendered, post-curses processing.\n- **`rc_read_stream`** — for pure log-style apps (no cursor tricks) where\n  you want every byte in order, with a cursor for incremental reads.\n- **`rc_flutter_drain_errors`** — once a session has VM-service connected\n  this is **always preferred over PTY grepping**. Structured events with\n  stream origin, timestamp, message, and the raw VM-service payload.\n\n## Develop\n\n```bash\nnpm test               # vitest — 25 tests (keys, sessions, endpoints)\nnpm run typecheck      # strict tsc --noEmit\nnpm run build          # emit dist/\n\n# Live end-to-end demo scripts (each drives a fresh MCP server over stdio):\nnpm run smoke                                # 14-tool list + generic PTY happy path\nnode scripts/flutter-drive.mjs               # spawn flutter, hot-reload, quit\nnode scripts/flutter-error-detect.mjs        # detect runtime exceptions via PTY\nnode scripts/flutter-vm-agentic-loop.mjs     # full VM-service feature tour\n```\n\n## What this is not\n\n- **Not an agentic UI testing framework.** v0.6 tried (taps, gestures,\n  text input, widget tree); v0.7 removed those tools after a real-world\n  comparison with [Marionette MCP](https://pub.dev/packages/marionette_mcp)\n  showed they do it better with an in-app binding. We complement\n  Marionette — they handle interaction inside the app, we handle the\n  outside-the-app remote control + observability.\n- **Not network-remote.** Stdio only — MCP client and controlled processes\n  run on the same machine. (Architecture is ready for it; just no transport\n  written.)\n- **Not multi-user.** Single process, single session registry, no auth.\n- **No persistence.** Killing the MCP server kills every child it started.\n- **No Windows yet.** node-pty supports ConPTY; untested with this code.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoinsen-dev%2Fagentic_rc_cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoinsen-dev%2Fagentic_rc_cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoinsen-dev%2Fagentic_rc_cli/lists"}