@fmode/studio 0.0.3 → 0.0.6

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.
Files changed (2) hide show
  1. package/bin/fmode.js +8 -336
  2. package/package.json +1 -1
package/bin/fmode.js CHANGED
@@ -1,350 +1,22 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * Fmode Studio CLI Entry Point
5
5
  * npm/npx compatible entry point
6
6
  *
7
- * Features:
8
- * - Version checking against repos.fmode.cn
9
- * - Automatic binary download and caching
10
- * - Update command for manual updates
11
- * - Fallback to Node.js/Bun execution
7
+ * This is a thin router that delegates to the modular CLI structure.
8
+ * All command logic is now in cli/ subdirectories.
12
9
  *
13
10
  * Usage:
14
11
  * npx @fmode/studio
15
- * npx @fmode/studio -y
16
- * npx @fmode/studio update
17
- * fmode
12
+ * npx @fmode/studio start
13
+ * npx @fmode/studio server start
18
14
  */
19
15
 
20
- import { spawn } from 'child_process';
21
- import { fileURLToPath } from 'url';
22
- import { dirname, join } from 'path';
23
- import { existsSync, mkdirSync, chmodSync, renameSync } from 'fs';
24
- import { createReadStream, createWriteStream } from 'fs';
25
- import { pipeline } from 'stream/promises';
26
- import { unlink, readFile } from 'fs/promises';
16
+ import { runCli } from '../cli/index.ts';
27
17
 
28
- const __filename = fileURLToPath(import.meta.url);
29
- const __dirname = dirname(__filename);
30
- const rootDir = dirname(__dirname);
31
-
32
- // Configuration
33
- const REPOS_BASE_URL = 'https://repos.fmode.cn/x/fmode-studio';
34
- const MANIFEST_URL = `${REPOS_BASE_URL}/manifest.json`;
35
- const CACHE_DIR = join(rootDir, '.cache', 'binaries');
36
-
37
- // Platform detection
38
- const platform = process.platform;
39
- const arch = process.arch;
40
-
41
- // Map platform/arch to executable name
42
- function getExecutableName() {
43
- const ext = platform === 'win32' ? '.exe' : '';
44
-
45
- if (platform === 'win32' && arch === 'x64') {
46
- return `fmode-win-x64${ext}`;
47
- } else if (platform === 'win32' && arch === 'arm64') {
48
- return `fmode-win-arm64${ext}`;
49
- } else if (platform === 'linux' && arch === 'x64') {
50
- return 'fmode-linux-x64';
51
- } else if (platform === 'linux' && arch === 'arm64') {
52
- return 'fmode-linux-arm64';
53
- } else if (platform === 'darwin' && arch === 'x64') {
54
- return 'fmode-macos-x64';
55
- } else if (platform === 'darwin' && arch === 'arm64') {
56
- return 'fmode-macos-arm64';
57
- } else {
58
- throw new Error(`Unsupported platform: ${platform} ${arch}`);
59
- }
60
- }
61
-
62
- // Get local package version
63
- async function getLocalVersion() {
64
- const packageJson = join(rootDir, 'package.json');
65
- try {
66
- const content = await readFile(packageJson, 'utf-8');
67
- const pkg = JSON.parse(content);
68
- return pkg.version;
69
- } catch {
70
- return null;
71
- }
72
- }
73
-
74
- // Fetch remote manifest from repos.fmode.cn
75
- async function fetchRemoteManifest() {
76
- try {
77
- const response = await fetch(MANIFEST_URL);
78
- if (!response.ok) {
79
- throw new Error(`HTTP ${response.status}`);
80
- }
81
- return await response.json();
82
- } catch (error) {
83
- console.warn(`Warning: Could not fetch remote manifest: ${error.message}`);
84
- return null;
85
- }
86
- }
87
-
88
- // Compare versions (returns positive if a > b, negative if a < b, 0 if equal)
89
- function compareVersions(a, b) {
90
- const partsA = a.split('.').map(Number);
91
- const partsB = b.split('.').map(Number);
92
-
93
- for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
94
- const partA = partsA[i] || 0;
95
- const partB = partsB[i] || 0;
96
- if (partA !== partB) {
97
- return partA - partB;
98
- }
99
- }
100
- return 0;
101
- }
102
-
103
- // Download binary from repos.fmode.cn
104
- async function downloadBinary(version, executableName) {
105
- const url = `${REPOS_BASE_URL}/${version}/${executableName}`;
106
- const tempPath = join(CACHE_DIR, `${executableName}.tmp`);
107
- const finalPath = join(CACHE_DIR, executableName);
108
-
109
- console.log(`Downloading ${url}...`);
110
-
111
- try {
112
- const response = await fetch(url);
113
- if (!response.ok) {
114
- throw new Error(`HTTP ${response.status}`);
115
- }
116
-
117
- // Ensure cache directory exists
118
- if (!existsSync(CACHE_DIR)) {
119
- mkdirSync(CACHE_DIR, { recursive: true });
120
- }
121
-
122
- // Download to temp file
123
- const fileStream = createWriteStream(tempPath);
124
- await pipeline(response.body, fileStream);
125
-
126
- // Make executable (Unix-like systems)
127
- if (platform !== 'win32') {
128
- chmodSync(tempPath, 0o755);
129
- }
130
-
131
- // Move to final path
132
- if (existsSync(finalPath)) {
133
- await unlink(finalPath);
134
- }
135
- renameSync(tempPath, finalPath);
136
-
137
- console.log(`Downloaded to ${finalPath}`);
138
- return finalPath;
139
- } catch (error) {
140
- // Clean up temp file on error
141
- if (existsSync(tempPath)) {
142
- await unlink(tempPath).catch(() => {});
143
- }
144
- throw error;
145
- }
146
- }
147
-
148
- // Check for updates
149
- async function checkForUpdates() {
150
- const localVersion = await getLocalVersion();
151
- const manifest = await fetchRemoteManifest();
152
-
153
- if (!manifest || !manifest.version) {
154
- console.log('Could not check for updates.');
155
- return null;
156
- }
157
-
158
- if (!localVersion) {
159
- console.log(`Remote version: ${manifest.version}`);
160
- console.log('Local version: unknown');
161
- return manifest.version;
162
- }
163
-
164
- const comparison = compareVersions(manifest.version, localVersion);
165
-
166
- if (comparison > 0) {
167
- console.log(`Update available: ${localVersion} -> ${manifest.version}`);
168
- return manifest.version;
169
- } else if (comparison === 0) {
170
- console.log(`Already up to date: ${localVersion}`);
171
- return null;
172
- } else {
173
- console.log(`Local version is newer: ${localVersion} > ${manifest.version}`);
174
- return null;
175
- }
176
- }
177
-
178
- // Run the executable
179
- async function runExecutable(executablePath, args) {
180
- const child = spawn(executablePath, args, {
181
- stdio: 'inherit',
182
- env: { ...process.env }
183
- });
184
-
185
- return new Promise((resolve, reject) => {
186
- child.on('exit', (code) => {
187
- resolve(code ?? 0);
188
- process.exit(code ?? 0);
189
- });
190
-
191
- child.on('error', (err) => {
192
- reject(err);
193
- });
194
- });
195
- }
196
-
197
- // Main execution
198
- async function main() {
199
- const args = process.argv.slice(2);
200
-
201
- // Handle -y flag (auto-confirm, no-op in our case)
202
- const yesIndex = args.indexOf('-y');
203
- if (yesIndex !== -1) {
204
- args.splice(yesIndex, 1);
205
- }
206
-
207
- // Handle update command
208
- if (args[0] === 'update') {
209
- console.log('Checking for updates...');
210
- const newVersion = await checkForUpdates();
211
-
212
- if (newVersion) {
213
- const executableName = getExecutableName();
214
- console.log(`\nDownloading version ${newVersion}...`);
215
- try {
216
- await downloadBinary(newVersion, executableName);
217
- console.log('\n✓ Update complete!');
218
- } catch (error) {
219
- console.error(`\n✗ Download failed: ${error.message}`);
220
- process.exit(1);
221
- }
222
- }
223
- process.exit(0);
224
- }
225
-
226
- // Handle --version flag
227
- if (args.includes('--version') || args.includes('-v')) {
228
- const localVersion = await getLocalVersion();
229
- if (localVersion) {
230
- console.log(`@fmode/studio v${localVersion}`);
231
- } else {
232
- console.log('@fmode/studio (version unknown)');
233
- }
234
- process.exit(0);
235
- }
236
-
237
- // Handle --help flag
238
- if (args.includes('--help') || (args.includes('-h') && !args.includes('--host'))) {
239
- console.log(`
240
- Fmode Code UI Server
241
-
242
- USAGE:
243
- fmode [OPTIONS]
244
- npx @fmode/studio [OPTIONS]
245
- fmode update Check and install updates
246
-
247
- OPTIONS:
248
- -p, --port <PORT> Set the server port (default: 6666)
249
- -h, --host <HOST> Set the server host (default: 0.0.0.0)
250
- --help Show this help message
251
- --version, -v Show version information
252
- -y Auto-confirm (no-op, for compatibility)
253
- update Check and install updates
254
-
255
- ENVIRONMENT VARIABLES:
256
- PORT Server port (default: 6666)
257
- HOST Server host (default: 0.0.0.0)
258
- VITE_IS_PLATFORM Set to 'true' for platform mode
259
-
260
- EXAMPLES:
261
- fmode # Start with default settings
262
- fmode --port 8080 # Start on port 8080
263
- npx @fmode/studio -y # Start with npx (auto-confirm)
264
- npx @fmode/studio --port 3000 # Start on port 3000
265
- fmode update # Check for updates
266
-
267
- UPDATE MECHANISM:
268
- The CLI automatically downloads the latest binary from repos.fmode.cn
269
- and caches it locally. Use 'fmode update' to manually check for updates.
270
- `);
271
- process.exit(0);
272
- }
273
-
274
- const executableName = getExecutableName();
275
- let executablePath = null;
276
-
277
- // Priority 1: Try cached binary (most recent download)
278
- const cachedPath = join(CACHE_DIR, executableName);
279
- if (existsSync(cachedPath)) {
280
- executablePath = cachedPath;
281
- console.log(`Using cached binary: ${cachedPath}`);
282
- }
283
-
284
- // Priority 2: Try bundled binary (shipped with npm package)
285
- if (!executablePath) {
286
- const bundledPath = join(rootDir, 'dist', 'bin', executableName);
287
- if (existsSync(bundledPath)) {
288
- executablePath = bundledPath;
289
- }
290
- }
291
-
292
- // Priority 3: Download from repos.fmode.cn
293
- if (!executablePath) {
294
- console.log('No local binary found. Downloading...');
295
- try {
296
- const manifest = await fetchRemoteManifest();
297
- if (manifest && manifest.version) {
298
- executablePath = await downloadBinary(manifest.version, executableName);
299
- }
300
- } catch (error) {
301
- console.warn(`Could not download binary: ${error.message}`);
302
- }
303
- }
304
-
305
- // If we have an executable, run it
306
- if (executablePath) {
307
- try {
308
- await runExecutable(executablePath, args);
309
- return;
310
- } catch (err) {
311
- console.error('Failed to start native executable:', err.message);
312
- // Fall through to Node.js/Bun fallback
313
- }
314
- }
315
-
316
- // Priority 4: Fallback to Node.js/Bun
317
- fallbackToNode(args);
318
- }
319
-
320
- function fallbackToNode(args) {
321
- const cliPath = join(rootDir, 'cli.ts');
322
-
323
- if (existsSync(cliPath)) {
324
- console.log('Running with bun...');
325
- const child = spawn('bun', [cliPath, ...args], {
326
- stdio: 'inherit',
327
- env: { ...process.env }
328
- });
329
-
330
- child.on('exit', (code) => {
331
- process.exit(code ?? 0);
332
- });
333
-
334
- child.on('error', (err) => {
335
- console.error('Failed to start with bun:', err.message);
336
- console.error('Please install bun: https://bun.sh');
337
- process.exit(1);
338
- });
339
- } else {
340
- console.error(`Error: Fmode Studio not found for platform ${platform} ${arch}`);
341
- console.error('Please reinstall the package or report this issue.');
342
- process.exit(1);
343
- }
344
- }
345
-
346
- // Run main function
347
- main().catch((error) => {
18
+ // Run the CLI
19
+ runCli().catch((error) => {
348
20
  console.error('Fatal error:', error);
349
21
  process.exit(1);
350
22
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fmode/studio",
3
- "version": "0.0.3",
3
+ "version": "0.0.6",
4
4
  "description": "AI PaaS IDE for Vibe Coding - Cross-platform CLI tool",
5
5
  "license": "ISC",
6
6
  "author": "Fmode Studio Team",