@leanmcp/cli 0.3.1 → 0.4.1

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 +219 -294
  2. package/dist/index.js +124 -26
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,316 +1,295 @@
1
- # @leanmcp/cli
2
-
3
- Command-line tool for creating LeanMCP projects with production-ready templates.
1
+ <p align="center">
2
+ <img
3
+ src="https://raw.githubusercontent.com/LeanMCP/leanmcp-sdk/refs/heads/main/assets/logo.svg"
4
+ alt="LeanMCP Logo"
5
+ width="400"
6
+ />
7
+ </p>
8
+
9
+ <p align="center">
10
+ <strong>@leanmcp/cli</strong><br/>
11
+ Command-line tool for creating, developing, and deploying LeanMCP projects.
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://www.npmjs.com/package/@leanmcp/cli">
16
+ <img src="https://img.shields.io/npm/v/@leanmcp/cli" alt="npm version" />
17
+ </a>
18
+ <a href="https://www.npmjs.com/package/@leanmcp/cli">
19
+ <img src="https://img.shields.io/npm/dm/@leanmcp/cli" alt="npm downloads" />
20
+ </a>
21
+ <a href="https://docs.leanmcp.com/sdk/cli">
22
+ <img src="https://img.shields.io/badge/Docs-leanmcp-0A66C2?" />
23
+ </a>
24
+ <a href="https://discord.com/invite/DsRcA3GwPy">
25
+ <img src="https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white" />
26
+ </a>
27
+ <a href="https://x.com/LeanMcp">
28
+ <img src="https://img.shields.io/badge/@LeanMCP-f5f5f5?logo=x&logoColor=000000" />
29
+ </a>
30
+ </p>
4
31
 
5
32
  ## Features
6
33
 
7
- - **Interactive setup** - Guided prompts for dependency installation and dev server
8
- - **Quick project scaffolding** - Create new MCP servers in seconds
9
- - **Complete setup** - Includes TypeScript, dependencies, and configuration
10
- - **Best practices** - Generated projects follow MCP standards
11
- - **Ready to run** - Start developing immediately with hot reload
12
- - **Example service** - Includes working examples to get started
13
- - **Pure ESM** - Modern ES modules with full TypeScript support
34
+ - **Quick Scaffolding** Create production-ready MCP servers in seconds
35
+ - **Hot Reload Development** `leanmcp dev` with UI component hot-reload
36
+ - **Cloud Deployment** Deploy to LeanMCP Cloud with custom subdomains
37
+ - **Project Management** List, view, and delete cloud projects
38
+ - **Interactive Setup** Guided prompts for dependencies and dev server
14
39
 
15
40
  ## Installation
16
41
 
17
42
  ```bash
18
- # npm
19
43
  npm install -g @leanmcp/cli
20
-
21
- # GitHub Packages
22
- npm install -g @leanmcp/cli --registry=https://npm.pkg.github.com
23
44
  ```
24
45
 
25
- Or use without installing:
46
+ Or run without installing:
26
47
  ```bash
27
48
  npx @leanmcp/cli create my-mcp-server
28
49
  ```
29
50
 
30
- ## Usage
31
-
32
- ### Create a New Project
51
+ ## Commands Overview
33
52
 
34
53
  ```bash
35
- leanmcp create <project-name>
36
- ```
37
-
38
- Or with npx:
39
- ```bash
40
- npx @leanmcp/cli create my-mcp-server
54
+ # Local development
55
+ leanmcp create <name> # Create a new project
56
+ leanmcp add <service> # Add a service to existing project
57
+ leanmcp dev # Start development server with hot-reload
58
+ leanmcp build # Build for production
59
+ leanmcp start # Start production server
60
+
61
+ # Cloud commands
62
+ leanmcp login # Authenticate with LeanMCP Cloud
63
+ leanmcp logout # Remove API key
64
+ leanmcp whoami # Show login status
65
+ leanmcp deploy <folder> # Deploy to LeanMCP Cloud
66
+ leanmcp projects list # List your cloud projects
67
+ leanmcp projects get <id> # Get project details
68
+ leanmcp projects delete <id> # Delete a project
41
69
  ```
42
70
 
43
- ### Example
71
+ ---
44
72
 
45
- ```bash
46
- $ leanmcp create my-sentiment-tool
47
- ✔ Project my-sentiment-tool created!
48
-
49
- Success! Your MCP server is ready.
73
+ ## Local Development
50
74
 
51
- Next, navigate to your project:
52
- cd my-sentiment-tool
75
+ ### create
53
76
 
54
- ? Would you like to install dependencies now? (Y/n) Yes
55
- ✔ Dependencies installed successfully!
56
- ? Would you like to start the development server? (Y/n) Yes
77
+ Create a new MCP server project:
57
78
 
58
- Starting development server...
79
+ ```bash
80
+ leanmcp create my-sentiment-tool
81
+ ```
59
82
 
60
- > my-sentiment-tool@1.0.0 dev
61
- > tsx watch main.ts
83
+ Interactive prompts will guide you through:
84
+ 1. Creating the project structure
85
+ 2. Installing dependencies (optional)
86
+ 3. Starting the dev server (optional)
62
87
 
63
- [HTTP][INFO] Starting LeanMCP HTTP Server...
64
- [HTTP][INFO] Server running on http://localhost:3001
65
- [HTTP][INFO] MCP endpoint: http://localhost:3001/mcp
88
+ **Generated structure:**
89
+ ```
90
+ my-mcp-server/
91
+ ├── main.ts # Entry point with HTTP server
92
+ ├── package.json # Dependencies and scripts
93
+ ├── tsconfig.json # TypeScript configuration
94
+ └── mcp/ # Services directory
95
+ └── example/
96
+ └── index.ts # Example service with tools
66
97
  ```
67
98
 
68
- ### Add a New Service
99
+ ### add
69
100
 
70
- After creating a project, you can quickly add new services:
101
+ Add a new service to an existing project:
71
102
 
72
103
  ```bash
73
- leanmcp add <service-name>
104
+ cd my-mcp-server
105
+ leanmcp add weather
74
106
  ```
75
107
 
76
- This command:
77
- - Creates a new service file in `mcp/<service-name>.ts`
78
- - Includes example Tool, Prompt, and Resource decorators
108
+ This:
109
+ - Creates `mcp/weather/index.ts` with example Tool, Prompt, and Resource
79
110
  - Automatically registers the service in `main.ts`
80
- - Includes schema validation examples
111
+ - Includes `@SchemaConstraint` validation examples
112
+
113
+ ### dev
81
114
 
82
- **Example:**
115
+ Start the development server with hot-reload:
83
116
 
84
117
  ```bash
85
- $ leanmcp add weather
86
- ✔ Created new service: weather
87
- File: mcp/weather.ts
88
- Tool: greet
89
- Prompt: welcomePrompt
90
- Resource: getStatus
91
-
92
- Service automatically registered in main.ts!
118
+ leanmcp dev
93
119
  ```
94
120
 
95
- The generated service includes:
96
- - **Tool** - `greet()`: A callable function with schema validation
97
- - **Prompt** - `welcomePrompt()`: A reusable prompt template
98
- - **Resource** - `getStatus()`: A data endpoint
121
+ This command:
122
+ - Scans for `@UIApp` components and builds them
123
+ - Starts the HTTP server with `tsx watch`
124
+ - Watches `mcp/` directory for changes
125
+ - Automatically rebuilds UI components when modified
126
+ - Hot-reloads when adding/removing `@UIApp` decorators
99
127
 
100
- You can then customize these to fit your needs.
128
+ ```bash
129
+ $ leanmcp dev
101
130
 
102
- ## Generated Project Structure
131
+ LeanMCP Development Server
103
132
 
104
- ```
105
- my-mcp-server/
106
- ├── main.ts # Entry point with HTTP server
107
- ├── package.json # Dependencies and scripts
108
- ├── tsconfig.json # TypeScript configuration
109
- └── mcp/ # Services directory
110
- └── example.ts # Example service with tools
111
- ```
133
+ ℹ Found 2 @UIApp component(s)
134
+ ℹ UI components built
112
135
 
113
- ## Generated Files
114
-
115
- ### main.ts
116
- Entry point that:
117
- - Loads environment variables
118
- - Creates MCP server instance
119
- - Registers services
120
- - Starts HTTP server with session management
121
-
122
- ### mcp/example.ts
123
- Example service demonstrating:
124
- - `@Tool` decorator for callable functions
125
- - `@Resource` decorator for data sources
126
- - `@Prompt` decorator for prompt templates
127
- - Class-based schema validation with `@SchemaConstraint`
128
- - Input/output type safety
129
-
130
- ### package.json
131
- Includes:
132
- - `@leanmcp/core` - Core MCP functionality
133
- - `@modelcontextprotocol/sdk` - Official MCP SDK
134
- - `express` - HTTP server
135
- - `tsx` - TypeScript execution with hot reload
136
- - All type definitions
137
-
138
- ### tsconfig.json
139
- Configured with:
140
- - ESNext modules
141
- - Decorator support
142
- - Strict type checking
143
- - Source maps
136
+ Starting development server...
144
137
 
145
- ## NPM Scripts
138
+ [HTTP][INFO] Server running on http://localhost:3001
139
+ [HTTP][INFO] MCP endpoint: http://localhost:3001/mcp
140
+ ```
146
141
 
147
- Generated projects include:
142
+ ### build
143
+
144
+ Build the project for production:
148
145
 
149
146
  ```bash
150
- npm run dev # Start with hot reload (tsx watch)
151
- npm run build # Build for production
152
- npm run start # Run production build
153
- npm run clean # Remove build artifacts
147
+ leanmcp build
154
148
  ```
155
149
 
156
- ## Development Workflow
150
+ Compiles TypeScript and bundles UI components.
157
151
 
158
- ### Interactive Setup (Recommended)
152
+ ### start
159
153
 
160
- The CLI provides an interactive setup experience:
154
+ Start the production server:
161
155
 
162
156
  ```bash
163
- # Create project
164
- leanmcp create my-mcp-server
157
+ leanmcp start
158
+ ```
165
159
 
166
- # The CLI will:
167
- # 1. Create project structure
168
- # 2. Ask if you want to install dependencies (Y/n)
169
- # 3. If yes, ask if you want to start dev server (Y/n)
170
- # 4. If yes, start server with hot reload
160
+ Runs the compiled production build.
171
161
 
172
- # If you choose "No" to installation:
173
- cd my-mcp-server
174
- npm install
175
- npm run dev
176
- ```
162
+ ---
177
163
 
178
- ### Manual Setup
164
+ ## Cloud Commands
179
165
 
180
- If you prefer manual control:
166
+ ### login
181
167
 
182
- ```bash
183
- # 1. Create project (answer "No" to prompts)
184
- leanmcp create my-mcp-server
168
+ Authenticate with LeanMCP Cloud:
185
169
 
186
- # 2. Navigate to project
187
- cd my-mcp-server
170
+ ```bash
171
+ leanmcp login
172
+ ```
188
173
 
189
- # 3. Install dependencies
190
- npm install
174
+ Steps:
175
+ 1. Go to [ship.leanmcp.com/api-keys](https://ship.leanmcp.com/api-keys)
176
+ 2. Create an API key with "BUILD_AND_DEPLOY" scope
177
+ 3. Enter the key when prompted
191
178
 
192
- # 4. Start development server
193
- npm run dev
179
+ ### logout
194
180
 
195
- # 5. Server starts on http://localhost:3001
196
- # - Endpoint: http://localhost:3001/mcp
197
- # - Health check: http://localhost:3001/health
198
- # - Hot reload enabled
181
+ Remove your API key:
199
182
 
200
- # 6. Edit files in mcp/ directory
201
- # Server automatically reloads on changes
183
+ ```bash
184
+ leanmcp logout
202
185
  ```
203
186
 
204
- ## Testing Your Server
187
+ ### whoami
188
+
189
+ Check your current login status:
205
190
 
206
- Test with curl:
207
191
  ```bash
208
- # List available tools
209
- curl http://localhost:3001/mcp \
210
- -X POST \
211
- -H "Content-Type: application/json" \
212
- -d '{
213
- "jsonrpc": "2.0",
214
- "id": 1,
215
- "method": "tools/list"
216
- }'
217
-
218
- # Call a tool
219
- curl http://localhost:3001/mcp \
220
- -X POST \
221
- -H "Content-Type: application/json" \
222
- -d '{
223
- "jsonrpc": "2.0",
224
- "id": 1,
225
- "method": "tools/call",
226
- "params": {
227
- "name": "calculate",
228
- "arguments": {
229
- "a": 10,
230
- "b": 5,
231
- "operation": "add"
232
- }
233
- }
234
- }'
192
+ leanmcp whoami
235
193
  ```
236
194
 
237
- ## Customizing Generated Projects
195
+ ### deploy
238
196
 
239
- ### Add New Services
197
+ Deploy your MCP server to LeanMCP Cloud:
240
198
 
241
- **Quick Way (Recommended):**
199
+ ```bash
200
+ leanmcp deploy .
201
+ # Or specify a folder
202
+ leanmcp deploy ./my-project
203
+ ```
242
204
 
243
- Use the `add` command to automatically generate and register a new service:
205
+ Deployment process:
206
+ 1. Creates project (or updates existing)
207
+ 2. Packages and uploads code
208
+ 3. Builds container image
209
+ 4. Deploys to serverless Lambda
210
+ 5. Configures custom subdomain
244
211
 
245
212
  ```bash
246
- leanmcp add weather
247
- ```
213
+ $ leanmcp deploy .
248
214
 
249
- This creates `mcp/weather.ts` with example Tool, Prompt, and Resource decorators, and automatically registers it in `main.ts`.
215
+ LeanMCP Deploy
250
216
 
251
- **Manual Way:**
217
+ Generated project name: swift-coral-sunset
218
+ Path: /path/to/my-project
252
219
 
253
- Create a new file in `mcp/`:
220
+ ? Subdomain for your deployment: my-api
221
+ ✔ Subdomain 'my-api' is available
254
222
 
255
- ```typescript
256
- // mcp/weather.ts
257
- import { Tool } from "@leanmcp/core";
223
+ ? Proceed with deployment? Yes
258
224
 
259
- export class WeatherService {
260
- @Tool({ description: 'Get weather for a city' })
261
- async getWeather(input: { city: string }) {
262
- // Your implementation
263
- return { temperature: 72, condition: 'sunny' };
264
- }
265
- }
266
- ```
225
+ Project created: 7f4a3b2c...
226
+ Project uploaded
227
+ Build complete (45s)
228
+ Deployed
229
+ Subdomain configured
267
230
 
268
- Register in `main.ts`:
269
- ```typescript
270
- import { WeatherService } from "./mcp/weather.js";
231
+ ============================================================
232
+ DEPLOYMENT SUCCESSFUL!
233
+ ============================================================
271
234
 
272
- server.registerService(new WeatherService());
273
- ```
235
+ Your MCP server is now live:
274
236
 
275
- ### Add Authentication
237
+ URL: https://my-api.leanmcp.dev
276
238
 
277
- Install auth package:
278
- ```bash
279
- npm install @leanmcp/auth
239
+ Test endpoints:
240
+ curl https://my-api.leanmcp.dev/health
241
+ curl https://my-api.leanmcp.dev/mcp
280
242
  ```
281
243
 
282
- See [@leanmcp/auth](../auth) documentation for details.
244
+ ### projects
283
245
 
284
- ### Configure Port
246
+ Manage your cloud projects:
285
247
 
286
- Set in environment variable:
287
248
  ```bash
288
- PORT=4000 npm run dev
249
+ # List all projects
250
+ leanmcp projects list
251
+
252
+ # Get project details
253
+ leanmcp projects get <project-id>
254
+
255
+ # Delete a project
256
+ leanmcp projects delete <project-id>
257
+ leanmcp projects delete <project-id> --force # Skip confirmation
289
258
  ```
290
259
 
291
- Or in `.env` file:
260
+ ---
261
+
262
+ ## NPM Scripts
263
+
264
+ Generated projects include:
265
+
292
266
  ```bash
293
- PORT=4000
267
+ npm run dev # Start with hot reload (tsx watch)
268
+ npm run build # Build for production
269
+ npm run start # Run production build
270
+ npm run clean # Remove build artifacts
294
271
  ```
295
272
 
296
- ## Advanced Options
273
+ ## Configuration
297
274
 
298
- ### Custom Project Location
275
+ ### Port
299
276
 
300
277
  ```bash
301
- leanmcp create my-project
302
- cd my-project
278
+ PORT=4000 npm run dev
279
+ # Or in .env file
280
+ PORT=4000
303
281
  ```
304
282
 
305
- Project is created in current directory with the specified name.
283
+ ### LeanMCP Config
306
284
 
307
- ### Modify Template
308
-
309
- The generated project is fully customizable:
310
- - Edit `main.ts` for server configuration
311
- - Add/remove services in `mcp/` directory
312
- - Modify `package.json` for additional dependencies
313
- - Update `tsconfig.json` for compiler options
285
+ Stored in `~/.leanmcp/config.json`:
286
+ ```json
287
+ {
288
+ "apiKey": "airtrain_...",
289
+ "apiUrl": "https://api.leanmcp.com",
290
+ "lastUpdated": "2024-01-15T10:30:00.000Z"
291
+ }
292
+ ```
314
293
 
315
294
  ## Troubleshooting
316
295
 
@@ -323,106 +302,52 @@ PORT=3002
323
302
 
324
303
  ### Module Not Found Errors
325
304
 
326
- Ensure you've installed dependencies:
305
+ Ensure dependencies are installed:
327
306
  ```bash
328
307
  npm install
329
308
  ```
330
309
 
331
- ### TypeScript Errors
332
-
333
- Check your `tsconfig.json` and ensure:
334
- - `experimentalDecorators: true`
335
- - `emitDecoratorMetadata: true`
310
+ ### TypeScript Decorator Errors
336
311
 
337
- ### Hot Reload Not Working
338
-
339
- Try restarting the dev server:
340
- ```bash
341
- npm run dev
312
+ Ensure your `tsconfig.json` has:
313
+ ```json
314
+ {
315
+ "compilerOptions": {
316
+ "experimentalDecorators": true,
317
+ "emitDecoratorMetadata": true
318
+ }
319
+ }
342
320
  ```
343
321
 
344
- ## Project Types
345
-
346
- Currently supports:
347
- - **MCP Server** - Standard MCP server with HTTP transport
322
+ ### Deploy: Not Logged In
348
323
 
349
- Coming soon:
350
- - MCP Server with Auth
351
- - MCP Server with Database
352
- - MCP Server with File Storage
324
+ Run `leanmcp login` first to authenticate with your API key.
353
325
 
354
- ## Examples
326
+ ### Deploy: Subdomain Taken
355
327
 
356
- See the [examples](../../examples) directory for complete working examples:
357
- - [basic-sentiment-tool](../../examples/basic-sentiment-tool) - Simple sentiment analysis
358
- - [slack-with-auth](../../examples/slack-with-auth) - Slack integration with Cognito auth
328
+ Choose a different subdomain when prompted.
359
329
 
360
330
  ## Requirements
361
331
 
362
332
  - Node.js >= 18.0.0
363
333
  - npm >= 9.0.0
364
334
 
365
- ## CLI Commands
366
-
367
- ```bash
368
- leanmcp create <name> # Create new project
369
- leanmcp add <service> # Add new service to existing project
370
- leanmcp --version # Show version
371
- leanmcp --help # Show help
372
- ```
373
-
374
- ### Command Details
335
+ ## Documentation
375
336
 
376
- #### `create <project-name>`
377
- Creates a complete MCP server project with:
378
- - Entry point (`main.ts`)
379
- - Example service with Tool, Resource, and Prompt decorators
380
- - TypeScript configuration
381
- - Package.json with all dependencies
382
- - Development and build scripts
337
+ - [Full Documentation](https://docs.leanmcp.com/sdk/cli)
383
338
 
384
- **Interactive Prompts:**
385
- - Asks if you want to install dependencies
386
- - If installed, asks if you want to start dev server
387
- - Runs commands in the project directory automatically
388
-
389
- #### `add <service-name>`
390
- Adds a new service to an existing project:
391
- - Must be run inside a LeanMCP project directory
392
- - Creates `mcp/<service-name>.ts` with template code
393
- - Automatically imports and registers in `main.ts`
394
- - Includes example Tool, Prompt, and Resource implementations
395
- - Uses schema validation with `@SchemaConstraint` decorators
396
-
397
- ## 🌟 Showcase Your MCP Server
398
-
399
- Built something cool with LeanMCP? We'd love to feature it!
339
+ ## Related Packages
400
340
 
401
- ### How to Get Featured
341
+ - [@leanmcp/core](https://www.npmjs.com/package/@leanmcp/core) Core MCP server functionality
342
+ - [@leanmcp/auth](https://www.npmjs.com/package/@leanmcp/auth) — Authentication decorators
343
+ - [@leanmcp/ui](https://www.npmjs.com/package/@leanmcp/ui) — MCP App UI components
402
344
 
403
- 1. **Build** an awesome MCP server using LeanMCP
404
- 2. **Share** your project on GitHub
405
- 3. **Submit** for showcase:
406
- - Open an issue: [Request Showcase](https://github.com/LeanMCP/leanmcp-sdk/issues/new?title=[Showcase]%20Your%20Project%20Name)
407
- - Include:
408
- - Project name and description
409
- - GitHub repository link
410
- - What makes it unique
411
- - Screenshots or demo
345
+ ## Links
412
346
 
347
+ - [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
348
+ - [NPM Package](https://www.npmjs.com/package/@leanmcp/cli)
349
+ - [LeanMCP Dashboard](https://ship.leanmcp.com)
413
350
 
414
351
  ## License
415
352
 
416
353
  MIT
417
-
418
- ## Related Packages
419
-
420
- - [@leanmcp/core](../core) - Core MCP server functionality
421
- - [@leanmcp/auth](../auth) - Authentication decorators
422
- - [@leanmcp/utils](../utils) - Utility functions
423
-
424
- ## Links
425
-
426
- - [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
427
- - [Documentation](https://github.com/LeanMCP/leanmcp-sdk#readme)
428
- - [MCP Specification](https://spec.modelcontextprotocol.io/)
package/dist/index.js CHANGED
@@ -41,7 +41,7 @@ async function scanUIApp(projectDir) {
41
41
  for (const relativeFile of tsFiles) {
42
42
  const filePath = path.join(mcpDir, relativeFile);
43
43
  const content = await fs.readFile(filePath, "utf-8");
44
- if (!content.includes("@UIApp") || !content.includes("@leanmcp/ui")) {
44
+ if (!content.includes("@UIApp") && !content.includes("@GPTApp") || !content.includes("@leanmcp/ui")) {
45
45
  continue;
46
46
  }
47
47
  const uiApps = parseUIAppDecorators(content, filePath);
@@ -55,11 +55,13 @@ function parseUIAppDecorators(content, filePath) {
55
55
  const classMatch = content.match(/export\s+class\s+(\w+)/);
56
56
  const serviceName = classMatch ? classMatch[1] : "Unknown";
57
57
  const importMap = parseImports(content, filePath);
58
- const uiAppRegex = /@UIApp\s*\(\s*\{([^}]+)\}\s*\)\s*(?:async\s+)?(\w+)/g;
58
+ const uiAppRegex = /@(UIApp|GPTApp)\s*\(\s*\{([\s\S]+?)\}\s*\)\s*(?:async\s+)?(\w+)/g;
59
59
  let match;
60
60
  while ((match = uiAppRegex.exec(content)) !== null) {
61
- const decoratorBody = match[1];
62
- const methodName = match[2];
61
+ const decoratorName = match[1];
62
+ const decoratorBody = match[2];
63
+ const methodName = match[3];
64
+ const isGPTApp = decoratorName === "GPTApp";
63
65
  let componentPath;
64
66
  let componentName;
65
67
  const stringMatch = decoratorBody.match(/component\s*:\s*['"]([^'"]+)['"]/);
@@ -88,14 +90,32 @@ function parseUIAppDecorators(content, filePath) {
88
90
  }
89
91
  if (!componentPath) continue;
90
92
  const servicePrefix = serviceName.replace(/Service$/i, "").toLowerCase();
91
- const resourceUri = `ui://${servicePrefix}/${methodName}`;
93
+ let resourceUri = `ui://${servicePrefix}/${methodName}`;
94
+ const uriMatch = decoratorBody.match(/uri\s*:\s*['"]([^'"]+)['"]/);
95
+ if (uriMatch) {
96
+ resourceUri = uriMatch[1];
97
+ }
98
+ let gptOptions = void 0;
99
+ if (isGPTApp) {
100
+ gptOptions = {};
101
+ if (decoratorBody.includes("widgetAccessible: true")) gptOptions.widgetAccessible = true;
102
+ if (decoratorBody.includes("prefersBorder: true")) gptOptions.prefersBorder = true;
103
+ const visibilityMatch = decoratorBody.match(/visibility\s*:\s*['"](public|private)['"]/);
104
+ if (visibilityMatch) gptOptions.visibility = visibilityMatch[1];
105
+ const domainMatch = decoratorBody.match(/widgetDomain\s*:\s*['"]([^'"]+)['"]/);
106
+ if (domainMatch) gptOptions.widgetDomain = domainMatch[1];
107
+ const descriptionMatch = decoratorBody.match(/widgetDescription\s*:\s*['"]([^'"]+)['"]/);
108
+ if (descriptionMatch) gptOptions.widgetDescription = descriptionMatch[1];
109
+ }
92
110
  results.push({
93
111
  servicePath: filePath,
94
112
  componentPath,
95
113
  componentName,
96
114
  resourceUri,
97
115
  methodName,
98
- serviceName
116
+ serviceName,
117
+ isGPTApp,
118
+ gptOptions
99
119
  });
100
120
  }
101
121
  return results;
@@ -130,6 +150,8 @@ __name(parseImports, "parseImports");
130
150
  import * as vite from "vite";
131
151
  import react from "@vitejs/plugin-react";
132
152
  import { viteSingleFile } from "vite-plugin-singlefile";
153
+ import tailwindcss from "tailwindcss";
154
+ import autoprefixer from "autoprefixer";
133
155
  import fs2 from "fs-extra";
134
156
  import path2 from "path";
135
157
  function resolveReactDependency(startDir, packageName) {
@@ -313,7 +335,30 @@ module.exports = {
313
335
  }
314
336
  `);
315
337
  const relativeComponentPath = path2.relative(tempDir, componentPath).replace(/\\/g, "/");
316
- await fs2.writeFile(entryJs, `
338
+ const isGPTApp = uiApp.isGPTApp;
339
+ const entryContent = isGPTApp ? `
340
+ import React, { StrictMode } from 'react';
341
+ import { createRoot } from 'react-dom/client';
342
+ import { GPTAppProvider, Toaster } from '@leanmcp/ui';
343
+ import '@leanmcp/ui/styles.css';
344
+ import './styles.css';
345
+ import { ${componentName} } from '${relativeComponentPath.replace(/\.tsx?$/, "")}';
346
+
347
+ function App() {
348
+ return (
349
+ <GPTAppProvider appName="${componentName}">
350
+ <${componentName} />
351
+ <Toaster />
352
+ </GPTAppProvider>
353
+ );
354
+ }
355
+
356
+ createRoot(document.getElementById('root')!).render(
357
+ <StrictMode>
358
+ <App />
359
+ </StrictMode>
360
+ );
361
+ ` : `
317
362
  import React, { StrictMode } from 'react';
318
363
  import { createRoot } from 'react-dom/client';
319
364
  import { AppProvider, Toaster } from '@leanmcp/ui';
@@ -340,7 +385,8 @@ createRoot(document.getElementById('root')!).render(
340
385
  <App />
341
386
  </StrictMode>
342
387
  );
343
- `);
388
+ `;
389
+ await fs2.writeFile(entryJs, entryContent);
344
390
  try {
345
391
  const reactPath = resolveReactDependency(projectDir, "react");
346
392
  const reactDomPath = resolveReactDependency(projectDir, "react-dom");
@@ -362,10 +408,10 @@ createRoot(document.getElementById('root')!).render(
362
408
  css: {
363
409
  postcss: {
364
410
  plugins: [
365
- (await import("tailwindcss")).default({
411
+ tailwindcss({
366
412
  config: tailwindConfig
367
413
  }),
368
- (await import("autoprefixer")).default
414
+ autoprefixer
369
415
  ]
370
416
  }
371
417
  },
@@ -374,6 +420,8 @@ createRoot(document.getElementById('root')!).render(
374
420
  emptyOutDir: false,
375
421
  sourcemap: isDev ? "inline" : false,
376
422
  minify: !isDev,
423
+ // Force cache invalidation between builds to reduce memory accumulation
424
+ watch: null,
377
425
  rollupOptions: {
378
426
  input: entryHtml,
379
427
  output: {
@@ -477,7 +525,15 @@ async function devCommand() {
477
525
  for (const app of uiApps) {
478
526
  const result = await buildUIComponent(app, cwd, true);
479
527
  if (result.success) {
480
- manifest[app.resourceUri] = result.htmlPath;
528
+ if (app.isGPTApp) {
529
+ manifest[app.resourceUri] = {
530
+ htmlPath: result.htmlPath,
531
+ isGPTApp: true,
532
+ gptMeta: app.gptOptions
533
+ };
534
+ } else {
535
+ manifest[app.resourceUri] = result.htmlPath;
536
+ }
481
537
  } else {
482
538
  errors.push(`${app.componentName}: ${result.error}`);
483
539
  }
@@ -543,7 +599,15 @@ async function devCommand() {
543
599
  console.log(chalk.cyan(`${action} ${app.componentName}...`));
544
600
  const result = await buildUIComponent(app, cwd, true);
545
601
  if (result.success) {
546
- manifest[app.resourceUri] = result.htmlPath;
602
+ if (app.isGPTApp) {
603
+ manifest[app.resourceUri] = {
604
+ htmlPath: result.htmlPath,
605
+ isGPTApp: true,
606
+ gptMeta: app.gptOptions
607
+ };
608
+ } else {
609
+ manifest[app.resourceUri] = result.htmlPath;
610
+ }
547
611
  if (await fs3.pathExists(app.componentPath)) {
548
612
  componentHashCache.set(app.resourceUri, computeHash(app.componentPath));
549
613
  }
@@ -556,19 +620,33 @@ async function devCommand() {
556
620
  previousUIApps = currentUIApps;
557
621
  }, 150);
558
622
  });
623
+ const isWindows = process.platform === "win32";
559
624
  let isCleaningUp = false;
560
625
  const cleanup = /* @__PURE__ */ __name(() => {
561
626
  if (isCleaningUp) return;
562
627
  isCleaningUp = true;
563
628
  console.log(chalk.gray("\nShutting down..."));
564
- if (watcher) watcher.close();
565
- devServer.kill("SIGTERM");
629
+ if (watcher) {
630
+ watcher.close();
631
+ watcher = null;
632
+ }
633
+ if (!isWindows && !devServer.killed) {
634
+ devServer.kill("SIGTERM");
635
+ }
566
636
  }, "cleanup");
567
- process.on("SIGINT", cleanup);
568
- process.on("SIGTERM", cleanup);
569
- devServer.on("exit", (code) => {
570
- if (watcher) watcher.close();
571
- process.exit(code ?? 0);
637
+ process.once("SIGINT", cleanup);
638
+ process.once("SIGTERM", cleanup);
639
+ devServer.on("error", (err) => {
640
+ console.error(chalk.red(`Dev server error: ${err.message}`));
641
+ });
642
+ devServer.on("exit", (code, signal) => {
643
+ if (watcher) {
644
+ watcher.close();
645
+ watcher = null;
646
+ }
647
+ setImmediate(() => {
648
+ process.exit(code ?? (signal ? 1 : 0));
649
+ });
572
650
  });
573
651
  }
574
652
  __name(devCommand, "devCommand");
@@ -601,7 +679,11 @@ async function buildCommand() {
601
679
  for (const app of uiApps) {
602
680
  const result = await buildUIComponent(app, cwd, false);
603
681
  if (result.success) {
604
- manifest[app.resourceUri] = result.htmlPath;
682
+ manifest[app.resourceUri] = {
683
+ htmlPath: result.htmlPath,
684
+ isGPTApp: app.isGPTApp,
685
+ gptMeta: app.gptOptions
686
+ };
605
687
  } else {
606
688
  errors.push(`${app.componentName}: ${result.error}`);
607
689
  }
@@ -676,7 +758,15 @@ async function startCommand() {
676
758
  for (const app of uiApps) {
677
759
  const result = await buildUIComponent(app, cwd, false);
678
760
  if (result.success) {
679
- manifest[app.resourceUri] = result.htmlPath;
761
+ if (app.isGPTApp) {
762
+ manifest[app.resourceUri] = {
763
+ htmlPath: result.htmlPath,
764
+ isGPTApp: true,
765
+ gptMeta: app.gptOptions
766
+ };
767
+ } else {
768
+ manifest[app.resourceUri] = result.htmlPath;
769
+ }
680
770
  } else {
681
771
  errors.push(`${app.componentName}: ${result.error}`);
682
772
  }
@@ -725,17 +815,25 @@ async function startCommand() {
725
815
  stdio: "inherit",
726
816
  shell: true
727
817
  });
818
+ const isWindows = process.platform === "win32";
728
819
  let isCleaningUp = false;
729
820
  const cleanup = /* @__PURE__ */ __name(() => {
730
821
  if (isCleaningUp) return;
731
822
  isCleaningUp = true;
732
823
  console.log(chalk3.gray("\nShutting down..."));
733
- server.kill("SIGTERM");
824
+ if (!isWindows && !server.killed) {
825
+ server.kill("SIGTERM");
826
+ }
734
827
  }, "cleanup");
735
- process.on("SIGINT", cleanup);
736
- process.on("SIGTERM", cleanup);
737
- server.on("exit", (code) => {
738
- process.exit(code ?? 0);
828
+ process.once("SIGINT", cleanup);
829
+ process.once("SIGTERM", cleanup);
830
+ server.on("error", (err) => {
831
+ console.error(chalk3.red(`Server error: ${err.message}`));
832
+ });
833
+ server.on("exit", (code, signal) => {
834
+ setImmediate(() => {
835
+ process.exit(code ?? (signal ? 1 : 0));
836
+ });
739
837
  });
740
838
  }
741
839
  __name(startCommand, "startCommand");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leanmcp/cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "Command-line interface for scaffolding LeanMCP projects",
5
5
  "bin": {
6
6
  "leanmcp": "bin/leanmcp.js"