inkpal_bridge 1.4.1
inkpal_bridge: ^1.4.1 copied to clipboard
Flutter MCP bridge for AI agents — runtime error capture, HTTP monitor, widget tree inspection, tap/type/screenshot. 33+ VM service extensions, zero deps.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:inkpal_bridge/inkpal_bridge.dart';
void main() {
InkPalBridge.init(
serverUrl: 'ws://localhost:8765',
appRunner: () => runApp(const TestApp()),
licenseKey: const String.fromEnvironment('INKPAL_LICENSE_KEY'),
knownRoutes: ['/home', '/search', '/profile'],
routeDescriptions: {
'/home': 'Main feed with counters and buttons',
'/search': 'Search screen with text field',
'/profile': 'User profile page',
},
);
}
class TestApp extends StatelessWidget {
const TestApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'InkPal Bridge Test',
navigatorObservers: [
if (InkPalBridge.instance != null)
InkPalBridge.instance!.navigatorObserver,
],
home: RepaintBoundary(
key: InkPalBridge.instance?.repaintBoundaryKey,
child: const HomeScreen(),
),
routes: {
'/search': (_) => const SearchScreen(),
'/profile': (_) => const ProfileScreen(),
},
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Bridge Test')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Counter: $_counter', style: const TextStyle(fontSize: 24)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
setState(() => _counter++);
InkPalBridge.instance?.logger.log('Counter incremented to $_counter');
},
child: const Text('Increment'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () => Navigator.pushNamed(context, '/search'),
child: const Text('Go to Search'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () => throw Exception('Test error from bridge'),
child: const Text('Trigger Error'),
),
],
),
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
onTap: (i) {
final routes = ['/', '/search', '/profile'];
if (i > 0) Navigator.pushNamed(context, routes[i]);
},
),
);
}
}
class SearchScreen extends StatelessWidget {
const SearchScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Search')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
decoration: const InputDecoration(
hintText: 'Search...',
prefixIcon: Icon(Icons.search),
),
onChanged: (v) =>
InkPalBridge.instance?.logger.debug('Search: $v'),
),
const SizedBox(height: 16),
const Text('Type to search'),
],
),
),
);
}
}
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: const Center(child: Text('User Profile')),
);
}
}