@nitrostack/cli 1.0.0

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 (100) hide show
  1. package/README.md +131 -0
  2. package/dist/commands/build.d.ts +6 -0
  3. package/dist/commands/build.d.ts.map +1 -0
  4. package/dist/commands/build.js +185 -0
  5. package/dist/commands/dev.d.ts +7 -0
  6. package/dist/commands/dev.d.ts.map +1 -0
  7. package/dist/commands/dev.js +365 -0
  8. package/dist/commands/generate-types.d.ts +8 -0
  9. package/dist/commands/generate-types.d.ts.map +1 -0
  10. package/dist/commands/generate-types.js +219 -0
  11. package/dist/commands/generate.d.ts +12 -0
  12. package/dist/commands/generate.d.ts.map +1 -0
  13. package/dist/commands/generate.js +375 -0
  14. package/dist/commands/init.d.ts +7 -0
  15. package/dist/commands/init.d.ts.map +1 -0
  16. package/dist/commands/init.js +324 -0
  17. package/dist/commands/install.d.ts +10 -0
  18. package/dist/commands/install.d.ts.map +1 -0
  19. package/dist/commands/install.js +80 -0
  20. package/dist/commands/start.d.ts +6 -0
  21. package/dist/commands/start.d.ts.map +1 -0
  22. package/dist/commands/start.js +70 -0
  23. package/dist/commands/upgrade.d.ts +10 -0
  24. package/dist/commands/upgrade.d.ts.map +1 -0
  25. package/dist/commands/upgrade.js +214 -0
  26. package/dist/index.d.ts +11 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +94 -0
  29. package/dist/mcp-dev-wrapper.d.ts +15 -0
  30. package/dist/mcp-dev-wrapper.d.ts.map +1 -0
  31. package/dist/mcp-dev-wrapper.js +187 -0
  32. package/dist/ui/branding.d.ts +31 -0
  33. package/dist/ui/branding.d.ts.map +1 -0
  34. package/dist/ui/branding.js +136 -0
  35. package/package.json +69 -0
  36. package/templates/typescript-oauth/.env.example +27 -0
  37. package/templates/typescript-oauth/OAUTH_SETUP.md +592 -0
  38. package/templates/typescript-oauth/README.md +263 -0
  39. package/templates/typescript-oauth/package.json +29 -0
  40. package/templates/typescript-oauth/src/app.module.ts +92 -0
  41. package/templates/typescript-oauth/src/guards/oauth.guard.ts +126 -0
  42. package/templates/typescript-oauth/src/health/system.health.ts +55 -0
  43. package/templates/typescript-oauth/src/index.ts +63 -0
  44. package/templates/typescript-oauth/src/modules/flights/booking.tools.ts +323 -0
  45. package/templates/typescript-oauth/src/modules/flights/flights.module.ts +14 -0
  46. package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +228 -0
  47. package/templates/typescript-oauth/src/modules/flights/flights.resources.ts +215 -0
  48. package/templates/typescript-oauth/src/modules/flights/flights.tools.ts +457 -0
  49. package/templates/typescript-oauth/src/services/duffel.service.ts +285 -0
  50. package/templates/typescript-oauth/src/widgets/app/airport-search/page.tsx +270 -0
  51. package/templates/typescript-oauth/src/widgets/app/flight-details/page.tsx +261 -0
  52. package/templates/typescript-oauth/src/widgets/app/flight-search-results/page.tsx +378 -0
  53. package/templates/typescript-oauth/src/widgets/app/globals.css +167 -0
  54. package/templates/typescript-oauth/src/widgets/app/layout.tsx +18 -0
  55. package/templates/typescript-oauth/src/widgets/app/order-cancellation/page.tsx +207 -0
  56. package/templates/typescript-oauth/src/widgets/app/order-summary/page.tsx +245 -0
  57. package/templates/typescript-oauth/src/widgets/app/payment-confirmation/page.tsx +152 -0
  58. package/templates/typescript-oauth/src/widgets/app/seat-selection/page.tsx +486 -0
  59. package/templates/typescript-oauth/src/widgets/next-env.d.ts +5 -0
  60. package/templates/typescript-oauth/src/widgets/next.config.js +45 -0
  61. package/templates/typescript-oauth/src/widgets/package-lock.json +4493 -0
  62. package/templates/typescript-oauth/src/widgets/package.json +24 -0
  63. package/templates/typescript-oauth/src/widgets/tsconfig.json +28 -0
  64. package/templates/typescript-oauth/src/widgets/widget-manifest.json +395 -0
  65. package/templates/typescript-oauth/tsconfig.json +23 -0
  66. package/templates/typescript-pizzaz/README.md +252 -0
  67. package/templates/typescript-pizzaz/package.json +34 -0
  68. package/templates/typescript-pizzaz/src/app.module.ts +28 -0
  69. package/templates/typescript-pizzaz/src/index.ts +30 -0
  70. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.data.ts +106 -0
  71. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.module.ts +11 -0
  72. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.service.ts +60 -0
  73. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.tools.ts +197 -0
  74. package/templates/typescript-pizzaz/src/widgets/app/layout.tsx +18 -0
  75. package/templates/typescript-pizzaz/src/widgets/app/pizza-list/page.tsx +272 -0
  76. package/templates/typescript-pizzaz/src/widgets/app/pizza-map/page.tsx +216 -0
  77. package/templates/typescript-pizzaz/src/widgets/app/pizza-shop/page.tsx +374 -0
  78. package/templates/typescript-pizzaz/src/widgets/components/CompactShopCard.tsx +144 -0
  79. package/templates/typescript-pizzaz/src/widgets/components/PizzaCard.tsx +191 -0
  80. package/templates/typescript-pizzaz/src/widgets/next.config.js +45 -0
  81. package/templates/typescript-pizzaz/src/widgets/package.json +30 -0
  82. package/templates/typescript-pizzaz/src/widgets/tsconfig.json +28 -0
  83. package/templates/typescript-pizzaz/src/widgets/widget-manifest.json +253 -0
  84. package/templates/typescript-pizzaz/tsconfig.json +30 -0
  85. package/templates/typescript-starter/README.md +320 -0
  86. package/templates/typescript-starter/package.json +25 -0
  87. package/templates/typescript-starter/src/app.module.ts +34 -0
  88. package/templates/typescript-starter/src/health/system.health.ts +55 -0
  89. package/templates/typescript-starter/src/index.ts +29 -0
  90. package/templates/typescript-starter/src/modules/calculator/calculator.module.ts +12 -0
  91. package/templates/typescript-starter/src/modules/calculator/calculator.prompts.ts +73 -0
  92. package/templates/typescript-starter/src/modules/calculator/calculator.resources.ts +59 -0
  93. package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +166 -0
  94. package/templates/typescript-starter/src/widgets/app/calculator-result/page.tsx +180 -0
  95. package/templates/typescript-starter/src/widgets/app/layout.tsx +18 -0
  96. package/templates/typescript-starter/src/widgets/next.config.js +45 -0
  97. package/templates/typescript-starter/src/widgets/package.json +24 -0
  98. package/templates/typescript-starter/src/widgets/tsconfig.json +28 -0
  99. package/templates/typescript-starter/src/widgets/widget-manifest.json +48 -0
  100. package/templates/typescript-starter/tsconfig.json +23 -0
@@ -0,0 +1,375 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import { createHeader, createSuccessBox, createErrorBox, log, spacer, nextSteps } from '../ui/branding.js';
5
+ /**
6
+ * Generator templates
7
+ */
8
+ const TEMPLATES = {
9
+ middleware: (name) => `import { Middleware, MiddlewareInterface, ExecutionContext } from 'nitrostack';
10
+
11
+ /**
12
+ * ${name}
13
+ *
14
+ * TODO: Add description
15
+ */
16
+ @Middleware()
17
+ export class ${name} implements MiddlewareInterface {
18
+ async use(context: ExecutionContext, next: () => Promise<any>) {
19
+ // Before handler
20
+ console.log(\`[\${context.toolName}] Started\`);
21
+
22
+ const result = await next();
23
+
24
+ // After handler
25
+ console.log(\`[\${context.toolName}] Completed\`);
26
+
27
+ return result;
28
+ }
29
+ }
30
+ `,
31
+ interceptor: (name) => `import { Interceptor, InterceptorInterface, ExecutionContext } from 'nitrostack';
32
+
33
+ /**
34
+ * ${name}
35
+ *
36
+ * TODO: Add description
37
+ */
38
+ @Interceptor()
39
+ export class ${name} implements InterceptorInterface {
40
+ async intercept(context: ExecutionContext, next: () => Promise<any>) {
41
+ const result = await next();
42
+
43
+ // Transform response
44
+ return {
45
+ success: true,
46
+ data: result,
47
+ timestamp: new Date().toISOString(),
48
+ };
49
+ }
50
+ }
51
+ `,
52
+ pipe: (name) => `import { Pipe, PipeInterface, ArgumentMetadata } from 'nitrostack';
53
+
54
+ /**
55
+ * ${name}
56
+ *
57
+ * TODO: Add description
58
+ */
59
+ @Pipe()
60
+ export class ${name} implements PipeInterface {
61
+ transform(value: unknown, metadata: ArgumentMetadata) {
62
+ // Validate
63
+ if (!value) {
64
+ throw new Error('Value is required');
65
+ }
66
+
67
+ // Transform
68
+ return value;
69
+ }
70
+ }
71
+ `,
72
+ filter: (name) => `import { ExceptionFilter, ExceptionFilterInterface, ExecutionContext } from 'nitrostack';
73
+
74
+ /**
75
+ * ${name}
76
+ *
77
+ * TODO: Add description
78
+ */
79
+ @ExceptionFilter()
80
+ export class ${name} implements ExceptionFilterInterface {
81
+ catch(exception: unknown, context: ExecutionContext) {
82
+ const errorMessage = exception instanceof Error ? exception.message : String(exception);
83
+ context.logger.error('Exception caught', {
84
+ tool: context.toolName,
85
+ error: errorMessage,
86
+ });
87
+
88
+ return {
89
+ error: true,
90
+ message: errorMessage,
91
+ timestamp: new Date().toISOString(),
92
+ };
93
+ }
94
+ }
95
+ `,
96
+ service: (name) => `import { Injectable } from 'nitrostack';
97
+
98
+ /**
99
+ * ${name}
100
+ *
101
+ * TODO: Add description
102
+ */
103
+ @Injectable()
104
+ export class ${name} {
105
+ // Add your service methods here
106
+
107
+ async findOne(id: string): Promise<Record<string, unknown>> {
108
+ // TODO: Implement
109
+ return { id };
110
+ }
111
+
112
+ async findAll(): Promise<Record<string, unknown>[]> {
113
+ // TODO: Implement
114
+ return [];
115
+ }
116
+
117
+ async create(data: Record<string, unknown>): Promise<Record<string, unknown>> {
118
+ // TODO: Implement
119
+ return { id: '1', ...data };
120
+ }
121
+
122
+ async update(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>> {
123
+ // TODO: Implement
124
+ return { id, ...data };
125
+ }
126
+
127
+ async delete(id: string): Promise<void> {
128
+ // TODO: Implement
129
+ }
130
+ }
131
+ `,
132
+ guard: (name) => `import { Guard, ExecutionContext } from 'nitrostack';
133
+
134
+ /**
135
+ * ${name}
136
+ *
137
+ * TODO: Add description
138
+ */
139
+ export class ${name} implements Guard {
140
+ async canActivate(context: ExecutionContext): Promise<boolean> {
141
+ // TODO: Implement your guard logic
142
+
143
+ // Example: Check if user has required role
144
+ const userRole = context.auth?.role;
145
+
146
+ if (!userRole) {
147
+ throw new Error('Authentication required');
148
+ }
149
+
150
+ // Check role
151
+ if (userRole !== 'admin') {
152
+ throw new Error('Insufficient permissions');
153
+ }
154
+
155
+ return true;
156
+ }
157
+ }
158
+ `,
159
+ health: (name) => `import { HealthCheck, HealthCheckInterface, HealthCheckResult } from 'nitrostack';
160
+
161
+ /**
162
+ * ${name}
163
+ *
164
+ * TODO: Add description
165
+ */
166
+ @HealthCheck({
167
+ name: '${name.toLowerCase().replace(/healthcheck$/, '').replace(/health$/, '')}',
168
+ description: 'TODO: Add description',
169
+ interval: 30 // Check every 30 seconds
170
+ })
171
+ export class ${name} implements HealthCheckInterface {
172
+ async check(): Promise<HealthCheckResult> {
173
+ try {
174
+ // TODO: Implement health check logic
175
+
176
+ return {
177
+ status: 'up',
178
+ message: 'Service is healthy',
179
+ };
180
+ } catch (error: unknown) {
181
+ return {
182
+ status: 'down',
183
+ message: 'Service is down',
184
+ details: error instanceof Error ? error.message : String(error),
185
+ };
186
+ }
187
+ }
188
+ }
189
+ `,
190
+ module: (name) => `import { Module } from 'nitrostack';
191
+ import { ${name}Tools } from './${name.toLowerCase()}.tools.js';
192
+ import { ${name}Resources } from './${name.toLowerCase()}.resources.js';
193
+ import { ${name}Prompts } from './${name.toLowerCase()}.prompts.js';
194
+
195
+ @Module({
196
+ name: '${name.toLowerCase()}',
197
+ description: 'TODO: Add description',
198
+ controllers: [${name}Tools, ${name}Resources, ${name}Prompts],
199
+ })
200
+ export class ${name}Module {}
201
+ `,
202
+ tools: (name) => `import { ToolDecorator as Tool, z, ExecutionContext, Injectable } from 'nitrostack';
203
+
204
+ /**
205
+ * ${name} Tools
206
+ *
207
+ * TODO: Add description
208
+ */
209
+ @Injectable()
210
+ export class ${name}Tools {
211
+ @Tool({
212
+ name: '${name.toLowerCase()}_example',
213
+ description: 'TODO: Add description',
214
+ inputSchema: z.object({
215
+ id: z.string().describe('ID parameter'),
216
+ }),
217
+ })
218
+ async exampleTool(input: { id: string }, context: ExecutionContext) {
219
+ // TODO: Implement tool logic
220
+ return { id: input.id, result: 'success' };
221
+ }
222
+ }
223
+ `,
224
+ resources: (name) => `import { ResourceDecorator as Resource, ExecutionContext } from 'nitrostack';
225
+
226
+ /**
227
+ * ${name} Resources
228
+ *
229
+ * TODO: Add description
230
+ */
231
+ export class ${name}Resources {
232
+ @Resource({
233
+ uri: '${name.toLowerCase()}://example',
234
+ name: 'Example Resource',
235
+ description: 'TODO: Add description',
236
+ mimeType: 'application/json',
237
+ })
238
+ async exampleResource(context: ExecutionContext) {
239
+ // TODO: Implement resource logic
240
+ return {
241
+ type: 'text' as const,
242
+ text: JSON.stringify({ example: 'data' }, null, 2),
243
+ };
244
+ }
245
+ }
246
+ `,
247
+ prompts: (name) => `import { PromptDecorator as Prompt, ExecutionContext } from 'nitrostack';
248
+
249
+ /**
250
+ * ${name} Prompts
251
+ *
252
+ * TODO: Add description
253
+ */
254
+ export class ${name}Prompts {
255
+ @Prompt({
256
+ name: '${name.toLowerCase()}-help',
257
+ description: 'TODO: Add description',
258
+ })
259
+ async helpPrompt(args: Record<string, unknown>, context: ExecutionContext) {
260
+ return [
261
+ {
262
+ role: 'user' as const,
263
+ content: {
264
+ type: 'text' as const,
265
+ text: 'TODO: Add prompt content',
266
+ },
267
+ },
268
+ ];
269
+ }
270
+ }
271
+ `,
272
+ };
273
+ /**
274
+ * Get file path for generated file
275
+ */
276
+ function getFilePath(type, name, module) {
277
+ const basePath = process.cwd();
278
+ if (module) {
279
+ return path.join(basePath, 'src', 'modules', module, `${module}.${type}.ts`);
280
+ }
281
+ const dirMap = {
282
+ middleware: 'middleware',
283
+ interceptor: 'interceptors',
284
+ pipe: 'pipes',
285
+ filter: 'filters',
286
+ service: 'services',
287
+ guard: 'guards',
288
+ health: 'health',
289
+ };
290
+ const dir = dirMap[type] || type;
291
+ const fileName = name.toLowerCase().replace(/\s+/g, '-') + '.ts';
292
+ return path.join(basePath, 'src', dir, fileName);
293
+ }
294
+ /**
295
+ * Generate command
296
+ */
297
+ export async function generate(type, name, options = {}) {
298
+ // Special case for types generation
299
+ if (type === 'types') {
300
+ const { generateTypes } = await import('./generate-types.js');
301
+ await generateTypes(options);
302
+ return;
303
+ }
304
+ console.log(createHeader('Generate', `Creating ${type}: ${name}`));
305
+ // Validate type
306
+ const validTypes = Object.keys(TEMPLATES);
307
+ if (!validTypes.includes(type)) {
308
+ console.log(createErrorBox('Invalid Type', `Valid types: ${validTypes.join(', ')}, types`));
309
+ process.exit(1);
310
+ }
311
+ // Get template
312
+ const template = TEMPLATES[type](name);
313
+ const generatedFiles = [];
314
+ // Determine file path
315
+ let filePath;
316
+ if (type === 'module' || type === 'tools' || type === 'resources' || type === 'prompts') {
317
+ const moduleName = options.module || name.toLowerCase();
318
+ filePath = getFilePath(type, name, moduleName);
319
+ // Create module directory if it doesn't exist
320
+ const moduleDir = path.dirname(filePath);
321
+ if (!fs.existsSync(moduleDir)) {
322
+ fs.mkdirSync(moduleDir, { recursive: true });
323
+ }
324
+ }
325
+ else {
326
+ filePath = getFilePath(type, name);
327
+ // Create directory if it doesn't exist
328
+ const dir = path.dirname(filePath);
329
+ if (!fs.existsSync(dir)) {
330
+ fs.mkdirSync(dir, { recursive: true });
331
+ }
332
+ }
333
+ // Check if file already exists
334
+ if (fs.existsSync(filePath) && !options.force) {
335
+ console.log(createErrorBox('File Exists', 'Use --force to overwrite'));
336
+ spacer();
337
+ log(`Path: ${filePath}`, 'dim');
338
+ process.exit(1);
339
+ }
340
+ // Write file
341
+ fs.writeFileSync(filePath, template, 'utf-8');
342
+ generatedFiles.push(path.relative(process.cwd(), filePath));
343
+ log(`Created ${type}: ${chalk.cyan(path.relative(process.cwd(), filePath))}`, 'success');
344
+ // If generating a module, create all related files
345
+ if (type === 'module' && !options.skipRelated) {
346
+ const moduleName = name;
347
+ const moduleDir = path.dirname(filePath);
348
+ const toolsPath = path.join(moduleDir, `${name.toLowerCase()}.tools.ts`);
349
+ const resourcesPath = path.join(moduleDir, `${name.toLowerCase()}.resources.ts`);
350
+ const promptsPath = path.join(moduleDir, `${name.toLowerCase()}.prompts.ts`);
351
+ if (!fs.existsSync(toolsPath)) {
352
+ fs.writeFileSync(toolsPath, TEMPLATES.tools(moduleName), 'utf-8');
353
+ generatedFiles.push(path.relative(process.cwd(), toolsPath));
354
+ log(`Created tools: ${chalk.cyan(path.relative(process.cwd(), toolsPath))}`, 'success');
355
+ }
356
+ if (!fs.existsSync(resourcesPath)) {
357
+ fs.writeFileSync(resourcesPath, TEMPLATES.resources(moduleName), 'utf-8');
358
+ generatedFiles.push(path.relative(process.cwd(), resourcesPath));
359
+ log(`Created resources: ${chalk.cyan(path.relative(process.cwd(), resourcesPath))}`, 'success');
360
+ }
361
+ if (!fs.existsSync(promptsPath)) {
362
+ fs.writeFileSync(promptsPath, TEMPLATES.prompts(moduleName), 'utf-8');
363
+ generatedFiles.push(path.relative(process.cwd(), promptsPath));
364
+ log(`Created prompts: ${chalk.cyan(path.relative(process.cwd(), promptsPath))}`, 'success');
365
+ }
366
+ }
367
+ // Summary
368
+ spacer();
369
+ console.log(createSuccessBox('Generation Complete', generatedFiles));
370
+ nextSteps([
371
+ 'Open the generated file and implement the TODOs',
372
+ 'Register the component in your module',
373
+ 'Build and test your changes',
374
+ ]);
375
+ }
@@ -0,0 +1,7 @@
1
+ interface InitOptions {
2
+ template: string;
3
+ skipInstall?: boolean;
4
+ }
5
+ export declare function initCommand(projectName: string | undefined, options: InitOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAiDA,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,WAAW,iBA+MtF"}
@@ -0,0 +1,324 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname } from 'path';
5
+ import chalk from 'chalk';
6
+ import inquirer from 'inquirer';
7
+ import { execSync } from 'child_process';
8
+ import { NITRO_BANNER_FULL, createSuccessBox, NitroSpinner, log, spacer, nextSteps, brand } from '../ui/branding.js';
9
+ // ES module equivalent of __dirname
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+ /**
13
+ * Check if nitrostack is installed globally from npm registry
14
+ */
15
+ function isNitrostackFromNpm() {
16
+ try {
17
+ const result = execSync('npm list -g nitrostack --json', {
18
+ encoding: 'utf-8',
19
+ stdio: 'pipe'
20
+ });
21
+ const parsed = JSON.parse(result);
22
+ if (parsed.dependencies && parsed.dependencies.nitrostack) {
23
+ const nitrostackInfo = parsed.dependencies.nitrostack;
24
+ return nitrostackInfo.version && !nitrostackInfo.resolved?.includes('file:');
25
+ }
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ return false;
31
+ }
32
+ /**
33
+ * Check if we're running nitrostack from a local development environment
34
+ */
35
+ function isLocalDevelopment() {
36
+ return __dirname.includes('/src/cli/') || __dirname.includes('\\src\\cli\\');
37
+ }
38
+ export async function initCommand(projectName, options) {
39
+ let spinner = null;
40
+ try {
41
+ // Show banner
42
+ console.log(NITRO_BANNER_FULL);
43
+ console.log(chalk.dim(' Create a new MCP server project\n'));
44
+ // Prompt for project name if not provided
45
+ if (!projectName) {
46
+ const answers = await inquirer.prompt([
47
+ {
48
+ type: 'input',
49
+ name: 'projectName',
50
+ message: chalk.white('Project name:'),
51
+ default: 'my-mcp-server',
52
+ validate: (input) => {
53
+ if (input.trim().length === 0) {
54
+ return 'Project name cannot be empty';
55
+ }
56
+ return true;
57
+ },
58
+ },
59
+ ]);
60
+ projectName = answers.projectName;
61
+ }
62
+ const finalProjectName = projectName;
63
+ const targetDir = path.join(process.cwd(), finalProjectName);
64
+ // Check if directory exists
65
+ if (fs.existsSync(targetDir)) {
66
+ const { overwrite } = await inquirer.prompt([
67
+ {
68
+ type: 'confirm',
69
+ name: 'overwrite',
70
+ message: chalk.yellow(`Directory ${finalProjectName} already exists. Overwrite?`),
71
+ default: false,
72
+ },
73
+ ]);
74
+ if (!overwrite) {
75
+ log('Cancelled', 'warning');
76
+ process.exit(0);
77
+ }
78
+ fs.removeSync(targetDir);
79
+ }
80
+ // Prompt for template and details
81
+ const answers = await inquirer.prompt([
82
+ {
83
+ type: 'list',
84
+ name: 'template',
85
+ message: chalk.white('Choose a template:'),
86
+ choices: [
87
+ {
88
+ name: `${brand.accent('Starter')} ${chalk.dim('Simple calculator for learning basics')}`,
89
+ value: 'typescript-starter',
90
+ },
91
+ {
92
+ name: `${brand.accent('Advanced')} ${chalk.dim('Pizza shop finder with maps & widgets')}`,
93
+ value: 'typescript-pizzaz',
94
+ },
95
+ {
96
+ name: `${brand.accent('OAuth')} ${chalk.dim('Flight booking with OAuth 2.1 auth')}`,
97
+ value: 'typescript-oauth',
98
+ },
99
+ ],
100
+ default: 'typescript-starter',
101
+ },
102
+ {
103
+ type: 'input',
104
+ name: 'description',
105
+ message: chalk.white('Description:'),
106
+ default: 'My awesome MCP server',
107
+ },
108
+ {
109
+ type: 'input',
110
+ name: 'author',
111
+ message: chalk.white('Author:'),
112
+ default: '',
113
+ },
114
+ ]);
115
+ spacer();
116
+ spinner = new NitroSpinner('Creating project structure...').start();
117
+ // Create project directory
118
+ fs.mkdirSync(targetDir, { recursive: true });
119
+ // Get template path - templates are in the CLI package root
120
+ const templateDir = path.join(__dirname, '../../templates', answers.template);
121
+ // Copy template files
122
+ if (fs.existsSync(templateDir)) {
123
+ fs.copySync(templateDir, targetDir);
124
+ }
125
+ else {
126
+ await createProjectFromScratch(targetDir, finalProjectName, answers);
127
+ }
128
+ // Update package.json
129
+ const packageJsonPath = path.join(targetDir, 'package.json');
130
+ if (fs.existsSync(packageJsonPath)) {
131
+ const packageJson = fs.readJSONSync(packageJsonPath);
132
+ packageJson.name = finalProjectName;
133
+ packageJson.description = answers.description;
134
+ packageJson.author = answers.author;
135
+ fs.writeJSONSync(packageJsonPath, packageJson, { spaces: 2 });
136
+ }
137
+ spinner.succeed('Project created');
138
+ // Install dependencies
139
+ if (!options.skipInstall) {
140
+ spinner = new NitroSpinner('Installing dependencies...').start();
141
+ try {
142
+ execSync('npm install', {
143
+ cwd: targetDir,
144
+ stdio: 'pipe',
145
+ });
146
+ spinner.succeed('Dependencies installed');
147
+ }
148
+ catch {
149
+ spinner.warn('Dependencies installation failed - run npm install manually');
150
+ }
151
+ }
152
+ // Handle widgets
153
+ if (!options.skipInstall && ['typescript-starter', 'typescript-pizzaz', 'typescript-oauth'].includes(answers.template)) {
154
+ const fromNpm = isNitrostackFromNpm();
155
+ const isLocalDev = isLocalDevelopment();
156
+ if (isLocalDev && !fromNpm) {
157
+ // Local development - link nitrostack
158
+ spinner = new NitroSpinner('Linking local nitrostack...').start();
159
+ try {
160
+ execSync('npm link nitrostack', { cwd: targetDir, stdio: 'pipe' });
161
+ spinner.succeed('Local nitrostack linked');
162
+ }
163
+ catch {
164
+ spinner.warn('Failed to link nitrostack');
165
+ }
166
+ spinner = new NitroSpinner('Installing widget dependencies...').start();
167
+ try {
168
+ const widgetsDir = path.join(targetDir, 'src', 'widgets');
169
+ execSync('npm link nitrostack', { cwd: widgetsDir, stdio: 'pipe' });
170
+ execSync('npm install', { cwd: widgetsDir, stdio: 'pipe' });
171
+ spinner.succeed('Widget dependencies installed');
172
+ }
173
+ catch {
174
+ spinner.warn('Widget dependencies will be installed on first run');
175
+ }
176
+ }
177
+ else {
178
+ // Published package
179
+ spinner = new NitroSpinner('Installing widget dependencies...').start();
180
+ try {
181
+ const widgetsDir = path.join(targetDir, 'src', 'widgets');
182
+ execSync('npm install', { cwd: widgetsDir, stdio: 'pipe' });
183
+ execSync('npm install nitrostack@latest', { cwd: widgetsDir, stdio: 'pipe' });
184
+ spinner.succeed('Widget dependencies installed');
185
+ }
186
+ catch {
187
+ spinner.warn('Widget dependencies will be installed on first run');
188
+ }
189
+ }
190
+ }
191
+ // Success summary
192
+ spacer();
193
+ console.log(createSuccessBox('Project Ready', [
194
+ `Name: ${finalProjectName}`,
195
+ `Template: ${answers.template}`,
196
+ `Path: ${targetDir}`,
197
+ ]));
198
+ // Next steps
199
+ const steps = [
200
+ `cd ${finalProjectName}`,
201
+ ];
202
+ if (options.skipInstall) {
203
+ steps.push('npm install');
204
+ }
205
+ if (answers.template === 'typescript-oauth') {
206
+ steps.push('cp .env.example .env # Configure OAuth');
207
+ }
208
+ steps.push('npm run dev');
209
+ nextSteps(steps);
210
+ // Template-specific tips
211
+ if (answers.template === 'typescript-oauth') {
212
+ console.log(chalk.dim(' OAuth Setup: See OAUTH_SETUP.md for provider guides\n'));
213
+ }
214
+ else if (answers.template === 'typescript-pizzaz') {
215
+ console.log(chalk.dim(' Mapbox (optional): Get free key from mapbox.com\n'));
216
+ }
217
+ console.log(chalk.dim(' Happy coding! 🎉\n'));
218
+ }
219
+ catch (error) {
220
+ if (spinner) {
221
+ spinner.fail('Failed to create project');
222
+ }
223
+ spacer();
224
+ log(error instanceof Error ? error.message : String(error), 'error');
225
+ process.exit(1);
226
+ }
227
+ }
228
+ async function createProjectFromScratch(targetDir, projectName, answers) {
229
+ const packageJson = {
230
+ name: projectName,
231
+ version: '1.0.0',
232
+ description: answers.description,
233
+ author: answers.author,
234
+ main: 'dist/index.js',
235
+ scripts: {
236
+ dev: 'tsx watch src/index.ts',
237
+ build: 'tsc',
238
+ start: 'node dist/index.js',
239
+ },
240
+ dependencies: {
241
+ nitrostack: '^1.0.0',
242
+ dotenv: '^16.3.1',
243
+ },
244
+ devDependencies: {
245
+ '@types/node': '^20.10.5',
246
+ typescript: '^5.3.3',
247
+ tsx: '^4.7.0',
248
+ },
249
+ };
250
+ fs.writeJSONSync(path.join(targetDir, 'package.json'), packageJson, { spaces: 2 });
251
+ const tsconfig = {
252
+ compilerOptions: {
253
+ target: 'ES2022',
254
+ module: 'commonjs',
255
+ outDir: './dist',
256
+ rootDir: './src',
257
+ strict: true,
258
+ esModuleInterop: true,
259
+ skipLibCheck: true,
260
+ forceConsistentCasingInFileNames: true,
261
+ },
262
+ include: ['src/**/*'],
263
+ exclude: ['node_modules'],
264
+ };
265
+ fs.writeJSONSync(path.join(targetDir, 'tsconfig.json'), tsconfig, { spaces: 2 });
266
+ fs.mkdirSync(path.join(targetDir, 'src'), { recursive: true });
267
+ const indexTs = `import { createServer, createTool, z } from 'nitrostack';
268
+
269
+ const server = createServer({
270
+ name: '${projectName}',
271
+ version: '1.0.0',
272
+ description: '${answers.description}',
273
+ });
274
+
275
+ server.tool(
276
+ createTool({
277
+ name: 'hello',
278
+ description: 'Say hello to someone',
279
+ inputSchema: z.object({
280
+ name: z.string().describe('The name to greet'),
281
+ }),
282
+ handler: async (input, context) => {
283
+ context.logger.info(\`Greeting \${input.name}\`);
284
+ return \`Hello, \${input.name}! 👋\`;
285
+ },
286
+ })
287
+ );
288
+
289
+ server.start().catch((error) => {
290
+ console.error('Failed to start server:', error);
291
+ process.exit(1);
292
+ });
293
+ `;
294
+ fs.writeFileSync(path.join(targetDir, 'src', 'index.ts'), indexTs);
295
+ fs.writeFileSync(path.join(targetDir, '.env'), '# Environment variables\n');
296
+ const gitignore = `node_modules/
297
+ dist/
298
+ .env
299
+ .env.local
300
+ *.log
301
+ `;
302
+ fs.writeFileSync(path.join(targetDir, '.gitignore'), gitignore);
303
+ const readme = `# ${projectName}
304
+
305
+ ${answers.description}
306
+
307
+ ## Getting Started
308
+
309
+ \`\`\`bash
310
+ npm install
311
+ npm run dev
312
+ \`\`\`
313
+
314
+ ## Building for Production
315
+
316
+ \`\`\`bash
317
+ npm run build
318
+ npm start
319
+ \`\`\`
320
+
321
+ Built with [NitroStack](https://nitrostack.dev) âš¡
322
+ `;
323
+ fs.writeFileSync(path.join(targetDir, 'README.md'), readme);
324
+ }