flutter-skill-mcp 0.2.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/README.md +71 -0
- package/bin/cli.js +73 -0
- package/dart/.dart_tool/package_config.json +196 -0
- package/dart/.dart_tool/package_graph.json +258 -0
- package/dart/.dart_tool/version +1 -0
- package/dart/bin/server.dart +3 -0
- package/dart/lib/flutter_skill.dart +1167 -0
- package/dart/lib/src/cli/act.dart +118 -0
- package/dart/lib/src/cli/inspect.dart +65 -0
- package/dart/lib/src/cli/launch.dart +89 -0
- package/dart/lib/src/cli/server.dart +594 -0
- package/dart/lib/src/cli/setup.dart +76 -0
- package/dart/lib/src/flutter_skill_client.dart +286 -0
- package/dart/pubspec.lock +237 -0
- package/dart/pubspec.yaml +26 -0
- package/index.js +7 -0
- package/package.json +41 -0
- package/scripts/build.js +67 -0
- package/scripts/check-dart.js +39 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import 'dart:io';
|
|
2
|
+
import 'package:vm_service/vm_service.dart';
|
|
3
|
+
import 'package:vm_service/vm_service_io.dart';
|
|
4
|
+
|
|
5
|
+
class FlutterSkillClient {
|
|
6
|
+
final String wsUri;
|
|
7
|
+
VmService? _service;
|
|
8
|
+
String? _isolateId;
|
|
9
|
+
|
|
10
|
+
FlutterSkillClient(this.wsUri);
|
|
11
|
+
|
|
12
|
+
Future<void> connect() async {
|
|
13
|
+
print('DEBUG: Connecting to $wsUri');
|
|
14
|
+
_service = await vmServiceConnectUri(wsUri);
|
|
15
|
+
print('DEBUG: Connected to VM Service');
|
|
16
|
+
|
|
17
|
+
final vm = await _service!.getVM();
|
|
18
|
+
print('DEBUG: Got VM info');
|
|
19
|
+
final isolates = vm.isolates;
|
|
20
|
+
if (isolates == null || isolates.isEmpty) {
|
|
21
|
+
throw Exception('No isolates found');
|
|
22
|
+
}
|
|
23
|
+
_isolateId = isolates.first.id!;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Future<void> disconnect() async {
|
|
27
|
+
await _service?.dispose();
|
|
28
|
+
_service = null;
|
|
29
|
+
_isolateId = null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Future<Map<String, dynamic>> _call(String method, [Map<String, dynamic>? args]) async {
|
|
33
|
+
if (_service == null || _isolateId == null) {
|
|
34
|
+
throw Exception('Not connected');
|
|
35
|
+
}
|
|
36
|
+
final response = await _service!.callServiceExtension(
|
|
37
|
+
method,
|
|
38
|
+
isolateId: _isolateId!,
|
|
39
|
+
args: args,
|
|
40
|
+
);
|
|
41
|
+
return response.json ?? {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ==================== EXISTING METHODS ====================
|
|
45
|
+
|
|
46
|
+
Future<List<dynamic>> getInteractiveElements() async {
|
|
47
|
+
final result = await _call('ext.flutter.flutter_skill.interactive');
|
|
48
|
+
print('DEBUG: Interactive Result type: ${result.runtimeType}');
|
|
49
|
+
print('DEBUG: Interactive Result: $result');
|
|
50
|
+
|
|
51
|
+
if (result.containsKey('elements')) {
|
|
52
|
+
return result['elements'] as List<dynamic>;
|
|
53
|
+
}
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
Future<void> tap({String? key, String? text}) async {
|
|
58
|
+
if (key == null && text == null) {
|
|
59
|
+
throw ArgumentError('Must provide key or text for tap');
|
|
60
|
+
}
|
|
61
|
+
await _call('ext.flutter.flutter_skill.tap', {
|
|
62
|
+
if (key != null) 'key': key,
|
|
63
|
+
if (text != null) 'text': text,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
Future<void> enterText(String key, String text) async {
|
|
68
|
+
await _call('ext.flutter.flutter_skill.enterText', {
|
|
69
|
+
'key': key,
|
|
70
|
+
'text': text,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Future<void> scrollTo({String? key, String? text}) async {
|
|
75
|
+
await _call('ext.flutter.flutter_skill.scroll', {
|
|
76
|
+
if (key != null) 'key': key,
|
|
77
|
+
if (text != null) 'text': text,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ==================== UI INSPECTION ====================
|
|
82
|
+
|
|
83
|
+
Future<Map<String, dynamic>> getWidgetTree({int maxDepth = 10}) async {
|
|
84
|
+
final result = await _call('ext.flutter.flutter_skill.getWidgetTree', {
|
|
85
|
+
'maxDepth': maxDepth.toString(),
|
|
86
|
+
});
|
|
87
|
+
return result['tree'] ?? {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
Future<Map<String, dynamic>?> getWidgetProperties(String key) async {
|
|
91
|
+
final result = await _call('ext.flutter.flutter_skill.getWidgetProperties', {
|
|
92
|
+
'key': key,
|
|
93
|
+
});
|
|
94
|
+
return result['properties'];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
Future<List<dynamic>> getTextContent() async {
|
|
98
|
+
final result = await _call('ext.flutter.flutter_skill.getTextContent');
|
|
99
|
+
return result['texts'] ?? [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Future<List<dynamic>> findByType(String type) async {
|
|
103
|
+
final result = await _call('ext.flutter.flutter_skill.findByType', {
|
|
104
|
+
'type': type,
|
|
105
|
+
});
|
|
106
|
+
return result['elements'] ?? [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ==================== MORE INTERACTIONS ====================
|
|
110
|
+
|
|
111
|
+
Future<bool> longPress({String? key, String? text, int duration = 500}) async {
|
|
112
|
+
final result = await _call('ext.flutter.flutter_skill.longPress', {
|
|
113
|
+
if (key != null) 'key': key,
|
|
114
|
+
if (text != null) 'text': text,
|
|
115
|
+
'duration': duration.toString(),
|
|
116
|
+
});
|
|
117
|
+
return result['success'] == true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
Future<bool> swipe({required String direction, double distance = 300, String? key}) async {
|
|
121
|
+
final result = await _call('ext.flutter.flutter_skill.swipe', {
|
|
122
|
+
'direction': direction,
|
|
123
|
+
'distance': distance.toString(),
|
|
124
|
+
if (key != null) 'key': key,
|
|
125
|
+
});
|
|
126
|
+
return result['success'] == true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
Future<bool> drag({required String fromKey, required String toKey}) async {
|
|
130
|
+
final result = await _call('ext.flutter.flutter_skill.drag', {
|
|
131
|
+
'fromKey': fromKey,
|
|
132
|
+
'toKey': toKey,
|
|
133
|
+
});
|
|
134
|
+
return result['success'] == true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
Future<bool> doubleTap({String? key, String? text}) async {
|
|
138
|
+
final result = await _call('ext.flutter.flutter_skill.doubleTap', {
|
|
139
|
+
if (key != null) 'key': key,
|
|
140
|
+
if (text != null) 'text': text,
|
|
141
|
+
});
|
|
142
|
+
return result['success'] == true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ==================== STATE & VALIDATION ====================
|
|
146
|
+
|
|
147
|
+
Future<String?> getTextValue(String key) async {
|
|
148
|
+
final result = await _call('ext.flutter.flutter_skill.getTextValue', {
|
|
149
|
+
'key': key,
|
|
150
|
+
});
|
|
151
|
+
return result['value'];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
Future<bool?> getCheckboxState(String key) async {
|
|
155
|
+
final result = await _call('ext.flutter.flutter_skill.getCheckboxState', {
|
|
156
|
+
'key': key,
|
|
157
|
+
});
|
|
158
|
+
return result['checked'];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
Future<double?> getSliderValue(String key) async {
|
|
162
|
+
final result = await _call('ext.flutter.flutter_skill.getSliderValue', {
|
|
163
|
+
'key': key,
|
|
164
|
+
});
|
|
165
|
+
return result['value']?.toDouble();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
Future<bool> waitForElement({String? key, String? text, int timeout = 5000}) async {
|
|
169
|
+
final result = await _call('ext.flutter.flutter_skill.waitForElement', {
|
|
170
|
+
if (key != null) 'key': key,
|
|
171
|
+
if (text != null) 'text': text,
|
|
172
|
+
'timeout': timeout.toString(),
|
|
173
|
+
});
|
|
174
|
+
return result['found'] == true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
Future<bool> waitForGone({String? key, String? text, int timeout = 5000}) async {
|
|
178
|
+
final result = await _call('ext.flutter.flutter_skill.waitForGone', {
|
|
179
|
+
if (key != null) 'key': key,
|
|
180
|
+
if (text != null) 'text': text,
|
|
181
|
+
'timeout': timeout.toString(),
|
|
182
|
+
});
|
|
183
|
+
return result['gone'] == true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ==================== SCREENSHOT ====================
|
|
187
|
+
|
|
188
|
+
Future<String?> takeScreenshot() async {
|
|
189
|
+
final result = await _call('ext.flutter.flutter_skill.screenshot');
|
|
190
|
+
return result['image'];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
Future<String?> takeElementScreenshot(String key) async {
|
|
194
|
+
final result = await _call('ext.flutter.flutter_skill.screenshotElement', {
|
|
195
|
+
'key': key,
|
|
196
|
+
});
|
|
197
|
+
return result['image'];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ==================== NAVIGATION ====================
|
|
201
|
+
|
|
202
|
+
Future<String?> getCurrentRoute() async {
|
|
203
|
+
final result = await _call('ext.flutter.flutter_skill.getCurrentRoute');
|
|
204
|
+
return result['route'];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
Future<bool> goBack() async {
|
|
208
|
+
final result = await _call('ext.flutter.flutter_skill.goBack');
|
|
209
|
+
return result['success'] == true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
Future<List<String>> getNavigationStack() async {
|
|
213
|
+
final result = await _call('ext.flutter.flutter_skill.getNavigationStack');
|
|
214
|
+
return (result['stack'] as List?)?.cast<String>() ?? [];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ==================== DEBUG & LOGS ====================
|
|
218
|
+
|
|
219
|
+
Future<List<String>> getLogs() async {
|
|
220
|
+
final result = await _call('ext.flutter.flutter_skill.getLogs');
|
|
221
|
+
return (result['logs'] as List?)?.cast<String>() ?? [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
Future<List<dynamic>> getErrors() async {
|
|
225
|
+
final result = await _call('ext.flutter.flutter_skill.getErrors');
|
|
226
|
+
return result['errors'] ?? [];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
Future<void> clearLogs() async {
|
|
230
|
+
await _call('ext.flutter.flutter_skill.clearLogs');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
Future<Map<String, dynamic>> getPerformance() async {
|
|
234
|
+
return await _call('ext.flutter.flutter_skill.getPerformance');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ==================== EXISTING HELPERS ====================
|
|
238
|
+
|
|
239
|
+
Future<void> hotReload() async {
|
|
240
|
+
if (_service == null || _isolateId == null) {
|
|
241
|
+
throw Exception('Not connected');
|
|
242
|
+
}
|
|
243
|
+
await _service!.reloadSources(_isolateId!);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
Future<void> hotRestart() async {
|
|
247
|
+
if (_service == null || _isolateId == null) {
|
|
248
|
+
throw Exception('Not connected');
|
|
249
|
+
}
|
|
250
|
+
await _service!.reloadSources(_isolateId!);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
Future<Map<String, dynamic>> getLayoutTree() async {
|
|
254
|
+
try {
|
|
255
|
+
final groupName = 'flutter_skill_${DateTime.now().millisecondsSinceEpoch}';
|
|
256
|
+
final result = await _call('ext.flutter.inspector.getRootWidgetSummaryTree', {
|
|
257
|
+
'objectGroup': groupName,
|
|
258
|
+
});
|
|
259
|
+
return result;
|
|
260
|
+
} catch (e) {
|
|
261
|
+
rethrow;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
bool get isConnected => _service != null && _isolateId != null;
|
|
266
|
+
|
|
267
|
+
static Future<String> resolveUri(List<String> args) async {
|
|
268
|
+
if (args.isNotEmpty) {
|
|
269
|
+
final arg = args[0];
|
|
270
|
+
if (arg.startsWith('ws://') || arg.startsWith('http://')) {
|
|
271
|
+
return arg;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
final file = File('.flutter_skill_uri');
|
|
276
|
+
if (await file.exists()) {
|
|
277
|
+
final uri = (await file.readAsString()).trim();
|
|
278
|
+
if (uri.isNotEmpty) {
|
|
279
|
+
return uri;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
throw ArgumentError(
|
|
284
|
+
'No URI provided and .flutter_skill_uri not found/empty. Run `flutter_skill launch` or provide URI as first argument.');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Generated by pub
|
|
2
|
+
# See https://dart.dev/tools/pub/glossary#lockfile
|
|
3
|
+
packages:
|
|
4
|
+
async:
|
|
5
|
+
dependency: transitive
|
|
6
|
+
description:
|
|
7
|
+
name: async
|
|
8
|
+
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
|
9
|
+
url: "https://pub.flutter-io.cn"
|
|
10
|
+
source: hosted
|
|
11
|
+
version: "2.13.0"
|
|
12
|
+
boolean_selector:
|
|
13
|
+
dependency: transitive
|
|
14
|
+
description:
|
|
15
|
+
name: boolean_selector
|
|
16
|
+
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
|
17
|
+
url: "https://pub.flutter-io.cn"
|
|
18
|
+
source: hosted
|
|
19
|
+
version: "2.1.2"
|
|
20
|
+
characters:
|
|
21
|
+
dependency: transitive
|
|
22
|
+
description:
|
|
23
|
+
name: characters
|
|
24
|
+
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
|
25
|
+
url: "https://pub.flutter-io.cn"
|
|
26
|
+
source: hosted
|
|
27
|
+
version: "1.4.1"
|
|
28
|
+
clock:
|
|
29
|
+
dependency: transitive
|
|
30
|
+
description:
|
|
31
|
+
name: clock
|
|
32
|
+
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
|
33
|
+
url: "https://pub.flutter-io.cn"
|
|
34
|
+
source: hosted
|
|
35
|
+
version: "1.1.2"
|
|
36
|
+
collection:
|
|
37
|
+
dependency: transitive
|
|
38
|
+
description:
|
|
39
|
+
name: collection
|
|
40
|
+
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
|
41
|
+
url: "https://pub.flutter-io.cn"
|
|
42
|
+
source: hosted
|
|
43
|
+
version: "1.19.1"
|
|
44
|
+
fake_async:
|
|
45
|
+
dependency: transitive
|
|
46
|
+
description:
|
|
47
|
+
name: fake_async
|
|
48
|
+
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
|
49
|
+
url: "https://pub.flutter-io.cn"
|
|
50
|
+
source: hosted
|
|
51
|
+
version: "1.3.3"
|
|
52
|
+
flutter:
|
|
53
|
+
dependency: "direct main"
|
|
54
|
+
description: flutter
|
|
55
|
+
source: sdk
|
|
56
|
+
version: "0.0.0"
|
|
57
|
+
flutter_test:
|
|
58
|
+
dependency: "direct dev"
|
|
59
|
+
description: flutter
|
|
60
|
+
source: sdk
|
|
61
|
+
version: "0.0.0"
|
|
62
|
+
http:
|
|
63
|
+
dependency: "direct main"
|
|
64
|
+
description:
|
|
65
|
+
name: http
|
|
66
|
+
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
|
67
|
+
url: "https://pub.flutter-io.cn"
|
|
68
|
+
source: hosted
|
|
69
|
+
version: "1.6.0"
|
|
70
|
+
http_parser:
|
|
71
|
+
dependency: transitive
|
|
72
|
+
description:
|
|
73
|
+
name: http_parser
|
|
74
|
+
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
|
75
|
+
url: "https://pub.flutter-io.cn"
|
|
76
|
+
source: hosted
|
|
77
|
+
version: "4.1.2"
|
|
78
|
+
leak_tracker:
|
|
79
|
+
dependency: transitive
|
|
80
|
+
description:
|
|
81
|
+
name: leak_tracker
|
|
82
|
+
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
|
83
|
+
url: "https://pub.flutter-io.cn"
|
|
84
|
+
source: hosted
|
|
85
|
+
version: "11.0.2"
|
|
86
|
+
leak_tracker_flutter_testing:
|
|
87
|
+
dependency: transitive
|
|
88
|
+
description:
|
|
89
|
+
name: leak_tracker_flutter_testing
|
|
90
|
+
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
|
91
|
+
url: "https://pub.flutter-io.cn"
|
|
92
|
+
source: hosted
|
|
93
|
+
version: "3.0.10"
|
|
94
|
+
leak_tracker_testing:
|
|
95
|
+
dependency: transitive
|
|
96
|
+
description:
|
|
97
|
+
name: leak_tracker_testing
|
|
98
|
+
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
|
99
|
+
url: "https://pub.flutter-io.cn"
|
|
100
|
+
source: hosted
|
|
101
|
+
version: "3.0.2"
|
|
102
|
+
lints:
|
|
103
|
+
dependency: "direct dev"
|
|
104
|
+
description:
|
|
105
|
+
name: lints
|
|
106
|
+
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
|
107
|
+
url: "https://pub.flutter-io.cn"
|
|
108
|
+
source: hosted
|
|
109
|
+
version: "3.0.0"
|
|
110
|
+
logging:
|
|
111
|
+
dependency: "direct main"
|
|
112
|
+
description:
|
|
113
|
+
name: logging
|
|
114
|
+
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
|
115
|
+
url: "https://pub.flutter-io.cn"
|
|
116
|
+
source: hosted
|
|
117
|
+
version: "1.3.0"
|
|
118
|
+
matcher:
|
|
119
|
+
dependency: transitive
|
|
120
|
+
description:
|
|
121
|
+
name: matcher
|
|
122
|
+
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
|
123
|
+
url: "https://pub.flutter-io.cn"
|
|
124
|
+
source: hosted
|
|
125
|
+
version: "0.12.18"
|
|
126
|
+
material_color_utilities:
|
|
127
|
+
dependency: transitive
|
|
128
|
+
description:
|
|
129
|
+
name: material_color_utilities
|
|
130
|
+
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
|
131
|
+
url: "https://pub.flutter-io.cn"
|
|
132
|
+
source: hosted
|
|
133
|
+
version: "0.13.0"
|
|
134
|
+
meta:
|
|
135
|
+
dependency: transitive
|
|
136
|
+
description:
|
|
137
|
+
name: meta
|
|
138
|
+
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
|
139
|
+
url: "https://pub.flutter-io.cn"
|
|
140
|
+
source: hosted
|
|
141
|
+
version: "1.17.0"
|
|
142
|
+
path:
|
|
143
|
+
dependency: "direct main"
|
|
144
|
+
description:
|
|
145
|
+
name: path
|
|
146
|
+
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
|
147
|
+
url: "https://pub.flutter-io.cn"
|
|
148
|
+
source: hosted
|
|
149
|
+
version: "1.9.1"
|
|
150
|
+
sky_engine:
|
|
151
|
+
dependency: transitive
|
|
152
|
+
description: flutter
|
|
153
|
+
source: sdk
|
|
154
|
+
version: "0.0.0"
|
|
155
|
+
source_span:
|
|
156
|
+
dependency: transitive
|
|
157
|
+
description:
|
|
158
|
+
name: source_span
|
|
159
|
+
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
|
160
|
+
url: "https://pub.flutter-io.cn"
|
|
161
|
+
source: hosted
|
|
162
|
+
version: "1.10.1"
|
|
163
|
+
stack_trace:
|
|
164
|
+
dependency: transitive
|
|
165
|
+
description:
|
|
166
|
+
name: stack_trace
|
|
167
|
+
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
|
168
|
+
url: "https://pub.flutter-io.cn"
|
|
169
|
+
source: hosted
|
|
170
|
+
version: "1.12.1"
|
|
171
|
+
stream_channel:
|
|
172
|
+
dependency: transitive
|
|
173
|
+
description:
|
|
174
|
+
name: stream_channel
|
|
175
|
+
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
|
176
|
+
url: "https://pub.flutter-io.cn"
|
|
177
|
+
source: hosted
|
|
178
|
+
version: "2.1.4"
|
|
179
|
+
string_scanner:
|
|
180
|
+
dependency: transitive
|
|
181
|
+
description:
|
|
182
|
+
name: string_scanner
|
|
183
|
+
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
|
184
|
+
url: "https://pub.flutter-io.cn"
|
|
185
|
+
source: hosted
|
|
186
|
+
version: "1.4.1"
|
|
187
|
+
term_glyph:
|
|
188
|
+
dependency: transitive
|
|
189
|
+
description:
|
|
190
|
+
name: term_glyph
|
|
191
|
+
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
|
192
|
+
url: "https://pub.flutter-io.cn"
|
|
193
|
+
source: hosted
|
|
194
|
+
version: "1.2.2"
|
|
195
|
+
test_api:
|
|
196
|
+
dependency: transitive
|
|
197
|
+
description:
|
|
198
|
+
name: test_api
|
|
199
|
+
sha256: "19a78f63e83d3a61f00826d09bc2f60e191bf3504183c001262be6ac75589fb8"
|
|
200
|
+
url: "https://pub.flutter-io.cn"
|
|
201
|
+
source: hosted
|
|
202
|
+
version: "0.7.8"
|
|
203
|
+
typed_data:
|
|
204
|
+
dependency: transitive
|
|
205
|
+
description:
|
|
206
|
+
name: typed_data
|
|
207
|
+
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
|
208
|
+
url: "https://pub.flutter-io.cn"
|
|
209
|
+
source: hosted
|
|
210
|
+
version: "1.4.0"
|
|
211
|
+
vector_math:
|
|
212
|
+
dependency: transitive
|
|
213
|
+
description:
|
|
214
|
+
name: vector_math
|
|
215
|
+
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
|
216
|
+
url: "https://pub.flutter-io.cn"
|
|
217
|
+
source: hosted
|
|
218
|
+
version: "2.2.0"
|
|
219
|
+
vm_service:
|
|
220
|
+
dependency: "direct main"
|
|
221
|
+
description:
|
|
222
|
+
name: vm_service
|
|
223
|
+
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
|
224
|
+
url: "https://pub.flutter-io.cn"
|
|
225
|
+
source: hosted
|
|
226
|
+
version: "14.3.1"
|
|
227
|
+
web:
|
|
228
|
+
dependency: transitive
|
|
229
|
+
description:
|
|
230
|
+
name: web
|
|
231
|
+
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
|
232
|
+
url: "https://pub.flutter-io.cn"
|
|
233
|
+
source: hosted
|
|
234
|
+
version: "1.1.1"
|
|
235
|
+
sdks:
|
|
236
|
+
dart: ">=3.9.0-0 <4.0.0"
|
|
237
|
+
flutter: ">=3.18.0-18.0.pre.54"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: flutter_skill
|
|
2
|
+
description: Give your AI Agent eyes and hands inside your Flutter app.
|
|
3
|
+
version: 0.2.0
|
|
4
|
+
homepage: https://github.com/ai-dashboad/flutter-skill
|
|
5
|
+
repository: https://github.com/ai-dashboad/flutter-skill
|
|
6
|
+
# publish_to: 'none' # Remove this when ready to publish to pub.dev
|
|
7
|
+
|
|
8
|
+
executables:
|
|
9
|
+
flutter_skill: flutter_skill
|
|
10
|
+
|
|
11
|
+
environment:
|
|
12
|
+
sdk: '>=3.0.0 <4.0.0'
|
|
13
|
+
flutter: ">=3.0.0"
|
|
14
|
+
|
|
15
|
+
dependencies:
|
|
16
|
+
flutter:
|
|
17
|
+
sdk: flutter
|
|
18
|
+
vm_service: ^14.0.0
|
|
19
|
+
http: ^1.1.0
|
|
20
|
+
path: ^1.8.0
|
|
21
|
+
logging: ^1.2.0
|
|
22
|
+
|
|
23
|
+
dev_dependencies:
|
|
24
|
+
flutter_test:
|
|
25
|
+
sdk: flutter
|
|
26
|
+
lints: ^3.0.0
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flutter-skill-mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP Server for Flutter app automation - Give your AI Agent eyes and hands inside your Flutter app",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"flutter-skill-mcp": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"postinstall": "node scripts/check-dart.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"flutter",
|
|
14
|
+
"mcp",
|
|
15
|
+
"ai",
|
|
16
|
+
"automation",
|
|
17
|
+
"testing",
|
|
18
|
+
"claude",
|
|
19
|
+
"cursor",
|
|
20
|
+
"windsurf"
|
|
21
|
+
],
|
|
22
|
+
"author": "ai-dashboad",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/ai-dashboad/flutter-skill.git"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/ai-dashboad/flutter-skill",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/ai-dashboad/flutter-skill/issues"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=16.0.0"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"bin/",
|
|
37
|
+
"scripts/",
|
|
38
|
+
"dart/",
|
|
39
|
+
"README.md"
|
|
40
|
+
]
|
|
41
|
+
}
|
package/scripts/build.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const rootDir = path.join(__dirname, '..', '..');
|
|
7
|
+
const npmDir = path.join(__dirname, '..');
|
|
8
|
+
const dartDir = path.join(npmDir, 'dart');
|
|
9
|
+
|
|
10
|
+
// Files to copy
|
|
11
|
+
const filesToCopy = [
|
|
12
|
+
'pubspec.yaml',
|
|
13
|
+
'lib/flutter_skill.dart',
|
|
14
|
+
'lib/src/flutter_skill_client.dart',
|
|
15
|
+
'lib/src/cli/server.dart',
|
|
16
|
+
'lib/src/cli/setup.dart',
|
|
17
|
+
'lib/src/cli/launch.dart',
|
|
18
|
+
'lib/src/cli/inspect.dart',
|
|
19
|
+
'lib/src/cli/act.dart',
|
|
20
|
+
'bin/server.dart',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// Create directories
|
|
24
|
+
function mkdirp(dir) {
|
|
25
|
+
if (!fs.existsSync(dir)) {
|
|
26
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Copy file
|
|
31
|
+
function copyFile(src, dest) {
|
|
32
|
+
mkdirp(path.dirname(dest));
|
|
33
|
+
fs.copyFileSync(src, dest);
|
|
34
|
+
console.log(`Copied: ${path.relative(rootDir, src)} -> ${path.relative(npmDir, dest)}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Main
|
|
38
|
+
console.log('Building npm package...\n');
|
|
39
|
+
|
|
40
|
+
// Clean dart directory
|
|
41
|
+
if (fs.existsSync(dartDir)) {
|
|
42
|
+
fs.rmSync(dartDir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
mkdirp(dartDir);
|
|
45
|
+
|
|
46
|
+
// Copy files
|
|
47
|
+
for (const file of filesToCopy) {
|
|
48
|
+
const src = path.join(rootDir, file);
|
|
49
|
+
const dest = path.join(dartDir, file);
|
|
50
|
+
if (fs.existsSync(src)) {
|
|
51
|
+
copyFile(src, dest);
|
|
52
|
+
} else {
|
|
53
|
+
console.warn(`Warning: ${file} not found`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Update version in package.json from pubspec.yaml
|
|
58
|
+
const pubspec = fs.readFileSync(path.join(rootDir, 'pubspec.yaml'), 'utf8');
|
|
59
|
+
const versionMatch = pubspec.match(/version:\s*(\S+)/);
|
|
60
|
+
if (versionMatch) {
|
|
61
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(npmDir, 'package.json'), 'utf8'));
|
|
62
|
+
packageJson.version = versionMatch[1];
|
|
63
|
+
fs.writeFileSync(path.join(npmDir, 'package.json'), JSON.stringify(packageJson, null, 2) + '\n');
|
|
64
|
+
console.log(`\nUpdated package.json version to ${versionMatch[1]}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log('\nBuild complete!');
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
function checkCommand(cmd) {
|
|
6
|
+
try {
|
|
7
|
+
execSync(`${cmd} --version`, { stdio: 'ignore' });
|
|
8
|
+
return true;
|
|
9
|
+
} catch (e) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const hasDart = checkCommand('dart');
|
|
15
|
+
const hasFlutter = checkCommand('flutter');
|
|
16
|
+
|
|
17
|
+
if (!hasDart && !hasFlutter) {
|
|
18
|
+
console.log('\n' + '='.repeat(60));
|
|
19
|
+
console.log('flutter-skill-mcp requires Dart SDK');
|
|
20
|
+
console.log('='.repeat(60));
|
|
21
|
+
console.log('\nPlease install Flutter (includes Dart):');
|
|
22
|
+
console.log(' https://docs.flutter.dev/get-started/install\n');
|
|
23
|
+
console.log('Or install Dart standalone:');
|
|
24
|
+
console.log(' https://dart.dev/get-dart\n');
|
|
25
|
+
} else if (hasDart && !hasFlutter) {
|
|
26
|
+
console.log('\nNote: Flutter SDK not found. Some features may be limited.');
|
|
27
|
+
console.log('Install Flutter for full functionality:');
|
|
28
|
+
console.log(' https://docs.flutter.dev/get-started/install\n');
|
|
29
|
+
} else {
|
|
30
|
+
console.log('\nflutter-skill-mcp installed successfully!');
|
|
31
|
+
console.log('\nMCP Config:');
|
|
32
|
+
console.log(JSON.stringify({
|
|
33
|
+
"flutter-skill": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": ["flutter-skill-mcp"]
|
|
36
|
+
}
|
|
37
|
+
}, null, 2));
|
|
38
|
+
console.log('\n');
|
|
39
|
+
}
|