flutter-skill-mcp 0.2.7 → 0.2.8
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 +166 -45
- package/package.json +2 -2
- package/scripts/postinstall.js +99 -0
package/bin/cli.js
CHANGED
|
@@ -3,65 +3,186 @@
|
|
|
3
3
|
const { spawn, execSync } = require('child_process');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const os = require('os');
|
|
6
8
|
|
|
7
|
-
//
|
|
8
|
-
const
|
|
9
|
-
const
|
|
9
|
+
// Package info
|
|
10
|
+
const packageJson = require('../package.json');
|
|
11
|
+
const VERSION = packageJson.version;
|
|
10
12
|
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
+
// Paths
|
|
14
|
+
const cacheDir = path.join(os.homedir(), '.flutter-skill');
|
|
15
|
+
const binDir = path.join(cacheDir, 'bin');
|
|
16
|
+
|
|
17
|
+
// Get platform-specific binary name
|
|
18
|
+
function getBinaryName() {
|
|
19
|
+
const platform = os.platform();
|
|
20
|
+
const arch = os.arch();
|
|
21
|
+
|
|
22
|
+
if (platform === 'darwin') {
|
|
23
|
+
return arch === 'arm64' ? 'flutter-skill-macos-arm64' : 'flutter-skill-macos-x64';
|
|
24
|
+
} else if (platform === 'linux') {
|
|
25
|
+
return 'flutter-skill-linux-x64';
|
|
26
|
+
} else if (platform === 'win32') {
|
|
27
|
+
return 'flutter-skill-windows-x64.exe';
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Get the local binary path
|
|
33
|
+
function getLocalBinaryPath() {
|
|
34
|
+
const binaryName = getBinaryName();
|
|
35
|
+
if (!binaryName) return null;
|
|
36
|
+
return path.join(binDir, `${binaryName}-v${VERSION}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Download binary from GitHub releases
|
|
40
|
+
function downloadBinary(url, destPath) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
// Ensure directory exists
|
|
43
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
44
|
+
|
|
45
|
+
const file = fs.createWriteStream(destPath);
|
|
46
|
+
|
|
47
|
+
const request = (url) => {
|
|
48
|
+
https.get(url, (response) => {
|
|
49
|
+
// Handle redirects
|
|
50
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
51
|
+
request(response.headers.location);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (response.statusCode !== 200) {
|
|
56
|
+
reject(new Error(`Failed to download: ${response.statusCode}`));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
response.pipe(file);
|
|
61
|
+
file.on('finish', () => {
|
|
62
|
+
file.close();
|
|
63
|
+
// Make executable
|
|
64
|
+
fs.chmodSync(destPath, 0o755);
|
|
65
|
+
resolve(destPath);
|
|
66
|
+
});
|
|
67
|
+
}).on('error', (err) => {
|
|
68
|
+
fs.unlink(destPath, () => {});
|
|
69
|
+
reject(err);
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
request(url);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Try to use native binary, fallback to Dart
|
|
78
|
+
async function main() {
|
|
79
|
+
const binaryName = getBinaryName();
|
|
80
|
+
const localBinaryPath = getLocalBinaryPath();
|
|
81
|
+
|
|
82
|
+
// Try to use existing native binary
|
|
83
|
+
if (localBinaryPath && fs.existsSync(localBinaryPath)) {
|
|
84
|
+
runNativeBinary(localBinaryPath);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Try to download native binary
|
|
89
|
+
if (binaryName && localBinaryPath) {
|
|
90
|
+
const downloadUrl = `https://github.com/ai-dashboad/flutter-skill/releases/download/v${VERSION}/${binaryName}`;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Download in background, don't block startup for first time
|
|
94
|
+
// For now, just fall through to Dart
|
|
95
|
+
// Future: implement async download with progress
|
|
96
|
+
console.error(`[flutter-skill] Native binary not found, using Dart runtime`);
|
|
97
|
+
console.error(`[flutter-skill] To install native binary for faster startup:`);
|
|
98
|
+
console.error(`[flutter-skill] curl -L ${downloadUrl} -o ${localBinaryPath} && chmod +x ${localBinaryPath}`);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
// Ignore download errors, fall back to Dart
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Fallback to Dart
|
|
105
|
+
runWithDart();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Run using native binary
|
|
109
|
+
function runNativeBinary(binaryPath) {
|
|
110
|
+
const args = process.argv.slice(2);
|
|
111
|
+
// Default to 'server' command if no args
|
|
112
|
+
if (args.length === 0) {
|
|
113
|
+
args.push('server');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const server = spawn(binaryPath, args, {
|
|
117
|
+
stdio: 'inherit'
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
server.on('close', (code) => {
|
|
121
|
+
process.exit(code || 0);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
process.on('SIGINT', () => server.kill('SIGINT'));
|
|
125
|
+
process.on('SIGTERM', () => server.kill('SIGTERM'));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Run using Dart
|
|
129
|
+
function runWithDart() {
|
|
130
|
+
const dartDir = path.join(__dirname, '..', 'dart');
|
|
131
|
+
const serverScript = path.join(dartDir, 'bin', 'server.dart');
|
|
132
|
+
|
|
133
|
+
// Check if Dart is installed
|
|
13
134
|
try {
|
|
14
135
|
execSync('dart --version', { stdio: 'ignore' });
|
|
15
|
-
return true;
|
|
16
136
|
} catch (e) {
|
|
17
|
-
|
|
137
|
+
console.error('Error: Dart SDK not found. Please install Flutter/Dart first.');
|
|
138
|
+
console.error(' https://docs.flutter.dev/get-started/install');
|
|
139
|
+
process.exit(1);
|
|
18
140
|
}
|
|
19
|
-
}
|
|
20
141
|
|
|
21
|
-
// Check if
|
|
22
|
-
|
|
142
|
+
// Check if server script exists
|
|
143
|
+
if (!fs.existsSync(serverScript)) {
|
|
144
|
+
console.error('Error: Server script not found at:', serverScript);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Get dependencies silently
|
|
23
149
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
150
|
+
const pubCmd = checkFlutter() ? 'flutter' : 'dart';
|
|
151
|
+
execSync(`${pubCmd} pub get`, {
|
|
152
|
+
cwd: dartDir,
|
|
153
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
154
|
+
});
|
|
26
155
|
} catch (e) {
|
|
27
|
-
|
|
156
|
+
// Ignore pub get errors
|
|
28
157
|
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (!checkDart()) {
|
|
32
|
-
console.error('Error: Dart SDK not found. Please install Flutter/Dart first.');
|
|
33
|
-
console.error(' https://docs.flutter.dev/get-started/install');
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
158
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
159
|
+
// Start with Dart
|
|
160
|
+
const args = process.argv.slice(2);
|
|
161
|
+
if (args.length === 0) {
|
|
162
|
+
args.push('server');
|
|
163
|
+
}
|
|
42
164
|
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
try {
|
|
46
|
-
execSync(`${pubCmd} pub get`, {
|
|
165
|
+
const dartArgs = ['run', serverScript, ...args];
|
|
166
|
+
const server = spawn('dart', dartArgs, {
|
|
47
167
|
cwd: dartDir,
|
|
48
|
-
stdio:
|
|
168
|
+
stdio: 'inherit'
|
|
49
169
|
});
|
|
50
|
-
} catch (e) {
|
|
51
|
-
// Log to stderr if pub get fails
|
|
52
|
-
console.error('Warning: pub get failed, dependencies may be missing');
|
|
53
|
-
}
|
|
54
170
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
stdio: 'inherit' // stdin/stdout/stderr passed through for MCP communication
|
|
59
|
-
});
|
|
171
|
+
server.on('close', (code) => {
|
|
172
|
+
process.exit(code || 0);
|
|
173
|
+
});
|
|
60
174
|
|
|
61
|
-
|
|
62
|
-
process.
|
|
63
|
-
}
|
|
175
|
+
process.on('SIGINT', () => server.kill('SIGINT'));
|
|
176
|
+
process.on('SIGTERM', () => server.kill('SIGTERM'));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function checkFlutter() {
|
|
180
|
+
try {
|
|
181
|
+
execSync('flutter --version', { stdio: 'ignore' });
|
|
182
|
+
return true;
|
|
183
|
+
} catch (e) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
64
187
|
|
|
65
|
-
|
|
66
|
-
process.on('SIGINT', () => server.kill('SIGINT'));
|
|
67
|
-
process.on('SIGTERM', () => server.kill('SIGTERM'));
|
|
188
|
+
main().catch(console.error);
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flutter-skill-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "MCP Server for Flutter app automation - Give your AI Agent eyes and hands inside your Flutter app",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"flutter-skill-mcp": "./bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"postinstall": "node scripts/
|
|
10
|
+
"postinstall": "node scripts/postinstall.js"
|
|
11
11
|
},
|
|
12
12
|
"keywords": [
|
|
13
13
|
"flutter",
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
|
|
8
|
+
const packageJson = require('../package.json');
|
|
9
|
+
const VERSION = packageJson.version;
|
|
10
|
+
|
|
11
|
+
const cacheDir = path.join(os.homedir(), '.flutter-skill');
|
|
12
|
+
const binDir = path.join(cacheDir, 'bin');
|
|
13
|
+
|
|
14
|
+
function getBinaryName() {
|
|
15
|
+
const platform = os.platform();
|
|
16
|
+
const arch = os.arch();
|
|
17
|
+
|
|
18
|
+
if (platform === 'darwin') {
|
|
19
|
+
return arch === 'arm64' ? 'flutter-skill-macos-arm64' : 'flutter-skill-macos-x64';
|
|
20
|
+
} else if (platform === 'linux') {
|
|
21
|
+
return 'flutter-skill-linux-x64';
|
|
22
|
+
} else if (platform === 'win32') {
|
|
23
|
+
return 'flutter-skill-windows-x64.exe';
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function downloadBinary(url, destPath) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
31
|
+
|
|
32
|
+
const file = fs.createWriteStream(destPath);
|
|
33
|
+
|
|
34
|
+
const request = (url) => {
|
|
35
|
+
https.get(url, (response) => {
|
|
36
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
37
|
+
request(response.headers.location);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (response.statusCode !== 200) {
|
|
42
|
+
reject(new Error(`HTTP ${response.statusCode}`));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const totalBytes = parseInt(response.headers['content-length'], 10);
|
|
47
|
+
let downloadedBytes = 0;
|
|
48
|
+
|
|
49
|
+
response.on('data', (chunk) => {
|
|
50
|
+
downloadedBytes += chunk.length;
|
|
51
|
+
if (totalBytes) {
|
|
52
|
+
const percent = Math.round((downloadedBytes / totalBytes) * 100);
|
|
53
|
+
process.stdout.write(`\r[flutter-skill] Downloading native binary... ${percent}%`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
response.pipe(file);
|
|
58
|
+
file.on('finish', () => {
|
|
59
|
+
file.close();
|
|
60
|
+
fs.chmodSync(destPath, 0o755);
|
|
61
|
+
console.log('\n[flutter-skill] Native binary installed successfully!');
|
|
62
|
+
resolve(destPath);
|
|
63
|
+
});
|
|
64
|
+
}).on('error', reject);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
request(url);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function main() {
|
|
72
|
+
const binaryName = getBinaryName();
|
|
73
|
+
if (!binaryName) {
|
|
74
|
+
console.log('[flutter-skill] No native binary available for this platform, using Dart runtime');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const localPath = path.join(binDir, `${binaryName}-v${VERSION}`);
|
|
79
|
+
|
|
80
|
+
if (fs.existsSync(localPath)) {
|
|
81
|
+
console.log('[flutter-skill] Native binary already installed');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const downloadUrl = `https://github.com/ai-dashboad/flutter-skill/releases/download/v${VERSION}/${binaryName}`;
|
|
86
|
+
|
|
87
|
+
console.log(`[flutter-skill] Installing native binary for faster startup...`);
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
await downloadBinary(downloadUrl, localPath);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.log(`[flutter-skill] Could not download native binary (${error.message}), will use Dart runtime`);
|
|
93
|
+
console.log('[flutter-skill] This is normal for new releases, Dart fallback works fine');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main().catch(() => {
|
|
98
|
+
// Silent fail - Dart fallback will work
|
|
99
|
+
});
|