inkpal_bridge 1.4.3
inkpal_bridge: ^1.4.3 copied to clipboard
Debug-only in-app driver for AI coding agents. Read widget tree, tap, type, screenshot, monitor HTTP, capture runtime errors, replay sessions. Zero third-party deps, zero release-build overhead.
inkpal_bridge #
Give your AI a working set of hands inside your Flutter app.
inkpal_bridge is a debug-only in-app driver for AI coding agents (Claude
Code, Cursor, Windsurf, Codex, Copilot). It lets the agent read your
widget tree, tap buttons, fill forms, capture screenshots, monitor HTTP,
catch runtime errors, and replay sessions — without flutter test
setup, flutter_driver overhead, or third-party dependencies.
// Add to main(). That's the install. (Debug-only — release builds bypass.)
void main() => InkPalBridge.init(
serverUrl: 'ws://localhost:8765',
appRunner: () => runApp(const MyApp()),
);
Why this exists #
AI coding agents are great at writing Flutter code. They're terrible at
checking it actually works — because the agent can't see your running
app. inkpal_bridge closes that loop:
- agent writes a screen → agent runs it → agent sees the overflow → agent fixes it → agent verifies the screenshot
- without you tabbing into the simulator
- without
integration_testceremony - without leaving the editor
How it compares #
| inkpal_bridge | marionette_flutter | flutter_driver | |
|---|---|---|---|
| Install | 1 init() call |
1 init() call |
integration_test setup |
| Read widget tree | ✅ | ✅ | ✅ |
| Tap / scroll / type | ✅ | ✅ | ✅ |
| Screenshot | ✅ | ✅ | ✅ |
| Runtime error capture | ✅ three-layer + dedup | basic logs | ❌ |
| HTTP request monitor | ✅ with PII redaction | ❌ | ❌ |
| Network mocking + offline | ✅ | ❌ | ❌ |
| State time-travel (snapshot/diff/rewind) | ✅ | ❌ | ❌ |
| Recording → integration test | ✅ JSON or Dart | video only (.webm) | ❌ |
| App-registered VM extensions | ✅ | ❌ | ❌ |
| Third-party deps | 0 | 1 | (Flutter SDK) |
| Release-build overhead | 0 | 0 | n/a |
| Publisher | inkpal.ai | leancode.co | Flutter team |
| Pricing | Free 24h trial · paid after | Open-source | Open-source (Flutter SDK) |
marionette_flutter is excellent for headless test driving. inkpal_bridge is
for AI agents that also need to see what's wrong, fix it, and prove the
fix worked — error capture, network mocking, state diffing, and replay
ship in the same package.
Quick start #
1. Add to pubspec.yaml #
dependencies:
inkpal_bridge: ^1.4.2
2. Wrap your runApp #
import 'package:inkpal_bridge/inkpal_bridge.dart';
void main() {
InkPalBridge.init(
serverUrl: 'ws://localhost:8765',
appRunner: () => runApp(const MyApp()),
);
}
That's the minimum. The bridge boots only in debug mode — kReleaseMode
collapses init() to a direct appRunner() call with zero overhead,
zero memory allocation, no extension registration, no socket.
3. (Optional) Wire navigator + screenshot anchor #
For named-route navigation and faster screenshots, add the optional hooks
to MaterialApp:
MaterialApp(
navigatorObservers: [
if (InkPalBridge.instance != null)
InkPalBridge.instance!.navigatorObserver,
],
home: RepaintBoundary(
key: InkPalBridge.instance?.repaintBoundaryKey,
child: const HomeScreen(),
),
)
4. Start free #
24-hour full-access trial. Every capability unlocked. Email only, no card, no auto-charge. One command:
npx inkpal trial your@email.com
# → key arrives in your inbox + the terminal
flutter run --dart-define=INKPAL_LICENSE_KEY=ink_your_key_here
That's the install. Take it for a spin on a real project — debug a RenderFlex overflow, generate a screen from a sentence, audit your widget tree, replay a session as an integration test. Convince yourself it earns its keep.
If it does, continue at inkpal.ai. If it doesn't, your trial expires in 24 hours and that's the end — no email blasts, no payment dialog, no follow-up.
Offline / no key? The bridge still boots — you get widget-tree reads, route tracking, screenshots, and the health-check ping. Useful for a 30-second look-see before grabbing the trial key. Everything beyond inspection (interaction, error telemetry, recording, network mocking) needs a valid license. See Privacy + security.
What you can do #
Everything below is what your free 24-hour trial unlocks. There's no Pro/Studio gating — get the key, get the lot.
| Capability | What it solves |
|---|---|
| Read widget tree (semantics) | Agent answers "what's on screen right now?" |
| Tap, scroll, text input, gestures | Agent drives flows end-to-end |
| Three-layer error capture | FlutterError + PlatformDispatcher + zone errors with 2s dedupe |
| HTTP request monitor | Inspect requests live, with PII redaction on Authorization, Cookie, etc. |
| State time-travel | Snapshot → diff → rewind your app state to reproduce bugs |
| Recording → integration test | Turn a manual session into a permanent flutter test script |
| Network mocking | Per-URL response stubs, offline simulation, latency injection |
| FPS + jank metrics | Catch perf regressions before users do |
| App manifest | Structured map of your screens for the agent's LLM context |
| App-registered VM extensions | Expose proprietary operations to agents without republishing the bridge |
Custom widget recognition (walkerHooks) |
Agent recognises your BrandButton etc. — no per-callsite Semantics needed |
Offline mode (no key, no Railway) gives you widget-tree reads, route tracking, screenshots, and the health-check ping — useful for first-look local exploration but not the product surface.
Architecture #
AI Tool (Claude / Cursor / Windsurf / Codex / Copilot)
│
▼ MCP (stdio)
inkpal-mcp (npm proxy)
│
▼ WebSocket (primary) + VM Service extensions (fallback)
inkpal_bridge (in-app)
│
▼
Your running Flutter app
Commands flow in (tap, inspect, navigate). Telemetry flows out
(logs, errors, performance). The WebSocket channel is primary; 33 VM
service extensions under ext.flutter.inkpal.* serve as fallback when
no WebSocket server is running.
Router support #
Works with every major Flutter router. Pass an onNavigateToRoute
callback so the bridge can drive your navigation:
// go_router
InkPalBridge.init(
serverUrl: 'ws://localhost:8765',
appRunner: () => runApp(const MyApp()),
onNavigateToRoute: (route) async => router.go(route),
);
// GetX
InkPalBridge.init(
serverUrl: 'ws://localhost:8765',
appRunner: () => runApp(const MyApp()),
navigatorKey: Get.key,
onNavigateToRoute: (route) async => Get.toNamed(route),
);
// Beamer
InkPalBridge.init(
serverUrl: 'ws://localhost:8765',
appRunner: () => runApp(const MyApp()),
onNavigateToRoute: (route) async => beamerDelegate.beamToNamed(route),
);
// Standard Navigator — no callback needed
Custom widgets (walkerHooks) #
Proprietary design-system widgets (BrandButton, GlassCard, MyTextField)
don't expose standard Material/Cupertino semantics — the AI agent walking
the semantics tree sees a plain GestureDetector with no label.
Teach the bridge to recognise them with InkPalWalkerHooks:
InkPalBridge.init(
serverUrl: 'ws://localhost:8765',
appRunner: () => runApp(const MyApp()),
walkerHooks: InkPalWalkerHooks(
isInteractiveWidget: (widget) =>
widget is BrandButton || widget is GlassCard,
extractTextFrom: (widget) {
if (widget is BrandButton) return widget.label;
if (widget is GlassCard) return widget.title;
return null;
},
),
);
The agent can then say "tap the Save button" and the bridge resolves it
correctly, with no need to wrap every callsite in a Semantics(label:).
A working demo lives in example/lib/main.dart —
the Custom widgets zone shows three BrandButtons being driven by an
agent without any per-callsite semantics annotation.
App-state context #
Expose your app's runtime state to the agent:
InkPalBridge.init(
serverUrl: 'ws://localhost:8765',
appRunner: () => runApp(const MyApp()),
globalStateProvider: () async => {
'user': {'name': currentUser.name, 'plan': currentUser.plan},
'cart': {'items': cart.length, 'total': cart.total},
'theme': isDarkMode ? 'dark' : 'light',
},
);
The agent reads this via state_get and uses it as context for whatever
it's about to do.
App-registered VM extensions (1.4.0+) #
Expose app-specific operations to AI agents without republishing the bridge — useful for proprietary domain operations (cart checkout, auth state mutation, feature flag toggles):
InkPalAppExtensions.register(
name: 'add_to_cart',
description: 'Add a product to the cart by id',
handler: (args) async {
await cart.add(args['productId'] as String);
return {'success': true, 'cart_size': cart.length};
},
);
The agent discovers + invokes these via inkpal_list_app_extensions
and inkpal_call_app_extension.
Touch visualization #
Optional overlay so you can see what the AI is doing:
TouchVisualizerOverlay(
controller: InkPalBridge.instance!.touchVisualizer,
child: const MyApp(),
)
Logging #
Built-in structured logger, batched (500ms) and streamed to the agent:
InkPalBridge.instance?.logger.log('User tapped checkout');
InkPalBridge.instance?.logger.warning('Payment timeout');
InkPalBridge.instance?.logger.error('Failed to load', error: e);
Errors are sent immediately (no batching). Severity-tagged so the agent can filter.
Privacy + security #
- Debug mode only. Release builds bypass everything — bridge is
null, no socket, no extensions, no allocation. - Local-first. WebSocket binds to
localhost. Nothing leaves your machine unless you wire it explicitly. - HTTP redaction. The HTTP monitor redacts
Authorization,X-Api-Key,Cookie, and other sensitive header patterns before surfacing requests. - License validation is signed. HMAC-SHA256 grants with 24h TTL and 7-day grace; the bridge can run offline against a cached grant.
- Open source. MIT-licensed. Inspect the wire. The intelligence layer (rule packs, error patterns, design system extraction) lives on the InkPal API behind license validation — that's the moat. The bridge is the runtime contract, free to read.
Requirements #
- Flutter ≥ 3.10.0
- Dart ≥ 3.0.0
- Debug mode (zero overhead in release)
Supported platforms: Android · iOS · macOS · Linux · Windows.
Documentation #
- Install + first-success guide
- Quickstart (5-minute walkthrough)
- Demo — see the bridge in action
- Support — Discord, GitHub, email
- Pricing — when you're ready
License #
MIT — see LICENSE.