@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.
Files changed (3) hide show
  1. package/README.md +477 -477
  2. package/dist/index.js +315 -57
  3. 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 fs11 from "fs-extra";
7
- import path11 from "path";
8
- import ora8 from "ora";
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/projects.ts
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 = ora6("Fetching projects...").start();
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 = ora6("Fetching project...").start();
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 = ora6("Deleting project...").start();
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 ora7 from "ora";
2545
- import path10 from "path";
2546
- import fs10 from "fs-extra";
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 DEBUG_MODE4 = false;
2797
+ var DEBUG_MODE5 = false;
2549
2798
  function setEnvDebugMode(enabled) {
2550
- DEBUG_MODE4 = enabled;
2799
+ DEBUG_MODE5 = enabled;
2551
2800
  }
2552
2801
  __name(setEnvDebugMode, "setEnvDebugMode");
2553
- function debug4(message, ...args) {
2554
- if (DEBUG_MODE4) {
2802
+ function debug5(message, ...args) {
2803
+ if (DEBUG_MODE5) {
2555
2804
  console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
2556
2805
  }
2557
2806
  }
2558
- __name(debug4, "debug");
2807
+ __name(debug5, "debug");
2559
2808
  async function debugFetch2(url, options = {}) {
2560
- debug4(`HTTP ${options.method || "GET"} ${url}`);
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
- debug4("Request body:", JSON.stringify(body, null, 2));
2813
+ debug5("Request body:", JSON.stringify(body, null, 2));
2565
2814
  } catch {
2566
- debug4("Request body:", options.body);
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
- debug4(`Response: ${response.status} ${response.statusText} (${duration}ms)`);
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 = path10.join(projectPath, LEANMCP_CONFIG_DIR2, LEANMCP_CONFIG_FILE2);
2828
+ const configPath = path11.join(projectPath, LEANMCP_CONFIG_DIR2, LEANMCP_CONFIG_FILE2);
2580
2829
  try {
2581
- if (await fs10.pathExists(configPath)) {
2582
- const config = await fs10.readJSON(configPath);
2583
- debug4("Found existing .leanmcp config:", config);
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
- debug4("Could not read .leanmcp config:", e);
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 = path10.resolve(process.cwd(), folderPath);
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 = ora7("Fetching environment variables...").start();
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 = ora7(`Loading from ${options.file}...`).start();
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 = ora7("Updating environment variables...").start();
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 = ora7(`Removing ${key}...`).start();
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 = ora7("Fetching environment variables...").start();
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 = ora7(`Loading from ${inputFile}...`).start();
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 = ora7("Pushing environment variables...").start();
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 = ora8(`Creating project ${projectName}...`).start();
3737
- const targetDir = path11.join(process.cwd(), projectName);
3738
- if (fs11.existsSync(targetDir)) {
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 fs11.mkdirp(targetDir);
3991
+ await fs12.mkdirp(targetDir);
3743
3992
  const isPython = options.python === true;
3744
3993
  if (isPython) {
3745
3994
  const requirements = getPythonRequirementsTemplate();
3746
- await fs11.writeFile(path11.join(targetDir, "requirements.txt"), requirements);
3995
+ await fs12.writeFile(path12.join(targetDir, "requirements.txt"), requirements);
3747
3996
  const mainPy = getPythonMainTemplate(projectName);
3748
- await fs11.writeFile(path11.join(targetDir, "main.py"), mainPy);
3749
- await fs11.writeFile(path11.join(targetDir, ".gitignore"), pythonGitignoreTemplate);
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 fs11.writeFile(path11.join(targetDir, ".env"), env);
4004
+ await fs12.writeFile(path12.join(targetDir, ".env"), env);
3756
4005
  const readme = getPythonReadmeTemplate(projectName);
3757
- await fs11.writeFile(path11.join(targetDir, "README.md"), readme);
4006
+ await fs12.writeFile(path12.join(targetDir, "README.md"), readme);
3758
4007
  } else {
3759
- await fs11.mkdirp(path11.join(targetDir, "mcp", "example"));
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 fs11.writeJSON(path11.join(targetDir, "package.json"), pkg2, {
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 fs11.writeJSON(path11.join(targetDir, "tsconfig.json"), tsconfig, {
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 fs11.writeFile(path11.join(targetDir, "main.ts"), mainTs);
4073
+ await fs12.writeFile(path12.join(targetDir, "main.ts"), mainTs);
3825
4074
  const exampleServiceTs = getExampleServiceTemplate(projectName);
3826
- await fs11.writeFile(path11.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
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 fs11.writeFile(path11.join(targetDir, ".gitignore"), gitignore);
3835
- await fs11.writeFile(path11.join(targetDir, ".env"), env);
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 fs11.writeFile(path11.join(targetDir, "README.md"), readme);
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 = ora8("Installing dependencies...").start();
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 = path11.join(cwd, "mcp");
3962
- if (!fs11.existsSync(path11.join(cwd, "main.ts"))) {
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 = path11.join(mcpDir, serviceName);
3967
- const serviceFile = path11.join(serviceDir, "index.ts");
3968
- if (fs11.existsSync(serviceDir)) {
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 fs11.mkdirp(serviceDir);
4230
+ await fs12.mkdirp(serviceDir);
3973
4231
  const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
3974
- await fs11.writeFile(serviceFile, indexTs);
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.6",
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
+ }