@leanmcp/cli 0.5.6 → 0.5.7
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 +315 -57
- 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 fs12 from "fs-extra";
|
|
7
|
+
import path12 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";
|
|
@@ -2404,8 +2404,257 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
2404
2404
|
}
|
|
2405
2405
|
__name(deployCommand, "deployCommand");
|
|
2406
2406
|
|
|
2407
|
-
// src/commands/
|
|
2407
|
+
// src/commands/feedback.ts
|
|
2408
2408
|
import ora6 from "ora";
|
|
2409
|
+
import fs10 from "fs-extra";
|
|
2410
|
+
import path10 from "path";
|
|
2411
|
+
import os4 from "os";
|
|
2412
|
+
var DEBUG_MODE4 = false;
|
|
2413
|
+
function debug4(message, ...args) {
|
|
2414
|
+
if (DEBUG_MODE4) {
|
|
2415
|
+
console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
__name(debug4, "debug");
|
|
2419
|
+
async function readFileAsBase64(filePath) {
|
|
2420
|
+
try {
|
|
2421
|
+
const absolutePath = path10.resolve(filePath);
|
|
2422
|
+
const stats = await fs10.stat(absolutePath);
|
|
2423
|
+
if (!stats.isFile()) {
|
|
2424
|
+
throw new Error(`${filePath} is not a file`);
|
|
2425
|
+
}
|
|
2426
|
+
const content = await fs10.readFile(absolutePath, "base64");
|
|
2427
|
+
const ext = path10.extname(absolutePath).toLowerCase();
|
|
2428
|
+
let mimeType = "application/octet-stream";
|
|
2429
|
+
switch (ext) {
|
|
2430
|
+
case ".txt":
|
|
2431
|
+
case ".log":
|
|
2432
|
+
mimeType = "text/plain";
|
|
2433
|
+
break;
|
|
2434
|
+
case ".json":
|
|
2435
|
+
mimeType = "application/json";
|
|
2436
|
+
break;
|
|
2437
|
+
case ".js":
|
|
2438
|
+
mimeType = "application/javascript";
|
|
2439
|
+
break;
|
|
2440
|
+
case ".ts":
|
|
2441
|
+
mimeType = "application/typescript";
|
|
2442
|
+
break;
|
|
2443
|
+
case ".md":
|
|
2444
|
+
mimeType = "text/markdown";
|
|
2445
|
+
break;
|
|
2446
|
+
}
|
|
2447
|
+
return {
|
|
2448
|
+
content,
|
|
2449
|
+
size: stats.size,
|
|
2450
|
+
type: mimeType
|
|
2451
|
+
};
|
|
2452
|
+
} catch (error) {
|
|
2453
|
+
throw new Error(`Failed to read file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
__name(readFileAsBase64, "readFileAsBase64");
|
|
2457
|
+
async function collectLogFiles() {
|
|
2458
|
+
const attachments = [];
|
|
2459
|
+
const logLocations = [
|
|
2460
|
+
path10.join(os4.homedir(), ".leanmcp", "logs"),
|
|
2461
|
+
path10.join(process.cwd(), "logs"),
|
|
2462
|
+
path10.join(process.cwd(), ".leanmcp", "logs")
|
|
2463
|
+
];
|
|
2464
|
+
for (const logDir of logLocations) {
|
|
2465
|
+
try {
|
|
2466
|
+
if (await fs10.pathExists(logDir)) {
|
|
2467
|
+
const files = await fs10.readdir(logDir);
|
|
2468
|
+
for (const file of files) {
|
|
2469
|
+
const filePath = path10.join(logDir, file);
|
|
2470
|
+
try {
|
|
2471
|
+
const fileData = await readFileAsBase64(filePath);
|
|
2472
|
+
attachments.push({
|
|
2473
|
+
name: file,
|
|
2474
|
+
...fileData
|
|
2475
|
+
});
|
|
2476
|
+
} catch (error) {
|
|
2477
|
+
debug4(`Failed to read log file ${filePath}: ${error}`);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
} catch (error) {
|
|
2482
|
+
debug4(`Failed to scan log directory ${logDir}: ${error}`);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
const npmDebugLog = path10.join(os4.homedir(), ".npm", "_logs");
|
|
2486
|
+
try {
|
|
2487
|
+
if (await fs10.pathExists(npmDebugLog)) {
|
|
2488
|
+
const logFiles = await fs10.readdir(npmDebugLog);
|
|
2489
|
+
const latestLog = logFiles.filter((file) => file.endsWith(".log")).sort().pop();
|
|
2490
|
+
if (latestLog) {
|
|
2491
|
+
const filePath = path10.join(npmDebugLog, latestLog);
|
|
2492
|
+
try {
|
|
2493
|
+
const fileData = await readFileAsBase64(filePath);
|
|
2494
|
+
attachments.push({
|
|
2495
|
+
name: `npm-${latestLog}`,
|
|
2496
|
+
...fileData
|
|
2497
|
+
});
|
|
2498
|
+
} catch (error) {
|
|
2499
|
+
debug4(`Failed to read npm debug log: ${error}`);
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
} catch (error) {
|
|
2504
|
+
debug4(`Failed to collect npm debug logs: ${error}`);
|
|
2505
|
+
}
|
|
2506
|
+
return attachments;
|
|
2507
|
+
}
|
|
2508
|
+
__name(collectLogFiles, "collectLogFiles");
|
|
2509
|
+
async function sendFeedbackToApi(message, attachments = [], isAnonymous = false) {
|
|
2510
|
+
const apiUrl = await getApiUrl();
|
|
2511
|
+
const endpoint = isAnonymous ? "/feedback/anonymous" : "/feedback";
|
|
2512
|
+
const url = `${apiUrl}${endpoint}`;
|
|
2513
|
+
debug4("API URL:", apiUrl);
|
|
2514
|
+
debug4("Endpoint:", endpoint);
|
|
2515
|
+
debug4("Message length:", message.length);
|
|
2516
|
+
debug4("Attachments count:", attachments.length);
|
|
2517
|
+
const headers = {
|
|
2518
|
+
"Content-Type": "application/json"
|
|
2519
|
+
};
|
|
2520
|
+
if (!isAnonymous) {
|
|
2521
|
+
const apiKey = await getApiKey();
|
|
2522
|
+
if (!apiKey) {
|
|
2523
|
+
throw new Error("Not authenticated. Please run `leanmcp login` first.");
|
|
2524
|
+
}
|
|
2525
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
2526
|
+
}
|
|
2527
|
+
const payload = {
|
|
2528
|
+
message,
|
|
2529
|
+
attachments: attachments.map((att) => ({
|
|
2530
|
+
name: att.name,
|
|
2531
|
+
content: att.content,
|
|
2532
|
+
size: att.size,
|
|
2533
|
+
type: att.type
|
|
2534
|
+
}))
|
|
2535
|
+
};
|
|
2536
|
+
debug4("Sending feedback request...");
|
|
2537
|
+
const response = await fetch(url, {
|
|
2538
|
+
method: "POST",
|
|
2539
|
+
headers,
|
|
2540
|
+
body: JSON.stringify(payload)
|
|
2541
|
+
});
|
|
2542
|
+
debug4("Response status:", response.status);
|
|
2543
|
+
debug4("Response ok:", response.ok);
|
|
2544
|
+
if (!response.ok) {
|
|
2545
|
+
const errorText = await response.text();
|
|
2546
|
+
debug4("Error response:", errorText);
|
|
2547
|
+
if (response.status === 401) {
|
|
2548
|
+
throw new Error("Authentication failed. Please run `leanmcp login` to re-authenticate.");
|
|
2549
|
+
} else if (response.status === 413) {
|
|
2550
|
+
throw new Error("Attachments too large. Please try again without log files.");
|
|
2551
|
+
} else {
|
|
2552
|
+
throw new Error(`Failed to send feedback: ${response.status} ${response.statusText}`);
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
return await response.json();
|
|
2556
|
+
}
|
|
2557
|
+
__name(sendFeedbackToApi, "sendFeedbackToApi");
|
|
2558
|
+
async function sendFeedbackCommand(message, options) {
|
|
2559
|
+
logger.info("\nLeanMCP Feedback\n");
|
|
2560
|
+
const isAnonymous = options.anon || false;
|
|
2561
|
+
const includeLogs = options.includeLogs || false;
|
|
2562
|
+
debug4("Feedback options:", {
|
|
2563
|
+
isAnonymous,
|
|
2564
|
+
includeLogs
|
|
2565
|
+
});
|
|
2566
|
+
let feedbackMessage = message;
|
|
2567
|
+
if (!feedbackMessage && !process.stdin.isTTY) {
|
|
2568
|
+
debug4("Reading feedback message from stdin...");
|
|
2569
|
+
feedbackMessage = await new Promise((resolve) => {
|
|
2570
|
+
let data = "";
|
|
2571
|
+
process.stdin.on("data", (chunk) => {
|
|
2572
|
+
data += chunk;
|
|
2573
|
+
});
|
|
2574
|
+
process.stdin.on("end", () => {
|
|
2575
|
+
resolve(data.trim());
|
|
2576
|
+
});
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
if (!feedbackMessage || feedbackMessage.trim().length === 0) {
|
|
2580
|
+
logger.error("Feedback message cannot be empty.");
|
|
2581
|
+
logger.info("Usage examples:");
|
|
2582
|
+
logger.info(' leanmcp send-feedback "Your message"');
|
|
2583
|
+
logger.gray(" leanmcp send-feedback << EOF");
|
|
2584
|
+
logger.gray(" multi-line");
|
|
2585
|
+
logger.gray(" message");
|
|
2586
|
+
logger.gray(" EOF");
|
|
2587
|
+
logger.info(' leanmcp send-feedback --anon "Anonymous feedback"');
|
|
2588
|
+
logger.info(' leanmcp send-feedback "Issue with deploy" --include-logs');
|
|
2589
|
+
process.exit(1);
|
|
2590
|
+
}
|
|
2591
|
+
if (feedbackMessage.length > 5e3) {
|
|
2592
|
+
logger.error("Feedback message is too long (max 5000 characters).");
|
|
2593
|
+
process.exit(1);
|
|
2594
|
+
}
|
|
2595
|
+
if (!isAnonymous) {
|
|
2596
|
+
const apiKey = await getApiKey();
|
|
2597
|
+
if (!apiKey) {
|
|
2598
|
+
logger.error("need to login");
|
|
2599
|
+
logger.info("Please run `leanmcp login` to authenticate, or use `--anon` for anonymous feedback.");
|
|
2600
|
+
process.exit(1);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
let attachments = [];
|
|
2604
|
+
if (includeLogs) {
|
|
2605
|
+
const spinner2 = ora6("Collecting log files...").start();
|
|
2606
|
+
try {
|
|
2607
|
+
attachments = await collectLogFiles();
|
|
2608
|
+
spinner2.succeed(`Collected ${attachments.length} log file(s)`);
|
|
2609
|
+
if (attachments.length > 0) {
|
|
2610
|
+
logger.log("Log files:", chalk.gray);
|
|
2611
|
+
attachments.forEach((att) => {
|
|
2612
|
+
logger.log(` - ${att.name} (${(att.size / 1024).toFixed(1)} KB)`, chalk.gray);
|
|
2613
|
+
});
|
|
2614
|
+
logger.log("");
|
|
2615
|
+
} else {
|
|
2616
|
+
logger.log("No log files found.", chalk.gray);
|
|
2617
|
+
logger.log("");
|
|
2618
|
+
}
|
|
2619
|
+
} catch (error) {
|
|
2620
|
+
spinner2.fail("Failed to collect log files");
|
|
2621
|
+
debug4("Log collection error:", error);
|
|
2622
|
+
logger.warn("Continuing without log files...");
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
const spinner = ora6("Sending feedback...").start();
|
|
2626
|
+
try {
|
|
2627
|
+
const result = await sendFeedbackToApi(feedbackMessage, attachments, isAnonymous);
|
|
2628
|
+
spinner.succeed("Feedback sent successfully!");
|
|
2629
|
+
logger.success("\nThank you for your feedback!");
|
|
2630
|
+
logger.log(`Feedback ID: ${result.id}`, chalk.gray);
|
|
2631
|
+
if (isAnonymous) {
|
|
2632
|
+
logger.log("Type: Anonymous", chalk.gray);
|
|
2633
|
+
} else {
|
|
2634
|
+
logger.log("Type: Authenticated", chalk.gray);
|
|
2635
|
+
}
|
|
2636
|
+
if (attachments.length > 0) {
|
|
2637
|
+
logger.log(`Attachments: ${attachments.length}`, chalk.gray);
|
|
2638
|
+
}
|
|
2639
|
+
logger.log("\nWe appreciate your input and will review it soon.", chalk.cyan);
|
|
2640
|
+
} catch (error) {
|
|
2641
|
+
spinner.fail("Failed to send feedback");
|
|
2642
|
+
if (error instanceof Error) {
|
|
2643
|
+
logger.error(`
|
|
2644
|
+
${error.message}`);
|
|
2645
|
+
} else {
|
|
2646
|
+
logger.error("\nAn unknown error occurred.");
|
|
2647
|
+
}
|
|
2648
|
+
if (DEBUG_MODE4) {
|
|
2649
|
+
debug4("Full error:", error);
|
|
2650
|
+
}
|
|
2651
|
+
process.exit(1);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
__name(sendFeedbackCommand, "sendFeedbackCommand");
|
|
2655
|
+
|
|
2656
|
+
// src/commands/projects.ts
|
|
2657
|
+
import ora7 from "ora";
|
|
2409
2658
|
var API_ENDPOINT = "/api/projects";
|
|
2410
2659
|
async function projectsListCommand() {
|
|
2411
2660
|
const apiKey = await getApiKey();
|
|
@@ -2414,7 +2663,7 @@ async function projectsListCommand() {
|
|
|
2414
2663
|
logger.gray("Run `leanmcp login` first to authenticate.\n");
|
|
2415
2664
|
process.exit(1);
|
|
2416
2665
|
}
|
|
2417
|
-
const spinner =
|
|
2666
|
+
const spinner = ora7("Fetching projects...").start();
|
|
2418
2667
|
try {
|
|
2419
2668
|
const apiUrl = await getApiUrl();
|
|
2420
2669
|
const response = await fetch(`${apiUrl}${API_ENDPOINT}`, {
|
|
@@ -2459,7 +2708,7 @@ async function projectsGetCommand(projectId) {
|
|
|
2459
2708
|
logger.gray("Run `leanmcp login` first to authenticate.\n");
|
|
2460
2709
|
process.exit(1);
|
|
2461
2710
|
}
|
|
2462
|
-
const spinner =
|
|
2711
|
+
const spinner = ora7("Fetching project...").start();
|
|
2463
2712
|
try {
|
|
2464
2713
|
const apiUrl = await getApiUrl();
|
|
2465
2714
|
const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
|
|
@@ -2514,7 +2763,7 @@ async function projectsDeleteCommand(projectId, options = {}) {
|
|
|
2514
2763
|
return;
|
|
2515
2764
|
}
|
|
2516
2765
|
}
|
|
2517
|
-
const spinner =
|
|
2766
|
+
const spinner = ora7("Deleting project...").start();
|
|
2518
2767
|
try {
|
|
2519
2768
|
const apiUrl = await getApiUrl();
|
|
2520
2769
|
const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
|
|
@@ -2541,50 +2790,50 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
2541
2790
|
__name(projectsDeleteCommand, "projectsDeleteCommand");
|
|
2542
2791
|
|
|
2543
2792
|
// src/commands/env.ts
|
|
2544
|
-
import
|
|
2545
|
-
import
|
|
2546
|
-
import
|
|
2793
|
+
import ora8 from "ora";
|
|
2794
|
+
import path11 from "path";
|
|
2795
|
+
import fs11 from "fs-extra";
|
|
2547
2796
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
2548
|
-
var
|
|
2797
|
+
var DEBUG_MODE5 = false;
|
|
2549
2798
|
function setEnvDebugMode(enabled) {
|
|
2550
|
-
|
|
2799
|
+
DEBUG_MODE5 = enabled;
|
|
2551
2800
|
}
|
|
2552
2801
|
__name(setEnvDebugMode, "setEnvDebugMode");
|
|
2553
|
-
function
|
|
2554
|
-
if (
|
|
2802
|
+
function debug5(message, ...args) {
|
|
2803
|
+
if (DEBUG_MODE5) {
|
|
2555
2804
|
console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
|
|
2556
2805
|
}
|
|
2557
2806
|
}
|
|
2558
|
-
__name(
|
|
2807
|
+
__name(debug5, "debug");
|
|
2559
2808
|
async function debugFetch2(url, options = {}) {
|
|
2560
|
-
|
|
2809
|
+
debug5(`HTTP ${options.method || "GET"} ${url}`);
|
|
2561
2810
|
if (options.body && typeof options.body === "string") {
|
|
2562
2811
|
try {
|
|
2563
2812
|
const body = JSON.parse(options.body);
|
|
2564
|
-
|
|
2813
|
+
debug5("Request body:", JSON.stringify(body, null, 2));
|
|
2565
2814
|
} catch {
|
|
2566
|
-
|
|
2815
|
+
debug5("Request body:", options.body);
|
|
2567
2816
|
}
|
|
2568
2817
|
}
|
|
2569
2818
|
const startTime = Date.now();
|
|
2570
2819
|
const response = await fetch(url, options);
|
|
2571
2820
|
const duration = Date.now() - startTime;
|
|
2572
|
-
|
|
2821
|
+
debug5(`Response: ${response.status} ${response.statusText} (${duration}ms)`);
|
|
2573
2822
|
return response;
|
|
2574
2823
|
}
|
|
2575
2824
|
__name(debugFetch2, "debugFetch");
|
|
2576
2825
|
var LEANMCP_CONFIG_DIR2 = ".leanmcp";
|
|
2577
2826
|
var LEANMCP_CONFIG_FILE2 = "config.json";
|
|
2578
2827
|
async function readLeanMCPConfig2(projectPath) {
|
|
2579
|
-
const configPath =
|
|
2828
|
+
const configPath = path11.join(projectPath, LEANMCP_CONFIG_DIR2, LEANMCP_CONFIG_FILE2);
|
|
2580
2829
|
try {
|
|
2581
|
-
if (await
|
|
2582
|
-
const config = await
|
|
2583
|
-
|
|
2830
|
+
if (await fs11.pathExists(configPath)) {
|
|
2831
|
+
const config = await fs11.readJSON(configPath);
|
|
2832
|
+
debug5("Found existing .leanmcp config:", config);
|
|
2584
2833
|
return config;
|
|
2585
2834
|
}
|
|
2586
2835
|
} catch (e) {
|
|
2587
|
-
|
|
2836
|
+
debug5("Could not read .leanmcp config:", e);
|
|
2588
2837
|
}
|
|
2589
2838
|
return null;
|
|
2590
2839
|
}
|
|
@@ -2597,7 +2846,7 @@ async function getDeploymentContext(folderPath) {
|
|
|
2597
2846
|
return null;
|
|
2598
2847
|
}
|
|
2599
2848
|
const apiUrl = await getApiUrl();
|
|
2600
|
-
const absolutePath =
|
|
2849
|
+
const absolutePath = path11.resolve(process.cwd(), folderPath);
|
|
2601
2850
|
const config = await readLeanMCPConfig2(absolutePath);
|
|
2602
2851
|
if (!config) {
|
|
2603
2852
|
logger.error("No deployment found.");
|
|
@@ -2624,7 +2873,7 @@ async function envListCommand(folderPath, options = {}) {
|
|
|
2624
2873
|
process.exit(1);
|
|
2625
2874
|
}
|
|
2626
2875
|
const { apiKey, apiUrl, config } = context;
|
|
2627
|
-
const spinner =
|
|
2876
|
+
const spinner = ora8("Fetching environment variables...").start();
|
|
2628
2877
|
try {
|
|
2629
2878
|
const reveal = options.reveal ? "?reveal=true" : "";
|
|
2630
2879
|
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env${reveal}`, {
|
|
@@ -2665,7 +2914,7 @@ async function envSetCommand(keyValue, folderPath, options = {}) {
|
|
|
2665
2914
|
const { apiKey, apiUrl, config } = context;
|
|
2666
2915
|
let variables = {};
|
|
2667
2916
|
if (options.file) {
|
|
2668
|
-
const spinner2 =
|
|
2917
|
+
const spinner2 = ora8(`Loading from ${options.file}...`).start();
|
|
2669
2918
|
try {
|
|
2670
2919
|
variables = await loadEnvFile(options.file);
|
|
2671
2920
|
spinner2.succeed(`Loaded ${Object.keys(variables).length} variable(s) from ${options.file}`);
|
|
@@ -2706,7 +2955,7 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
2706
2955
|
logger.warn("No variables to set.\n");
|
|
2707
2956
|
return;
|
|
2708
2957
|
}
|
|
2709
|
-
const spinner =
|
|
2958
|
+
const spinner = ora8("Updating environment variables...").start();
|
|
2710
2959
|
try {
|
|
2711
2960
|
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env`, {
|
|
2712
2961
|
method: "PUT",
|
|
@@ -2791,7 +3040,7 @@ async function envRemoveCommand(key, folderPath, options = {}) {
|
|
|
2791
3040
|
return;
|
|
2792
3041
|
}
|
|
2793
3042
|
}
|
|
2794
|
-
const spinner =
|
|
3043
|
+
const spinner = ora8(`Removing ${key}...`).start();
|
|
2795
3044
|
try {
|
|
2796
3045
|
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env/${key}`, {
|
|
2797
3046
|
method: "DELETE",
|
|
@@ -2820,7 +3069,7 @@ async function envPullCommand(folderPath, options = {}) {
|
|
|
2820
3069
|
}
|
|
2821
3070
|
const { apiKey, apiUrl, config } = context;
|
|
2822
3071
|
const outputFile = options.file || ".env.remote";
|
|
2823
|
-
const spinner =
|
|
3072
|
+
const spinner = ora8("Fetching environment variables...").start();
|
|
2824
3073
|
try {
|
|
2825
3074
|
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env?reveal=true`, {
|
|
2826
3075
|
headers: {
|
|
@@ -2859,7 +3108,7 @@ async function envPushCommand(folderPath, options = {}) {
|
|
|
2859
3108
|
}
|
|
2860
3109
|
const { apiKey, apiUrl, config } = context;
|
|
2861
3110
|
const inputFile = options.file || ".env";
|
|
2862
|
-
const loadSpinner =
|
|
3111
|
+
const loadSpinner = ora8(`Loading from ${inputFile}...`).start();
|
|
2863
3112
|
let variables;
|
|
2864
3113
|
try {
|
|
2865
3114
|
variables = await loadEnvFile(inputFile);
|
|
@@ -2886,7 +3135,7 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
2886
3135
|
return;
|
|
2887
3136
|
}
|
|
2888
3137
|
}
|
|
2889
|
-
const pushSpinner =
|
|
3138
|
+
const pushSpinner = ora8("Pushing environment variables...").start();
|
|
2890
3139
|
try {
|
|
2891
3140
|
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env`, {
|
|
2892
3141
|
method: "PUT",
|
|
@@ -3733,30 +3982,30 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
|
|
|
3733
3982
|
projectName,
|
|
3734
3983
|
...options
|
|
3735
3984
|
});
|
|
3736
|
-
const spinner =
|
|
3737
|
-
const targetDir =
|
|
3738
|
-
if (
|
|
3985
|
+
const spinner = ora9(`Creating project ${projectName}...`).start();
|
|
3986
|
+
const targetDir = path12.join(process.cwd(), projectName);
|
|
3987
|
+
if (fs12.existsSync(targetDir)) {
|
|
3739
3988
|
spinner.fail(`Folder ${projectName} already exists.`);
|
|
3740
3989
|
process.exit(1);
|
|
3741
3990
|
}
|
|
3742
|
-
await
|
|
3991
|
+
await fs12.mkdirp(targetDir);
|
|
3743
3992
|
const isPython = options.python === true;
|
|
3744
3993
|
if (isPython) {
|
|
3745
3994
|
const requirements = getPythonRequirementsTemplate();
|
|
3746
|
-
await
|
|
3995
|
+
await fs12.writeFile(path12.join(targetDir, "requirements.txt"), requirements);
|
|
3747
3996
|
const mainPy = getPythonMainTemplate(projectName);
|
|
3748
|
-
await
|
|
3749
|
-
await
|
|
3997
|
+
await fs12.writeFile(path12.join(targetDir, "main.py"), mainPy);
|
|
3998
|
+
await fs12.writeFile(path12.join(targetDir, ".gitignore"), pythonGitignoreTemplate);
|
|
3750
3999
|
const env = `# Server Configuration
|
|
3751
4000
|
PORT=3001
|
|
3752
4001
|
|
|
3753
4002
|
# Add your environment variables here
|
|
3754
4003
|
`;
|
|
3755
|
-
await
|
|
4004
|
+
await fs12.writeFile(path12.join(targetDir, ".env"), env);
|
|
3756
4005
|
const readme = getPythonReadmeTemplate(projectName);
|
|
3757
|
-
await
|
|
4006
|
+
await fs12.writeFile(path12.join(targetDir, "README.md"), readme);
|
|
3758
4007
|
} else {
|
|
3759
|
-
await
|
|
4008
|
+
await fs12.mkdirp(path12.join(targetDir, "mcp", "example"));
|
|
3760
4009
|
const pkg2 = {
|
|
3761
4010
|
name: projectName,
|
|
3762
4011
|
version: "1.0.0",
|
|
@@ -3792,7 +4041,7 @@ PORT=3001
|
|
|
3792
4041
|
typescript: "^5.6.3"
|
|
3793
4042
|
}
|
|
3794
4043
|
};
|
|
3795
|
-
await
|
|
4044
|
+
await fs12.writeJSON(path12.join(targetDir, "package.json"), pkg2, {
|
|
3796
4045
|
spaces: 2
|
|
3797
4046
|
});
|
|
3798
4047
|
const tsconfig = {
|
|
@@ -3815,15 +4064,15 @@ PORT=3001
|
|
|
3815
4064
|
"dist"
|
|
3816
4065
|
]
|
|
3817
4066
|
};
|
|
3818
|
-
await
|
|
4067
|
+
await fs12.writeJSON(path12.join(targetDir, "tsconfig.json"), tsconfig, {
|
|
3819
4068
|
spaces: 2
|
|
3820
4069
|
});
|
|
3821
4070
|
const dashboardLine = options.dashboard === false ? `
|
|
3822
4071
|
dashboard: false, // Dashboard disabled via --no-dashboard` : "";
|
|
3823
4072
|
const mainTs = getMainTsTemplate(projectName, dashboardLine);
|
|
3824
|
-
await
|
|
4073
|
+
await fs12.writeFile(path12.join(targetDir, "main.ts"), mainTs);
|
|
3825
4074
|
const exampleServiceTs = getExampleServiceTemplate(projectName);
|
|
3826
|
-
await
|
|
4075
|
+
await fs12.writeFile(path12.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
|
|
3827
4076
|
const gitignore = gitignoreTemplate;
|
|
3828
4077
|
const env = `# Server Configuration
|
|
3829
4078
|
PORT=3001
|
|
@@ -3831,10 +4080,10 @@ NODE_ENV=development
|
|
|
3831
4080
|
|
|
3832
4081
|
# Add your environment variables here
|
|
3833
4082
|
`;
|
|
3834
|
-
await
|
|
3835
|
-
await
|
|
4083
|
+
await fs12.writeFile(path12.join(targetDir, ".gitignore"), gitignore);
|
|
4084
|
+
await fs12.writeFile(path12.join(targetDir, ".env"), env);
|
|
3836
4085
|
const readme = getReadmeTemplate(projectName);
|
|
3837
|
-
await
|
|
4086
|
+
await fs12.writeFile(path12.join(targetDir, "README.md"), readme);
|
|
3838
4087
|
}
|
|
3839
4088
|
spinner.succeed(`Project ${projectName} created!`);
|
|
3840
4089
|
logger.log("\nSuccess! Your MCP server is ready.\n", chalk.green);
|
|
@@ -3881,7 +4130,7 @@ NODE_ENV=development
|
|
|
3881
4130
|
default: true
|
|
3882
4131
|
});
|
|
3883
4132
|
if (shouldInstall) {
|
|
3884
|
-
const installSpinner =
|
|
4133
|
+
const installSpinner = ora9("Installing dependencies...").start();
|
|
3885
4134
|
try {
|
|
3886
4135
|
await new Promise((resolve, reject) => {
|
|
3887
4136
|
const npmInstall = spawn4("npm", [
|
|
@@ -3954,24 +4203,33 @@ NODE_ENV=development
|
|
|
3954
4203
|
logger.log("To deploy to LeanMCP cloud:", chalk.cyan);
|
|
3955
4204
|
logger.log(` cd ${projectName}`, chalk.gray);
|
|
3956
4205
|
logger.log(` leanmcp deploy .`, chalk.gray);
|
|
4206
|
+
logger.log("\nSend us feedback:", chalk.cyan);
|
|
4207
|
+
logger.log(' leanmcp send-feedback "Great tool!"\n', chalk.gray);
|
|
3957
4208
|
}
|
|
3958
4209
|
});
|
|
4210
|
+
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) => {
|
|
4211
|
+
trackCommand("send-feedback", {
|
|
4212
|
+
hasMessage: !!message,
|
|
4213
|
+
...options
|
|
4214
|
+
});
|
|
4215
|
+
await sendFeedbackCommand(message, options);
|
|
4216
|
+
});
|
|
3959
4217
|
program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
|
|
3960
4218
|
const cwd = process.cwd();
|
|
3961
|
-
const mcpDir =
|
|
3962
|
-
if (!
|
|
4219
|
+
const mcpDir = path12.join(cwd, "mcp");
|
|
4220
|
+
if (!fs12.existsSync(path12.join(cwd, "main.ts"))) {
|
|
3963
4221
|
logger.log("ERROR: Not a LeanMCP project (main.ts missing).", chalk.red);
|
|
3964
4222
|
process.exit(1);
|
|
3965
4223
|
}
|
|
3966
|
-
const serviceDir =
|
|
3967
|
-
const serviceFile =
|
|
3968
|
-
if (
|
|
4224
|
+
const serviceDir = path12.join(mcpDir, serviceName);
|
|
4225
|
+
const serviceFile = path12.join(serviceDir, "index.ts");
|
|
4226
|
+
if (fs12.existsSync(serviceDir)) {
|
|
3969
4227
|
logger.log(`ERROR: Service ${serviceName} already exists.`, chalk.red);
|
|
3970
4228
|
process.exit(1);
|
|
3971
4229
|
}
|
|
3972
|
-
await
|
|
4230
|
+
await fs12.mkdirp(serviceDir);
|
|
3973
4231
|
const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
|
|
3974
|
-
await
|
|
4232
|
+
await fs12.writeFile(serviceFile, indexTs);
|
|
3975
4233
|
logger.log(`\\nCreated new service: ${chalk.bold(serviceName)}`, chalk.green);
|
|
3976
4234
|
logger.log(` File: mcp/${serviceName}/index.ts`, chalk.gray);
|
|
3977
4235
|
logger.log(` Tool: greet`, chalk.gray);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leanmcp/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"description": "Command-line interface for scaffolding LeanMCP projects",
|
|
5
5
|
"bin": {
|
|
6
6
|
"leanmcp": "bin/leanmcp.js"
|
|
@@ -71,4 +71,4 @@
|
|
|
71
71
|
"publishConfig": {
|
|
72
72
|
"access": "public"
|
|
73
73
|
}
|
|
74
|
-
}
|
|
74
|
+
}
|