@leanmcp/cli 0.5.6 → 0.5.8
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/README.md +477 -477
- package/dist/index.js +428 -118
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,9 +3,9 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
6
|
+
import fs13 from "fs-extra";
|
|
7
|
+
import path13 from "path";
|
|
8
|
+
import ora9 from "ora";
|
|
9
9
|
import { createRequire } from "module";
|
|
10
10
|
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
11
11
|
import { spawn as spawn4 } from "child_process";
|
|
@@ -13,8 +13,8 @@ import { spawn as spawn4 } from "child_process";
|
|
|
13
13
|
// src/commands/dev.ts
|
|
14
14
|
import { spawn } from "child_process";
|
|
15
15
|
import ora from "ora";
|
|
16
|
-
import
|
|
17
|
-
import
|
|
16
|
+
import path4 from "path";
|
|
17
|
+
import fs4 from "fs-extra";
|
|
18
18
|
import crypto2 from "crypto";
|
|
19
19
|
import chokidar from "chokidar";
|
|
20
20
|
|
|
@@ -471,6 +471,8 @@ __name(deleteUIComponent, "deleteUIComponent");
|
|
|
471
471
|
import chalk from "chalk";
|
|
472
472
|
import os from "os";
|
|
473
473
|
import crypto from "crypto";
|
|
474
|
+
import path3 from "path";
|
|
475
|
+
import fs3 from "fs-extra";
|
|
474
476
|
import { execSync } from "child_process";
|
|
475
477
|
var POSTHOG_API_KEY = "phc_EoMHKFbx6j2wUFsf8ywqgHntY4vEXC3ZzLFoPJVjRRT";
|
|
476
478
|
var POSTHOG_API_HOST = "https://d18m0xvdtnkibr.cloudfront.net";
|
|
@@ -568,12 +570,40 @@ var sendToPostHog = /* @__PURE__ */ __name((eventName, properties = {}) => {
|
|
|
568
570
|
}
|
|
569
571
|
});
|
|
570
572
|
}, "sendToPostHog");
|
|
573
|
+
var LOG_DIR = path3.join(os.homedir(), ".leanmcp", "logs");
|
|
574
|
+
try {
|
|
575
|
+
fs3.ensureDirSync(LOG_DIR);
|
|
576
|
+
} catch (err) {
|
|
577
|
+
}
|
|
578
|
+
function getTimestamp() {
|
|
579
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
580
|
+
}
|
|
581
|
+
__name(getTimestamp, "getTimestamp");
|
|
582
|
+
function getLogFilePath() {
|
|
583
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
584
|
+
return path3.join(LOG_DIR, `cli-${date}.log`);
|
|
585
|
+
}
|
|
586
|
+
__name(getLogFilePath, "getLogFilePath");
|
|
587
|
+
function redactSensitive(text) {
|
|
588
|
+
if (!text) return text;
|
|
589
|
+
return text.replace(/(leanmcp_|airtain_)[a-zA-Z0-9]{20,}/g, "$1********************");
|
|
590
|
+
}
|
|
591
|
+
__name(redactSensitive, "redactSensitive");
|
|
571
592
|
var LoggerClass = class LoggerClass2 {
|
|
572
593
|
static {
|
|
573
594
|
__name(this, "LoggerClass");
|
|
574
595
|
}
|
|
596
|
+
writeToFile(level, message) {
|
|
597
|
+
try {
|
|
598
|
+
const timestamp = getTimestamp();
|
|
599
|
+
const logLine = `[${timestamp}] [${level.toUpperCase()}] ${redactSensitive(message)}
|
|
600
|
+
`;
|
|
601
|
+
fs3.appendFileSync(getLogFilePath(), logLine);
|
|
602
|
+
} catch (err) {
|
|
603
|
+
}
|
|
604
|
+
}
|
|
575
605
|
/**
|
|
576
|
-
* Log a message to console and send cli_log event to PostHog
|
|
606
|
+
* Log a message to console, file, and send cli_log event to PostHog
|
|
577
607
|
* @param text - The text to log
|
|
578
608
|
* @param styleFn - Optional chalk style function (e.g., chalk.cyan, chalk.green)
|
|
579
609
|
*/
|
|
@@ -583,32 +613,35 @@ var LoggerClass = class LoggerClass2 {
|
|
|
583
613
|
} else {
|
|
584
614
|
console.log(text);
|
|
585
615
|
}
|
|
616
|
+
this.writeToFile("log", text);
|
|
586
617
|
sendToPostHog("cli_log", {
|
|
587
618
|
message: text,
|
|
588
619
|
level: "log"
|
|
589
620
|
});
|
|
590
621
|
}
|
|
591
622
|
/**
|
|
592
|
-
* Log a warning to console and send cli_warn event to PostHog
|
|
623
|
+
* Log a warning to console, file, and send cli_warn event to PostHog
|
|
593
624
|
* @param text - The warning text
|
|
594
625
|
* @param styleFn - Optional chalk style function (defaults to chalk.yellow)
|
|
595
626
|
*/
|
|
596
627
|
warn(text, styleFn) {
|
|
597
628
|
const style = styleFn || chalk.yellow;
|
|
598
629
|
console.log(style(text));
|
|
630
|
+
this.writeToFile("warn", text);
|
|
599
631
|
sendToPostHog("cli_warn", {
|
|
600
632
|
message: text,
|
|
601
633
|
level: "warn"
|
|
602
634
|
});
|
|
603
635
|
}
|
|
604
636
|
/**
|
|
605
|
-
* Log an error to console and send cli_error event to PostHog
|
|
637
|
+
* Log an error to console, file, and send cli_error event to PostHog
|
|
606
638
|
* @param text - The error text
|
|
607
639
|
* @param styleFn - Optional chalk style function (defaults to chalk.red)
|
|
608
640
|
*/
|
|
609
641
|
error(text, styleFn) {
|
|
610
642
|
const style = styleFn || chalk.red;
|
|
611
643
|
console.error(style(text));
|
|
644
|
+
this.writeToFile("error", text);
|
|
612
645
|
sendToPostHog("cli_error", {
|
|
613
646
|
message: text,
|
|
614
647
|
level: "error"
|
|
@@ -620,6 +653,7 @@ var LoggerClass = class LoggerClass2 {
|
|
|
620
653
|
*/
|
|
621
654
|
info(text) {
|
|
622
655
|
console.log(chalk.cyan(text));
|
|
656
|
+
this.writeToFile("info", text);
|
|
623
657
|
sendToPostHog("cli_log", {
|
|
624
658
|
message: text,
|
|
625
659
|
level: "info"
|
|
@@ -631,6 +665,7 @@ var LoggerClass = class LoggerClass2 {
|
|
|
631
665
|
*/
|
|
632
666
|
success(text) {
|
|
633
667
|
console.log(chalk.green(text));
|
|
668
|
+
this.writeToFile("success", text);
|
|
634
669
|
sendToPostHog("cli_log", {
|
|
635
670
|
message: text,
|
|
636
671
|
level: "success"
|
|
@@ -642,6 +677,7 @@ var LoggerClass = class LoggerClass2 {
|
|
|
642
677
|
*/
|
|
643
678
|
gray(text) {
|
|
644
679
|
console.log(chalk.gray(text));
|
|
680
|
+
this.writeToFile("debug", text);
|
|
645
681
|
sendToPostHog("cli_log", {
|
|
646
682
|
message: text,
|
|
647
683
|
level: "gray"
|
|
@@ -658,7 +694,7 @@ var trackCommand = /* @__PURE__ */ __name((command, options = {}) => {
|
|
|
658
694
|
|
|
659
695
|
// src/commands/dev.ts
|
|
660
696
|
function computeHash(filePath) {
|
|
661
|
-
const content =
|
|
697
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
662
698
|
return crypto2.createHash("md5").update(content).digest("hex");
|
|
663
699
|
}
|
|
664
700
|
__name(computeHash, "computeHash");
|
|
@@ -675,7 +711,7 @@ function computeDiff(previousUIApps, currentUIApps, hashCache) {
|
|
|
675
711
|
} else {
|
|
676
712
|
const oldHash = hashCache.get(app.resourceUri);
|
|
677
713
|
let newHash;
|
|
678
|
-
if (
|
|
714
|
+
if (fs4.existsSync(app.componentPath)) {
|
|
679
715
|
newHash = computeHash(app.componentPath);
|
|
680
716
|
}
|
|
681
717
|
if (oldHash !== newHash) {
|
|
@@ -692,7 +728,7 @@ function computeDiff(previousUIApps, currentUIApps, hashCache) {
|
|
|
692
728
|
__name(computeDiff, "computeDiff");
|
|
693
729
|
async function devCommand() {
|
|
694
730
|
const cwd = process.cwd();
|
|
695
|
-
if (!await
|
|
731
|
+
if (!await fs4.pathExists(path4.join(cwd, "main.ts"))) {
|
|
696
732
|
logger.error("ERROR: Not a LeanMCP project (main.ts not found).");
|
|
697
733
|
logger.gray("Run this command from your project root.");
|
|
698
734
|
process.exit(1);
|
|
@@ -748,12 +784,12 @@ async function devCommand() {
|
|
|
748
784
|
let watcher = null;
|
|
749
785
|
const componentHashCache = /* @__PURE__ */ new Map();
|
|
750
786
|
for (const app of uiApps) {
|
|
751
|
-
if (await
|
|
787
|
+
if (await fs4.pathExists(app.componentPath)) {
|
|
752
788
|
componentHashCache.set(app.resourceUri, computeHash(app.componentPath));
|
|
753
789
|
}
|
|
754
790
|
}
|
|
755
791
|
let previousUIApps = uiApps;
|
|
756
|
-
const mcpPath =
|
|
792
|
+
const mcpPath = path4.join(cwd, "mcp");
|
|
757
793
|
watcher = chokidar.watch(mcpPath, {
|
|
758
794
|
ignoreInitial: true,
|
|
759
795
|
ignored: [
|
|
@@ -795,7 +831,7 @@ async function devCommand() {
|
|
|
795
831
|
} else {
|
|
796
832
|
manifest[app.resourceUri] = result.htmlPath;
|
|
797
833
|
}
|
|
798
|
-
if (await
|
|
834
|
+
if (await fs4.pathExists(app.componentPath)) {
|
|
799
835
|
componentHashCache.set(app.resourceUri, computeHash(app.componentPath));
|
|
800
836
|
}
|
|
801
837
|
logger.success(`${app.componentName} ${action.toLowerCase()} complete`);
|
|
@@ -841,12 +877,12 @@ __name(devCommand, "devCommand");
|
|
|
841
877
|
// src/commands/build.ts
|
|
842
878
|
import { spawn as spawn2 } from "child_process";
|
|
843
879
|
import ora2 from "ora";
|
|
844
|
-
import
|
|
845
|
-
import
|
|
880
|
+
import path6 from "path";
|
|
881
|
+
import fs6 from "fs-extra";
|
|
846
882
|
|
|
847
883
|
// src/schema-extractor.ts
|
|
848
|
-
import
|
|
849
|
-
import
|
|
884
|
+
import path5 from "path";
|
|
885
|
+
import fs5 from "fs-extra";
|
|
850
886
|
import { Project, Node } from "ts-morph";
|
|
851
887
|
async function generateSchemaMetadata(projectDir) {
|
|
852
888
|
const metadata = {};
|
|
@@ -921,9 +957,9 @@ async function generateSchemaMetadata(projectDir) {
|
|
|
921
957
|
}
|
|
922
958
|
processedClasses.add(className);
|
|
923
959
|
}
|
|
924
|
-
const outputPath =
|
|
925
|
-
await
|
|
926
|
-
await
|
|
960
|
+
const outputPath = path5.join(projectDir, "dist", "schema-metadata.json");
|
|
961
|
+
await fs5.ensureDir(path5.dirname(outputPath));
|
|
962
|
+
await fs5.writeJSON(outputPath, metadata, {
|
|
927
963
|
spaces: 2
|
|
928
964
|
});
|
|
929
965
|
}
|
|
@@ -958,11 +994,11 @@ __name(extractBaseClassName, "extractBaseClassName");
|
|
|
958
994
|
async function findTsFiles(dir) {
|
|
959
995
|
const files = [];
|
|
960
996
|
async function scan(currentDir) {
|
|
961
|
-
const entries = await
|
|
997
|
+
const entries = await fs5.readdir(currentDir, {
|
|
962
998
|
withFileTypes: true
|
|
963
999
|
});
|
|
964
1000
|
for (const entry of entries) {
|
|
965
|
-
const fullPath =
|
|
1001
|
+
const fullPath = path5.join(currentDir, entry.name);
|
|
966
1002
|
if (entry.isDirectory()) {
|
|
967
1003
|
if ([
|
|
968
1004
|
"node_modules",
|
|
@@ -1028,7 +1064,7 @@ __name(mapPrimitiveType, "mapPrimitiveType");
|
|
|
1028
1064
|
// src/commands/build.ts
|
|
1029
1065
|
async function buildCommand() {
|
|
1030
1066
|
const cwd = process.cwd();
|
|
1031
|
-
if (!await
|
|
1067
|
+
if (!await fs6.pathExists(path6.join(cwd, "main.ts"))) {
|
|
1032
1068
|
logger.error("ERROR: Not a LeanMCP project (main.ts not found).");
|
|
1033
1069
|
logger.gray("Run this command from your project root.");
|
|
1034
1070
|
process.exit(1);
|
|
@@ -1110,11 +1146,11 @@ __name(buildCommand, "buildCommand");
|
|
|
1110
1146
|
// src/commands/start.ts
|
|
1111
1147
|
import { spawn as spawn3 } from "child_process";
|
|
1112
1148
|
import ora3 from "ora";
|
|
1113
|
-
import
|
|
1114
|
-
import
|
|
1149
|
+
import path7 from "path";
|
|
1150
|
+
import fs7 from "fs-extra";
|
|
1115
1151
|
async function startCommand() {
|
|
1116
1152
|
const cwd = process.cwd();
|
|
1117
|
-
if (!await
|
|
1153
|
+
if (!await fs7.pathExists(path7.join(cwd, "main.ts"))) {
|
|
1118
1154
|
logger.error("ERROR: Not a LeanMCP project (main.ts not found).");
|
|
1119
1155
|
logger.gray("Run this command from your project root.");
|
|
1120
1156
|
process.exit(1);
|
|
@@ -1216,8 +1252,8 @@ __name(startCommand, "startCommand");
|
|
|
1216
1252
|
|
|
1217
1253
|
// src/commands/login.ts
|
|
1218
1254
|
import ora4 from "ora";
|
|
1219
|
-
import
|
|
1220
|
-
import
|
|
1255
|
+
import path8 from "path";
|
|
1256
|
+
import fs8 from "fs-extra";
|
|
1221
1257
|
import os2 from "os";
|
|
1222
1258
|
import { input, confirm } from "@inquirer/prompts";
|
|
1223
1259
|
var DEBUG_MODE2 = false;
|
|
@@ -1231,12 +1267,12 @@ function debug2(message, ...args) {
|
|
|
1231
1267
|
}
|
|
1232
1268
|
}
|
|
1233
1269
|
__name(debug2, "debug");
|
|
1234
|
-
var CONFIG_DIR =
|
|
1235
|
-
var CONFIG_FILE =
|
|
1270
|
+
var CONFIG_DIR = path8.join(os2.homedir(), ".leanmcp");
|
|
1271
|
+
var CONFIG_FILE = path8.join(CONFIG_DIR, "config.json");
|
|
1236
1272
|
async function loadConfig() {
|
|
1237
1273
|
try {
|
|
1238
|
-
if (await
|
|
1239
|
-
return await
|
|
1274
|
+
if (await fs8.pathExists(CONFIG_FILE)) {
|
|
1275
|
+
return await fs8.readJSON(CONFIG_FILE);
|
|
1240
1276
|
}
|
|
1241
1277
|
} catch (error) {
|
|
1242
1278
|
}
|
|
@@ -1244,8 +1280,8 @@ async function loadConfig() {
|
|
|
1244
1280
|
}
|
|
1245
1281
|
__name(loadConfig, "loadConfig");
|
|
1246
1282
|
async function saveConfig(config) {
|
|
1247
|
-
await
|
|
1248
|
-
await
|
|
1283
|
+
await fs8.ensureDir(CONFIG_DIR);
|
|
1284
|
+
await fs8.writeJSON(CONFIG_FILE, config, {
|
|
1249
1285
|
spaces: 2
|
|
1250
1286
|
});
|
|
1251
1287
|
}
|
|
@@ -1452,8 +1488,8 @@ __name(whoamiCommand, "whoamiCommand");
|
|
|
1452
1488
|
|
|
1453
1489
|
// src/commands/deploy.ts
|
|
1454
1490
|
import ora5 from "ora";
|
|
1455
|
-
import
|
|
1456
|
-
import
|
|
1491
|
+
import path10 from "path";
|
|
1492
|
+
import fs10 from "fs-extra";
|
|
1457
1493
|
import os3 from "os";
|
|
1458
1494
|
import archiver from "archiver";
|
|
1459
1495
|
import { input as input2, confirm as confirm2, select } from "@inquirer/prompts";
|
|
@@ -1636,8 +1672,8 @@ function generateProjectName() {
|
|
|
1636
1672
|
__name(generateProjectName, "generateProjectName");
|
|
1637
1673
|
|
|
1638
1674
|
// src/utils/env-parser.ts
|
|
1639
|
-
import
|
|
1640
|
-
import
|
|
1675
|
+
import fs9 from "fs-extra";
|
|
1676
|
+
import path9 from "path";
|
|
1641
1677
|
var RESERVED_ENV_KEYS = [
|
|
1642
1678
|
"AWS_REGION",
|
|
1643
1679
|
"AWS_ACCESS_KEY_ID",
|
|
@@ -1700,16 +1736,16 @@ function parseEnvFile(content) {
|
|
|
1700
1736
|
}
|
|
1701
1737
|
__name(parseEnvFile, "parseEnvFile");
|
|
1702
1738
|
async function loadEnvFile(filePath) {
|
|
1703
|
-
const absolutePath =
|
|
1704
|
-
if (!await
|
|
1739
|
+
const absolutePath = path9.resolve(filePath);
|
|
1740
|
+
if (!await fs9.pathExists(absolutePath)) {
|
|
1705
1741
|
throw new Error(`Env file not found: ${absolutePath}`);
|
|
1706
1742
|
}
|
|
1707
|
-
const content = await
|
|
1743
|
+
const content = await fs9.readFile(absolutePath, "utf-8");
|
|
1708
1744
|
return parseEnvFile(content);
|
|
1709
1745
|
}
|
|
1710
1746
|
__name(loadEnvFile, "loadEnvFile");
|
|
1711
1747
|
async function writeEnvFile(filePath, vars) {
|
|
1712
|
-
const absolutePath =
|
|
1748
|
+
const absolutePath = path9.resolve(filePath);
|
|
1713
1749
|
const lines = [
|
|
1714
1750
|
"# Environment variables",
|
|
1715
1751
|
`# Generated by leanmcp CLI at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
@@ -1725,7 +1761,7 @@ async function writeEnvFile(filePath, vars) {
|
|
|
1725
1761
|
}
|
|
1726
1762
|
}
|
|
1727
1763
|
lines.push("");
|
|
1728
|
-
await
|
|
1764
|
+
await fs9.writeFile(absolutePath, lines.join("\n"));
|
|
1729
1765
|
}
|
|
1730
1766
|
__name(writeEnvFile, "writeEnvFile");
|
|
1731
1767
|
function isValidEnvKey(key) {
|
|
@@ -1839,10 +1875,10 @@ var API_ENDPOINTS = {
|
|
|
1839
1875
|
var LEANMCP_CONFIG_DIR = ".leanmcp";
|
|
1840
1876
|
var LEANMCP_CONFIG_FILE = "config.json";
|
|
1841
1877
|
async function readLeanMCPConfig(projectPath) {
|
|
1842
|
-
const configPath =
|
|
1878
|
+
const configPath = path10.join(projectPath, LEANMCP_CONFIG_DIR, LEANMCP_CONFIG_FILE);
|
|
1843
1879
|
try {
|
|
1844
|
-
if (await
|
|
1845
|
-
const config = await
|
|
1880
|
+
if (await fs10.pathExists(configPath)) {
|
|
1881
|
+
const config = await fs10.readJSON(configPath);
|
|
1846
1882
|
debug3("Found existing .leanmcp config:", config);
|
|
1847
1883
|
return config;
|
|
1848
1884
|
}
|
|
@@ -1853,10 +1889,10 @@ async function readLeanMCPConfig(projectPath) {
|
|
|
1853
1889
|
}
|
|
1854
1890
|
__name(readLeanMCPConfig, "readLeanMCPConfig");
|
|
1855
1891
|
async function writeLeanMCPConfig(projectPath, config) {
|
|
1856
|
-
const configDir =
|
|
1857
|
-
const configPath =
|
|
1858
|
-
await
|
|
1859
|
-
await
|
|
1892
|
+
const configDir = path10.join(projectPath, LEANMCP_CONFIG_DIR);
|
|
1893
|
+
const configPath = path10.join(configDir, LEANMCP_CONFIG_FILE);
|
|
1894
|
+
await fs10.ensureDir(configDir);
|
|
1895
|
+
await fs10.writeJSON(configPath, config, {
|
|
1860
1896
|
spaces: 2
|
|
1861
1897
|
});
|
|
1862
1898
|
debug3("Saved .leanmcp config:", config);
|
|
@@ -1864,7 +1900,7 @@ async function writeLeanMCPConfig(projectPath, config) {
|
|
|
1864
1900
|
__name(writeLeanMCPConfig, "writeLeanMCPConfig");
|
|
1865
1901
|
async function createZipArchive(folderPath, outputPath) {
|
|
1866
1902
|
return new Promise((resolve, reject) => {
|
|
1867
|
-
const output =
|
|
1903
|
+
const output = fs10.createWriteStream(outputPath);
|
|
1868
1904
|
const archive = archiver("zip", {
|
|
1869
1905
|
zlib: {
|
|
1870
1906
|
level: 9
|
|
@@ -1982,16 +2018,16 @@ async function deployCommand(folderPath, options = {}) {
|
|
|
1982
2018
|
}
|
|
1983
2019
|
const apiUrl = await getApiUrl();
|
|
1984
2020
|
debug3("API URL:", apiUrl);
|
|
1985
|
-
const absolutePath =
|
|
1986
|
-
if (!await
|
|
2021
|
+
const absolutePath = path10.resolve(process.cwd(), folderPath);
|
|
2022
|
+
if (!await fs10.pathExists(absolutePath)) {
|
|
1987
2023
|
logger.error(`Folder not found: ${absolutePath}`);
|
|
1988
2024
|
process.exit(1);
|
|
1989
2025
|
}
|
|
1990
|
-
const hasMainTs = await
|
|
1991
|
-
const hasPackageJson = await
|
|
1992
|
-
const hasMainPy = await
|
|
1993
|
-
const hasRequirementsTxt = await
|
|
1994
|
-
const hasPyprojectToml = await
|
|
2026
|
+
const hasMainTs = await fs10.pathExists(path10.join(absolutePath, "main.ts"));
|
|
2027
|
+
const hasPackageJson = await fs10.pathExists(path10.join(absolutePath, "package.json"));
|
|
2028
|
+
const hasMainPy = await fs10.pathExists(path10.join(absolutePath, "main.py"));
|
|
2029
|
+
const hasRequirementsTxt = await fs10.pathExists(path10.join(absolutePath, "requirements.txt"));
|
|
2030
|
+
const hasPyprojectToml = await fs10.pathExists(path10.join(absolutePath, "pyproject.toml"));
|
|
1995
2031
|
const isNodeProject = hasMainTs || hasPackageJson;
|
|
1996
2032
|
const isPythonProject = hasMainPy || hasRequirementsTxt || hasPyprojectToml;
|
|
1997
2033
|
if (!isNodeProject && !isPythonProject) {
|
|
@@ -2063,10 +2099,10 @@ Generated project name: ${chalk.bold(projectName)}
|
|
|
2063
2099
|
} catch (e) {
|
|
2064
2100
|
debug3("Could not fetch existing projects");
|
|
2065
2101
|
}
|
|
2066
|
-
let folderName =
|
|
2102
|
+
let folderName = path10.basename(absolutePath);
|
|
2067
2103
|
if (hasPackageJson) {
|
|
2068
2104
|
try {
|
|
2069
|
-
const pkg2 = await
|
|
2105
|
+
const pkg2 = await fs10.readJSON(path10.join(absolutePath, "package.json"));
|
|
2070
2106
|
folderName = pkg2.name || folderName;
|
|
2071
2107
|
} catch (e) {
|
|
2072
2108
|
}
|
|
@@ -2217,7 +2253,7 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
2217
2253
|
}
|
|
2218
2254
|
const uploadSpinner = ora5("Packaging and uploading...").start();
|
|
2219
2255
|
try {
|
|
2220
|
-
const tempZip =
|
|
2256
|
+
const tempZip = path10.join(os3.tmpdir(), `leanmcp-${Date.now()}.zip`);
|
|
2221
2257
|
const zipSize = await createZipArchive(absolutePath, tempZip);
|
|
2222
2258
|
uploadSpinner.text = `Packaging... (${Math.round(zipSize / 1024)}KB)`;
|
|
2223
2259
|
debug3("Step 2a: Getting upload URL for project:", projectId);
|
|
@@ -2244,7 +2280,7 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
2244
2280
|
throw new Error("Backend did not return upload URL");
|
|
2245
2281
|
}
|
|
2246
2282
|
debug3("Step 2b: Uploading to S3...");
|
|
2247
|
-
const zipBuffer = await
|
|
2283
|
+
const zipBuffer = await fs10.readFile(tempZip);
|
|
2248
2284
|
const s3Response = await fetch(uploadUrl, {
|
|
2249
2285
|
method: "PUT",
|
|
2250
2286
|
body: zipBuffer,
|
|
@@ -2267,7 +2303,7 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
2267
2303
|
s3Location
|
|
2268
2304
|
})
|
|
2269
2305
|
});
|
|
2270
|
-
await
|
|
2306
|
+
await fs10.remove(tempZip);
|
|
2271
2307
|
uploadSpinner.succeed("Project uploaded");
|
|
2272
2308
|
} catch (error) {
|
|
2273
2309
|
uploadSpinner.fail("Failed to upload");
|
|
@@ -2404,8 +2440,273 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
2404
2440
|
}
|
|
2405
2441
|
__name(deployCommand, "deployCommand");
|
|
2406
2442
|
|
|
2407
|
-
// src/commands/
|
|
2443
|
+
// src/commands/feedback.ts
|
|
2408
2444
|
import ora6 from "ora";
|
|
2445
|
+
import fs11 from "fs-extra";
|
|
2446
|
+
import path11 from "path";
|
|
2447
|
+
import os4 from "os";
|
|
2448
|
+
var DEBUG_MODE4 = false;
|
|
2449
|
+
function debug4(message, ...args) {
|
|
2450
|
+
if (DEBUG_MODE4) {
|
|
2451
|
+
console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
__name(debug4, "debug");
|
|
2455
|
+
async function readFileAsBase64(filePath) {
|
|
2456
|
+
try {
|
|
2457
|
+
const absolutePath = path11.resolve(filePath);
|
|
2458
|
+
const stats = await fs11.stat(absolutePath);
|
|
2459
|
+
if (!stats.isFile()) {
|
|
2460
|
+
throw new Error(`${filePath} is not a file`);
|
|
2461
|
+
}
|
|
2462
|
+
const content = await fs11.readFile(absolutePath, "base64");
|
|
2463
|
+
const ext = path11.extname(absolutePath).toLowerCase();
|
|
2464
|
+
let mimeType = "application/octet-stream";
|
|
2465
|
+
switch (ext) {
|
|
2466
|
+
case ".txt":
|
|
2467
|
+
case ".log":
|
|
2468
|
+
mimeType = "text/plain";
|
|
2469
|
+
break;
|
|
2470
|
+
case ".json":
|
|
2471
|
+
mimeType = "application/json";
|
|
2472
|
+
break;
|
|
2473
|
+
case ".js":
|
|
2474
|
+
mimeType = "application/javascript";
|
|
2475
|
+
break;
|
|
2476
|
+
case ".ts":
|
|
2477
|
+
mimeType = "application/typescript";
|
|
2478
|
+
break;
|
|
2479
|
+
case ".md":
|
|
2480
|
+
mimeType = "text/markdown";
|
|
2481
|
+
break;
|
|
2482
|
+
}
|
|
2483
|
+
return {
|
|
2484
|
+
content,
|
|
2485
|
+
size: stats.size,
|
|
2486
|
+
type: mimeType
|
|
2487
|
+
};
|
|
2488
|
+
} catch (error) {
|
|
2489
|
+
throw new Error(`Failed to read file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
__name(readFileAsBase64, "readFileAsBase64");
|
|
2493
|
+
async function collectLogFiles() {
|
|
2494
|
+
const attachments = [];
|
|
2495
|
+
const logLocations = [
|
|
2496
|
+
path11.join(os4.homedir(), ".leanmcp", "logs"),
|
|
2497
|
+
path11.join(process.cwd(), "logs"),
|
|
2498
|
+
path11.join(process.cwd(), ".leanmcp", "logs")
|
|
2499
|
+
];
|
|
2500
|
+
for (const logDir of logLocations) {
|
|
2501
|
+
try {
|
|
2502
|
+
if (await fs11.pathExists(logDir)) {
|
|
2503
|
+
const files = await fs11.readdir(logDir);
|
|
2504
|
+
const logFiles = [];
|
|
2505
|
+
for (const file of files) {
|
|
2506
|
+
if (!file.endsWith(".log")) continue;
|
|
2507
|
+
try {
|
|
2508
|
+
const filePath = path11.join(logDir, file);
|
|
2509
|
+
const stats = await fs11.stat(filePath);
|
|
2510
|
+
logFiles.push({
|
|
2511
|
+
file,
|
|
2512
|
+
filePath,
|
|
2513
|
+
mtime: stats.mtimeMs
|
|
2514
|
+
});
|
|
2515
|
+
} catch (e) {
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
const recentLogs = logFiles.sort((a, b) => b.mtime - a.mtime).slice(0, 3);
|
|
2519
|
+
for (const log of recentLogs) {
|
|
2520
|
+
try {
|
|
2521
|
+
const fileData = await readFileAsBase64(log.filePath);
|
|
2522
|
+
if (!attachments.some((a) => a.name === log.file)) {
|
|
2523
|
+
attachments.push({
|
|
2524
|
+
name: log.file,
|
|
2525
|
+
...fileData
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
} catch (error) {
|
|
2529
|
+
debug4(`Failed to read log file ${log.filePath}: ${error}`);
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
} catch (error) {
|
|
2534
|
+
debug4(`Failed to scan log directory ${logDir}: ${error}`);
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
const npmDebugLog = path11.join(os4.homedir(), ".npm", "_logs");
|
|
2538
|
+
try {
|
|
2539
|
+
if (await fs11.pathExists(npmDebugLog)) {
|
|
2540
|
+
const logFiles = await fs11.readdir(npmDebugLog);
|
|
2541
|
+
const latestLog = logFiles.filter((file) => file.endsWith(".log")).sort().pop();
|
|
2542
|
+
if (latestLog) {
|
|
2543
|
+
const filePath = path11.join(npmDebugLog, latestLog);
|
|
2544
|
+
try {
|
|
2545
|
+
const fileData = await readFileAsBase64(filePath);
|
|
2546
|
+
attachments.push({
|
|
2547
|
+
name: `npm-${latestLog}`,
|
|
2548
|
+
...fileData
|
|
2549
|
+
});
|
|
2550
|
+
} catch (error) {
|
|
2551
|
+
debug4(`Failed to read npm debug log: ${error}`);
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
} catch (error) {
|
|
2556
|
+
debug4(`Failed to collect npm debug logs: ${error}`);
|
|
2557
|
+
}
|
|
2558
|
+
return attachments;
|
|
2559
|
+
}
|
|
2560
|
+
__name(collectLogFiles, "collectLogFiles");
|
|
2561
|
+
async function sendFeedbackToApi(message, attachments = [], isAnonymous = false) {
|
|
2562
|
+
const apiUrl = await getApiUrl();
|
|
2563
|
+
const endpoint = isAnonymous ? "/feedback/anonymous" : "/feedback";
|
|
2564
|
+
const url = `${apiUrl}${endpoint}`;
|
|
2565
|
+
debug4("API URL:", apiUrl);
|
|
2566
|
+
debug4("Endpoint:", endpoint);
|
|
2567
|
+
debug4("Message length:", message.length);
|
|
2568
|
+
debug4("Attachments count:", attachments.length);
|
|
2569
|
+
const headers = {
|
|
2570
|
+
"Content-Type": "application/json"
|
|
2571
|
+
};
|
|
2572
|
+
if (!isAnonymous) {
|
|
2573
|
+
const apiKey = await getApiKey();
|
|
2574
|
+
if (!apiKey) {
|
|
2575
|
+
throw new Error("Not authenticated. Please run `leanmcp login` first.");
|
|
2576
|
+
}
|
|
2577
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
2578
|
+
}
|
|
2579
|
+
const payload = {
|
|
2580
|
+
message,
|
|
2581
|
+
attachments: attachments.map((att) => ({
|
|
2582
|
+
name: att.name,
|
|
2583
|
+
content: att.content,
|
|
2584
|
+
size: att.size,
|
|
2585
|
+
type: att.type
|
|
2586
|
+
}))
|
|
2587
|
+
};
|
|
2588
|
+
debug4("Sending feedback request...");
|
|
2589
|
+
const response = await fetch(url, {
|
|
2590
|
+
method: "POST",
|
|
2591
|
+
headers,
|
|
2592
|
+
body: JSON.stringify(payload)
|
|
2593
|
+
});
|
|
2594
|
+
debug4("Response status:", response.status);
|
|
2595
|
+
debug4("Response ok:", response.ok);
|
|
2596
|
+
if (!response.ok) {
|
|
2597
|
+
const errorText = await response.text();
|
|
2598
|
+
debug4("Error response:", errorText);
|
|
2599
|
+
if (response.status === 401) {
|
|
2600
|
+
throw new Error("Authentication failed. Please run `leanmcp login` to re-authenticate.");
|
|
2601
|
+
} else if (response.status === 413) {
|
|
2602
|
+
throw new Error("Attachments too large. Please try again without log files.");
|
|
2603
|
+
} else {
|
|
2604
|
+
throw new Error(`Failed to send feedback: ${response.status} ${response.statusText}`);
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
return await response.json();
|
|
2608
|
+
}
|
|
2609
|
+
__name(sendFeedbackToApi, "sendFeedbackToApi");
|
|
2610
|
+
async function sendFeedbackCommand(message, options) {
|
|
2611
|
+
logger.info("\nLeanMCP Feedback\n");
|
|
2612
|
+
const isAnonymous = options.anon || false;
|
|
2613
|
+
const includeLogs = options.includeLogs || false;
|
|
2614
|
+
debug4("Feedback options:", {
|
|
2615
|
+
isAnonymous,
|
|
2616
|
+
includeLogs
|
|
2617
|
+
});
|
|
2618
|
+
let feedbackMessage = message;
|
|
2619
|
+
if (!feedbackMessage && !process.stdin.isTTY) {
|
|
2620
|
+
debug4("Reading feedback message from stdin...");
|
|
2621
|
+
feedbackMessage = await new Promise((resolve) => {
|
|
2622
|
+
let data = "";
|
|
2623
|
+
process.stdin.on("data", (chunk) => {
|
|
2624
|
+
data += chunk;
|
|
2625
|
+
});
|
|
2626
|
+
process.stdin.on("end", () => {
|
|
2627
|
+
resolve(data.trim());
|
|
2628
|
+
});
|
|
2629
|
+
});
|
|
2630
|
+
}
|
|
2631
|
+
if (!feedbackMessage || feedbackMessage.trim().length === 0) {
|
|
2632
|
+
logger.error("Feedback message cannot be empty.");
|
|
2633
|
+
logger.info("Usage examples:");
|
|
2634
|
+
logger.info(' leanmcp send-feedback "Your message"');
|
|
2635
|
+
logger.gray(" leanmcp send-feedback << EOF");
|
|
2636
|
+
logger.gray(" multi-line");
|
|
2637
|
+
logger.gray(" message");
|
|
2638
|
+
logger.gray(" EOF");
|
|
2639
|
+
logger.info(' leanmcp send-feedback --anon "Anonymous feedback"');
|
|
2640
|
+
logger.info(' leanmcp send-feedback "Issue with deploy" --include-logs');
|
|
2641
|
+
process.exit(1);
|
|
2642
|
+
}
|
|
2643
|
+
if (feedbackMessage.length > 5e3) {
|
|
2644
|
+
logger.error("Feedback message is too long (max 5000 characters).");
|
|
2645
|
+
process.exit(1);
|
|
2646
|
+
}
|
|
2647
|
+
if (!isAnonymous) {
|
|
2648
|
+
const apiKey = await getApiKey();
|
|
2649
|
+
if (!apiKey) {
|
|
2650
|
+
logger.error("need to login");
|
|
2651
|
+
logger.info("Please run `leanmcp login` to authenticate, or use `--anon` for anonymous feedback.");
|
|
2652
|
+
process.exit(1);
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
let attachments = [];
|
|
2656
|
+
if (includeLogs) {
|
|
2657
|
+
const spinner2 = ora6("Collecting log files...").start();
|
|
2658
|
+
try {
|
|
2659
|
+
attachments = await collectLogFiles();
|
|
2660
|
+
spinner2.succeed(`Collected ${attachments.length} log file(s)`);
|
|
2661
|
+
if (attachments.length > 0) {
|
|
2662
|
+
logger.log("Log files:", chalk.gray);
|
|
2663
|
+
attachments.forEach((att) => {
|
|
2664
|
+
logger.log(` - ${att.name} (${(att.size / 1024).toFixed(1)} KB)`, chalk.gray);
|
|
2665
|
+
});
|
|
2666
|
+
logger.log("");
|
|
2667
|
+
} else {
|
|
2668
|
+
logger.log("No log files found.", chalk.gray);
|
|
2669
|
+
logger.log("");
|
|
2670
|
+
}
|
|
2671
|
+
} catch (error) {
|
|
2672
|
+
spinner2.fail("Failed to collect log files");
|
|
2673
|
+
debug4("Log collection error:", error);
|
|
2674
|
+
logger.warn("Continuing without log files...");
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
const spinner = ora6("Sending feedback...").start();
|
|
2678
|
+
try {
|
|
2679
|
+
const result = await sendFeedbackToApi(feedbackMessage, attachments, isAnonymous);
|
|
2680
|
+
spinner.succeed("Feedback sent successfully!");
|
|
2681
|
+
logger.success("\nThank you for your feedback!");
|
|
2682
|
+
logger.log(`Feedback ID: ${result.id}`, chalk.gray);
|
|
2683
|
+
if (isAnonymous) {
|
|
2684
|
+
logger.log("Type: Anonymous", chalk.gray);
|
|
2685
|
+
} else {
|
|
2686
|
+
logger.log("Type: Authenticated", chalk.gray);
|
|
2687
|
+
}
|
|
2688
|
+
if (attachments.length > 0) {
|
|
2689
|
+
logger.log(`Attachments: ${attachments.length}`, chalk.gray);
|
|
2690
|
+
}
|
|
2691
|
+
logger.log("\nWe appreciate your input and will review it soon.", chalk.cyan);
|
|
2692
|
+
} catch (error) {
|
|
2693
|
+
spinner.fail("Failed to send feedback");
|
|
2694
|
+
if (error instanceof Error) {
|
|
2695
|
+
logger.error(`
|
|
2696
|
+
${error.message}`);
|
|
2697
|
+
} else {
|
|
2698
|
+
logger.error("\nAn unknown error occurred.");
|
|
2699
|
+
}
|
|
2700
|
+
if (DEBUG_MODE4) {
|
|
2701
|
+
debug4("Full error:", error);
|
|
2702
|
+
}
|
|
2703
|
+
process.exit(1);
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
__name(sendFeedbackCommand, "sendFeedbackCommand");
|
|
2707
|
+
|
|
2708
|
+
// src/commands/projects.ts
|
|
2709
|
+
import ora7 from "ora";
|
|
2409
2710
|
var API_ENDPOINT = "/api/projects";
|
|
2410
2711
|
async function projectsListCommand() {
|
|
2411
2712
|
const apiKey = await getApiKey();
|
|
@@ -2414,7 +2715,7 @@ async function projectsListCommand() {
|
|
|
2414
2715
|
logger.gray("Run `leanmcp login` first to authenticate.\n");
|
|
2415
2716
|
process.exit(1);
|
|
2416
2717
|
}
|
|
2417
|
-
const spinner =
|
|
2718
|
+
const spinner = ora7("Fetching projects...").start();
|
|
2418
2719
|
try {
|
|
2419
2720
|
const apiUrl = await getApiUrl();
|
|
2420
2721
|
const response = await fetch(`${apiUrl}${API_ENDPOINT}`, {
|
|
@@ -2459,7 +2760,7 @@ async function projectsGetCommand(projectId) {
|
|
|
2459
2760
|
logger.gray("Run `leanmcp login` first to authenticate.\n");
|
|
2460
2761
|
process.exit(1);
|
|
2461
2762
|
}
|
|
2462
|
-
const spinner =
|
|
2763
|
+
const spinner = ora7("Fetching project...").start();
|
|
2463
2764
|
try {
|
|
2464
2765
|
const apiUrl = await getApiUrl();
|
|
2465
2766
|
const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
|
|
@@ -2514,7 +2815,7 @@ async function projectsDeleteCommand(projectId, options = {}) {
|
|
|
2514
2815
|
return;
|
|
2515
2816
|
}
|
|
2516
2817
|
}
|
|
2517
|
-
const spinner =
|
|
2818
|
+
const spinner = ora7("Deleting project...").start();
|
|
2518
2819
|
try {
|
|
2519
2820
|
const apiUrl = await getApiUrl();
|
|
2520
2821
|
const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
|
|
@@ -2541,50 +2842,50 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
2541
2842
|
__name(projectsDeleteCommand, "projectsDeleteCommand");
|
|
2542
2843
|
|
|
2543
2844
|
// src/commands/env.ts
|
|
2544
|
-
import
|
|
2545
|
-
import
|
|
2546
|
-
import
|
|
2845
|
+
import ora8 from "ora";
|
|
2846
|
+
import path12 from "path";
|
|
2847
|
+
import fs12 from "fs-extra";
|
|
2547
2848
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
2548
|
-
var
|
|
2849
|
+
var DEBUG_MODE5 = false;
|
|
2549
2850
|
function setEnvDebugMode(enabled) {
|
|
2550
|
-
|
|
2851
|
+
DEBUG_MODE5 = enabled;
|
|
2551
2852
|
}
|
|
2552
2853
|
__name(setEnvDebugMode, "setEnvDebugMode");
|
|
2553
|
-
function
|
|
2554
|
-
if (
|
|
2854
|
+
function debug5(message, ...args) {
|
|
2855
|
+
if (DEBUG_MODE5) {
|
|
2555
2856
|
console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
|
|
2556
2857
|
}
|
|
2557
2858
|
}
|
|
2558
|
-
__name(
|
|
2859
|
+
__name(debug5, "debug");
|
|
2559
2860
|
async function debugFetch2(url, options = {}) {
|
|
2560
|
-
|
|
2861
|
+
debug5(`HTTP ${options.method || "GET"} ${url}`);
|
|
2561
2862
|
if (options.body && typeof options.body === "string") {
|
|
2562
2863
|
try {
|
|
2563
2864
|
const body = JSON.parse(options.body);
|
|
2564
|
-
|
|
2865
|
+
debug5("Request body:", JSON.stringify(body, null, 2));
|
|
2565
2866
|
} catch {
|
|
2566
|
-
|
|
2867
|
+
debug5("Request body:", options.body);
|
|
2567
2868
|
}
|
|
2568
2869
|
}
|
|
2569
2870
|
const startTime = Date.now();
|
|
2570
2871
|
const response = await fetch(url, options);
|
|
2571
2872
|
const duration = Date.now() - startTime;
|
|
2572
|
-
|
|
2873
|
+
debug5(`Response: ${response.status} ${response.statusText} (${duration}ms)`);
|
|
2573
2874
|
return response;
|
|
2574
2875
|
}
|
|
2575
2876
|
__name(debugFetch2, "debugFetch");
|
|
2576
2877
|
var LEANMCP_CONFIG_DIR2 = ".leanmcp";
|
|
2577
2878
|
var LEANMCP_CONFIG_FILE2 = "config.json";
|
|
2578
2879
|
async function readLeanMCPConfig2(projectPath) {
|
|
2579
|
-
const configPath =
|
|
2880
|
+
const configPath = path12.join(projectPath, LEANMCP_CONFIG_DIR2, LEANMCP_CONFIG_FILE2);
|
|
2580
2881
|
try {
|
|
2581
|
-
if (await
|
|
2582
|
-
const config = await
|
|
2583
|
-
|
|
2882
|
+
if (await fs12.pathExists(configPath)) {
|
|
2883
|
+
const config = await fs12.readJSON(configPath);
|
|
2884
|
+
debug5("Found existing .leanmcp config:", config);
|
|
2584
2885
|
return config;
|
|
2585
2886
|
}
|
|
2586
2887
|
} catch (e) {
|
|
2587
|
-
|
|
2888
|
+
debug5("Could not read .leanmcp config:", e);
|
|
2588
2889
|
}
|
|
2589
2890
|
return null;
|
|
2590
2891
|
}
|
|
@@ -2597,7 +2898,7 @@ async function getDeploymentContext(folderPath) {
|
|
|
2597
2898
|
return null;
|
|
2598
2899
|
}
|
|
2599
2900
|
const apiUrl = await getApiUrl();
|
|
2600
|
-
const absolutePath =
|
|
2901
|
+
const absolutePath = path12.resolve(process.cwd(), folderPath);
|
|
2601
2902
|
const config = await readLeanMCPConfig2(absolutePath);
|
|
2602
2903
|
if (!config) {
|
|
2603
2904
|
logger.error("No deployment found.");
|
|
@@ -2624,7 +2925,7 @@ async function envListCommand(folderPath, options = {}) {
|
|
|
2624
2925
|
process.exit(1);
|
|
2625
2926
|
}
|
|
2626
2927
|
const { apiKey, apiUrl, config } = context;
|
|
2627
|
-
const spinner =
|
|
2928
|
+
const spinner = ora8("Fetching environment variables...").start();
|
|
2628
2929
|
try {
|
|
2629
2930
|
const reveal = options.reveal ? "?reveal=true" : "";
|
|
2630
2931
|
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env${reveal}`, {
|
|
@@ -2665,7 +2966,7 @@ async function envSetCommand(keyValue, folderPath, options = {}) {
|
|
|
2665
2966
|
const { apiKey, apiUrl, config } = context;
|
|
2666
2967
|
let variables = {};
|
|
2667
2968
|
if (options.file) {
|
|
2668
|
-
const spinner2 =
|
|
2969
|
+
const spinner2 = ora8(`Loading from ${options.file}...`).start();
|
|
2669
2970
|
try {
|
|
2670
2971
|
variables = await loadEnvFile(options.file);
|
|
2671
2972
|
spinner2.succeed(`Loaded ${Object.keys(variables).length} variable(s) from ${options.file}`);
|
|
@@ -2706,7 +3007,7 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
2706
3007
|
logger.warn("No variables to set.\n");
|
|
2707
3008
|
return;
|
|
2708
3009
|
}
|
|
2709
|
-
const spinner =
|
|
3010
|
+
const spinner = ora8("Updating environment variables...").start();
|
|
2710
3011
|
try {
|
|
2711
3012
|
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env`, {
|
|
2712
3013
|
method: "PUT",
|
|
@@ -2791,7 +3092,7 @@ async function envRemoveCommand(key, folderPath, options = {}) {
|
|
|
2791
3092
|
return;
|
|
2792
3093
|
}
|
|
2793
3094
|
}
|
|
2794
|
-
const spinner =
|
|
3095
|
+
const spinner = ora8(`Removing ${key}...`).start();
|
|
2795
3096
|
try {
|
|
2796
3097
|
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env/${key}`, {
|
|
2797
3098
|
method: "DELETE",
|
|
@@ -2820,7 +3121,7 @@ async function envPullCommand(folderPath, options = {}) {
|
|
|
2820
3121
|
}
|
|
2821
3122
|
const { apiKey, apiUrl, config } = context;
|
|
2822
3123
|
const outputFile = options.file || ".env.remote";
|
|
2823
|
-
const spinner =
|
|
3124
|
+
const spinner = ora8("Fetching environment variables...").start();
|
|
2824
3125
|
try {
|
|
2825
3126
|
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env?reveal=true`, {
|
|
2826
3127
|
headers: {
|
|
@@ -2859,7 +3160,7 @@ async function envPushCommand(folderPath, options = {}) {
|
|
|
2859
3160
|
}
|
|
2860
3161
|
const { apiKey, apiUrl, config } = context;
|
|
2861
3162
|
const inputFile = options.file || ".env";
|
|
2862
|
-
const loadSpinner =
|
|
3163
|
+
const loadSpinner = ora8(`Loading from ${inputFile}...`).start();
|
|
2863
3164
|
let variables;
|
|
2864
3165
|
try {
|
|
2865
3166
|
variables = await loadEnvFile(inputFile);
|
|
@@ -2886,7 +3187,7 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
2886
3187
|
return;
|
|
2887
3188
|
}
|
|
2888
3189
|
}
|
|
2889
|
-
const pushSpinner =
|
|
3190
|
+
const pushSpinner = ora8("Pushing environment variables...").start();
|
|
2890
3191
|
try {
|
|
2891
3192
|
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env`, {
|
|
2892
3193
|
method: "PUT",
|
|
@@ -3733,30 +4034,30 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
|
|
|
3733
4034
|
projectName,
|
|
3734
4035
|
...options
|
|
3735
4036
|
});
|
|
3736
|
-
const spinner =
|
|
3737
|
-
const targetDir =
|
|
3738
|
-
if (
|
|
4037
|
+
const spinner = ora9(`Creating project ${projectName}...`).start();
|
|
4038
|
+
const targetDir = path13.join(process.cwd(), projectName);
|
|
4039
|
+
if (fs13.existsSync(targetDir)) {
|
|
3739
4040
|
spinner.fail(`Folder ${projectName} already exists.`);
|
|
3740
4041
|
process.exit(1);
|
|
3741
4042
|
}
|
|
3742
|
-
await
|
|
4043
|
+
await fs13.mkdirp(targetDir);
|
|
3743
4044
|
const isPython = options.python === true;
|
|
3744
4045
|
if (isPython) {
|
|
3745
4046
|
const requirements = getPythonRequirementsTemplate();
|
|
3746
|
-
await
|
|
4047
|
+
await fs13.writeFile(path13.join(targetDir, "requirements.txt"), requirements);
|
|
3747
4048
|
const mainPy = getPythonMainTemplate(projectName);
|
|
3748
|
-
await
|
|
3749
|
-
await
|
|
4049
|
+
await fs13.writeFile(path13.join(targetDir, "main.py"), mainPy);
|
|
4050
|
+
await fs13.writeFile(path13.join(targetDir, ".gitignore"), pythonGitignoreTemplate);
|
|
3750
4051
|
const env = `# Server Configuration
|
|
3751
4052
|
PORT=3001
|
|
3752
4053
|
|
|
3753
4054
|
# Add your environment variables here
|
|
3754
4055
|
`;
|
|
3755
|
-
await
|
|
4056
|
+
await fs13.writeFile(path13.join(targetDir, ".env"), env);
|
|
3756
4057
|
const readme = getPythonReadmeTemplate(projectName);
|
|
3757
|
-
await
|
|
4058
|
+
await fs13.writeFile(path13.join(targetDir, "README.md"), readme);
|
|
3758
4059
|
} else {
|
|
3759
|
-
await
|
|
4060
|
+
await fs13.mkdirp(path13.join(targetDir, "mcp", "example"));
|
|
3760
4061
|
const pkg2 = {
|
|
3761
4062
|
name: projectName,
|
|
3762
4063
|
version: "1.0.0",
|
|
@@ -3792,7 +4093,7 @@ PORT=3001
|
|
|
3792
4093
|
typescript: "^5.6.3"
|
|
3793
4094
|
}
|
|
3794
4095
|
};
|
|
3795
|
-
await
|
|
4096
|
+
await fs13.writeJSON(path13.join(targetDir, "package.json"), pkg2, {
|
|
3796
4097
|
spaces: 2
|
|
3797
4098
|
});
|
|
3798
4099
|
const tsconfig = {
|
|
@@ -3815,15 +4116,15 @@ PORT=3001
|
|
|
3815
4116
|
"dist"
|
|
3816
4117
|
]
|
|
3817
4118
|
};
|
|
3818
|
-
await
|
|
4119
|
+
await fs13.writeJSON(path13.join(targetDir, "tsconfig.json"), tsconfig, {
|
|
3819
4120
|
spaces: 2
|
|
3820
4121
|
});
|
|
3821
4122
|
const dashboardLine = options.dashboard === false ? `
|
|
3822
4123
|
dashboard: false, // Dashboard disabled via --no-dashboard` : "";
|
|
3823
4124
|
const mainTs = getMainTsTemplate(projectName, dashboardLine);
|
|
3824
|
-
await
|
|
4125
|
+
await fs13.writeFile(path13.join(targetDir, "main.ts"), mainTs);
|
|
3825
4126
|
const exampleServiceTs = getExampleServiceTemplate(projectName);
|
|
3826
|
-
await
|
|
4127
|
+
await fs13.writeFile(path13.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
|
|
3827
4128
|
const gitignore = gitignoreTemplate;
|
|
3828
4129
|
const env = `# Server Configuration
|
|
3829
4130
|
PORT=3001
|
|
@@ -3831,10 +4132,10 @@ NODE_ENV=development
|
|
|
3831
4132
|
|
|
3832
4133
|
# Add your environment variables here
|
|
3833
4134
|
`;
|
|
3834
|
-
await
|
|
3835
|
-
await
|
|
4135
|
+
await fs13.writeFile(path13.join(targetDir, ".gitignore"), gitignore);
|
|
4136
|
+
await fs13.writeFile(path13.join(targetDir, ".env"), env);
|
|
3836
4137
|
const readme = getReadmeTemplate(projectName);
|
|
3837
|
-
await
|
|
4138
|
+
await fs13.writeFile(path13.join(targetDir, "README.md"), readme);
|
|
3838
4139
|
}
|
|
3839
4140
|
spinner.succeed(`Project ${projectName} created!`);
|
|
3840
4141
|
logger.log("\nSuccess! Your MCP server is ready.\n", chalk.green);
|
|
@@ -3881,7 +4182,7 @@ NODE_ENV=development
|
|
|
3881
4182
|
default: true
|
|
3882
4183
|
});
|
|
3883
4184
|
if (shouldInstall) {
|
|
3884
|
-
const installSpinner =
|
|
4185
|
+
const installSpinner = ora9("Installing dependencies...").start();
|
|
3885
4186
|
try {
|
|
3886
4187
|
await new Promise((resolve, reject) => {
|
|
3887
4188
|
const npmInstall = spawn4("npm", [
|
|
@@ -3954,24 +4255,33 @@ NODE_ENV=development
|
|
|
3954
4255
|
logger.log("To deploy to LeanMCP cloud:", chalk.cyan);
|
|
3955
4256
|
logger.log(` cd ${projectName}`, chalk.gray);
|
|
3956
4257
|
logger.log(` leanmcp deploy .`, chalk.gray);
|
|
4258
|
+
logger.log("\nSend us feedback:", chalk.cyan);
|
|
4259
|
+
logger.log(' leanmcp send-feedback "Great tool!"\n', chalk.gray);
|
|
3957
4260
|
}
|
|
3958
4261
|
});
|
|
4262
|
+
program.command("send-feedback [message]").description("Send feedback to the LeanMCP team").option("--anon", "Send feedback anonymously").option("--include-logs", "Include local log files with feedback").action(async (message, options) => {
|
|
4263
|
+
trackCommand("send-feedback", {
|
|
4264
|
+
hasMessage: !!message,
|
|
4265
|
+
...options
|
|
4266
|
+
});
|
|
4267
|
+
await sendFeedbackCommand(message, options);
|
|
4268
|
+
});
|
|
3959
4269
|
program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
|
|
3960
4270
|
const cwd = process.cwd();
|
|
3961
|
-
const mcpDir =
|
|
3962
|
-
if (!
|
|
4271
|
+
const mcpDir = path13.join(cwd, "mcp");
|
|
4272
|
+
if (!fs13.existsSync(path13.join(cwd, "main.ts"))) {
|
|
3963
4273
|
logger.log("ERROR: Not a LeanMCP project (main.ts missing).", chalk.red);
|
|
3964
4274
|
process.exit(1);
|
|
3965
4275
|
}
|
|
3966
|
-
const serviceDir =
|
|
3967
|
-
const serviceFile =
|
|
3968
|
-
if (
|
|
4276
|
+
const serviceDir = path13.join(mcpDir, serviceName);
|
|
4277
|
+
const serviceFile = path13.join(serviceDir, "index.ts");
|
|
4278
|
+
if (fs13.existsSync(serviceDir)) {
|
|
3969
4279
|
logger.log(`ERROR: Service ${serviceName} already exists.`, chalk.red);
|
|
3970
4280
|
process.exit(1);
|
|
3971
4281
|
}
|
|
3972
|
-
await
|
|
4282
|
+
await fs13.mkdirp(serviceDir);
|
|
3973
4283
|
const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
|
|
3974
|
-
await
|
|
4284
|
+
await fs13.writeFile(serviceFile, indexTs);
|
|
3975
4285
|
logger.log(`\\nCreated new service: ${chalk.bold(serviceName)}`, chalk.green);
|
|
3976
4286
|
logger.log(` File: mcp/${serviceName}/index.ts`, chalk.gray);
|
|
3977
4287
|
logger.log(` Tool: greet`, chalk.gray);
|