@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,365 @@
1
+ import chalk from 'chalk';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { spawn, execSync } from 'child_process';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname } from 'path';
7
+ import chokidar from 'chokidar';
8
+ import ora from 'ora';
9
+ // ES module equivalent of __dirname
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+ // ═══════════════════════════════════════════════════════════════════════════
13
+ // BRANDING & UI COMPONENTS
14
+ // ═══════════════════════════════════════════════════════════════════════════
15
+ const NITRO_BANNER = `
16
+ ${chalk.hex('#FF6B35').bold('╔══════════════════════════════════════════════════════════════════╗')}
17
+ ${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FF6B35').bold('║')}
18
+ ${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FF6B35').bold('███╗ ██╗██╗████████╗██████╗ ██████╗ ')} ${chalk.hex('#FF6B35').bold('║')}
19
+ ${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FF8C42').bold('████╗ ██║██║╚══██╔══╝██╔══██╗██╔═══██╗')} ${chalk.hex('#FF6B35').bold('║')}
20
+ ${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FFA94D').bold('██╔██╗ ██║██║ ██║ ██████╔╝██║ ██║')} ${chalk.hex('#FF6B35').bold('║')}
21
+ ${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FFCC80').bold('██║╚██╗██║██║ ██║ ██╔══██╗██║ ██║')} ${chalk.hex('#FF6B35').bold('║')}
22
+ ${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FFE0B2').bold('██║ ╚████║██║ ██║ ██║ ██║╚██████╔╝')} ${chalk.hex('#FF6B35').bold('║')}
23
+ ${chalk.hex('#FF6B35').bold('║')} ${chalk.dim('╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ')} ${chalk.hex('#FF6B35').bold('║')}
24
+ ${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FF6B35').bold('║')}
25
+ ${chalk.hex('#FF6B35').bold('║')} ${chalk.white.bold('S T A C K')} ${chalk.dim('─────────────────────────────────────────')} ${chalk.hex('#FF6B35').bold('║')}
26
+ ${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FF6B35').bold('║')}
27
+ ${chalk.hex('#FF6B35').bold('║')} ${chalk.dim('MCP Development Framework')} ${chalk.hex('#FF6B35').bold('║')}
28
+ ${chalk.hex('#FF6B35').bold('║')} ${chalk.hex('#FF6B35').bold('║')}
29
+ ${chalk.hex('#FF6B35').bold('╚══════════════════════════════════════════════════════════════════╝')}
30
+ `;
31
+ class DevUI {
32
+ stages = [];
33
+ currentSpinner = null;
34
+ startTime = Date.now();
35
+ constructor() {
36
+ this.stages = [
37
+ { name: 'Project', status: 'pending' },
38
+ { name: 'TypeScript', status: 'pending' },
39
+ { name: 'Widgets', status: 'pending' },
40
+ { name: 'MCP Server', status: 'pending' },
41
+ ];
42
+ }
43
+ showBanner() {
44
+ console.clear();
45
+ console.log(NITRO_BANNER);
46
+ }
47
+ updateStage(name, status, message) {
48
+ const stage = this.stages.find(s => s.name === name);
49
+ if (stage) {
50
+ stage.status = status;
51
+ stage.message = message;
52
+ }
53
+ }
54
+ startSpinner(text) {
55
+ if (this.currentSpinner) {
56
+ this.currentSpinner.stop();
57
+ }
58
+ this.currentSpinner = ora({
59
+ text,
60
+ color: 'yellow',
61
+ spinner: 'dots12'
62
+ }).start();
63
+ return this.currentSpinner;
64
+ }
65
+ stopSpinner(success = true, text) {
66
+ if (this.currentSpinner) {
67
+ if (success) {
68
+ this.currentSpinner.succeed(text);
69
+ }
70
+ else {
71
+ this.currentSpinner.fail(text);
72
+ }
73
+ this.currentSpinner = null;
74
+ }
75
+ }
76
+ showReadyScreen(config) {
77
+ const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
78
+ console.log('\n');
79
+ console.log(chalk.hex('#FF6B35').bold('┌──────────────────────────────────────────────────────────────────┐'));
80
+ console.log(chalk.hex('#FF6B35').bold('│') + chalk.green.bold(' ✓ Development Server Ready ') + chalk.hex('#FF6B35').bold('│'));
81
+ console.log(chalk.hex('#FF6B35').bold('├──────────────────────────────────────────────────────────────────┤'));
82
+ console.log(chalk.hex('#FF6B35').bold('│') + ' ' + chalk.hex('#FF6B35').bold('│'));
83
+ console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.white.bold('MCP Server')} ${chalk.dim('Running (STDIO transport)')} ` + chalk.hex('#FF6B35').bold('│'));
84
+ if (config.hasWidgets) {
85
+ console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.white.bold('Widgets')} ${chalk.cyan(`http://localhost:${config.widgetsPort}`)} ` + chalk.hex('#FF6B35').bold('│'));
86
+ }
87
+ console.log(chalk.hex('#FF6B35').bold('│') + ' ' + chalk.hex('#FF6B35').bold('│'));
88
+ console.log(chalk.hex('#FF6B35').bold('├──────────────────────────────────────────────────────────────────┤'));
89
+ console.log(chalk.hex('#FF6B35').bold('│') + ' ' + chalk.hex('#FF6B35').bold('│'));
90
+ console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.yellow.bold('To use NitroStack Studio:')} ` + chalk.hex('#FF6B35').bold('│'));
91
+ console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.dim('1. Start NitroStack Studio separately')} ` + chalk.hex('#FF6B35').bold('│'));
92
+ console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.dim('2. Enter project path:')} ${chalk.cyan(config.projectPath.substring(0, 30))}${config.projectPath.length > 30 ? '...' : ' '}` + chalk.hex('#FF6B35').bold('│'));
93
+ console.log(chalk.hex('#FF6B35').bold('│') + ' ' + chalk.hex('#FF6B35').bold('│'));
94
+ console.log(chalk.hex('#FF6B35').bold('├──────────────────────────────────────────────────────────────────┤'));
95
+ console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.dim(`Started in ${elapsed}s`)} ` + chalk.hex('#FF6B35').bold('│'));
96
+ console.log(chalk.hex('#FF6B35').bold('│') + ` ${chalk.dim('Press')} ${chalk.white('Ctrl+C')} ${chalk.dim('to stop')} ` + chalk.hex('#FF6B35').bold('│'));
97
+ console.log(chalk.hex('#FF6B35').bold('│') + ' ' + chalk.hex('#FF6B35').bold('│'));
98
+ console.log(chalk.hex('#FF6B35').bold('└──────────────────────────────────────────────────────────────────┘'));
99
+ console.log('\n');
100
+ }
101
+ showShutdown() {
102
+ console.log('\n');
103
+ console.log(chalk.hex('#FF6B35').bold('┌──────────────────────────────────────────────────────────────────┐'));
104
+ console.log(chalk.hex('#FF6B35').bold('│') + chalk.yellow.bold(' Shutting down... ') + chalk.hex('#FF6B35').bold('│'));
105
+ console.log(chalk.hex('#FF6B35').bold('└──────────────────────────────────────────────────────────────────┘'));
106
+ }
107
+ showError(message) {
108
+ console.log('\n');
109
+ console.log(chalk.red.bold('┌──────────────────────────────────────────────────────────────────┐'));
110
+ console.log(chalk.red.bold('│') + chalk.red.bold(' ✗ Error ') + chalk.red.bold('│'));
111
+ console.log(chalk.red.bold('├──────────────────────────────────────────────────────────────────┤'));
112
+ console.log(chalk.red.bold('│') + ` ${chalk.white(message.substring(0, 60).padEnd(60))} ` + chalk.red.bold('│'));
113
+ console.log(chalk.red.bold('└──────────────────────────────────────────────────────────────────┘'));
114
+ }
115
+ log(message, type = 'info') {
116
+ const prefix = {
117
+ info: chalk.blue('ℹ'),
118
+ success: chalk.green('✓'),
119
+ warn: chalk.yellow('⚠'),
120
+ error: chalk.red('✗'),
121
+ }[type];
122
+ console.log(`${prefix} ${message}`);
123
+ }
124
+ }
125
+ // ═══════════════════════════════════════════════════════════════════════════
126
+ // UTILITY FUNCTIONS
127
+ // ═══════════════════════════════════════════════════════════════════════════
128
+ function isNitrostackFromNpm() {
129
+ try {
130
+ const result = execSync('npm list -g nitrostack --json', {
131
+ encoding: 'utf-8',
132
+ stdio: 'pipe'
133
+ });
134
+ const parsed = JSON.parse(result);
135
+ if (parsed.dependencies && parsed.dependencies.nitrostack) {
136
+ const nitrostackInfo = parsed.dependencies.nitrostack;
137
+ return nitrostackInfo.version && !nitrostackInfo.resolved?.includes('file:');
138
+ }
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ return false;
144
+ }
145
+ function isLocalDevelopment() {
146
+ return __dirname.includes('/src/cli/') || __dirname.includes('\\src\\cli\\');
147
+ }
148
+ function execSilent(command, options = {}) {
149
+ try {
150
+ execSync(command, {
151
+ ...options,
152
+ stdio: 'pipe',
153
+ encoding: 'utf-8',
154
+ });
155
+ return true;
156
+ }
157
+ catch {
158
+ return false;
159
+ }
160
+ }
161
+ export async function devCommand(options) {
162
+ const ui = new DevUI();
163
+ let tscWatchProcess = null;
164
+ let widgetsDevProcess = null;
165
+ let isShuttingDown = false;
166
+ const shutdown = (code = 0) => {
167
+ if (isShuttingDown)
168
+ return;
169
+ isShuttingDown = true;
170
+ ui.showShutdown();
171
+ if (tscWatchProcess) {
172
+ tscWatchProcess.kill();
173
+ tscWatchProcess = null;
174
+ }
175
+ if (widgetsDevProcess) {
176
+ widgetsDevProcess.kill();
177
+ widgetsDevProcess = null;
178
+ }
179
+ setTimeout(() => {
180
+ console.log(chalk.dim('\nGoodbye! 👋\n'));
181
+ process.exit(code);
182
+ }, 500);
183
+ };
184
+ process.on('SIGINT', () => shutdown(0));
185
+ process.on('SIGTERM', () => shutdown(0));
186
+ try {
187
+ // Show banner
188
+ ui.showBanner();
189
+ const projectPath = process.cwd();
190
+ // ─────────────────────────────────────────────────────────────────────
191
+ // STAGE 1: Validate Project
192
+ // ─────────────────────────────────────────────────────────────────────
193
+ let spinner = ui.startSpinner('Validating project...');
194
+ const packageJsonPath = path.join(projectPath, 'package.json');
195
+ if (!fs.existsSync(packageJsonPath)) {
196
+ ui.stopSpinner(false, 'package.json not found');
197
+ throw new Error('Not a NitroStack project');
198
+ }
199
+ const widgetsPath = path.join(projectPath, 'src', 'widgets');
200
+ const widgetsPackageJsonPath = path.join(widgetsPath, 'package.json');
201
+ const hasWidgets = fs.existsSync(widgetsPackageJsonPath);
202
+ ui.stopSpinner(true, `Project validated ${hasWidgets ? chalk.dim('(with widgets)') : ''}`);
203
+ // ─────────────────────────────────────────────────────────────────────
204
+ // STAGE 2: Setup Widgets (if present)
205
+ // ─────────────────────────────────────────────────────────────────────
206
+ if (hasWidgets) {
207
+ spinner = ui.startSpinner('Checking widget dependencies...');
208
+ const fromNpm = isNitrostackFromNpm();
209
+ const isLocalDev = isLocalDevelopment();
210
+ const widgetsNodeModulesPath = path.join(widgetsPath, 'node_modules');
211
+ const widgetsNitrostackPath = path.join(widgetsNodeModulesPath, '@nitrostack');
212
+ if (!fs.existsSync(widgetsNodeModulesPath) || !fs.existsSync(widgetsNitrostackPath)) {
213
+ // Link local packages if in dev mode
214
+ if (isLocalDev && !fromNpm) {
215
+ spinner.text = 'Linking local @nitrostack/widgets...';
216
+ execSilent('npm link @nitrostack/widgets', { cwd: widgetsPath });
217
+ }
218
+ spinner.text = 'Installing widget dependencies...';
219
+ const installSuccess = execSilent('npm install', { cwd: widgetsPath });
220
+ if (!installSuccess) {
221
+ ui.stopSpinner(false, 'Failed to install widget dependencies');
222
+ throw new Error('Widget dependency installation failed');
223
+ }
224
+ }
225
+ ui.stopSpinner(true, 'Widget dependencies ready');
226
+ // Check if widgets need initial build
227
+ const widgetsOutPath = path.join(widgetsPath, '.next');
228
+ if (!fs.existsSync(widgetsOutPath)) {
229
+ spinner = ui.startSpinner('Building widgets for first time...');
230
+ const buildSuccess = execSilent('npm run build', {
231
+ cwd: widgetsPath,
232
+ env: { ...process.env, NODE_ENV: 'production' }
233
+ });
234
+ if (buildSuccess) {
235
+ ui.stopSpinner(true, 'Widgets built');
236
+ }
237
+ else {
238
+ ui.stopSpinner(false, 'Widget build failed (will try dev server)');
239
+ }
240
+ }
241
+ }
242
+ // ─────────────────────────────────────────────────────────────────────
243
+ // STAGE 3: Build TypeScript
244
+ // ─────────────────────────────────────────────────────────────────────
245
+ const distIndexPath = path.join(projectPath, 'dist', 'index.js');
246
+ if (!fs.existsSync(distIndexPath)) {
247
+ spinner = ui.startSpinner('Building MCP server...');
248
+ const buildSuccess = execSilent('npx tsc', { cwd: projectPath });
249
+ if (!buildSuccess) {
250
+ ui.stopSpinner(false, 'TypeScript build failed');
251
+ throw new Error('Build failed. Please fix errors and try again.');
252
+ }
253
+ ui.stopSpinner(true, 'MCP server built');
254
+ }
255
+ // ─────────────────────────────────────────────────────────────────────
256
+ // STAGE 4: Start TypeScript Watch Mode
257
+ // ─────────────────────────────────────────────────────────────────────
258
+ spinner = ui.startSpinner('Starting TypeScript compiler...');
259
+ tscWatchProcess = spawn('npx', ['tsc', '--watch', '--preserveWatchOutput'], {
260
+ cwd: projectPath,
261
+ stdio: ['ignore', 'pipe', 'pipe'],
262
+ shell: true,
263
+ });
264
+ // Only log compilation status, suppress verbose output
265
+ let tscReady = false;
266
+ if (tscWatchProcess.stdout) {
267
+ tscWatchProcess.stdout.on('data', (data) => {
268
+ const output = data.toString();
269
+ if (output.includes('Found 0 errors') || output.includes('Watching for file changes')) {
270
+ if (!tscReady) {
271
+ tscReady = true;
272
+ }
273
+ else {
274
+ console.log(chalk.green('✓') + chalk.dim(' MCP server recompiled'));
275
+ }
276
+ }
277
+ else if (output.includes('error TS')) {
278
+ console.log(chalk.red('✗') + chalk.dim(' TypeScript error - check your code'));
279
+ }
280
+ });
281
+ }
282
+ ui.stopSpinner(true, 'TypeScript watch mode started');
283
+ // ─────────────────────────────────────────────────────────────────────
284
+ // STAGE 5: Start Widget Dev Server (if present)
285
+ // ─────────────────────────────────────────────────────────────────────
286
+ const widgetsPort = 3001;
287
+ if (hasWidgets) {
288
+ spinner = ui.startSpinner('Starting widget dev server...');
289
+ widgetsDevProcess = spawn('npm', ['run', 'dev', '--', '--port', String(widgetsPort)], {
290
+ cwd: widgetsPath,
291
+ env: { ...process.env, NODE_ENV: 'development' },
292
+ stdio: ['ignore', 'pipe', 'pipe'],
293
+ shell: true,
294
+ });
295
+ // Wait for widgets to be ready
296
+ await new Promise((resolve) => {
297
+ const timeout = setTimeout(resolve, 8000);
298
+ if (widgetsDevProcess?.stdout) {
299
+ widgetsDevProcess.stdout.on('data', (data) => {
300
+ const output = data.toString();
301
+ if (output.includes('Ready') || output.includes('started') || output.includes(String(widgetsPort))) {
302
+ clearTimeout(timeout);
303
+ resolve();
304
+ }
305
+ });
306
+ }
307
+ });
308
+ ui.stopSpinner(true, `Widget dev server running on :${widgetsPort}`);
309
+ // Set up watcher for new widget pages
310
+ const widgetPagesPath = path.join(widgetsPath, 'app');
311
+ const widgetNextCachePath = path.join(widgetsPath, '.next');
312
+ const watcher = chokidar.watch(widgetPagesPath, {
313
+ ignored: /(^|[\/\\])\../,
314
+ persistent: true,
315
+ ignoreInitial: true,
316
+ awaitWriteFinish: { stabilityThreshold: 1000, pollInterval: 100 }
317
+ });
318
+ let lastRestart = 0;
319
+ const RESTART_COOLDOWN = 3000;
320
+ watcher.on('add', (filepath) => {
321
+ if (filepath.endsWith('page.tsx') || filepath.endsWith('page.jsx')) {
322
+ const now = Date.now();
323
+ if (now - lastRestart < RESTART_COOLDOWN)
324
+ return;
325
+ console.log(chalk.yellow('⚡') + chalk.dim(' New widget detected, restarting...'));
326
+ lastRestart = Date.now();
327
+ if (widgetsDevProcess) {
328
+ widgetsDevProcess.kill();
329
+ }
330
+ if (fs.existsSync(widgetNextCachePath)) {
331
+ fs.rmSync(widgetNextCachePath, { recursive: true, force: true });
332
+ }
333
+ widgetsDevProcess = spawn('npm', ['run', 'dev', '--', '--port', String(widgetsPort)], {
334
+ cwd: widgetsPath,
335
+ env: { ...process.env, NODE_ENV: 'development' },
336
+ stdio: ['ignore', 'pipe', 'pipe'],
337
+ shell: true,
338
+ });
339
+ }
340
+ });
341
+ }
342
+ // ─────────────────────────────────────────────────────────────────────
343
+ // READY!
344
+ // ─────────────────────────────────────────────────────────────────────
345
+ ui.showReadyScreen({
346
+ widgetsPort,
347
+ hasWidgets,
348
+ mcpServerPath: distIndexPath,
349
+ projectPath,
350
+ });
351
+ // Set up log forwarding for errors only
352
+ widgetsDevProcess?.stderr?.on('data', (data) => {
353
+ const output = data.toString();
354
+ if (output.includes('error')) {
355
+ console.log(chalk.red('✗') + chalk.dim(' Widgets: ') + output.trim());
356
+ }
357
+ });
358
+ // Keep process alive
359
+ await new Promise(() => { });
360
+ }
361
+ catch (error) {
362
+ ui.showError(error instanceof Error ? error.message : String(error));
363
+ shutdown(1);
364
+ }
365
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Generates TypeScript types from tool definitions
3
+ * Scans all *.tools.ts files and extracts types from Zod schemas and examples
4
+ */
5
+ export declare function generateTypes(options?: {
6
+ output?: string;
7
+ }): Promise<void>;
8
+ //# sourceMappingURL=generate-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-types.d.ts","sourceRoot":"","sources":["../../src/commands/generate-types.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;CAAO,iBA4BrE"}
@@ -0,0 +1,219 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname } from 'path';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+ /**
8
+ * Generates TypeScript types from tool definitions
9
+ * Scans all *.tools.ts files and extracts types from Zod schemas and examples
10
+ */
11
+ export async function generateTypes(options = {}) {
12
+ const projectRoot = process.cwd();
13
+ const outputPath = options.output || path.join(projectRoot, 'src', 'types', 'generated-tools.ts');
14
+ console.log('🔍 Scanning for tool definitions...');
15
+ // Find all *.tools.ts files
16
+ const toolFiles = await findToolFiles(path.join(projectRoot, 'src'));
17
+ if (toolFiles.length === 0) {
18
+ console.log('⚠️ No tool files found');
19
+ return;
20
+ }
21
+ console.log(`📝 Found ${toolFiles.length} tool file(s)`);
22
+ // Parse tools and generate types
23
+ const types = await generateTypesFromFiles(toolFiles);
24
+ // Write output file
25
+ const outputDir = path.dirname(outputPath);
26
+ if (!fs.existsSync(outputDir)) {
27
+ fs.mkdirSync(outputDir, { recursive: true });
28
+ }
29
+ fs.writeFileSync(outputPath, types);
30
+ console.log(`✅ Generated types at: ${outputPath}`);
31
+ }
32
+ async function findToolFiles(dir) {
33
+ const files = [];
34
+ if (!fs.existsSync(dir)) {
35
+ return files;
36
+ }
37
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
38
+ for (const entry of entries) {
39
+ const fullPath = path.join(dir, entry.name);
40
+ if (entry.isDirectory() && !entry.name.includes('node_modules') && !entry.name.includes('dist')) {
41
+ files.push(...await findToolFiles(fullPath));
42
+ }
43
+ else if (entry.isFile() && entry.name.endsWith('.tools.ts')) {
44
+ files.push(fullPath);
45
+ }
46
+ }
47
+ return files;
48
+ }
49
+ async function generateTypesFromFiles(files) {
50
+ let output = `/**
51
+ * Auto-generated types from tool definitions
52
+ * DO NOT EDIT THIS FILE MANUALLY
53
+ *
54
+ * Generated by: nitrostack generate-types
55
+ * Generated at: ${new Date().toISOString()}
56
+ */
57
+
58
+ // ==========================================
59
+ // Common Types
60
+ // ==========================================
61
+
62
+ export interface ToolInput<T = unknown> {
63
+ [key: string]: T;
64
+ }
65
+
66
+ export interface ToolOutput<T = unknown> {
67
+ [key: string]: T;
68
+ }
69
+
70
+ // ==========================================
71
+ // Tool-specific Types
72
+ // ==========================================
73
+
74
+ `;
75
+ const toolTypes = new Map();
76
+ for (const file of files) {
77
+ const content = fs.readFileSync(file, 'utf-8');
78
+ // Extract @Tool decorators
79
+ const toolRegex = /@Tool\(\{[\s\S]*?name:\s*['"]([^'"]+)['"]\s*,[\s\S]*?\}\)/g;
80
+ let match;
81
+ while ((match = toolRegex.exec(content)) !== null) {
82
+ const toolName = match[1];
83
+ const decoratorContent = match[0];
84
+ // Extract inputSchema (Zod schema)
85
+ const inputSchemaMatch = decoratorContent.match(/inputSchema:\s*z\.object\(\{([\s\S]*?)\}\)/);
86
+ // Extract example response
87
+ const examplesMatch = decoratorContent.match(/examples:\s*\{[\s\S]*?response:\s*(\{[\s\S]*?\})\s*\}/);
88
+ if (inputSchemaMatch || examplesMatch) {
89
+ const pascalCaseName = toPascalCase(toolName);
90
+ // Generate input type from Zod schema
91
+ let inputType = 'Record<string, unknown>';
92
+ if (inputSchemaMatch) {
93
+ inputType = generateTypeFromZodSchema(inputSchemaMatch[1]);
94
+ }
95
+ // Generate output type from example
96
+ let outputType = 'unknown';
97
+ if (examplesMatch) {
98
+ outputType = generateTypeFromExample(examplesMatch[1]);
99
+ }
100
+ toolTypes.set(toolName, { input: inputType, output: outputType });
101
+ output += `// ${toolName}\n`;
102
+ output += `export type ${pascalCaseName}Input = ${inputType};\n`;
103
+ output += `export type ${pascalCaseName}Output = ${outputType};\n\n`;
104
+ }
105
+ }
106
+ }
107
+ // Add a union type of all tool names
108
+ output += `// ==========================================\n`;
109
+ output += `// Tool Name Union\n`;
110
+ output += `// ==========================================\n\n`;
111
+ output += `export type ToolName = ${Array.from(toolTypes.keys()).map(name => `'${name}'`).join(' | ')};\n\n`;
112
+ // Add a mapped type for inputs and outputs
113
+ output += `// ==========================================\n`;
114
+ output += `// Mapped Types\n`;
115
+ output += `// ==========================================\n\n`;
116
+ output += `export interface ToolInputs {\n`;
117
+ for (const [toolName] of toolTypes) {
118
+ const pascalCase = toPascalCase(toolName);
119
+ output += ` '${toolName}': ${pascalCase}Input;\n`;
120
+ }
121
+ output += `}\n\n`;
122
+ output += `export interface ToolOutputs {\n`;
123
+ for (const [toolName] of toolTypes) {
124
+ const pascalCase = toPascalCase(toolName);
125
+ output += ` '${toolName}': ${pascalCase}Output;\n`;
126
+ }
127
+ output += `}\n`;
128
+ return output;
129
+ }
130
+ function toPascalCase(str) {
131
+ return str
132
+ .split(/[_-]/)
133
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
134
+ .join('');
135
+ }
136
+ function generateTypeFromZodSchema(zodContent) {
137
+ // Simple parser for basic Zod schemas
138
+ // This is a simplified version - in production, you might want to use actual Zod parsing
139
+ const fields = [];
140
+ // Match field definitions like: field_name: z.string().describe('...')
141
+ const fieldRegex = /(\w+):\s*z\.(\w+)\(\)(?:\.optional\(\))?(?:\.describe\([^)]*\))?/g;
142
+ let match;
143
+ while ((match = fieldRegex.exec(zodContent)) !== null) {
144
+ const fieldName = match[1];
145
+ const zodType = match[2];
146
+ const isOptional = zodContent.includes(`${fieldName}:`) && zodContent.includes('.optional()');
147
+ let tsType = 'unknown';
148
+ switch (zodType) {
149
+ case 'string':
150
+ tsType = 'string';
151
+ break;
152
+ case 'number':
153
+ tsType = 'number';
154
+ break;
155
+ case 'boolean':
156
+ tsType = 'boolean';
157
+ break;
158
+ case 'enum':
159
+ // Extract enum values if possible
160
+ const enumMatch = zodContent.match(new RegExp(`${fieldName}:\\s*z\\.enum\\(\\[([^\\]]+)\\]\\)`));
161
+ if (enumMatch) {
162
+ tsType = enumMatch[1].replace(/'/g, '"');
163
+ }
164
+ else {
165
+ tsType = 'string';
166
+ }
167
+ break;
168
+ case 'array':
169
+ tsType = 'unknown[]';
170
+ break;
171
+ case 'object':
172
+ tsType = 'Record<string, unknown>';
173
+ break;
174
+ }
175
+ fields.push(`${fieldName}${isOptional ? '?' : ''}: ${tsType}`);
176
+ }
177
+ return fields.length > 0 ? `{\n ${fields.join(';\n ')};\n}` : 'Record<string, unknown>';
178
+ }
179
+ function generateTypeFromExample(exampleJson) {
180
+ try {
181
+ // Parse the example JSON to infer types
182
+ // This is a simplified version
183
+ const cleaned = exampleJson
184
+ .replace(/\/\/.*/g, '') // Remove comments
185
+ .trim();
186
+ const obj = eval('(' + cleaned + ')');
187
+ return generateTypeFromObject(obj);
188
+ }
189
+ catch (e) {
190
+ return 'unknown';
191
+ }
192
+ }
193
+ function generateTypeFromObject(obj, indent = ' ') {
194
+ if (obj === null || obj === undefined) {
195
+ return 'unknown';
196
+ }
197
+ if (Array.isArray(obj)) {
198
+ if (obj.length === 0) {
199
+ return 'unknown[]';
200
+ }
201
+ const itemType = generateTypeFromObject(obj[0], indent + ' ');
202
+ return `Array<${itemType}>`;
203
+ }
204
+ if (typeof obj === 'object') {
205
+ const fields = [];
206
+ for (const [key, value] of Object.entries(obj)) {
207
+ const type = generateTypeFromObject(value, indent + ' ');
208
+ fields.push(`${key}: ${type}`);
209
+ }
210
+ return `{\n${indent}${fields.join(`;\n${indent}`)};\n${indent.slice(2)}}`;
211
+ }
212
+ if (typeof obj === 'string')
213
+ return 'string';
214
+ if (typeof obj === 'number')
215
+ return 'number';
216
+ if (typeof obj === 'boolean')
217
+ return 'boolean';
218
+ return 'unknown';
219
+ }
@@ -0,0 +1,12 @@
1
+ interface GenerateOptions {
2
+ module?: string;
3
+ output?: string;
4
+ force?: boolean;
5
+ skipRelated?: boolean;
6
+ }
7
+ /**
8
+ * Generate command
9
+ */
10
+ export declare function generate(type: string, name: string, options?: GenerateOptions): Promise<void>;
11
+ export {};
12
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AA8TA,UAAU,eAAe;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6FvG"}