@leanmcp/cli 0.2.14 → 0.3.1

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/dist/index.js +770 -552
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,13 +3,13 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
3
3
 
4
4
  // src/index.ts
5
5
  import { Command } from "commander";
6
- import chalk6 from "chalk";
7
- import fs7 from "fs-extra";
8
- import path7 from "path";
9
- import ora6 from "ora";
6
+ import chalk7 from "chalk";
7
+ import fs8 from "fs-extra";
8
+ import path8 from "path";
9
+ import ora7 from "ora";
10
10
  import { createRequire } from "module";
11
11
  import { confirm as confirm3 } from "@inquirer/prompts";
12
- import { spawn as spawn3 } from "child_process";
12
+ import { spawn as spawn4 } from "child_process";
13
13
 
14
14
  // src/commands/dev.ts
15
15
  import { spawn } from "child_process";
@@ -17,6 +17,7 @@ import chalk from "chalk";
17
17
  import ora from "ora";
18
18
  import path3 from "path";
19
19
  import fs3 from "fs-extra";
20
+ import crypto from "crypto";
20
21
  import chokidar from "chokidar";
21
22
 
22
23
  // src/vite/scanUIApp.ts
@@ -131,6 +132,29 @@ import react from "@vitejs/plugin-react";
131
132
  import { viteSingleFile } from "vite-plugin-singlefile";
132
133
  import fs2 from "fs-extra";
133
134
  import path2 from "path";
135
+ function resolveReactDependency(startDir, packageName) {
136
+ const localPath = path2.join(startDir, "node_modules", packageName);
137
+ if (fs2.existsSync(localPath)) {
138
+ return localPath;
139
+ }
140
+ let currentDir = path2.dirname(startDir);
141
+ const maxDepth = 10;
142
+ let depth = 0;
143
+ while (depth < maxDepth) {
144
+ const nodeModulesPath = path2.join(currentDir, "node_modules", packageName);
145
+ if (fs2.existsSync(nodeModulesPath)) {
146
+ return nodeModulesPath;
147
+ }
148
+ const parentDir = path2.dirname(currentDir);
149
+ if (parentDir === currentDir) {
150
+ break;
151
+ }
152
+ currentDir = parentDir;
153
+ depth++;
154
+ }
155
+ return localPath;
156
+ }
157
+ __name(resolveReactDependency, "resolveReactDependency");
134
158
  async function buildUIComponent(uiApp, projectDir, isDev = false) {
135
159
  const { componentPath, componentName, resourceUri } = uiApp;
136
160
  const safeFileName = resourceUri.replace("ui://", "").replace(/\//g, "-") + ".html";
@@ -318,6 +342,8 @@ createRoot(document.getElementById('root')!).render(
318
342
  );
319
343
  `);
320
344
  try {
345
+ const reactPath = resolveReactDependency(projectDir, "react");
346
+ const reactDomPath = resolveReactDependency(projectDir, "react-dom");
321
347
  await vite.build({
322
348
  root: tempDir,
323
349
  plugins: [
@@ -326,11 +352,11 @@ createRoot(document.getElementById('root')!).render(
326
352
  ],
327
353
  resolve: {
328
354
  alias: {
329
- // Resolve React from user's project node_modules
330
- "react": path2.join(projectDir, "node_modules", "react"),
331
- "react-dom": path2.join(projectDir, "node_modules", "react-dom"),
332
- "react/jsx-runtime": path2.join(projectDir, "node_modules", "react", "jsx-runtime"),
333
- "react/jsx-dev-runtime": path2.join(projectDir, "node_modules", "react", "jsx-dev-runtime")
355
+ // Resolve React from project or workspace root node_modules
356
+ "react": reactPath,
357
+ "react-dom": reactDomPath,
358
+ "react/jsx-runtime": path2.join(reactPath, "jsx-runtime"),
359
+ "react/jsx-dev-runtime": path2.join(reactPath, "jsx-dev-runtime")
334
360
  }
335
361
  },
336
362
  css: {
@@ -386,8 +412,49 @@ async function writeUIManifest(manifest, projectDir) {
386
412
  });
387
413
  }
388
414
  __name(writeUIManifest, "writeUIManifest");
415
+ async function deleteUIComponent(uri, projectDir) {
416
+ const safeFileName = uri.replace("ui://", "").replace(/\//g, "-") + ".html";
417
+ const htmlPath = path2.join(projectDir, "dist", "ui", safeFileName);
418
+ if (await fs2.pathExists(htmlPath)) {
419
+ await fs2.remove(htmlPath);
420
+ }
421
+ }
422
+ __name(deleteUIComponent, "deleteUIComponent");
389
423
 
390
424
  // src/commands/dev.ts
425
+ function computeHash(filePath) {
426
+ const content = fs3.readFileSync(filePath, "utf-8");
427
+ return crypto.createHash("md5").update(content).digest("hex");
428
+ }
429
+ __name(computeHash, "computeHash");
430
+ function computeDiff(previousUIApps, currentUIApps, hashCache) {
431
+ const previousURIs = new Set(previousUIApps.map((app) => app.resourceUri));
432
+ const currentURIs = new Set(currentUIApps.map((app) => app.resourceUri));
433
+ const removed = Array.from(previousURIs).filter((uri) => !currentURIs.has(uri));
434
+ const added = new Set(Array.from(currentURIs).filter((uri) => !previousURIs.has(uri)));
435
+ const addedOrChanged = [];
436
+ for (const app of currentUIApps) {
437
+ const isNew = added.has(app.resourceUri);
438
+ if (isNew) {
439
+ addedOrChanged.push(app);
440
+ } else {
441
+ const oldHash = hashCache.get(app.resourceUri);
442
+ let newHash;
443
+ if (fs3.existsSync(app.componentPath)) {
444
+ newHash = computeHash(app.componentPath);
445
+ }
446
+ if (oldHash !== newHash) {
447
+ addedOrChanged.push(app);
448
+ }
449
+ }
450
+ }
451
+ return {
452
+ removed,
453
+ added,
454
+ addedOrChanged
455
+ };
456
+ }
457
+ __name(computeDiff, "computeDiff");
391
458
  async function devCommand() {
392
459
  const cwd = process.cwd();
393
460
  if (!await fs3.pathExists(path3.join(cwd, "main.ts"))) {
@@ -395,16 +462,13 @@ async function devCommand() {
395
462
  console.error(chalk.gray("Run this command from your project root."));
396
463
  process.exit(1);
397
464
  }
398
- console.log(chalk.cyan("\n\u{1F680} LeanMCP Development Server\n"));
465
+ console.log(chalk.cyan("\nLeanMCP Development Server\n"));
399
466
  const scanSpinner = ora("Scanning for @UIApp components...").start();
400
467
  const uiApps = await scanUIApp(cwd);
401
468
  if (uiApps.length === 0) {
402
- scanSpinner.succeed("No @UIApp components found");
469
+ scanSpinner.info("No @UIApp components found");
403
470
  } else {
404
- scanSpinner.succeed(`Found ${uiApps.length} @UIApp component(s)`);
405
- for (const app of uiApps) {
406
- console.log(chalk.gray(` \u2022 ${app.componentName} \u2192 ${app.resourceUri}`));
407
- }
471
+ scanSpinner.info(`Found ${uiApps.length} @UIApp component(s)`);
408
472
  }
409
473
  const manifest = {};
410
474
  if (uiApps.length > 0) {
@@ -422,10 +486,10 @@ async function devCommand() {
422
486
  if (errors.length > 0) {
423
487
  buildSpinner.warn("Built with warnings");
424
488
  for (const error of errors) {
425
- console.error(chalk.yellow(` \u26A0 ${error}`));
489
+ console.error(chalk.yellow(` ${error}`));
426
490
  }
427
491
  } else {
428
- buildSpinner.succeed("UI components built");
492
+ buildSpinner.info("UI components built");
429
493
  }
430
494
  }
431
495
  console.log(chalk.cyan("\nStarting development server...\n"));
@@ -439,26 +503,59 @@ async function devCommand() {
439
503
  shell: true
440
504
  });
441
505
  let watcher = null;
442
- if (uiApps.length > 0) {
443
- const componentPaths = uiApps.map((app) => app.componentPath);
444
- watcher = chokidar.watch(componentPaths, {
445
- ignoreInitial: true
446
- });
447
- watcher.on("change", async (changedPath) => {
448
- const app = uiApps.find((a) => a.componentPath === changedPath);
449
- if (!app) return;
450
- console.log(chalk.cyan(`
451
- [UI] Rebuilding ${app.componentName}...`));
452
- const result = await buildUIComponent(app, cwd, true);
453
- if (result.success) {
454
- manifest[app.resourceUri] = result.htmlPath;
455
- await writeUIManifest(manifest, cwd);
456
- console.log(chalk.green(`[UI] ${app.componentName} rebuilt successfully`));
457
- } else {
458
- console.log(chalk.yellow(`[UI] ${app.componentName} build failed: ${result.error}`));
459
- }
460
- });
506
+ const componentHashCache = /* @__PURE__ */ new Map();
507
+ for (const app of uiApps) {
508
+ if (await fs3.pathExists(app.componentPath)) {
509
+ componentHashCache.set(app.resourceUri, computeHash(app.componentPath));
510
+ }
461
511
  }
512
+ let previousUIApps = uiApps;
513
+ const mcpPath = path3.join(cwd, "mcp");
514
+ watcher = chokidar.watch(mcpPath, {
515
+ ignoreInitial: true,
516
+ ignored: [
517
+ "**/node_modules/**",
518
+ "**/*.d.ts"
519
+ ],
520
+ persistent: true,
521
+ awaitWriteFinish: {
522
+ stabilityThreshold: 100,
523
+ pollInterval: 50
524
+ }
525
+ });
526
+ let debounceTimer = null;
527
+ watcher.on("all", async (event, changedPath) => {
528
+ if (debounceTimer) clearTimeout(debounceTimer);
529
+ debounceTimer = setTimeout(async () => {
530
+ const currentUIApps = await scanUIApp(cwd);
531
+ const diff = computeDiff(previousUIApps, currentUIApps, componentHashCache);
532
+ if (diff.removed.length === 0 && diff.addedOrChanged.length === 0) {
533
+ return;
534
+ }
535
+ for (const uri of diff.removed) {
536
+ console.log(chalk.yellow(`Removing ${uri}...`));
537
+ await deleteUIComponent(uri, cwd);
538
+ delete manifest[uri];
539
+ componentHashCache.delete(uri);
540
+ }
541
+ for (const app of diff.addedOrChanged) {
542
+ const action = diff.added.has(app.resourceUri) ? "Building" : "Rebuilding";
543
+ console.log(chalk.cyan(`${action} ${app.componentName}...`));
544
+ const result = await buildUIComponent(app, cwd, true);
545
+ if (result.success) {
546
+ manifest[app.resourceUri] = result.htmlPath;
547
+ if (await fs3.pathExists(app.componentPath)) {
548
+ componentHashCache.set(app.resourceUri, computeHash(app.componentPath));
549
+ }
550
+ console.log(chalk.green(`${app.componentName} ${action.toLowerCase()} complete`));
551
+ } else {
552
+ console.log(chalk.yellow(`Build failed: ${result.error}`));
553
+ }
554
+ }
555
+ await writeUIManifest(manifest, cwd);
556
+ previousUIApps = currentUIApps;
557
+ }, 150);
558
+ });
462
559
  let isCleaningUp = false;
463
560
  const cleanup = /* @__PURE__ */ __name(() => {
464
561
  if (isCleaningUp) return;
@@ -476,20 +573,20 @@ async function devCommand() {
476
573
  }
477
574
  __name(devCommand, "devCommand");
478
575
 
479
- // src/commands/start.ts
576
+ // src/commands/build.ts
480
577
  import { spawn as spawn2 } from "child_process";
481
578
  import chalk2 from "chalk";
482
579
  import ora2 from "ora";
483
580
  import path4 from "path";
484
581
  import fs4 from "fs-extra";
485
- async function startCommand() {
582
+ async function buildCommand() {
486
583
  const cwd = process.cwd();
487
584
  if (!await fs4.pathExists(path4.join(cwd, "main.ts"))) {
488
585
  console.error(chalk2.red("ERROR: Not a LeanMCP project (main.ts not found)."));
489
586
  console.error(chalk2.gray("Run this command from your project root."));
490
587
  process.exit(1);
491
588
  }
492
- console.log(chalk2.cyan("\n\u{1F680} LeanMCP Production Build\n"));
589
+ console.log(chalk2.cyan("\n\u{1F528} LeanMCP Build\n"));
493
590
  const scanSpinner = ora2("Scanning for @UIApp components...").start();
494
591
  const uiApps = await scanUIApp(cwd);
495
592
  if (uiApps.length === 0) {
@@ -545,8 +642,83 @@ async function startCommand() {
545
642
  console.error(chalk2.red(error instanceof Error ? error.message : String(error)));
546
643
  process.exit(1);
547
644
  }
548
- console.log(chalk2.cyan("\nStarting production server...\n"));
549
- const server = spawn2("node", [
645
+ console.log(chalk2.green("\nBuild complete!"));
646
+ console.log(chalk2.gray("\nTo start the server:"));
647
+ console.log(chalk2.cyan(" npm run start:node\n"));
648
+ }
649
+ __name(buildCommand, "buildCommand");
650
+
651
+ // src/commands/start.ts
652
+ import { spawn as spawn3 } from "child_process";
653
+ import chalk3 from "chalk";
654
+ import ora3 from "ora";
655
+ import path5 from "path";
656
+ import fs5 from "fs-extra";
657
+ async function startCommand() {
658
+ const cwd = process.cwd();
659
+ if (!await fs5.pathExists(path5.join(cwd, "main.ts"))) {
660
+ console.error(chalk3.red("ERROR: Not a LeanMCP project (main.ts not found)."));
661
+ console.error(chalk3.gray("Run this command from your project root."));
662
+ process.exit(1);
663
+ }
664
+ console.log(chalk3.cyan("\n\u{1F680} LeanMCP Production Build\n"));
665
+ const scanSpinner = ora3("Scanning for @UIApp components...").start();
666
+ const uiApps = await scanUIApp(cwd);
667
+ if (uiApps.length === 0) {
668
+ scanSpinner.succeed("No @UIApp components found");
669
+ } else {
670
+ scanSpinner.succeed(`Found ${uiApps.length} @UIApp component(s)`);
671
+ }
672
+ const manifest = {};
673
+ if (uiApps.length > 0) {
674
+ const buildSpinner = ora3("Building UI components...").start();
675
+ const errors = [];
676
+ for (const app of uiApps) {
677
+ const result = await buildUIComponent(app, cwd, false);
678
+ if (result.success) {
679
+ manifest[app.resourceUri] = result.htmlPath;
680
+ } else {
681
+ errors.push(`${app.componentName}: ${result.error}`);
682
+ }
683
+ }
684
+ await writeUIManifest(manifest, cwd);
685
+ if (errors.length > 0) {
686
+ buildSpinner.fail("Build failed");
687
+ for (const error of errors) {
688
+ console.error(chalk3.red(` \u2717 ${error}`));
689
+ }
690
+ process.exit(1);
691
+ }
692
+ buildSpinner.succeed("UI components built");
693
+ }
694
+ const tscSpinner = ora3("Compiling TypeScript...").start();
695
+ try {
696
+ await new Promise((resolve, reject) => {
697
+ const tsc = spawn3("npx", [
698
+ "tsc"
699
+ ], {
700
+ cwd,
701
+ stdio: "pipe",
702
+ shell: true
703
+ });
704
+ let stderr = "";
705
+ tsc.stderr?.on("data", (data) => {
706
+ stderr += data;
707
+ });
708
+ tsc.on("close", (code) => {
709
+ if (code === 0) resolve();
710
+ else reject(new Error(stderr || `tsc exited with code ${code}`));
711
+ });
712
+ tsc.on("error", reject);
713
+ });
714
+ tscSpinner.succeed("TypeScript compiled");
715
+ } catch (error) {
716
+ tscSpinner.fail("TypeScript compilation failed");
717
+ console.error(chalk3.red(error instanceof Error ? error.message : String(error)));
718
+ process.exit(1);
719
+ }
720
+ console.log(chalk3.cyan("\nStarting production server...\n"));
721
+ const server = spawn3("node", [
550
722
  "dist/main.js"
551
723
  ], {
552
724
  cwd,
@@ -557,7 +729,7 @@ async function startCommand() {
557
729
  const cleanup = /* @__PURE__ */ __name(() => {
558
730
  if (isCleaningUp) return;
559
731
  isCleaningUp = true;
560
- console.log(chalk2.gray("\nShutting down..."));
732
+ console.log(chalk3.gray("\nShutting down..."));
561
733
  server.kill("SIGTERM");
562
734
  }, "cleanup");
563
735
  process.on("SIGINT", cleanup);
@@ -569,10 +741,10 @@ async function startCommand() {
569
741
  __name(startCommand, "startCommand");
570
742
 
571
743
  // src/commands/login.ts
572
- import chalk3 from "chalk";
573
- import ora3 from "ora";
574
- import path5 from "path";
575
- import fs5 from "fs-extra";
744
+ import chalk4 from "chalk";
745
+ import ora4 from "ora";
746
+ import path6 from "path";
747
+ import fs6 from "fs-extra";
576
748
  import os from "os";
577
749
  import { input, confirm } from "@inquirer/prompts";
578
750
  var DEBUG_MODE = false;
@@ -582,16 +754,16 @@ function setDebugMode(enabled) {
582
754
  __name(setDebugMode, "setDebugMode");
583
755
  function debug(message, ...args) {
584
756
  if (DEBUG_MODE) {
585
- console.log(chalk3.gray(`[DEBUG] ${message}`), ...args);
757
+ console.log(chalk4.gray(`[DEBUG] ${message}`), ...args);
586
758
  }
587
759
  }
588
760
  __name(debug, "debug");
589
- var CONFIG_DIR = path5.join(os.homedir(), ".leanmcp");
590
- var CONFIG_FILE = path5.join(CONFIG_DIR, "config.json");
761
+ var CONFIG_DIR = path6.join(os.homedir(), ".leanmcp");
762
+ var CONFIG_FILE = path6.join(CONFIG_DIR, "config.json");
591
763
  async function loadConfig() {
592
764
  try {
593
- if (await fs5.pathExists(CONFIG_FILE)) {
594
- return await fs5.readJSON(CONFIG_FILE);
765
+ if (await fs6.pathExists(CONFIG_FILE)) {
766
+ return await fs6.readJSON(CONFIG_FILE);
595
767
  }
596
768
  } catch (error) {
597
769
  }
@@ -599,8 +771,8 @@ async function loadConfig() {
599
771
  }
600
772
  __name(loadConfig, "loadConfig");
601
773
  async function saveConfig(config) {
602
- await fs5.ensureDir(CONFIG_DIR);
603
- await fs5.writeJSON(CONFIG_FILE, config, {
774
+ await fs6.ensureDir(CONFIG_DIR);
775
+ await fs6.writeJSON(CONFIG_FILE, config, {
604
776
  spaces: 2
605
777
  });
606
778
  }
@@ -638,24 +810,24 @@ Allowed URLs:
638
810
  }
639
811
  __name(getApiUrl, "getApiUrl");
640
812
  async function loginCommand() {
641
- console.log(chalk3.cyan("\nLeanMCP Login\n"));
813
+ console.log(chalk4.cyan("\nLeanMCP Login\n"));
642
814
  const existingConfig = await loadConfig();
643
815
  if (existingConfig.apiKey) {
644
- console.log(chalk3.yellow("You are already logged in."));
816
+ console.log(chalk4.yellow("You are already logged in."));
645
817
  const shouldRelogin = await confirm({
646
818
  message: "Do you want to replace the existing API key?",
647
819
  default: false
648
820
  });
649
821
  if (!shouldRelogin) {
650
- console.log(chalk3.gray("\nLogin cancelled. Existing API key preserved."));
822
+ console.log(chalk4.gray("\nLogin cancelled. Existing API key preserved."));
651
823
  return;
652
824
  }
653
825
  }
654
- console.log(chalk3.white("To authenticate, you need an API key from LeanMCP.\n"));
655
- console.log(chalk3.cyan("Steps:"));
656
- console.log(chalk3.gray(" 1. Go to: ") + chalk3.blue.underline("https://ship.leanmcp.com/api-keys"));
657
- console.log(chalk3.gray(' 2. Create a new API key with "BUILD_AND_DEPLOY" scope'));
658
- console.log(chalk3.gray(" 3. Copy the API key and paste it below\n"));
826
+ console.log(chalk4.white("To authenticate, you need an API key from LeanMCP.\n"));
827
+ console.log(chalk4.cyan("Steps:"));
828
+ console.log(chalk4.gray(" 1. Go to: ") + chalk4.blue.underline("https://ship.leanmcp.com/api-keys"));
829
+ console.log(chalk4.gray(' 2. Create a new API key with "BUILD_AND_DEPLOY" scope'));
830
+ console.log(chalk4.gray(" 3. Copy the API key and paste it below\n"));
659
831
  const apiKey = await input({
660
832
  message: "Enter your API key:",
661
833
  validate: /* @__PURE__ */ __name((value) => {
@@ -668,7 +840,7 @@ async function loginCommand() {
668
840
  return true;
669
841
  }, "validate")
670
842
  });
671
- const spinner = ora3("Validating API key...").start();
843
+ const spinner = ora4("Validating API key...").start();
672
844
  try {
673
845
  const apiUrl = await getApiUrl();
674
846
  const validateUrl = `${apiUrl}/api-keys/validate`;
@@ -688,10 +860,10 @@ async function loginCommand() {
688
860
  const errorText = await response.text();
689
861
  debug("Error response:", errorText);
690
862
  spinner.fail("Invalid API key");
691
- console.error(chalk3.red("\nThe API key is invalid or has expired."));
692
- console.log(chalk3.gray("Please check your API key and try again."));
863
+ console.error(chalk4.red("\nThe API key is invalid or has expired."));
864
+ console.log(chalk4.gray("Please check your API key and try again."));
693
865
  if (DEBUG_MODE) {
694
- console.log(chalk3.gray(`Debug: Status ${response.status}, Response: ${errorText}`));
866
+ console.log(chalk4.gray(`Debug: Status ${response.status}, Response: ${errorText}`));
695
867
  }
696
868
  process.exit(1);
697
869
  }
@@ -701,24 +873,24 @@ async function loginCommand() {
701
873
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
702
874
  });
703
875
  spinner.succeed("API key validated and saved");
704
- console.log(chalk3.green("\nLogin successful!"));
705
- console.log(chalk3.gray(` Config saved to: ${CONFIG_FILE}
876
+ console.log(chalk4.green("\nLogin successful!"));
877
+ console.log(chalk4.gray(` Config saved to: ${CONFIG_FILE}
706
878
  `));
707
- console.log(chalk3.cyan("You can now use:"));
708
- console.log(chalk3.gray(" leanmcp deploy <folder> - Deploy your MCP server"));
709
- console.log(chalk3.gray(" leanmcp logout - Remove your API key"));
879
+ console.log(chalk4.cyan("You can now use:"));
880
+ console.log(chalk4.gray(" leanmcp deploy <folder> - Deploy your MCP server"));
881
+ console.log(chalk4.gray(" leanmcp logout - Remove your API key"));
710
882
  } catch (error) {
711
883
  spinner.fail("Failed to validate API key");
712
884
  debug("Error:", error);
713
885
  if (error instanceof Error && error.message.includes("fetch")) {
714
- console.error(chalk3.red("\nCould not connect to LeanMCP servers."));
715
- console.log(chalk3.gray("Please check your internet connection and try again."));
886
+ console.error(chalk4.red("\nCould not connect to LeanMCP servers."));
887
+ console.log(chalk4.gray("Please check your internet connection and try again."));
716
888
  } else {
717
- console.error(chalk3.red(`
889
+ console.error(chalk4.red(`
718
890
  Error: ${error instanceof Error ? error.message : String(error)}`));
719
891
  }
720
892
  if (DEBUG_MODE) {
721
- console.log(chalk3.gray(`
893
+ console.log(chalk4.gray(`
722
894
  Debug: Full error: ${error}`));
723
895
  }
724
896
  process.exit(1);
@@ -726,10 +898,10 @@ Debug: Full error: ${error}`));
726
898
  }
727
899
  __name(loginCommand, "loginCommand");
728
900
  async function logoutCommand() {
729
- console.log(chalk3.cyan("\nLeanMCP Logout\n"));
901
+ console.log(chalk4.cyan("\nLeanMCP Logout\n"));
730
902
  const config = await loadConfig();
731
903
  if (!config.apiKey) {
732
- console.log(chalk3.yellow("You are not currently logged in."));
904
+ console.log(chalk4.yellow("You are not currently logged in."));
733
905
  return;
734
906
  }
735
907
  const shouldLogout = await confirm({
@@ -737,7 +909,7 @@ async function logoutCommand() {
737
909
  default: false
738
910
  });
739
911
  if (!shouldLogout) {
740
- console.log(chalk3.gray("\nLogout cancelled."));
912
+ console.log(chalk4.gray("\nLogout cancelled."));
741
913
  return;
742
914
  }
743
915
  await saveConfig({
@@ -745,33 +917,33 @@ async function logoutCommand() {
745
917
  apiKey: void 0,
746
918
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
747
919
  });
748
- console.log(chalk3.green("\nLogged out successfully!"));
749
- console.log(chalk3.gray(` API key removed from: ${CONFIG_FILE}`));
920
+ console.log(chalk4.green("\nLogged out successfully!"));
921
+ console.log(chalk4.gray(` API key removed from: ${CONFIG_FILE}`));
750
922
  }
751
923
  __name(logoutCommand, "logoutCommand");
752
924
  async function whoamiCommand() {
753
925
  const config = await loadConfig();
754
926
  if (!config.apiKey) {
755
- console.log(chalk3.yellow("\nYou are not logged in."));
756
- console.log(chalk3.gray("Run `leanmcp login` to authenticate.\n"));
927
+ console.log(chalk4.yellow("\nYou are not logged in."));
928
+ console.log(chalk4.gray("Run `leanmcp login` to authenticate.\n"));
757
929
  return;
758
930
  }
759
- console.log(chalk3.cyan("\nLeanMCP Authentication Status\n"));
760
- console.log(chalk3.green("Logged in"));
761
- console.log(chalk3.gray(` API Key: ${config.apiKey.substring(0, 15)}...`));
762
- console.log(chalk3.gray(` API URL: ${config.apiUrl || "https://ship.leanmcp.com"}`));
931
+ console.log(chalk4.cyan("\nLeanMCP Authentication Status\n"));
932
+ console.log(chalk4.green("Logged in"));
933
+ console.log(chalk4.gray(` API Key: ${config.apiKey.substring(0, 15)}...`));
934
+ console.log(chalk4.gray(` API URL: ${config.apiUrl || "https://ship.leanmcp.com"}`));
763
935
  if (config.lastUpdated) {
764
- console.log(chalk3.gray(` Last updated: ${new Date(config.lastUpdated).toLocaleString()}`));
936
+ console.log(chalk4.gray(` Last updated: ${new Date(config.lastUpdated).toLocaleString()}`));
765
937
  }
766
938
  console.log();
767
939
  }
768
940
  __name(whoamiCommand, "whoamiCommand");
769
941
 
770
942
  // src/commands/deploy.ts
771
- import chalk4 from "chalk";
772
- import ora4 from "ora";
773
- import path6 from "path";
774
- import fs6 from "fs-extra";
943
+ import chalk5 from "chalk";
944
+ import ora5 from "ora";
945
+ import path7 from "path";
946
+ import fs7 from "fs-extra";
775
947
  import os2 from "os";
776
948
  import archiver from "archiver";
777
949
  import { input as input2, confirm as confirm2, select } from "@inquirer/prompts";
@@ -961,7 +1133,7 @@ function setDeployDebugMode(enabled) {
961
1133
  __name(setDeployDebugMode, "setDeployDebugMode");
962
1134
  function debug2(message, ...args) {
963
1135
  if (DEBUG_MODE2) {
964
- console.log(chalk4.gray(`[DEBUG] ${message}`), ...args);
1136
+ console.log(chalk5.gray(`[DEBUG] ${message}`), ...args);
965
1137
  }
966
1138
  }
967
1139
  __name(debug2, "debug");
@@ -998,7 +1170,7 @@ var API_ENDPOINTS = {
998
1170
  };
999
1171
  async function createZipArchive(folderPath, outputPath) {
1000
1172
  return new Promise((resolve, reject) => {
1001
- const output = fs6.createWriteStream(outputPath);
1173
+ const output = fs7.createWriteStream(outputPath);
1002
1174
  const archive = archiver("zip", {
1003
1175
  zlib: {
1004
1176
  level: 9
@@ -1085,26 +1257,26 @@ async function waitForDeployment(apiUrl, apiKey, deploymentId, spinner) {
1085
1257
  __name(waitForDeployment, "waitForDeployment");
1086
1258
  async function deployCommand(folderPath, options = {}) {
1087
1259
  const deployStartTime = Date.now();
1088
- console.log(chalk4.cyan("\nLeanMCP Deploy\n"));
1260
+ console.log(chalk5.cyan("\nLeanMCP Deploy\n"));
1089
1261
  debug2("Starting deployment...");
1090
1262
  const apiKey = await getApiKey();
1091
1263
  if (!apiKey) {
1092
- console.error(chalk4.red("Not logged in."));
1093
- console.log(chalk4.gray("Run `leanmcp login` first to authenticate.\n"));
1264
+ console.error(chalk5.red("Not logged in."));
1265
+ console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
1094
1266
  process.exit(1);
1095
1267
  }
1096
1268
  const apiUrl = await getApiUrl();
1097
1269
  debug2("API URL:", apiUrl);
1098
- const absolutePath = path6.resolve(process.cwd(), folderPath);
1099
- if (!await fs6.pathExists(absolutePath)) {
1100
- console.error(chalk4.red(`Folder not found: ${absolutePath}`));
1270
+ const absolutePath = path7.resolve(process.cwd(), folderPath);
1271
+ if (!await fs7.pathExists(absolutePath)) {
1272
+ console.error(chalk5.red(`Folder not found: ${absolutePath}`));
1101
1273
  process.exit(1);
1102
1274
  }
1103
- const hasMainTs = await fs6.pathExists(path6.join(absolutePath, "main.ts"));
1104
- const hasPackageJson = await fs6.pathExists(path6.join(absolutePath, "package.json"));
1275
+ const hasMainTs = await fs7.pathExists(path7.join(absolutePath, "main.ts"));
1276
+ const hasPackageJson = await fs7.pathExists(path7.join(absolutePath, "package.json"));
1105
1277
  if (!hasMainTs && !hasPackageJson) {
1106
- console.error(chalk4.red("Not a valid project folder."));
1107
- console.log(chalk4.gray("Expected main.ts or package.json in the folder.\n"));
1278
+ console.error(chalk5.red("Not a valid project folder."));
1279
+ console.log(chalk5.gray("Expected main.ts or package.json in the folder.\n"));
1108
1280
  process.exit(1);
1109
1281
  }
1110
1282
  debug2("Fetching existing projects...");
@@ -1125,17 +1297,17 @@ async function deployCommand(folderPath, options = {}) {
1125
1297
  let projectName;
1126
1298
  let existingProject = null;
1127
1299
  let isUpdate = false;
1128
- let folderName = path6.basename(absolutePath);
1300
+ let folderName = path7.basename(absolutePath);
1129
1301
  if (hasPackageJson) {
1130
1302
  try {
1131
- const pkg2 = await fs6.readJSON(path6.join(absolutePath, "package.json"));
1303
+ const pkg2 = await fs7.readJSON(path7.join(absolutePath, "package.json"));
1132
1304
  folderName = pkg2.name || folderName;
1133
1305
  } catch (e) {
1134
1306
  }
1135
1307
  }
1136
1308
  const matchingProject = existingProjects.find((p) => p.name === folderName);
1137
1309
  if (matchingProject) {
1138
- console.log(chalk4.yellow(`Project '${folderName}' already exists.
1310
+ console.log(chalk5.yellow(`Project '${folderName}' already exists.
1139
1311
  `));
1140
1312
  const choice = await select({
1141
1313
  message: "What would you like to do?",
@@ -1155,26 +1327,26 @@ async function deployCommand(folderPath, options = {}) {
1155
1327
  ]
1156
1328
  });
1157
1329
  if (choice === "cancel") {
1158
- console.log(chalk4.gray("\nDeployment cancelled.\n"));
1330
+ console.log(chalk5.gray("\nDeployment cancelled.\n"));
1159
1331
  return;
1160
1332
  }
1161
1333
  if (choice === "update") {
1162
1334
  existingProject = matchingProject;
1163
1335
  projectName = matchingProject.name;
1164
1336
  isUpdate = true;
1165
- console.log(chalk4.yellow("\nWARNING: This will replace the existing deployment."));
1166
- console.log(chalk4.gray("The previous version will be overwritten.\n"));
1337
+ console.log(chalk5.yellow("\nWARNING: This will replace the existing deployment."));
1338
+ console.log(chalk5.gray("The previous version will be overwritten.\n"));
1167
1339
  } else {
1168
1340
  projectName = generateProjectName();
1169
- console.log(chalk4.cyan(`
1170
- Generated project name: ${chalk4.bold(projectName)}
1341
+ console.log(chalk5.cyan(`
1342
+ Generated project name: ${chalk5.bold(projectName)}
1171
1343
  `));
1172
1344
  }
1173
1345
  } else {
1174
1346
  projectName = generateProjectName();
1175
- console.log(chalk4.cyan(`Generated project name: ${chalk4.bold(projectName)}`));
1347
+ console.log(chalk5.cyan(`Generated project name: ${chalk5.bold(projectName)}`));
1176
1348
  }
1177
- console.log(chalk4.gray(`Path: ${absolutePath}
1349
+ console.log(chalk5.gray(`Path: ${absolutePath}
1178
1350
  `));
1179
1351
  let subdomain = options.subdomain;
1180
1352
  if (!subdomain) {
@@ -1196,7 +1368,7 @@ Generated project name: ${chalk4.bold(projectName)}
1196
1368
  }, "validate")
1197
1369
  });
1198
1370
  }
1199
- const checkSpinner = ora4("Checking subdomain availability...").start();
1371
+ const checkSpinner = ora5("Checking subdomain availability...").start();
1200
1372
  try {
1201
1373
  debug2("Checking subdomain:", subdomain);
1202
1374
  const checkResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.checkSubdomain}/${subdomain}`, {
@@ -1208,7 +1380,7 @@ Generated project name: ${chalk4.bold(projectName)}
1208
1380
  const result = await checkResponse.json();
1209
1381
  if (!result.available) {
1210
1382
  checkSpinner.fail(`Subdomain '${subdomain}' is already taken`);
1211
- console.log(chalk4.gray("\nPlease choose a different subdomain.\n"));
1383
+ console.log(chalk5.gray("\nPlease choose a different subdomain.\n"));
1212
1384
  process.exit(1);
1213
1385
  }
1214
1386
  }
@@ -1217,17 +1389,17 @@ Generated project name: ${chalk4.bold(projectName)}
1217
1389
  checkSpinner.warn("Could not verify subdomain availability");
1218
1390
  }
1219
1391
  if (!options.skipConfirm) {
1220
- console.log(chalk4.cyan("\nDeployment Details:"));
1221
- console.log(chalk4.gray(` Project: ${projectName}`));
1222
- console.log(chalk4.gray(` Subdomain: ${subdomain}`));
1223
- console.log(chalk4.gray(` URL: https://${subdomain}.leanmcp.dev
1392
+ console.log(chalk5.cyan("\nDeployment Details:"));
1393
+ console.log(chalk5.gray(` Project: ${projectName}`));
1394
+ console.log(chalk5.gray(` Subdomain: ${subdomain}`));
1395
+ console.log(chalk5.gray(` URL: https://${subdomain}.leanmcp.dev
1224
1396
  `));
1225
1397
  const shouldDeploy = await confirm2({
1226
1398
  message: "Proceed with deployment?",
1227
1399
  default: true
1228
1400
  });
1229
1401
  if (!shouldDeploy) {
1230
- console.log(chalk4.gray("\nDeployment cancelled.\n"));
1402
+ console.log(chalk5.gray("\nDeployment cancelled.\n"));
1231
1403
  return;
1232
1404
  }
1233
1405
  }
@@ -1235,9 +1407,9 @@ Generated project name: ${chalk4.bold(projectName)}
1235
1407
  let projectId;
1236
1408
  if (isUpdate && existingProject) {
1237
1409
  projectId = existingProject.id;
1238
- console.log(chalk4.gray(`Using existing project: ${projectId.substring(0, 8)}...`));
1410
+ console.log(chalk5.gray(`Using existing project: ${projectId.substring(0, 8)}...`));
1239
1411
  } else {
1240
- const projectSpinner = ora4("Creating project...").start();
1412
+ const projectSpinner = ora5("Creating project...").start();
1241
1413
  try {
1242
1414
  debug2("Step 1: Creating project:", projectName);
1243
1415
  const createResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}`, {
@@ -1259,14 +1431,14 @@ Generated project name: ${chalk4.bold(projectName)}
1259
1431
  projectSpinner.succeed(`Project created: ${projectId.substring(0, 8)}...`);
1260
1432
  } catch (error) {
1261
1433
  projectSpinner.fail("Failed to create project");
1262
- console.error(chalk4.red(`
1434
+ console.error(chalk5.red(`
1263
1435
  ${error instanceof Error ? error.message : String(error)}`));
1264
1436
  process.exit(1);
1265
1437
  }
1266
1438
  }
1267
- const uploadSpinner = ora4("Packaging and uploading...").start();
1439
+ const uploadSpinner = ora5("Packaging and uploading...").start();
1268
1440
  try {
1269
- const tempZip = path6.join(os2.tmpdir(), `leanmcp-${Date.now()}.zip`);
1441
+ const tempZip = path7.join(os2.tmpdir(), `leanmcp-${Date.now()}.zip`);
1270
1442
  const zipSize = await createZipArchive(absolutePath, tempZip);
1271
1443
  uploadSpinner.text = `Packaging... (${Math.round(zipSize / 1024)}KB)`;
1272
1444
  debug2("Step 2a: Getting upload URL for project:", projectId);
@@ -1293,7 +1465,7 @@ ${error instanceof Error ? error.message : String(error)}`));
1293
1465
  throw new Error("Backend did not return upload URL");
1294
1466
  }
1295
1467
  debug2("Step 2b: Uploading to S3...");
1296
- const zipBuffer = await fs6.readFile(tempZip);
1468
+ const zipBuffer = await fs7.readFile(tempZip);
1297
1469
  const s3Response = await fetch(uploadUrl, {
1298
1470
  method: "PUT",
1299
1471
  body: zipBuffer,
@@ -1316,15 +1488,15 @@ ${error instanceof Error ? error.message : String(error)}`));
1316
1488
  s3Location
1317
1489
  })
1318
1490
  });
1319
- await fs6.remove(tempZip);
1491
+ await fs7.remove(tempZip);
1320
1492
  uploadSpinner.succeed("Project uploaded");
1321
1493
  } catch (error) {
1322
1494
  uploadSpinner.fail("Failed to upload");
1323
- console.error(chalk4.red(`
1495
+ console.error(chalk5.red(`
1324
1496
  ${error instanceof Error ? error.message : String(error)}`));
1325
1497
  process.exit(1);
1326
1498
  }
1327
- const buildSpinner = ora4("Building...").start();
1499
+ const buildSpinner = ora5("Building...").start();
1328
1500
  const buildStartTime = Date.now();
1329
1501
  let buildId;
1330
1502
  let imageUri;
@@ -1348,11 +1520,11 @@ ${error instanceof Error ? error.message : String(error)}`));
1348
1520
  buildSpinner.succeed(`Build complete (${buildDuration}s)`);
1349
1521
  } catch (error) {
1350
1522
  buildSpinner.fail("Build failed");
1351
- console.error(chalk4.red(`
1523
+ console.error(chalk5.red(`
1352
1524
  ${error instanceof Error ? error.message : String(error)}`));
1353
1525
  process.exit(1);
1354
1526
  }
1355
- const deploySpinner = ora4("Deploying to LeanMCP...").start();
1527
+ const deploySpinner = ora5("Deploying to LeanMCP...").start();
1356
1528
  let deploymentId;
1357
1529
  let functionUrl;
1358
1530
  try {
@@ -1378,11 +1550,11 @@ ${error instanceof Error ? error.message : String(error)}`));
1378
1550
  deploySpinner.succeed("Deployed");
1379
1551
  } catch (error) {
1380
1552
  deploySpinner.fail("Deployment failed");
1381
- console.error(chalk4.red(`
1553
+ console.error(chalk5.red(`
1382
1554
  ${error instanceof Error ? error.message : String(error)}`));
1383
1555
  process.exit(1);
1384
1556
  }
1385
- const mappingSpinner = ora4("Configuring subdomain...").start();
1557
+ const mappingSpinner = ora5("Configuring subdomain...").start();
1386
1558
  try {
1387
1559
  debug2("Step 5: Creating subdomain mapping:", subdomain);
1388
1560
  const mappingResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.createMapping}`, {
@@ -1406,38 +1578,43 @@ ${error instanceof Error ? error.message : String(error)}`));
1406
1578
  } catch (error) {
1407
1579
  mappingSpinner.warn("Subdomain mapping may need manual setup");
1408
1580
  }
1409
- console.log(chalk4.green("\n" + "=".repeat(60)));
1410
- console.log(chalk4.green.bold(" DEPLOYMENT SUCCESSFUL!"));
1411
- console.log(chalk4.green("=".repeat(60) + "\n"));
1412
- console.log(chalk4.white(" Your MCP server is now live:\n"));
1413
- console.log(chalk4.cyan(` URL: `) + chalk4.white.bold(`https://${subdomain}.leanmcp.dev`));
1581
+ console.log(chalk5.green("\n" + "=".repeat(60)));
1582
+ console.log(chalk5.green.bold(" DEPLOYMENT SUCCESSFUL!"));
1583
+ console.log(chalk5.green("=".repeat(60) + "\n"));
1584
+ console.log(chalk5.white(" Your MCP server is now live:\n"));
1585
+ console.log(chalk5.cyan(` URL: `) + chalk5.white.bold(`https://${subdomain}.leanmcp.dev`));
1414
1586
  console.log();
1415
- console.log(chalk4.gray(" Test endpoints:"));
1416
- console.log(chalk4.gray(` curl https://${subdomain}.leanmcp.dev/health`));
1417
- console.log(chalk4.gray(` curl https://${subdomain}.leanmcp.dev/mcp`));
1587
+ console.log(chalk5.gray(" Test endpoints:"));
1588
+ console.log(chalk5.gray(` curl https://${subdomain}.leanmcp.dev/health`));
1589
+ console.log(chalk5.gray(` curl https://${subdomain}.leanmcp.dev/mcp`));
1418
1590
  console.log();
1419
1591
  const totalDuration = Math.round((Date.now() - deployStartTime) / 1e3);
1420
- console.log(chalk4.gray(` Total time: ${totalDuration}s`));
1592
+ console.log(chalk5.gray(` Total time: ${totalDuration}s`));
1421
1593
  console.log();
1422
- console.log(chalk4.gray(` Project ID: ${projectId}`));
1423
- console.log(chalk4.gray(` Build ID: ${buildId}`));
1424
- console.log(chalk4.gray(` Deployment ID: ${deploymentId}`));
1594
+ const dashboardBaseUrl = "https://ship.leanmcp.com";
1595
+ console.log(chalk5.cyan(" Dashboard links:"));
1596
+ console.log(chalk5.gray(` Project: ${dashboardBaseUrl}/projects/${projectId}`));
1597
+ console.log(chalk5.gray(` Build: ${dashboardBaseUrl}/builds/${buildId}`));
1598
+ console.log(chalk5.gray(` Deployment: ${dashboardBaseUrl}/deployments/${deploymentId}`));
1599
+ console.log();
1600
+ console.log(chalk5.cyan(" Need help? Join our Discord:"));
1601
+ console.log(chalk5.blue(" https://discord.com/invite/DsRcA3GwPy"));
1425
1602
  console.log();
1426
1603
  }
1427
1604
  __name(deployCommand, "deployCommand");
1428
1605
 
1429
1606
  // src/commands/projects.ts
1430
- import chalk5 from "chalk";
1431
- import ora5 from "ora";
1607
+ import chalk6 from "chalk";
1608
+ import ora6 from "ora";
1432
1609
  var API_ENDPOINT = "/api/projects";
1433
1610
  async function projectsListCommand() {
1434
1611
  const apiKey = await getApiKey();
1435
1612
  if (!apiKey) {
1436
- console.error(chalk5.red("\nNot logged in."));
1437
- console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
1613
+ console.error(chalk6.red("\nNot logged in."));
1614
+ console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
1438
1615
  process.exit(1);
1439
1616
  }
1440
- const spinner = ora5("Fetching projects...").start();
1617
+ const spinner = ora6("Fetching projects...").start();
1441
1618
  try {
1442
1619
  const apiUrl = await getApiUrl();
1443
1620
  const response = await fetch(`${apiUrl}${API_ENDPOINT}`, {
@@ -1451,25 +1628,25 @@ async function projectsListCommand() {
1451
1628
  const projects = await response.json();
1452
1629
  spinner.stop();
1453
1630
  if (projects.length === 0) {
1454
- console.log(chalk5.yellow("\nNo projects found."));
1455
- console.log(chalk5.gray("Create one with: leanmcp deploy <folder>\n"));
1631
+ console.log(chalk6.yellow("\nNo projects found."));
1632
+ console.log(chalk6.gray("Create one with: leanmcp deploy <folder>\n"));
1456
1633
  return;
1457
1634
  }
1458
- console.log(chalk5.cyan(`
1635
+ console.log(chalk6.cyan(`
1459
1636
  Your Projects (${projects.length})
1460
1637
  `));
1461
- console.log(chalk5.gray("\u2500".repeat(60)));
1638
+ console.log(chalk6.gray("\u2500".repeat(60)));
1462
1639
  for (const project of projects) {
1463
- const statusColor = project.status === "ACTIVE" ? chalk5.green : chalk5.yellow;
1464
- console.log(chalk5.white.bold(` ${project.name}`));
1465
- console.log(chalk5.gray(` ID: ${project.id}`));
1466
- console.log(chalk5.gray(` Status: `) + statusColor(project.status));
1467
- console.log(chalk5.gray(` Created: ${new Date(project.createdAt).toLocaleDateString()}`));
1640
+ const statusColor = project.status === "ACTIVE" ? chalk6.green : chalk6.yellow;
1641
+ console.log(chalk6.white.bold(` ${project.name}`));
1642
+ console.log(chalk6.gray(` ID: ${project.id}`));
1643
+ console.log(chalk6.gray(` Status: `) + statusColor(project.status));
1644
+ console.log(chalk6.gray(` Created: ${new Date(project.createdAt).toLocaleDateString()}`));
1468
1645
  console.log();
1469
1646
  }
1470
1647
  } catch (error) {
1471
1648
  spinner.fail("Failed to fetch projects");
1472
- console.error(chalk5.red(`
1649
+ console.error(chalk6.red(`
1473
1650
  ${error instanceof Error ? error.message : String(error)}`));
1474
1651
  process.exit(1);
1475
1652
  }
@@ -1478,11 +1655,11 @@ __name(projectsListCommand, "projectsListCommand");
1478
1655
  async function projectsGetCommand(projectId) {
1479
1656
  const apiKey = await getApiKey();
1480
1657
  if (!apiKey) {
1481
- console.error(chalk5.red("\nNot logged in."));
1482
- console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
1658
+ console.error(chalk6.red("\nNot logged in."));
1659
+ console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
1483
1660
  process.exit(1);
1484
1661
  }
1485
- const spinner = ora5("Fetching project...").start();
1662
+ const spinner = ora6("Fetching project...").start();
1486
1663
  try {
1487
1664
  const apiUrl = await getApiUrl();
1488
1665
  const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
@@ -1498,22 +1675,22 @@ async function projectsGetCommand(projectId) {
1498
1675
  }
1499
1676
  const project = await response.json();
1500
1677
  spinner.stop();
1501
- console.log(chalk5.cyan("\nProject Details\n"));
1502
- console.log(chalk5.gray("\u2500".repeat(60)));
1503
- console.log(chalk5.white.bold(` Name: ${project.name}`));
1504
- console.log(chalk5.gray(` ID: ${project.id}`));
1505
- console.log(chalk5.gray(` Status: ${project.status}`));
1678
+ console.log(chalk6.cyan("\nProject Details\n"));
1679
+ console.log(chalk6.gray("\u2500".repeat(60)));
1680
+ console.log(chalk6.white.bold(` Name: ${project.name}`));
1681
+ console.log(chalk6.gray(` ID: ${project.id}`));
1682
+ console.log(chalk6.gray(` Status: ${project.status}`));
1506
1683
  if (project.s3Location) {
1507
- console.log(chalk5.gray(` S3 Location: ${project.s3Location}`));
1684
+ console.log(chalk6.gray(` S3 Location: ${project.s3Location}`));
1508
1685
  }
1509
- console.log(chalk5.gray(` Created: ${new Date(project.createdAt).toLocaleString()}`));
1686
+ console.log(chalk6.gray(` Created: ${new Date(project.createdAt).toLocaleString()}`));
1510
1687
  if (project.updatedAt) {
1511
- console.log(chalk5.gray(` Updated: ${new Date(project.updatedAt).toLocaleString()}`));
1688
+ console.log(chalk6.gray(` Updated: ${new Date(project.updatedAt).toLocaleString()}`));
1512
1689
  }
1513
1690
  console.log();
1514
1691
  } catch (error) {
1515
1692
  spinner.fail("Failed to fetch project");
1516
- console.error(chalk5.red(`
1693
+ console.error(chalk6.red(`
1517
1694
  ${error instanceof Error ? error.message : String(error)}`));
1518
1695
  process.exit(1);
1519
1696
  }
@@ -1522,8 +1699,8 @@ __name(projectsGetCommand, "projectsGetCommand");
1522
1699
  async function projectsDeleteCommand(projectId, options = {}) {
1523
1700
  const apiKey = await getApiKey();
1524
1701
  if (!apiKey) {
1525
- console.error(chalk5.red("\nNot logged in."));
1526
- console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
1702
+ console.error(chalk6.red("\nNot logged in."));
1703
+ console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
1527
1704
  process.exit(1);
1528
1705
  }
1529
1706
  if (!options.force) {
@@ -1533,11 +1710,11 @@ async function projectsDeleteCommand(projectId, options = {}) {
1533
1710
  default: false
1534
1711
  });
1535
1712
  if (!shouldDelete) {
1536
- console.log(chalk5.gray("\nDeletion cancelled.\n"));
1713
+ console.log(chalk6.gray("\nDeletion cancelled.\n"));
1537
1714
  return;
1538
1715
  }
1539
1716
  }
1540
- const spinner = ora5("Deleting project...").start();
1717
+ const spinner = ora6("Deleting project...").start();
1541
1718
  try {
1542
1719
  const apiUrl = await getApiUrl();
1543
1720
  const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
@@ -1556,238 +1733,102 @@ async function projectsDeleteCommand(projectId, options = {}) {
1556
1733
  console.log();
1557
1734
  } catch (error) {
1558
1735
  spinner.fail("Failed to delete project");
1559
- console.error(chalk5.red(`
1736
+ console.error(chalk6.red(`
1560
1737
  ${error instanceof Error ? error.message : String(error)}`));
1561
1738
  process.exit(1);
1562
1739
  }
1563
1740
  }
1564
1741
  __name(projectsDeleteCommand, "projectsDeleteCommand");
1565
1742
 
1566
- // src/index.ts
1567
- var require2 = createRequire(import.meta.url);
1568
- var pkg = require2("../package.json");
1569
- function capitalize(str) {
1570
- return str.charAt(0).toUpperCase() + str.slice(1);
1571
- }
1572
- __name(capitalize, "capitalize");
1573
- var program = new Command();
1574
- program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version).addHelpText("after", `
1575
- Examples:
1576
- $ leanmcp create my-app # Create new project (interactive)
1577
- $ leanmcp create my-app --install # Create and install deps (non-interactive)
1578
- $ leanmcp create my-app --no-install # Create without installing deps
1579
- $ leanmcp dev # Start development server
1580
- $ leanmcp login # Authenticate with LeanMCP cloud
1581
- $ leanmcp deploy ./my-app # Deploy to LeanMCP cloud
1582
- $ leanmcp projects list # List your cloud projects
1583
- $ leanmcp projects delete <id> # Delete a cloud project
1584
- `);
1585
- program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").option("--allow-all", "Skip interactive confirmations and assume Yes").option("--no-dashboard", "Disable dashboard UI at / and /mcp GET endpoints").option("--install", "Install dependencies automatically (non-interactive, no dev server)").option("--no-install", "Skip dependency installation (non-interactive)").action(async (projectName, options) => {
1586
- const spinner = ora6(`Creating project ${projectName}...`).start();
1587
- const targetDir = path7.join(process.cwd(), projectName);
1588
- if (fs7.existsSync(targetDir)) {
1589
- spinner.fail(`Folder ${projectName} already exists.`);
1590
- process.exit(1);
1591
- }
1592
- await fs7.mkdirp(targetDir);
1593
- await fs7.mkdirp(path7.join(targetDir, "mcp", "example"));
1594
- const pkg2 = {
1595
- name: projectName,
1596
- version: "1.0.0",
1597
- description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
1598
- main: "dist/main.js",
1599
- type: "module",
1600
- scripts: {
1601
- dev: "tsx watch main.ts",
1602
- build: "tsc",
1603
- start: "node dist/main.js",
1604
- clean: "rm -rf dist"
1605
- },
1606
- keywords: [
1607
- "mcp",
1608
- "model-context-protocol",
1609
- "streamable-http",
1610
- "leanmcp"
1611
- ],
1612
- author: "",
1613
- license: "MIT",
1614
- dependencies: {
1615
- "@leanmcp/core": "^0.3.5",
1616
- "dotenv": "^16.5.0"
1617
- },
1618
- devDependencies: {
1619
- "@types/node": "^20.0.0",
1620
- "tsx": "^4.20.3",
1621
- "typescript": "^5.6.3"
1622
- }
1623
- };
1624
- await fs7.writeJSON(path7.join(targetDir, "package.json"), pkg2, {
1625
- spaces: 2
1626
- });
1627
- const tsconfig = {
1628
- compilerOptions: {
1629
- module: "ESNext",
1630
- target: "ES2022",
1631
- moduleResolution: "Node",
1632
- esModuleInterop: true,
1633
- strict: true,
1634
- skipLibCheck: true,
1635
- outDir: "dist",
1636
- experimentalDecorators: true,
1637
- emitDecoratorMetadata: true
1638
- },
1639
- include: [
1640
- "**/*.ts"
1641
- ],
1642
- exclude: [
1643
- "node_modules",
1644
- "dist"
1645
- ]
1646
- };
1647
- await fs7.writeJSON(path7.join(targetDir, "tsconfig.json"), tsconfig, {
1648
- spaces: 2
1649
- });
1650
- const dashboardLine = options.dashboard === false ? `
1651
- dashboard: false, // Dashboard disabled via --no-dashboard` : "";
1652
- const mainTs = `import dotenv from "dotenv";
1653
- import { createHTTPServer } from "@leanmcp/core";
1743
+ // src/templates/readme_v1.ts
1744
+ var getReadmeTemplate = /* @__PURE__ */ __name((projectName) => `# ${projectName}
1654
1745
 
1655
- // Load environment variables
1656
- dotenv.config();
1746
+ MCP Server with Streamable HTTP Transport built with LeanMCP SDK
1657
1747
 
1658
- // Services are automatically discovered from ./mcp directory
1659
- await createHTTPServer({
1660
- name: "${projectName}",
1661
- version: "1.0.0",
1662
- port: 3001,
1663
- cors: true,
1664
- logging: true${dashboardLine}
1665
- });
1748
+ ## Quick Start
1666
1749
 
1667
- console.log("\\n${projectName} MCP Server");
1668
- `;
1669
- await fs7.writeFile(path7.join(targetDir, "main.ts"), mainTs);
1670
- const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
1750
+ \`\`\`bash
1751
+ # Install dependencies
1752
+ npm install
1671
1753
 
1672
- /**
1673
- * Example service demonstrating LeanMCP SDK decorators
1674
- *
1675
- * This is a simple example to get you started. Add your own tools, resources, and prompts here!
1676
- */
1754
+ # Start development server (hot reload)
1755
+ npm run dev
1677
1756
 
1678
- // Input schema with validation decorators
1679
- class CalculateInput {
1680
- @SchemaConstraint({ description: "First number" })
1681
- a!: number;
1757
+ # Build for production
1758
+ npm run build
1682
1759
 
1683
- @SchemaConstraint({ description: "Second number" })
1684
- b!: number;
1760
+ # Run production server
1761
+ npm start
1762
+ \`\`\`
1685
1763
 
1686
- @Optional()
1687
- @SchemaConstraint({
1688
- description: "Operation to perform",
1689
- enum: ["add", "subtract", "multiply", "divide"],
1690
- default: "add"
1691
- })
1692
- operation?: string;
1693
- }
1694
-
1695
- class EchoInput {
1696
- @SchemaConstraint({
1697
- description: "Message to echo back",
1698
- minLength: 1
1699
- })
1700
- message!: string;
1701
- }
1764
+ ## Project Structure
1702
1765
 
1703
- export class ExampleService {
1704
- @Tool({
1705
- description: "Perform arithmetic operations with automatic schema validation",
1706
- inputClass: CalculateInput
1707
- })
1708
- async calculate(input: CalculateInput) {
1709
- // Ensure numerical operations by explicitly converting to numbers
1710
- const a = Number(input.a);
1711
- const b = Number(input.b);
1712
- let result: number;
1766
+ \`\`\`
1767
+ ${projectName}/
1768
+ \u251C\u2500\u2500 main.ts # Server entry point
1769
+ \u251C\u2500\u2500 mcp/ # Services directory (auto-discovered)
1770
+ \u2502 \u2514\u2500\u2500 example/
1771
+ \u2502 \u2514\u2500\u2500 index.ts # Example service
1772
+ \u251C\u2500\u2500 .env # Environment variables
1773
+ \u2514\u2500\u2500 package.json
1774
+ \`\`\`
1713
1775
 
1714
- switch (input.operation || "add") {
1715
- case "add":
1716
- result = a + b;
1717
- break;
1718
- case "subtract":
1719
- result = a - b;
1720
- break;
1721
- case "multiply":
1722
- result = a * b;
1723
- break;
1724
- case "divide":
1725
- if (b === 0) throw new Error("Cannot divide by zero");
1726
- result = a / b;
1727
- break;
1728
- default:
1729
- throw new Error("Invalid operation");
1730
- }
1776
+ ## Adding New Services
1731
1777
 
1732
- return {
1733
- content: [{
1734
- type: "text" as const,
1735
- text: JSON.stringify({
1736
- operation: input.operation || "add",
1737
- operands: { a: input.a, b: input.b },
1738
- result
1739
- }, null, 2)
1740
- }]
1741
- };
1742
- }
1778
+ Create a new service directory in \`mcp/\`:
1743
1779
 
1744
- @Tool({
1745
- description: "Echo a message back",
1746
- inputClass: EchoInput
1747
- })
1748
- async echo(input: EchoInput) {
1749
- return {
1750
- content: [{
1751
- type: "text" as const,
1752
- text: JSON.stringify({
1753
- echoed: input.message,
1754
- timestamp: new Date().toISOString()
1755
- }, null, 2)
1756
- }]
1757
- };
1758
- }
1780
+ \`\`\`typescript
1781
+ // mcp/myservice/index.ts
1782
+ import { Tool, SchemaConstraint } from "@leanmcp/core";
1759
1783
 
1760
- @Resource({ description: "Get server information" })
1761
- async serverInfo() {
1762
- return {
1763
- contents: [{
1764
- uri: "server://info",
1765
- mimeType: "application/json",
1766
- text: JSON.stringify({
1767
- name: "${projectName}",
1768
- version: "1.0.0",
1769
- uptime: process.uptime()
1770
- }, null, 2)
1771
- }]
1772
- };
1773
- }
1784
+ // Define input schema
1785
+ class MyToolInput {
1786
+ @SchemaConstraint({
1787
+ description: "Message to process",
1788
+ minLength: 1
1789
+ })
1790
+ message!: string;
1791
+ }
1774
1792
 
1775
- @Prompt({ description: "Generate a greeting prompt" })
1776
- async greeting(args: { name?: string }) {
1777
- return {
1778
- messages: [{
1779
- role: "user" as const,
1780
- content: {
1781
- type: "text" as const,
1782
- text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
1783
- }
1793
+ export class MyService {
1794
+ @Tool({
1795
+ description: "My awesome tool",
1796
+ inputClass: MyToolInput
1797
+ })
1798
+ async myTool(input: MyToolInput) {
1799
+ return {
1800
+ content: [{
1801
+ type: "text",
1802
+ text: \`You said: \${input.message}\`
1784
1803
  }]
1785
1804
  };
1786
1805
  }
1787
1806
  }
1788
- `;
1789
- await fs7.writeFile(path7.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
1790
- const gitignore = `# Logs
1807
+ \`\`\`
1808
+
1809
+ Services are automatically discovered and registered - no need to modify \`main.ts\`!
1810
+
1811
+ ## Features
1812
+
1813
+ - **Zero-config auto-discovery** - Services automatically registered from \`./mcp\` directory
1814
+ - **Type-safe decorators** - \`@Tool\`, \`@Prompt\`, \`@Resource\` with full TypeScript support
1815
+ - **Schema validation** - Automatic input validation with \`@SchemaConstraint\`
1816
+ - **HTTP transport** - Production-ready HTTP server with session management
1817
+ - **Hot reload** - Development mode with automatic restart on file changes
1818
+
1819
+ ## Testing with MCP Inspector
1820
+
1821
+ \`\`\`bash
1822
+ npx @modelcontextprotocol/inspector http://localhost:3001/mcp
1823
+ \`\`\`
1824
+
1825
+ ## License
1826
+
1827
+ MIT
1828
+ `, "getReadmeTemplate");
1829
+
1830
+ // src/templates/gitignore_v1.ts
1831
+ var gitignoreTemplate = `# Logs
1791
1832
  logs
1792
1833
  *.log
1793
1834
  npm-debug.log*
@@ -1929,111 +1970,333 @@ vite.config.js.timestamp-*
1929
1970
  vite.config.ts.timestamp-*
1930
1971
  .vite/
1931
1972
  `;
1932
- const env = `# Server Configuration
1933
- PORT=3001
1934
- NODE_ENV=development
1935
1973
 
1936
- # Add your environment variables here
1937
- `;
1938
- await fs7.writeFile(path7.join(targetDir, ".gitignore"), gitignore);
1939
- await fs7.writeFile(path7.join(targetDir, ".env"), env);
1940
- const readme = `# ${projectName}
1974
+ // src/templates/example_service_v1.ts
1975
+ var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
1941
1976
 
1942
- MCP Server with Streamable HTTP Transport built with LeanMCP SDK
1977
+ /**
1978
+ * Example service demonstrating LeanMCP SDK decorators
1979
+ *
1980
+ * This is a simple example to get you started. Add your own tools, resources, and prompts here!
1981
+ */
1943
1982
 
1944
- ## Quick Start
1983
+ // Input schema with validation decorators
1984
+ class CalculateInput {
1985
+ @SchemaConstraint({ description: "First number" })
1986
+ a!: number;
1945
1987
 
1946
- \`\`\`bash
1947
- # Install dependencies
1948
- npm install
1988
+ @SchemaConstraint({ description: "Second number" })
1989
+ b!: number;
1949
1990
 
1950
- # Start development server (hot reload)
1951
- npm run dev
1991
+ @Optional()
1992
+ @SchemaConstraint({
1993
+ description: "Operation to perform",
1994
+ enum: ["add", "subtract", "multiply", "divide"],
1995
+ default: "add"
1996
+ })
1997
+ operation?: string;
1998
+ }
1952
1999
 
1953
- # Build for production
1954
- npm run build
2000
+ class EchoInput {
2001
+ @SchemaConstraint({
2002
+ description: "Message to echo back",
2003
+ minLength: 1
2004
+ })
2005
+ message!: string;
2006
+ }
1955
2007
 
1956
- # Run production server
1957
- npm start
1958
- \`\`\`
2008
+ export class ExampleService {
2009
+ @Tool({
2010
+ description: "Perform arithmetic operations with automatic schema validation",
2011
+ inputClass: CalculateInput
2012
+ })
2013
+ async calculate(input: CalculateInput) {
2014
+ // Ensure numerical operations by explicitly converting to numbers
2015
+ const a = Number(input.a);
2016
+ const b = Number(input.b);
2017
+ let result: number;
1959
2018
 
1960
- ## Project Structure
2019
+ switch (input.operation || "add") {
2020
+ case "add":
2021
+ result = a + b;
2022
+ break;
2023
+ case "subtract":
2024
+ result = a - b;
2025
+ break;
2026
+ case "multiply":
2027
+ result = a * b;
2028
+ break;
2029
+ case "divide":
2030
+ if (b === 0) throw new Error("Cannot divide by zero");
2031
+ result = a / b;
2032
+ break;
2033
+ default:
2034
+ throw new Error("Invalid operation");
2035
+ }
1961
2036
 
1962
- \`\`\`
1963
- ${projectName}/
1964
- \u251C\u2500\u2500 main.ts # Server entry point
1965
- \u251C\u2500\u2500 mcp/ # Services directory (auto-discovered)
1966
- \u2502 \u2514\u2500\u2500 example/
1967
- \u2502 \u2514\u2500\u2500 index.ts # Example service
1968
- \u251C\u2500\u2500 .env # Environment variables
1969
- \u2514\u2500\u2500 package.json
1970
- \`\`\`
2037
+ return {
2038
+ content: [{
2039
+ type: "text" as const,
2040
+ text: JSON.stringify({
2041
+ operation: input.operation || "add",
2042
+ operands: { a: input.a, b: input.b },
2043
+ result
2044
+ }, null, 2)
2045
+ }]
2046
+ };
2047
+ }
1971
2048
 
1972
- ## Adding New Services
2049
+ @Tool({
2050
+ description: "Echo a message back",
2051
+ inputClass: EchoInput
2052
+ })
2053
+ async echo(input: EchoInput) {
2054
+ return {
2055
+ content: [{
2056
+ type: "text" as const,
2057
+ text: JSON.stringify({
2058
+ echoed: input.message,
2059
+ timestamp: new Date().toISOString()
2060
+ }, null, 2)
2061
+ }]
2062
+ };
2063
+ }
1973
2064
 
1974
- Create a new service directory in \`mcp/\`:
2065
+ @Resource({ description: "Get server information" })
2066
+ async serverInfo() {
2067
+ return {
2068
+ contents: [{
2069
+ uri: "server://info",
2070
+ mimeType: "application/json",
2071
+ text: JSON.stringify({
2072
+ name: "${projectName}",
2073
+ version: "1.0.0",
2074
+ uptime: process.uptime()
2075
+ }, null, 2)
2076
+ }]
2077
+ };
2078
+ }
1975
2079
 
1976
- \`\`\`typescript
1977
- // mcp/myservice/index.ts
1978
- import { Tool, SchemaConstraint } from "@leanmcp/core";
2080
+ @Prompt({ description: "Generate a greeting prompt" })
2081
+ async greeting(args: { name?: string }) {
2082
+ return {
2083
+ messages: [{
2084
+ role: "user" as const,
2085
+ content: {
2086
+ type: "text" as const,
2087
+ text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
2088
+ }
2089
+ }]
2090
+ };
2091
+ }
2092
+ }
2093
+ `, "getExampleServiceTemplate");
1979
2094
 
1980
- // Define input schema
1981
- class MyToolInput {
1982
- @SchemaConstraint({
1983
- description: "Message to process",
2095
+ // src/templates/main_ts_v1.ts
2096
+ var getMainTsTemplate = /* @__PURE__ */ __name((projectName, dashboardLine) => `import dotenv from "dotenv";
2097
+ import { createHTTPServer } from "@leanmcp/core";
2098
+
2099
+ // Load environment variables
2100
+ dotenv.config();
2101
+
2102
+ // Services are automatically discovered from ./mcp directory
2103
+ await createHTTPServer({
2104
+ name: "${projectName}",
2105
+ version: "1.0.0",
2106
+ port: 3001,
2107
+ cors: true,
2108
+ logging: true${dashboardLine}
2109
+ });
2110
+
2111
+ console.log("\\n${projectName} MCP Server");
2112
+ `, "getMainTsTemplate");
2113
+
2114
+ // src/templates/service_index_v1.ts
2115
+ var getServiceIndexTemplate = /* @__PURE__ */ __name((serviceName, capitalizedName) => `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
2116
+
2117
+ // Input schema for greeting
2118
+ class GreetInput {
2119
+ @SchemaConstraint({
2120
+ description: "Name to greet",
1984
2121
  minLength: 1
1985
2122
  })
1986
- message!: string;
2123
+ name!: string;
1987
2124
  }
1988
2125
 
1989
- export class MyService {
2126
+ /**
2127
+ * ${capitalizedName} Service
2128
+ *
2129
+ * This service demonstrates the three types of MCP primitives:
2130
+ * - Tools: Callable functions (like API endpoints)
2131
+ * - Prompts: Reusable prompt templates
2132
+ * - Resources: Data sources/endpoints
2133
+ */
2134
+ export class ${capitalizedName}Service {
2135
+ // TOOL - Callable function
2136
+ // Tool name: "greet" (from function name)
1990
2137
  @Tool({
1991
- description: "My awesome tool",
1992
- inputClass: MyToolInput
2138
+ description: "Greet a user by name",
2139
+ inputClass: GreetInput
1993
2140
  })
1994
- async myTool(input: MyToolInput) {
2141
+ greet(args: GreetInput) {
2142
+ return { message: \`Hello, \${args.name}! from ${serviceName}\` };
2143
+ }
2144
+
2145
+ // PROMPT - Prompt template
2146
+ // Prompt name: "welcomePrompt" (from function name)
2147
+ @Prompt({ description: "Welcome message prompt template" })
2148
+ welcomePrompt(args: { userName?: string }) {
1995
2149
  return {
1996
- content: [{
1997
- type: "text",
1998
- text: \`You said: \${input.message}\`
1999
- }]
2150
+ messages: [
2151
+ {
2152
+ role: "user",
2153
+ content: {
2154
+ type: "text",
2155
+ text: \`Welcome \${args.userName || 'user'}! How can I help you with ${serviceName}?\`
2156
+ }
2157
+ }
2158
+ ]
2000
2159
  };
2001
2160
  }
2002
- }
2003
- \`\`\`
2004
-
2005
- Services are automatically discovered and registered - no need to modify \`main.ts\`!
2006
-
2007
- ## Features
2008
2161
 
2009
- - **Zero-config auto-discovery** - Services automatically registered from \`./mcp\` directory
2010
- - **Type-safe decorators** - \`@Tool\`, \`@Prompt\`, \`@Resource\` with full TypeScript support
2011
- - **Schema validation** - Automatic input validation with \`@SchemaConstraint\`
2012
- - **HTTP transport** - Production-ready HTTP server with session management
2013
- - **Hot reload** - Development mode with automatic restart on file changes
2014
-
2015
- ## Testing with MCP Inspector
2016
-
2017
- \`\`\`bash
2018
- npx @modelcontextprotocol/inspector http://localhost:3001/mcp
2019
- \`\`\`
2162
+ // RESOURCE - Data endpoint
2163
+ // Resource URI auto-generated from class and method name
2164
+ @Resource({ description: "${capitalizedName} service status" })
2165
+ getStatus() {
2166
+ return {
2167
+ service: "${serviceName}",
2168
+ status: "active",
2169
+ timestamp: new Date().toISOString()
2170
+ };
2171
+ }
2172
+ }
2173
+ `, "getServiceIndexTemplate");
2020
2174
 
2021
- ## License
2175
+ // src/index.ts
2176
+ var require2 = createRequire(import.meta.url);
2177
+ var pkg = require2("../package.json");
2178
+ function capitalize(str) {
2179
+ return str.charAt(0).toUpperCase() + str.slice(1);
2180
+ }
2181
+ __name(capitalize, "capitalize");
2182
+ var program = new Command();
2183
+ program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version).addHelpText("after", `
2184
+ Examples:
2185
+ $ leanmcp create my-app # Create new project (interactive)
2186
+ $ leanmcp create my-app --install # Create and install deps (non-interactive)
2187
+ $ leanmcp create my-app --no-install # Create without installing deps
2188
+ $ leanmcp dev # Start development server
2189
+ $ leanmcp build # Build UI components and compile TypeScript
2190
+ $ leanmcp start # Build and start production server
2191
+ $ leanmcp login # Authenticate with LeanMCP cloud
2192
+ $ leanmcp deploy ./my-app # Deploy to LeanMCP cloud
2193
+ $ leanmcp projects list # List your cloud projects
2194
+ $ leanmcp projects delete <id> # Delete a cloud project
2195
+ `);
2196
+ program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").option("--allow-all", "Skip interactive confirmations and assume Yes").option("--no-dashboard", "Disable dashboard UI at / and /mcp GET endpoints").option("--install", "Install dependencies automatically (non-interactive, no dev server)").option("--no-install", "Skip dependency installation (non-interactive)").action(async (projectName, options) => {
2197
+ const spinner = ora7(`Creating project ${projectName}...`).start();
2198
+ const targetDir = path8.join(process.cwd(), projectName);
2199
+ if (fs8.existsSync(targetDir)) {
2200
+ spinner.fail(`Folder ${projectName} already exists.`);
2201
+ process.exit(1);
2202
+ }
2203
+ await fs8.mkdirp(targetDir);
2204
+ await fs8.mkdirp(path8.join(targetDir, "mcp", "example"));
2205
+ const pkg2 = {
2206
+ name: projectName,
2207
+ version: "1.0.0",
2208
+ description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
2209
+ main: "dist/main.js",
2210
+ type: "module",
2211
+ scripts: {
2212
+ dev: "leanmcp dev",
2213
+ build: "leanmcp build",
2214
+ start: "leanmcp start",
2215
+ "start:node": "node dist/main.js",
2216
+ clean: "rm -rf dist"
2217
+ },
2218
+ keywords: [
2219
+ "mcp",
2220
+ "model-context-protocol",
2221
+ "streamable-http",
2222
+ "leanmcp"
2223
+ ],
2224
+ author: "",
2225
+ license: "MIT",
2226
+ dependencies: {
2227
+ "@leanmcp/core": "^0.3.9",
2228
+ "@leanmcp/ui": "^0.2.1",
2229
+ "@leanmcp/auth": "^0.3.2",
2230
+ "dotenv": "^16.5.0"
2231
+ },
2232
+ devDependencies: {
2233
+ "@leanmcp/cli": "^0.3.0",
2234
+ "@types/node": "^20.0.0",
2235
+ "tsx": "^4.20.3",
2236
+ "typescript": "^5.6.3"
2237
+ }
2238
+ };
2239
+ await fs8.writeJSON(path8.join(targetDir, "package.json"), pkg2, {
2240
+ spaces: 2
2241
+ });
2242
+ const tsconfig = {
2243
+ compilerOptions: {
2244
+ module: "ESNext",
2245
+ target: "ES2022",
2246
+ moduleResolution: "Node",
2247
+ esModuleInterop: true,
2248
+ strict: true,
2249
+ skipLibCheck: true,
2250
+ outDir: "dist",
2251
+ experimentalDecorators: true,
2252
+ emitDecoratorMetadata: true
2253
+ },
2254
+ include: [
2255
+ "**/*.ts"
2256
+ ],
2257
+ exclude: [
2258
+ "node_modules",
2259
+ "dist"
2260
+ ]
2261
+ };
2262
+ await fs8.writeJSON(path8.join(targetDir, "tsconfig.json"), tsconfig, {
2263
+ spaces: 2
2264
+ });
2265
+ const dashboardLine = options.dashboard === false ? `
2266
+ dashboard: false, // Dashboard disabled via --no-dashboard` : "";
2267
+ const mainTs = getMainTsTemplate(projectName, dashboardLine);
2268
+ await fs8.writeFile(path8.join(targetDir, "main.ts"), mainTs);
2269
+ const exampleServiceTs = getExampleServiceTemplate(projectName);
2270
+ await fs8.writeFile(path8.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
2271
+ const gitignore = gitignoreTemplate;
2272
+ const env = `# Server Configuration
2273
+ PORT=3001
2274
+ NODE_ENV=development
2022
2275
 
2023
- MIT
2276
+ # Add your environment variables here
2024
2277
  `;
2025
- await fs7.writeFile(path7.join(targetDir, "README.md"), readme);
2278
+ await fs8.writeFile(path8.join(targetDir, ".gitignore"), gitignore);
2279
+ await fs8.writeFile(path8.join(targetDir, ".env"), env);
2280
+ const readme = getReadmeTemplate(projectName);
2281
+ await fs8.writeFile(path8.join(targetDir, "README.md"), readme);
2026
2282
  spinner.succeed(`Project ${projectName} created!`);
2027
- console.log(chalk6.green("\nSuccess! Your MCP server is ready.\n"));
2028
- console.log(chalk6.cyan(`Next, navigate to your project:
2029
- cd ${projectName}
2283
+ console.log(chalk7.green("\nSuccess! Your MCP server is ready.\n"));
2284
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2285
+ console.log(chalk7.gray(` cd ${projectName}`));
2286
+ console.log(chalk7.gray(` leanmcp deploy .
2030
2287
  `));
2288
+ console.log(chalk7.cyan("Need help? Join our Discord:"));
2289
+ console.log(chalk7.blue(" https://discord.com/invite/DsRcA3GwPy\n"));
2031
2290
  const isNonInteractive = options.install !== void 0 || options.allowAll;
2032
2291
  if (options.install === false) {
2033
- console.log(chalk6.cyan("\nTo get started:"));
2034
- console.log(chalk6.gray(` cd ${projectName}`));
2035
- console.log(chalk6.gray(` npm install`));
2036
- console.log(chalk6.gray(` npm run dev`));
2292
+ console.log(chalk7.cyan("To get started:"));
2293
+ console.log(chalk7.gray(` cd ${projectName}`));
2294
+ console.log(chalk7.gray(` npm install`));
2295
+ console.log(chalk7.gray(` npm run dev`));
2296
+ console.log();
2297
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2298
+ console.log(chalk7.gray(` cd ${projectName}`));
2299
+ console.log(chalk7.gray(` leanmcp deploy .`));
2037
2300
  return;
2038
2301
  }
2039
2302
  const shouldInstall = isNonInteractive ? true : await confirm3({
@@ -2041,10 +2304,10 @@ MIT
2041
2304
  default: true
2042
2305
  });
2043
2306
  if (shouldInstall) {
2044
- const installSpinner = ora6("Installing dependencies...").start();
2307
+ const installSpinner = ora7("Installing dependencies...").start();
2045
2308
  try {
2046
2309
  await new Promise((resolve, reject) => {
2047
- const npmInstall = spawn3("npm", [
2310
+ const npmInstall = spawn4("npm", [
2048
2311
  "install"
2049
2312
  ], {
2050
2313
  cwd: targetDir,
@@ -2062,9 +2325,13 @@ MIT
2062
2325
  });
2063
2326
  installSpinner.succeed("Dependencies installed successfully!");
2064
2327
  if (options.install === true) {
2065
- console.log(chalk6.cyan("\nTo start the development server:"));
2066
- console.log(chalk6.gray(` cd ${projectName}`));
2067
- console.log(chalk6.gray(` npm run dev`));
2328
+ console.log(chalk7.cyan("\nTo start the development server:"));
2329
+ console.log(chalk7.gray(` cd ${projectName}`));
2330
+ console.log(chalk7.gray(` npm run dev`));
2331
+ console.log();
2332
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2333
+ console.log(chalk7.gray(` cd ${projectName}`));
2334
+ console.log(chalk7.gray(` leanmcp deploy .`));
2068
2335
  return;
2069
2336
  }
2070
2337
  const shouldStartDev = options.allowAll ? true : await confirm3({
@@ -2072,8 +2339,8 @@ MIT
2072
2339
  default: true
2073
2340
  });
2074
2341
  if (shouldStartDev) {
2075
- console.log(chalk6.cyan("\nStarting development server...\n"));
2076
- const devServer = spawn3("npm", [
2342
+ console.log(chalk7.cyan("\nStarting development server...\n"));
2343
+ const devServer = spawn4("npm", [
2077
2344
  "run",
2078
2345
  "dev"
2079
2346
  ], {
@@ -2086,106 +2353,57 @@ MIT
2086
2353
  process.exit(0);
2087
2354
  });
2088
2355
  } else {
2089
- console.log(chalk6.cyan("\nTo start the development server later:"));
2090
- console.log(chalk6.gray(` cd ${projectName}`));
2091
- console.log(chalk6.gray(` npm run dev`));
2356
+ console.log(chalk7.cyan("\nTo start the development server later:"));
2357
+ console.log(chalk7.gray(` cd ${projectName}`));
2358
+ console.log(chalk7.gray(` npm run dev`));
2359
+ console.log();
2360
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2361
+ console.log(chalk7.gray(` cd ${projectName}`));
2362
+ console.log(chalk7.gray(` leanmcp deploy .`));
2092
2363
  }
2093
2364
  } catch (error) {
2094
2365
  installSpinner.fail("Failed to install dependencies");
2095
- console.error(chalk6.red(error instanceof Error ? error.message : String(error)));
2096
- console.log(chalk6.cyan("\nYou can install dependencies manually:"));
2097
- console.log(chalk6.gray(` cd ${projectName}`));
2098
- console.log(chalk6.gray(` npm install`));
2366
+ console.error(chalk7.red(error instanceof Error ? error.message : String(error)));
2367
+ console.log(chalk7.cyan("\nYou can install dependencies manually:"));
2368
+ console.log(chalk7.gray(` cd ${projectName}`));
2369
+ console.log(chalk7.gray(` npm install`));
2099
2370
  }
2100
2371
  } else {
2101
- console.log(chalk6.cyan("\nTo get started:"));
2102
- console.log(chalk6.gray(` cd ${projectName}`));
2103
- console.log(chalk6.gray(` npm install`));
2104
- console.log(chalk6.gray(` npm run dev`));
2372
+ console.log(chalk7.cyan("\nTo get started:"));
2373
+ console.log(chalk7.gray(` cd ${projectName}`));
2374
+ console.log(chalk7.gray(` npm install`));
2375
+ console.log(chalk7.gray(` npm run dev`));
2376
+ console.log();
2377
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2378
+ console.log(chalk7.gray(` cd ${projectName}`));
2379
+ console.log(chalk7.gray(` leanmcp deploy .`));
2105
2380
  }
2106
2381
  });
2107
2382
  program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
2108
2383
  const cwd = process.cwd();
2109
- const mcpDir = path7.join(cwd, "mcp");
2110
- if (!fs7.existsSync(path7.join(cwd, "main.ts"))) {
2111
- console.error(chalk6.red("ERROR: Not a LeanMCP project (main.ts missing)."));
2384
+ const mcpDir = path8.join(cwd, "mcp");
2385
+ if (!fs8.existsSync(path8.join(cwd, "main.ts"))) {
2386
+ console.error(chalk7.red("ERROR: Not a LeanMCP project (main.ts missing)."));
2112
2387
  process.exit(1);
2113
2388
  }
2114
- const serviceDir = path7.join(mcpDir, serviceName);
2115
- const serviceFile = path7.join(serviceDir, "index.ts");
2116
- if (fs7.existsSync(serviceDir)) {
2117
- console.error(chalk6.red(`ERROR: Service ${serviceName} already exists.`));
2389
+ const serviceDir = path8.join(mcpDir, serviceName);
2390
+ const serviceFile = path8.join(serviceDir, "index.ts");
2391
+ if (fs8.existsSync(serviceDir)) {
2392
+ console.error(chalk7.red(`ERROR: Service ${serviceName} already exists.`));
2118
2393
  process.exit(1);
2119
2394
  }
2120
- await fs7.mkdirp(serviceDir);
2121
- const indexTs = `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
2122
-
2123
- // Input schema for greeting
2124
- class GreetInput {
2125
- @SchemaConstraint({
2126
- description: "Name to greet",
2127
- minLength: 1
2128
- })
2129
- name!: string;
2130
- }
2131
-
2132
- /**
2133
- * ${capitalize(serviceName)} Service
2134
- *
2135
- * This service demonstrates the three types of MCP primitives:
2136
- * - Tools: Callable functions (like API endpoints)
2137
- * - Prompts: Reusable prompt templates
2138
- * - Resources: Data sources/endpoints
2139
- */
2140
- export class ${capitalize(serviceName)}Service {
2141
- // TOOL - Callable function
2142
- // Tool name: "greet" (from function name)
2143
- @Tool({
2144
- description: "Greet a user by name",
2145
- inputClass: GreetInput
2146
- })
2147
- greet(args: GreetInput) {
2148
- return { message: \`Hello, \${args.name}! from ${serviceName}\` };
2149
- }
2150
-
2151
- // PROMPT - Prompt template
2152
- // Prompt name: "welcomePrompt" (from function name)
2153
- @Prompt({ description: "Welcome message prompt template" })
2154
- welcomePrompt(args: { userName?: string }) {
2155
- return {
2156
- messages: [
2157
- {
2158
- role: "user",
2159
- content: {
2160
- type: "text",
2161
- text: \`Welcome \${args.userName || 'user'}! How can I help you with ${serviceName}?\`
2162
- }
2163
- }
2164
- ]
2165
- };
2166
- }
2167
-
2168
- // RESOURCE - Data endpoint
2169
- // Resource URI auto-generated from class and method name
2170
- @Resource({ description: "${capitalize(serviceName)} service status" })
2171
- getStatus() {
2172
- return {
2173
- service: "${serviceName}",
2174
- status: "active",
2175
- timestamp: new Date().toISOString()
2176
- };
2177
- }
2178
- }
2179
- `;
2180
- await fs7.writeFile(serviceFile, indexTs);
2181
- console.log(chalk6.green(`\\nCreated new service: ${chalk6.bold(serviceName)}`));
2182
- console.log(chalk6.gray(` File: mcp/${serviceName}/index.ts`));
2183
- console.log(chalk6.gray(` Tool: greet`));
2184
- console.log(chalk6.gray(` Prompt: welcomePrompt`));
2185
- console.log(chalk6.gray(` Resource: getStatus`));
2186
- console.log(chalk6.green(`\\nService will be automatically discovered on next server start!`));
2395
+ await fs8.mkdirp(serviceDir);
2396
+ const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
2397
+ await fs8.writeFile(serviceFile, indexTs);
2398
+ console.log(chalk7.green(`\\nCreated new service: ${chalk7.bold(serviceName)}`));
2399
+ console.log(chalk7.gray(` File: mcp/${serviceName}/index.ts`));
2400
+ console.log(chalk7.gray(` Tool: greet`));
2401
+ console.log(chalk7.gray(` Prompt: welcomePrompt`));
2402
+ console.log(chalk7.gray(` Resource: getStatus`));
2403
+ console.log(chalk7.green(`\\nService will be automatically discovered on next server start!`));
2187
2404
  });
2188
2405
  program.command("dev").description("Start development server with UI hot-reload (builds @UIApp components)").action(devCommand);
2406
+ program.command("build").description("Build UI components and compile TypeScript for production").action(buildCommand);
2189
2407
  program.command("start").description("Build UI components and start production server").action(startCommand);
2190
2408
  program.command("login").description("Authenticate with LeanMCP cloud using an API key").option("--debug", "Enable debug logging").action(async (options) => {
2191
2409
  if (options.debug) {