@fmode/studio 0.0.2 → 0.0.5

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 +511 -145
  2. package/package.json +2 -6
package/bin/fmode.js CHANGED
@@ -7,23 +7,28 @@
7
7
  * Features:
8
8
  * - Version checking against repos.fmode.cn
9
9
  * - Automatic binary download and caching
10
- * - Update command for manual updates
11
- * - Fallback to Node.js/Bun execution
10
+ * - Process management (start/stop)
11
+ * - Cache clearing
12
+ * - Smart upgrade with version detection
12
13
  *
13
14
  * Usage:
14
15
  * npx @fmode/studio
15
- * npx @fmode/studio -y
16
- * npx @fmode/studio update
17
- * fmode
16
+ * npx @fmode/studio start
17
+ * npx @fmode/studio stop
18
+ * npx @fmode/studio clear
19
+ * npx @fmode/studio upgrade
18
20
  */
19
21
 
20
- import { spawn } from 'child_process';
22
+ import { spawn, exec } from 'child_process';
21
23
  import { fileURLToPath } from 'url';
22
24
  import { dirname, join } from 'path';
23
- import { existsSync, mkdirSync, chmodSync, renameSync } from 'fs';
25
+ import { existsSync, mkdirSync, chmodSync, renameSync, rmSync } from 'fs';
24
26
  import { createReadStream, createWriteStream } from 'fs';
25
27
  import { pipeline } from 'stream/promises';
26
- import { unlink, readFile } from 'fs/promises';
28
+ import { unlink, readFile, writeFile, readdir } from 'fs/promises';
29
+ import { promisify } from 'util';
30
+
31
+ const execAsync = promisify(exec);
27
32
 
28
33
  const __filename = fileURLToPath(import.meta.url);
29
34
  const __dirname = dirname(__filename);
@@ -33,6 +38,10 @@ const rootDir = dirname(__dirname);
33
38
  const REPOS_BASE_URL = 'https://repos.fmode.cn/x/fmode-studio';
34
39
  const MANIFEST_URL = `${REPOS_BASE_URL}/manifest.json`;
35
40
  const CACHE_DIR = join(rootDir, '.cache', 'binaries');
41
+ const PID_DIR = join(rootDir, '.cache', 'pids');
42
+ const PID_FILE = join(PID_DIR, 'fmode.pid');
43
+ const LOG_DIR = join(rootDir, '.cache', 'logs');
44
+ const LOG_FILE = join(LOG_DIR, 'fmode.log');
36
45
 
37
46
  // Platform detection
38
47
  const platform = process.platform;
@@ -145,86 +154,463 @@ async function downloadBinary(version, executableName) {
145
154
  }
146
155
  }
147
156
 
148
- // Check for updates
149
- async function checkForUpdates() {
150
- const localVersion = await getLocalVersion();
151
- const manifest = await fetchRemoteManifest();
157
+ // Get executable path (cached or bundled)
158
+ async function getExecutablePath() {
159
+ const executableName = getExecutableName();
152
160
 
153
- if (!manifest || !manifest.version) {
154
- console.log('Could not check for updates.');
161
+ // Priority 1: Try cached binary
162
+ const cachedPath = join(CACHE_DIR, executableName);
163
+ if (existsSync(cachedPath)) {
164
+ return cachedPath;
165
+ }
166
+
167
+ // Priority 2: Try bundled binary
168
+ const bundledPath = join(rootDir, 'dist', 'bin', executableName);
169
+ if (existsSync(bundledPath)) {
170
+ return bundledPath;
171
+ }
172
+
173
+ return null;
174
+ }
175
+
176
+ // Get current binary version with timeout
177
+ async function getBinaryVersion(executablePath) {
178
+ return new Promise((resolve) => {
179
+ let output = '';
180
+ let timedOut = false;
181
+
182
+ const timeout = setTimeout(() => {
183
+ timedOut = true;
184
+ child.kill();
185
+ resolve(null); // Timeout = version unknown
186
+ }, 3000);
187
+
188
+ const child = spawn(executablePath, ['-vvv'], {
189
+ stdio: ['ignore', 'pipe', 'pipe'],
190
+ env: { ...process.env }
191
+ });
192
+
193
+ child.stdout.on('data', (data) => {
194
+ output += data.toString();
195
+ });
196
+
197
+ child.on('exit', (code) => {
198
+ clearTimeout(timeout);
199
+ if (timedOut) return;
200
+
201
+ // Parse version from output: "@fmode/studio version x.x.x"
202
+ const match = output.match(/version\s+(\d+\.\d+\.\d+)/);
203
+ if (match) {
204
+ resolve(match[1]);
205
+ } else {
206
+ resolve(null);
207
+ }
208
+ });
209
+
210
+ child.on('error', () => {
211
+ clearTimeout(timeout);
212
+ resolve(null);
213
+ });
214
+ });
215
+ }
216
+
217
+ // Save PID to file
218
+ function savePid(pid) {
219
+ if (!existsSync(PID_DIR)) {
220
+ mkdirSync(PID_DIR, { recursive: true });
221
+ }
222
+ writeFile(PID_FILE, String(pid));
223
+ }
224
+
225
+ // Read PID from file
226
+ async function readPid() {
227
+ try {
228
+ const content = await readFile(PID_FILE, 'utf-8');
229
+ return parseInt(content.trim(), 10);
230
+ } catch {
155
231
  return null;
156
232
  }
233
+ }
234
+
235
+ // Check if process is running
236
+ async function isProcessRunning(pid) {
237
+ try {
238
+ if (platform === 'win32') {
239
+ // Windows: 使用 tasklist 检查进程是否存在
240
+ const { stdout } = await execAsync(`tasklist /FI "PID eq ${pid}" /FO CSV`);
241
+ return stdout.includes(String(pid));
242
+ } else {
243
+ // Unix: 使用 process.kill(pid, 0) 检查
244
+ process.kill(pid, 0);
245
+ return true;
246
+ }
247
+ } catch {
248
+ return false;
249
+ }
250
+ }
157
251
 
158
- if (!localVersion) {
159
- console.log(`Remote version: ${manifest.version}`);
160
- console.log('Local version: unknown');
161
- return manifest.version;
252
+ // Kill process by PID
253
+ async function killProcess(pid) {
254
+ try {
255
+ if (platform === 'win32') {
256
+ // Windows: 使用 taskkill 强制终止进程
257
+ await execAsync(`taskkill /F /PID ${pid}`);
258
+ return true;
259
+ } else {
260
+ // Unix: 使用 process.kill
261
+ process.kill(pid);
262
+ return true;
263
+ }
264
+ } catch {
265
+ return false;
162
266
  }
267
+ }
163
268
 
164
- const comparison = compareVersions(manifest.version, localVersion);
269
+ // Find fmode processes
270
+ async function findFmodeProcesses() {
271
+ try {
272
+ const command = platform === 'win32'
273
+ ? 'tasklist /FI "IMAGENAME eq fmode*" /FO CSV'
274
+ : 'ps aux | grep -E "fmode|fmode-win|fmode-linux|fmode-macos" | grep -v grep';
165
275
 
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}`);
276
+ const { stdout } = await execAsync(command);
277
+ return stdout;
278
+ } catch {
279
+ return '';
280
+ }
281
+ }
282
+
283
+ // Find process by port and return PID
284
+ async function findProcessByPort(port) {
285
+ try {
286
+ let command;
287
+ if (platform === 'win32') {
288
+ // Windows: use netstat to find PID listening on port
289
+ command = `netstat -ano | findstr :${port} | findstr LISTENING`;
290
+ } else {
291
+ // Unix-like: use lsof
292
+ command = `lsof -ti:${port}`;
293
+ }
294
+
295
+ const { stdout } = await execAsync(command);
296
+
297
+ if (platform === 'win32') {
298
+ // Parse Windows netstat output: format is "protocol local_address foreign_address state pid"
299
+ // Example: TCP 0.0.0.0:16666 0.0.0.0:0 LISTENING 12345
300
+ const lines = stdout.trim().split('\n');
301
+ for (const line of lines) {
302
+ const parts = line.trim().split(/\s+/);
303
+ const pid = parseInt(parts[parts.length - 1], 10);
304
+ if (!isNaN(pid)) {
305
+ return pid;
306
+ }
307
+ }
308
+ } else {
309
+ // lsof -ti returns just the PID
310
+ const pid = parseInt(stdout.trim(), 10);
311
+ if (!isNaN(pid)) {
312
+ return pid;
313
+ }
314
+ }
171
315
  return null;
172
- } else {
173
- console.log(`Local version is newer: ${localVersion} > ${manifest.version}`);
316
+ } catch {
174
317
  return null;
175
318
  }
176
319
  }
177
320
 
178
- // Run the executable
179
- async function runExecutable(executablePath, args) {
180
- const child = spawn(executablePath, args, {
321
+ // Get port from args (default 16666)
322
+ function getPortFromArgs(args) {
323
+ const portIndex = args.indexOf('-p');
324
+ if (portIndex !== -1 && args[portIndex + 1]) {
325
+ return parseInt(args[portIndex + 1], 10);
326
+ }
327
+ const portIndex2 = args.indexOf('--port');
328
+ if (portIndex2 !== -1 && args[portIndex2 + 1]) {
329
+ return parseInt(args[portIndex2 + 1], 10);
330
+ }
331
+ return 16666; // default port
332
+ }
333
+
334
+ // Save PID with port info
335
+ function savePidWithPort(pid, port) {
336
+ if (!existsSync(PID_DIR)) {
337
+ mkdirSync(PID_DIR, { recursive: true });
338
+ }
339
+ // Save as "pid:port" format
340
+ writeFile(PID_FILE, `${pid}:${port}`);
341
+ }
342
+
343
+ // Read PID and port from file
344
+ async function readPidWithPort() {
345
+ try {
346
+ const content = await readFile(PID_FILE, 'utf-8');
347
+ const parts = content.trim().split(':');
348
+ if (parts.length === 2) {
349
+ return { pid: parseInt(parts[0], 10), port: parseInt(parts[1], 10) };
350
+ }
351
+ return { pid: parseInt(content.trim(), 10), port: null };
352
+ } catch {
353
+ return { pid: null, port: null };
354
+ }
355
+ }
356
+
357
+ // ============================================================
358
+ // COMMAND: START
359
+ // ============================================================
360
+ /**
361
+ * 启动 Fmode Studio 服务
362
+ * 直接启动二进制文件,输出直接显示在终端
363
+ */
364
+ async function cmdStart(args) {
365
+ const executablePath = await getExecutablePath();
366
+
367
+ // If no executable, download first
368
+ let finalExecutablePath = executablePath;
369
+ if (!finalExecutablePath) {
370
+ console.log('No local binary found. Downloading...');
371
+ const manifest = await fetchRemoteManifest();
372
+ if (manifest && manifest.version) {
373
+ finalExecutablePath = await downloadBinary(manifest.version, getExecutableName());
374
+ }
375
+ }
376
+
377
+ // 如果仍然没有可执行文件,退出
378
+ if (!finalExecutablePath) {
379
+ console.error('No executable found and could not download. Please run "fmode upgrade" first.');
380
+ process.exit(1);
381
+ }
382
+
383
+ // 直接启动二进制文件,不使用 detached,让输出直接显示在终端
384
+ spawn(finalExecutablePath, args, {
181
385
  stdio: 'inherit',
182
- env: { ...process.env }
386
+ env: {
387
+ ...process.env,
388
+ FORCE_COLOR: process.env.FORCE_COLOR || '1',
389
+ TERM: process.env.TERM || 'xterm-256color'
390
+ }
391
+ }).on('exit', (code) => {
392
+ process.exit(code ?? 0);
183
393
  });
394
+ }
184
395
 
185
- return new Promise((resolve, reject) => {
186
- child.on('exit', (code) => {
187
- resolve(code ?? 0);
188
- process.exit(code ?? 0);
396
+ // ============================================================
397
+ // COMMAND: STOP
398
+ // ============================================================
399
+ async function cmdStop(args) {
400
+ const port = getPortFromArgs(args);
401
+ const { pid: savedPid, port: savedPort } = await readPidWithPort();
402
+
403
+ // First try to find process by port
404
+ const portPid = await findProcessByPort(port);
405
+
406
+ if (portPid && await isProcessRunning(portPid)) {
407
+ console.log(`Stopping Fmode Studio on port ${port} (PID: ${portPid})...`);
408
+ if (await killProcess(portPid)) {
409
+ console.log('Fmode Studio stopped.');
410
+ await unlink(PID_FILE).catch(() => {});
411
+ return;
412
+ } else {
413
+ console.error('Failed to stop Fmode Studio.');
414
+ process.exit(1);
415
+ }
416
+ }
417
+
418
+ // If no process found on port, check saved PID
419
+ if (savedPid && await isProcessRunning(savedPid)) {
420
+ console.log(`Stopping Fmode Studio (PID: ${savedPid})...`);
421
+ if (await killProcess(savedPid)) {
422
+ console.log('Fmode Studio stopped.');
423
+ await unlink(PID_FILE).catch(() => {});
424
+ return;
425
+ } else {
426
+ console.error('Failed to stop Fmode Studio.');
427
+ process.exit(1);
428
+ }
429
+ }
430
+
431
+ // Clean up stale PID file
432
+ if (savedPid || portPid) {
433
+ console.log('Process not running. Cleaning up PID file...');
434
+ await unlink(PID_FILE).catch(() => {});
435
+ }
436
+
437
+ // Try to find any fmode processes
438
+ console.log(`No process found on port ${port}. Searching for Fmode processes...`);
439
+ const processes = await findFmodeProcesses();
440
+
441
+ if (processes.trim()) {
442
+ console.log('\nFound Fmode processes:');
443
+ console.log(processes);
444
+ console.log('\nPlease manually kill the process or use:');
445
+ console.log(platform === 'win32' ? ' taskkill /F /PID <pid>' : ' kill -9 <pid>');
446
+ } else {
447
+ console.log('No Fmode Studio process found running.');
448
+ }
449
+ }
450
+
451
+ // ============================================================
452
+ // COMMAND: CLEAR
453
+ // ============================================================
454
+ async function cmdClear() {
455
+ console.log('Clearing cached binaries...');
456
+
457
+ if (!existsSync(CACHE_DIR)) {
458
+ console.log('No cache directory found.');
459
+ return;
460
+ }
461
+
462
+ try {
463
+ const files = await readdir(CACHE_DIR);
464
+ let removedCount = 0;
465
+
466
+ for (const file of files) {
467
+ const filePath = join(CACHE_DIR, file);
468
+ try {
469
+ await unlink(filePath);
470
+ console.log(` Removed: ${file}`);
471
+ removedCount++;
472
+ } catch (err) {
473
+ console.warn(` Skipped: ${file} (${err.message})`);
474
+ }
475
+ }
476
+
477
+ console.log(`\nCleared ${removedCount} file(s).`);
478
+ } catch (error) {
479
+ console.error(`Failed to clear cache: ${error.message}`);
480
+ process.exit(1);
481
+ }
482
+ }
483
+
484
+ // ============================================================
485
+ // COMMAND: LOG
486
+ // ============================================================
487
+ async function cmdLog(args) {
488
+ // Check for -f or --follow flag
489
+ const followMode = args.includes('-f') || args.includes('--follow');
490
+ const linesIndex = args.indexOf('-n') !== -1 ? args.indexOf('-n') : args.indexOf('--lines');
491
+ const numLines = linesIndex !== -1 ? parseInt(args[linesIndex + 1] || '50', 10) : 50;
492
+
493
+ if (!existsSync(LOG_FILE)) {
494
+ console.log('No log file found. Has the server been started?');
495
+ console.log(`Expected log file: ${LOG_FILE}`);
496
+ return;
497
+ }
498
+
499
+ if (followMode) {
500
+ // Follow mode (tail -f)
501
+ console.log(`Following log file: ${LOG_FILE} (Ctrl+C to exit)`);
502
+ console.log(`${'='.repeat(60)}\n`);
503
+
504
+ let tailCmd;
505
+ if (platform === 'win32') {
506
+ // Windows: use Get-Content with -Wait
507
+ tailCmd = spawn('powershell', ['-Command', `Get-Content -Path "${LOG_FILE}" -Wait -Tail 50`], {
508
+ stdio: 'inherit',
509
+ windowsHide: false // 需要窗口显示输出
510
+ });
511
+ } else {
512
+ // Unix: use tail -f
513
+ tailCmd = spawn('tail', ['-n', String(numLines), '-f', LOG_FILE], {
514
+ stdio: 'inherit'
515
+ });
516
+ }
517
+
518
+ tailCmd.on('error', (err) => {
519
+ console.log(`Follow mode not available: ${err.message}`);
520
+ console.log('Showing current log content:');
521
+ showLogContent(numLines);
189
522
  });
190
523
 
191
- child.on('error', (err) => {
192
- reject(err);
524
+ // 等待 tail 进程结束(用户按 Ctrl+C)
525
+ await new Promise((resolve) => {
526
+ tailCmd.on('exit', resolve);
193
527
  });
194
- });
528
+ } else {
529
+ showLogContent(numLines);
530
+ }
531
+ }
532
+
533
+ async function showLogContent(numLines) {
534
+ const content = await readFile(LOG_FILE, 'utf-8');
535
+ const lines = content.split('\n');
536
+
537
+ if (lines.length <= numLines) {
538
+ console.log(content);
539
+ } else {
540
+ console.log(lines.slice(-numLines).join('\n'));
541
+ }
542
+ }
543
+
544
+ // ============================================================
545
+ // COMMAND: UPGRADE
546
+ // ============================================================
547
+ async function cmdUpgrade() {
548
+ console.log('Checking for updates...\n');
549
+
550
+ const manifest = await fetchRemoteManifest();
551
+
552
+ if (!manifest || !manifest.version) {
553
+ console.log('Could not fetch remote version.');
554
+ return;
555
+ }
556
+
557
+ const remoteVersion = manifest.version;
558
+ console.log(`Remote version: ${remoteVersion}`);
559
+
560
+ const executablePath = await getExecutablePath();
561
+
562
+ // Case 1: No binary found
563
+ if (!executablePath) {
564
+ console.log('No binary found. Downloading latest version...');
565
+ await downloadBinary(remoteVersion, getExecutableName());
566
+ console.log('\nUpgrade complete!');
567
+ return;
568
+ }
569
+
570
+ console.log(`Local binary: ${executablePath}`);
571
+
572
+ // Case 2: Check current binary version
573
+ console.log('Checking local binary version...');
574
+ const localVersion = await getBinaryVersion(executablePath);
575
+
576
+ if (localVersion) {
577
+ console.log(`Local version: ${localVersion}`);
578
+
579
+ const comparison = compareVersions(remoteVersion, localVersion);
580
+
581
+ if (comparison > 0) {
582
+ console.log(`Update available: ${localVersion} -> ${remoteVersion}`);
583
+ console.log('Downloading...');
584
+ await downloadBinary(remoteVersion, getExecutableName());
585
+ console.log('\nUpgrade complete!');
586
+ } else if (comparison === 0) {
587
+ console.log('Already up to date.');
588
+ } else {
589
+ console.log('Local version is newer.');
590
+ }
591
+ } else {
592
+ // Timeout or error getting version - download latest
593
+ console.log('Could not determine local version (timeout or error)');
594
+ console.log('Downloading latest version to ensure compatibility...');
595
+ await downloadBinary(remoteVersion, getExecutableName());
596
+ console.log('\nUpgrade complete!');
597
+ }
195
598
  }
196
599
 
197
- // Main execution
600
+ // ============================================================
601
+ // MAIN ENTRY POINT
602
+ // ============================================================
198
603
  async function main() {
199
604
  const args = process.argv.slice(2);
200
605
 
201
- // Handle -y flag (auto-confirm, no-op in our case)
606
+ // Handle -y flag (auto-confirm)
202
607
  const yesIndex = args.indexOf('-y');
203
608
  if (yesIndex !== -1) {
204
609
  args.splice(yesIndex, 1);
205
610
  }
206
611
 
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')) {
612
+ // Handle --version flag FIRST (before switch)
613
+ if (args.includes('--version') || (args.includes('-v') && !args.includes('-vvv'))) {
228
614
  const localVersion = await getLocalVersion();
229
615
  if (localVersion) {
230
616
  console.log(`@fmode/studio v${localVersion}`);
@@ -232,114 +618,94 @@ async function main() {
232
618
  console.log('@fmode/studio (version unknown)');
233
619
  }
234
620
  process.exit(0);
621
+ return;
235
622
  }
236
623
 
237
- // Handle --help flag
624
+ // Handle --help flag FIRST (before switch)
238
625
  if (args.includes('--help') || (args.includes('-h') && !args.includes('--host'))) {
239
626
  console.log(`
240
627
  Fmode Code UI Server
241
628
 
242
629
  USAGE:
243
- fmode [OPTIONS]
244
- npx @fmode/studio [OPTIONS]
245
- fmode update Check and install updates
630
+ fmode <COMMAND> [OPTIONS]
631
+ npx @fmode/studio <COMMAND> [OPTIONS]
632
+
633
+ COMMANDS:
634
+ start Start the Fmode Studio server (default)
635
+ stop Stop the running Fmode Studio server
636
+ log View server logs
637
+ clear Clear cached binary files
638
+ upgrade / update Upgrade to the latest version
246
639
 
247
640
  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
641
+ -p, --port <PORT> Set the server port (default: 16666)
642
+ -h, --host <HOST> Set the server host (default: 0.0.0.0)
643
+ --help Show this help message
644
+ --version, -v Show version information
645
+
646
+ LOG OPTIONS:
647
+ -f, --follow Follow log output (like tail -f)
648
+ -n, --lines <NUM> Number of lines to show (default: 50)
259
649
 
260
650
  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.
651
+ fmode # Start with default settings
652
+ fmode start # Explicitly start the server
653
+ fmode start --port 8080 # Start on port 8080
654
+ fmode --port 8080 # Start on port 8080 (same as above)
655
+ fmode stop # Stop the server
656
+ fmode stop --port 8080 # Stop server on port 8080
657
+ fmode log # Show recent logs
658
+ fmode log -f # Follow logs in real-time
659
+ fmode log -n 100 # Show last 100 log lines
660
+ fmode clear # Clear cached binaries
661
+ fmode upgrade # Upgrade to latest version
662
+
663
+ UPGRADE MECHANISM:
664
+ The CLI automatically downloads the latest binary from repos.fmode.cn.
665
+ Use 'fmode upgrade' to manually upgrade.
270
666
  `);
271
667
  process.exit(0);
668
+ return;
272
669
  }
273
670
 
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
- }
671
+ const command = args[0];
291
672
 
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
- }
673
+ // Handle commands
674
+ switch (command) {
675
+ case 'start':
676
+ await cmdStart(args.slice(1));
677
+ return;
304
678
 
305
- // If we have an executable, run it
306
- if (executablePath) {
307
- try {
308
- await runExecutable(executablePath, args);
679
+ case 'stop':
680
+ await cmdStop(args.slice(1));
681
+ process.exit(0);
309
682
  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
683
 
316
- // Priority 4: Fallback to Node.js/Bun
317
- fallbackToNode(args);
318
- }
684
+ case 'log':
685
+ await cmdLog(args.slice(1));
686
+ process.exit(0);
687
+ return;
319
688
 
320
- function fallbackToNode(args) {
321
- const cliPath = join(rootDir, 'cli.ts');
689
+ case 'clear':
690
+ await cmdClear();
691
+ process.exit(0);
692
+ return;
322
693
 
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
- });
694
+ case 'upgrade':
695
+ await cmdUpgrade();
696
+ process.exit(0);
697
+ return;
329
698
 
330
- child.on('exit', (code) => {
331
- process.exit(code ?? 0);
332
- });
699
+ case 'update':
700
+ // Legacy command - same as upgrade
701
+ await cmdUpgrade();
702
+ process.exit(0);
703
+ return;
333
704
 
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);
705
+ default:
706
+ // Default: start the server with all args (no command specified)
707
+ await cmdStart(args);
708
+ return;
343
709
  }
344
710
  }
345
711
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fmode/studio",
3
- "version": "0.0.2",
3
+ "version": "0.0.5",
4
4
  "description": "AI PaaS IDE for Vibe Coding - Cross-platform CLI tool",
5
5
  "license": "ISC",
6
6
  "author": "Fmode Studio Team",
@@ -28,9 +28,5 @@
28
28
  "x64",
29
29
  "arm64"
30
30
  ],
31
- "dependencies": {
32
- "@aws-sdk/client-s3": "^3.965.0",
33
- "@siteboon/claude-code-ui": "^1.13.6",
34
- "bun-pty": "^0.4.6"
35
- }
31
+ "dependencies": {}
36
32
  }