inkpal_bridge 4.0.0
inkpal_bridge: ^4.0.0 copied to clipboard
Let AI agents inspect, debug, and control your running Flutter app — read widgets, tap, screenshot, audit. Zero deps. Debug-only. Free for everyone.
inkpal_bridge #
Let your AI inspect, debug, and control your running Flutter app.
Works with Claude Code, Cursor, Windsurf, Codex CLI, and Copilot in VS Code.
60-second setup #
# pubspec.yaml
dependencies:
inkpal_bridge: ^4.0.0
// lib/main.dart
import 'package:inkpal_bridge/inkpal_bridge.dart';
void main() => inkpalRunApp(const MyApp());
flutter run
That's it. No license key required, no signup, no flags. The bridge starts itself and prints:
┌──────────────────────────────────────────────────────────────┐
│ InkPal Bridge active · ws://localhost:8765 │
│ │
│ Connect an AI assistant to see + debug your running app. │
│ │
│ npx inkpal start (one-line setup for Claude/Cursor/…) │
│ inkpal.ai/setup (manual MCP config + per-IDE guides) │
│ │
│ Free for everyone · no API key, no signup │
└──────────────────────────────────────────────────────────────┘
The bridge is already watching for errors, HTTP traffic, navigation events,
and widget-tree changes. It runs entirely debug-only — release builds
collapse inkpalRunApp to a plain runApp with zero overhead.
Connect an AI assistant #
Run this in another terminal:
npx inkpal start
That installs the InkPal MCP server into every editor it can find (Claude Code, Cursor, Windsurf, Codex CLI, Copilot). Restart your editor, then ask your AI assistant any of these:
"What does this app look like right now?"
"Audit my UI for layout issues."
"Trace the navigation from / to /settings."
"Show me where this error came from."
"Tap the save button and screenshot the result."
The AI uses the bridge to look at, drive, and reason about your real running app — not a static analysis of your code.
What it looks like in your editor #
You: "There's a layout bug on the settings screen. Find it and fix it."
Agent: → navigates to /settings
→ reads the widget tree
→ captures the runtime error
→ screenshots the broken state
→ applies the fix in lib/screens/settings.dart
→ re-runs the screen
→ screenshots the fixed state
"Fixed: Row was missing Expanded around the long Text. Diff committed."
That's the loop. Real Flutter apps. Real fixes. Visible proof.
What you get #
Read your app #
Every widget, the active route, form values, error context, log history.
No more "tell me what you see in the simulator." The agent just looks — or
grabs route + interactive elements + recent logs + errors + live app-state in
a single observe() snapshot, so it can ground its next action in one call.
Drive your app #
Tap, scroll, type, navigate, capture a screenshot, replay a flow. The agent can run end-to-end paths through your real app the same way a human QA would, and verify each step worked.
Catch what went wrong #
When something throws, the bridge captures the error with the widget tree, navigation stack, and recent logs already attached. The agent gets enough context to find the cause on the first attempt instead of the fifth.
Free for everyone #
inkpal_bridge is free for personal and commercial use. No signup, no
API key, no tier — the bridge runs unconditionally in debug mode and
collapses to a plain runApp in release.
Migrating from 3.x #
No source changes required. The 3.x licenseKey: and apiUrl:
parameters on inkpalRunApp / InkPalBridge.init are accepted as
deprecated no-ops in 4.0 — your existing code keeps compiling, you'll
see an analyzer hint, and a one-line debug log:
[InkPal] DEPRECATED: inkpalRunApp(licenseKey: ...) is ignored —
inkpal_bridge is free for everyone in 4.0. Remove the parameter.
To silence both, delete the two parameters:
// 3.x
inkpalRunApp(
MyApp(),
licenseKey: const String.fromEnvironment('INKPAL_LICENSE_KEY'),
apiUrl: 'https://mcp.inkpal.ai',
);
// 4.0
inkpalRunApp(MyApp());
If you launched with flutter run --dart-define=INKPAL_LICENSE_KEY=...,
drop the --dart-define — the env var is no longer read. The deprecated
parameters will be removed in 5.0.
Custom widgets (walkerHooks) #
Have proprietary design-system widgets (BrandButton, GlassCard) without
standard Material semantics? Teach the bridge to recognise them:
inkpalRunApp(
const MyApp(),
walkerHooks: InkPalWalkerHooks(
isInteractiveWidget: (w) => w is BrandButton,
extractTextFrom: (w) => w is BrandButton ? w.label : null,
),
);
The agent can then say "tap the Save button" and the bridge resolves it
correctly — no need to wrap every callsite in Semantics(label:).
A working demo lives in example/lib/main.dart.
Router support #
Works with every major Flutter router.
go_router — just pass your GoRouter instance. The bridge drives
navigation and tracks the current route automatically, including
imperative context.push(...), with no NavigatorObserver wiring:
final router = GoRouter(routes: [...]);
inkpalRunApp(
MyApp(router: router),
router: router, // drives navigation + auto-tracks the route for observe()
);
Other routers — pass onNavigateToRoute so the bridge can drive named
navigation:
// GetX
onNavigateToRoute: (route) async => Get.toNamed(route),
// Beamer
onNavigateToRoute: (route) async => beamerDelegate.beamToNamed(route),
Standard Navigator works without any callback.
App-state context #
Expose your app's runtime state so the agent has more to reason about:
inkpalRunApp(
const MyApp(),
globalStateProvider: () async => {
'user': {'plan': currentUser.plan},
'cart': {'items': cart.length, 'total': cart.total},
},
);
App extensions (seed state + custom ops) #
Expose app-specific operations the agent can invoke directly — reset/seed
data, flip a feature flag, jump onboarding — without shipping a new bridge
version. Register handlers and the MCP surfaces them through
inkpal_list_app_extensions / inkpal_call_app_extension:
InkPalAppExtensions.register(
name: 'seed',
description: 'Wipe and reseed the sample dataset.',
handler: (params) async {
await db.reseed();
return {'reseeded': true};
},
);
The agent can now set up any starting state in one call instead of clicking through the UI — ideal for deterministic, repeatable runs.
Architecture #
Your AI assistant ⇄ inkpal_bridge ⇄ your running Flutter app
The bridge runs entirely in debug mode. In release builds, inkpalRunApp
collapses to a direct runApp call — zero overhead, zero memory
allocation, no socket, no extension registration. Your release builds
ship as if the bridge wasn't there.
Privacy + security #
- Debug-only. Release builds bypass everything.
- Local-first. Communication binds to localhost. Nothing leaves your machine without explicit configuration.
- Sensitive headers redacted.
Authorization,Cookie,X-Api-Key, and similar patterns are stripped before any HTTP request is surfaced to the agent. - Open-source. MIT-licensed.
Advanced configuration #
For release-mode shipping, custom server URLs, or fine-grained control over
the bridge's subsystems, use InkPalBridge.init directly:
void main() {
InkPalBridge.init(
serverUrl: 'ws://localhost:8765',
appRunner: () => runApp(const MyApp()),
);
}
See API docs for the full surface.
Documentation #
- Get started — sign up + run in 60 seconds
- Quickstart — first agent-driven workflow
- Demo — see it in action
- Support — Discord, GitHub, email
Requirements #
- Flutter ≥ 3.10
- Dart ≥ 3.0