@leanmcp/cli 0.4.1 → 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/LICENSE +21 -21
- package/README.md +353 -353
- package/bin/leanmcp.js +3 -3
- package/dist/index.js +751 -310
- package/package.json +73 -73
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
|
|
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
|
|
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
|
-
|
|
510
|
-
|
|
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
|
-
|
|
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
|
-
|
|
732
|
+
logger.warn(` ${error}`);
|
|
546
733
|
}
|
|
547
734
|
} else {
|
|
548
735
|
buildSpinner.info("UI components built");
|
|
549
736
|
}
|
|
550
737
|
}
|
|
551
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
801
|
+
logger.success(`${app.componentName} ${action.toLowerCase()} complete`);
|
|
615
802
|
} else {
|
|
616
|
-
|
|
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
|
-
|
|
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
|
-
|
|
664
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
910
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
725
911
|
process.exit(1);
|
|
726
912
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
|
|
743
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
992
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
808
993
|
process.exit(1);
|
|
809
994
|
}
|
|
810
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
1030
|
+
import os2 from "os";
|
|
847
1031
|
import { input, confirm } from "@inquirer/prompts";
|
|
848
|
-
var
|
|
849
|
-
function
|
|
850
|
-
|
|
1032
|
+
var DEBUG_MODE2 = false;
|
|
1033
|
+
function setDebugMode2(enabled) {
|
|
1034
|
+
DEBUG_MODE2 = enabled;
|
|
851
1035
|
}
|
|
852
|
-
__name(
|
|
853
|
-
function
|
|
854
|
-
if (
|
|
855
|
-
console.log(
|
|
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(
|
|
859
|
-
var CONFIG_DIR = path6.join(
|
|
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
|
-
|
|
1095
|
+
logger.info("\nLeanMCP Login\n");
|
|
912
1096
|
const existingConfig = await loadConfig();
|
|
913
1097
|
if (existingConfig.apiKey) {
|
|
914
|
-
|
|
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
|
-
|
|
1104
|
+
logger.gray("\nLogin cancelled. Existing API key preserved.");
|
|
921
1105
|
return;
|
|
922
1106
|
}
|
|
923
1107
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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
|
-
|
|
946
|
-
|
|
947
|
-
|
|
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
|
-
|
|
956
|
-
|
|
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
|
-
|
|
1143
|
+
debug2("Error response:", errorText);
|
|
960
1144
|
spinner.fail("Invalid API key");
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
if (
|
|
964
|
-
|
|
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
|
-
|
|
975
|
-
|
|
976
|
-
`)
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
-
|
|
1166
|
+
debug2("Error:", error);
|
|
983
1167
|
if (error instanceof Error && error.message.includes("fetch")) {
|
|
984
|
-
|
|
985
|
-
|
|
1168
|
+
logger.error("\nCould not connect to LeanMCP servers.");
|
|
1169
|
+
logger.gray("Please check your internet connection and try again.");
|
|
986
1170
|
} else {
|
|
987
|
-
|
|
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 (
|
|
991
|
-
|
|
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
|
-
|
|
1183
|
+
logger.info("\nLeanMCP Logout\n");
|
|
1000
1184
|
const config = await loadConfig();
|
|
1001
1185
|
if (!config.apiKey) {
|
|
1002
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1019
|
-
|
|
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
|
-
|
|
1026
|
-
|
|
1209
|
+
logger.warn("\nYou are not logged in.");
|
|
1210
|
+
logger.gray("Run `leanmcp login` to authenticate.\n");
|
|
1027
1211
|
return;
|
|
1028
1212
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
|
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
|
|
1448
|
+
var DEBUG_MODE3 = false;
|
|
1228
1449
|
function setDeployDebugMode(enabled) {
|
|
1229
|
-
|
|
1450
|
+
DEBUG_MODE3 = enabled;
|
|
1230
1451
|
}
|
|
1231
1452
|
__name(setDeployDebugMode, "setDeployDebugMode");
|
|
1232
|
-
function
|
|
1233
|
-
if (
|
|
1234
|
-
console.log(
|
|
1453
|
+
function debug3(message, ...args) {
|
|
1454
|
+
if (DEBUG_MODE3) {
|
|
1455
|
+
console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
|
|
1235
1456
|
}
|
|
1236
1457
|
}
|
|
1237
|
-
__name(
|
|
1458
|
+
__name(debug3, "debug");
|
|
1238
1459
|
async function debugFetch(url, options = {}) {
|
|
1239
|
-
|
|
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
|
-
|
|
1464
|
+
debug3("Request body:", JSON.stringify(body, null, 2));
|
|
1244
1465
|
} catch {
|
|
1245
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1359
|
-
|
|
1606
|
+
logger.info("\nLeanMCP Deploy\n");
|
|
1607
|
+
debug3("Starting deployment...");
|
|
1360
1608
|
const apiKey = await getApiKey();
|
|
1361
1609
|
if (!apiKey) {
|
|
1362
|
-
|
|
1363
|
-
|
|
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
|
-
|
|
1615
|
+
debug3("API URL:", apiUrl);
|
|
1368
1616
|
const absolutePath = path7.resolve(process.cwd(), folderPath);
|
|
1369
1617
|
if (!await fs7.pathExists(absolutePath)) {
|
|
1370
|
-
|
|
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
|
-
|
|
1377
|
-
|
|
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
|
-
|
|
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
|
|
1399
|
-
if (
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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
|
|
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
|
-
|
|
1657
|
+
logger.gray("\nDeployment cancelled.\n");
|
|
1429
1658
|
return;
|
|
1430
1659
|
}
|
|
1431
1660
|
if (choice === "update") {
|
|
1432
|
-
existingProject =
|
|
1433
|
-
|
|
1661
|
+
existingProject = {
|
|
1662
|
+
id: existingConfig.projectId,
|
|
1663
|
+
name: existingConfig.projectName
|
|
1664
|
+
};
|
|
1665
|
+
projectName = existingConfig.projectName;
|
|
1666
|
+
subdomain = existingConfig.subdomain;
|
|
1434
1667
|
isUpdate = true;
|
|
1435
|
-
|
|
1436
|
-
|
|
1668
|
+
logger.warn("\nUpdating existing deployment...");
|
|
1669
|
+
logger.gray("The previous version will be replaced.\n");
|
|
1437
1670
|
} else {
|
|
1438
1671
|
projectName = generateProjectName();
|
|
1439
|
-
|
|
1440
|
-
Generated project name: ${
|
|
1441
|
-
`)
|
|
1672
|
+
logger.info(`
|
|
1673
|
+
Generated project name: ${chalk.bold(projectName)}
|
|
1674
|
+
`);
|
|
1442
1675
|
}
|
|
1443
1676
|
} else {
|
|
1444
|
-
|
|
1445
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
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
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
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
|
-
|
|
1807
|
+
logger.gray("\nDeployment cancelled.\n");
|
|
1501
1808
|
return;
|
|
1502
1809
|
}
|
|
1503
1810
|
}
|
|
1504
|
-
|
|
1811
|
+
logger.log("");
|
|
1505
1812
|
let projectId;
|
|
1506
1813
|
if (isUpdate && existingProject) {
|
|
1507
1814
|
projectId = existingProject.id;
|
|
1508
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
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
|
-
|
|
1691
|
-
|
|
2012
|
+
logger.gray(` Total time: ${totalDuration}s`);
|
|
2013
|
+
logger.log("");
|
|
1692
2014
|
const dashboardBaseUrl = "https://ship.leanmcp.com";
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
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
|
-
|
|
1712
|
-
|
|
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
|
-
|
|
1730
|
-
|
|
2050
|
+
logger.warn("\nNo projects found.");
|
|
2051
|
+
logger.gray("Create one with: leanmcp deploy <folder>\n");
|
|
1731
2052
|
return;
|
|
1732
2053
|
}
|
|
1733
|
-
|
|
2054
|
+
logger.info(`
|
|
1734
2055
|
Your Projects (${projects.length})
|
|
1735
|
-
`)
|
|
1736
|
-
|
|
2056
|
+
`);
|
|
2057
|
+
logger.gray("\u2500".repeat(60));
|
|
1737
2058
|
for (const project of projects) {
|
|
1738
|
-
const statusColor = project.status === "ACTIVE" ?
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1757
|
-
|
|
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
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
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
|
-
|
|
2103
|
+
logger.gray(` S3 Location: ${project.s3Location}`);
|
|
1783
2104
|
}
|
|
1784
|
-
|
|
2105
|
+
logger.gray(` Created: ${new Date(project.createdAt).toLocaleString()}`);
|
|
1785
2106
|
if (project.updatedAt) {
|
|
1786
|
-
|
|
2107
|
+
logger.gray(` Updated: ${new Date(project.updatedAt).toLocaleString()}`);
|
|
1787
2108
|
}
|
|
1788
|
-
|
|
2109
|
+
logger.log("");
|
|
1789
2110
|
} catch (error) {
|
|
1790
2111
|
spinner.fail("Failed to fetch project");
|
|
1791
|
-
|
|
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
|
-
|
|
1801
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2152
|
+
logger.log("");
|
|
1832
2153
|
} catch (error) {
|
|
1833
2154
|
spinner.fail("Failed to delete project");
|
|
1834
|
-
|
|
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) =>
|
|
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 = `#
|
|
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
|
-
|
|
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
|
|
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.
|
|
2737
|
+
"@leanmcp/core": "^0.3.14",
|
|
2326
2738
|
"@leanmcp/ui": "^0.2.1",
|
|
2327
|
-
"@leanmcp/auth": "^0.
|
|
2739
|
+
"@leanmcp/auth": "^0.4.0",
|
|
2328
2740
|
"dotenv": "^16.5.0"
|
|
2329
2741
|
},
|
|
2330
2742
|
devDependencies: {
|
|
2331
|
-
"@leanmcp/cli": "^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
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
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
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
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
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
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
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
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
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
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(
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
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(
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
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(
|
|
2527
|
-
|
|
2528
|
-
|
|
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();
|