flutter-skill 0.7.4

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/bin/cli.js ADDED
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn, execSync } = require('child_process');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const https = require('https');
7
+ const os = require('os');
8
+
9
+ // Package info
10
+ const packageJson = require('../package.json');
11
+ const VERSION = packageJson.version;
12
+
13
+ // Update check config
14
+ const CHECK_INTERVAL_HOURS = 24;
15
+ const UPDATE_CHECK_FILE = path.join(os.homedir(), '.flutter-skill', 'update-check.json');
16
+
17
+ // Paths
18
+ const cacheDir = path.join(os.homedir(), '.flutter-skill');
19
+ const binDir = path.join(cacheDir, 'bin');
20
+
21
+ // Get platform-specific binary name
22
+ function getBinaryName() {
23
+ const platform = os.platform();
24
+ const arch = os.arch();
25
+
26
+ if (platform === 'darwin') {
27
+ return arch === 'arm64' ? 'flutter-skill-macos-arm64' : 'flutter-skill-macos-x64';
28
+ } else if (platform === 'linux') {
29
+ return 'flutter-skill-linux-x64';
30
+ } else if (platform === 'win32') {
31
+ return 'flutter-skill-windows-x64.exe';
32
+ }
33
+ return null;
34
+ }
35
+
36
+ // Get the local binary path
37
+ function getLocalBinaryPath() {
38
+ const binaryName = getBinaryName();
39
+ if (!binaryName) return null;
40
+ return path.join(binDir, `${binaryName}-v${VERSION}`);
41
+ }
42
+
43
+ // Download binary from GitHub releases
44
+ function downloadBinary(url, destPath) {
45
+ return new Promise((resolve, reject) => {
46
+ // Ensure directory exists
47
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
48
+
49
+ const file = fs.createWriteStream(destPath);
50
+
51
+ const request = (url) => {
52
+ https.get(url, (response) => {
53
+ // Handle redirects
54
+ if (response.statusCode === 302 || response.statusCode === 301) {
55
+ request(response.headers.location);
56
+ return;
57
+ }
58
+
59
+ if (response.statusCode !== 200) {
60
+ reject(new Error(`Failed to download: ${response.statusCode}`));
61
+ return;
62
+ }
63
+
64
+ response.pipe(file);
65
+ file.on('finish', () => {
66
+ file.close();
67
+ // Make executable
68
+ fs.chmodSync(destPath, 0o755);
69
+ resolve(destPath);
70
+ });
71
+ }).on('error', (err) => {
72
+ fs.unlink(destPath, () => {});
73
+ reject(err);
74
+ });
75
+ };
76
+
77
+ request(url);
78
+ });
79
+ }
80
+
81
+ // Try to use native binary, fallback to Dart
82
+ async function main() {
83
+ const binaryName = getBinaryName();
84
+ const localBinaryPath = getLocalBinaryPath();
85
+
86
+ // Try to use existing native binary
87
+ if (localBinaryPath && fs.existsSync(localBinaryPath)) {
88
+ runNativeBinary(localBinaryPath);
89
+ return;
90
+ }
91
+
92
+ // Try to download native binary
93
+ if (binaryName && localBinaryPath) {
94
+ const downloadUrl = `https://github.com/ai-dashboad/flutter-skill/releases/download/v${VERSION}/${binaryName}`;
95
+
96
+ try {
97
+ // Download in background, don't block startup for first time
98
+ // For now, just fall through to Dart
99
+ // Future: implement async download with progress
100
+ console.error(`[flutter-skill] Native binary not found, using Dart runtime`);
101
+ console.error(`[flutter-skill] To install native binary for faster startup:`);
102
+ console.error(`[flutter-skill] curl -L ${downloadUrl} -o ${localBinaryPath} && chmod +x ${localBinaryPath}`);
103
+ } catch (e) {
104
+ // Ignore download errors, fall back to Dart
105
+ }
106
+ }
107
+
108
+ // Fallback to Dart
109
+ runWithDart();
110
+ }
111
+
112
+ // Run using native binary
113
+ function runNativeBinary(binaryPath) {
114
+ const args = process.argv.slice(2);
115
+ // Default to 'server' command if no args
116
+ if (args.length === 0) {
117
+ args.push('server');
118
+ }
119
+
120
+ const server = spawn(binaryPath, args, {
121
+ stdio: 'inherit'
122
+ });
123
+
124
+ server.on('close', (code) => {
125
+ process.exit(code || 0);
126
+ });
127
+
128
+ process.on('SIGINT', () => server.kill('SIGINT'));
129
+ process.on('SIGTERM', () => server.kill('SIGTERM'));
130
+ }
131
+
132
+ // Run using Dart
133
+ function runWithDart() {
134
+ const dartDir = path.join(__dirname, '..', 'dart');
135
+ const serverScript = path.join(dartDir, 'bin', 'server.dart');
136
+
137
+ // Check if Dart is installed
138
+ try {
139
+ execSync('dart --version', { stdio: 'ignore' });
140
+ } catch (e) {
141
+ console.error('Error: Dart SDK not found. Please install Flutter/Dart first.');
142
+ console.error(' https://docs.flutter.dev/get-started/install');
143
+ process.exit(1);
144
+ }
145
+
146
+ // Check if server script exists
147
+ if (!fs.existsSync(serverScript)) {
148
+ console.error('Error: Server script not found at:', serverScript);
149
+ process.exit(1);
150
+ }
151
+
152
+ // Get dependencies silently
153
+ try {
154
+ const pubCmd = checkFlutter() ? 'flutter' : 'dart';
155
+ execSync(`${pubCmd} pub get`, {
156
+ cwd: dartDir,
157
+ stdio: ['ignore', 'pipe', 'pipe']
158
+ });
159
+ } catch (e) {
160
+ // Ignore pub get errors
161
+ }
162
+
163
+ // Start with Dart
164
+ const args = process.argv.slice(2);
165
+ if (args.length === 0) {
166
+ args.push('server');
167
+ }
168
+
169
+ const dartArgs = ['run', serverScript, ...args];
170
+ const server = spawn('dart', dartArgs, {
171
+ cwd: dartDir,
172
+ stdio: 'inherit'
173
+ });
174
+
175
+ server.on('close', (code) => {
176
+ process.exit(code || 0);
177
+ });
178
+
179
+ process.on('SIGINT', () => server.kill('SIGINT'));
180
+ process.on('SIGTERM', () => server.kill('SIGTERM'));
181
+ }
182
+
183
+ function checkFlutter() {
184
+ try {
185
+ execSync('flutter --version', { stdio: 'ignore' });
186
+ return true;
187
+ } catch (e) {
188
+ return false;
189
+ }
190
+ }
191
+
192
+ // Check for updates (non-blocking, runs in background)
193
+ function checkForUpdates() {
194
+ // Only check periodically
195
+ try {
196
+ const checkDir = path.dirname(UPDATE_CHECK_FILE);
197
+ if (!fs.existsSync(checkDir)) {
198
+ fs.mkdirSync(checkDir, { recursive: true });
199
+ }
200
+
201
+ let lastCheck = 0;
202
+ let skippedVersion = null;
203
+
204
+ if (fs.existsSync(UPDATE_CHECK_FILE)) {
205
+ try {
206
+ const data = JSON.parse(fs.readFileSync(UPDATE_CHECK_FILE, 'utf-8'));
207
+ lastCheck = data.lastCheck || 0;
208
+ skippedVersion = data.skippedVersion;
209
+ } catch {}
210
+ }
211
+
212
+ const now = Date.now();
213
+ const hoursSinceLastCheck = (now - lastCheck) / (1000 * 60 * 60);
214
+
215
+ if (hoursSinceLastCheck < CHECK_INTERVAL_HOURS) {
216
+ return;
217
+ }
218
+
219
+ // Update last check time
220
+ fs.writeFileSync(UPDATE_CHECK_FILE, JSON.stringify({
221
+ lastCheck: now,
222
+ skippedVersion
223
+ }));
224
+
225
+ // Check npm registry for latest version (async, non-blocking)
226
+ https.get('https://registry.npmjs.org/flutter-skill', (res) => {
227
+ let data = '';
228
+ res.on('data', (chunk) => data += chunk);
229
+ res.on('end', () => {
230
+ try {
231
+ const json = JSON.parse(data);
232
+ const latestVersion = json['dist-tags'].latest;
233
+
234
+ if (latestVersion && compareVersions(latestVersion, VERSION) > 0) {
235
+ if (skippedVersion !== latestVersion) {
236
+ console.error(`\n[flutter-skill] Update available: ${VERSION} → ${latestVersion}`);
237
+ console.error(`[flutter-skill] Run: npm update -g flutter-skill\n`);
238
+ }
239
+ }
240
+ } catch {}
241
+ });
242
+ }).on('error', () => {});
243
+ } catch {}
244
+ }
245
+
246
+ function compareVersions(v1, v2) {
247
+ const parts1 = v1.split('.').map(Number);
248
+ const parts2 = v2.split('.').map(Number);
249
+
250
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
251
+ const p1 = parts1[i] || 0;
252
+ const p2 = parts2[i] || 0;
253
+ if (p1 > p2) return 1;
254
+ if (p1 < p2) return -1;
255
+ }
256
+ return 0;
257
+ }
258
+
259
+ // Show tips when running interactively without arguments
260
+ function showTips() {
261
+ const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
262
+ const args = process.argv.slice(2);
263
+
264
+ if (!isInteractive || args.length > 0) {
265
+ return false; // Not interactive or has args, don't show tips
266
+ }
267
+
268
+ console.log(`Flutter Skill v${VERSION} - AI Agent Bridge for Flutter Apps`);
269
+ console.log('');
270
+ console.log('Commands:');
271
+ console.log(' server Start MCP server (default when launched by IDE)');
272
+ console.log(' launch Launch and connect to a Flutter app');
273
+ console.log(' inspect Inspect interactive elements in running app');
274
+ console.log(' act Perform actions (tap, scroll, enter_text)');
275
+ console.log(' doctor Check installation and environment health');
276
+ console.log(' setup Install tool priority rules for Claude Code');
277
+ console.log(' --version Show version');
278
+ console.log('');
279
+ console.log('Quick Start:');
280
+ console.log(' flutter-skill doctor Check your environment is ready');
281
+ console.log(' flutter-skill launch ./my_app Launch and connect to your app');
282
+ console.log('');
283
+ console.log('What can AI agents do with Flutter Skill?');
284
+ console.log(' - Launch your Flutter app and auto-connect');
285
+ console.log(' - Inspect UI: find buttons, text fields, lists');
286
+ console.log(' - Tap, swipe, scroll, and enter text');
287
+ console.log(' - Take screenshots to verify visual changes');
288
+ console.log(' - Read app logs and debug issues');
289
+ console.log(' - Hot reload after code changes');
290
+ console.log('');
291
+ console.log('Example: Ask your AI agent:');
292
+ console.log(' "Launch my Flutter app and tap the login button"');
293
+ console.log(' "Take a screenshot and check if the list is showing"');
294
+ console.log(' "Enter \'hello@test.com\' in the email field and submit"');
295
+ console.log('');
296
+ console.log('Docs: https://pub.dev/packages/flutter_skill');
297
+ console.log('');
298
+ return true;
299
+ }
300
+
301
+ // Handle --version / -v directly (fast, no binary needed)
302
+ const cliArgs = process.argv.slice(2);
303
+ if (cliArgs.includes('--version') || cliArgs.includes('-v')) {
304
+ console.log(VERSION);
305
+ process.exit(0);
306
+ }
307
+
308
+ // Show tips if interactive, otherwise run server
309
+ if (showTips()) {
310
+ process.exit(0);
311
+ }
312
+
313
+ // Run update check in background (non-blocking)
314
+ checkForUpdates();
315
+
316
+ main().catch(console.error);
@@ -0,0 +1,3 @@
1
+ import 'package:flutter_skill/src/cli/server.dart';
2
+
3
+ void main(List<String> args) => runServer(args);