nitrostack 1.0.0 → 1.0.2
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/CHANGELOG.md +30 -0
- package/dist/cli/index.js +4 -1
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/src/studio/README.md +140 -0
- package/src/studio/app/api/auth/fetch-metadata/route.ts +71 -0
- package/src/studio/app/api/auth/register-client/route.ts +67 -0
- package/src/studio/app/api/chat/route.ts +123 -0
- package/src/studio/app/api/health/checks/route.ts +42 -0
- package/src/studio/app/api/health/route.ts +13 -0
- package/src/studio/app/api/init/route.ts +85 -0
- package/src/studio/app/api/ping/route.ts +13 -0
- package/src/studio/app/api/prompts/[name]/route.ts +21 -0
- package/src/studio/app/api/prompts/route.ts +13 -0
- package/src/studio/app/api/resources/[...uri]/route.ts +18 -0
- package/src/studio/app/api/resources/route.ts +13 -0
- package/src/studio/app/api/roots/route.ts +13 -0
- package/src/studio/app/api/sampling/route.ts +14 -0
- package/src/studio/app/api/tools/[name]/call/route.ts +41 -0
- package/src/studio/app/api/tools/route.ts +23 -0
- package/src/studio/app/api/widget-examples/route.ts +44 -0
- package/src/studio/app/auth/callback/page.tsx +160 -0
- package/src/studio/app/auth/page.tsx +543 -0
- package/src/studio/app/chat/page.tsx +530 -0
- package/src/studio/app/chat/page.tsx.backup +390 -0
- package/src/studio/app/globals.css +410 -0
- package/src/studio/app/health/page.tsx +177 -0
- package/src/studio/app/layout.tsx +48 -0
- package/src/studio/app/page.tsx +337 -0
- package/src/studio/app/page.tsx.backup +346 -0
- package/src/studio/app/ping/page.tsx +204 -0
- package/src/studio/app/prompts/page.tsx +228 -0
- package/src/studio/app/resources/page.tsx +313 -0
- package/src/studio/components/EnlargeModal.tsx +116 -0
- package/src/studio/components/Sidebar.tsx +133 -0
- package/src/studio/components/ToolCard.tsx +108 -0
- package/src/studio/components/WidgetRenderer.tsx +99 -0
- package/src/studio/lib/api.ts +207 -0
- package/src/studio/lib/llm-service.ts +361 -0
- package/src/studio/lib/mcp-client.ts +168 -0
- package/src/studio/lib/store.ts +192 -0
- package/src/studio/lib/theme-provider.tsx +50 -0
- package/src/studio/lib/types.ts +107 -0
- package/src/studio/lib/widget-loader.ts +90 -0
- package/src/studio/middleware.ts +27 -0
- package/src/studio/next.config.js +16 -0
- package/src/studio/package-lock.json +2696 -0
- package/src/studio/package.json +34 -0
- package/src/studio/postcss.config.mjs +10 -0
- package/src/studio/tailwind.config.ts +67 -0
- package/src/studio/tsconfig.json +41 -0
- package/templates/typescript-auth/.env.example +23 -0
- package/templates/typescript-auth/src/app.module.ts +103 -0
- package/templates/typescript-auth/src/db/database.ts +163 -0
- package/templates/typescript-auth/src/db/seed.ts +374 -0
- package/templates/typescript-auth/src/db/setup.ts +87 -0
- package/templates/typescript-auth/src/events/analytics.service.ts +52 -0
- package/templates/typescript-auth/src/events/notification.service.ts +40 -0
- package/templates/typescript-auth/src/filters/global-exception.filter.ts +28 -0
- package/templates/typescript-auth/src/guards/README.md +75 -0
- package/templates/typescript-auth/src/guards/jwt.guard.ts +105 -0
- package/templates/typescript-auth/src/health/database.health.ts +41 -0
- package/templates/typescript-auth/src/index.ts +26 -0
- package/templates/typescript-auth/src/interceptors/transform.interceptor.ts +24 -0
- package/templates/typescript-auth/src/middleware/logging.middleware.ts +42 -0
- package/templates/typescript-auth/src/modules/addresses/addresses.module.ts +16 -0
- package/templates/typescript-auth/src/modules/addresses/addresses.prompts.ts +114 -0
- package/templates/typescript-auth/src/modules/addresses/addresses.resources.ts +40 -0
- package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +241 -0
- package/templates/typescript-auth/src/modules/auth/auth.module.ts +16 -0
- package/templates/typescript-auth/src/modules/auth/auth.prompts.ts +147 -0
- package/templates/typescript-auth/src/modules/auth/auth.resources.ts +84 -0
- package/templates/typescript-auth/src/modules/auth/auth.tools.ts +139 -0
- package/templates/typescript-auth/src/modules/cart/cart.module.ts +16 -0
- package/templates/typescript-auth/src/modules/cart/cart.prompts.ts +95 -0
- package/templates/typescript-auth/src/modules/cart/cart.resources.ts +44 -0
- package/templates/typescript-auth/src/modules/cart/cart.tools.ts +281 -0
- package/templates/typescript-auth/src/modules/orders/orders.module.ts +16 -0
- package/templates/typescript-auth/src/modules/orders/orders.prompts.ts +88 -0
- package/templates/typescript-auth/src/modules/orders/orders.resources.ts +48 -0
- package/templates/typescript-auth/src/modules/orders/orders.tools.ts +281 -0
- package/templates/typescript-auth/src/modules/products/products.module.ts +16 -0
- package/templates/typescript-auth/src/modules/products/products.prompts.ts +146 -0
- package/templates/typescript-auth/src/modules/products/products.resources.ts +98 -0
- package/templates/typescript-auth/src/modules/products/products.tools.ts +266 -0
- package/templates/typescript-auth/src/pipes/validation.pipe.ts +42 -0
- package/templates/typescript-auth/src/services/database.service.ts +90 -0
- package/templates/typescript-auth/src/widgets/app/add-to-cart/page.tsx +122 -0
- package/templates/typescript-auth/src/widgets/app/address-added/page.tsx +116 -0
- package/templates/typescript-auth/src/widgets/app/address-deleted/page.tsx +105 -0
- package/templates/typescript-auth/src/widgets/app/address-list/page.tsx +139 -0
- package/templates/typescript-auth/src/widgets/app/address-updated/page.tsx +153 -0
- package/templates/typescript-auth/src/widgets/app/cart-cleared/page.tsx +86 -0
- package/templates/typescript-auth/src/widgets/app/cart-updated/page.tsx +116 -0
- package/templates/typescript-auth/src/widgets/app/categories/page.tsx +134 -0
- package/templates/typescript-auth/src/widgets/app/layout.tsx +21 -0
- package/templates/typescript-auth/src/widgets/app/login-result/page.tsx +129 -0
- package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +206 -0
- package/templates/typescript-auth/src/widgets/app/order-details/page.tsx +225 -0
- package/templates/typescript-auth/src/widgets/app/order-history/page.tsx +218 -0
- package/templates/typescript-auth/src/widgets/app/product-card/page.tsx +121 -0
- package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +173 -0
- package/templates/typescript-auth/src/widgets/app/shopping-cart/page.tsx +187 -0
- package/templates/typescript-auth/src/widgets/app/whoami/page.tsx +165 -0
- package/templates/typescript-auth/src/widgets/next.config.js +38 -0
- package/templates/typescript-auth/src/widgets/package.json +18 -0
- package/templates/typescript-auth/src/widgets/styles/ecommerce.ts +169 -0
- package/templates/typescript-auth/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-auth/src/widgets/types/tool-data.ts +141 -0
- package/templates/typescript-auth/src/widgets/widget-manifest.json +464 -0
- package/templates/typescript-auth/tsconfig.json +27 -0
- package/templates/typescript-auth-api-key/.env +15 -0
- package/templates/typescript-auth-api-key/.env.example +4 -0
- package/templates/typescript-auth-api-key/src/app.module.ts +38 -0
- package/templates/typescript-auth-api-key/src/guards/apikey.guard.ts +47 -0
- package/templates/typescript-auth-api-key/src/guards/multi-auth.guard.ts +157 -0
- package/templates/typescript-auth-api-key/src/health/system.health.ts +55 -0
- package/templates/typescript-auth-api-key/src/index.ts +47 -0
- package/templates/typescript-auth-api-key/src/modules/calculator/calculator.module.ts +12 -0
- package/templates/typescript-auth-api-key/src/modules/calculator/calculator.prompts.ts +73 -0
- package/templates/typescript-auth-api-key/src/modules/calculator/calculator.resources.ts +60 -0
- package/templates/typescript-auth-api-key/src/modules/calculator/calculator.tools.ts +71 -0
- package/templates/typescript-auth-api-key/src/modules/demo/demo.module.ts +18 -0
- package/templates/typescript-auth-api-key/src/modules/demo/demo.tools.ts +155 -0
- package/templates/typescript-auth-api-key/src/modules/demo/multi-auth.tools.ts +123 -0
- package/templates/typescript-auth-api-key/src/widgets/app/calculator-operations/page.tsx +133 -0
- package/templates/typescript-auth-api-key/src/widgets/app/calculator-result/page.tsx +134 -0
- package/templates/typescript-auth-api-key/src/widgets/app/layout.tsx +14 -0
- package/templates/typescript-auth-api-key/src/widgets/next.config.js +37 -0
- package/templates/typescript-auth-api-key/src/widgets/package.json +24 -0
- package/templates/typescript-auth-api-key/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-auth-api-key/src/widgets/widget-manifest.json +48 -0
- package/templates/typescript-auth-api-key/tsconfig.json +23 -0
- package/templates/typescript-oauth/.env.example +91 -0
- package/templates/typescript-oauth/src/app.module.ts +89 -0
- package/templates/typescript-oauth/src/guards/oauth.guard.ts +127 -0
- package/templates/typescript-oauth/src/index.ts +74 -0
- package/templates/typescript-oauth/src/modules/demo/demo.module.ts +16 -0
- package/templates/typescript-oauth/src/modules/demo/demo.tools.ts +190 -0
- package/templates/typescript-oauth/src/widgets/app/calculator-operations/page.tsx +133 -0
- package/templates/typescript-oauth/src/widgets/app/calculator-result/page.tsx +134 -0
- package/templates/typescript-oauth/src/widgets/app/layout.tsx +14 -0
- package/templates/typescript-oauth/src/widgets/next.config.js +37 -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 +48 -0
- package/templates/typescript-oauth/tsconfig.json +23 -0
- package/templates/typescript-starter/.env.example +4 -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 +27 -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 +60 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +71 -0
- package/templates/typescript-starter/src/widgets/app/calculator-operations/page.tsx +133 -0
- package/templates/typescript-starter/src/widgets/app/calculator-result/page.tsx +134 -0
- package/templates/typescript-starter/src/widgets/app/layout.tsx +14 -0
- package/templates/typescript-starter/src/widgets/next.config.js +37 -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
- package/LICENSE_URLS_UPDATE_COMPLETE.md +0 -388
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
import { Guard, ExecutionContext, JWTPayload, JWTModule } from 'nitrostack';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* JWT Guard - Validates JWT tokens and populates context.auth
|
|
6
|
+
*
|
|
7
|
+
* This is an example implementation. You can customize it for your needs.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* @Tool({ name: 'whoami', ... })
|
|
12
|
+
* @UseGuards(JWTGuard)
|
|
13
|
+
* async whoami(input: any, context: ExecutionContext) {
|
|
14
|
+
* // context.auth is populated with JWT payload
|
|
15
|
+
* return { user: context.auth };
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export class JWTGuard implements Guard {
|
|
20
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
21
|
+
try {
|
|
22
|
+
// Get JWT secret from JWTModule configuration
|
|
23
|
+
const secret = JWTModule.getSecret();
|
|
24
|
+
if (!secret) {
|
|
25
|
+
context.logger.warn('[JWTGuard] JWT secret not configured');
|
|
26
|
+
throw new Error('JWT authentication not configured. Set JWT_SECRET environment variable.');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Extract token from context
|
|
30
|
+
const token = this.extractToken(context);
|
|
31
|
+
if (!token) {
|
|
32
|
+
context.logger.info('[JWTGuard] No token found in request');
|
|
33
|
+
throw new Error('Authentication required. Please login first and include your JWT token.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Verify token
|
|
37
|
+
const payload = await this.verifyToken(token, secret);
|
|
38
|
+
if (!payload) {
|
|
39
|
+
context.logger.warn('[JWTGuard] Token verification failed');
|
|
40
|
+
throw new Error('Invalid or expired token. Please login again.');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Populate context.auth
|
|
44
|
+
context.auth = {
|
|
45
|
+
subject: payload.sub,
|
|
46
|
+
scopes: [],
|
|
47
|
+
email: payload.email,
|
|
48
|
+
name: payload.name,
|
|
49
|
+
...payload,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
context.logger.info(`[JWTGuard] Authentication successful for user: ${payload.sub}`);
|
|
53
|
+
return true;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
context.logger.error('[JWTGuard] Authentication failed', error);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Extract JWT token from execution context
|
|
62
|
+
* Supports multiple token locations for maximum compatibility
|
|
63
|
+
*/
|
|
64
|
+
private extractToken(context: ExecutionContext): string | null {
|
|
65
|
+
// Priority 1: Check metadata for _jwt field (MCP protocol)
|
|
66
|
+
if (context.metadata?._jwt) {
|
|
67
|
+
return context.metadata._jwt as string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Priority 2: Check Authorization header format in metadata
|
|
71
|
+
if (context.metadata?.authorization) {
|
|
72
|
+
const auth = context.metadata.authorization as string;
|
|
73
|
+
if (auth.startsWith('Bearer ')) {
|
|
74
|
+
return auth.substring(7);
|
|
75
|
+
}
|
|
76
|
+
return auth;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Priority 3: Check metadata.headers (if present)
|
|
80
|
+
const headers = context.metadata?.headers as any;
|
|
81
|
+
if (headers?.authorization) {
|
|
82
|
+
const auth = headers.authorization as string;
|
|
83
|
+
if (auth.startsWith('Bearer ')) {
|
|
84
|
+
return auth.substring(7);
|
|
85
|
+
}
|
|
86
|
+
return auth;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Verify JWT token
|
|
94
|
+
*/
|
|
95
|
+
private async verifyToken(token: string, secret: string): Promise<JWTPayload | null> {
|
|
96
|
+
try {
|
|
97
|
+
const cleanSecret = secret.trim();
|
|
98
|
+
const decoded = jwt.verify(token, cleanSecret) as JWTPayload;
|
|
99
|
+
return decoded;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { HealthCheck, HealthCheckInterface, HealthCheckResult } from 'nitrostack';
|
|
2
|
+
import { DatabaseService } from '../services/database.service.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Database Health Check
|
|
6
|
+
*
|
|
7
|
+
* Monitors database connectivity
|
|
8
|
+
*/
|
|
9
|
+
@HealthCheck({
|
|
10
|
+
name: 'database',
|
|
11
|
+
description: 'Database connectivity check',
|
|
12
|
+
interval: 30 // Check every 30 seconds
|
|
13
|
+
})
|
|
14
|
+
export class DatabaseHealthCheck implements HealthCheckInterface {
|
|
15
|
+
constructor(private database: DatabaseService) {}
|
|
16
|
+
|
|
17
|
+
async check(): Promise<HealthCheckResult> {
|
|
18
|
+
try {
|
|
19
|
+
const isConnected = await this.database.ping();
|
|
20
|
+
|
|
21
|
+
if (isConnected) {
|
|
22
|
+
return {
|
|
23
|
+
status: 'up',
|
|
24
|
+
message: 'Database is connected and responsive',
|
|
25
|
+
};
|
|
26
|
+
} else {
|
|
27
|
+
return {
|
|
28
|
+
status: 'down',
|
|
29
|
+
message: 'Database connection failed',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
} catch (error: any) {
|
|
33
|
+
return {
|
|
34
|
+
status: 'down',
|
|
35
|
+
message: 'Database health check failed',
|
|
36
|
+
details: error.message,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E-commerce MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Main entry point for the MCP server.
|
|
5
|
+
* Uses the new @McpApp decorator pattern for clean, NestJS-style architecture.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { McpApplicationFactory } from 'nitrostack';
|
|
9
|
+
import { AppModule } from './app.module.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Bootstrap the application
|
|
13
|
+
*/
|
|
14
|
+
async function bootstrap() {
|
|
15
|
+
// Create and start the MCP server from AppModule
|
|
16
|
+
const server = await McpApplicationFactory.create(AppModule);
|
|
17
|
+
|
|
18
|
+
// Start the server
|
|
19
|
+
await server.start();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Start the application
|
|
23
|
+
bootstrap().catch((error) => {
|
|
24
|
+
console.error('❌ Failed to start server:', error);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Interceptor, InterceptorInterface, ExecutionContext } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Transform Interceptor
|
|
5
|
+
*
|
|
6
|
+
* Wraps all responses in a standard format with metadata
|
|
7
|
+
*/
|
|
8
|
+
@Interceptor()
|
|
9
|
+
export class TransformInterceptor implements InterceptorInterface {
|
|
10
|
+
async intercept(context: ExecutionContext, next: () => Promise<any>) {
|
|
11
|
+
const result = await next();
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
success: true,
|
|
15
|
+
data: result,
|
|
16
|
+
metadata: {
|
|
17
|
+
timestamp: new Date().toISOString(),
|
|
18
|
+
requestId: context.requestId,
|
|
19
|
+
tool: context.toolName || 'unknown',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Middleware, MiddlewareInterface, ExecutionContext } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Logging Middleware
|
|
5
|
+
*
|
|
6
|
+
* Logs all tool executions with timing information
|
|
7
|
+
*/
|
|
8
|
+
@Middleware()
|
|
9
|
+
export class LoggingMiddleware implements MiddlewareInterface {
|
|
10
|
+
async use(context: ExecutionContext, next: () => Promise<any>) {
|
|
11
|
+
const start = performance.now();
|
|
12
|
+
const toolName = context.toolName || 'unknown';
|
|
13
|
+
|
|
14
|
+
context.logger.info(`[${toolName}] Started`, {
|
|
15
|
+
requestId: context.requestId,
|
|
16
|
+
user: context.auth?.subject || 'anonymous',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const result = await next();
|
|
21
|
+
const duration = performance.now() - start;
|
|
22
|
+
|
|
23
|
+
context.logger.info(`[${toolName}] Completed in ${duration.toFixed(2)}ms`, {
|
|
24
|
+
requestId: context.requestId,
|
|
25
|
+
duration,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return result;
|
|
29
|
+
} catch (error: any) {
|
|
30
|
+
const duration = performance.now() - start;
|
|
31
|
+
|
|
32
|
+
context.logger.error(`[${toolName}] Failed after ${duration.toFixed(2)}ms`, {
|
|
33
|
+
requestId: context.requestId,
|
|
34
|
+
duration,
|
|
35
|
+
error: error.message,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Module } from 'nitrostack';
|
|
2
|
+
import { AddressesTools } from './addresses.tools.js';
|
|
3
|
+
import { AddressesResources } from './addresses.resources.js';
|
|
4
|
+
import { AddressesPrompts } from './addresses.prompts.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Addresses Module
|
|
8
|
+
* Provides shipping address management, resources, and prompts
|
|
9
|
+
*/
|
|
10
|
+
@Module({
|
|
11
|
+
name: 'addresses',
|
|
12
|
+
description: 'Shipping address management',
|
|
13
|
+
controllers: [AddressesTools, AddressesResources, AddressesPrompts],
|
|
14
|
+
})
|
|
15
|
+
export class AddressesModule {}
|
|
16
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { PromptDecorator as Prompt, ExecutionContext } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Addresses Prompts
|
|
5
|
+
* Provides helpful prompts for address management
|
|
6
|
+
*/
|
|
7
|
+
export class AddressesPrompts {
|
|
8
|
+
/**
|
|
9
|
+
* Address management help prompt
|
|
10
|
+
*/
|
|
11
|
+
@Prompt({
|
|
12
|
+
name: 'address-help',
|
|
13
|
+
description: 'Get help managing shipping addresses',
|
|
14
|
+
})
|
|
15
|
+
async addressHelp(args: Record<string, any>, context: ExecutionContext) {
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
role: 'user' as const,
|
|
19
|
+
content: 'I need help managing shipping addresses',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
role: 'assistant' as const,
|
|
23
|
+
content: `**Shipping Address Management Guide**
|
|
24
|
+
|
|
25
|
+
**Available Tools (Requires Authentication):**
|
|
26
|
+
1. \`add_address\` - Add new shipping address
|
|
27
|
+
2. \`get_addresses\` - View all your addresses
|
|
28
|
+
3. \`set_default_address\` - Set default for orders
|
|
29
|
+
4. \`delete_address\` - Remove an address
|
|
30
|
+
|
|
31
|
+
**Adding an Address:**
|
|
32
|
+
\`\`\`
|
|
33
|
+
add_address({
|
|
34
|
+
full_name: "John Doe",
|
|
35
|
+
street: "123 Main St",
|
|
36
|
+
city: "New York",
|
|
37
|
+
state: "NY",
|
|
38
|
+
zip_code: "10001",
|
|
39
|
+
country: "USA",
|
|
40
|
+
phone: "+1234567890",
|
|
41
|
+
is_default: true
|
|
42
|
+
})
|
|
43
|
+
\`\`\`
|
|
44
|
+
|
|
45
|
+
**Required Fields:**
|
|
46
|
+
- ✓ full_name
|
|
47
|
+
- ✓ street
|
|
48
|
+
- ✓ city
|
|
49
|
+
- ✓ state
|
|
50
|
+
- ✓ zip_code
|
|
51
|
+
- ✓ phone
|
|
52
|
+
|
|
53
|
+
**Optional Fields:**
|
|
54
|
+
- country (defaults to "USA")
|
|
55
|
+
- is_default (defaults to false)
|
|
56
|
+
|
|
57
|
+
**Default Address:**
|
|
58
|
+
- Used automatically for orders
|
|
59
|
+
- Only one default address per user
|
|
60
|
+
- Setting new default unsets old one
|
|
61
|
+
|
|
62
|
+
**Viewing Addresses:**
|
|
63
|
+
\`\`\`
|
|
64
|
+
get_addresses({})
|
|
65
|
+
\`\`\`
|
|
66
|
+
Returns all addresses, sorted by:
|
|
67
|
+
1. Default address first
|
|
68
|
+
2. Then by creation date
|
|
69
|
+
|
|
70
|
+
**Setting Default:**
|
|
71
|
+
\`\`\`
|
|
72
|
+
set_default_address({
|
|
73
|
+
address_id: "address-uuid"
|
|
74
|
+
})
|
|
75
|
+
\`\`\`
|
|
76
|
+
|
|
77
|
+
**Deleting Address:**
|
|
78
|
+
\`\`\`
|
|
79
|
+
delete_address({
|
|
80
|
+
address_id: "address-uuid"
|
|
81
|
+
})
|
|
82
|
+
\`\`\`
|
|
83
|
+
⚠️ Cannot be undone!
|
|
84
|
+
|
|
85
|
+
**Address Format Tips:**
|
|
86
|
+
📍 **Street:** Include apartment/unit if applicable
|
|
87
|
+
📍 **State:** Use 2-letter code (NY, CA, TX)
|
|
88
|
+
📍 **ZIP:** 5-digit or 9-digit format
|
|
89
|
+
📍 **Phone:** Include country code for international
|
|
90
|
+
|
|
91
|
+
**Common Operations:**
|
|
92
|
+
1. **First Time Setup:**
|
|
93
|
+
- Add address with is_default: true
|
|
94
|
+
- Will be used for all orders
|
|
95
|
+
|
|
96
|
+
2. **Multiple Addresses:**
|
|
97
|
+
- Add each address separately
|
|
98
|
+
- Set one as default
|
|
99
|
+
- Choose specific address when ordering
|
|
100
|
+
|
|
101
|
+
3. **Update Address:**
|
|
102
|
+
- Delete old address
|
|
103
|
+
- Add new address with updated info
|
|
104
|
+
|
|
105
|
+
**Tips:**
|
|
106
|
+
✓ Add address before first order
|
|
107
|
+
✓ Keep at least one address on file
|
|
108
|
+
✓ Verify address details before saving
|
|
109
|
+
✓ Use default for frequent shipping location`,
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ResourceDecorator as Resource, ExecutionContext } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Addresses Resources
|
|
5
|
+
* Provides address-related resource endpoints
|
|
6
|
+
*/
|
|
7
|
+
export class AddressesResources {
|
|
8
|
+
/**
|
|
9
|
+
* Address schema resource
|
|
10
|
+
*/
|
|
11
|
+
@Resource({
|
|
12
|
+
uri: 'addresses://schema',
|
|
13
|
+
name: 'Address Schema',
|
|
14
|
+
description: 'Schema definition for shipping address objects',
|
|
15
|
+
mimeType: 'application/json',
|
|
16
|
+
})
|
|
17
|
+
async getAddressSchema(context: ExecutionContext) {
|
|
18
|
+
return {
|
|
19
|
+
type: 'json' as const,
|
|
20
|
+
data: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
id: { type: 'string', format: 'uuid', description: 'Address ID' },
|
|
24
|
+
user_id: { type: 'string', description: 'User ID' },
|
|
25
|
+
full_name: { type: 'string', description: 'Full name for delivery' },
|
|
26
|
+
street: { type: 'string', description: 'Street address' },
|
|
27
|
+
city: { type: 'string', description: 'City' },
|
|
28
|
+
state: { type: 'string', description: 'State/Province' },
|
|
29
|
+
zip_code: { type: 'string', description: 'ZIP/Postal code' },
|
|
30
|
+
country: { type: 'string', default: 'USA', description: 'Country' },
|
|
31
|
+
phone: { type: 'string', description: 'Contact phone number' },
|
|
32
|
+
is_default: { type: 'boolean', description: 'Is default shipping address' },
|
|
33
|
+
created_at: { type: 'string', format: 'date-time', description: 'Address creation time' },
|
|
34
|
+
},
|
|
35
|
+
required: ['full_name', 'street', 'city', 'state', 'zip_code', 'phone'],
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { ToolDecorator as Tool, Widget, UseGuards, z, ExecutionContext } from 'nitrostack';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import { getDatabase } from '../../db/database.js';
|
|
4
|
+
import { JWTGuard } from '../../guards/jwt.guard.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Addresses Tools
|
|
8
|
+
* Handles user shipping addresses (requires authentication)
|
|
9
|
+
*/
|
|
10
|
+
export class AddressesTools {
|
|
11
|
+
/**
|
|
12
|
+
* Add new address - Requires authentication
|
|
13
|
+
*/
|
|
14
|
+
@Tool({
|
|
15
|
+
name: 'add_address',
|
|
16
|
+
description: 'Add a new shipping address. Requires authentication.',
|
|
17
|
+
inputSchema: z.object({
|
|
18
|
+
full_name: z.string().describe('Full name for delivery'),
|
|
19
|
+
street: z.string().describe('Street address'),
|
|
20
|
+
city: z.string().describe('City'),
|
|
21
|
+
state: z.string().describe('State/Province'),
|
|
22
|
+
zip_code: z.string().describe('ZIP/Postal code'),
|
|
23
|
+
country: z.string().default('USA').describe('Country'),
|
|
24
|
+
phone: z.string().describe('Contact phone number'),
|
|
25
|
+
is_default: z.boolean().default(false).describe('Set as default address'),
|
|
26
|
+
}),
|
|
27
|
+
examples: {
|
|
28
|
+
request: {
|
|
29
|
+
full_name: 'Emily Johnson',
|
|
30
|
+
street: '123 Main Street',
|
|
31
|
+
city: 'San Francisco',
|
|
32
|
+
state: 'CA',
|
|
33
|
+
zip_code: '94102',
|
|
34
|
+
country: 'USA',
|
|
35
|
+
phone: '+1-555-0100',
|
|
36
|
+
is_default: true
|
|
37
|
+
},
|
|
38
|
+
response: {
|
|
39
|
+
message: 'Address added successfully!',
|
|
40
|
+
addressId: 'addr-1',
|
|
41
|
+
isDefault: true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
@Widget('address-added')
|
|
46
|
+
@UseGuards(JWTGuard)
|
|
47
|
+
async addAddress(input: any, context: ExecutionContext) {
|
|
48
|
+
const db = getDatabase();
|
|
49
|
+
const userId = context.auth?.subject;
|
|
50
|
+
const addressId = uuidv4();
|
|
51
|
+
|
|
52
|
+
// If this is set as default, unset other defaults
|
|
53
|
+
if (input.is_default) {
|
|
54
|
+
db.prepare(`
|
|
55
|
+
UPDATE addresses SET is_default = 0 WHERE user_id = ?
|
|
56
|
+
`).run(userId);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
db.prepare(`
|
|
60
|
+
INSERT INTO addresses (id, user_id, full_name, street, city, state, zip_code, country, phone, is_default)
|
|
61
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
62
|
+
`).run(
|
|
63
|
+
addressId,
|
|
64
|
+
userId,
|
|
65
|
+
input.full_name,
|
|
66
|
+
input.street,
|
|
67
|
+
input.city,
|
|
68
|
+
input.state,
|
|
69
|
+
input.zip_code,
|
|
70
|
+
input.country,
|
|
71
|
+
input.phone,
|
|
72
|
+
input.is_default ? 1 : 0
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
context.logger.info(`Address added: ${input.city}, ${input.state}`);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
message: 'Address added successfully!',
|
|
79
|
+
addressId,
|
|
80
|
+
isDefault: input.is_default,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get all addresses - Requires authentication
|
|
86
|
+
*/
|
|
87
|
+
@Tool({
|
|
88
|
+
name: 'get_addresses',
|
|
89
|
+
description: 'Get all saved shipping addresses. Requires authentication.',
|
|
90
|
+
inputSchema: z.object({}),
|
|
91
|
+
examples: {
|
|
92
|
+
request: {},
|
|
93
|
+
response: {
|
|
94
|
+
addresses: [
|
|
95
|
+
{
|
|
96
|
+
id: 'addr-1',
|
|
97
|
+
full_name: 'Emily Johnson',
|
|
98
|
+
street: '123 Main Street',
|
|
99
|
+
city: 'San Francisco',
|
|
100
|
+
state: 'CA',
|
|
101
|
+
zip_code: '94102',
|
|
102
|
+
country: 'USA',
|
|
103
|
+
phone: '+1-555-0100',
|
|
104
|
+
is_default: true
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'addr-2',
|
|
108
|
+
full_name: 'Emily Johnson',
|
|
109
|
+
street: '456 Office Blvd',
|
|
110
|
+
city: 'San Francisco',
|
|
111
|
+
state: 'CA',
|
|
112
|
+
zip_code: '94103',
|
|
113
|
+
country: 'USA',
|
|
114
|
+
phone: '+1-555-0100',
|
|
115
|
+
is_default: false
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
@Widget('address-list')
|
|
122
|
+
@UseGuards(JWTGuard)
|
|
123
|
+
async getAddresses(input: any, context: ExecutionContext) {
|
|
124
|
+
const db = getDatabase();
|
|
125
|
+
const userId = context.auth?.subject;
|
|
126
|
+
|
|
127
|
+
const addresses: any[] = db.prepare(`
|
|
128
|
+
SELECT * FROM addresses WHERE user_id = ? ORDER BY is_default DESC, created_at DESC
|
|
129
|
+
`).all(userId);
|
|
130
|
+
|
|
131
|
+
context.logger.info(`Retrieved ${addresses.length} addresses`);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
addresses: addresses.map(addr => ({
|
|
135
|
+
...addr,
|
|
136
|
+
is_default: addr.is_default === 1,
|
|
137
|
+
})),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Set default address - Requires authentication
|
|
143
|
+
*/
|
|
144
|
+
@Tool({
|
|
145
|
+
name: 'set_default_address',
|
|
146
|
+
description: 'Set an address as the default shipping address. Requires authentication.',
|
|
147
|
+
examples: {
|
|
148
|
+
request: {
|
|
149
|
+
address_id: 'addr-2'
|
|
150
|
+
},
|
|
151
|
+
response: {
|
|
152
|
+
message: 'Default address updated!',
|
|
153
|
+
addressId: 'addr-2'
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
inputSchema: z.object({
|
|
157
|
+
address_id: z.string().describe('The ID of the address to set as default'),
|
|
158
|
+
}),
|
|
159
|
+
})
|
|
160
|
+
@Widget('address-updated')
|
|
161
|
+
@UseGuards(JWTGuard)
|
|
162
|
+
async setDefaultAddress(input: any, context: ExecutionContext) {
|
|
163
|
+
const db = getDatabase();
|
|
164
|
+
const userId = context.auth?.subject;
|
|
165
|
+
|
|
166
|
+
// Verify address belongs to user
|
|
167
|
+
const address: any = db.prepare(`
|
|
168
|
+
SELECT * FROM addresses WHERE id = ? AND user_id = ?
|
|
169
|
+
`).get(input.address_id, userId);
|
|
170
|
+
|
|
171
|
+
if (!address) {
|
|
172
|
+
throw new Error('Address not found or you don\'t have access to it.');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Unset all defaults
|
|
176
|
+
db.prepare(`
|
|
177
|
+
UPDATE addresses SET is_default = 0 WHERE user_id = ?
|
|
178
|
+
`).run(userId);
|
|
179
|
+
|
|
180
|
+
// Set new default
|
|
181
|
+
db.prepare(`
|
|
182
|
+
UPDATE addresses SET is_default = 1 WHERE id = ?
|
|
183
|
+
`).run(input.address_id);
|
|
184
|
+
|
|
185
|
+
context.logger.info(`Set default address: ${address.city}, ${address.state}`);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
message: 'Default address updated!',
|
|
189
|
+
address: {
|
|
190
|
+
...address,
|
|
191
|
+
is_default: true,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Delete address - Requires authentication
|
|
198
|
+
*/
|
|
199
|
+
@Tool({
|
|
200
|
+
name: 'delete_address',
|
|
201
|
+
description: 'Delete a shipping address. Requires authentication.',
|
|
202
|
+
inputSchema: z.object({
|
|
203
|
+
address_id: z.string().describe('The ID of the address to delete'),
|
|
204
|
+
}),
|
|
205
|
+
examples: {
|
|
206
|
+
request: {
|
|
207
|
+
address_id: 'addr-2'
|
|
208
|
+
},
|
|
209
|
+
response: {
|
|
210
|
+
message: 'Address deleted successfully!',
|
|
211
|
+
addressId: 'addr-2'
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
@Widget('address-deleted')
|
|
216
|
+
@UseGuards(JWTGuard)
|
|
217
|
+
async deleteAddress(input: any, context: ExecutionContext) {
|
|
218
|
+
const db = getDatabase();
|
|
219
|
+
const userId = context.auth?.subject;
|
|
220
|
+
|
|
221
|
+
// Verify address belongs to user
|
|
222
|
+
const address: any = db.prepare(`
|
|
223
|
+
SELECT * FROM addresses WHERE id = ? AND user_id = ?
|
|
224
|
+
`).get(input.address_id, userId);
|
|
225
|
+
|
|
226
|
+
if (!address) {
|
|
227
|
+
throw new Error('Address not found or you don\'t have access to it.');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
db.prepare(`
|
|
231
|
+
DELETE FROM addresses WHERE id = ?
|
|
232
|
+
`).run(input.address_id);
|
|
233
|
+
|
|
234
|
+
context.logger.info(`Deleted address: ${address.city}, ${address.state}`);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
message: 'Address deleted successfully!',
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Module } from 'nitrostack';
|
|
2
|
+
import { AuthTools } from './auth.tools.js';
|
|
3
|
+
import { AuthResources } from './auth.resources.js';
|
|
4
|
+
import { AuthPrompts } from './auth.prompts.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Authentication Module
|
|
8
|
+
* Provides login, user profile tools, resources, and prompts
|
|
9
|
+
*/
|
|
10
|
+
@Module({
|
|
11
|
+
name: 'auth',
|
|
12
|
+
description: 'Authentication and user management',
|
|
13
|
+
controllers: [AuthTools, AuthResources, AuthPrompts],
|
|
14
|
+
})
|
|
15
|
+
export class AuthModule {}
|
|
16
|
+
|