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.
@@ -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
@@ -0,0 +1,7 @@
1
+ // flutter-skill-mcp
2
+ // MCP Server for Flutter app automation
3
+
4
+ module.exports = {
5
+ name: 'flutter-skill-mcp',
6
+ version: require('./package.json').version
7
+ };
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
+ }
@@ -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
+ }