@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,55 @@
1
+ import { HealthCheck, HealthCheckInterface, HealthCheckResult } from 'nitrostack';
2
+
3
+ /**
4
+ * System Health Check
5
+ *
6
+ * Monitors system resources and uptime
7
+ */
8
+ @HealthCheck({
9
+ name: 'system',
10
+ description: 'System resource and uptime check',
11
+ interval: 30 // Check every 30 seconds
12
+ })
13
+ export class SystemHealthCheck implements HealthCheckInterface {
14
+ private startTime: number;
15
+
16
+ constructor() {
17
+ this.startTime = Date.now();
18
+ }
19
+
20
+ async check(): Promise<HealthCheckResult> {
21
+ try {
22
+ const memoryUsage = process.memoryUsage();
23
+ const uptime = Date.now() - this.startTime;
24
+ const uptimeSeconds = Math.floor(uptime / 1000);
25
+
26
+ // Convert memory to MB
27
+ const memoryUsedMB = Math.round(memoryUsage.heapUsed / 1024 / 1024);
28
+ const memoryTotalMB = Math.round(memoryUsage.heapTotal / 1024 / 1024);
29
+
30
+ // Consider unhealthy if memory usage is > 90%
31
+ const memoryPercent = (memoryUsage.heapUsed / memoryUsage.heapTotal) * 100;
32
+ const isHealthy = memoryPercent < 90;
33
+
34
+ return {
35
+ status: isHealthy ? 'up' : 'degraded',
36
+ message: isHealthy
37
+ ? 'System is healthy'
38
+ : 'High memory usage detected',
39
+ details: {
40
+ uptime: `${uptimeSeconds}s`,
41
+ memory: `${memoryUsedMB}MB / ${memoryTotalMB}MB (${Math.round(memoryPercent)}%)`,
42
+ pid: process.pid,
43
+ nodeVersion: process.version,
44
+ },
45
+ };
46
+ } catch (error: any) {
47
+ return {
48
+ status: 'down',
49
+ message: 'System health check failed',
50
+ details: error.message,
51
+ };
52
+ }
53
+ }
54
+ }
55
+
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Calculator MCP Server
3
+ *
4
+ * Main entry point for the MCP server.
5
+ * Uses the @McpApp decorator pattern for clean, NestJS-style architecture.
6
+ *
7
+ * Transport Configuration:
8
+ * - Development (NODE_ENV=development): STDIO only
9
+ * - Production (NODE_ENV=production): Dual transport (STDIO + HTTP SSE)
10
+ */
11
+
12
+ import 'dotenv/config';
13
+ import { McpApplicationFactory } from 'nitrostack';
14
+ import { AppModule } from './app.module.js';
15
+
16
+ /**
17
+ * Bootstrap the application
18
+ */
19
+ async function bootstrap() {
20
+ // Create and start the MCP server
21
+ const server = await McpApplicationFactory.create(AppModule);
22
+ await server.start();
23
+ }
24
+
25
+ // Start the application
26
+ bootstrap().catch((error) => {
27
+ console.error('❌ Failed to start server:', error);
28
+ process.exit(1);
29
+ });
@@ -0,0 +1,12 @@
1
+ import { Module } from 'nitrostack';
2
+ import { CalculatorTools } from './calculator.tools.js';
3
+ import { CalculatorResources } from './calculator.resources.js';
4
+ import { CalculatorPrompts } from './calculator.prompts.js';
5
+
6
+ @Module({
7
+ name: 'calculator',
8
+ description: 'Basic arithmetic calculator',
9
+ controllers: [CalculatorTools, CalculatorResources, CalculatorPrompts]
10
+ })
11
+ export class CalculatorModule {}
12
+
@@ -0,0 +1,73 @@
1
+ import { PromptDecorator as Prompt, ExecutionContext } from 'nitrostack';
2
+
3
+ export class CalculatorPrompts {
4
+ @Prompt({
5
+ name: 'calculator_help',
6
+ description: 'Get help with calculator operations',
7
+ arguments: [
8
+ {
9
+ name: 'operation',
10
+ description: 'The operation to get help with (optional)',
11
+ required: false
12
+ }
13
+ ]
14
+ })
15
+ async getHelp(args: any, ctx: ExecutionContext) {
16
+ ctx.logger.info('Generating calculator help prompt');
17
+
18
+ const operation = args.operation;
19
+
20
+ if (operation) {
21
+ // Help for specific operation
22
+ const helpText = this.getOperationHelp(operation);
23
+ return [
24
+ {
25
+ role: 'user' as const,
26
+ content: `How do I use the ${operation} operation in the calculator?`
27
+ },
28
+ {
29
+ role: 'assistant' as const,
30
+ content: helpText
31
+ }
32
+ ];
33
+ }
34
+
35
+ // General help
36
+ return [
37
+ {
38
+ role: 'user' as const,
39
+ content: 'How do I use the calculator?'
40
+ },
41
+ {
42
+ role: 'assistant' as const,
43
+ content: `The calculator supports four basic operations:
44
+
45
+ 1. **Addition** - Add two numbers together
46
+ Example: calculate(operation="add", a=5, b=3) = 8
47
+
48
+ 2. **Subtraction** - Subtract one number from another
49
+ Example: calculate(operation="subtract", a=10, b=4) = 6
50
+
51
+ 3. **Multiplication** - Multiply two numbers
52
+ Example: calculate(operation="multiply", a=6, b=7) = 42
53
+
54
+ 4. **Division** - Divide one number by another
55
+ Example: calculate(operation="divide", a=20, b=5) = 4
56
+
57
+ Just call the 'calculate' tool with the operation and two numbers!`
58
+ }
59
+ ];
60
+ }
61
+
62
+ private getOperationHelp(operation: string): string {
63
+ const helps: Record<string, string> = {
64
+ add: 'Use addition to sum two numbers. Call calculate(operation="add", a=5, b=3) to get 8.',
65
+ subtract: 'Use subtraction to find the difference. Call calculate(operation="subtract", a=10, b=4) to get 6.',
66
+ multiply: 'Use multiplication to find the product. Call calculate(operation="multiply", a=6, b=7) to get 42.',
67
+ divide: 'Use division to find the quotient. Call calculate(operation="divide", a=20, b=5) to get 4. Note: Cannot divide by zero!'
68
+ };
69
+
70
+ return helps[operation] || 'Unknown operation. Available operations: add, subtract, multiply, divide.';
71
+ }
72
+ }
73
+
@@ -0,0 +1,59 @@
1
+ import { ResourceDecorator as Resource, Widget, ExecutionContext } from 'nitrostack';
2
+
3
+ export class CalculatorResources {
4
+ @Resource({
5
+ uri: 'calculator://operations',
6
+ name: 'Calculator Operations',
7
+ description: 'List of available calculator operations',
8
+ mimeType: 'application/json',
9
+ examples: {
10
+ response: {
11
+ operations: [
12
+ { name: 'add', symbol: '+', description: 'Addition' },
13
+ { name: 'subtract', symbol: '-', description: 'Subtraction' },
14
+ { name: 'multiply', symbol: '×', description: 'Multiplication' },
15
+ { name: 'divide', symbol: '÷', description: 'Division' }
16
+ ]
17
+ }
18
+ }
19
+ })
20
+ async getOperations(uri: string, ctx: ExecutionContext) {
21
+ ctx.logger.info('Fetching calculator operations');
22
+
23
+ const operations = [
24
+ {
25
+ name: 'add',
26
+ symbol: '+',
27
+ description: 'Addition',
28
+ example: '5 + 3 = 8'
29
+ },
30
+ {
31
+ name: 'subtract',
32
+ symbol: '-',
33
+ description: 'Subtraction',
34
+ example: '10 - 4 = 6'
35
+ },
36
+ {
37
+ name: 'multiply',
38
+ symbol: '×',
39
+ description: 'Multiplication',
40
+ example: '6 × 7 = 42'
41
+ },
42
+ {
43
+ name: 'divide',
44
+ symbol: '÷',
45
+ description: 'Division',
46
+ example: '20 ÷ 5 = 4'
47
+ }
48
+ ];
49
+
50
+ return {
51
+ contents: [{
52
+ uri,
53
+ mimeType: 'application/json',
54
+ text: JSON.stringify({ operations }, null, 2)
55
+ }]
56
+ };
57
+ }
58
+ }
59
+
@@ -0,0 +1,166 @@
1
+ import { ToolDecorator as Tool, Widget, ExecutionContext, z } from 'nitrostack';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ export class CalculatorTools {
6
+ @Tool({
7
+ name: 'calculate',
8
+ description: 'Perform basic arithmetic calculations',
9
+ inputSchema: z.object({
10
+ operation: z.enum(['add', 'subtract', 'multiply', 'divide']).describe('The operation to perform'),
11
+ a: z.number().describe('First number'),
12
+ b: z.number().describe('Second number')
13
+ }),
14
+ examples: {
15
+ request: {
16
+ operation: 'add',
17
+ a: 5,
18
+ b: 3
19
+ },
20
+ response: {
21
+ operation: 'add',
22
+ a: 5,
23
+ b: 3,
24
+ result: 8,
25
+ expression: '5 + 3 = 8'
26
+ }
27
+ }
28
+ })
29
+ @Widget('calculator-result')
30
+ async calculate(input: any, ctx: ExecutionContext) {
31
+ ctx.logger.info('Performing calculation', {
32
+ operation: input.operation,
33
+ a: input.a,
34
+ b: input.b
35
+ });
36
+
37
+ let result: number;
38
+ let symbol: string;
39
+
40
+ switch (input.operation) {
41
+ case 'add':
42
+ result = input.a + input.b;
43
+ symbol = '+';
44
+ break;
45
+ case 'subtract':
46
+ result = input.a - input.b;
47
+ symbol = '-';
48
+ break;
49
+ case 'multiply':
50
+ result = input.a * input.b;
51
+ symbol = '×';
52
+ break;
53
+ case 'divide':
54
+ if (input.b === 0) {
55
+ throw new Error('Cannot divide by zero');
56
+ }
57
+ result = input.a / input.b;
58
+ symbol = '÷';
59
+ break;
60
+ default:
61
+ throw new Error('Invalid operation');
62
+ }
63
+
64
+ return {
65
+ operation: input.operation,
66
+ a: input.a,
67
+ b: input.b,
68
+ result,
69
+ expression: `${input.a} ${symbol} ${input.b} = ${result}`
70
+ };
71
+ }
72
+
73
+ @Tool({
74
+ name: 'convert_temperature',
75
+ description: 'Convert temperature units based on file content or direct input. Supports Celsius (C) and Fahrenheit (F).',
76
+ inputSchema: z.object({
77
+ file_name: z.string().describe('Name of the uploaded file'),
78
+ file_type: z.string().describe('MIME type of the uploaded file'),
79
+ file_content: z.string().describe('Base64 encoded file content. Will be injected by system.'),
80
+ value: z.number().optional().describe('Temperature value to convert'),
81
+ from_unit: z.enum(['C', 'F']).optional().describe('Unit to convert from (C or F)'),
82
+ to_unit: z.enum(['C', 'F']).optional().describe('Unit to convert to (C or F)')
83
+ })
84
+ })
85
+ async convertTemperature(input: any, ctx: ExecutionContext) {
86
+ ctx.logger.info('Processing temperature file', {
87
+ name: input.file_name,
88
+ type: input.file_type,
89
+ value: input.value,
90
+ from: input.from_unit,
91
+ to: input.to_unit
92
+ });
93
+
94
+ // Save file to uploads directory
95
+ const uploadsDir = path.join(process.cwd(), 'uploads');
96
+ if (!fs.existsSync(uploadsDir)) {
97
+ fs.mkdirSync(uploadsDir, { recursive: true });
98
+ }
99
+
100
+ const filePath = path.join(uploadsDir, input.file_name);
101
+
102
+ // Decode base64
103
+ if (input.file_content) {
104
+ try {
105
+ const matches = input.file_content.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
106
+ let buffer;
107
+
108
+ if (matches && matches.length === 3) {
109
+ buffer = Buffer.from(matches[2], 'base64');
110
+ } else {
111
+ buffer = Buffer.from(input.file_content, 'base64');
112
+ }
113
+
114
+ fs.writeFileSync(filePath, buffer);
115
+ ctx.logger.info(`Saved file to ${filePath}`);
116
+ } catch (e) {
117
+ ctx.logger.error('Failed to save file', { error: e instanceof Error ? e.message : String(e) });
118
+ }
119
+ }
120
+
121
+ const fileStats = {
122
+ name: input.file_name,
123
+ type: input.file_type,
124
+ saved_path: filePath,
125
+ status: 'saved'
126
+ };
127
+
128
+ let result: number | null = null;
129
+ let message = `Successfully processed and saved file ${input.file_name}`;
130
+
131
+ // Perform conversion logic
132
+ if (input.value !== undefined && input.from_unit && input.to_unit) {
133
+ try {
134
+ message += `. Converting ${input.value}°${input.from_unit} to ${input.to_unit}`;
135
+
136
+ if (input.from_unit === input.to_unit) {
137
+ result = input.value;
138
+ } else if (input.from_unit === 'C' && input.to_unit === 'F') {
139
+ result = (input.value * 9 / 5) + 32;
140
+ } else if (input.from_unit === 'F' && input.to_unit === 'C') {
141
+ result = (input.value - 32) * 5 / 9;
142
+ } else {
143
+ throw new Error('Unsupported unit conversion');
144
+ }
145
+
146
+ // Round to 2 decimal places
147
+ if (result !== null) {
148
+ result = Math.round(result * 100) / 100;
149
+ message += `. Result: ${result}°${input.to_unit}`;
150
+ }
151
+ } catch (e: any) {
152
+ message += `. Conversion failed: ${e.message}`;
153
+ }
154
+ } else {
155
+ message += `. No valid conversion parameters detected from manual input or file extraction.`;
156
+ }
157
+
158
+ return {
159
+ status: 'success',
160
+ message,
161
+ file_info: fileStats,
162
+ conversion_result: result !== null ? { value: result, unit: input.to_unit } : null,
163
+ original_value: input.value !== undefined ? { value: input.value, unit: input.from_unit } : null
164
+ };
165
+ }
166
+ }
@@ -0,0 +1,180 @@
1
+ 'use client';
2
+
3
+ import { useTheme, useWidgetState, useWidgetSDK } from '@nitrostack/widgets';
4
+
5
+ /**
6
+ * Example widget demonstrating NitroStack Widget SDK
7
+ * This widget is fully compatible with OpenAI ChatGPT
8
+ */
9
+
10
+ interface CalculatorData {
11
+ operation: string;
12
+ a: number;
13
+ b: number;
14
+ result: number;
15
+ expression: string;
16
+ }
17
+
18
+ export default function CalculatorResult() {
19
+ // Use Widget SDK hooks
20
+ const theme = useTheme();
21
+ const { getToolOutput } = useWidgetSDK();
22
+ const [state, setState] = useWidgetState<{ viewMode: 'compact' | 'detailed' }>(() => ({
23
+ viewMode: 'detailed'
24
+ }));
25
+
26
+ // Access tool output from Widget SDK
27
+ const data = getToolOutput<CalculatorData>();
28
+
29
+ if (!data) {
30
+ return (
31
+ <div style={{
32
+ padding: '24px',
33
+ textAlign: 'center',
34
+ color: theme === 'dark' ? '#fff' : '#000',
35
+ }}>
36
+ Loading...
37
+ </div>
38
+ );
39
+ }
40
+
41
+ const getOperationColor = (op: string) => {
42
+ const colors: Record<string, string> = {
43
+ add: '#10b981',
44
+ subtract: '#f59e0b',
45
+ multiply: '#3b82f6',
46
+ divide: '#8b5cf6'
47
+ };
48
+ return colors[op] || '#6b7280';
49
+ };
50
+
51
+ const getOperationIcon = (op: string) => {
52
+ const icons: Record<string, string> = {
53
+ add: '➕',
54
+ subtract: '➖',
55
+ multiply: '✖️',
56
+ divide: '➗'
57
+ };
58
+ return icons[op] || '🔢';
59
+ };
60
+
61
+ const isDark = theme === 'dark';
62
+ const bgColor = isDark ? '#1a1a1a' : '#ffffff';
63
+ const textColor = isDark ? '#ffffff' : '#000000';
64
+ const mutedColor = isDark ? 'rgba(255,255,255,0.6)' : 'rgba(0,0,0,0.6)';
65
+
66
+ return (
67
+ <div style={{
68
+ padding: '24px',
69
+ background: isDark
70
+ ? 'linear-gradient(135deg, #2d3748 0%, #1a202c 100%)'
71
+ : 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
72
+ borderRadius: '16px',
73
+ color: 'white',
74
+ maxWidth: '400px',
75
+ boxShadow: '0 10px 30px rgba(0,0,0,0.2)',
76
+ transition: 'all 0.3s ease',
77
+ }}>
78
+ <div style={{
79
+ display: 'flex',
80
+ alignItems: 'center',
81
+ justifyContent: 'space-between',
82
+ marginBottom: '20px'
83
+ }}>
84
+ <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
85
+ <span style={{ fontSize: '32px' }}>
86
+ {getOperationIcon(data.operation)}
87
+ </span>
88
+ <div>
89
+ <h3 style={{ margin: 0, fontSize: '18px', opacity: 0.9 }}>
90
+ Calculator Result
91
+ </h3>
92
+ <p style={{ margin: '4px 0 0 0', fontSize: '14px', opacity: 0.7 }}>
93
+ {data.operation.charAt(0).toUpperCase() + data.operation.slice(1)}
94
+ </p>
95
+ </div>
96
+ </div>
97
+
98
+ {/* View mode toggle */}
99
+ <button
100
+ onClick={() => setState({
101
+ viewMode: state?.viewMode === 'compact' ? 'detailed' : 'compact'
102
+ })}
103
+ style={{
104
+ padding: '6px 12px',
105
+ borderRadius: '8px',
106
+ border: '1px solid rgba(255,255,255,0.3)',
107
+ background: 'rgba(255,255,255,0.1)',
108
+ color: 'white',
109
+ cursor: 'pointer',
110
+ fontSize: '12px',
111
+ transition: 'all 0.2s',
112
+ }}
113
+ >
114
+ {state?.viewMode === 'compact' ? '📊 Detailed' : '📋 Compact'}
115
+ </button>
116
+ </div>
117
+
118
+ <div style={{
119
+ background: 'rgba(255, 255, 255, 0.15)',
120
+ borderRadius: '12px',
121
+ padding: '20px',
122
+ backdropFilter: 'blur(10px)',
123
+ marginBottom: '16px'
124
+ }}>
125
+ <div style={{
126
+ fontSize: '28px',
127
+ fontWeight: 'bold',
128
+ textAlign: 'center',
129
+ marginBottom: '12px',
130
+ fontFamily: 'monospace'
131
+ }}>
132
+ {data.expression}
133
+ </div>
134
+
135
+ {state?.viewMode === 'detailed' && (
136
+ <div style={{
137
+ display: 'flex',
138
+ justifyContent: 'space-around',
139
+ fontSize: '14px',
140
+ opacity: 0.9,
141
+ marginTop: '16px'
142
+ }}>
143
+ <div style={{ textAlign: 'center' }}>
144
+ <div style={{ fontSize: '12px', opacity: 0.7 }}>First</div>
145
+ <div style={{ fontWeight: 'bold', fontSize: '20px' }}>{data.a}</div>
146
+ </div>
147
+ <div style={{ textAlign: 'center' }}>
148
+ <div style={{ fontSize: '12px', opacity: 0.7 }}>Second</div>
149
+ <div style={{ fontWeight: 'bold', fontSize: '20px' }}>{data.b}</div>
150
+ </div>
151
+ <div style={{ textAlign: 'center' }}>
152
+ <div style={{ fontSize: '12px', opacity: 0.7 }}>Result</div>
153
+ <div style={{
154
+ fontWeight: 'bold',
155
+ fontSize: '24px',
156
+ color: '#fbbf24'
157
+ }}>
158
+ {data.result}
159
+ </div>
160
+ </div>
161
+ </div>
162
+ )}
163
+ </div>
164
+
165
+ <div style={{
166
+ fontSize: '12px',
167
+ textAlign: 'center',
168
+ opacity: 0.7,
169
+ display: 'flex',
170
+ justifyContent: 'space-between',
171
+ alignItems: 'center',
172
+ }}>
173
+ <span>✨ NitroStack Calculator</span>
174
+ <span style={{ fontSize: '10px' }}>
175
+ Theme: {theme || 'light'} | Mode: {state?.viewMode || 'detailed'}
176
+ </span>
177
+ </div>
178
+ </div>
179
+ );
180
+ }
@@ -0,0 +1,18 @@
1
+ 'use client';
2
+
3
+ import { WidgetLayout } from '@nitrostack/widgets';
4
+
5
+
6
+ export default function RootLayout({
7
+ children,
8
+ }: {
9
+ children: React.ReactNode;
10
+ }) {
11
+ return (
12
+ <html lang="en">
13
+ <body style={{ margin: 0, padding: 0, fontFamily: 'system-ui, sans-serif' }}>
14
+ <WidgetLayout>{children}</WidgetLayout>
15
+ </body>
16
+ </html>
17
+ );
18
+ }
@@ -0,0 +1,45 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true,
4
+ transpilePackages: ['nitrostack'],
5
+
6
+ // Static export for production builds
7
+ ...(process.env.NODE_ENV === 'production' && {
8
+ output: 'export',
9
+ distDir: 'out',
10
+ images: {
11
+ unoptimized: true,
12
+ },
13
+ }),
14
+
15
+ // Development optimizations to prevent cache corruption
16
+ ...(process.env.NODE_ENV === 'development' && {
17
+ // Use memory cache instead of filesystem cache in dev to avoid stale chunks
18
+ webpack: (config, { isServer }) => {
19
+ // Disable persistent caching in development to prevent chunk reference errors
20
+ if (config.cache && config.cache.type === 'filesystem') {
21
+ config.cache = {
22
+ type: 'memory',
23
+ };
24
+ }
25
+
26
+ // Improve cache busting for new files
27
+ if (!isServer) {
28
+ config.cache = false; // Disable cache completely on client in dev
29
+ }
30
+
31
+ return config;
32
+ },
33
+
34
+ // Disable build activity indicator which can cause issues
35
+ devIndicators: {
36
+ buildActivity: false,
37
+ buildActivityPosition: 'bottom-right',
38
+ },
39
+
40
+ // Faster dev server
41
+ compress: false,
42
+ }),
43
+ };
44
+
45
+ export default nextConfig;
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "calculator-widgets",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "next dev -p 3001 --port 3001",
8
+ "build": "next build",
9
+ "start": "next start -p 3001"
10
+ },
11
+ "dependencies": {
12
+ "next": "^14.2.5",
13
+ "react": "^18.3.1",
14
+ "react-dom": "^18.3.1",
15
+ "@nitrostack/widgets": "^1"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^20",
19
+ "@types/react": "^18",
20
+ "@types/react-dom": "^18",
21
+ "typescript": "^5"
22
+ }
23
+ }
24
+
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }
28
+