@leanmcp/cli 0.4.2 → 0.4.3

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.
package/dist/index.js CHANGED
@@ -3,7 +3,6 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
3
3
 
4
4
  // src/index.ts
5
5
  import { Command } from "commander";
6
- import chalk7 from "chalk";
7
6
  import fs8 from "fs-extra";
8
7
  import path8 from "path";
9
8
  import ora7 from "ora";
@@ -13,11 +12,10 @@ import { spawn as spawn4 } from "child_process";
13
12
 
14
13
  // src/commands/dev.ts
15
14
  import { spawn } from "child_process";
16
- import chalk from "chalk";
17
15
  import ora from "ora";
18
16
  import path3 from "path";
19
17
  import fs3 from "fs-extra";
20
- import crypto from "crypto";
18
+ import crypto2 from "crypto";
21
19
  import chokidar from "chokidar";
22
20
 
23
21
  // src/vite/scanUIApp.ts
@@ -469,10 +467,199 @@ async function deleteUIComponent(uri, projectDir) {
469
467
  }
470
468
  __name(deleteUIComponent, "deleteUIComponent");
471
469
 
470
+ // src/logger.ts
471
+ import chalk from "chalk";
472
+ import os from "os";
473
+ import crypto from "crypto";
474
+ import { execSync } from "child_process";
475
+ var POSTHOG_API_KEY = "phc_EoMHKFbx6j2wUFsf8ywqgHntY4vEXC3ZzLFoPJVjRRT";
476
+ var POSTHOG_API_HOST = "https://d18m0xvdtnkibr.cloudfront.net";
477
+ var DEBUG_MODE = false;
478
+ function setDebugMode(enabled) {
479
+ DEBUG_MODE = enabled;
480
+ }
481
+ __name(setDebugMode, "setDebugMode");
482
+ function isDebugMode() {
483
+ return DEBUG_MODE;
484
+ }
485
+ __name(isDebugMode, "isDebugMode");
486
+ function debug(message, ...args) {
487
+ if (DEBUG_MODE) {
488
+ console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
489
+ }
490
+ }
491
+ __name(debug, "debug");
492
+ var isTelemetryDisabled = /* @__PURE__ */ __name(() => {
493
+ return process.env.LEANMCP_DISABLE_TELEMETRY === "true";
494
+ }, "isTelemetryDisabled");
495
+ var getAnonymousId = /* @__PURE__ */ __name(() => {
496
+ const identifier = `${os.hostname()}-${os.userInfo().username}`;
497
+ return crypto.createHash("sha256").update(identifier).digest("hex").substring(0, 16);
498
+ }, "getAnonymousId");
499
+ var cachedNpmVersion = null;
500
+ var getNpmVersion = /* @__PURE__ */ __name(() => {
501
+ if (cachedNpmVersion) return cachedNpmVersion;
502
+ try {
503
+ cachedNpmVersion = execSync("npm --version", {
504
+ encoding: "utf-8"
505
+ }).trim();
506
+ } catch {
507
+ cachedNpmVersion = "unknown";
508
+ }
509
+ return cachedNpmVersion;
510
+ }, "getNpmVersion");
511
+ var getSystemInfo = /* @__PURE__ */ __name(() => {
512
+ return {
513
+ $os: os.platform(),
514
+ $os_version: os.release(),
515
+ arch: os.arch(),
516
+ node_version: process.version,
517
+ npm_version: getNpmVersion(),
518
+ cpu_count: os.cpus().length,
519
+ cpu_model: os.cpus()[0]?.model || "unknown",
520
+ ram_total_gb: Math.round(os.totalmem() / (1024 * 1024 * 1024) * 10) / 10,
521
+ ram_free_gb: Math.round(os.freemem() / (1024 * 1024 * 1024) * 10) / 10,
522
+ hostname_hash: getAnonymousId()
523
+ };
524
+ }, "getSystemInfo");
525
+ var systemInfoSent = false;
526
+ var sendToPostHog = /* @__PURE__ */ __name((eventName, properties = {}) => {
527
+ if (isTelemetryDisabled()) {
528
+ debug("[PostHog] Telemetry disabled, skipping event:", eventName);
529
+ return;
530
+ }
531
+ setImmediate(() => {
532
+ try {
533
+ const systemProps = !systemInfoSent ? getSystemInfo() : {};
534
+ systemInfoSent = true;
535
+ const payload = {
536
+ api_key: POSTHOG_API_KEY,
537
+ event: eventName,
538
+ properties: {
539
+ distinct_id: getAnonymousId(),
540
+ $lib: "leanmcp-cli",
541
+ ...systemProps,
542
+ ...properties
543
+ },
544
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
545
+ };
546
+ const url = `${POSTHOG_API_HOST}/capture/`;
547
+ debug(`[PostHog] POST ${url}`);
548
+ debug(`[PostHog] Event: ${eventName}`);
549
+ debug(`[PostHog] Payload:`, JSON.stringify(payload, null, 2));
550
+ const controller = new AbortController();
551
+ const timeoutId = setTimeout(() => controller.abort(), 3e3);
552
+ fetch(url, {
553
+ method: "POST",
554
+ headers: {
555
+ "Content-Type": "application/json"
556
+ },
557
+ body: JSON.stringify(payload),
558
+ signal: controller.signal
559
+ }).then((response) => {
560
+ clearTimeout(timeoutId);
561
+ debug(`[PostHog] Response: ${response.status} ${response.statusText}`);
562
+ }).catch((err) => {
563
+ clearTimeout(timeoutId);
564
+ debug(`[PostHog] Error: ${err.message || err}`);
565
+ });
566
+ } catch (err) {
567
+ debug(`[PostHog] Exception: ${err.message || err}`);
568
+ }
569
+ });
570
+ }, "sendToPostHog");
571
+ var LoggerClass = class LoggerClass2 {
572
+ static {
573
+ __name(this, "LoggerClass");
574
+ }
575
+ /**
576
+ * Log a message to console and send cli_log event to PostHog
577
+ * @param text - The text to log
578
+ * @param styleFn - Optional chalk style function (e.g., chalk.cyan, chalk.green)
579
+ */
580
+ log(text, styleFn) {
581
+ if (styleFn) {
582
+ console.log(styleFn(text));
583
+ } else {
584
+ console.log(text);
585
+ }
586
+ sendToPostHog("cli_log", {
587
+ message: text,
588
+ level: "log"
589
+ });
590
+ }
591
+ /**
592
+ * Log a warning to console and send cli_warn event to PostHog
593
+ * @param text - The warning text
594
+ * @param styleFn - Optional chalk style function (defaults to chalk.yellow)
595
+ */
596
+ warn(text, styleFn) {
597
+ const style = styleFn || chalk.yellow;
598
+ console.log(style(text));
599
+ sendToPostHog("cli_warn", {
600
+ message: text,
601
+ level: "warn"
602
+ });
603
+ }
604
+ /**
605
+ * Log an error to console and send cli_error event to PostHog
606
+ * @param text - The error text
607
+ * @param styleFn - Optional chalk style function (defaults to chalk.red)
608
+ */
609
+ error(text, styleFn) {
610
+ const style = styleFn || chalk.red;
611
+ console.error(style(text));
612
+ sendToPostHog("cli_error", {
613
+ message: text,
614
+ level: "error"
615
+ });
616
+ }
617
+ /**
618
+ * Log an info message (alias for log with cyan styling)
619
+ * @param text - The info text
620
+ */
621
+ info(text) {
622
+ console.log(chalk.cyan(text));
623
+ sendToPostHog("cli_log", {
624
+ message: text,
625
+ level: "info"
626
+ });
627
+ }
628
+ /**
629
+ * Log a success message (green styling)
630
+ * @param text - The success text
631
+ */
632
+ success(text) {
633
+ console.log(chalk.green(text));
634
+ sendToPostHog("cli_log", {
635
+ message: text,
636
+ level: "success"
637
+ });
638
+ }
639
+ /**
640
+ * Log a gray/muted message
641
+ * @param text - The text to log
642
+ */
643
+ gray(text) {
644
+ console.log(chalk.gray(text));
645
+ sendToPostHog("cli_log", {
646
+ message: text,
647
+ level: "gray"
648
+ });
649
+ }
650
+ };
651
+ var logger = new LoggerClass();
652
+ var trackCommand = /* @__PURE__ */ __name((command, options = {}) => {
653
+ sendToPostHog("cli_command", {
654
+ command,
655
+ options
656
+ });
657
+ }, "trackCommand");
658
+
472
659
  // src/commands/dev.ts
473
660
  function computeHash(filePath) {
474
661
  const content = fs3.readFileSync(filePath, "utf-8");
475
- return crypto.createHash("md5").update(content).digest("hex");
662
+ return crypto2.createHash("md5").update(content).digest("hex");
476
663
  }
477
664
  __name(computeHash, "computeHash");
478
665
  function computeDiff(previousUIApps, currentUIApps, hashCache) {
@@ -506,11 +693,11 @@ __name(computeDiff, "computeDiff");
506
693
  async function devCommand() {
507
694
  const cwd = process.cwd();
508
695
  if (!await fs3.pathExists(path3.join(cwd, "main.ts"))) {
509
- console.error(chalk.red("ERROR: Not a LeanMCP project (main.ts not found)."));
510
- console.error(chalk.gray("Run this command from your project root."));
696
+ logger.error("ERROR: Not a LeanMCP project (main.ts not found).");
697
+ logger.gray("Run this command from your project root.");
511
698
  process.exit(1);
512
699
  }
513
- console.log(chalk.cyan("\nLeanMCP Development Server\n"));
700
+ logger.info("\nLeanMCP Development Server\n");
514
701
  const scanSpinner = ora("Scanning for @UIApp components...").start();
515
702
  const uiApps = await scanUIApp(cwd);
516
703
  if (uiApps.length === 0) {
@@ -542,13 +729,13 @@ async function devCommand() {
542
729
  if (errors.length > 0) {
543
730
  buildSpinner.warn("Built with warnings");
544
731
  for (const error of errors) {
545
- console.error(chalk.yellow(` ${error}`));
732
+ logger.warn(` ${error}`);
546
733
  }
547
734
  } else {
548
735
  buildSpinner.info("UI components built");
549
736
  }
550
737
  }
551
- console.log(chalk.cyan("\nStarting development server...\n"));
738
+ logger.info("\nStarting development server...\n");
552
739
  const devServer = spawn("npx", [
553
740
  "tsx",
554
741
  "watch",
@@ -589,14 +776,14 @@ async function devCommand() {
589
776
  return;
590
777
  }
591
778
  for (const uri of diff.removed) {
592
- console.log(chalk.yellow(`Removing ${uri}...`));
779
+ logger.warn(`Removing ${uri}...`);
593
780
  await deleteUIComponent(uri, cwd);
594
781
  delete manifest[uri];
595
782
  componentHashCache.delete(uri);
596
783
  }
597
784
  for (const app of diff.addedOrChanged) {
598
785
  const action = diff.added.has(app.resourceUri) ? "Building" : "Rebuilding";
599
- console.log(chalk.cyan(`${action} ${app.componentName}...`));
786
+ logger.info(`${action} ${app.componentName}...`);
600
787
  const result = await buildUIComponent(app, cwd, true);
601
788
  if (result.success) {
602
789
  if (app.isGPTApp) {
@@ -611,9 +798,9 @@ async function devCommand() {
611
798
  if (await fs3.pathExists(app.componentPath)) {
612
799
  componentHashCache.set(app.resourceUri, computeHash(app.componentPath));
613
800
  }
614
- console.log(chalk.green(`${app.componentName} ${action.toLowerCase()} complete`));
801
+ logger.success(`${app.componentName} ${action.toLowerCase()} complete`);
615
802
  } else {
616
- console.log(chalk.yellow(`Build failed: ${result.error}`));
803
+ logger.warn(`Build failed: ${result.error}`);
617
804
  }
618
805
  }
619
806
  await writeUIManifest(manifest, cwd);
@@ -625,7 +812,7 @@ async function devCommand() {
625
812
  const cleanup = /* @__PURE__ */ __name(() => {
626
813
  if (isCleaningUp) return;
627
814
  isCleaningUp = true;
628
- console.log(chalk.gray("\nShutting down..."));
815
+ logger.gray("\nShutting down...");
629
816
  if (watcher) {
630
817
  watcher.close();
631
818
  watcher = null;
@@ -653,18 +840,17 @@ __name(devCommand, "devCommand");
653
840
 
654
841
  // src/commands/build.ts
655
842
  import { spawn as spawn2 } from "child_process";
656
- import chalk2 from "chalk";
657
843
  import ora2 from "ora";
658
844
  import path4 from "path";
659
845
  import fs4 from "fs-extra";
660
846
  async function buildCommand() {
661
847
  const cwd = process.cwd();
662
848
  if (!await fs4.pathExists(path4.join(cwd, "main.ts"))) {
663
- console.error(chalk2.red("ERROR: Not a LeanMCP project (main.ts not found)."));
664
- console.error(chalk2.gray("Run this command from your project root."));
849
+ logger.error("ERROR: Not a LeanMCP project (main.ts not found).");
850
+ logger.gray("Run this command from your project root.");
665
851
  process.exit(1);
666
852
  }
667
- console.log(chalk2.cyan("\n\u{1F528} LeanMCP Build\n"));
853
+ logger.info("\n\u{1F528} LeanMCP Build\n");
668
854
  const scanSpinner = ora2("Scanning for @UIApp components...").start();
669
855
  const uiApps = await scanUIApp(cwd);
670
856
  if (uiApps.length === 0) {
@@ -692,7 +878,7 @@ async function buildCommand() {
692
878
  if (errors.length > 0) {
693
879
  buildSpinner.fail("Build failed");
694
880
  for (const error of errors) {
695
- console.error(chalk2.red(` \u2717 ${error}`));
881
+ logger.error(` \u2717 ${error}`);
696
882
  }
697
883
  process.exit(1);
698
884
  }
@@ -721,29 +907,28 @@ async function buildCommand() {
721
907
  tscSpinner.succeed("TypeScript compiled");
722
908
  } catch (error) {
723
909
  tscSpinner.fail("TypeScript compilation failed");
724
- console.error(chalk2.red(error instanceof Error ? error.message : String(error)));
910
+ logger.error(error instanceof Error ? error.message : String(error));
725
911
  process.exit(1);
726
912
  }
727
- console.log(chalk2.green("\nBuild complete!"));
728
- console.log(chalk2.gray("\nTo start the server:"));
729
- console.log(chalk2.cyan(" npm run start:node\n"));
913
+ logger.success("\nBuild complete!");
914
+ logger.gray("\nTo start the server:");
915
+ logger.info(" npm run start:node\n");
730
916
  }
731
917
  __name(buildCommand, "buildCommand");
732
918
 
733
919
  // src/commands/start.ts
734
920
  import { spawn as spawn3 } from "child_process";
735
- import chalk3 from "chalk";
736
921
  import ora3 from "ora";
737
922
  import path5 from "path";
738
923
  import fs5 from "fs-extra";
739
924
  async function startCommand() {
740
925
  const cwd = process.cwd();
741
926
  if (!await fs5.pathExists(path5.join(cwd, "main.ts"))) {
742
- console.error(chalk3.red("ERROR: Not a LeanMCP project (main.ts not found)."));
743
- console.error(chalk3.gray("Run this command from your project root."));
927
+ logger.error("ERROR: Not a LeanMCP project (main.ts not found).");
928
+ logger.gray("Run this command from your project root.");
744
929
  process.exit(1);
745
930
  }
746
- console.log(chalk3.cyan("\n\u{1F680} LeanMCP Production Build\n"));
931
+ logger.info("\n\u{1F680} LeanMCP Production Build\n");
747
932
  const scanSpinner = ora3("Scanning for @UIApp components...").start();
748
933
  const uiApps = await scanUIApp(cwd);
749
934
  if (uiApps.length === 0) {
@@ -775,7 +960,7 @@ async function startCommand() {
775
960
  if (errors.length > 0) {
776
961
  buildSpinner.fail("Build failed");
777
962
  for (const error of errors) {
778
- console.error(chalk3.red(` \u2717 ${error}`));
963
+ logger.error(` \u2717 ${error}`);
779
964
  }
780
965
  process.exit(1);
781
966
  }
@@ -804,10 +989,10 @@ async function startCommand() {
804
989
  tscSpinner.succeed("TypeScript compiled");
805
990
  } catch (error) {
806
991
  tscSpinner.fail("TypeScript compilation failed");
807
- console.error(chalk3.red(error instanceof Error ? error.message : String(error)));
992
+ logger.error(error instanceof Error ? error.message : String(error));
808
993
  process.exit(1);
809
994
  }
810
- console.log(chalk3.cyan("\nStarting production server...\n"));
995
+ logger.info("\nStarting production server...\n");
811
996
  const server = spawn3("node", [
812
997
  "dist/main.js"
813
998
  ], {
@@ -820,7 +1005,7 @@ async function startCommand() {
820
1005
  const cleanup = /* @__PURE__ */ __name(() => {
821
1006
  if (isCleaningUp) return;
822
1007
  isCleaningUp = true;
823
- console.log(chalk3.gray("\nShutting down..."));
1008
+ logger.gray("\nShutting down...");
824
1009
  if (!isWindows && !server.killed) {
825
1010
  server.kill("SIGTERM");
826
1011
  }
@@ -828,7 +1013,7 @@ async function startCommand() {
828
1013
  process.once("SIGINT", cleanup);
829
1014
  process.once("SIGTERM", cleanup);
830
1015
  server.on("error", (err) => {
831
- console.error(chalk3.red(`Server error: ${err.message}`));
1016
+ console.error(chalk.red(`Server error: ${err.message}`));
832
1017
  });
833
1018
  server.on("exit", (code, signal) => {
834
1019
  setImmediate(() => {
@@ -839,24 +1024,23 @@ async function startCommand() {
839
1024
  __name(startCommand, "startCommand");
840
1025
 
841
1026
  // src/commands/login.ts
842
- import chalk4 from "chalk";
843
1027
  import ora4 from "ora";
844
1028
  import path6 from "path";
845
1029
  import fs6 from "fs-extra";
846
- import os from "os";
1030
+ import os2 from "os";
847
1031
  import { input, confirm } from "@inquirer/prompts";
848
- var DEBUG_MODE = false;
849
- function setDebugMode(enabled) {
850
- DEBUG_MODE = enabled;
1032
+ var DEBUG_MODE2 = false;
1033
+ function setDebugMode2(enabled) {
1034
+ DEBUG_MODE2 = enabled;
851
1035
  }
852
- __name(setDebugMode, "setDebugMode");
853
- function debug(message, ...args) {
854
- if (DEBUG_MODE) {
855
- console.log(chalk4.gray(`[DEBUG] ${message}`), ...args);
1036
+ __name(setDebugMode2, "setDebugMode");
1037
+ function debug2(message, ...args) {
1038
+ if (DEBUG_MODE2 || isDebugMode()) {
1039
+ console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
856
1040
  }
857
1041
  }
858
- __name(debug, "debug");
859
- var CONFIG_DIR = path6.join(os.homedir(), ".leanmcp");
1042
+ __name(debug2, "debug");
1043
+ var CONFIG_DIR = path6.join(os2.homedir(), ".leanmcp");
860
1044
  var CONFIG_FILE = path6.join(CONFIG_DIR, "config.json");
861
1045
  async function loadConfig() {
862
1046
  try {
@@ -908,24 +1092,24 @@ Allowed URLs:
908
1092
  }
909
1093
  __name(getApiUrl, "getApiUrl");
910
1094
  async function loginCommand() {
911
- console.log(chalk4.cyan("\nLeanMCP Login\n"));
1095
+ logger.info("\nLeanMCP Login\n");
912
1096
  const existingConfig = await loadConfig();
913
1097
  if (existingConfig.apiKey) {
914
- console.log(chalk4.yellow("You are already logged in."));
1098
+ logger.warn("You are already logged in.");
915
1099
  const shouldRelogin = await confirm({
916
1100
  message: "Do you want to replace the existing API key?",
917
1101
  default: false
918
1102
  });
919
1103
  if (!shouldRelogin) {
920
- console.log(chalk4.gray("\nLogin cancelled. Existing API key preserved."));
1104
+ logger.gray("\nLogin cancelled. Existing API key preserved.");
921
1105
  return;
922
1106
  }
923
1107
  }
924
- console.log(chalk4.white("To authenticate, you need an API key from LeanMCP.\n"));
925
- console.log(chalk4.cyan("Steps:"));
926
- console.log(chalk4.gray(" 1. Go to: ") + chalk4.blue.underline("https://ship.leanmcp.com/api-keys"));
927
- console.log(chalk4.gray(' 2. Create a new API key with "BUILD_AND_DEPLOY" scope'));
928
- console.log(chalk4.gray(" 3. Copy the API key and paste it below\n"));
1108
+ logger.log("To authenticate, you need an API key from LeanMCP.\n", chalk.white);
1109
+ logger.info("Steps:");
1110
+ logger.log(" 1. Go to: " + chalk.blue.underline("https://ship.leanmcp.com/api-keys"), chalk.gray);
1111
+ logger.gray(' 2. Create a new API key with "BUILD_AND_DEPLOY" scope');
1112
+ logger.gray(" 3. Copy the API key and paste it below\n");
929
1113
  const apiKey = await input({
930
1114
  message: "Enter your API key:",
931
1115
  validate: /* @__PURE__ */ __name((value) => {
@@ -942,9 +1126,9 @@ async function loginCommand() {
942
1126
  try {
943
1127
  const apiUrl = await getApiUrl();
944
1128
  const validateUrl = `${apiUrl}/api-keys/validate`;
945
- debug("API URL:", apiUrl);
946
- debug("Validate URL:", validateUrl);
947
- debug("Making validation request...");
1129
+ debug2("API URL:", apiUrl);
1130
+ debug2("Validate URL:", validateUrl);
1131
+ debug2("Making validation request...");
948
1132
  const response = await fetch(validateUrl, {
949
1133
  method: "GET",
950
1134
  headers: {
@@ -952,16 +1136,16 @@ async function loginCommand() {
952
1136
  "Content-Type": "application/json"
953
1137
  }
954
1138
  });
955
- debug("Response status:", response.status);
956
- debug("Response ok:", response.ok);
1139
+ debug2("Response status:", response.status);
1140
+ debug2("Response ok:", response.ok);
957
1141
  if (!response.ok) {
958
1142
  const errorText = await response.text();
959
- debug("Error response:", errorText);
1143
+ debug2("Error response:", errorText);
960
1144
  spinner.fail("Invalid API key");
961
- console.error(chalk4.red("\nThe API key is invalid or has expired."));
962
- console.log(chalk4.gray("Please check your API key and try again."));
963
- if (DEBUG_MODE) {
964
- console.log(chalk4.gray(`Debug: Status ${response.status}, Response: ${errorText}`));
1145
+ logger.error("\nThe API key is invalid or has expired.");
1146
+ logger.gray("Please check your API key and try again.");
1147
+ if (DEBUG_MODE2 || isDebugMode()) {
1148
+ logger.gray(`Debug: Status ${response.status}, Response: ${errorText}`);
965
1149
  }
966
1150
  process.exit(1);
967
1151
  }
@@ -971,35 +1155,35 @@ async function loginCommand() {
971
1155
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
972
1156
  });
973
1157
  spinner.succeed("API key validated and saved");
974
- console.log(chalk4.green("\nLogin successful!"));
975
- console.log(chalk4.gray(` Config saved to: ${CONFIG_FILE}
976
- `));
977
- console.log(chalk4.cyan("You can now use:"));
978
- console.log(chalk4.gray(" leanmcp deploy <folder> - Deploy your MCP server"));
979
- console.log(chalk4.gray(" leanmcp logout - Remove your API key"));
1158
+ logger.success("\nLogin successful!");
1159
+ logger.gray(` Config saved to: ${CONFIG_FILE}
1160
+ `);
1161
+ logger.info("You can now use:");
1162
+ logger.gray(" leanmcp deploy <folder> - Deploy your MCP server");
1163
+ logger.gray(" leanmcp logout - Remove your API key");
980
1164
  } catch (error) {
981
1165
  spinner.fail("Failed to validate API key");
982
- debug("Error:", error);
1166
+ debug2("Error:", error);
983
1167
  if (error instanceof Error && error.message.includes("fetch")) {
984
- console.error(chalk4.red("\nCould not connect to LeanMCP servers."));
985
- console.log(chalk4.gray("Please check your internet connection and try again."));
1168
+ logger.error("\nCould not connect to LeanMCP servers.");
1169
+ logger.gray("Please check your internet connection and try again.");
986
1170
  } else {
987
- console.error(chalk4.red(`
988
- Error: ${error instanceof Error ? error.message : String(error)}`));
1171
+ logger.error(`
1172
+ Error: ${error instanceof Error ? error.message : String(error)}`);
989
1173
  }
990
- if (DEBUG_MODE) {
991
- console.log(chalk4.gray(`
992
- Debug: Full error: ${error}`));
1174
+ if (DEBUG_MODE2 || isDebugMode()) {
1175
+ logger.gray(`
1176
+ Debug: Full error: ${error}`);
993
1177
  }
994
1178
  process.exit(1);
995
1179
  }
996
1180
  }
997
1181
  __name(loginCommand, "loginCommand");
998
1182
  async function logoutCommand() {
999
- console.log(chalk4.cyan("\nLeanMCP Logout\n"));
1183
+ logger.info("\nLeanMCP Logout\n");
1000
1184
  const config = await loadConfig();
1001
1185
  if (!config.apiKey) {
1002
- console.log(chalk4.yellow("You are not currently logged in."));
1186
+ logger.warn("You are not currently logged in.");
1003
1187
  return;
1004
1188
  }
1005
1189
  const shouldLogout = await confirm({
@@ -1007,7 +1191,7 @@ async function logoutCommand() {
1007
1191
  default: false
1008
1192
  });
1009
1193
  if (!shouldLogout) {
1010
- console.log(chalk4.gray("\nLogout cancelled."));
1194
+ logger.gray("\nLogout cancelled.");
1011
1195
  return;
1012
1196
  }
1013
1197
  await saveConfig({
@@ -1015,34 +1199,71 @@ async function logoutCommand() {
1015
1199
  apiKey: void 0,
1016
1200
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1017
1201
  });
1018
- console.log(chalk4.green("\nLogged out successfully!"));
1019
- console.log(chalk4.gray(` API key removed from: ${CONFIG_FILE}`));
1202
+ logger.success("\nLogged out successfully!");
1203
+ logger.gray(` API key removed from: ${CONFIG_FILE}`);
1020
1204
  }
1021
1205
  __name(logoutCommand, "logoutCommand");
1022
1206
  async function whoamiCommand() {
1023
1207
  const config = await loadConfig();
1024
1208
  if (!config.apiKey) {
1025
- console.log(chalk4.yellow("\nYou are not logged in."));
1026
- console.log(chalk4.gray("Run `leanmcp login` to authenticate.\n"));
1209
+ logger.warn("\nYou are not logged in.");
1210
+ logger.gray("Run `leanmcp login` to authenticate.\n");
1027
1211
  return;
1028
1212
  }
1029
- console.log(chalk4.cyan("\nLeanMCP Authentication Status\n"));
1030
- console.log(chalk4.green("Logged in"));
1031
- console.log(chalk4.gray(` API Key: ${config.apiKey.substring(0, 15)}...`));
1032
- console.log(chalk4.gray(` API URL: ${config.apiUrl || "https://ship.leanmcp.com"}`));
1033
- if (config.lastUpdated) {
1034
- console.log(chalk4.gray(` Last updated: ${new Date(config.lastUpdated).toLocaleString()}`));
1213
+ const spinner = ora4("Fetching account info...").start();
1214
+ try {
1215
+ const apiUrl = await getApiUrl();
1216
+ const whoamiUrl = `${apiUrl}/api-keys/whoami`;
1217
+ debug2("API URL:", apiUrl);
1218
+ debug2("Whoami URL:", whoamiUrl);
1219
+ const response = await fetch(whoamiUrl, {
1220
+ method: "GET",
1221
+ headers: {
1222
+ "Authorization": `Bearer ${config.apiKey}`,
1223
+ "Content-Type": "application/json"
1224
+ }
1225
+ });
1226
+ debug2("Response status:", response.status);
1227
+ if (!response.ok) {
1228
+ spinner.fail("Failed to fetch account info");
1229
+ if (response.status === 401) {
1230
+ logger.error("\nYour API key is invalid or has expired.");
1231
+ logger.gray("Run `leanmcp login` to re-authenticate.\n");
1232
+ } else {
1233
+ logger.error(`
1234
+ Error: ${response.status} ${response.statusText}`);
1235
+ }
1236
+ return;
1237
+ }
1238
+ const user = await response.json();
1239
+ spinner.succeed("Account info retrieved");
1240
+ logger.info("\nLeanMCP Account\n");
1241
+ logger.log(` ${chalk.bold("Name:")} ${user.displayName || user.username}`);
1242
+ logger.log(` ${chalk.bold("Email:")} ${user.email}`);
1243
+ logger.log(` ${chalk.bold("Username:")} ${user.username}`);
1244
+ logger.log(` ${chalk.bold("Tier:")} ${user.userTier || "free"}`);
1245
+ logger.gray(`
1246
+ API Key: ${config.apiKey.substring(0, 20)}...`);
1247
+ logger.log("");
1248
+ } catch (error) {
1249
+ spinner.fail("Failed to fetch account info");
1250
+ debug2("Error:", error);
1251
+ if (error instanceof Error && error.message.includes("fetch")) {
1252
+ logger.error("\nCould not connect to LeanMCP servers.");
1253
+ logger.gray("Please check your internet connection and try again.");
1254
+ } else {
1255
+ logger.error(`
1256
+ Error: ${error instanceof Error ? error.message : String(error)}`);
1257
+ }
1035
1258
  }
1036
- console.log();
1037
1259
  }
1038
1260
  __name(whoamiCommand, "whoamiCommand");
1039
1261
 
1040
1262
  // src/commands/deploy.ts
1041
- import chalk5 from "chalk";
1042
1263
  import ora5 from "ora";
1043
1264
  import path7 from "path";
1044
1265
  import fs7 from "fs-extra";
1045
- import os2 from "os";
1266
+ import os3 from "os";
1046
1267
  import archiver from "archiver";
1047
1268
  import { input as input2, confirm as confirm2, select } from "@inquirer/prompts";
1048
1269
 
@@ -1224,31 +1445,31 @@ function generateProjectName() {
1224
1445
  __name(generateProjectName, "generateProjectName");
1225
1446
 
1226
1447
  // src/commands/deploy.ts
1227
- var DEBUG_MODE2 = false;
1448
+ var DEBUG_MODE3 = false;
1228
1449
  function setDeployDebugMode(enabled) {
1229
- DEBUG_MODE2 = enabled;
1450
+ DEBUG_MODE3 = enabled;
1230
1451
  }
1231
1452
  __name(setDeployDebugMode, "setDeployDebugMode");
1232
- function debug2(message, ...args) {
1233
- if (DEBUG_MODE2) {
1234
- console.log(chalk5.gray(`[DEBUG] ${message}`), ...args);
1453
+ function debug3(message, ...args) {
1454
+ if (DEBUG_MODE3) {
1455
+ console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
1235
1456
  }
1236
1457
  }
1237
- __name(debug2, "debug");
1458
+ __name(debug3, "debug");
1238
1459
  async function debugFetch(url, options = {}) {
1239
- debug2(`HTTP ${options.method || "GET"} ${url}`);
1460
+ debug3(`HTTP ${options.method || "GET"} ${url}`);
1240
1461
  if (options.body && typeof options.body === "string") {
1241
1462
  try {
1242
1463
  const body = JSON.parse(options.body);
1243
- debug2("Request body:", JSON.stringify(body, null, 2));
1464
+ debug3("Request body:", JSON.stringify(body, null, 2));
1244
1465
  } catch {
1245
- debug2("Request body:", options.body);
1466
+ debug3("Request body:", options.body);
1246
1467
  }
1247
1468
  }
1248
1469
  const startTime = Date.now();
1249
1470
  const response = await fetch(url, options);
1250
1471
  const duration = Date.now() - startTime;
1251
- debug2(`Response: ${response.status} ${response.statusText} (${duration}ms)`);
1472
+ debug3(`Response: ${response.status} ${response.statusText} (${duration}ms)`);
1252
1473
  return response;
1253
1474
  }
1254
1475
  __name(debugFetch, "debugFetch");
@@ -1266,6 +1487,32 @@ var API_ENDPOINTS = {
1266
1487
  checkSubdomain: "/api/lambda-mapping/check",
1267
1488
  createMapping: "/api/lambda-mapping"
1268
1489
  };
1490
+ var LEANMCP_CONFIG_DIR = ".leanmcp";
1491
+ var LEANMCP_CONFIG_FILE = "config.json";
1492
+ async function readLeanMCPConfig(projectPath) {
1493
+ const configPath = path7.join(projectPath, LEANMCP_CONFIG_DIR, LEANMCP_CONFIG_FILE);
1494
+ try {
1495
+ if (await fs7.pathExists(configPath)) {
1496
+ const config = await fs7.readJSON(configPath);
1497
+ debug3("Found existing .leanmcp config:", config);
1498
+ return config;
1499
+ }
1500
+ } catch (e) {
1501
+ debug3("Could not read .leanmcp config:", e);
1502
+ }
1503
+ return null;
1504
+ }
1505
+ __name(readLeanMCPConfig, "readLeanMCPConfig");
1506
+ async function writeLeanMCPConfig(projectPath, config) {
1507
+ const configDir = path7.join(projectPath, LEANMCP_CONFIG_DIR);
1508
+ const configPath = path7.join(configDir, LEANMCP_CONFIG_FILE);
1509
+ await fs7.ensureDir(configDir);
1510
+ await fs7.writeJSON(configPath, config, {
1511
+ spaces: 2
1512
+ });
1513
+ debug3("Saved .leanmcp config:", config);
1514
+ }
1515
+ __name(writeLeanMCPConfig, "writeLeanMCPConfig");
1269
1516
  async function createZipArchive(folderPath, outputPath) {
1270
1517
  return new Promise((resolve, reject) => {
1271
1518
  const output = fs7.createWriteStream(outputPath);
@@ -1282,6 +1529,7 @@ async function createZipArchive(folderPath, outputPath) {
1282
1529
  ignore: [
1283
1530
  "node_modules/**",
1284
1531
  ".git/**",
1532
+ ".leanmcp/**",
1285
1533
  "dist/**",
1286
1534
  ".next/**",
1287
1535
  ".nuxt/**",
@@ -1355,64 +1603,45 @@ async function waitForDeployment(apiUrl, apiKey, deploymentId, spinner) {
1355
1603
  __name(waitForDeployment, "waitForDeployment");
1356
1604
  async function deployCommand(folderPath, options = {}) {
1357
1605
  const deployStartTime = Date.now();
1358
- console.log(chalk5.cyan("\nLeanMCP Deploy\n"));
1359
- debug2("Starting deployment...");
1606
+ logger.info("\nLeanMCP Deploy\n");
1607
+ debug3("Starting deployment...");
1360
1608
  const apiKey = await getApiKey();
1361
1609
  if (!apiKey) {
1362
- console.error(chalk5.red("Not logged in."));
1363
- console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
1610
+ logger.error("Not logged in.");
1611
+ logger.gray("Run `leanmcp login` first to authenticate.\n");
1364
1612
  process.exit(1);
1365
1613
  }
1366
1614
  const apiUrl = await getApiUrl();
1367
- debug2("API URL:", apiUrl);
1615
+ debug3("API URL:", apiUrl);
1368
1616
  const absolutePath = path7.resolve(process.cwd(), folderPath);
1369
1617
  if (!await fs7.pathExists(absolutePath)) {
1370
- console.error(chalk5.red(`Folder not found: ${absolutePath}`));
1618
+ logger.error(`Folder not found: ${absolutePath}`);
1371
1619
  process.exit(1);
1372
1620
  }
1373
1621
  const hasMainTs = await fs7.pathExists(path7.join(absolutePath, "main.ts"));
1374
1622
  const hasPackageJson = await fs7.pathExists(path7.join(absolutePath, "package.json"));
1375
1623
  if (!hasMainTs && !hasPackageJson) {
1376
- console.error(chalk5.red("Not a valid project folder."));
1377
- console.log(chalk5.gray("Expected main.ts or package.json in the folder.\n"));
1624
+ logger.error("Not a valid project folder.");
1625
+ logger.gray("Expected main.ts or package.json in the folder.\n");
1378
1626
  process.exit(1);
1379
1627
  }
1380
- debug2("Fetching existing projects...");
1381
- let existingProjects = [];
1382
- try {
1383
- const projectsResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}`, {
1384
- headers: {
1385
- "Authorization": `Bearer ${apiKey}`
1386
- }
1387
- });
1388
- if (projectsResponse.ok) {
1389
- existingProjects = await projectsResponse.json();
1390
- debug2(`Found ${existingProjects.length} existing projects`);
1391
- }
1392
- } catch (e) {
1393
- debug2("Could not fetch existing projects");
1394
- }
1628
+ const existingConfig = await readLeanMCPConfig(absolutePath);
1395
1629
  let projectName;
1396
1630
  let existingProject = null;
1397
1631
  let isUpdate = false;
1398
- let folderName = path7.basename(absolutePath);
1399
- if (hasPackageJson) {
1400
- try {
1401
- const pkg2 = await fs7.readJSON(path7.join(absolutePath, "package.json"));
1402
- folderName = pkg2.name || folderName;
1403
- } catch (e) {
1404
- }
1405
- }
1406
- const matchingProject = existingProjects.find((p) => p.name === folderName);
1407
- if (matchingProject) {
1408
- console.log(chalk5.yellow(`Project '${folderName}' already exists.
1409
- `));
1632
+ let subdomain = options.subdomain;
1633
+ if (existingConfig) {
1634
+ logger.info(`Found existing deployment config for '${existingConfig.projectName}'`);
1635
+ logger.gray(` Project ID: ${existingConfig.projectId}`);
1636
+ logger.gray(` URL: ${existingConfig.url}`);
1637
+ logger.gray(` Last deployed: ${existingConfig.lastDeployedAt}
1638
+ `);
1410
1639
  const choice = await select({
1411
1640
  message: "What would you like to do?",
1412
1641
  choices: [
1413
1642
  {
1414
1643
  value: "update",
1415
- name: `Update existing project '${folderName}'`
1644
+ name: `Update existing deployment '${existingConfig.projectName}'`
1416
1645
  },
1417
1646
  {
1418
1647
  value: "new",
@@ -1425,28 +1654,93 @@ async function deployCommand(folderPath, options = {}) {
1425
1654
  ]
1426
1655
  });
1427
1656
  if (choice === "cancel") {
1428
- console.log(chalk5.gray("\nDeployment cancelled.\n"));
1657
+ logger.gray("\nDeployment cancelled.\n");
1429
1658
  return;
1430
1659
  }
1431
1660
  if (choice === "update") {
1432
- existingProject = matchingProject;
1433
- projectName = matchingProject.name;
1661
+ existingProject = {
1662
+ id: existingConfig.projectId,
1663
+ name: existingConfig.projectName
1664
+ };
1665
+ projectName = existingConfig.projectName;
1666
+ subdomain = existingConfig.subdomain;
1434
1667
  isUpdate = true;
1435
- console.log(chalk5.yellow("\nWARNING: This will replace the existing deployment."));
1436
- console.log(chalk5.gray("The previous version will be overwritten.\n"));
1668
+ logger.warn("\nUpdating existing deployment...");
1669
+ logger.gray("The previous version will be replaced.\n");
1437
1670
  } else {
1438
1671
  projectName = generateProjectName();
1439
- console.log(chalk5.cyan(`
1440
- Generated project name: ${chalk5.bold(projectName)}
1441
- `));
1672
+ logger.info(`
1673
+ Generated project name: ${chalk.bold(projectName)}
1674
+ `);
1442
1675
  }
1443
1676
  } else {
1444
- projectName = generateProjectName();
1445
- console.log(chalk5.cyan(`Generated project name: ${chalk5.bold(projectName)}`));
1677
+ debug3("Fetching existing projects...");
1678
+ let existingProjects = [];
1679
+ try {
1680
+ const projectsResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}`, {
1681
+ headers: {
1682
+ "Authorization": `Bearer ${apiKey}`
1683
+ }
1684
+ });
1685
+ if (projectsResponse.ok) {
1686
+ existingProjects = await projectsResponse.json();
1687
+ debug3(`Found ${existingProjects.length} existing projects`);
1688
+ }
1689
+ } catch (e) {
1690
+ debug3("Could not fetch existing projects");
1691
+ }
1692
+ let folderName = path7.basename(absolutePath);
1693
+ if (hasPackageJson) {
1694
+ try {
1695
+ const pkg2 = await fs7.readJSON(path7.join(absolutePath, "package.json"));
1696
+ folderName = pkg2.name || folderName;
1697
+ } catch (e) {
1698
+ }
1699
+ }
1700
+ const matchingProject = existingProjects.find((p) => p.name === folderName);
1701
+ if (matchingProject) {
1702
+ logger.warn(`Project '${folderName}' already exists.
1703
+ `);
1704
+ const choice = await select({
1705
+ message: "What would you like to do?",
1706
+ choices: [
1707
+ {
1708
+ value: "update",
1709
+ name: `Update existing project '${folderName}'`
1710
+ },
1711
+ {
1712
+ value: "new",
1713
+ name: "Create a new project with a random name"
1714
+ },
1715
+ {
1716
+ value: "cancel",
1717
+ name: "Cancel deployment"
1718
+ }
1719
+ ]
1720
+ });
1721
+ if (choice === "cancel") {
1722
+ logger.gray("\nDeployment cancelled.\n");
1723
+ return;
1724
+ }
1725
+ if (choice === "update") {
1726
+ existingProject = matchingProject;
1727
+ projectName = matchingProject.name;
1728
+ isUpdate = true;
1729
+ logger.warn("\nWARNING: This will replace the existing deployment.");
1730
+ logger.gray("The previous version will be overwritten.\n");
1731
+ } else {
1732
+ projectName = generateProjectName();
1733
+ logger.info(`
1734
+ Generated project name: ${chalk.bold(projectName)}
1735
+ `);
1736
+ }
1737
+ } else {
1738
+ projectName = generateProjectName();
1739
+ logger.info(`Generated project name: ${chalk.bold(projectName)}`);
1740
+ }
1446
1741
  }
1447
- console.log(chalk5.gray(`Path: ${absolutePath}
1448
- `));
1449
- let subdomain = options.subdomain;
1742
+ logger.gray(`Path: ${absolutePath}
1743
+ `);
1450
1744
  if (!subdomain) {
1451
1745
  const suggestedSubdomain = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1452
1746
  subdomain = await input2({
@@ -1468,7 +1762,7 @@ Generated project name: ${chalk5.bold(projectName)}
1468
1762
  }
1469
1763
  const checkSpinner = ora5("Checking subdomain availability...").start();
1470
1764
  try {
1471
- debug2("Checking subdomain:", subdomain);
1765
+ debug3("Checking subdomain:", subdomain);
1472
1766
  const checkResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.checkSubdomain}/${subdomain}`, {
1473
1767
  headers: {
1474
1768
  "Authorization": `Bearer ${apiKey}`
@@ -1476,40 +1770,53 @@ Generated project name: ${chalk5.bold(projectName)}
1476
1770
  });
1477
1771
  if (checkResponse.ok) {
1478
1772
  const result = await checkResponse.json();
1773
+ debug3("Subdomain check result:", result);
1479
1774
  if (!result.available) {
1480
- checkSpinner.fail(`Subdomain '${subdomain}' is already taken`);
1481
- console.log(chalk5.gray("\nPlease choose a different subdomain.\n"));
1482
- process.exit(1);
1775
+ const ourProjectId = isUpdate && existingProject ? existingProject.id : null;
1776
+ if (ourProjectId && result.ownedByProject === ourProjectId) {
1777
+ checkSpinner.succeed(`Subdomain '${subdomain}' is yours - will update existing mapping`);
1778
+ } else if (result.ownedByCurrentUser) {
1779
+ checkSpinner.fail(`Subdomain '${subdomain}' is used by your other project`);
1780
+ logger.gray(`
1781
+ This subdomain is associated with project: ${result.ownedByProject?.substring(0, 8)}...`);
1782
+ logger.gray("Please choose a different subdomain or update that project instead.\n");
1783
+ process.exit(1);
1784
+ } else {
1785
+ checkSpinner.fail(`Subdomain '${subdomain}' is not available`);
1786
+ logger.gray("\nThis subdomain is taken by another user. Please choose a different subdomain.\n");
1787
+ process.exit(1);
1788
+ }
1789
+ } else {
1790
+ checkSpinner.succeed(`Subdomain '${subdomain}' is available`);
1483
1791
  }
1484
1792
  }
1485
- checkSpinner.succeed(`Subdomain '${subdomain}' is available`);
1486
1793
  } catch (error) {
1487
1794
  checkSpinner.warn("Could not verify subdomain availability");
1488
1795
  }
1489
1796
  if (!options.skipConfirm) {
1490
- console.log(chalk5.cyan("\nDeployment Details:"));
1491
- console.log(chalk5.gray(` Project: ${projectName}`));
1492
- console.log(chalk5.gray(` Subdomain: ${subdomain}`));
1493
- console.log(chalk5.gray(` URL: https://${subdomain}.leanmcp.dev
1494
- `));
1797
+ logger.info("\nDeployment Details:");
1798
+ logger.gray(` Project: ${projectName}`);
1799
+ logger.gray(` Subdomain: ${subdomain}`);
1800
+ logger.gray(` URL: https://${subdomain}.leanmcp.app
1801
+ `);
1495
1802
  const shouldDeploy = await confirm2({
1496
1803
  message: "Proceed with deployment?",
1497
1804
  default: true
1498
1805
  });
1499
1806
  if (!shouldDeploy) {
1500
- console.log(chalk5.gray("\nDeployment cancelled.\n"));
1807
+ logger.gray("\nDeployment cancelled.\n");
1501
1808
  return;
1502
1809
  }
1503
1810
  }
1504
- console.log();
1811
+ logger.log("");
1505
1812
  let projectId;
1506
1813
  if (isUpdate && existingProject) {
1507
1814
  projectId = existingProject.id;
1508
- console.log(chalk5.gray(`Using existing project: ${projectId.substring(0, 8)}...`));
1815
+ logger.gray(`Using existing project: ${projectId.substring(0, 8)}...`);
1509
1816
  } else {
1510
1817
  const projectSpinner = ora5("Creating project...").start();
1511
1818
  try {
1512
- debug2("Step 1: Creating project:", projectName);
1819
+ debug3("Step 1: Creating project:", projectName);
1513
1820
  const createResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}`, {
1514
1821
  method: "POST",
1515
1822
  headers: {
@@ -1529,17 +1836,17 @@ Generated project name: ${chalk5.bold(projectName)}
1529
1836
  projectSpinner.succeed(`Project created: ${projectId.substring(0, 8)}...`);
1530
1837
  } catch (error) {
1531
1838
  projectSpinner.fail("Failed to create project");
1532
- console.error(chalk5.red(`
1533
- ${error instanceof Error ? error.message : String(error)}`));
1839
+ logger.error(`
1840
+ ${error instanceof Error ? error.message : String(error)}`);
1534
1841
  process.exit(1);
1535
1842
  }
1536
1843
  }
1537
1844
  const uploadSpinner = ora5("Packaging and uploading...").start();
1538
1845
  try {
1539
- const tempZip = path7.join(os2.tmpdir(), `leanmcp-${Date.now()}.zip`);
1846
+ const tempZip = path7.join(os3.tmpdir(), `leanmcp-${Date.now()}.zip`);
1540
1847
  const zipSize = await createZipArchive(absolutePath, tempZip);
1541
1848
  uploadSpinner.text = `Packaging... (${Math.round(zipSize / 1024)}KB)`;
1542
- debug2("Step 2a: Getting upload URL for project:", projectId);
1849
+ debug3("Step 2a: Getting upload URL for project:", projectId);
1543
1850
  const uploadUrlResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}/${projectId}/upload-url`, {
1544
1851
  method: "POST",
1545
1852
  headers: {
@@ -1558,11 +1865,11 @@ ${error instanceof Error ? error.message : String(error)}`));
1558
1865
  const uploadResult = await uploadUrlResponse.json();
1559
1866
  const uploadUrl = uploadResult.url || uploadResult.uploadUrl;
1560
1867
  const s3Location = uploadResult.s3Location;
1561
- debug2("Upload URL response:", JSON.stringify(uploadResult));
1868
+ debug3("Upload URL response:", JSON.stringify(uploadResult));
1562
1869
  if (!uploadUrl) {
1563
1870
  throw new Error("Backend did not return upload URL");
1564
1871
  }
1565
- debug2("Step 2b: Uploading to S3...");
1872
+ debug3("Step 2b: Uploading to S3...");
1566
1873
  const zipBuffer = await fs7.readFile(tempZip);
1567
1874
  const s3Response = await fetch(uploadUrl, {
1568
1875
  method: "PUT",
@@ -1571,11 +1878,11 @@ ${error instanceof Error ? error.message : String(error)}`));
1571
1878
  "Content-Type": "application/zip"
1572
1879
  }
1573
1880
  });
1574
- debug2("S3 upload response:", s3Response.status);
1881
+ debug3("S3 upload response:", s3Response.status);
1575
1882
  if (!s3Response.ok) {
1576
1883
  throw new Error("Failed to upload to S3");
1577
1884
  }
1578
- debug2("Step 2c: Updating project with S3 location:", s3Location);
1885
+ debug3("Step 2c: Updating project with S3 location:", s3Location);
1579
1886
  await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}/${projectId}`, {
1580
1887
  method: "PATCH",
1581
1888
  headers: {
@@ -1590,8 +1897,8 @@ ${error instanceof Error ? error.message : String(error)}`));
1590
1897
  uploadSpinner.succeed("Project uploaded");
1591
1898
  } catch (error) {
1592
1899
  uploadSpinner.fail("Failed to upload");
1593
- console.error(chalk5.red(`
1594
- ${error instanceof Error ? error.message : String(error)}`));
1900
+ logger.error(`
1901
+ ${error instanceof Error ? error.message : String(error)}`);
1595
1902
  process.exit(1);
1596
1903
  }
1597
1904
  const buildSpinner = ora5("Building...").start();
@@ -1599,7 +1906,7 @@ ${error instanceof Error ? error.message : String(error)}`));
1599
1906
  let buildId;
1600
1907
  let imageUri;
1601
1908
  try {
1602
- debug2("Step 3: Triggering build for project:", projectId);
1909
+ debug3("Step 3: Triggering build for project:", projectId);
1603
1910
  const buildResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.triggerBuild}/${projectId}`, {
1604
1911
  method: "POST",
1605
1912
  headers: {
@@ -1618,15 +1925,15 @@ ${error instanceof Error ? error.message : String(error)}`));
1618
1925
  buildSpinner.succeed(`Build complete (${buildDuration}s)`);
1619
1926
  } catch (error) {
1620
1927
  buildSpinner.fail("Build failed");
1621
- console.error(chalk5.red(`
1622
- ${error instanceof Error ? error.message : String(error)}`));
1928
+ logger.error(`
1929
+ ${error instanceof Error ? error.message : String(error)}`);
1623
1930
  process.exit(1);
1624
1931
  }
1625
1932
  const deploySpinner = ora5("Deploying to LeanMCP...").start();
1626
1933
  let deploymentId;
1627
1934
  let functionUrl;
1628
1935
  try {
1629
- debug2("Step 4: Creating deployment for build:", buildId);
1936
+ debug3("Step 4: Creating deployment for build:", buildId);
1630
1937
  const deployResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.createDeployment}`, {
1631
1938
  method: "POST",
1632
1939
  headers: {
@@ -1648,13 +1955,13 @@ ${error instanceof Error ? error.message : String(error)}`));
1648
1955
  deploySpinner.succeed("Deployed");
1649
1956
  } catch (error) {
1650
1957
  deploySpinner.fail("Deployment failed");
1651
- console.error(chalk5.red(`
1652
- ${error instanceof Error ? error.message : String(error)}`));
1958
+ logger.error(`
1959
+ ${error instanceof Error ? error.message : String(error)}`);
1653
1960
  process.exit(1);
1654
1961
  }
1655
1962
  const mappingSpinner = ora5("Configuring subdomain...").start();
1656
1963
  try {
1657
- debug2("Step 5: Creating subdomain mapping:", subdomain);
1964
+ debug3("Step 5: Creating subdomain mapping:", subdomain);
1658
1965
  const mappingResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.createMapping}`, {
1659
1966
  method: "POST",
1660
1967
  headers: {
@@ -1676,40 +1983,54 @@ ${error instanceof Error ? error.message : String(error)}`));
1676
1983
  } catch (error) {
1677
1984
  mappingSpinner.warn("Subdomain mapping may need manual setup");
1678
1985
  }
1679
- console.log(chalk5.green("\n" + "=".repeat(60)));
1680
- console.log(chalk5.green.bold(" DEPLOYMENT SUCCESSFUL!"));
1681
- console.log(chalk5.green("=".repeat(60) + "\n"));
1682
- console.log(chalk5.white(" Your MCP server is now live:\n"));
1683
- console.log(chalk5.cyan(` URL: `) + chalk5.white.bold(`https://${subdomain}.leanmcp.dev`));
1684
- console.log();
1685
- console.log(chalk5.gray(" Test endpoints:"));
1686
- console.log(chalk5.gray(` curl https://${subdomain}.leanmcp.dev/health`));
1687
- console.log(chalk5.gray(` curl https://${subdomain}.leanmcp.dev/mcp`));
1688
- console.log();
1986
+ const deploymentUrl = `https://${subdomain}.leanmcp.app`;
1987
+ try {
1988
+ await writeLeanMCPConfig(absolutePath, {
1989
+ projectId,
1990
+ projectName,
1991
+ subdomain,
1992
+ url: deploymentUrl,
1993
+ lastDeployedAt: (/* @__PURE__ */ new Date()).toISOString(),
1994
+ buildId,
1995
+ deploymentId
1996
+ });
1997
+ debug3("Saved .leanmcp config");
1998
+ } catch (e) {
1999
+ debug3("Could not save .leanmcp config:", e);
2000
+ }
2001
+ logger.success("\n" + "=".repeat(60));
2002
+ logger.log(" DEPLOYMENT SUCCESSFUL!", chalk.green.bold);
2003
+ logger.success("=".repeat(60) + "\n");
2004
+ logger.log(" Your MCP server is now live:\n", chalk.white);
2005
+ logger.log(` URL: ${chalk.white.bold(`https://${subdomain}.leanmcp.app`)}`, chalk.cyan);
2006
+ logger.log("");
2007
+ logger.gray(" Test endpoints:");
2008
+ logger.gray(` curl https://${subdomain}.leanmcp.app/health`);
2009
+ logger.gray(` curl https://${subdomain}.leanmcp.app/mcp`);
2010
+ logger.log("");
1689
2011
  const totalDuration = Math.round((Date.now() - deployStartTime) / 1e3);
1690
- console.log(chalk5.gray(` Total time: ${totalDuration}s`));
1691
- console.log();
2012
+ logger.gray(` Total time: ${totalDuration}s`);
2013
+ logger.log("");
1692
2014
  const dashboardBaseUrl = "https://ship.leanmcp.com";
1693
- console.log(chalk5.cyan(" Dashboard links:"));
1694
- console.log(chalk5.gray(` Project: ${dashboardBaseUrl}/projects/${projectId}`));
1695
- console.log(chalk5.gray(` Build: ${dashboardBaseUrl}/builds/${buildId}`));
1696
- console.log(chalk5.gray(` Deployment: ${dashboardBaseUrl}/deployments/${deploymentId}`));
1697
- console.log();
1698
- console.log(chalk5.cyan(" Need help? Join our Discord:"));
1699
- console.log(chalk5.blue(" https://discord.com/invite/DsRcA3GwPy"));
1700
- console.log();
2015
+ logger.info(" Dashboard links:");
2016
+ logger.gray(` Project: ${dashboardBaseUrl}/projects/${projectId}`);
2017
+ logger.gray(` Build: ${dashboardBaseUrl}/builds/${buildId}`);
2018
+ logger.gray(` Deployment: ${dashboardBaseUrl}/deployments/${deploymentId}`);
2019
+ logger.log("");
2020
+ logger.info(" Need help? Join our Discord:");
2021
+ logger.log(" https://discord.com/invite/DsRcA3GwPy", chalk.blue);
2022
+ logger.log("");
1701
2023
  }
1702
2024
  __name(deployCommand, "deployCommand");
1703
2025
 
1704
2026
  // src/commands/projects.ts
1705
- import chalk6 from "chalk";
1706
2027
  import ora6 from "ora";
1707
2028
  var API_ENDPOINT = "/api/projects";
1708
2029
  async function projectsListCommand() {
1709
2030
  const apiKey = await getApiKey();
1710
2031
  if (!apiKey) {
1711
- console.error(chalk6.red("\nNot logged in."));
1712
- console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
2032
+ logger.error("\nNot logged in.");
2033
+ logger.gray("Run `leanmcp login` first to authenticate.\n");
1713
2034
  process.exit(1);
1714
2035
  }
1715
2036
  const spinner = ora6("Fetching projects...").start();
@@ -1726,26 +2047,26 @@ async function projectsListCommand() {
1726
2047
  const projects = await response.json();
1727
2048
  spinner.stop();
1728
2049
  if (projects.length === 0) {
1729
- console.log(chalk6.yellow("\nNo projects found."));
1730
- console.log(chalk6.gray("Create one with: leanmcp deploy <folder>\n"));
2050
+ logger.warn("\nNo projects found.");
2051
+ logger.gray("Create one with: leanmcp deploy <folder>\n");
1731
2052
  return;
1732
2053
  }
1733
- console.log(chalk6.cyan(`
2054
+ logger.info(`
1734
2055
  Your Projects (${projects.length})
1735
- `));
1736
- console.log(chalk6.gray("\u2500".repeat(60)));
2056
+ `);
2057
+ logger.gray("\u2500".repeat(60));
1737
2058
  for (const project of projects) {
1738
- const statusColor = project.status === "ACTIVE" ? chalk6.green : chalk6.yellow;
1739
- console.log(chalk6.white.bold(` ${project.name}`));
1740
- console.log(chalk6.gray(` ID: ${project.id}`));
1741
- console.log(chalk6.gray(` Status: `) + statusColor(project.status));
1742
- console.log(chalk6.gray(` Created: ${new Date(project.createdAt).toLocaleDateString()}`));
1743
- console.log();
2059
+ const statusColor = project.status === "ACTIVE" ? chalk.green : chalk.yellow;
2060
+ logger.log(` ${project.name}`, chalk.white.bold);
2061
+ logger.gray(` ID: ${project.id}`);
2062
+ logger.log(` Status: ${statusColor(project.status)}`, chalk.gray);
2063
+ logger.gray(` Created: ${new Date(project.createdAt).toLocaleDateString()}`);
2064
+ logger.log("");
1744
2065
  }
1745
2066
  } catch (error) {
1746
2067
  spinner.fail("Failed to fetch projects");
1747
- console.error(chalk6.red(`
1748
- ${error instanceof Error ? error.message : String(error)}`));
2068
+ logger.error(`
2069
+ ${error instanceof Error ? error.message : String(error)}`);
1749
2070
  process.exit(1);
1750
2071
  }
1751
2072
  }
@@ -1753,8 +2074,8 @@ __name(projectsListCommand, "projectsListCommand");
1753
2074
  async function projectsGetCommand(projectId) {
1754
2075
  const apiKey = await getApiKey();
1755
2076
  if (!apiKey) {
1756
- console.error(chalk6.red("\nNot logged in."));
1757
- console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
2077
+ logger.error("\nNot logged in.");
2078
+ logger.gray("Run `leanmcp login` first to authenticate.\n");
1758
2079
  process.exit(1);
1759
2080
  }
1760
2081
  const spinner = ora6("Fetching project...").start();
@@ -1773,23 +2094,23 @@ async function projectsGetCommand(projectId) {
1773
2094
  }
1774
2095
  const project = await response.json();
1775
2096
  spinner.stop();
1776
- console.log(chalk6.cyan("\nProject Details\n"));
1777
- console.log(chalk6.gray("\u2500".repeat(60)));
1778
- console.log(chalk6.white.bold(` Name: ${project.name}`));
1779
- console.log(chalk6.gray(` ID: ${project.id}`));
1780
- console.log(chalk6.gray(` Status: ${project.status}`));
2097
+ logger.info("\nProject Details\n");
2098
+ logger.gray("\u2500".repeat(60));
2099
+ logger.log(` Name: ${project.name}`, chalk.white.bold);
2100
+ logger.gray(` ID: ${project.id}`);
2101
+ logger.gray(` Status: ${project.status}`);
1781
2102
  if (project.s3Location) {
1782
- console.log(chalk6.gray(` S3 Location: ${project.s3Location}`));
2103
+ logger.gray(` S3 Location: ${project.s3Location}`);
1783
2104
  }
1784
- console.log(chalk6.gray(` Created: ${new Date(project.createdAt).toLocaleString()}`));
2105
+ logger.gray(` Created: ${new Date(project.createdAt).toLocaleString()}`);
1785
2106
  if (project.updatedAt) {
1786
- console.log(chalk6.gray(` Updated: ${new Date(project.updatedAt).toLocaleString()}`));
2107
+ logger.gray(` Updated: ${new Date(project.updatedAt).toLocaleString()}`);
1787
2108
  }
1788
- console.log();
2109
+ logger.log("");
1789
2110
  } catch (error) {
1790
2111
  spinner.fail("Failed to fetch project");
1791
- console.error(chalk6.red(`
1792
- ${error instanceof Error ? error.message : String(error)}`));
2112
+ logger.error(`
2113
+ ${error instanceof Error ? error.message : String(error)}`);
1793
2114
  process.exit(1);
1794
2115
  }
1795
2116
  }
@@ -1797,8 +2118,8 @@ __name(projectsGetCommand, "projectsGetCommand");
1797
2118
  async function projectsDeleteCommand(projectId, options = {}) {
1798
2119
  const apiKey = await getApiKey();
1799
2120
  if (!apiKey) {
1800
- console.error(chalk6.red("\nNot logged in."));
1801
- console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
2121
+ logger.error("\nNot logged in.");
2122
+ logger.gray("Run `leanmcp login` first to authenticate.\n");
1802
2123
  process.exit(1);
1803
2124
  }
1804
2125
  if (!options.force) {
@@ -1808,7 +2129,7 @@ async function projectsDeleteCommand(projectId, options = {}) {
1808
2129
  default: false
1809
2130
  });
1810
2131
  if (!shouldDelete) {
1811
- console.log(chalk6.gray("\nDeletion cancelled.\n"));
2132
+ logger.gray("\nDeletion cancelled.\n");
1812
2133
  return;
1813
2134
  }
1814
2135
  }
@@ -1828,18 +2149,56 @@ async function projectsDeleteCommand(projectId, options = {}) {
1828
2149
  throw new Error(`Failed to delete project: ${response.statusText}`);
1829
2150
  }
1830
2151
  spinner.succeed("Project deleted successfully");
1831
- console.log();
2152
+ logger.log("");
1832
2153
  } catch (error) {
1833
2154
  spinner.fail("Failed to delete project");
1834
- console.error(chalk6.red(`
1835
- ${error instanceof Error ? error.message : String(error)}`));
2155
+ logger.error(`
2156
+ ${error instanceof Error ? error.message : String(error)}`);
1836
2157
  process.exit(1);
1837
2158
  }
1838
2159
  }
1839
2160
  __name(projectsDeleteCommand, "projectsDeleteCommand");
1840
2161
 
1841
2162
  // src/templates/readme_v1.ts
1842
- var getReadmeTemplate = /* @__PURE__ */ __name((projectName) => `# ${projectName}
2163
+ var getReadmeTemplate = /* @__PURE__ */ __name((projectName) => `<p align="center">
2164
+ <img
2165
+ src="https://raw.githubusercontent.com/LeanMCP/leanmcp-sdk/refs/heads/main/assets/logo.svg"
2166
+ alt="LeanMCP Logo"
2167
+ width="500"
2168
+ />
2169
+ </p>
2170
+
2171
+ <p align="center">
2172
+ <a href="https://ship.leanmcp.com">
2173
+ <img src="https://raw.githubusercontent.com/LeanMCP/leanmcp-sdk/refs/heads/main/badges/deploy.svg" alt="Deploy on LeanMCP" height="30">
2174
+ </a>
2175
+ <a href="https://ship.leanmcp.com/playground">
2176
+ <img src="https://raw.githubusercontent.com/LeanMCP/leanmcp-sdk/refs/heads/main/badges/test.svg" alt="Test on LeanMCP" height="30">
2177
+ </a>
2178
+ </p>
2179
+
2180
+ <p align="center">
2181
+ <a href="https://www.npmjs.com/package/@leanmcp/core">
2182
+ <img src="https://img.shields.io/npm/v/@leanmcp/core" alt="npm version" />
2183
+ </a>
2184
+ <a href="https://www.npmjs.com/package/@leanmcp/cli">
2185
+ <img src="https://img.shields.io/npm/dm/@leanmcp/cli" alt="npm downloads" />
2186
+ </a>
2187
+ <a href="https://leanmcp.com/">
2188
+ <img src="https://img.shields.io/badge/Website-leanmcp-0A66C2?" />
2189
+ </a>
2190
+ <a href="https://discord.com/invite/DsRcA3GwPy">
2191
+ <img src="https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white" />
2192
+ </a>
2193
+ <a href="https://x.com/LeanMcp">
2194
+ <img src="https://img.shields.io/badge/@LeanMCP-f5f5f5?logo=x&logoColor=000000" />
2195
+ </a>
2196
+ <a href="https://deepwiki.com/LeanMCP/leanmcp-sdk">
2197
+ <img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki" />
2198
+ </a>
2199
+ </p>
2200
+
2201
+ # ${projectName}
1843
2202
 
1844
2203
  MCP Server with Streamable HTTP Transport built with LeanMCP SDK
1845
2204
 
@@ -1914,19 +2273,51 @@ Services are automatically discovered and registered - no need to modify \`main.
1914
2273
  - **HTTP transport** - Production-ready HTTP server with session management
1915
2274
  - **Hot reload** - Development mode with automatic restart on file changes
1916
2275
 
2276
+ ## Deploy to LeanMCP Cloud
2277
+
2278
+ Deploy your MCP server to LeanMCP's global edge network with a single command:
2279
+
2280
+ \`\`\`bash
2281
+ # Login to LeanMCP (first time only)
2282
+ leanmcp login
2283
+
2284
+ # Deploy your server
2285
+ leanmcp deploy .
2286
+ \`\`\`
2287
+
2288
+ After deployment, your server will be available at:
2289
+ - **URL**: \`https://your-project.leanmcp.app\`
2290
+ - **Health**: \`https://your-project.leanmcp.app/health\`
2291
+ - **MCP Endpoint**: \`https://your-project.leanmcp.app/mcp\`
2292
+
2293
+ Manage your deployments at [ship.leanmcp.com](https://ship.leanmcp.com)
2294
+
1917
2295
  ## Testing with MCP Inspector
1918
2296
 
1919
2297
  \`\`\`bash
2298
+ # Test locally
1920
2299
  npx @modelcontextprotocol/inspector http://localhost:3001/mcp
2300
+
2301
+ # Test deployed server
2302
+ npx @modelcontextprotocol/inspector https://your-project.leanmcp.app/mcp
1921
2303
  \`\`\`
1922
2304
 
2305
+ ## Support
2306
+
2307
+ - [Documentation](https://docs.leanmcp.com)
2308
+ - [Discord Community](https://discord.com/invite/DsRcA3GwPy)
2309
+ - [Report Issues](https://github.com/leanmcp/leanmcp-sdk/issues)
2310
+
1923
2311
  ## License
1924
2312
 
1925
2313
  MIT
1926
2314
  `, "getReadmeTemplate");
1927
2315
 
1928
2316
  // src/templates/gitignore_v1.ts
1929
- var gitignoreTemplate = `# Logs
2317
+ var gitignoreTemplate = `# LeanMCP deployment config (contains project-specific IDs)
2318
+ .leanmcp/
2319
+
2320
+ # Logs
1930
2321
  logs
1931
2322
  *.log
1932
2323
  npm-debug.log*
@@ -2278,10 +2669,21 @@ function capitalize(str) {
2278
2669
  }
2279
2670
  __name(capitalize, "capitalize");
2280
2671
  var program = new Command();
2281
- program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version).addHelpText("after", `
2672
+ function enableDebugIfNeeded() {
2673
+ const args = process.argv;
2674
+ if (args.includes("--debug") || args.includes("-d")) {
2675
+ setDebugMode(true);
2676
+ setDebugMode2(true);
2677
+ setDeployDebugMode(true);
2678
+ debug("Debug mode enabled globally");
2679
+ }
2680
+ }
2681
+ __name(enableDebugIfNeeded, "enableDebugIfNeeded");
2682
+ enableDebugIfNeeded();
2683
+ program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version, "-v, --version", "Output the current version").helpOption("-h, --help", "Display help for command").option("-d, --debug", "Enable debug logging for all commands").addHelpText("after", `
2282
2684
  Examples:
2283
2685
  $ leanmcp create my-app # Create new project (interactive)
2284
- $ leanmcp create my-app --install # Create and install deps (non-interactive)
2686
+ $ leanmcp create my-app -i # Create and install deps (non-interactive)
2285
2687
  $ leanmcp create my-app --no-install # Create without installing deps
2286
2688
  $ leanmcp dev # Start development server
2287
2689
  $ leanmcp build # Build UI components and compile TypeScript
@@ -2290,8 +2692,17 @@ Examples:
2290
2692
  $ leanmcp deploy ./my-app # Deploy to LeanMCP cloud
2291
2693
  $ leanmcp projects list # List your cloud projects
2292
2694
  $ leanmcp projects delete <id> # Delete a cloud project
2695
+
2696
+ Global Options:
2697
+ -v, --version Output the current version
2698
+ -h, --help Display help for command
2699
+ -d, --debug Enable debug logging for all commands
2293
2700
  `);
2294
- 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) => {
2701
+ 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("-i, --install", "Install dependencies automatically (non-interactive, no dev server)").option("--no-install", "Skip dependency installation (non-interactive)").action(async (projectName, options) => {
2702
+ trackCommand("create", {
2703
+ projectName,
2704
+ ...options
2705
+ });
2295
2706
  const spinner = ora7(`Creating project ${projectName}...`).start();
2296
2707
  const targetDir = path8.join(process.cwd(), projectName);
2297
2708
  if (fs8.existsSync(targetDir)) {
@@ -2310,6 +2721,7 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
2310
2721
  dev: "leanmcp dev",
2311
2722
  build: "leanmcp build",
2312
2723
  start: "leanmcp start",
2724
+ inspect: "npx @modelcontextprotocol/inspector node dist/main.js",
2313
2725
  "start:node": "node dist/main.js",
2314
2726
  clean: "rm -rf dist"
2315
2727
  },
@@ -2322,13 +2734,13 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
2322
2734
  author: "",
2323
2735
  license: "MIT",
2324
2736
  dependencies: {
2325
- "@leanmcp/core": "^0.3.9",
2737
+ "@leanmcp/core": "^0.3.14",
2326
2738
  "@leanmcp/ui": "^0.2.1",
2327
- "@leanmcp/auth": "^0.3.2",
2739
+ "@leanmcp/auth": "^0.4.0",
2328
2740
  "dotenv": "^16.5.0"
2329
2741
  },
2330
2742
  devDependencies: {
2331
- "@leanmcp/cli": "^0.3.0",
2743
+ "@leanmcp/cli": "^0.4.0",
2332
2744
  "@types/node": "^20.0.0",
2333
2745
  "tsx": "^4.20.3",
2334
2746
  "typescript": "^5.6.3"
@@ -2378,23 +2790,23 @@ NODE_ENV=development
2378
2790
  const readme = getReadmeTemplate(projectName);
2379
2791
  await fs8.writeFile(path8.join(targetDir, "README.md"), readme);
2380
2792
  spinner.succeed(`Project ${projectName} created!`);
2381
- console.log(chalk7.green("\nSuccess! Your MCP server is ready.\n"));
2382
- console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2383
- console.log(chalk7.gray(` cd ${projectName}`));
2384
- console.log(chalk7.gray(` leanmcp deploy .
2385
- `));
2386
- console.log(chalk7.cyan("Need help? Join our Discord:"));
2387
- console.log(chalk7.blue(" https://discord.com/invite/DsRcA3GwPy\n"));
2793
+ logger.log("\nSuccess! Your MCP server is ready.\n", chalk.green);
2794
+ logger.log("To deploy to LeanMCP cloud:", chalk.cyan);
2795
+ logger.log(` cd ${projectName}`, chalk.gray);
2796
+ logger.log(` leanmcp deploy .
2797
+ `, chalk.gray);
2798
+ logger.log("Need help? Join our Discord:", chalk.cyan);
2799
+ logger.log(" https://discord.com/invite/DsRcA3GwPy\n", chalk.blue);
2388
2800
  const isNonInteractive = options.install !== void 0 || options.allowAll;
2389
2801
  if (options.install === false) {
2390
- console.log(chalk7.cyan("To get started:"));
2391
- console.log(chalk7.gray(` cd ${projectName}`));
2392
- console.log(chalk7.gray(` npm install`));
2393
- console.log(chalk7.gray(` npm run dev`));
2394
- console.log();
2395
- console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2396
- console.log(chalk7.gray(` cd ${projectName}`));
2397
- console.log(chalk7.gray(` leanmcp deploy .`));
2802
+ logger.log("To get started:", chalk.cyan);
2803
+ logger.log(` cd ${projectName}`, chalk.gray);
2804
+ logger.log(` npm install`, chalk.gray);
2805
+ logger.log(` npm run dev`, chalk.gray);
2806
+ logger.log("");
2807
+ logger.log("To deploy to LeanMCP cloud:", chalk.cyan);
2808
+ logger.log(` cd ${projectName}`, chalk.gray);
2809
+ logger.log(` leanmcp deploy .`, chalk.gray);
2398
2810
  return;
2399
2811
  }
2400
2812
  const shouldInstall = isNonInteractive ? true : await confirm3({
@@ -2423,13 +2835,13 @@ NODE_ENV=development
2423
2835
  });
2424
2836
  installSpinner.succeed("Dependencies installed successfully!");
2425
2837
  if (options.install === true) {
2426
- console.log(chalk7.cyan("\nTo start the development server:"));
2427
- console.log(chalk7.gray(` cd ${projectName}`));
2428
- console.log(chalk7.gray(` npm run dev`));
2429
- console.log();
2430
- console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2431
- console.log(chalk7.gray(` cd ${projectName}`));
2432
- console.log(chalk7.gray(` leanmcp deploy .`));
2838
+ logger.log("\nTo start the development server:", chalk.cyan);
2839
+ logger.log(` cd ${projectName}`, chalk.gray);
2840
+ logger.log(` npm run dev`, chalk.gray);
2841
+ logger.log("");
2842
+ logger.log("To deploy to LeanMCP cloud:", chalk.cyan);
2843
+ logger.log(` cd ${projectName}`, chalk.gray);
2844
+ logger.log(` leanmcp deploy .`, chalk.gray);
2433
2845
  return;
2434
2846
  }
2435
2847
  const shouldStartDev = options.allowAll ? true : await confirm3({
@@ -2437,7 +2849,7 @@ NODE_ENV=development
2437
2849
  default: true
2438
2850
  });
2439
2851
  if (shouldStartDev) {
2440
- console.log(chalk7.cyan("\nStarting development server...\n"));
2852
+ logger.log("\nStarting development server...\n", chalk.cyan);
2441
2853
  const devServer = spawn4("npm", [
2442
2854
  "run",
2443
2855
  "dev"
@@ -2451,71 +2863,86 @@ NODE_ENV=development
2451
2863
  process.exit(0);
2452
2864
  });
2453
2865
  } else {
2454
- console.log(chalk7.cyan("\nTo start the development server later:"));
2455
- console.log(chalk7.gray(` cd ${projectName}`));
2456
- console.log(chalk7.gray(` npm run dev`));
2457
- console.log();
2458
- console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2459
- console.log(chalk7.gray(` cd ${projectName}`));
2460
- console.log(chalk7.gray(` leanmcp deploy .`));
2866
+ logger.log("\nTo start the development server later:", chalk.cyan);
2867
+ logger.log(` cd ${projectName}`, chalk.gray);
2868
+ logger.log(` npm run dev`, chalk.gray);
2869
+ logger.log("");
2870
+ logger.log("To deploy to LeanMCP cloud:", chalk.cyan);
2871
+ logger.log(` cd ${projectName}`, chalk.gray);
2872
+ logger.log(` leanmcp deploy .`, chalk.gray);
2461
2873
  }
2462
2874
  } catch (error) {
2463
2875
  installSpinner.fail("Failed to install dependencies");
2464
- console.error(chalk7.red(error instanceof Error ? error.message : String(error)));
2465
- console.log(chalk7.cyan("\nYou can install dependencies manually:"));
2466
- console.log(chalk7.gray(` cd ${projectName}`));
2467
- console.log(chalk7.gray(` npm install`));
2876
+ logger.log(error instanceof Error ? error.message : String(error), chalk.red);
2877
+ logger.log("\nYou can install dependencies manually:", chalk.cyan);
2878
+ logger.log(` cd ${projectName}`, chalk.gray);
2879
+ logger.log(` npm install`, chalk.gray);
2468
2880
  }
2469
2881
  } else {
2470
- console.log(chalk7.cyan("\nTo get started:"));
2471
- console.log(chalk7.gray(` cd ${projectName}`));
2472
- console.log(chalk7.gray(` npm install`));
2473
- console.log(chalk7.gray(` npm run dev`));
2474
- console.log();
2475
- console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2476
- console.log(chalk7.gray(` cd ${projectName}`));
2477
- console.log(chalk7.gray(` leanmcp deploy .`));
2882
+ logger.log("\nTo get started:", chalk.cyan);
2883
+ logger.log(` cd ${projectName}`, chalk.gray);
2884
+ logger.log(` npm install`, chalk.gray);
2885
+ logger.log(` npm run dev`, chalk.gray);
2886
+ logger.log("");
2887
+ logger.log("To deploy to LeanMCP cloud:", chalk.cyan);
2888
+ logger.log(` cd ${projectName}`, chalk.gray);
2889
+ logger.log(` leanmcp deploy .`, chalk.gray);
2478
2890
  }
2479
2891
  });
2480
2892
  program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
2481
2893
  const cwd = process.cwd();
2482
2894
  const mcpDir = path8.join(cwd, "mcp");
2483
2895
  if (!fs8.existsSync(path8.join(cwd, "main.ts"))) {
2484
- console.error(chalk7.red("ERROR: Not a LeanMCP project (main.ts missing)."));
2896
+ logger.log("ERROR: Not a LeanMCP project (main.ts missing).", chalk.red);
2485
2897
  process.exit(1);
2486
2898
  }
2487
2899
  const serviceDir = path8.join(mcpDir, serviceName);
2488
2900
  const serviceFile = path8.join(serviceDir, "index.ts");
2489
2901
  if (fs8.existsSync(serviceDir)) {
2490
- console.error(chalk7.red(`ERROR: Service ${serviceName} already exists.`));
2902
+ logger.log(`ERROR: Service ${serviceName} already exists.`, chalk.red);
2491
2903
  process.exit(1);
2492
2904
  }
2493
2905
  await fs8.mkdirp(serviceDir);
2494
2906
  const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
2495
2907
  await fs8.writeFile(serviceFile, indexTs);
2496
- console.log(chalk7.green(`\\nCreated new service: ${chalk7.bold(serviceName)}`));
2497
- console.log(chalk7.gray(` File: mcp/${serviceName}/index.ts`));
2498
- console.log(chalk7.gray(` Tool: greet`));
2499
- console.log(chalk7.gray(` Prompt: welcomePrompt`));
2500
- console.log(chalk7.gray(` Resource: getStatus`));
2501
- console.log(chalk7.green(`\\nService will be automatically discovered on next server start!`));
2908
+ logger.log(`\\nCreated new service: ${chalk.bold(serviceName)}`, chalk.green);
2909
+ logger.log(` File: mcp/${serviceName}/index.ts`, chalk.gray);
2910
+ logger.log(` Tool: greet`, chalk.gray);
2911
+ logger.log(` Prompt: welcomePrompt`, chalk.gray);
2912
+ logger.log(` Resource: getStatus`, chalk.gray);
2913
+ logger.log(`\\nService will be automatically discovered on next server start!`, chalk.green);
2914
+ });
2915
+ program.command("dev").description("Start development server with UI hot-reload (builds @UIApp components)").action(() => {
2916
+ trackCommand("dev");
2917
+ devCommand();
2918
+ });
2919
+ program.command("build").description("Compile TypeScript to JavaScript").action(() => {
2920
+ trackCommand("build");
2921
+ buildCommand();
2502
2922
  });
2503
- program.command("dev").description("Start development server with UI hot-reload (builds @UIApp components)").action(devCommand);
2504
2923
  program.command("build").description("Build UI components and compile TypeScript for production").action(buildCommand);
2505
- program.command("start").description("Build UI components and start production server").action(startCommand);
2506
- program.command("login").description("Authenticate with LeanMCP cloud using an API key").option("--debug", "Enable debug logging").action(async (options) => {
2507
- if (options.debug) {
2508
- setDebugMode(true);
2509
- }
2924
+ program.command("start").description("Build UI components and start production server").action(() => {
2925
+ trackCommand("start");
2926
+ startCommand();
2927
+ });
2928
+ program.command("login").description("Authenticate with LeanMCP cloud using an API key").action(async () => {
2929
+ trackCommand("login");
2510
2930
  await loginCommand();
2511
2931
  });
2512
- program.command("logout").description("Remove stored API key and logout from LeanMCP cloud").action(logoutCommand);
2513
- program.command("whoami").description("Show current authentication status").action(whoamiCommand);
2514
- program.command("deploy [folder]").description("Deploy an MCP server to LeanMCP cloud").option("-s, --subdomain <subdomain>", "Subdomain for deployment").option("-y, --yes", "Skip confirmation prompts").option("--debug", "Enable debug logging for network calls").action(async (folder, options) => {
2515
- if (options.debug) {
2516
- setDebugMode(true);
2517
- setDeployDebugMode(true);
2518
- }
2932
+ program.command("logout").description("Remove stored API key and logout from LeanMCP cloud").action(() => {
2933
+ trackCommand("logout");
2934
+ logoutCommand();
2935
+ });
2936
+ program.command("whoami").description("Show current authentication status").action(() => {
2937
+ trackCommand("whoami");
2938
+ whoamiCommand();
2939
+ });
2940
+ program.command("deploy [folder]").description("Deploy an MCP server to LeanMCP cloud").option("-s, --subdomain <subdomain>", "Subdomain for deployment").option("-y, --yes", "Skip confirmation prompts").action(async (folder, options) => {
2941
+ trackCommand("deploy", {
2942
+ folder,
2943
+ subdomain: options.subdomain,
2944
+ yes: options.yes
2945
+ });
2519
2946
  const targetFolder = folder || ".";
2520
2947
  await deployCommand(targetFolder, {
2521
2948
  subdomain: options.subdomain,
@@ -2523,7 +2950,21 @@ program.command("deploy [folder]").description("Deploy an MCP server to LeanMCP
2523
2950
  });
2524
2951
  });
2525
2952
  var projectsCmd = program.command("projects").description("Manage LeanMCP cloud projects");
2526
- projectsCmd.command("list").alias("ls").description("List all your projects").action(projectsListCommand);
2527
- projectsCmd.command("get <projectId>").description("Get details of a specific project").action(projectsGetCommand);
2528
- projectsCmd.command("delete <projectId>").alias("rm").description("Delete a project").option("-f, --force", "Skip confirmation prompt").action((projectId, options) => projectsDeleteCommand(projectId, options));
2953
+ projectsCmd.command("list").alias("ls").description("List all your projects").action(() => {
2954
+ trackCommand("projects_list");
2955
+ projectsListCommand();
2956
+ });
2957
+ projectsCmd.command("get <projectId>").description("Get details of a specific project").action((projectId) => {
2958
+ trackCommand("projects_get", {
2959
+ projectId
2960
+ });
2961
+ projectsGetCommand(projectId);
2962
+ });
2963
+ projectsCmd.command("delete <projectId>").alias("rm").description("Delete a project").option("-f, --force", "Skip confirmation prompt").action((projectId, options) => {
2964
+ trackCommand("projects_delete", {
2965
+ projectId,
2966
+ force: options.force
2967
+ });
2968
+ projectsDeleteCommand(projectId, options);
2969
+ });
2529
2970
  program.parse();