@fmode/studio 0.0.5 → 0.0.7

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 +6 -700
  2. package/package.json +1 -1
package/bin/fmode.js CHANGED
@@ -4,713 +4,19 @@
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
- * - Process management (start/stop)
11
- * - Cache clearing
12
- * - Smart upgrade with version detection
7
+ * This is a thin router that delegates to the modular CLI structure.
8
+ * All command logic is now in cli/ subdirectories.
13
9
  *
14
10
  * Usage:
15
11
  * npx @fmode/studio
16
12
  * npx @fmode/studio start
17
- * npx @fmode/studio stop
18
- * npx @fmode/studio clear
19
- * npx @fmode/studio upgrade
13
+ * npx @fmode/studio server start
20
14
  */
21
15
 
22
- import { spawn, exec } from 'child_process';
23
- import { fileURLToPath } from 'url';
24
- import { dirname, join } from 'path';
25
- import { existsSync, mkdirSync, chmodSync, renameSync, rmSync } from 'fs';
26
- import { createReadStream, createWriteStream } from 'fs';
27
- import { pipeline } from 'stream/promises';
28
- import { unlink, readFile, writeFile, readdir } from 'fs/promises';
29
- import { promisify } from 'util';
16
+ import { runCli } from '../cli/index.ts';
30
17
 
31
- const execAsync = promisify(exec);
32
-
33
- const __filename = fileURLToPath(import.meta.url);
34
- const __dirname = dirname(__filename);
35
- const rootDir = dirname(__dirname);
36
-
37
- // Configuration
38
- const REPOS_BASE_URL = 'https://repos.fmode.cn/x/fmode-studio';
39
- const MANIFEST_URL = `${REPOS_BASE_URL}/manifest.json`;
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');
45
-
46
- // Platform detection
47
- const platform = process.platform;
48
- const arch = process.arch;
49
-
50
- // Map platform/arch to executable name
51
- function getExecutableName() {
52
- const ext = platform === 'win32' ? '.exe' : '';
53
-
54
- if (platform === 'win32' && arch === 'x64') {
55
- return `fmode-win-x64${ext}`;
56
- } else if (platform === 'win32' && arch === 'arm64') {
57
- return `fmode-win-arm64${ext}`;
58
- } else if (platform === 'linux' && arch === 'x64') {
59
- return 'fmode-linux-x64';
60
- } else if (platform === 'linux' && arch === 'arm64') {
61
- return 'fmode-linux-arm64';
62
- } else if (platform === 'darwin' && arch === 'x64') {
63
- return 'fmode-macos-x64';
64
- } else if (platform === 'darwin' && arch === 'arm64') {
65
- return 'fmode-macos-arm64';
66
- } else {
67
- throw new Error(`Unsupported platform: ${platform} ${arch}`);
68
- }
69
- }
70
-
71
- // Get local package version
72
- async function getLocalVersion() {
73
- const packageJson = join(rootDir, 'package.json');
74
- try {
75
- const content = await readFile(packageJson, 'utf-8');
76
- const pkg = JSON.parse(content);
77
- return pkg.version;
78
- } catch {
79
- return null;
80
- }
81
- }
82
-
83
- // Fetch remote manifest from repos.fmode.cn
84
- async function fetchRemoteManifest() {
85
- try {
86
- const response = await fetch(MANIFEST_URL);
87
- if (!response.ok) {
88
- throw new Error(`HTTP ${response.status}`);
89
- }
90
- return await response.json();
91
- } catch (error) {
92
- console.warn(`Warning: Could not fetch remote manifest: ${error.message}`);
93
- return null;
94
- }
95
- }
96
-
97
- // Compare versions (returns positive if a > b, negative if a < b, 0 if equal)
98
- function compareVersions(a, b) {
99
- const partsA = a.split('.').map(Number);
100
- const partsB = b.split('.').map(Number);
101
-
102
- for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
103
- const partA = partsA[i] || 0;
104
- const partB = partsB[i] || 0;
105
- if (partA !== partB) {
106
- return partA - partB;
107
- }
108
- }
109
- return 0;
110
- }
111
-
112
- // Download binary from repos.fmode.cn
113
- async function downloadBinary(version, executableName) {
114
- const url = `${REPOS_BASE_URL}/${version}/${executableName}`;
115
- const tempPath = join(CACHE_DIR, `${executableName}.tmp`);
116
- const finalPath = join(CACHE_DIR, executableName);
117
-
118
- console.log(`Downloading ${url}...`);
119
-
120
- try {
121
- const response = await fetch(url);
122
- if (!response.ok) {
123
- throw new Error(`HTTP ${response.status}`);
124
- }
125
-
126
- // Ensure cache directory exists
127
- if (!existsSync(CACHE_DIR)) {
128
- mkdirSync(CACHE_DIR, { recursive: true });
129
- }
130
-
131
- // Download to temp file
132
- const fileStream = createWriteStream(tempPath);
133
- await pipeline(response.body, fileStream);
134
-
135
- // Make executable (Unix-like systems)
136
- if (platform !== 'win32') {
137
- chmodSync(tempPath, 0o755);
138
- }
139
-
140
- // Move to final path
141
- if (existsSync(finalPath)) {
142
- await unlink(finalPath);
143
- }
144
- renameSync(tempPath, finalPath);
145
-
146
- console.log(`Downloaded to ${finalPath}`);
147
- return finalPath;
148
- } catch (error) {
149
- // Clean up temp file on error
150
- if (existsSync(tempPath)) {
151
- await unlink(tempPath).catch(() => {});
152
- }
153
- throw error;
154
- }
155
- }
156
-
157
- // Get executable path (cached or bundled)
158
- async function getExecutablePath() {
159
- const executableName = getExecutableName();
160
-
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 {
231
- return null;
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
- }
251
-
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;
266
- }
267
- }
268
-
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';
275
-
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
- }
315
- return null;
316
- } catch {
317
- return null;
318
- }
319
- }
320
-
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, {
385
- stdio: 'inherit',
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);
393
- });
394
- }
395
-
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);
522
- });
523
-
524
- // 等待 tail 进程结束(用户按 Ctrl+C)
525
- await new Promise((resolve) => {
526
- tailCmd.on('exit', resolve);
527
- });
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
- }
598
- }
599
-
600
- // ============================================================
601
- // MAIN ENTRY POINT
602
- // ============================================================
603
- async function main() {
604
- const args = process.argv.slice(2);
605
-
606
- // Handle -y flag (auto-confirm)
607
- const yesIndex = args.indexOf('-y');
608
- if (yesIndex !== -1) {
609
- args.splice(yesIndex, 1);
610
- }
611
-
612
- // Handle --version flag FIRST (before switch)
613
- if (args.includes('--version') || (args.includes('-v') && !args.includes('-vvv'))) {
614
- const localVersion = await getLocalVersion();
615
- if (localVersion) {
616
- console.log(`@fmode/studio v${localVersion}`);
617
- } else {
618
- console.log('@fmode/studio (version unknown)');
619
- }
620
- process.exit(0);
621
- return;
622
- }
623
-
624
- // Handle --help flag FIRST (before switch)
625
- if (args.includes('--help') || (args.includes('-h') && !args.includes('--host'))) {
626
- console.log(`
627
- Fmode Code UI Server
628
-
629
- USAGE:
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
639
-
640
- OPTIONS:
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)
649
-
650
- EXAMPLES:
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.
666
- `);
667
- process.exit(0);
668
- return;
669
- }
670
-
671
- const command = args[0];
672
-
673
- // Handle commands
674
- switch (command) {
675
- case 'start':
676
- await cmdStart(args.slice(1));
677
- return;
678
-
679
- case 'stop':
680
- await cmdStop(args.slice(1));
681
- process.exit(0);
682
- return;
683
-
684
- case 'log':
685
- await cmdLog(args.slice(1));
686
- process.exit(0);
687
- return;
688
-
689
- case 'clear':
690
- await cmdClear();
691
- process.exit(0);
692
- return;
693
-
694
- case 'upgrade':
695
- await cmdUpgrade();
696
- process.exit(0);
697
- return;
698
-
699
- case 'update':
700
- // Legacy command - same as upgrade
701
- await cmdUpgrade();
702
- process.exit(0);
703
- return;
704
-
705
- default:
706
- // Default: start the server with all args (no command specified)
707
- await cmdStart(args);
708
- return;
709
- }
710
- }
711
-
712
- // Run main function
713
- main().catch((error) => {
18
+ // Run the CLI
19
+ runCli().catch((error) => {
714
20
  console.error('Fatal error:', error);
715
21
  process.exit(1);
716
22
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fmode/studio",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "AI PaaS IDE for Vibe Coding - Cross-platform CLI tool",
5
5
  "license": "ISC",
6
6
  "author": "Fmode Studio Team",