@leejungkiin/awkit 1.3.8 → 1.4.2

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 (135) hide show
  1. package/bin/awk.js +630 -52
  2. package/bin/claude-generators.js +122 -0
  3. package/core/AGENTS.md +54 -0
  4. package/core/CLAUDE.md +155 -0
  5. package/core/GEMINI.md +44 -9
  6. package/core/GEMINI.md.bak +126 -199
  7. package/package.json +1 -1
  8. package/skills/ai-sprite-maker/SKILL.md +81 -0
  9. package/skills/ai-sprite-maker/scripts/animate_sprite.py +102 -0
  10. package/skills/ai-sprite-maker/scripts/process_sprites.py +140 -0
  11. package/skills/awf-session-restore/SKILL.md +12 -2
  12. package/skills/brainstorm-agent/SKILL.md +11 -8
  13. package/skills/code-review/SKILL.md +21 -33
  14. package/skills/gitnexus/gitnexus-cli/SKILL.md +82 -0
  15. package/skills/gitnexus/gitnexus-debugging/SKILL.md +89 -0
  16. package/skills/gitnexus/gitnexus-exploring/SKILL.md +78 -0
  17. package/skills/gitnexus/gitnexus-guide/SKILL.md +64 -0
  18. package/skills/gitnexus/gitnexus-impact-analysis/SKILL.md +97 -0
  19. package/skills/gitnexus/gitnexus-refactoring/SKILL.md +121 -0
  20. package/skills/lucylab-tts/SKILL.md +64 -0
  21. package/skills/lucylab-tts/resources/voices_library.json +908 -0
  22. package/skills/lucylab-tts/scripts/.env +1 -0
  23. package/skills/lucylab-tts/scripts/lucylab_tts.py +506 -0
  24. package/skills/nm-memory-sync/SKILL.md +14 -1
  25. package/skills/orchestrator/SKILL.md +5 -38
  26. package/skills/ship-to-code/SKILL.md +115 -0
  27. package/skills/short-maker/SKILL.md +150 -0
  28. package/skills/short-maker/_backup/storyboard.html +106 -0
  29. package/skills/short-maker/_backup/video_mixer.py +296 -0
  30. package/skills/short-maker/outputs/fitbite-promo/background.jpg +0 -0
  31. package/skills/short-maker/outputs/fitbite-promo/final/promo-final.mp4 +0 -0
  32. package/skills/short-maker/outputs/fitbite-promo/script.md +19 -0
  33. package/skills/short-maker/outputs/fitbite-promo/segments/scene-01.mp4 +0 -0
  34. package/skills/short-maker/outputs/fitbite-promo/segments/scene-02.mp4 +0 -0
  35. package/skills/short-maker/outputs/fitbite-promo/segments/scene-03.mp4 +0 -0
  36. package/skills/short-maker/outputs/fitbite-promo/segments/scene-04.mp4 +0 -0
  37. package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-01.png +0 -0
  38. package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-02.png +0 -0
  39. package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-03.png +0 -0
  40. package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-04.png +0 -0
  41. package/skills/short-maker/outputs/fitbite-promo/storyboard.html +133 -0
  42. package/skills/short-maker/outputs/fitbite-promo/storyboard.json +38 -0
  43. package/skills/short-maker/outputs/fitbite-promo/temp/merged_chroma.mp4 +0 -0
  44. package/skills/short-maker/outputs/fitbite-promo/temp/merged_crossfaded.mp4 +0 -0
  45. package/skills/short-maker/outputs/fitbite-promo/temp/ready_00.mp4 +0 -0
  46. package/skills/short-maker/outputs/fitbite-promo/temp/ready_01.mp4 +0 -0
  47. package/skills/short-maker/outputs/fitbite-promo/temp/ready_02.mp4 +0 -0
  48. package/skills/short-maker/outputs/fitbite-promo/temp/ready_03.mp4 +0 -0
  49. package/skills/short-maker/outputs/fitbite-promo/tts/manifest.json +31 -0
  50. package/skills/short-maker/outputs/fitbite-promo/tts/scene-01.wav +0 -0
  51. package/skills/short-maker/outputs/fitbite-promo/tts/scene-02.wav +0 -0
  52. package/skills/short-maker/outputs/fitbite-promo/tts/scene-03.wav +0 -0
  53. package/skills/short-maker/outputs/fitbite-promo/tts/scene-04.wav +0 -0
  54. package/skills/short-maker/outputs/fitbite-promo/tts_script.txt +11 -0
  55. package/skills/short-maker/scripts/google-flow-cli/.project-identity +41 -0
  56. package/skills/short-maker/scripts/google-flow-cli/.trae/rules/project_rules.md +52 -0
  57. package/skills/short-maker/scripts/google-flow-cli/CODEBASE.md +67 -0
  58. package/skills/short-maker/scripts/google-flow-cli/GoogleFlowCli.code-workspace +29 -0
  59. package/skills/short-maker/scripts/google-flow-cli/README.md +168 -0
  60. package/skills/short-maker/scripts/google-flow-cli/docs/specs/PROJECT.md +12 -0
  61. package/skills/short-maker/scripts/google-flow-cli/docs/specs/REQUIREMENTS.md +22 -0
  62. package/skills/short-maker/scripts/google-flow-cli/docs/specs/ROADMAP.md +16 -0
  63. package/skills/short-maker/scripts/google-flow-cli/docs/specs/TECH-SPEC.md +13 -0
  64. package/skills/short-maker/scripts/google-flow-cli/gflow/__init__.py +3 -0
  65. package/skills/short-maker/scripts/google-flow-cli/gflow/api/__init__.py +19 -0
  66. package/skills/short-maker/scripts/google-flow-cli/gflow/api/client.py +1921 -0
  67. package/skills/short-maker/scripts/google-flow-cli/gflow/api/models.py +64 -0
  68. package/skills/short-maker/scripts/google-flow-cli/gflow/api/rpc_ids.py +98 -0
  69. package/skills/short-maker/scripts/google-flow-cli/gflow/auth/__init__.py +15 -0
  70. package/skills/short-maker/scripts/google-flow-cli/gflow/auth/browser_auth.py +692 -0
  71. package/skills/short-maker/scripts/google-flow-cli/gflow/auth/humanizer.py +417 -0
  72. package/skills/short-maker/scripts/google-flow-cli/gflow/auth/proxy_ext.py +120 -0
  73. package/skills/short-maker/scripts/google-flow-cli/gflow/auth/recaptcha.py +482 -0
  74. package/skills/short-maker/scripts/google-flow-cli/gflow/batchexecute/__init__.py +5 -0
  75. package/skills/short-maker/scripts/google-flow-cli/gflow/batchexecute/client.py +414 -0
  76. package/skills/short-maker/scripts/google-flow-cli/gflow/cli/__init__.py +1 -0
  77. package/skills/short-maker/scripts/google-flow-cli/gflow/cli/main.py +1075 -0
  78. package/skills/short-maker/scripts/google-flow-cli/pyproject.toml +36 -0
  79. package/skills/short-maker/scripts/google-flow-cli/script.txt +22 -0
  80. package/skills/short-maker/scripts/google-flow-cli/tests/__init__.py +0 -0
  81. package/skills/short-maker/scripts/google-flow-cli/tests/test_batchexecute.py +113 -0
  82. package/skills/short-maker/scripts/google-flow-cli/tests/test_client.py +190 -0
  83. package/skills/short-maker/templates/aida_script.md +40 -0
  84. package/skills/short-maker/templates/mimic_analyzer.md +29 -0
  85. package/skills/single-flow-task-execution/SKILL.md +412 -0
  86. package/skills/single-flow-task-execution/code-quality-reviewer-prompt.md +20 -0
  87. package/skills/single-flow-task-execution/implementer-prompt.md +78 -0
  88. package/skills/single-flow-task-execution/spec-reviewer-prompt.md +61 -0
  89. package/skills/skill-creator/SKILL.md +44 -0
  90. package/skills/spm-build-analysis/SKILL.md +92 -0
  91. package/skills/spm-build-analysis/references/build-optimization-sources.md +155 -0
  92. package/skills/spm-build-analysis/references/recommendation-format.md +85 -0
  93. package/skills/spm-build-analysis/references/spm-analysis-checks.md +105 -0
  94. package/skills/spm-build-analysis/scripts/check_spm_pins.py +118 -0
  95. package/skills/symphony-enforcer/SKILL.md +83 -97
  96. package/skills/symphony-orchestrator/SKILL.md +1 -1
  97. package/skills/trello-sync/SKILL.md +52 -45
  98. package/skills/verification-gate/SKILL.md +13 -2
  99. package/skills/xcode-build-benchmark/SKILL.md +88 -0
  100. package/skills/xcode-build-benchmark/references/benchmark-artifacts.md +94 -0
  101. package/skills/xcode-build-benchmark/references/benchmarking-workflow.md +67 -0
  102. package/skills/xcode-build-benchmark/schemas/build-benchmark.schema.json +230 -0
  103. package/skills/xcode-build-benchmark/scripts/benchmark_builds.py +308 -0
  104. package/skills/xcode-build-fixer/SKILL.md +218 -0
  105. package/skills/xcode-build-fixer/references/build-settings-best-practices.md +216 -0
  106. package/skills/xcode-build-fixer/references/fix-patterns.md +290 -0
  107. package/skills/xcode-build-fixer/references/recommendation-format.md +85 -0
  108. package/skills/xcode-build-fixer/scripts/benchmark_builds.py +308 -0
  109. package/skills/xcode-build-orchestrator/SKILL.md +156 -0
  110. package/skills/xcode-build-orchestrator/references/benchmark-artifacts.md +94 -0
  111. package/skills/xcode-build-orchestrator/references/build-settings-best-practices.md +216 -0
  112. package/skills/xcode-build-orchestrator/references/orchestration-report-template.md +143 -0
  113. package/skills/xcode-build-orchestrator/references/recommendation-format.md +85 -0
  114. package/skills/xcode-build-orchestrator/scripts/benchmark_builds.py +308 -0
  115. package/skills/xcode-build-orchestrator/scripts/diagnose_compilation.py +273 -0
  116. package/skills/xcode-build-orchestrator/scripts/generate_optimization_report.py +533 -0
  117. package/skills/xcode-compilation-analyzer/SKILL.md +89 -0
  118. package/skills/xcode-compilation-analyzer/references/build-optimization-sources.md +155 -0
  119. package/skills/xcode-compilation-analyzer/references/code-compilation-checks.md +106 -0
  120. package/skills/xcode-compilation-analyzer/references/recommendation-format.md +85 -0
  121. package/skills/xcode-compilation-analyzer/scripts/diagnose_compilation.py +273 -0
  122. package/skills/xcode-project-analyzer/SKILL.md +76 -0
  123. package/skills/xcode-project-analyzer/references/build-optimization-sources.md +155 -0
  124. package/skills/xcode-project-analyzer/references/build-settings-best-practices.md +216 -0
  125. package/skills/xcode-project-analyzer/references/project-audit-checks.md +101 -0
  126. package/skills/xcode-project-analyzer/references/recommendation-format.md +85 -0
  127. package/templates/CODEBASE.md +26 -42
  128. package/templates/configs/trello-config.json +2 -2
  129. package/templates/workflow_dual_mode_template.md +5 -5
  130. package/workflows/_uncategorized/conductor-codex.md +125 -0
  131. package/workflows/_uncategorized/conductor.md +97 -0
  132. package/workflows/_uncategorized/ship-to-code.md +85 -0
  133. package/workflows/_uncategorized/trello-sync.md +52 -0
  134. package/workflows/context/codebase-sync.md +10 -87
  135. package/workflows/quality/visual-debug.md +66 -12
package/bin/awk.js CHANGED
@@ -36,6 +36,7 @@ const HOME = process.env.HOME || process.env.USERPROFILE;
36
36
 
37
37
  const { generateClineRules, generateClineWorkflows, generateClineSkills } = require('./cline-generators');
38
38
  const { generateCodexAgentsMd, generateCodexSkills, generateCodexAgents } = require('./codex-generators');
39
+ const { generateClaudeRules, generateClaudeSkills } = require('./claude-generators');
39
40
 
40
41
  // ─── Platform Definitions ──────────────────────────────────────────────────
41
42
 
@@ -73,6 +74,15 @@ const PLATFORMS = {
73
74
  agents: 'agents',
74
75
  skills: '../.agents/skills',
75
76
  },
77
+ },
78
+ claude: {
79
+ name: 'Claude Code',
80
+ globalRoot: process.cwd(), // Local to project
81
+ rulesFile: 'CLAUDE.md',
82
+ versionFile: '.claude/awk_version',
83
+ dirs: {
84
+ skills: '.claude/skills',
85
+ },
76
86
  }
77
87
  };
78
88
 
@@ -290,35 +300,90 @@ function checkSymphony({ silent = false } = {}) {
290
300
  }
291
301
  }
292
302
 
293
- function cmdInstall(platformArg) {
303
+ function cmdInstall(args = []) {
294
304
  log('');
295
305
  log(`${C.cyan}${C.bold}╔══════════════════════════════════════════════════════════╗${C.reset}`);
296
306
  log(`${C.cyan}${C.bold}║ 🚀 AWK v${AWK_VERSION} — Antigravity Workflow Kit ║${C.reset}`);
297
307
  log(`${C.cyan}${C.bold}╚══════════════════════════════════════════════════════════╝${C.reset}`);
298
308
  log('');
299
309
 
300
- // Platform selection
301
- let platform = platformArg || getActivePlatform();
310
+ const isUpdate = args.includes('--update');
311
+ let selectedPlatforms = [];
302
312
 
303
- if (!PLATFORMS[platform]) {
304
- err(`Unknown platform: ${platform}.`);
305
- return;
313
+ if (args.includes('--all') || args.includes('-a') || args.includes('all')) {
314
+ selectedPlatforms = Object.keys(PLATFORMS);
315
+ } else {
316
+ if (args.includes('--gemini') || args.includes('-g') || args.includes('antigravity')) selectedPlatforms.push('antigravity');
317
+ if (args.includes('--cline') || args.includes('cline')) selectedPlatforms.push('cline');
318
+ if (args.includes('--codex') || args.includes('-x')) selectedPlatforms.push('codex');
319
+ if (args.includes('--claude-code') || args.includes('--claude') || args.includes('-c') || args.includes('claude')) selectedPlatforms.push('claude');
320
+
321
+ const pIdx = args.indexOf('--platform');
322
+ let legacyArg = null;
323
+ if (pIdx !== -1 && args[pIdx + 1]) {
324
+ legacyArg = args[pIdx + 1];
325
+ } else if (args.length > 0 && !args[0].startsWith('-') && args[0] !== 'all' && args[0] !== '--update') {
326
+ legacyArg = args[0];
327
+ }
328
+
329
+ if (legacyArg && PLATFORMS[legacyArg] && !selectedPlatforms.includes(legacyArg)) {
330
+ selectedPlatforms.push(legacyArg);
331
+ }
306
332
  }
307
333
 
308
- activePlatform = platform;
309
- savePlatform(platform);
334
+ if (selectedPlatforms.length === 0) {
335
+ if (isUpdate) {
336
+ selectedPlatforms = [getActivePlatform()];
337
+ } else {
338
+ log(`${C.cyan}Select platforms to install (e.g., type "1,2", "all", or "1,2,3,4"):${C.reset}`);
339
+ log(` 1. Gemini Code Assist (antigravity)`);
340
+ log(` 2. Cline (VS Code)`);
341
+ log(` 3. Codex CLI (codex)`);
342
+ log(` 4. Claude Code (.claude/)`);
343
+ log(` 5. All of the above`);
344
+ const choice = promptChoice('Choice', '5').trim().toLowerCase();
345
+
346
+ if (choice === '5' || choice === 'all' || choice === '') {
347
+ selectedPlatforms = Object.keys(PLATFORMS);
348
+ } else {
349
+ if (choice.includes('1')) selectedPlatforms.push('antigravity');
350
+ if (choice.includes('2')) selectedPlatforms.push('cline');
351
+ if (choice.includes('3')) selectedPlatforms.push('codex');
352
+ if (choice.includes('4')) selectedPlatforms.push('claude');
353
+ }
354
+ }
355
+ }
310
356
 
311
- const plat = PLATFORMS[platform];
312
- const target = plat.globalRoot;
357
+ if (selectedPlatforms.length === 0) {
358
+ selectedPlatforms = [getActivePlatform()];
359
+ }
313
360
 
314
- info(`Installing for ${C.bold}${plat.name}${C.reset}...`);
315
- log('');
361
+ log(`${C.cyan}Installing to: ${selectedPlatforms.map(p => PLATFORMS[p].name).join(', ')}${C.reset}`);
362
+
363
+ // Main installation loop
364
+ for (const platform of selectedPlatforms) {
365
+ if (!PLATFORMS[platform]) {
366
+ err(`Unknown platform: ${platform}.`);
367
+ continue;
368
+ }
369
+
370
+ activePlatform = platform;
371
+ if (platform === selectedPlatforms[0]) {
372
+ savePlatform(platform); // Store primary platform
373
+ }
316
374
 
317
- // 0. Check Symphony dependency
318
- info('Checking dependencies...');
319
- checkSymphony();
375
+ const plat = PLATFORMS[platform];
376
+ const target = plat.globalRoot;
320
377
 
321
- // 1. Ensure target dirs exist
378
+ log('');
379
+ info(`Installing for ${C.bold}${plat.name}${C.reset}...`);
380
+ log('');
381
+
382
+ // 0. Check Symphony dependency
383
+ info('Checking dependencies...');
384
+ checkSymphony({ silent: platform !== selectedPlatforms[0] });
385
+
386
+ // 1. Ensure target dirs exist
322
387
  info('Creating directories...');
323
388
  const dirKeys = Object.values(plat.dirs);
324
389
  for (const dir of dirKeys) {
@@ -339,6 +404,11 @@ function cmdInstall(platformArg) {
339
404
  } else if (platform === 'codex') {
340
405
  info('Generating Codex AGENTS.md...');
341
406
  generateCodexAgentsMd(path.join(AWK_ROOT, 'core', 'GEMINI.md'), plat.rulesFile);
407
+ } else if (platform === 'claude') {
408
+ info('Generating Claude Code CLAUDE.md...');
409
+ const claudeTemplateSrc = path.join(AWK_ROOT, 'core', 'CLAUDE.md');
410
+ const claudeRulesDest = path.join(target, plat.rulesFile);
411
+ generateClaudeRules(claudeTemplateSrc, claudeRulesDest);
342
412
  }
343
413
 
344
414
  // 3. Backup and install workflows
@@ -392,6 +462,8 @@ function cmdInstall(platformArg) {
392
462
  generateCodexSkills(skillsSrc, skillsDest);
393
463
  const agentsDest = path.join(target, plat.dirs.agents);
394
464
  generateCodexAgents(skillsSrc, agentsDest);
465
+ } else if (platform === 'claude') {
466
+ generateClaudeSkills(skillsSrc, skillsDest);
395
467
  } else {
396
468
  const skillCount = copyDirRecursive(skillsSrc, skillsDest);
397
469
  ok(`${skillCount} skill files installed`);
@@ -462,6 +534,7 @@ function cmdInstall(platformArg) {
462
534
  }
463
535
  log(`${C.cyan}👉 Run 'awkit doctor' to verify installation.${C.reset}`);
464
536
  log('');
537
+ } // End of platform loop
465
538
  }
466
539
 
467
540
  /**
@@ -570,7 +643,7 @@ function cmdUpdate() {
570
643
  }
571
644
  log('');
572
645
  info('Applying new workflows, skills & schemas...');
573
- cmdInstall();
646
+ cmdInstall(['--update']);
574
647
  return;
575
648
  }
576
649
 
@@ -579,7 +652,7 @@ function cmdUpdate() {
579
652
  ok(`Already on latest version (v${AWK_VERSION}) — could not verify with npm`);
580
653
  } else {
581
654
  info(`Upgrading from v${installedVersion} → v${AWK_VERSION} (local only, npm unreachable)`);
582
- cmdInstall();
655
+ cmdInstall(['--update']);
583
656
  }
584
657
  }
585
658
 
@@ -663,6 +736,77 @@ function cmdDoctor() {
663
736
  log('');
664
737
  }
665
738
 
739
+ /**
740
+ * Handle browser-related tasks (e.g., cleaning up recordings).
741
+ */
742
+ function cmdBrowser(args) {
743
+ if (args[0] !== 'clean') {
744
+ err('Unknown browser command. Use "awkit browser clean".');
745
+ return;
746
+ }
747
+
748
+ const recordingsDir = path.join(TARGETS.antigravity, 'browser_recordings');
749
+
750
+ log('');
751
+ log(`${C.cyan}${C.bold}🧹 AWK Browser Cleanup${C.reset}`);
752
+ log('');
753
+
754
+ if (!fs.existsSync(recordingsDir)) {
755
+ ok(`No browser_recordings directory found at ${recordingsDir}. Nothing to clean.`);
756
+ return;
757
+ }
758
+
759
+ const files = fs.readdirSync(recordingsDir).filter(f => f.endsWith('.webm') || f.endsWith('.webp') || f.endsWith('.mp4'));
760
+ if (files.length === 0) {
761
+ ok('No browser recordings found. Nothing to clean.');
762
+ return;
763
+ }
764
+
765
+ let keepDays = 7; // default 7 days
766
+ const daysArgIdx = args.indexOf('--days');
767
+ if (daysArgIdx !== -1 && args[daysArgIdx + 1]) {
768
+ keepDays = parseInt(args[daysArgIdx + 1], 10);
769
+ } else if (args.includes('--all')) {
770
+ keepDays = 0;
771
+ }
772
+
773
+ if (keepDays === 0) {
774
+ log(`Cleaning ${C.yellow}ALL${C.reset} browser recordings...`);
775
+ } else {
776
+ log(`Cleaning browser recordings older than ${C.yellow}${keepDays} days${C.reset}...`);
777
+ }
778
+
779
+ const now = Date.now();
780
+ const cutoff = now - (keepDays * 24 * 60 * 60 * 1000);
781
+ let deletedCount = 0;
782
+ let totalSizeFreed = 0;
783
+
784
+ for (const file of files) {
785
+ const filePath = path.join(recordingsDir, file);
786
+ try {
787
+ const stats = fs.statSync(filePath);
788
+ if (stats.mtimeMs < cutoff) {
789
+ totalSizeFreed += stats.size;
790
+ fs.unlinkSync(filePath);
791
+ deletedCount++;
792
+ dim(`Deleted: ${file}`);
793
+ }
794
+ } catch (e) {
795
+ warn(`Failed to process ${file}: ${e.message}`);
796
+ }
797
+ }
798
+
799
+ log('');
800
+ const sizeMb = (totalSizeFreed / (1024 * 1024)).toFixed(2);
801
+ if (deletedCount > 0) {
802
+ ok(`Cleaned ${C.green}${C.bold}${deletedCount}${C.reset} recording(s).`);
803
+ ok(`Freed ${C.green}${C.bold}${sizeMb} MB${C.reset} of disk space.`);
804
+ } else {
805
+ ok(`No recordings older than ${keepDays} days found. Disk space is already optimized.`);
806
+ }
807
+ log('');
808
+ }
809
+
666
810
  /**
667
811
  * Find a compatible Python interpreter meeting the minimum version requirement.
668
812
  * Tries python3.13, python3.12, python3.11, python3, python in order.
@@ -1327,6 +1471,95 @@ function cmdSync() {
1327
1471
  log('');
1328
1472
  }
1329
1473
 
1474
+ async function cmdAdmin() {
1475
+ info('Mở Symphony Dashboard...');
1476
+ try {
1477
+ const http = require('http');
1478
+ const isRunning = await new Promise((resolve) => {
1479
+ const req = http.get('http://localhost:3100', (res) => {
1480
+ resolve(true);
1481
+ }).on('error', () => {
1482
+ resolve(false);
1483
+ });
1484
+ req.setTimeout(1000, () => {
1485
+ req.destroy();
1486
+ resolve(false);
1487
+ });
1488
+ });
1489
+
1490
+ if (!isRunning) {
1491
+ info('Symphony service chưa chạy. Đang khởi động ngầm...');
1492
+ const { spawn } = require('child_process');
1493
+ const child = spawn('symphony', ['start'], {
1494
+ detached: true,
1495
+ stdio: 'ignore'
1496
+ });
1497
+ child.unref();
1498
+
1499
+ info('Vui lòng đợi 3 giây để service khởi động...');
1500
+ await new Promise(r => setTimeout(r, 3000));
1501
+ }
1502
+
1503
+ try {
1504
+ execSync('symphony dashboard', { stdio: 'inherit' });
1505
+ } catch (_) {
1506
+ err('Không thể mở Symphony Dashboard.');
1507
+ }
1508
+ } catch (e) {
1509
+ err('Không thể khởi động Symphony Dashboard.');
1510
+ dim('Vui lòng cài đặt: npm install -g @leejungkiin/awkit-symphony');
1511
+ }
1512
+ }
1513
+
1514
+ async function cmdRestart() {
1515
+ info('Đang restart service awkit (Symphony)...');
1516
+ try {
1517
+ const { execSync, spawn } = require('child_process');
1518
+ try {
1519
+ // Find and kill process on port 3100
1520
+ const pids = execSync('lsof -t -i:3100').toString().trim().split('\n');
1521
+ for (const pid of pids) {
1522
+ if (pid) {
1523
+ process.kill(parseInt(pid, 10), 'SIGTERM');
1524
+ }
1525
+ }
1526
+ info('Đã dừng service hiện tại.');
1527
+ } catch (e) {
1528
+ // Probably no process running on port 3100
1529
+ dim('Không tìm thấy service đang chạy.');
1530
+ }
1531
+
1532
+ await new Promise(r => setTimeout(r, 1000));
1533
+
1534
+ // Auto-build production bundle so code changes take effect
1535
+ const symphonyDir = path.join(AWK_ROOT, '..', 'symphony');
1536
+ if (fs.existsSync(path.join(symphonyDir, 'package.json'))) {
1537
+ info('Đang build production bundle...');
1538
+ try {
1539
+ execSync('npm run build', { cwd: symphonyDir, stdio: 'pipe' });
1540
+ log(`${C.green}✅ Build thành công!${C.reset}`);
1541
+ } catch (buildErr) {
1542
+ warn('Build thất bại, sử dụng bundle cũ.');
1543
+ dim(buildErr.message?.slice(0, 200));
1544
+ }
1545
+ }
1546
+
1547
+ info('Đang khởi động lại service ngầm...');
1548
+ const child = spawn('symphony', ['start'], {
1549
+ detached: true,
1550
+ stdio: 'ignore'
1551
+ });
1552
+ child.unref();
1553
+
1554
+ info('Vui lòng đợi 3 giây để service khởi động...');
1555
+ await new Promise(r => setTimeout(r, 3000));
1556
+
1557
+ log(`${C.green}✅ Restart thành công!${C.reset}`);
1558
+ } catch (e) {
1559
+ err('Lỗi khi restart service: ' + e.message);
1560
+ }
1561
+ }
1562
+
1330
1563
  function cmdHelp() {
1331
1564
  const line = `${C.gray}${'─'.repeat(56)}${C.reset}`;
1332
1565
  log('');
@@ -1354,6 +1587,15 @@ function cmdHelp() {
1354
1587
  log(` ${C.gray} CODEBASE.md, .symphony/ (Symphony task DB)${C.reset}`);
1355
1588
  log('');
1356
1589
 
1590
+ // Maintenance
1591
+ log(`${C.bold}🧹 Maintenance${C.reset}`);
1592
+ log(line);
1593
+ log(` ${C.green}serve${C.reset} [dir] [-p <port>] Start local HTTP server for assets in CWD`);
1594
+ log(` ${C.green}browser clean${C.reset} Clean browser recordings`);
1595
+ log(` ${C.gray} --days <N>${C.reset} Keep recordings from last N days (default: 7)`);
1596
+ log(` ${C.gray} --all${C.reset} Delete all recordings`);
1597
+ log('');
1598
+
1357
1599
  // Sync
1358
1600
  log(`${C.bold}🔄 Sync${C.reset}`);
1359
1601
  log(line);
@@ -1362,6 +1604,12 @@ function cmdHelp() {
1362
1604
  log(` ${C.green}sync${C.reset} Full sync: harvest + install (one shot)`);
1363
1605
  log('');
1364
1606
 
1607
+ // Symphony
1608
+ log(`${C.bold}🎶 Symphony${C.reset}`);
1609
+ log(line);
1610
+ log(` ${C.green}admin${C.reset} Khởi động Symphony Dashboard`);
1611
+ log('');
1612
+
1365
1613
  // Packs
1366
1614
  log(`${C.bold}📦 Skill Packs${C.reset}`);
1367
1615
  log(line);
@@ -1604,7 +1852,12 @@ function buildProjectIdentity(projectName, projectType, cwd, date) {
1604
1852
  };
1605
1853
 
1606
1854
  return {
1855
+ _comments: {
1856
+ projectId: 'Auto-generated. DO NOT change — used by Symphony for task scoping.',
1857
+ trello: 'Fill in your Trello board/list/card names. Run "awkit trello info" to verify.',
1858
+ },
1607
1859
  projectName,
1860
+ projectId: bundleBase,
1608
1861
  projectType: cfg.projectType,
1609
1862
  ...(cfg.bundleIdentifier && { bundleIdentifier: cfg.bundleIdentifier }),
1610
1863
  ...(cfg.packageName && { packageName: cfg.packageName }),
@@ -1616,6 +1869,11 @@ function buildProjectIdentity(projectName, projectType, cwd, date) {
1616
1869
  features: ['analytics', 'crashlytics', 'remote-config', 'auth'],
1617
1870
  },
1618
1871
  },
1872
+ trello: {
1873
+ board: 'Your Board Name',
1874
+ list: 'Your List Name',
1875
+ card: 'Your Card Name',
1876
+ },
1619
1877
  projectStage: 'development',
1620
1878
  codingStandards: cfg.codingStandards,
1621
1879
  projectGoals: [],
@@ -1784,25 +2042,38 @@ async function cmdInit(forceFlag = false) {
1784
2042
  ok(`${workspaceName} created`);
1785
2043
  }
1786
2044
 
1787
- // ── 3.5. .trello-config.json ───────────────────────────────────────────────
2045
+ // ── 3.5. Trello config (embedded in .project-identity) ──────────────────────
2046
+ // Migration: If .trello-config.json exists but identity has no trello key, merge it in
1788
2047
  const trelloConfigPath = path.join(cwd, '.trello-config.json');
1789
- if (fs.existsSync(trelloConfigPath) && !forceFlag) {
1790
- warn('.trello-config.json already exists — skipping (use --force to overwrite)');
2048
+ if (fs.existsSync(trelloConfigPath)) {
2049
+ try {
2050
+ const oldCfg = JSON.parse(fs.readFileSync(trelloConfigPath, 'utf8'));
2051
+ const currentIdentity = JSON.parse(fs.readFileSync(identityPath, 'utf8'));
2052
+ if (!currentIdentity.trello) {
2053
+ currentIdentity.trello = {
2054
+ board: oldCfg.BOARD_NAME || oldCfg.board || 'Your Board Name',
2055
+ list: oldCfg.LIST_NAME || oldCfg.list || 'Your List',
2056
+ card: oldCfg.CARD_NAME || oldCfg.card || 'Your Card',
2057
+ };
2058
+ fs.writeFileSync(identityPath, JSON.stringify(currentIdentity, null, 2) + '\n');
2059
+ ok('Migrated Trello config from .trello-config.json → .project-identity');
2060
+ dim('You can safely delete .trello-config.json now.');
2061
+ }
2062
+ } catch (_) { /* ignore migration errors */ }
1791
2063
  } else {
1792
- info('Creating .trello-config.json...');
1793
- const templatePath = path.join(TARGETS.antigravity, 'templates', 'configs', 'trello-config.json');
1794
- if (fs.existsSync(templatePath)) {
1795
- fs.copyFileSync(templatePath, trelloConfigPath);
1796
- ok('.trello-config.json created from template');
1797
- } else {
1798
- const defaultTrelloConfig = {
1799
- "BOARD_NAME": "Your Board Name",
1800
- "LIST_NAME": "Your Backlog List",
1801
- "CARD_NAME": "Project Card Name or ID"
1802
- };
1803
- fs.writeFileSync(trelloConfigPath, JSON.stringify(defaultTrelloConfig, null, 2) + '\n');
1804
- ok('.trello-config.json created with default values');
1805
- }
2064
+ // Ensure identity has trello placeholder if not already present
2065
+ try {
2066
+ const currentIdentity = JSON.parse(fs.readFileSync(identityPath, 'utf8'));
2067
+ if (!currentIdentity.trello) {
2068
+ currentIdentity.trello = {
2069
+ board: 'Your Board Name',
2070
+ list: 'Your List',
2071
+ card: 'Your Card',
2072
+ };
2073
+ fs.writeFileSync(identityPath, JSON.stringify(currentIdentity, null, 2) + '\n');
2074
+ ok('Added Trello config placeholder to .project-identity');
2075
+ }
2076
+ } catch (_) { /* ignore */ }
1806
2077
  }
1807
2078
 
1808
2079
  const trelloCred = trelloLoadCredentials();
@@ -1894,7 +2165,7 @@ async function cmdInit(forceFlag = false) {
1894
2165
  log('');
1895
2166
  dim(`Type: ${projectType}`);
1896
2167
  dim(`Firebase: analytics, crashlytics, remote-config, auth`);
1897
- dim(`Files: .project-identity, ${workspaceName}, CODEBASE.md, .trello-config.json`);
2168
+ dim(`Files: .project-identity, ${workspaceName}, CODEBASE.md`);
1898
2169
  dim(`Symphony: task tracking ready)`);
1899
2170
  log('');
1900
2171
  log(`${C.cyan}👉 Open ${workspaceName} in VS Code to get started.${C.reset}`);
@@ -2097,6 +2368,178 @@ function cmdTelegram(args) {
2097
2368
  }
2098
2369
  }
2099
2370
 
2371
+ // ─── Credentials Management ───────────────────────────────────────────────────
2372
+
2373
+ const CREDENTIALS_CONFIG_PATH = path.join(TARGETS.antigravity, '.credentials.json');
2374
+
2375
+ function credentialsLoad() {
2376
+ if (!fs.existsSync(CREDENTIALS_CONFIG_PATH)) return {};
2377
+ try {
2378
+ return JSON.parse(fs.readFileSync(CREDENTIALS_CONFIG_PATH, 'utf8'));
2379
+ } catch (_) {
2380
+ return {};
2381
+ }
2382
+ }
2383
+
2384
+ function credentialsSave(config) {
2385
+ const dir = path.dirname(CREDENTIALS_CONFIG_PATH);
2386
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
2387
+ fs.writeFileSync(CREDENTIALS_CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
2388
+ }
2389
+
2390
+ function credentialsHelp() {
2391
+ log('');
2392
+ log(`${C.cyan}${C.bold}🔑 Credentials Commands${C.reset}`);
2393
+ log('');
2394
+ log(` ${C.green}awkit credentials list${C.reset} List all stored credentials`);
2395
+ log(` ${C.green}awkit credentials set${C.reset} <key> <value> Set a credential`);
2396
+ log(` ${C.green}awkit credentials get${C.reset} <key> Get a credential value`);
2397
+ log(` ${C.green}awkit credentials remove${C.reset} <key> Remove a credential`);
2398
+ log(` ${C.green}awkit credentials setup${C.reset} Interactive setup wizard`);
2399
+ log('');
2400
+ log(` ${C.gray}Known keys: gemini_api_key, lucylab_bearer${C.reset}`);
2401
+ log(` ${C.gray}Config: ${CREDENTIALS_CONFIG_PATH}${C.reset}`);
2402
+ log('');
2403
+ }
2404
+
2405
+ async function credentialsSetup() {
2406
+ log('');
2407
+ log(`${C.cyan}${C.bold}🔑 API Credentials Setup${C.reset}`);
2408
+ log('');
2409
+ log(`${C.gray} Credentials are stored in: ${CREDENTIALS_CONFIG_PATH}${C.reset}`);
2410
+ log(`${C.gray} Used by Short Maker, Symphony Admin, and other services.${C.reset}`);
2411
+ log('');
2412
+
2413
+ const readline = require('readline');
2414
+ const rl = readline.createInterface({
2415
+ input: process.stdin,
2416
+ output: process.stdout
2417
+ });
2418
+
2419
+ const question = (query) => new Promise(resolve => rl.question(query, resolve));
2420
+ const sanitize = (s) => s.trim().replace(/^bearer\s+/i, '').replace(/\s+/g, '');
2421
+
2422
+ const config = credentialsLoad();
2423
+
2424
+ try {
2425
+ // Gemini API Key
2426
+ log(`${C.gray} Get your key at: https://aistudio.google.com/apikey${C.reset}`);
2427
+ const geminiKey = sanitize(await question(` ${C.yellow}Gemini API Key${config.gemini_api_key ? ` [${config.gemini_api_key.slice(0, 8)}...]` : ''}: ${C.reset}`));
2428
+ if (geminiKey) {
2429
+ config.gemini_api_key = geminiKey;
2430
+ ok('Gemini API Key saved');
2431
+ } else if (config.gemini_api_key) {
2432
+ dim('Kept existing Gemini API Key');
2433
+ }
2434
+
2435
+ log('');
2436
+
2437
+ // LucyLab Bearer
2438
+ log(`${C.gray} LucyLab TTS bearer token for voice generation${C.reset}`);
2439
+ const lucylabToken = sanitize(await question(` ${C.yellow}LucyLab Bearer${config.lucylab_bearer ? ` [${config.lucylab_bearer.slice(0, 8)}...]` : ''}: ${C.reset}`));
2440
+ if (lucylabToken) {
2441
+ config.lucylab_bearer = lucylabToken;
2442
+ ok('LucyLab Bearer saved');
2443
+ } else if (config.lucylab_bearer) {
2444
+ dim('Kept existing LucyLab Bearer');
2445
+ }
2446
+
2447
+ credentialsSave(config);
2448
+ log('');
2449
+ ok(`Credentials saved to ${CREDENTIALS_CONFIG_PATH}`);
2450
+ log('');
2451
+ } catch (e) {
2452
+ warn(`Failed to setup credentials: ${e.message}`);
2453
+ } finally {
2454
+ rl.close();
2455
+ }
2456
+ }
2457
+
2458
+ function cmdCredentials(args) {
2459
+ const subCmd = args[0];
2460
+ const key = args[1];
2461
+ const value = args.slice(2).join(' ');
2462
+
2463
+ switch (subCmd) {
2464
+ case 'list': {
2465
+ const config = credentialsLoad();
2466
+ const keys = Object.keys(config);
2467
+ if (keys.length === 0) {
2468
+ warn('No credentials stored. Run "awkit credentials setup" to configure.');
2469
+ return;
2470
+ }
2471
+ log('');
2472
+ log(`${C.cyan}${C.bold}🔑 Stored Credentials${C.reset}`);
2473
+ log('');
2474
+ for (const k of keys) {
2475
+ const val = config[k];
2476
+ const masked = val ? `${val.slice(0, 8)}${'•'.repeat(Math.max(0, val.length - 8))}` : '(empty)';
2477
+ log(` ${C.green}${k}${C.reset} = ${C.gray}${masked}${C.reset}`);
2478
+ }
2479
+ log('');
2480
+ dim(`Config: ${CREDENTIALS_CONFIG_PATH}`);
2481
+ log('');
2482
+ break;
2483
+ }
2484
+
2485
+ case 'set': {
2486
+ let valueToSet = value;
2487
+ if (valueToSet) {
2488
+ valueToSet = valueToSet.trim().replace(/^bearer\s+/i, '').replace(/\s+/g, '');
2489
+ }
2490
+ if (!key || !valueToSet) {
2491
+ err('Usage: awkit credentials set <key> <value>');
2492
+ dim('Example: awkit credentials set gemini_api_key AIzaSy...');
2493
+ return;
2494
+ }
2495
+ const config = credentialsLoad();
2496
+ config[key] = valueToSet;
2497
+ credentialsSave(config);
2498
+ ok(`${key} saved ✅`);
2499
+ break;
2500
+ }
2501
+
2502
+ case 'get': {
2503
+ if (!key) {
2504
+ err('Usage: awkit credentials get <key>');
2505
+ return;
2506
+ }
2507
+ const config = credentialsLoad();
2508
+ if (config[key]) {
2509
+ log(config[key]);
2510
+ } else {
2511
+ warn(`Key "${key}" not found.`);
2512
+ }
2513
+ break;
2514
+ }
2515
+
2516
+ case 'remove':
2517
+ case 'delete': {
2518
+ if (!key) {
2519
+ err('Usage: awkit credentials remove <key>');
2520
+ return;
2521
+ }
2522
+ const config = credentialsLoad();
2523
+ if (config[key]) {
2524
+ delete config[key];
2525
+ credentialsSave(config);
2526
+ ok(`${key} removed`);
2527
+ } else {
2528
+ warn(`Key "${key}" not found.`);
2529
+ }
2530
+ break;
2531
+ }
2532
+
2533
+ case 'setup':
2534
+ credentialsSetup();
2535
+ break;
2536
+
2537
+ default:
2538
+ credentialsHelp();
2539
+ break;
2540
+ }
2541
+ }
2542
+
2100
2543
  // ─── Trello Integration ───────────────────────────────────────────────────────
2101
2544
 
2102
2545
  /**
@@ -2111,10 +2554,28 @@ function trelloLoadCredentials() {
2111
2554
  }
2112
2555
 
2113
2556
  /**
2114
- * Load Trello project config from .trello-config.json in CWD.
2557
+ * Load Trello project config from .project-identity (preferred) or .trello-config.json (fallback).
2115
2558
  * Returns { board, list, card } or null.
2116
2559
  */
2117
2560
  function trelloLoadProjectConfig() {
2561
+ // 1. Try .project-identity → trello key
2562
+ const identityPath = path.join(process.cwd(), '.project-identity');
2563
+ if (fs.existsSync(identityPath)) {
2564
+ try {
2565
+ const identity = JSON.parse(fs.readFileSync(identityPath, 'utf8'));
2566
+ if (identity.trello) {
2567
+ const t = identity.trello;
2568
+ const board = t.board || t.BOARD_NAME;
2569
+ const list = t.list || t.LIST_NAME;
2570
+ const card = t.card || t.CARD_NAME;
2571
+ if (board && list && card) {
2572
+ return { board, list, card };
2573
+ }
2574
+ }
2575
+ } catch (_) { /* ignore parse error */ }
2576
+ }
2577
+
2578
+ // 2. Fallback: .trello-config.json
2118
2579
  const configPath = path.join(process.cwd(), '.trello-config.json');
2119
2580
  if (!fs.existsSync(configPath)) return null;
2120
2581
  try {
@@ -2141,8 +2602,8 @@ function trelloExec(cliArgs, retries = 3) {
2141
2602
  return false;
2142
2603
  }
2143
2604
  if (!cfg) {
2144
- err('.trello-config.json not found in current directory.');
2145
- log(` Run ${C.cyan}awkit init${C.reset} to generate one, or create it manually.`);
2605
+ err('Trello config not found. Add "trello" key to .project-identity or create .trello-config.json.');
2606
+ log(` Run ${C.cyan}awkit init${C.reset} to set up, or add manually to .project-identity.`);
2146
2607
  return false;
2147
2608
  }
2148
2609
 
@@ -2191,9 +2652,12 @@ function trelloHelp() {
2191
2652
  log(` ${C.green}awkit trello complete${C.reset} <name> Mark checklist item ✅ complete`);
2192
2653
  log(` ${C.green}awkit trello block${C.reset} <reason> Label card Blocked + comment`);
2193
2654
  log(` ${C.green}awkit trello checklist${C.reset} <name> Create a new checklist on card`);
2655
+ log(` ${C.green}awkit trello info${C.reset} Show card details`);
2656
+ log(` ${C.green}awkit trello checklists${C.reset} List checklists on card`);
2657
+ log(` ${C.green}awkit trello comments${C.reset} List comments on card`);
2194
2658
  log('');
2195
2659
  log(` ${C.gray}Credentials: env vars TRELLO_KEY and TRELLO_TOKEN${C.reset}`);
2196
- log(` ${C.gray}Project config: .trello-config.json in CWD${C.reset}`);
2660
+ log(` ${C.gray}Project config: "trello" key in .project-identity (fallback: .trello-config.json)${C.reset}`);
2197
2661
  log('');
2198
2662
  }
2199
2663
 
@@ -2206,12 +2670,28 @@ function cmdTrello(args) {
2206
2670
  return;
2207
2671
  }
2208
2672
 
2209
- if (!text) {
2673
+ const noTextCmds = ['info', 'checklists', 'comments'];
2674
+ if (!text && !noTextCmds.includes(subCmd)) {
2210
2675
  err(`Missing argument for 'trello ${subCmd}'. Usage: awkit trello ${subCmd} <text>`);
2211
2676
  return;
2212
2677
  }
2213
2678
 
2214
2679
  switch (subCmd) {
2680
+ case 'info':
2681
+ info(`Fetching card details...`);
2682
+ trelloExec(['card:show']);
2683
+ break;
2684
+
2685
+ case 'checklists':
2686
+ info(`Fetching card checklists...`);
2687
+ trelloExec(['card:checklists']);
2688
+ break;
2689
+
2690
+ case 'comments':
2691
+ info(`Fetching card comments...`);
2692
+ trelloExec(['card:comments']);
2693
+ break;
2694
+
2215
2695
  case 'desc':
2216
2696
  info(`Updating card description...`);
2217
2697
  trelloExec(['card:update', '--description', text]);
@@ -2331,6 +2811,98 @@ function checkAutoUpdate() {
2331
2811
  }
2332
2812
  }
2333
2813
 
2814
+ // ─── Native HTTP Server ───────────────────────────────────────────────────────
2815
+
2816
+ function cmdServe(args) {
2817
+ const http = require('http');
2818
+
2819
+ let port = 8080;
2820
+ let serveDir = process.cwd();
2821
+
2822
+ for (let i = 0; i < args.length; i++) {
2823
+ if (args[i] === '--port' || args[i] === '-p') {
2824
+ port = parseInt(args[++i], 10) || 8080;
2825
+ } else if (!args[i].startsWith('-')) {
2826
+ serveDir = path.resolve(process.cwd(), args[i]);
2827
+ }
2828
+ }
2829
+
2830
+ if (!fs.existsSync(serveDir)) {
2831
+ err(`Directory not found: ${serveDir}`);
2832
+ return;
2833
+ }
2834
+
2835
+ const mimeTypes = {
2836
+ '.html': 'text/html',
2837
+ '.js': 'text/javascript',
2838
+ '.css': 'text/css',
2839
+ '.json': 'application/json',
2840
+ '.png': 'image/png',
2841
+ '.jpg': 'image/jpeg',
2842
+ '.jpeg': 'image/jpeg',
2843
+ '.gif': 'image/gif',
2844
+ '.svg': 'image/svg+xml',
2845
+ '.wav': 'audio/wav',
2846
+ '.mp4': 'video/mp4',
2847
+ '.woff': 'application/font-woff',
2848
+ '.ttf': 'application/font-ttf',
2849
+ '.eot': 'application/vnd.ms-fontobject',
2850
+ '.otf': 'application/font-otf',
2851
+ '.wasm': 'application/wasm'
2852
+ };
2853
+
2854
+ const server = http.createServer((request, response) => {
2855
+ response.setHeader('Access-Control-Allow-Origin', '*');
2856
+ response.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
2857
+ response.setHeader('Access-Control-Allow-Headers', 'Content-Type');
2858
+
2859
+ if (request.method === 'OPTIONS') {
2860
+ response.writeHead(200);
2861
+ response.end();
2862
+ return;
2863
+ }
2864
+
2865
+ let filePath = '.' + request.url.split('?')[0];
2866
+ if (filePath === './') {
2867
+ filePath = './index.html';
2868
+ }
2869
+
2870
+ const absPath = path.join(serveDir, filePath);
2871
+ const extname = String(path.extname(absPath)).toLowerCase();
2872
+ const contentType = mimeTypes[extname] || 'application/octet-stream';
2873
+
2874
+ fs.readFile(absPath, (error, content) => {
2875
+ if (error) {
2876
+ if (error.code === 'ENOENT') {
2877
+ response.writeHead(404, { 'Content-Type': 'text/plain' });
2878
+ response.end('404 Not Found', 'utf-8');
2879
+ } else {
2880
+ response.writeHead(500, { 'Content-Type': 'text/plain' });
2881
+ response.end('500 Internal Server Error: ' + error.code, 'utf-8');
2882
+ }
2883
+ } else {
2884
+ response.writeHead(200, { 'Content-Type': contentType });
2885
+ response.end(content, 'utf-8');
2886
+ }
2887
+ });
2888
+ });
2889
+
2890
+ server.listen(port, '0.0.0.0', () => {
2891
+ log('');
2892
+ log(`${C.cyan}${C.bold}🚀 awkit serve running at:${C.reset}`);
2893
+ log(`${C.green} http://localhost:${port}${C.reset}`);
2894
+ dim(`Serving directory: ${serveDir}`);
2895
+ dim(`Press Ctrl+C to stop`);
2896
+ log('');
2897
+ }).on('error', (e) => {
2898
+ if (e.code === 'EADDRINUSE') {
2899
+ err(`Port ${port} is already in use. Try a different port with --port <number>`);
2900
+ } else {
2901
+ err(`Server error: ${e.message}`);
2902
+ }
2903
+ });
2904
+ }
2905
+
2334
2906
  // ─── Main ────────────────────────────────────────────────────────────────────
2335
2907
 
2336
2908
  // Check for updates (max once per day) before continuing
@@ -2344,17 +2916,7 @@ const [, , command, ...args] = process.argv;
2344
2916
  await cmdInit(args.includes('--force'));
2345
2917
  break;
2346
2918
  case 'install':
2347
- // Parse platform from either first arg or --platform flag
2348
- {
2349
- const pIdx = args.indexOf('--platform');
2350
- let platformArg = null;
2351
- if (pIdx !== -1 && args[pIdx + 1]) {
2352
- platformArg = args[pIdx + 1];
2353
- } else if (args[0] && !args[0].startsWith('-')) {
2354
- platformArg = args[0];
2355
- }
2356
- cmdInstall(platformArg);
2357
- }
2919
+ cmdInstall(args);
2358
2920
  break;
2359
2921
  case 'uninstall':
2360
2922
  cmdUninstall();
@@ -2374,6 +2936,9 @@ const [, , command, ...args] = process.argv;
2374
2936
  case 'doctor':
2375
2937
  cmdDoctor();
2376
2938
  break;
2939
+ case 'browser':
2940
+ cmdBrowser(args);
2941
+ break;
2377
2942
  case 'enable-pack':
2378
2943
  cmdEnablePack(args[0]);
2379
2944
  break;
@@ -2398,6 +2963,19 @@ const [, , command, ...args] = process.argv;
2398
2963
  case 'telegram':
2399
2964
  cmdTelegram(args);
2400
2965
  break;
2966
+ case 'credentials':
2967
+ case 'creds':
2968
+ cmdCredentials(args);
2969
+ break;
2970
+ case 'serve':
2971
+ cmdServe(args);
2972
+ break;
2973
+ case 'admin':
2974
+ cmdAdmin();
2975
+ break;
2976
+ case 'restart':
2977
+ await cmdRestart();
2978
+ break;
2401
2979
  case 'help':
2402
2980
  case '--help':
2403
2981
  case '-h':