@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.
- package/README.md +131 -0
- package/dist/commands/build.d.ts +6 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +185 -0
- package/dist/commands/dev.d.ts +7 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +365 -0
- package/dist/commands/generate-types.d.ts +8 -0
- package/dist/commands/generate-types.d.ts.map +1 -0
- package/dist/commands/generate-types.js +219 -0
- package/dist/commands/generate.d.ts +12 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +375 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +324 -0
- package/dist/commands/install.d.ts +10 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +80 -0
- package/dist/commands/start.d.ts +6 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +70 -0
- package/dist/commands/upgrade.d.ts +10 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +214 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/mcp-dev-wrapper.d.ts +15 -0
- package/dist/mcp-dev-wrapper.d.ts.map +1 -0
- package/dist/mcp-dev-wrapper.js +187 -0
- package/dist/ui/branding.d.ts +31 -0
- package/dist/ui/branding.d.ts.map +1 -0
- package/dist/ui/branding.js +136 -0
- package/package.json +69 -0
- package/templates/typescript-oauth/.env.example +27 -0
- package/templates/typescript-oauth/OAUTH_SETUP.md +592 -0
- package/templates/typescript-oauth/README.md +263 -0
- package/templates/typescript-oauth/package.json +29 -0
- package/templates/typescript-oauth/src/app.module.ts +92 -0
- package/templates/typescript-oauth/src/guards/oauth.guard.ts +126 -0
- package/templates/typescript-oauth/src/health/system.health.ts +55 -0
- package/templates/typescript-oauth/src/index.ts +63 -0
- package/templates/typescript-oauth/src/modules/flights/booking.tools.ts +323 -0
- package/templates/typescript-oauth/src/modules/flights/flights.module.ts +14 -0
- package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +228 -0
- package/templates/typescript-oauth/src/modules/flights/flights.resources.ts +215 -0
- package/templates/typescript-oauth/src/modules/flights/flights.tools.ts +457 -0
- package/templates/typescript-oauth/src/services/duffel.service.ts +285 -0
- package/templates/typescript-oauth/src/widgets/app/airport-search/page.tsx +270 -0
- package/templates/typescript-oauth/src/widgets/app/flight-details/page.tsx +261 -0
- package/templates/typescript-oauth/src/widgets/app/flight-search-results/page.tsx +378 -0
- package/templates/typescript-oauth/src/widgets/app/globals.css +167 -0
- package/templates/typescript-oauth/src/widgets/app/layout.tsx +18 -0
- package/templates/typescript-oauth/src/widgets/app/order-cancellation/page.tsx +207 -0
- package/templates/typescript-oauth/src/widgets/app/order-summary/page.tsx +245 -0
- package/templates/typescript-oauth/src/widgets/app/payment-confirmation/page.tsx +152 -0
- package/templates/typescript-oauth/src/widgets/app/seat-selection/page.tsx +486 -0
- package/templates/typescript-oauth/src/widgets/next-env.d.ts +5 -0
- package/templates/typescript-oauth/src/widgets/next.config.js +45 -0
- package/templates/typescript-oauth/src/widgets/package-lock.json +4493 -0
- package/templates/typescript-oauth/src/widgets/package.json +24 -0
- package/templates/typescript-oauth/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-oauth/src/widgets/widget-manifest.json +395 -0
- package/templates/typescript-oauth/tsconfig.json +23 -0
- package/templates/typescript-pizzaz/README.md +252 -0
- package/templates/typescript-pizzaz/package.json +34 -0
- package/templates/typescript-pizzaz/src/app.module.ts +28 -0
- package/templates/typescript-pizzaz/src/index.ts +30 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.data.ts +106 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.module.ts +11 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.service.ts +60 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.tools.ts +197 -0
- package/templates/typescript-pizzaz/src/widgets/app/layout.tsx +18 -0
- package/templates/typescript-pizzaz/src/widgets/app/pizza-list/page.tsx +272 -0
- package/templates/typescript-pizzaz/src/widgets/app/pizza-map/page.tsx +216 -0
- package/templates/typescript-pizzaz/src/widgets/app/pizza-shop/page.tsx +374 -0
- package/templates/typescript-pizzaz/src/widgets/components/CompactShopCard.tsx +144 -0
- package/templates/typescript-pizzaz/src/widgets/components/PizzaCard.tsx +191 -0
- package/templates/typescript-pizzaz/src/widgets/next.config.js +45 -0
- package/templates/typescript-pizzaz/src/widgets/package.json +30 -0
- package/templates/typescript-pizzaz/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-pizzaz/src/widgets/widget-manifest.json +253 -0
- package/templates/typescript-pizzaz/tsconfig.json +30 -0
- package/templates/typescript-starter/README.md +320 -0
- package/templates/typescript-starter/package.json +25 -0
- package/templates/typescript-starter/src/app.module.ts +34 -0
- package/templates/typescript-starter/src/health/system.health.ts +55 -0
- package/templates/typescript-starter/src/index.ts +29 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.module.ts +12 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.prompts.ts +73 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.resources.ts +59 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +166 -0
- package/templates/typescript-starter/src/widgets/app/calculator-result/page.tsx +180 -0
- package/templates/typescript-starter/src/widgets/app/layout.tsx +18 -0
- package/templates/typescript-starter/src/widgets/next.config.js +45 -0
- package/templates/typescript-starter/src/widgets/package.json +24 -0
- package/templates/typescript-starter/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-starter/src/widgets/widget-manifest.json +48 -0
- 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
|
+
|