@leanmcp/cli 0.2.11 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +428 -428
- package/bin/leanmcp.js +3 -3
- package/dist/index.js +1098 -215
- package/package.json +72 -70
package/dist/index.js
CHANGED
|
@@ -3,12 +3,12 @@ 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
|
|
9
|
-
import
|
|
6
|
+
import chalk6 from "chalk";
|
|
7
|
+
import fs7 from "fs-extra";
|
|
8
|
+
import path7 from "path";
|
|
9
|
+
import ora6 from "ora";
|
|
10
10
|
import { createRequire } from "module";
|
|
11
|
-
import { confirm } from "@inquirer/prompts";
|
|
11
|
+
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
12
12
|
import { spawn as spawn3 } from "child_process";
|
|
13
13
|
|
|
14
14
|
// src/commands/dev.ts
|
|
@@ -59,33 +59,14 @@ function parseUIAppDecorators(content, filePath) {
|
|
|
59
59
|
while ((match = uiAppRegex.exec(content)) !== null) {
|
|
60
60
|
const decoratorBody = match[1];
|
|
61
61
|
const methodName = match[2];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (!resolvedPath.endsWith(".tsx") && !resolvedPath.endsWith(".ts")) {
|
|
70
|
-
if (fs.existsSync(resolvedPath + ".tsx")) {
|
|
71
|
-
resolvedPath += ".tsx";
|
|
72
|
-
} else if (fs.existsSync(resolvedPath + ".ts")) {
|
|
73
|
-
resolvedPath += ".ts";
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
componentPath = resolvedPath;
|
|
77
|
-
componentName = path.basename(relativePath).replace(/\.(tsx?|jsx?)$/, "");
|
|
78
|
-
} else {
|
|
79
|
-
const identifierMatch = decoratorBody.match(/component\s*:\s*(\w+)/);
|
|
80
|
-
if (!identifierMatch) continue;
|
|
81
|
-
componentName = identifierMatch[1];
|
|
82
|
-
componentPath = importMap[componentName];
|
|
83
|
-
if (!componentPath) {
|
|
84
|
-
console.warn(`[scanUIApp] Could not resolve import for component: ${componentName}`);
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
62
|
+
const componentMatch = decoratorBody.match(/component\s*:\s*(\w+)/);
|
|
63
|
+
if (!componentMatch) continue;
|
|
64
|
+
const componentName = componentMatch[1];
|
|
65
|
+
const componentPath = importMap[componentName];
|
|
66
|
+
if (!componentPath) {
|
|
67
|
+
console.warn(`[scanUIApp] Could not resolve import for component: ${componentName}`);
|
|
68
|
+
continue;
|
|
87
69
|
}
|
|
88
|
-
if (!componentPath) continue;
|
|
89
70
|
const servicePrefix = serviceName.replace(/Service$/i, "").toLowerCase();
|
|
90
71
|
const resourceUri = `ui://${servicePrefix}/${methodName}`;
|
|
91
72
|
results.push({
|
|
@@ -153,137 +134,11 @@ async function buildUIComponent(uiApp, projectDir, isDev = false) {
|
|
|
153
134
|
<script type="module" src="./entry.tsx"></script>
|
|
154
135
|
</body>
|
|
155
136
|
</html>`);
|
|
156
|
-
const tailwindConfig = path2.join(tempDir, "tailwind.config.js");
|
|
157
|
-
await fs2.writeFile(tailwindConfig, `
|
|
158
|
-
/** @type {import('tailwindcss').Config} */
|
|
159
|
-
module.exports = {
|
|
160
|
-
content: [
|
|
161
|
-
'${path2.join(projectDir, "**/*.{ts,tsx,js,jsx}").replace(/\\/g, "/")}',
|
|
162
|
-
'${path2.join(projectDir, "mcp/**/*.{ts,tsx,js,jsx}").replace(/\\/g, "/")}',
|
|
163
|
-
'${path2.join(projectDir, "node_modules/@leanmcp/ui/**/*.{js,mjs}").replace(/\\/g, "/")}',
|
|
164
|
-
'${path2.join(projectDir, "../../packages/ui/src/**/*.{ts,tsx}").replace(/\\/g, "/")}',
|
|
165
|
-
],
|
|
166
|
-
darkMode: ['class'],
|
|
167
|
-
theme: {
|
|
168
|
-
container: {
|
|
169
|
-
center: true,
|
|
170
|
-
padding: '2rem',
|
|
171
|
-
screens: {
|
|
172
|
-
'2xl': '1400px',
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
extend: {
|
|
176
|
-
colors: {
|
|
177
|
-
border: 'hsl(var(--border))',
|
|
178
|
-
input: 'hsl(var(--input))',
|
|
179
|
-
ring: 'hsl(var(--ring))',
|
|
180
|
-
background: 'hsl(var(--background))',
|
|
181
|
-
foreground: 'hsl(var(--foreground))',
|
|
182
|
-
primary: {
|
|
183
|
-
DEFAULT: 'hsl(var(--primary))',
|
|
184
|
-
foreground: 'hsl(var(--primary-foreground))',
|
|
185
|
-
},
|
|
186
|
-
secondary: {
|
|
187
|
-
DEFAULT: 'hsl(var(--secondary))',
|
|
188
|
-
foreground: 'hsl(var(--secondary-foreground))',
|
|
189
|
-
},
|
|
190
|
-
destructive: {
|
|
191
|
-
DEFAULT: 'hsl(var(--destructive))',
|
|
192
|
-
foreground: 'hsl(var(--destructive-foreground))',
|
|
193
|
-
},
|
|
194
|
-
muted: {
|
|
195
|
-
DEFAULT: 'hsl(var(--muted))',
|
|
196
|
-
foreground: 'hsl(var(--muted-foreground))',
|
|
197
|
-
},
|
|
198
|
-
accent: {
|
|
199
|
-
DEFAULT: 'hsl(var(--accent))',
|
|
200
|
-
foreground: 'hsl(var(--accent-foreground))',
|
|
201
|
-
},
|
|
202
|
-
popover: {
|
|
203
|
-
DEFAULT: 'hsl(var(--popover))',
|
|
204
|
-
foreground: 'hsl(var(--popover-foreground))',
|
|
205
|
-
},
|
|
206
|
-
card: {
|
|
207
|
-
DEFAULT: 'hsl(var(--card))',
|
|
208
|
-
foreground: 'hsl(var(--card-foreground))',
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
borderRadius: {
|
|
212
|
-
lg: 'var(--radius)',
|
|
213
|
-
md: 'calc(var(--radius) - 2px)',
|
|
214
|
-
sm: 'calc(var(--radius) - 4px)',
|
|
215
|
-
},
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
plugins: [],
|
|
219
|
-
}
|
|
220
|
-
`);
|
|
221
|
-
const stylesCss = path2.join(tempDir, "styles.css");
|
|
222
|
-
await fs2.writeFile(stylesCss, `
|
|
223
|
-
@tailwind base;
|
|
224
|
-
@tailwind components;
|
|
225
|
-
@tailwind utilities;
|
|
226
|
-
|
|
227
|
-
@layer base {
|
|
228
|
-
:root {
|
|
229
|
-
--background: 0 0% 100%;
|
|
230
|
-
--foreground: 222.2 84% 4.9%;
|
|
231
|
-
--card: 0 0% 100%;
|
|
232
|
-
--card-foreground: 222.2 84% 4.9%;
|
|
233
|
-
--popover: 0 0% 100%;
|
|
234
|
-
--popover-foreground: 222.2 84% 4.9%;
|
|
235
|
-
--primary: 222.2 47.4% 11.2%;
|
|
236
|
-
--primary-foreground: 210 40% 98%;
|
|
237
|
-
--secondary: 210 40% 96.1%;
|
|
238
|
-
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
239
|
-
--muted: 210 40% 96.1%;
|
|
240
|
-
--muted-foreground: 215.4 16.3% 46.9%;
|
|
241
|
-
--accent: 210 40% 96.1%;
|
|
242
|
-
--accent-foreground: 222.2 47.4% 11.2%;
|
|
243
|
-
--destructive: 0 84.2% 60.2%;
|
|
244
|
-
--destructive-foreground: 210 40% 98%;
|
|
245
|
-
--border: 214.3 31.8% 91.4%;
|
|
246
|
-
--input: 214.3 31.8% 91.4%;
|
|
247
|
-
--ring: 222.2 84% 4.9%;
|
|
248
|
-
--radius: 0.5rem;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.dark {
|
|
252
|
-
--background: 222.2 84% 4.9%;
|
|
253
|
-
--foreground: 210 40% 98%;
|
|
254
|
-
--card: 222.2 84% 4.9%;
|
|
255
|
-
--card-foreground: 210 40% 98%;
|
|
256
|
-
--popover: 222.2 84% 4.9%;
|
|
257
|
-
--popover-foreground: 210 40% 98%;
|
|
258
|
-
--primary: 210 40% 98%;
|
|
259
|
-
--primary-foreground: 222.2 47.4% 11.2%;
|
|
260
|
-
--secondary: 217.2 32.6% 17.5%;
|
|
261
|
-
--secondary-foreground: 210 40% 98%;
|
|
262
|
-
--muted: 217.2 32.6% 17.5%;
|
|
263
|
-
--muted-foreground: 215 20.2% 65.1%;
|
|
264
|
-
--accent: 217.2 32.6% 17.5%;
|
|
265
|
-
--accent-foreground: 210 40% 98%;
|
|
266
|
-
--destructive: 0 62.8% 30.6%;
|
|
267
|
-
--destructive-foreground: 210 40% 98%;
|
|
268
|
-
--border: 217.2 32.6% 17.5%;
|
|
269
|
-
--input: 217.2 32.6% 17.5%;
|
|
270
|
-
--ring: 212.7 26.8% 83.9%;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
body {
|
|
274
|
-
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
275
|
-
background-color: hsl(var(--background));
|
|
276
|
-
color: hsl(var(--foreground));
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
`);
|
|
280
137
|
const relativeComponentPath = path2.relative(tempDir, componentPath).replace(/\\/g, "/");
|
|
281
138
|
await fs2.writeFile(entryJs, `
|
|
282
139
|
import React, { StrictMode } from 'react';
|
|
283
140
|
import { createRoot } from 'react-dom/client';
|
|
284
|
-
import { AppProvider
|
|
285
|
-
import '@leanmcp/ui/styles.css';
|
|
286
|
-
import './styles.css';
|
|
141
|
+
import { AppProvider } from '@leanmcp/ui';
|
|
287
142
|
import { ${componentName} } from '${relativeComponentPath.replace(/\.tsx?$/, "")}';
|
|
288
143
|
|
|
289
144
|
const APP_INFO = {
|
|
@@ -295,7 +150,6 @@ function App() {
|
|
|
295
150
|
return (
|
|
296
151
|
<AppProvider appInfo={APP_INFO}>
|
|
297
152
|
<${componentName} />
|
|
298
|
-
<Toaster />
|
|
299
153
|
</AppProvider>
|
|
300
154
|
);
|
|
301
155
|
}
|
|
@@ -313,16 +167,6 @@ createRoot(document.getElementById('root')!).render(
|
|
|
313
167
|
react(),
|
|
314
168
|
viteSingleFile()
|
|
315
169
|
],
|
|
316
|
-
css: {
|
|
317
|
-
postcss: {
|
|
318
|
-
plugins: [
|
|
319
|
-
(await import("tailwindcss")).default({
|
|
320
|
-
config: tailwindConfig
|
|
321
|
-
}),
|
|
322
|
-
(await import("autoprefixer")).default
|
|
323
|
-
]
|
|
324
|
-
}
|
|
325
|
-
},
|
|
326
170
|
build: {
|
|
327
171
|
outDir,
|
|
328
172
|
emptyOutDir: false,
|
|
@@ -544,6 +388,1001 @@ async function startCommand() {
|
|
|
544
388
|
}
|
|
545
389
|
__name(startCommand, "startCommand");
|
|
546
390
|
|
|
391
|
+
// src/commands/login.ts
|
|
392
|
+
import chalk3 from "chalk";
|
|
393
|
+
import ora3 from "ora";
|
|
394
|
+
import path5 from "path";
|
|
395
|
+
import fs5 from "fs-extra";
|
|
396
|
+
import os from "os";
|
|
397
|
+
import { input, confirm } from "@inquirer/prompts";
|
|
398
|
+
var DEBUG_MODE = false;
|
|
399
|
+
function setDebugMode(enabled) {
|
|
400
|
+
DEBUG_MODE = enabled;
|
|
401
|
+
}
|
|
402
|
+
__name(setDebugMode, "setDebugMode");
|
|
403
|
+
function debug(message, ...args) {
|
|
404
|
+
if (DEBUG_MODE) {
|
|
405
|
+
console.log(chalk3.gray(`[DEBUG] ${message}`), ...args);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
__name(debug, "debug");
|
|
409
|
+
var CONFIG_DIR = path5.join(os.homedir(), ".leanmcp");
|
|
410
|
+
var CONFIG_FILE = path5.join(CONFIG_DIR, "config.json");
|
|
411
|
+
async function loadConfig() {
|
|
412
|
+
try {
|
|
413
|
+
if (await fs5.pathExists(CONFIG_FILE)) {
|
|
414
|
+
return await fs5.readJSON(CONFIG_FILE);
|
|
415
|
+
}
|
|
416
|
+
} catch (error) {
|
|
417
|
+
}
|
|
418
|
+
return {};
|
|
419
|
+
}
|
|
420
|
+
__name(loadConfig, "loadConfig");
|
|
421
|
+
async function saveConfig(config) {
|
|
422
|
+
await fs5.ensureDir(CONFIG_DIR);
|
|
423
|
+
await fs5.writeJSON(CONFIG_FILE, config, {
|
|
424
|
+
spaces: 2
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
__name(saveConfig, "saveConfig");
|
|
428
|
+
async function getApiKey() {
|
|
429
|
+
const config = await loadConfig();
|
|
430
|
+
return config.apiKey || null;
|
|
431
|
+
}
|
|
432
|
+
__name(getApiKey, "getApiKey");
|
|
433
|
+
var ALLOWED_API_URLS = [
|
|
434
|
+
"https://api.leanmcp.com",
|
|
435
|
+
"https://devapi.leanmcp.com",
|
|
436
|
+
"https://qaapi.leanmcp.com",
|
|
437
|
+
"http://localhost:3001"
|
|
438
|
+
];
|
|
439
|
+
var DEFAULT_API_URL = "https://api.leanmcp.com";
|
|
440
|
+
function isValidApiUrl(url) {
|
|
441
|
+
return ALLOWED_API_URLS.includes(url);
|
|
442
|
+
}
|
|
443
|
+
__name(isValidApiUrl, "isValidApiUrl");
|
|
444
|
+
async function getApiUrl() {
|
|
445
|
+
const config = await loadConfig();
|
|
446
|
+
if (!config.apiUrl) {
|
|
447
|
+
return DEFAULT_API_URL;
|
|
448
|
+
}
|
|
449
|
+
if (!isValidApiUrl(config.apiUrl)) {
|
|
450
|
+
throw new Error(`Invalid API URL: ${config.apiUrl}
|
|
451
|
+
Allowed URLs:
|
|
452
|
+
- https://api.leanmcp.com
|
|
453
|
+
- https://devapi.leanmcp.com
|
|
454
|
+
- https://qaapi.leanmcp.com
|
|
455
|
+
- http://localhost:3001`);
|
|
456
|
+
}
|
|
457
|
+
return config.apiUrl;
|
|
458
|
+
}
|
|
459
|
+
__name(getApiUrl, "getApiUrl");
|
|
460
|
+
async function loginCommand() {
|
|
461
|
+
console.log(chalk3.cyan("\nLeanMCP Login\n"));
|
|
462
|
+
const existingConfig = await loadConfig();
|
|
463
|
+
if (existingConfig.apiKey) {
|
|
464
|
+
console.log(chalk3.yellow("You are already logged in."));
|
|
465
|
+
const shouldRelogin = await confirm({
|
|
466
|
+
message: "Do you want to replace the existing API key?",
|
|
467
|
+
default: false
|
|
468
|
+
});
|
|
469
|
+
if (!shouldRelogin) {
|
|
470
|
+
console.log(chalk3.gray("\nLogin cancelled. Existing API key preserved."));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
console.log(chalk3.white("To authenticate, you need an API key from LeanMCP.\n"));
|
|
475
|
+
console.log(chalk3.cyan("Steps:"));
|
|
476
|
+
console.log(chalk3.gray(" 1. Go to: ") + chalk3.blue.underline("https://ship.leanmcp.com/api-keys"));
|
|
477
|
+
console.log(chalk3.gray(' 2. Create a new API key with "BUILD_AND_DEPLOY" scope'));
|
|
478
|
+
console.log(chalk3.gray(" 3. Copy the API key and paste it below\n"));
|
|
479
|
+
const apiKey = await input({
|
|
480
|
+
message: "Enter your API key:",
|
|
481
|
+
validate: /* @__PURE__ */ __name((value) => {
|
|
482
|
+
if (!value || value.trim().length === 0) {
|
|
483
|
+
return "API key is required";
|
|
484
|
+
}
|
|
485
|
+
if (!value.startsWith("airtrain_")) {
|
|
486
|
+
return 'Invalid API key format. API key should start with "airtrain_"';
|
|
487
|
+
}
|
|
488
|
+
return true;
|
|
489
|
+
}, "validate")
|
|
490
|
+
});
|
|
491
|
+
const spinner = ora3("Validating API key...").start();
|
|
492
|
+
try {
|
|
493
|
+
const apiUrl = await getApiUrl();
|
|
494
|
+
const validateUrl = `${apiUrl}/api-keys/validate`;
|
|
495
|
+
debug("API URL:", apiUrl);
|
|
496
|
+
debug("Validate URL:", validateUrl);
|
|
497
|
+
debug("Making validation request...");
|
|
498
|
+
const response = await fetch(validateUrl, {
|
|
499
|
+
method: "GET",
|
|
500
|
+
headers: {
|
|
501
|
+
"Authorization": `Bearer ${apiKey.trim()}`,
|
|
502
|
+
"Content-Type": "application/json"
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
debug("Response status:", response.status);
|
|
506
|
+
debug("Response ok:", response.ok);
|
|
507
|
+
if (!response.ok) {
|
|
508
|
+
const errorText = await response.text();
|
|
509
|
+
debug("Error response:", errorText);
|
|
510
|
+
spinner.fail("Invalid API key");
|
|
511
|
+
console.error(chalk3.red("\nThe API key is invalid or has expired."));
|
|
512
|
+
console.log(chalk3.gray("Please check your API key and try again."));
|
|
513
|
+
if (DEBUG_MODE) {
|
|
514
|
+
console.log(chalk3.gray(`Debug: Status ${response.status}, Response: ${errorText}`));
|
|
515
|
+
}
|
|
516
|
+
process.exit(1);
|
|
517
|
+
}
|
|
518
|
+
await saveConfig({
|
|
519
|
+
apiKey: apiKey.trim(),
|
|
520
|
+
apiUrl,
|
|
521
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
522
|
+
});
|
|
523
|
+
spinner.succeed("API key validated and saved");
|
|
524
|
+
console.log(chalk3.green("\nLogin successful!"));
|
|
525
|
+
console.log(chalk3.gray(` Config saved to: ${CONFIG_FILE}
|
|
526
|
+
`));
|
|
527
|
+
console.log(chalk3.cyan("You can now use:"));
|
|
528
|
+
console.log(chalk3.gray(" leanmcp deploy <folder> - Deploy your MCP server"));
|
|
529
|
+
console.log(chalk3.gray(" leanmcp logout - Remove your API key"));
|
|
530
|
+
} catch (error) {
|
|
531
|
+
spinner.fail("Failed to validate API key");
|
|
532
|
+
debug("Error:", error);
|
|
533
|
+
if (error instanceof Error && error.message.includes("fetch")) {
|
|
534
|
+
console.error(chalk3.red("\nCould not connect to LeanMCP servers."));
|
|
535
|
+
console.log(chalk3.gray("Please check your internet connection and try again."));
|
|
536
|
+
} else {
|
|
537
|
+
console.error(chalk3.red(`
|
|
538
|
+
Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
539
|
+
}
|
|
540
|
+
if (DEBUG_MODE) {
|
|
541
|
+
console.log(chalk3.gray(`
|
|
542
|
+
Debug: Full error: ${error}`));
|
|
543
|
+
}
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
__name(loginCommand, "loginCommand");
|
|
548
|
+
async function logoutCommand() {
|
|
549
|
+
console.log(chalk3.cyan("\nLeanMCP Logout\n"));
|
|
550
|
+
const config = await loadConfig();
|
|
551
|
+
if (!config.apiKey) {
|
|
552
|
+
console.log(chalk3.yellow("You are not currently logged in."));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const shouldLogout = await confirm({
|
|
556
|
+
message: "Are you sure you want to logout and remove your API key?",
|
|
557
|
+
default: false
|
|
558
|
+
});
|
|
559
|
+
if (!shouldLogout) {
|
|
560
|
+
console.log(chalk3.gray("\nLogout cancelled."));
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
await saveConfig({
|
|
564
|
+
...config,
|
|
565
|
+
apiKey: void 0,
|
|
566
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
567
|
+
});
|
|
568
|
+
console.log(chalk3.green("\nLogged out successfully!"));
|
|
569
|
+
console.log(chalk3.gray(` API key removed from: ${CONFIG_FILE}`));
|
|
570
|
+
}
|
|
571
|
+
__name(logoutCommand, "logoutCommand");
|
|
572
|
+
async function whoamiCommand() {
|
|
573
|
+
const config = await loadConfig();
|
|
574
|
+
if (!config.apiKey) {
|
|
575
|
+
console.log(chalk3.yellow("\nYou are not logged in."));
|
|
576
|
+
console.log(chalk3.gray("Run `leanmcp login` to authenticate.\n"));
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
console.log(chalk3.cyan("\nLeanMCP Authentication Status\n"));
|
|
580
|
+
console.log(chalk3.green("Logged in"));
|
|
581
|
+
console.log(chalk3.gray(` API Key: ${config.apiKey.substring(0, 15)}...`));
|
|
582
|
+
console.log(chalk3.gray(` API URL: ${config.apiUrl || "https://ship.leanmcp.com"}`));
|
|
583
|
+
if (config.lastUpdated) {
|
|
584
|
+
console.log(chalk3.gray(` Last updated: ${new Date(config.lastUpdated).toLocaleString()}`));
|
|
585
|
+
}
|
|
586
|
+
console.log();
|
|
587
|
+
}
|
|
588
|
+
__name(whoamiCommand, "whoamiCommand");
|
|
589
|
+
|
|
590
|
+
// src/commands/deploy.ts
|
|
591
|
+
import chalk4 from "chalk";
|
|
592
|
+
import ora4 from "ora";
|
|
593
|
+
import path6 from "path";
|
|
594
|
+
import fs6 from "fs-extra";
|
|
595
|
+
import os2 from "os";
|
|
596
|
+
import archiver from "archiver";
|
|
597
|
+
import { input as input2, confirm as confirm2, select } from "@inquirer/prompts";
|
|
598
|
+
|
|
599
|
+
// src/utils/name-generator.ts
|
|
600
|
+
var ADJECTIVES = [
|
|
601
|
+
"swift",
|
|
602
|
+
"bright",
|
|
603
|
+
"calm",
|
|
604
|
+
"bold",
|
|
605
|
+
"cool",
|
|
606
|
+
"crisp",
|
|
607
|
+
"dark",
|
|
608
|
+
"dawn",
|
|
609
|
+
"deep",
|
|
610
|
+
"fair",
|
|
611
|
+
"fast",
|
|
612
|
+
"fine",
|
|
613
|
+
"glad",
|
|
614
|
+
"gold",
|
|
615
|
+
"good",
|
|
616
|
+
"gray",
|
|
617
|
+
"green",
|
|
618
|
+
"happy",
|
|
619
|
+
"keen",
|
|
620
|
+
"kind",
|
|
621
|
+
"late",
|
|
622
|
+
"lean",
|
|
623
|
+
"light",
|
|
624
|
+
"long",
|
|
625
|
+
"loud",
|
|
626
|
+
"mild",
|
|
627
|
+
"neat",
|
|
628
|
+
"new",
|
|
629
|
+
"nice",
|
|
630
|
+
"old",
|
|
631
|
+
"pale",
|
|
632
|
+
"pink",
|
|
633
|
+
"plain",
|
|
634
|
+
"prime",
|
|
635
|
+
"pure",
|
|
636
|
+
"quick",
|
|
637
|
+
"quiet",
|
|
638
|
+
"rare",
|
|
639
|
+
"red",
|
|
640
|
+
"rich",
|
|
641
|
+
"rough",
|
|
642
|
+
"royal",
|
|
643
|
+
"safe",
|
|
644
|
+
"sharp",
|
|
645
|
+
"shy",
|
|
646
|
+
"silver",
|
|
647
|
+
"slim",
|
|
648
|
+
"slow",
|
|
649
|
+
"smart",
|
|
650
|
+
"smooth",
|
|
651
|
+
"soft",
|
|
652
|
+
"solid",
|
|
653
|
+
"still",
|
|
654
|
+
"sunny",
|
|
655
|
+
"super",
|
|
656
|
+
"sweet",
|
|
657
|
+
"tall",
|
|
658
|
+
"tidy",
|
|
659
|
+
"tiny",
|
|
660
|
+
"true",
|
|
661
|
+
"warm",
|
|
662
|
+
"wild",
|
|
663
|
+
"wise",
|
|
664
|
+
"young"
|
|
665
|
+
];
|
|
666
|
+
var SCIENTISTS = [
|
|
667
|
+
// AI & Computer Science
|
|
668
|
+
"turing",
|
|
669
|
+
"shannon",
|
|
670
|
+
"mccarthy",
|
|
671
|
+
"minsky",
|
|
672
|
+
"newell",
|
|
673
|
+
"simon",
|
|
674
|
+
"dijkstra",
|
|
675
|
+
"knuth",
|
|
676
|
+
"hopper",
|
|
677
|
+
"lovelace",
|
|
678
|
+
"hinton",
|
|
679
|
+
"lecun",
|
|
680
|
+
"bengio",
|
|
681
|
+
"schmidhuber",
|
|
682
|
+
"hochreiter",
|
|
683
|
+
"sutskever",
|
|
684
|
+
"salakhutdinov",
|
|
685
|
+
"goodfellow",
|
|
686
|
+
"vaswani",
|
|
687
|
+
"bahdanau",
|
|
688
|
+
"mikolov",
|
|
689
|
+
"pearl",
|
|
690
|
+
"jordan",
|
|
691
|
+
"bishop",
|
|
692
|
+
"russell",
|
|
693
|
+
"norvig",
|
|
694
|
+
"ng",
|
|
695
|
+
"koller",
|
|
696
|
+
"murphy",
|
|
697
|
+
"bottou",
|
|
698
|
+
"hassabis",
|
|
699
|
+
"silver",
|
|
700
|
+
"hopfield",
|
|
701
|
+
"feifei",
|
|
702
|
+
"karpathy",
|
|
703
|
+
"li",
|
|
704
|
+
// Physics
|
|
705
|
+
"einstein",
|
|
706
|
+
"newton",
|
|
707
|
+
"hawking",
|
|
708
|
+
"feynman",
|
|
709
|
+
"bohr",
|
|
710
|
+
"planck",
|
|
711
|
+
"dirac",
|
|
712
|
+
"heisenberg",
|
|
713
|
+
"schrodinger",
|
|
714
|
+
"maxwell",
|
|
715
|
+
"faraday",
|
|
716
|
+
"curie",
|
|
717
|
+
"fermi",
|
|
718
|
+
"tesla",
|
|
719
|
+
"oppenheimer",
|
|
720
|
+
"pauli",
|
|
721
|
+
"born",
|
|
722
|
+
"yang",
|
|
723
|
+
"lee",
|
|
724
|
+
"wu",
|
|
725
|
+
"chandrasekhar",
|
|
726
|
+
"raman",
|
|
727
|
+
"bose",
|
|
728
|
+
"salam",
|
|
729
|
+
"weinberg",
|
|
730
|
+
"gell-mann",
|
|
731
|
+
"higgs",
|
|
732
|
+
"penrose",
|
|
733
|
+
// Mathematics
|
|
734
|
+
"euler",
|
|
735
|
+
"gauss",
|
|
736
|
+
"riemann",
|
|
737
|
+
"hilbert",
|
|
738
|
+
"godel",
|
|
739
|
+
"turing",
|
|
740
|
+
"nash",
|
|
741
|
+
"ramanujan",
|
|
742
|
+
"erdos",
|
|
743
|
+
"noether",
|
|
744
|
+
"galois",
|
|
745
|
+
"cauchy",
|
|
746
|
+
"laplace",
|
|
747
|
+
"fourier",
|
|
748
|
+
"leibniz",
|
|
749
|
+
"pascal",
|
|
750
|
+
"fermat",
|
|
751
|
+
"descartes",
|
|
752
|
+
"archimedes",
|
|
753
|
+
"euclid",
|
|
754
|
+
"pythagoras",
|
|
755
|
+
"fibonacci",
|
|
756
|
+
"cantor",
|
|
757
|
+
"poincare",
|
|
758
|
+
"kolmogorov",
|
|
759
|
+
"bayes",
|
|
760
|
+
"markov",
|
|
761
|
+
"rao",
|
|
762
|
+
"tao",
|
|
763
|
+
"mirzakhani",
|
|
764
|
+
"grothendieck",
|
|
765
|
+
"wiles",
|
|
766
|
+
"perelman"
|
|
767
|
+
];
|
|
768
|
+
function generateProjectName() {
|
|
769
|
+
const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
|
|
770
|
+
const scientist = SCIENTISTS[Math.floor(Math.random() * SCIENTISTS.length)];
|
|
771
|
+
const num = Math.floor(Math.random() * 100);
|
|
772
|
+
return `${adj}-${scientist}-${num}`;
|
|
773
|
+
}
|
|
774
|
+
__name(generateProjectName, "generateProjectName");
|
|
775
|
+
|
|
776
|
+
// src/commands/deploy.ts
|
|
777
|
+
var DEBUG_MODE2 = false;
|
|
778
|
+
function setDeployDebugMode(enabled) {
|
|
779
|
+
DEBUG_MODE2 = enabled;
|
|
780
|
+
}
|
|
781
|
+
__name(setDeployDebugMode, "setDeployDebugMode");
|
|
782
|
+
function debug2(message, ...args) {
|
|
783
|
+
if (DEBUG_MODE2) {
|
|
784
|
+
console.log(chalk4.gray(`[DEBUG] ${message}`), ...args);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
__name(debug2, "debug");
|
|
788
|
+
async function debugFetch(url, options = {}) {
|
|
789
|
+
debug2(`HTTP ${options.method || "GET"} ${url}`);
|
|
790
|
+
if (options.body && typeof options.body === "string") {
|
|
791
|
+
try {
|
|
792
|
+
const body = JSON.parse(options.body);
|
|
793
|
+
debug2("Request body:", JSON.stringify(body, null, 2));
|
|
794
|
+
} catch {
|
|
795
|
+
debug2("Request body:", options.body);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const startTime = Date.now();
|
|
799
|
+
const response = await fetch(url, options);
|
|
800
|
+
const duration = Date.now() - startTime;
|
|
801
|
+
debug2(`Response: ${response.status} ${response.statusText} (${duration}ms)`);
|
|
802
|
+
return response;
|
|
803
|
+
}
|
|
804
|
+
__name(debugFetch, "debugFetch");
|
|
805
|
+
var API_ENDPOINTS = {
|
|
806
|
+
// Projects
|
|
807
|
+
projects: "/api/projects",
|
|
808
|
+
getUploadUrl: "/api/projects",
|
|
809
|
+
// Lambda builds
|
|
810
|
+
triggerBuild: "/api/lambda-builds/trigger",
|
|
811
|
+
getBuild: "/api/lambda-builds",
|
|
812
|
+
// Lambda deployments
|
|
813
|
+
createDeployment: "/api/lambda-deploy",
|
|
814
|
+
getDeployment: "/api/lambda-deploy",
|
|
815
|
+
// Lambda mapping
|
|
816
|
+
checkSubdomain: "/api/lambda-mapping/check",
|
|
817
|
+
createMapping: "/api/lambda-mapping"
|
|
818
|
+
};
|
|
819
|
+
async function createZipArchive(folderPath, outputPath) {
|
|
820
|
+
return new Promise((resolve, reject) => {
|
|
821
|
+
const output = fs6.createWriteStream(outputPath);
|
|
822
|
+
const archive = archiver("zip", {
|
|
823
|
+
zlib: {
|
|
824
|
+
level: 9
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
output.on("close", () => resolve(archive.pointer()));
|
|
828
|
+
archive.on("error", reject);
|
|
829
|
+
archive.pipe(output);
|
|
830
|
+
archive.glob("**/*", {
|
|
831
|
+
cwd: folderPath,
|
|
832
|
+
ignore: [
|
|
833
|
+
"node_modules/**",
|
|
834
|
+
".git/**",
|
|
835
|
+
"dist/**",
|
|
836
|
+
".next/**",
|
|
837
|
+
".nuxt/**",
|
|
838
|
+
"__pycache__/**",
|
|
839
|
+
"*.log",
|
|
840
|
+
".env.local",
|
|
841
|
+
".DS_Store"
|
|
842
|
+
]
|
|
843
|
+
});
|
|
844
|
+
archive.finalize();
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
__name(createZipArchive, "createZipArchive");
|
|
848
|
+
async function waitForBuild(apiUrl, apiKey, buildId, spinner) {
|
|
849
|
+
const maxAttempts = 60;
|
|
850
|
+
let attempts = 0;
|
|
851
|
+
while (attempts < maxAttempts) {
|
|
852
|
+
const response = await debugFetch(`${apiUrl}${API_ENDPOINTS.getBuild}/${buildId}`, {
|
|
853
|
+
headers: {
|
|
854
|
+
"Authorization": `Bearer ${apiKey}`
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
if (!response.ok) {
|
|
858
|
+
throw new Error(`Failed to get build status: ${response.statusText}`);
|
|
859
|
+
}
|
|
860
|
+
const build2 = await response.json();
|
|
861
|
+
spinner.text = `Building... (${build2.status || "pending"})`;
|
|
862
|
+
if (build2.status === "succeeded" || build2.status === "SUCCEEDED") {
|
|
863
|
+
return {
|
|
864
|
+
imageUri: build2.imageUri,
|
|
865
|
+
status: "succeeded"
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
if (build2.status === "failed" || build2.status === "FAILED") {
|
|
869
|
+
throw new Error(`Build failed: ${build2.errorMessage || "Unknown error"}`);
|
|
870
|
+
}
|
|
871
|
+
await new Promise((r) => setTimeout(r, 5e3));
|
|
872
|
+
attempts++;
|
|
873
|
+
}
|
|
874
|
+
throw new Error("Build timed out after 5 minutes");
|
|
875
|
+
}
|
|
876
|
+
__name(waitForBuild, "waitForBuild");
|
|
877
|
+
async function waitForDeployment(apiUrl, apiKey, deploymentId, spinner) {
|
|
878
|
+
const maxAttempts = 60;
|
|
879
|
+
let attempts = 0;
|
|
880
|
+
while (attempts < maxAttempts) {
|
|
881
|
+
const response = await debugFetch(`${apiUrl}${API_ENDPOINTS.getDeployment}/${deploymentId}`, {
|
|
882
|
+
headers: {
|
|
883
|
+
"Authorization": `Bearer ${apiKey}`
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
if (!response.ok) {
|
|
887
|
+
throw new Error(`Failed to get deployment status: ${response.statusText}`);
|
|
888
|
+
}
|
|
889
|
+
const deployment = await response.json();
|
|
890
|
+
spinner.text = `Deploying... (${deployment.status || "pending"})`;
|
|
891
|
+
if (deployment.status === "RUNNING") {
|
|
892
|
+
return {
|
|
893
|
+
functionUrl: deployment.functionUrl,
|
|
894
|
+
status: "running"
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
if (deployment.status === "FAILED") {
|
|
898
|
+
throw new Error(`Deployment failed: ${deployment.errorMessage || "Unknown error"}`);
|
|
899
|
+
}
|
|
900
|
+
await new Promise((r) => setTimeout(r, 5e3));
|
|
901
|
+
attempts++;
|
|
902
|
+
}
|
|
903
|
+
throw new Error("Deployment timed out after 5 minutes");
|
|
904
|
+
}
|
|
905
|
+
__name(waitForDeployment, "waitForDeployment");
|
|
906
|
+
async function deployCommand(folderPath, options = {}) {
|
|
907
|
+
const deployStartTime = Date.now();
|
|
908
|
+
console.log(chalk4.cyan("\nLeanMCP Deploy\n"));
|
|
909
|
+
debug2("Starting deployment...");
|
|
910
|
+
const apiKey = await getApiKey();
|
|
911
|
+
if (!apiKey) {
|
|
912
|
+
console.error(chalk4.red("Not logged in."));
|
|
913
|
+
console.log(chalk4.gray("Run `leanmcp login` first to authenticate.\n"));
|
|
914
|
+
process.exit(1);
|
|
915
|
+
}
|
|
916
|
+
const apiUrl = await getApiUrl();
|
|
917
|
+
debug2("API URL:", apiUrl);
|
|
918
|
+
const absolutePath = path6.resolve(process.cwd(), folderPath);
|
|
919
|
+
if (!await fs6.pathExists(absolutePath)) {
|
|
920
|
+
console.error(chalk4.red(`Folder not found: ${absolutePath}`));
|
|
921
|
+
process.exit(1);
|
|
922
|
+
}
|
|
923
|
+
const hasMainTs = await fs6.pathExists(path6.join(absolutePath, "main.ts"));
|
|
924
|
+
const hasPackageJson = await fs6.pathExists(path6.join(absolutePath, "package.json"));
|
|
925
|
+
if (!hasMainTs && !hasPackageJson) {
|
|
926
|
+
console.error(chalk4.red("Not a valid project folder."));
|
|
927
|
+
console.log(chalk4.gray("Expected main.ts or package.json in the folder.\n"));
|
|
928
|
+
process.exit(1);
|
|
929
|
+
}
|
|
930
|
+
debug2("Fetching existing projects...");
|
|
931
|
+
let existingProjects = [];
|
|
932
|
+
try {
|
|
933
|
+
const projectsResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}`, {
|
|
934
|
+
headers: {
|
|
935
|
+
"Authorization": `Bearer ${apiKey}`
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
if (projectsResponse.ok) {
|
|
939
|
+
existingProjects = await projectsResponse.json();
|
|
940
|
+
debug2(`Found ${existingProjects.length} existing projects`);
|
|
941
|
+
}
|
|
942
|
+
} catch (e) {
|
|
943
|
+
debug2("Could not fetch existing projects");
|
|
944
|
+
}
|
|
945
|
+
let projectName;
|
|
946
|
+
let existingProject = null;
|
|
947
|
+
let isUpdate = false;
|
|
948
|
+
let folderName = path6.basename(absolutePath);
|
|
949
|
+
if (hasPackageJson) {
|
|
950
|
+
try {
|
|
951
|
+
const pkg2 = await fs6.readJSON(path6.join(absolutePath, "package.json"));
|
|
952
|
+
folderName = pkg2.name || folderName;
|
|
953
|
+
} catch (e) {
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
const matchingProject = existingProjects.find((p) => p.name === folderName);
|
|
957
|
+
if (matchingProject) {
|
|
958
|
+
console.log(chalk4.yellow(`Project '${folderName}' already exists.
|
|
959
|
+
`));
|
|
960
|
+
const choice = await select({
|
|
961
|
+
message: "What would you like to do?",
|
|
962
|
+
choices: [
|
|
963
|
+
{
|
|
964
|
+
value: "update",
|
|
965
|
+
name: `Update existing project '${folderName}'`
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
value: "new",
|
|
969
|
+
name: "Create a new project with a random name"
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
value: "cancel",
|
|
973
|
+
name: "Cancel deployment"
|
|
974
|
+
}
|
|
975
|
+
]
|
|
976
|
+
});
|
|
977
|
+
if (choice === "cancel") {
|
|
978
|
+
console.log(chalk4.gray("\nDeployment cancelled.\n"));
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
if (choice === "update") {
|
|
982
|
+
existingProject = matchingProject;
|
|
983
|
+
projectName = matchingProject.name;
|
|
984
|
+
isUpdate = true;
|
|
985
|
+
console.log(chalk4.yellow("\nWARNING: This will replace the existing deployment."));
|
|
986
|
+
console.log(chalk4.gray("The previous version will be overwritten.\n"));
|
|
987
|
+
} else {
|
|
988
|
+
projectName = generateProjectName();
|
|
989
|
+
console.log(chalk4.cyan(`
|
|
990
|
+
Generated project name: ${chalk4.bold(projectName)}
|
|
991
|
+
`));
|
|
992
|
+
}
|
|
993
|
+
} else {
|
|
994
|
+
projectName = generateProjectName();
|
|
995
|
+
console.log(chalk4.cyan(`Generated project name: ${chalk4.bold(projectName)}`));
|
|
996
|
+
}
|
|
997
|
+
console.log(chalk4.gray(`Path: ${absolutePath}
|
|
998
|
+
`));
|
|
999
|
+
let subdomain = options.subdomain;
|
|
1000
|
+
if (!subdomain) {
|
|
1001
|
+
const suggestedSubdomain = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1002
|
+
subdomain = await input2({
|
|
1003
|
+
message: "Subdomain for your deployment:",
|
|
1004
|
+
default: suggestedSubdomain,
|
|
1005
|
+
validate: /* @__PURE__ */ __name((value) => {
|
|
1006
|
+
if (!value || value.trim().length === 0) {
|
|
1007
|
+
return "Subdomain is required";
|
|
1008
|
+
}
|
|
1009
|
+
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
1010
|
+
return "Subdomain can only contain lowercase letters, numbers, and hyphens";
|
|
1011
|
+
}
|
|
1012
|
+
if (value.length < 3) {
|
|
1013
|
+
return "Subdomain must be at least 3 characters";
|
|
1014
|
+
}
|
|
1015
|
+
return true;
|
|
1016
|
+
}, "validate")
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
const checkSpinner = ora4("Checking subdomain availability...").start();
|
|
1020
|
+
try {
|
|
1021
|
+
debug2("Checking subdomain:", subdomain);
|
|
1022
|
+
const checkResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.checkSubdomain}/${subdomain}`, {
|
|
1023
|
+
headers: {
|
|
1024
|
+
"Authorization": `Bearer ${apiKey}`
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
if (checkResponse.ok) {
|
|
1028
|
+
const result = await checkResponse.json();
|
|
1029
|
+
if (!result.available) {
|
|
1030
|
+
checkSpinner.fail(`Subdomain '${subdomain}' is already taken`);
|
|
1031
|
+
console.log(chalk4.gray("\nPlease choose a different subdomain.\n"));
|
|
1032
|
+
process.exit(1);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
checkSpinner.succeed(`Subdomain '${subdomain}' is available`);
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
checkSpinner.warn("Could not verify subdomain availability");
|
|
1038
|
+
}
|
|
1039
|
+
if (!options.skipConfirm) {
|
|
1040
|
+
console.log(chalk4.cyan("\nDeployment Details:"));
|
|
1041
|
+
console.log(chalk4.gray(` Project: ${projectName}`));
|
|
1042
|
+
console.log(chalk4.gray(` Subdomain: ${subdomain}`));
|
|
1043
|
+
console.log(chalk4.gray(` URL: https://${subdomain}.leanmcp.dev
|
|
1044
|
+
`));
|
|
1045
|
+
const shouldDeploy = await confirm2({
|
|
1046
|
+
message: "Proceed with deployment?",
|
|
1047
|
+
default: true
|
|
1048
|
+
});
|
|
1049
|
+
if (!shouldDeploy) {
|
|
1050
|
+
console.log(chalk4.gray("\nDeployment cancelled.\n"));
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
console.log();
|
|
1055
|
+
let projectId;
|
|
1056
|
+
if (isUpdate && existingProject) {
|
|
1057
|
+
projectId = existingProject.id;
|
|
1058
|
+
console.log(chalk4.gray(`Using existing project: ${projectId.substring(0, 8)}...`));
|
|
1059
|
+
} else {
|
|
1060
|
+
const projectSpinner = ora4("Creating project...").start();
|
|
1061
|
+
try {
|
|
1062
|
+
debug2("Step 1: Creating project:", projectName);
|
|
1063
|
+
const createResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}`, {
|
|
1064
|
+
method: "POST",
|
|
1065
|
+
headers: {
|
|
1066
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1067
|
+
"Content-Type": "application/json"
|
|
1068
|
+
},
|
|
1069
|
+
body: JSON.stringify({
|
|
1070
|
+
name: projectName
|
|
1071
|
+
})
|
|
1072
|
+
});
|
|
1073
|
+
if (!createResponse.ok) {
|
|
1074
|
+
const error = await createResponse.text();
|
|
1075
|
+
throw new Error(`Failed to create project: ${error}`);
|
|
1076
|
+
}
|
|
1077
|
+
const project = await createResponse.json();
|
|
1078
|
+
projectId = project.id;
|
|
1079
|
+
projectSpinner.succeed(`Project created: ${projectId.substring(0, 8)}...`);
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
projectSpinner.fail("Failed to create project");
|
|
1082
|
+
console.error(chalk4.red(`
|
|
1083
|
+
${error instanceof Error ? error.message : String(error)}`));
|
|
1084
|
+
process.exit(1);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
const uploadSpinner = ora4("Packaging and uploading...").start();
|
|
1088
|
+
try {
|
|
1089
|
+
const tempZip = path6.join(os2.tmpdir(), `leanmcp-${Date.now()}.zip`);
|
|
1090
|
+
const zipSize = await createZipArchive(absolutePath, tempZip);
|
|
1091
|
+
uploadSpinner.text = `Packaging... (${Math.round(zipSize / 1024)}KB)`;
|
|
1092
|
+
debug2("Step 2a: Getting upload URL for project:", projectId);
|
|
1093
|
+
const uploadUrlResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}/${projectId}/upload-url`, {
|
|
1094
|
+
method: "POST",
|
|
1095
|
+
headers: {
|
|
1096
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1097
|
+
"Content-Type": "application/json"
|
|
1098
|
+
},
|
|
1099
|
+
body: JSON.stringify({
|
|
1100
|
+
fileName: `${subdomain}.zip`,
|
|
1101
|
+
fileType: "application/zip",
|
|
1102
|
+
fileSize: zipSize
|
|
1103
|
+
})
|
|
1104
|
+
});
|
|
1105
|
+
if (!uploadUrlResponse.ok) {
|
|
1106
|
+
throw new Error("Failed to get upload URL");
|
|
1107
|
+
}
|
|
1108
|
+
const uploadResult = await uploadUrlResponse.json();
|
|
1109
|
+
const uploadUrl = uploadResult.url || uploadResult.uploadUrl;
|
|
1110
|
+
const s3Location = uploadResult.s3Location;
|
|
1111
|
+
debug2("Upload URL response:", JSON.stringify(uploadResult));
|
|
1112
|
+
if (!uploadUrl) {
|
|
1113
|
+
throw new Error("Backend did not return upload URL");
|
|
1114
|
+
}
|
|
1115
|
+
debug2("Step 2b: Uploading to S3...");
|
|
1116
|
+
const zipBuffer = await fs6.readFile(tempZip);
|
|
1117
|
+
const s3Response = await fetch(uploadUrl, {
|
|
1118
|
+
method: "PUT",
|
|
1119
|
+
body: zipBuffer,
|
|
1120
|
+
headers: {
|
|
1121
|
+
"Content-Type": "application/zip"
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
debug2("S3 upload response:", s3Response.status);
|
|
1125
|
+
if (!s3Response.ok) {
|
|
1126
|
+
throw new Error("Failed to upload to S3");
|
|
1127
|
+
}
|
|
1128
|
+
debug2("Step 2c: Updating project with S3 location:", s3Location);
|
|
1129
|
+
await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}/${projectId}`, {
|
|
1130
|
+
method: "PATCH",
|
|
1131
|
+
headers: {
|
|
1132
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1133
|
+
"Content-Type": "application/json"
|
|
1134
|
+
},
|
|
1135
|
+
body: JSON.stringify({
|
|
1136
|
+
s3Location
|
|
1137
|
+
})
|
|
1138
|
+
});
|
|
1139
|
+
await fs6.remove(tempZip);
|
|
1140
|
+
uploadSpinner.succeed("Project uploaded");
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
uploadSpinner.fail("Failed to upload");
|
|
1143
|
+
console.error(chalk4.red(`
|
|
1144
|
+
${error instanceof Error ? error.message : String(error)}`));
|
|
1145
|
+
process.exit(1);
|
|
1146
|
+
}
|
|
1147
|
+
const buildSpinner = ora4("Building...").start();
|
|
1148
|
+
const buildStartTime = Date.now();
|
|
1149
|
+
let buildId;
|
|
1150
|
+
let imageUri;
|
|
1151
|
+
try {
|
|
1152
|
+
debug2("Step 3: Triggering build for project:", projectId);
|
|
1153
|
+
const buildResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.triggerBuild}/${projectId}`, {
|
|
1154
|
+
method: "POST",
|
|
1155
|
+
headers: {
|
|
1156
|
+
"Authorization": `Bearer ${apiKey}`
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
if (!buildResponse.ok) {
|
|
1160
|
+
const error = await buildResponse.text();
|
|
1161
|
+
throw new Error(`Failed to trigger build: ${error}`);
|
|
1162
|
+
}
|
|
1163
|
+
const build2 = await buildResponse.json();
|
|
1164
|
+
buildId = build2.id;
|
|
1165
|
+
const buildResult = await waitForBuild(apiUrl, apiKey, buildId, buildSpinner);
|
|
1166
|
+
imageUri = buildResult.imageUri;
|
|
1167
|
+
const buildDuration = Math.round((Date.now() - buildStartTime) / 1e3);
|
|
1168
|
+
buildSpinner.succeed(`Build complete (${buildDuration}s)`);
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
buildSpinner.fail("Build failed");
|
|
1171
|
+
console.error(chalk4.red(`
|
|
1172
|
+
${error instanceof Error ? error.message : String(error)}`));
|
|
1173
|
+
process.exit(1);
|
|
1174
|
+
}
|
|
1175
|
+
const deploySpinner = ora4("Deploying to LeanMCP...").start();
|
|
1176
|
+
let deploymentId;
|
|
1177
|
+
let functionUrl;
|
|
1178
|
+
try {
|
|
1179
|
+
debug2("Step 4: Creating deployment for build:", buildId);
|
|
1180
|
+
const deployResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.createDeployment}`, {
|
|
1181
|
+
method: "POST",
|
|
1182
|
+
headers: {
|
|
1183
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1184
|
+
"Content-Type": "application/json"
|
|
1185
|
+
},
|
|
1186
|
+
body: JSON.stringify({
|
|
1187
|
+
buildId
|
|
1188
|
+
})
|
|
1189
|
+
});
|
|
1190
|
+
if (!deployResponse.ok) {
|
|
1191
|
+
const error = await deployResponse.text();
|
|
1192
|
+
throw new Error(`Failed to create deployment: ${error}`);
|
|
1193
|
+
}
|
|
1194
|
+
const deployment = await deployResponse.json();
|
|
1195
|
+
deploymentId = deployment.id;
|
|
1196
|
+
const deployResult = await waitForDeployment(apiUrl, apiKey, deploymentId, deploySpinner);
|
|
1197
|
+
functionUrl = deployResult.functionUrl;
|
|
1198
|
+
deploySpinner.succeed("Deployed");
|
|
1199
|
+
} catch (error) {
|
|
1200
|
+
deploySpinner.fail("Deployment failed");
|
|
1201
|
+
console.error(chalk4.red(`
|
|
1202
|
+
${error instanceof Error ? error.message : String(error)}`));
|
|
1203
|
+
process.exit(1);
|
|
1204
|
+
}
|
|
1205
|
+
const mappingSpinner = ora4("Configuring subdomain...").start();
|
|
1206
|
+
try {
|
|
1207
|
+
debug2("Step 5: Creating subdomain mapping:", subdomain);
|
|
1208
|
+
const mappingResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.createMapping}`, {
|
|
1209
|
+
method: "POST",
|
|
1210
|
+
headers: {
|
|
1211
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1212
|
+
"Content-Type": "application/json"
|
|
1213
|
+
},
|
|
1214
|
+
body: JSON.stringify({
|
|
1215
|
+
subdomain,
|
|
1216
|
+
lambdaUrl: functionUrl,
|
|
1217
|
+
projectId,
|
|
1218
|
+
deploymentId
|
|
1219
|
+
})
|
|
1220
|
+
});
|
|
1221
|
+
if (!mappingResponse.ok) {
|
|
1222
|
+
mappingSpinner.warn("Subdomain mapping may need manual setup");
|
|
1223
|
+
} else {
|
|
1224
|
+
mappingSpinner.succeed("Subdomain configured");
|
|
1225
|
+
}
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
mappingSpinner.warn("Subdomain mapping may need manual setup");
|
|
1228
|
+
}
|
|
1229
|
+
console.log(chalk4.green("\n" + "=".repeat(60)));
|
|
1230
|
+
console.log(chalk4.green.bold(" DEPLOYMENT SUCCESSFUL!"));
|
|
1231
|
+
console.log(chalk4.green("=".repeat(60) + "\n"));
|
|
1232
|
+
console.log(chalk4.white(" Your MCP server is now live:\n"));
|
|
1233
|
+
console.log(chalk4.cyan(` URL: `) + chalk4.white.bold(`https://${subdomain}.leanmcp.dev`));
|
|
1234
|
+
console.log();
|
|
1235
|
+
console.log(chalk4.gray(" Test endpoints:"));
|
|
1236
|
+
console.log(chalk4.gray(` curl https://${subdomain}.leanmcp.dev/health`));
|
|
1237
|
+
console.log(chalk4.gray(` curl https://${subdomain}.leanmcp.dev/mcp`));
|
|
1238
|
+
console.log();
|
|
1239
|
+
const totalDuration = Math.round((Date.now() - deployStartTime) / 1e3);
|
|
1240
|
+
console.log(chalk4.gray(` Total time: ${totalDuration}s`));
|
|
1241
|
+
console.log();
|
|
1242
|
+
console.log(chalk4.gray(` Project ID: ${projectId}`));
|
|
1243
|
+
console.log(chalk4.gray(` Build ID: ${buildId}`));
|
|
1244
|
+
console.log(chalk4.gray(` Deployment ID: ${deploymentId}`));
|
|
1245
|
+
console.log();
|
|
1246
|
+
}
|
|
1247
|
+
__name(deployCommand, "deployCommand");
|
|
1248
|
+
|
|
1249
|
+
// src/commands/projects.ts
|
|
1250
|
+
import chalk5 from "chalk";
|
|
1251
|
+
import ora5 from "ora";
|
|
1252
|
+
var API_ENDPOINT = "/api/projects";
|
|
1253
|
+
async function projectsListCommand() {
|
|
1254
|
+
const apiKey = await getApiKey();
|
|
1255
|
+
if (!apiKey) {
|
|
1256
|
+
console.error(chalk5.red("\nNot logged in."));
|
|
1257
|
+
console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
|
|
1258
|
+
process.exit(1);
|
|
1259
|
+
}
|
|
1260
|
+
const spinner = ora5("Fetching projects...").start();
|
|
1261
|
+
try {
|
|
1262
|
+
const apiUrl = await getApiUrl();
|
|
1263
|
+
const response = await fetch(`${apiUrl}${API_ENDPOINT}`, {
|
|
1264
|
+
headers: {
|
|
1265
|
+
"Authorization": `Bearer ${apiKey}`
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
if (!response.ok) {
|
|
1269
|
+
throw new Error(`Failed to fetch projects: ${response.statusText}`);
|
|
1270
|
+
}
|
|
1271
|
+
const projects = await response.json();
|
|
1272
|
+
spinner.stop();
|
|
1273
|
+
if (projects.length === 0) {
|
|
1274
|
+
console.log(chalk5.yellow("\nNo projects found."));
|
|
1275
|
+
console.log(chalk5.gray("Create one with: leanmcp deploy <folder>\n"));
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
console.log(chalk5.cyan(`
|
|
1279
|
+
Your Projects (${projects.length})
|
|
1280
|
+
`));
|
|
1281
|
+
console.log(chalk5.gray("\u2500".repeat(60)));
|
|
1282
|
+
for (const project of projects) {
|
|
1283
|
+
const statusColor = project.status === "ACTIVE" ? chalk5.green : chalk5.yellow;
|
|
1284
|
+
console.log(chalk5.white.bold(` ${project.name}`));
|
|
1285
|
+
console.log(chalk5.gray(` ID: ${project.id}`));
|
|
1286
|
+
console.log(chalk5.gray(` Status: `) + statusColor(project.status));
|
|
1287
|
+
console.log(chalk5.gray(` Created: ${new Date(project.createdAt).toLocaleDateString()}`));
|
|
1288
|
+
console.log();
|
|
1289
|
+
}
|
|
1290
|
+
} catch (error) {
|
|
1291
|
+
spinner.fail("Failed to fetch projects");
|
|
1292
|
+
console.error(chalk5.red(`
|
|
1293
|
+
${error instanceof Error ? error.message : String(error)}`));
|
|
1294
|
+
process.exit(1);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
__name(projectsListCommand, "projectsListCommand");
|
|
1298
|
+
async function projectsGetCommand(projectId) {
|
|
1299
|
+
const apiKey = await getApiKey();
|
|
1300
|
+
if (!apiKey) {
|
|
1301
|
+
console.error(chalk5.red("\nNot logged in."));
|
|
1302
|
+
console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
|
|
1303
|
+
process.exit(1);
|
|
1304
|
+
}
|
|
1305
|
+
const spinner = ora5("Fetching project...").start();
|
|
1306
|
+
try {
|
|
1307
|
+
const apiUrl = await getApiUrl();
|
|
1308
|
+
const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
|
|
1309
|
+
headers: {
|
|
1310
|
+
"Authorization": `Bearer ${apiKey}`
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
if (!response.ok) {
|
|
1314
|
+
if (response.status === 404) {
|
|
1315
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
1316
|
+
}
|
|
1317
|
+
throw new Error(`Failed to fetch project: ${response.statusText}`);
|
|
1318
|
+
}
|
|
1319
|
+
const project = await response.json();
|
|
1320
|
+
spinner.stop();
|
|
1321
|
+
console.log(chalk5.cyan("\nProject Details\n"));
|
|
1322
|
+
console.log(chalk5.gray("\u2500".repeat(60)));
|
|
1323
|
+
console.log(chalk5.white.bold(` Name: ${project.name}`));
|
|
1324
|
+
console.log(chalk5.gray(` ID: ${project.id}`));
|
|
1325
|
+
console.log(chalk5.gray(` Status: ${project.status}`));
|
|
1326
|
+
if (project.s3Location) {
|
|
1327
|
+
console.log(chalk5.gray(` S3 Location: ${project.s3Location}`));
|
|
1328
|
+
}
|
|
1329
|
+
console.log(chalk5.gray(` Created: ${new Date(project.createdAt).toLocaleString()}`));
|
|
1330
|
+
if (project.updatedAt) {
|
|
1331
|
+
console.log(chalk5.gray(` Updated: ${new Date(project.updatedAt).toLocaleString()}`));
|
|
1332
|
+
}
|
|
1333
|
+
console.log();
|
|
1334
|
+
} catch (error) {
|
|
1335
|
+
spinner.fail("Failed to fetch project");
|
|
1336
|
+
console.error(chalk5.red(`
|
|
1337
|
+
${error instanceof Error ? error.message : String(error)}`));
|
|
1338
|
+
process.exit(1);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
__name(projectsGetCommand, "projectsGetCommand");
|
|
1342
|
+
async function projectsDeleteCommand(projectId, options = {}) {
|
|
1343
|
+
const apiKey = await getApiKey();
|
|
1344
|
+
if (!apiKey) {
|
|
1345
|
+
console.error(chalk5.red("\nNot logged in."));
|
|
1346
|
+
console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
|
|
1347
|
+
process.exit(1);
|
|
1348
|
+
}
|
|
1349
|
+
if (!options.force) {
|
|
1350
|
+
const { confirm: confirm4 } = await import("@inquirer/prompts");
|
|
1351
|
+
const shouldDelete = await confirm4({
|
|
1352
|
+
message: `Are you sure you want to delete project ${projectId}?`,
|
|
1353
|
+
default: false
|
|
1354
|
+
});
|
|
1355
|
+
if (!shouldDelete) {
|
|
1356
|
+
console.log(chalk5.gray("\nDeletion cancelled.\n"));
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
const spinner = ora5("Deleting project...").start();
|
|
1361
|
+
try {
|
|
1362
|
+
const apiUrl = await getApiUrl();
|
|
1363
|
+
const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
|
|
1364
|
+
method: "DELETE",
|
|
1365
|
+
headers: {
|
|
1366
|
+
"Authorization": `Bearer ${apiKey}`
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
if (!response.ok) {
|
|
1370
|
+
if (response.status === 404) {
|
|
1371
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
1372
|
+
}
|
|
1373
|
+
throw new Error(`Failed to delete project: ${response.statusText}`);
|
|
1374
|
+
}
|
|
1375
|
+
spinner.succeed("Project deleted successfully");
|
|
1376
|
+
console.log();
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
spinner.fail("Failed to delete project");
|
|
1379
|
+
console.error(chalk5.red(`
|
|
1380
|
+
${error instanceof Error ? error.message : String(error)}`));
|
|
1381
|
+
process.exit(1);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
__name(projectsDeleteCommand, "projectsDeleteCommand");
|
|
1385
|
+
|
|
547
1386
|
// src/index.ts
|
|
548
1387
|
var require2 = createRequire(import.meta.url);
|
|
549
1388
|
var pkg = require2("../package.json");
|
|
@@ -554,17 +1393,24 @@ __name(capitalize, "capitalize");
|
|
|
554
1393
|
var program = new Command();
|
|
555
1394
|
program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version).addHelpText("after", `
|
|
556
1395
|
Examples:
|
|
557
|
-
$ leanmcp create my-app
|
|
1396
|
+
$ leanmcp create my-app # Create new project (interactive)
|
|
1397
|
+
$ leanmcp create my-app --install # Create and install deps (non-interactive)
|
|
1398
|
+
$ leanmcp create my-app --no-install # Create without installing deps
|
|
1399
|
+
$ leanmcp dev # Start development server
|
|
1400
|
+
$ leanmcp login # Authenticate with LeanMCP cloud
|
|
1401
|
+
$ leanmcp deploy ./my-app # Deploy to LeanMCP cloud
|
|
1402
|
+
$ leanmcp projects list # List your cloud projects
|
|
1403
|
+
$ leanmcp projects delete <id> # Delete a cloud project
|
|
558
1404
|
`);
|
|
559
|
-
program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").option("--allow-all", "Skip interactive confirmations and assume Yes").option("--no-dashboard", "Disable dashboard UI at / and /mcp GET endpoints").action(async (projectName, options) => {
|
|
560
|
-
const spinner =
|
|
561
|
-
const targetDir =
|
|
562
|
-
if (
|
|
1405
|
+
program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").option("--allow-all", "Skip interactive confirmations and assume Yes").option("--no-dashboard", "Disable dashboard UI at / and /mcp GET endpoints").option("--install", "Install dependencies automatically (non-interactive, no dev server)").option("--no-install", "Skip dependency installation (non-interactive)").action(async (projectName, options) => {
|
|
1406
|
+
const spinner = ora6(`Creating project ${projectName}...`).start();
|
|
1407
|
+
const targetDir = path7.join(process.cwd(), projectName);
|
|
1408
|
+
if (fs7.existsSync(targetDir)) {
|
|
563
1409
|
spinner.fail(`Folder ${projectName} already exists.`);
|
|
564
1410
|
process.exit(1);
|
|
565
1411
|
}
|
|
566
|
-
await
|
|
567
|
-
await
|
|
1412
|
+
await fs7.mkdirp(targetDir);
|
|
1413
|
+
await fs7.mkdirp(path7.join(targetDir, "mcp", "example"));
|
|
568
1414
|
const pkg2 = {
|
|
569
1415
|
name: projectName,
|
|
570
1416
|
version: "1.0.0",
|
|
@@ -595,7 +1441,7 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
|
|
|
595
1441
|
"typescript": "^5.6.3"
|
|
596
1442
|
}
|
|
597
1443
|
};
|
|
598
|
-
await
|
|
1444
|
+
await fs7.writeJSON(path7.join(targetDir, "package.json"), pkg2, {
|
|
599
1445
|
spaces: 2
|
|
600
1446
|
});
|
|
601
1447
|
const tsconfig = {
|
|
@@ -618,7 +1464,7 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
|
|
|
618
1464
|
"dist"
|
|
619
1465
|
]
|
|
620
1466
|
};
|
|
621
|
-
await
|
|
1467
|
+
await fs7.writeJSON(path7.join(targetDir, "tsconfig.json"), tsconfig, {
|
|
622
1468
|
spaces: 2
|
|
623
1469
|
});
|
|
624
1470
|
const dashboardLine = options.dashboard === false ? `
|
|
@@ -640,7 +1486,7 @@ await createHTTPServer({
|
|
|
640
1486
|
|
|
641
1487
|
console.log("\\n${projectName} MCP Server");
|
|
642
1488
|
`;
|
|
643
|
-
await
|
|
1489
|
+
await fs7.writeFile(path7.join(targetDir, "main.ts"), mainTs);
|
|
644
1490
|
const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
|
|
645
1491
|
|
|
646
1492
|
/**
|
|
@@ -760,7 +1606,7 @@ console.log("\\n${projectName} MCP Server");
|
|
|
760
1606
|
}
|
|
761
1607
|
}
|
|
762
1608
|
`;
|
|
763
|
-
await
|
|
1609
|
+
await fs7.writeFile(path7.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
|
|
764
1610
|
const gitignore = `# Logs
|
|
765
1611
|
logs
|
|
766
1612
|
*.log
|
|
@@ -909,8 +1755,8 @@ NODE_ENV=development
|
|
|
909
1755
|
|
|
910
1756
|
# Add your environment variables here
|
|
911
1757
|
`;
|
|
912
|
-
await
|
|
913
|
-
await
|
|
1758
|
+
await fs7.writeFile(path7.join(targetDir, ".gitignore"), gitignore);
|
|
1759
|
+
await fs7.writeFile(path7.join(targetDir, ".env"), env);
|
|
914
1760
|
const readme = `# ${projectName}
|
|
915
1761
|
|
|
916
1762
|
MCP Server with Streamable HTTP Transport built with LeanMCP SDK
|
|
@@ -996,18 +1842,26 @@ npx @modelcontextprotocol/inspector http://localhost:3001/mcp
|
|
|
996
1842
|
|
|
997
1843
|
MIT
|
|
998
1844
|
`;
|
|
999
|
-
await
|
|
1845
|
+
await fs7.writeFile(path7.join(targetDir, "README.md"), readme);
|
|
1000
1846
|
spinner.succeed(`Project ${projectName} created!`);
|
|
1001
|
-
console.log(
|
|
1002
|
-
console.log(
|
|
1847
|
+
console.log(chalk6.green("\nSuccess! Your MCP server is ready.\n"));
|
|
1848
|
+
console.log(chalk6.cyan(`Next, navigate to your project:
|
|
1003
1849
|
cd ${projectName}
|
|
1004
1850
|
`));
|
|
1005
|
-
const
|
|
1851
|
+
const isNonInteractive = options.install !== void 0 || options.allowAll;
|
|
1852
|
+
if (options.install === false) {
|
|
1853
|
+
console.log(chalk6.cyan("\nTo get started:"));
|
|
1854
|
+
console.log(chalk6.gray(` cd ${projectName}`));
|
|
1855
|
+
console.log(chalk6.gray(` npm install`));
|
|
1856
|
+
console.log(chalk6.gray(` npm run dev`));
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
const shouldInstall = isNonInteractive ? true : await confirm3({
|
|
1006
1860
|
message: "Would you like to install dependencies now?",
|
|
1007
1861
|
default: true
|
|
1008
1862
|
});
|
|
1009
1863
|
if (shouldInstall) {
|
|
1010
|
-
const installSpinner =
|
|
1864
|
+
const installSpinner = ora6("Installing dependencies...").start();
|
|
1011
1865
|
try {
|
|
1012
1866
|
await new Promise((resolve, reject) => {
|
|
1013
1867
|
const npmInstall = spawn3("npm", [
|
|
@@ -1027,12 +1881,18 @@ MIT
|
|
|
1027
1881
|
npmInstall.on("error", reject);
|
|
1028
1882
|
});
|
|
1029
1883
|
installSpinner.succeed("Dependencies installed successfully!");
|
|
1030
|
-
|
|
1884
|
+
if (options.install === true) {
|
|
1885
|
+
console.log(chalk6.cyan("\nTo start the development server:"));
|
|
1886
|
+
console.log(chalk6.gray(` cd ${projectName}`));
|
|
1887
|
+
console.log(chalk6.gray(` npm run dev`));
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
const shouldStartDev = options.allowAll ? true : await confirm3({
|
|
1031
1891
|
message: "Would you like to start the development server?",
|
|
1032
1892
|
default: true
|
|
1033
1893
|
});
|
|
1034
1894
|
if (shouldStartDev) {
|
|
1035
|
-
console.log(
|
|
1895
|
+
console.log(chalk6.cyan("\nStarting development server...\n"));
|
|
1036
1896
|
const devServer = spawn3("npm", [
|
|
1037
1897
|
"run",
|
|
1038
1898
|
"dev"
|
|
@@ -1046,38 +1906,38 @@ MIT
|
|
|
1046
1906
|
process.exit(0);
|
|
1047
1907
|
});
|
|
1048
1908
|
} else {
|
|
1049
|
-
console.log(
|
|
1050
|
-
console.log(
|
|
1051
|
-
console.log(
|
|
1909
|
+
console.log(chalk6.cyan("\nTo start the development server later:"));
|
|
1910
|
+
console.log(chalk6.gray(` cd ${projectName}`));
|
|
1911
|
+
console.log(chalk6.gray(` npm run dev`));
|
|
1052
1912
|
}
|
|
1053
1913
|
} catch (error) {
|
|
1054
1914
|
installSpinner.fail("Failed to install dependencies");
|
|
1055
|
-
console.error(
|
|
1056
|
-
console.log(
|
|
1057
|
-
console.log(
|
|
1058
|
-
console.log(
|
|
1915
|
+
console.error(chalk6.red(error instanceof Error ? error.message : String(error)));
|
|
1916
|
+
console.log(chalk6.cyan("\nYou can install dependencies manually:"));
|
|
1917
|
+
console.log(chalk6.gray(` cd ${projectName}`));
|
|
1918
|
+
console.log(chalk6.gray(` npm install`));
|
|
1059
1919
|
}
|
|
1060
1920
|
} else {
|
|
1061
|
-
console.log(
|
|
1062
|
-
console.log(
|
|
1063
|
-
console.log(
|
|
1064
|
-
console.log(
|
|
1921
|
+
console.log(chalk6.cyan("\nTo get started:"));
|
|
1922
|
+
console.log(chalk6.gray(` cd ${projectName}`));
|
|
1923
|
+
console.log(chalk6.gray(` npm install`));
|
|
1924
|
+
console.log(chalk6.gray(` npm run dev`));
|
|
1065
1925
|
}
|
|
1066
1926
|
});
|
|
1067
1927
|
program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
|
|
1068
1928
|
const cwd = process.cwd();
|
|
1069
|
-
const mcpDir =
|
|
1070
|
-
if (!
|
|
1071
|
-
console.error(
|
|
1929
|
+
const mcpDir = path7.join(cwd, "mcp");
|
|
1930
|
+
if (!fs7.existsSync(path7.join(cwd, "main.ts"))) {
|
|
1931
|
+
console.error(chalk6.red("ERROR: Not a LeanMCP project (main.ts missing)."));
|
|
1072
1932
|
process.exit(1);
|
|
1073
1933
|
}
|
|
1074
|
-
const serviceDir =
|
|
1075
|
-
const serviceFile =
|
|
1076
|
-
if (
|
|
1077
|
-
console.error(
|
|
1934
|
+
const serviceDir = path7.join(mcpDir, serviceName);
|
|
1935
|
+
const serviceFile = path7.join(serviceDir, "index.ts");
|
|
1936
|
+
if (fs7.existsSync(serviceDir)) {
|
|
1937
|
+
console.error(chalk6.red(`ERROR: Service ${serviceName} already exists.`));
|
|
1078
1938
|
process.exit(1);
|
|
1079
1939
|
}
|
|
1080
|
-
await
|
|
1940
|
+
await fs7.mkdirp(serviceDir);
|
|
1081
1941
|
const indexTs = `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
|
|
1082
1942
|
|
|
1083
1943
|
// Input schema for greeting
|
|
@@ -1137,14 +1997,37 @@ export class ${capitalize(serviceName)}Service {
|
|
|
1137
1997
|
}
|
|
1138
1998
|
}
|
|
1139
1999
|
`;
|
|
1140
|
-
await
|
|
1141
|
-
console.log(
|
|
1142
|
-
console.log(
|
|
1143
|
-
console.log(
|
|
1144
|
-
console.log(
|
|
1145
|
-
console.log(
|
|
1146
|
-
console.log(
|
|
2000
|
+
await fs7.writeFile(serviceFile, indexTs);
|
|
2001
|
+
console.log(chalk6.green(`\\nCreated new service: ${chalk6.bold(serviceName)}`));
|
|
2002
|
+
console.log(chalk6.gray(` File: mcp/${serviceName}/index.ts`));
|
|
2003
|
+
console.log(chalk6.gray(` Tool: greet`));
|
|
2004
|
+
console.log(chalk6.gray(` Prompt: welcomePrompt`));
|
|
2005
|
+
console.log(chalk6.gray(` Resource: getStatus`));
|
|
2006
|
+
console.log(chalk6.green(`\\nService will be automatically discovered on next server start!`));
|
|
1147
2007
|
});
|
|
1148
2008
|
program.command("dev").description("Start development server with UI hot-reload (builds @UIApp components)").action(devCommand);
|
|
1149
2009
|
program.command("start").description("Build UI components and start production server").action(startCommand);
|
|
2010
|
+
program.command("login").description("Authenticate with LeanMCP cloud using an API key").option("--debug", "Enable debug logging").action(async (options) => {
|
|
2011
|
+
if (options.debug) {
|
|
2012
|
+
setDebugMode(true);
|
|
2013
|
+
}
|
|
2014
|
+
await loginCommand();
|
|
2015
|
+
});
|
|
2016
|
+
program.command("logout").description("Remove stored API key and logout from LeanMCP cloud").action(logoutCommand);
|
|
2017
|
+
program.command("whoami").description("Show current authentication status").action(whoamiCommand);
|
|
2018
|
+
program.command("deploy [folder]").description("Deploy an MCP server to LeanMCP cloud").option("-s, --subdomain <subdomain>", "Subdomain for deployment").option("-y, --yes", "Skip confirmation prompts").option("--debug", "Enable debug logging for network calls").action(async (folder, options) => {
|
|
2019
|
+
if (options.debug) {
|
|
2020
|
+
setDebugMode(true);
|
|
2021
|
+
setDeployDebugMode(true);
|
|
2022
|
+
}
|
|
2023
|
+
const targetFolder = folder || ".";
|
|
2024
|
+
await deployCommand(targetFolder, {
|
|
2025
|
+
subdomain: options.subdomain,
|
|
2026
|
+
skipConfirm: options.yes
|
|
2027
|
+
});
|
|
2028
|
+
});
|
|
2029
|
+
var projectsCmd = program.command("projects").description("Manage LeanMCP cloud projects");
|
|
2030
|
+
projectsCmd.command("list").alias("ls").description("List all your projects").action(projectsListCommand);
|
|
2031
|
+
projectsCmd.command("get <projectId>").description("Get details of a specific project").action(projectsGetCommand);
|
|
2032
|
+
projectsCmd.command("delete <projectId>").alias("rm").description("Delete a project").option("-f, --force", "Skip confirmation prompt").action((projectId, options) => projectsDeleteCommand(projectId, options));
|
|
1150
2033
|
program.parse();
|