@bdayadev/flutter-ultra-mcp 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +14 -0
- package/.mcp.json +67 -0
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +108 -0
- package/LICENSE +201 -0
- package/README.md +77 -0
- package/SECURITY.md +19 -0
- package/dart/ultra_flutter/CHANGELOG.md +34 -0
- package/dart/ultra_flutter/EXTENSIONS.md +61 -0
- package/dart/ultra_flutter/README.md +109 -0
- package/dart/ultra_flutter/pubspec.yaml +37 -0
- package/dart/ultra_flutter_devtools/README.md +7 -0
- package/dart/ultra_flutter_devtools/pubspec.yaml +27 -0
- package/docs/UPSTREAM-PATROL-PRS.md +5 -0
- package/docs/UPSTREAM-SENTRY-PR.md +62 -0
- package/docs/discovery-empirics.md +435 -0
- package/examples/counter-app/README.md +24 -0
- package/examples/counter-app/pubspec.yaml +23 -0
- package/examples/oidc-app/README.md +48 -0
- package/examples/oidc-app/pubspec.yaml +24 -0
- package/package.json +82 -0
- package/packages/flutter-ultra-browser/README.md +29 -0
- package/packages/flutter-ultra-browser/package.json +39 -0
- package/packages/flutter-ultra-build/README.md +60 -0
- package/packages/flutter-ultra-build/package.json +38 -0
- package/packages/flutter-ultra-devtools/README.md +7 -0
- package/packages/flutter-ultra-devtools/package.json +36 -0
- package/packages/flutter-ultra-gesture/README.md +51 -0
- package/packages/flutter-ultra-gesture/package.json +58 -0
- package/packages/flutter-ultra-native-desktop/README.md +131 -0
- package/packages/flutter-ultra-native-desktop/package.json +81 -0
- package/packages/flutter-ultra-native-mobile/README.md +103 -0
- package/packages/flutter-ultra-native-mobile/package.json +72 -0
- package/packages/flutter-ultra-patrol/README.md +40 -0
- package/packages/flutter-ultra-patrol/package.json +38 -0
- package/packages/flutter-ultra-runtime/README.md +63 -0
- package/packages/flutter-ultra-runtime/package.json +69 -0
- package/shared/contracts/package.json +31 -0
- package/shared/device-router/README.md +51 -0
- package/shared/device-router/package.json +62 -0
- package/shared/keyring/README.md +7 -0
- package/shared/keyring/package.json +24 -0
- package/shared/mcp-runtime/README.md +116 -0
- package/shared/mcp-runtime/package.json +58 -0
- package/shared/state-store/README.md +66 -0
- package/shared/state-store/package.json +60 -0
- package/shared/vm-service-client/README.md +135 -0
- package/shared/vm-service-client/package.json +62 -0
- package/skills/_internal-on-tool-failure/SKILL.md +13 -0
- package/skills/_internal-session-bootstrap/SKILL.md +18 -0
- package/skills/debug/SKILL.md +20 -0
- package/skills/devtools/SKILL.md +21 -0
- package/skills/drive/SKILL.md +20 -0
- package/skills/scaffold/SKILL.md +21 -0
- package/skills/setup/SKILL.md +26 -0
- package/skills/test/SKILL.md +19 -0
- package/skills/tour/SKILL.md +22 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# `ext.flutter.ultra.*` VM service extension catalogue
|
|
2
|
+
|
|
3
|
+
Every extension below is registered by `UltraFlutterBinding.initServiceExtensions()` when the binding is active. All extensions return a JSON envelope with `type: '_extensionType'`, `method`, and `status: 'Success'` on success; errors map to standard `developer.ServiceExtensionResponse` codes (`extensionErrorMin..extensionErrorMin+16` for typed errors, `invalidParams` for bad input).
|
|
4
|
+
|
|
5
|
+
## Parity matrix vs marionette
|
|
6
|
+
|
|
7
|
+
| `ext.flutter.ultra.*` | marionette equivalent | Notes |
|
|
8
|
+
| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------ |
|
|
9
|
+
| `getVersion` | `marionette.getVersion` | Returns `{version: <semver>}` from `src/version.g.dart`. |
|
|
10
|
+
| `interactiveElements` | `marionette.interactiveElements` | Returns `{elements: [...]}` from `ElementTreeFinder`. |
|
|
11
|
+
| `tap` | `marionette.tap` | Body: `WidgetMatcher` JSON (`key`/`text`/`type`/`x`+`y`/`focused`). |
|
|
12
|
+
| `doubleTap` | `marionette.doubleTap` | Optional `delay` (ms, positive). Default 100ms. |
|
|
13
|
+
| `longPress` | `marionette.longPress` | Optional `duration` (ms). Default 600ms. |
|
|
14
|
+
| `enterText` | `marionette.enterText` | Requires `input` plus a matcher. |
|
|
15
|
+
| `clearText` | _(new — enhancement)_ | Convenience wrapper around `enterText('')`. Saves agents two round-trips. |
|
|
16
|
+
| `swipe` | `marionette.swipe` | Either coord-based (`startX/Y` + `endX/Y`) or element + `direction` + `distance`. |
|
|
17
|
+
| `pinchZoom` | `marionette.pinchZoom` | Required `scale`, optional `startDistance` (default 200). |
|
|
18
|
+
| `scrollTo` | `marionette.scrollTo` | Scrolls until the matched widget is visible. |
|
|
19
|
+
| `getLogs` | `marionette.getLogs` | Requires a `LogCollector` in `UltraConfiguration`. |
|
|
20
|
+
| `takeScreenshots` | `marionette.takeScreenshots` | Returns one screenshot per `RenderView`. |
|
|
21
|
+
| `startScreencast` | `marionette.startScreencast` | Optional `maxWidth`, `maxHeight`, `wsPort`. |
|
|
22
|
+
| `stopScreencast` | `marionette.stopScreencast` | Idempotent. |
|
|
23
|
+
| `pressBackButton` | `marionette.pressBackButton` | Returns `didPop`. |
|
|
24
|
+
| `listExtensions` | `marionette.listExtensions` | Returns user-registered custom extensions (registered via `registerUltraExtension`). |
|
|
25
|
+
|
|
26
|
+
15 extensions in total: 14 ported + 1 enhancement (`clearText`).
|
|
27
|
+
|
|
28
|
+
## Adding a custom extension
|
|
29
|
+
|
|
30
|
+
```dart
|
|
31
|
+
import 'package:ultra_flutter/ultra_flutter.dart';
|
|
32
|
+
|
|
33
|
+
void registerMyExtensions() {
|
|
34
|
+
registerUltraExtension(
|
|
35
|
+
name: 'myApp.fetchProfile', // becomes `ext.flutter.myApp.fetchProfile`
|
|
36
|
+
description: 'Returns the currently-signed-in profile snapshot.',
|
|
37
|
+
callback: (params) async {
|
|
38
|
+
final id = params['id'];
|
|
39
|
+
if (id == null) {
|
|
40
|
+
return UltraExtensionResult.invalidParams('Missing "id"');
|
|
41
|
+
}
|
|
42
|
+
// ...
|
|
43
|
+
return UltraExtensionResult.success({'profile': {/* ... */}});
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Call `registerMyExtensions()` after `AppBinding.ensureInitialized()` and before `runApp()`. Custom extensions show up in the `listExtensions` response so the MCP server can advertise them to the LLM.
|
|
50
|
+
|
|
51
|
+
## Why `ext.flutter.ultra.*` and not `ext.ultra.*`
|
|
52
|
+
|
|
53
|
+
Flutter's DevTools and `package:vm_service` only allow registration under `ext.flutter.*` — the `flutter.` prefix is hard-coded by `developer.registerExtension` validation in our `register_extension_internal.dart`. The `ultra` second segment is our namespace.
|
|
54
|
+
|
|
55
|
+
## Planned future extensions
|
|
56
|
+
|
|
57
|
+
- `ultra.waitFor` — server-side polling that resolves when a matcher hits (saves agents N round-trips).
|
|
58
|
+
- `ultra.scrollUntilVisible` — single-call hybrid of `scrollTo` + `waitFor` for stubborn lazy lists.
|
|
59
|
+
- `ultra.matcher` — hierarchical matchers (`{type: 'TextField', ancestor: {key: 'login_form'}}`).
|
|
60
|
+
|
|
61
|
+
Tracked at plan §6.1; not in 0.0.1-dev.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# ultra_flutter
|
|
2
|
+
|
|
3
|
+
In-app Flutter binding mixin that exposes `ext.flutter.ultra.*` VM service
|
|
4
|
+
extensions used by the `flutter-ultra-mcp` Claude Code plugin. AI agents
|
|
5
|
+
attach via the Dart VM Service and drive your app — taps, text input,
|
|
6
|
+
scroll, screenshots, screencast, logs, hot reload, custom RPCs — without
|
|
7
|
+
any flutter_driver instrumentation.
|
|
8
|
+
|
|
9
|
+
A composable fork of [`marionette_flutter`][marionette] v0.5.0 with one
|
|
10
|
+
critical UX win: the binding is a **mixin** (`mixin UltraFlutterBinding on
|
|
11
|
+
WidgetsBinding`) instead of a class, so it stacks cleanly onto other
|
|
12
|
+
`WidgetsFlutterBinding` subclasses (Sentry, integration_test, etc).
|
|
13
|
+
|
|
14
|
+
[marionette]: https://github.com/leancodepl/marionette_mcp
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
dev_dependencies:
|
|
20
|
+
ultra_flutter: ^0.0.1-dev.1
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
(Only `dev_dependencies` — the binding code is fully tree-shaken out of
|
|
24
|
+
release builds when guarded behind `kDebugMode`.)
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Simple — no other custom binding
|
|
29
|
+
|
|
30
|
+
```dart
|
|
31
|
+
import 'package:flutter/foundation.dart';
|
|
32
|
+
import 'package:flutter/widgets.dart';
|
|
33
|
+
import 'package:ultra_flutter/ultra_flutter.dart';
|
|
34
|
+
|
|
35
|
+
Future<void> main() async {
|
|
36
|
+
if (kDebugMode) {
|
|
37
|
+
UltraFlutterBinding.ensureInitialized();
|
|
38
|
+
} else {
|
|
39
|
+
WidgetsFlutterBinding.ensureInitialized();
|
|
40
|
+
}
|
|
41
|
+
runApp(const MyApp());
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Composed with Sentry
|
|
46
|
+
|
|
47
|
+
Mix in `SentryWidgetsBindingMixin` directly alongside `UltraFlutterBinding`:
|
|
48
|
+
|
|
49
|
+
```dart
|
|
50
|
+
import 'package:flutter/widgets.dart';
|
|
51
|
+
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
52
|
+
import 'package:ultra_flutter/ultra_flutter.dart';
|
|
53
|
+
// ignore: implementation_imports
|
|
54
|
+
import 'package:sentry_flutter/src/binding_wrapper.dart';
|
|
55
|
+
|
|
56
|
+
class AppBinding extends WidgetsFlutterBinding
|
|
57
|
+
with SentryWidgetsBindingMixin, UltraFlutterBinding {}
|
|
58
|
+
|
|
59
|
+
Future<void> main() async {
|
|
60
|
+
AppBinding.ensureInitialized();
|
|
61
|
+
await SentryFlutter.init(
|
|
62
|
+
(options) => options.dsn = '<your dsn>',
|
|
63
|
+
appRunner: () => runApp(const MyApp()),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This sidesteps the historical marionette↔Sentry "must be the only binding"
|
|
69
|
+
race — both instrumentations live on a single composed binding instance,
|
|
70
|
+
no singleton race needed.
|
|
71
|
+
|
|
72
|
+
### Configuration
|
|
73
|
+
|
|
74
|
+
```dart
|
|
75
|
+
UltraFlutterBinding.setUltraConfiguration(
|
|
76
|
+
UltraConfiguration(
|
|
77
|
+
isInteractiveWidget: (type) => type.toString() == 'MyCustomButton',
|
|
78
|
+
extractText: (element) {
|
|
79
|
+
final widget = element.widget;
|
|
80
|
+
if (widget is MyCustomLabel) return widget.labelText;
|
|
81
|
+
return null;
|
|
82
|
+
},
|
|
83
|
+
logCollector: PrintLogCollector(), // or LoggingLogCollector, etc.
|
|
84
|
+
maxScreenshotSize: const Size(2000, 2000),
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
UltraFlutterBinding.ensureInitialized(); // or your AppBinding subclass
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Configuration **must** be set BEFORE the binding initializes (i.e. before
|
|
91
|
+
the first `runApp` / `ensureInitialized`). The mixin form can't take
|
|
92
|
+
constructor arguments, so the static slot pattern is required.
|
|
93
|
+
|
|
94
|
+
## Extensions
|
|
95
|
+
|
|
96
|
+
See [EXTENSIONS.md](EXTENSIONS.md) for the full catalogue of
|
|
97
|
+
`ext.flutter.ultra.*` service extensions and how to register your own
|
|
98
|
+
custom ones.
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
Apache-2.0. This is a fork of `marionette_flutter` (also Apache-2.0); see
|
|
103
|
+
top-level [LICENSE](../../LICENSE) and `NOTICE`.
|
|
104
|
+
|
|
105
|
+
## Status
|
|
106
|
+
|
|
107
|
+
Pre-1.0. Mixin shape stable; further extensions (`waitFor`,
|
|
108
|
+
`scrollUntilVisible`, hierarchical matchers) tracked in the plan §6.1
|
|
109
|
+
followups.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: ultra_flutter
|
|
2
|
+
description: >-
|
|
3
|
+
In-app Flutter binding mixin exposing ext.flutter.ultra.* VM service
|
|
4
|
+
extensions (gesture, screenshot, inspector, screencast, log collection)
|
|
5
|
+
for the flutter-ultra-mcp Claude Code plugin. Composable with Sentry and
|
|
6
|
+
other WidgetsFlutterBinding subclasses via the mixin form.
|
|
7
|
+
version: 0.0.1-dev.1
|
|
8
|
+
homepage: https://github.com/Bdaya-Dev/flutter-ultra-mcp
|
|
9
|
+
repository: https://github.com/Bdaya-Dev/flutter-ultra-mcp
|
|
10
|
+
issue_tracker: https://github.com/Bdaya-Dev/flutter-ultra-mcp/issues
|
|
11
|
+
topics:
|
|
12
|
+
- mcp
|
|
13
|
+
- ai
|
|
14
|
+
- claude
|
|
15
|
+
- agent
|
|
16
|
+
- testing
|
|
17
|
+
|
|
18
|
+
publish_to: none
|
|
19
|
+
|
|
20
|
+
environment:
|
|
21
|
+
sdk: '>=3.6.0 <4.0.0'
|
|
22
|
+
flutter: '>=3.27.0'
|
|
23
|
+
|
|
24
|
+
resolution: workspace
|
|
25
|
+
|
|
26
|
+
dependencies:
|
|
27
|
+
flutter:
|
|
28
|
+
sdk: flutter
|
|
29
|
+
web: ^1.1.0
|
|
30
|
+
|
|
31
|
+
dev_dependencies:
|
|
32
|
+
flutter_test:
|
|
33
|
+
sdk: flutter
|
|
34
|
+
test: '>=1.24.0'
|
|
35
|
+
|
|
36
|
+
flutter:
|
|
37
|
+
uses-material-design: false
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# ultra_flutter_devtools
|
|
2
|
+
|
|
3
|
+
Flutter DevTools extension UI for the `flutter-ultra-mcp` plugin. Shows live MCP activity: attached sessions, recent tool calls, errors, screenshot grid.
|
|
4
|
+
|
|
5
|
+
**Status:** scaffold stub. Implementation owner: **wave-2 devtools worker** (see plan §12, §6.2).
|
|
6
|
+
|
|
7
|
+
Loaded by Flutter DevTools as an iframe extension. Pairs with the [`flutter-ultra-devtools`](../../packages/flutter-ultra-devtools/) MCP server which tails `state/tool-events.jsonl` and pushes events over WebSocket.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: ultra_flutter_devtools
|
|
2
|
+
description: >-
|
|
3
|
+
Flutter DevTools extension showing live MCP activity for the flutter-ultra-mcp
|
|
4
|
+
Claude Code plugin (sessions, recent tool calls, errors, screenshot grid).
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
homepage: https://github.com/Bdaya-Dev/flutter-ultra-mcp
|
|
7
|
+
repository: https://github.com/Bdaya-Dev/flutter-ultra-mcp
|
|
8
|
+
issue_tracker: https://github.com/Bdaya-Dev/flutter-ultra-mcp/issues
|
|
9
|
+
|
|
10
|
+
publish_to: none
|
|
11
|
+
|
|
12
|
+
environment:
|
|
13
|
+
sdk: ^3.5.0
|
|
14
|
+
flutter: '>=3.24.0'
|
|
15
|
+
|
|
16
|
+
resolution: workspace
|
|
17
|
+
|
|
18
|
+
dependencies:
|
|
19
|
+
flutter:
|
|
20
|
+
sdk: flutter
|
|
21
|
+
devtools_extensions: ^0.3.0
|
|
22
|
+
devtools_app_shared: ^0.3.0
|
|
23
|
+
web: ^1.1.0
|
|
24
|
+
|
|
25
|
+
dev_dependencies:
|
|
26
|
+
flutter_test:
|
|
27
|
+
sdk: flutter
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Upstream Patrol PRs
|
|
2
|
+
|
|
3
|
+
Placeholder. **Wave-4 worker M** populates this document with the upstream Patrol PRs we open against [`leancodepl/patrol`](https://github.com/leancodepl/patrol) from the Bdaya patrol fork at `vendor/patrol/` — including PR URLs, status, the corresponding fork commits, and the rebase-back-to-bdaya-main plan for the merged ones.
|
|
4
|
+
|
|
5
|
+
See plan §12 wave 4 for the full schedule.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Upstream Sentry PR — expose `SentryWidgetsBindingMixin`
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
**Not filed yet.** Tracked as Path A per `flutter-ultra-mcp-v1.md` §6.1.
|
|
6
|
+
Filing this PR is opportunistic cleanup, not a blocker for v1.
|
|
7
|
+
|
|
8
|
+
## Current state
|
|
9
|
+
|
|
10
|
+
`package:sentry_flutter/sentry_flutter.dart` (v9.20.0) declares:
|
|
11
|
+
|
|
12
|
+
```dart
|
|
13
|
+
export 'src/binding_wrapper.dart'
|
|
14
|
+
show BindingWrapper, SentryWidgetsFlutterBinding;
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The `show` clause hides `SentryWidgetsBindingMixin` from the public API
|
|
18
|
+
even though the mixin is defined in the same file. Consumers reach it via a
|
|
19
|
+
deliberate `// ignore: implementation_imports` import of
|
|
20
|
+
`package:sentry_flutter/src/binding_wrapper.dart`.
|
|
21
|
+
|
|
22
|
+
## Proposed change
|
|
23
|
+
|
|
24
|
+
Extend the `show` clause to include the mixin:
|
|
25
|
+
|
|
26
|
+
```dart
|
|
27
|
+
export 'src/binding_wrapper.dart'
|
|
28
|
+
show BindingWrapper, SentryWidgetsFlutterBinding, SentryWidgetsBindingMixin;
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The mixin is already used as a typedef inside other public Sentry classes,
|
|
32
|
+
so exposing it is a non-breaking change.
|
|
33
|
+
|
|
34
|
+
## Motivation
|
|
35
|
+
|
|
36
|
+
Composability. Multiple Flutter ecosystem packages (testing harnesses,
|
|
37
|
+
debug bindings, AI-agent automation like ultra_flutter) need to mix
|
|
38
|
+
Sentry's binding instrumentation alongside their own. Today they either:
|
|
39
|
+
|
|
40
|
+
1. Subclass `SentryWidgetsFlutterBinding` (cannot also compose another
|
|
41
|
+
`WidgetsFlutterBinding` subclass — Dart `with` requires a mixin), OR
|
|
42
|
+
2. Reach into `package:sentry_flutter/src/binding_wrapper.dart` directly
|
|
43
|
+
with `// ignore: implementation_imports` (brittle, breaks the visibility
|
|
44
|
+
contract).
|
|
45
|
+
|
|
46
|
+
Making the mixin public moves option 2 from "private reach-through" to
|
|
47
|
+
"sanctioned composition".
|
|
48
|
+
|
|
49
|
+
## Risks
|
|
50
|
+
|
|
51
|
+
- Surface-area expansion: once public, the mixin name + signature are
|
|
52
|
+
versioned. Sentry maintainers would need to follow semver on it.
|
|
53
|
+
- Misuse: users might mix the mixin onto unusual base classes and hit
|
|
54
|
+
internal-state assumptions. Counter: the mixin already encapsulates its
|
|
55
|
+
state cleanly (`_isTrackingActive`, `_options`, etc.) and gracefully
|
|
56
|
+
degrades when the rest of Sentry isn't wired.
|
|
57
|
+
|
|
58
|
+
## When to file
|
|
59
|
+
|
|
60
|
+
After `ultra_flutter` ships v1.0 and we have at least one real consumer
|
|
61
|
+
demonstrating the composition pattern. PR will reference the
|
|
62
|
+
ultra_flutter use case as the concrete motivation.
|