@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,215 @@
1
+ import { ResourceDecorator as Resource, ExecutionContext, z, Injectable } from 'nitrostack';
2
+ import { DuffelService } from '../../services/duffel.service.js';
3
+
4
+ @Injectable()
5
+ export class FlightResources {
6
+ constructor(private duffelService: DuffelService) { }
7
+
8
+ @Resource({
9
+ uri: 'flight://search-history',
10
+ name: 'Flight Search History',
11
+ description: 'Access to recent flight searches and their results',
12
+ mimeType: 'application/json'
13
+ })
14
+ async getSearchHistory(ctx: ExecutionContext) {
15
+ // In a real implementation, this would fetch from a database
16
+ // For now, return a template structure
17
+ return {
18
+ searches: [],
19
+ message: 'Search history will be stored here after performing searches'
20
+ };
21
+ }
22
+
23
+ @Resource({
24
+ uri: 'flight://popular-routes',
25
+ name: 'Popular Flight Routes',
26
+ description: 'Information about popular flight routes and typical pricing',
27
+ mimeType: 'application/json'
28
+ })
29
+ async getPopularRoutes(ctx: ExecutionContext) {
30
+ return {
31
+ routes: [
32
+ {
33
+ route: 'JFK → LAX',
34
+ description: 'New York to Los Angeles',
35
+ averageDuration: '6h 30m',
36
+ typicalPrice: '$200-400',
37
+ airlines: ['American Airlines', 'Delta', 'JetBlue', 'United']
38
+ },
39
+ {
40
+ route: 'LHR → JFK',
41
+ description: 'London to New York',
42
+ averageDuration: '8h 00m',
43
+ typicalPrice: '$400-800',
44
+ airlines: ['British Airways', 'American Airlines', 'Virgin Atlantic']
45
+ },
46
+ {
47
+ route: 'SFO → NRT',
48
+ description: 'San Francisco to Tokyo',
49
+ averageDuration: '11h 30m',
50
+ typicalPrice: '$600-1200',
51
+ airlines: ['United', 'ANA', 'Japan Airlines']
52
+ },
53
+ {
54
+ route: 'DXB → LHR',
55
+ description: 'Dubai to London',
56
+ averageDuration: '7h 30m',
57
+ typicalPrice: '$400-900',
58
+ airlines: ['Emirates', 'British Airways']
59
+ }
60
+ ],
61
+ note: 'Prices are approximate and vary by season, booking time, and availability'
62
+ };
63
+ }
64
+
65
+ @Resource({
66
+ uri: 'flight://booking-guide',
67
+ name: 'Flight Booking Guide',
68
+ description: 'Comprehensive guide on how to search and book flights',
69
+ mimeType: 'text/markdown'
70
+ })
71
+ async getBookingGuide(ctx: ExecutionContext) {
72
+ return `# Flight Booking Guide
73
+
74
+ ## How to Search for Flights
75
+
76
+ ### 1. Prepare Your Information
77
+ - **Origin & Destination**: Use 3-letter IATA airport codes (e.g., JFK, LAX)
78
+ - Use \`search_airports\` tool if you don't know the code
79
+ - **Dates**: Provide dates in YYYY-MM-DD format
80
+ - **Passengers**: Specify number of adults, children, and infants
81
+ - **Cabin Class**: Choose from economy, premium_economy, business, or first
82
+
83
+ ### 2. Use the Search Tool
84
+ \`\`\`
85
+ search_flights({
86
+ origin: "JFK",
87
+ destination: "LAX",
88
+ departureDate: "2024-03-15",
89
+ returnDate: "2024-03-22",
90
+ adults: 2,
91
+ cabinClass: "economy"
92
+ })
93
+ \`\`\`
94
+
95
+ ### 3. Review Results
96
+ - Compare prices, airlines, and flight times
97
+ - Check number of stops and total duration
98
+ - Review baggage allowance and fare conditions
99
+
100
+ ### 4. Get Detailed Information
101
+ Use \`get_flight_details\` with an offer ID to see:
102
+ - Complete itinerary with all segments
103
+ - Baggage allowance per passenger
104
+ - Refund and change policies
105
+ - Payment requirements
106
+
107
+ ## Tips for Best Results
108
+
109
+ ### Flexible Dates
110
+ - Search multiple date combinations
111
+ - Weekday flights are often cheaper than weekends
112
+ - Avoid major holidays and peak seasons
113
+
114
+ ### Direct vs. Connecting Flights
115
+ - Use \`maxConnections: 0\` for direct flights only
116
+ - Connecting flights are usually cheaper but take longer
117
+ - Consider layover duration and airport
118
+
119
+ ### Cabin Class Selection
120
+ - **Economy**: Most affordable, basic amenities
121
+ - **Premium Economy**: More legroom and better service
122
+ - **Business**: Lie-flat seats, premium dining, lounge access
123
+ - **First**: Ultimate luxury, private suites, concierge service
124
+
125
+ ### Booking Timing
126
+ - Book 2-3 months in advance for domestic flights
127
+ - Book 3-6 months in advance for international flights
128
+ - Last-minute deals exist but are risky
129
+
130
+ ## Understanding Fare Conditions
131
+
132
+ ### Refundable vs. Non-Refundable
133
+ - **Refundable**: Can cancel and get money back (usually more expensive)
134
+ - **Non-Refundable**: No refund if cancelled (cheaper)
135
+
136
+ ### Change Policies
137
+ - Some fares allow changes with a fee
138
+ - Others don't allow any changes
139
+ - Check \`conditions\` in flight details
140
+
141
+ ### Baggage Allowance
142
+ - **Carry-on**: Usually 1 bag + 1 personal item
143
+ - **Checked**: Varies by airline and fare class
144
+ - International flights typically include checked bags
145
+
146
+ ## Booking Process
147
+
148
+ ### Hold Orders
149
+ - All bookings are automatically held (no payment required at booking time)
150
+ - Price is guaranteed until the hold expires
151
+ - You'll receive an expiration time when booking
152
+
153
+ ### Passenger Information Required
154
+ - Full legal name (as on passport/ID)
155
+ - Date of birth
156
+ - Gender
157
+ - Passport details for international flights
158
+
159
+ ## After Booking
160
+
161
+ ### Confirmation
162
+ - You'll receive a booking reference
163
+ - Save confirmation email
164
+ - Check-in opens 24 hours before departure
165
+
166
+ ### Manage Your Booking
167
+ - Add seat selections
168
+ - Purchase extra baggage
169
+ - Add special meals or assistance
170
+ - Update passenger information
171
+
172
+ ## Need Help?
173
+
174
+ Use the \`flight_search_assistant\` prompt for personalized assistance with:
175
+ - Finding the best flights for your needs
176
+ - Understanding complex itineraries
177
+ - Comparing different options
178
+ - Making booking decisions
179
+ `;
180
+ }
181
+
182
+ @Resource({
183
+ uri: 'flight://airline-codes',
184
+ name: 'Airline Codes Reference',
185
+ description: 'Common airline IATA codes and names',
186
+ mimeType: 'application/json'
187
+ })
188
+ async getAirlineCodes(ctx: ExecutionContext) {
189
+ try {
190
+ const airlines = await this.duffelService.getAirlines();
191
+ return {
192
+ airlines: airlines.slice(0, 50).map((airline: any) => ({
193
+ iataCode: airline.iata_code,
194
+ name: airline.name
195
+ })),
196
+ note: 'Showing top 50 airlines. More available through Duffel API.'
197
+ };
198
+ } catch (error) {
199
+ // Return common airlines if API call fails
200
+ return {
201
+ airlines: [
202
+ { iataCode: 'AA', name: 'American Airlines' },
203
+ { iataCode: 'DL', name: 'Delta Air Lines' },
204
+ { iataCode: 'UA', name: 'United Airlines' },
205
+ { iataCode: 'BA', name: 'British Airways' },
206
+ { iataCode: 'LH', name: 'Lufthansa' },
207
+ { iataCode: 'AF', name: 'Air France' },
208
+ { iataCode: 'EK', name: 'Emirates' },
209
+ { iataCode: 'QR', name: 'Qatar Airways' }
210
+ ],
211
+ note: 'Common airlines. Use search_airports tool for complete list.'
212
+ };
213
+ }
214
+ }
215
+ }
@@ -0,0 +1,457 @@
1
+ import { ToolDecorator as Tool, Widget, ExecutionContext, z, UseGuards, Injectable } from 'nitrostack';
2
+ import { OAuthGuard } from '../../guards/oauth.guard.js';
3
+ import { DuffelService } from '../../services/duffel.service.js';
4
+ import { format } from 'date-fns';
5
+
6
+ @Injectable()
7
+ export class FlightTools {
8
+ constructor(private duffelService: DuffelService) { }
9
+
10
+ @Tool({
11
+ name: 'search_flights',
12
+ description: 'Search for flight offers based on origin, destination, dates, and preferences. Returns available flight options with pricing and details.',
13
+ inputSchema: z.object({
14
+ origin: z.string().length(3).describe('Origin airport IATA code (e.g., "JFK", "LHR")'),
15
+ destination: z.string().length(3).describe('Destination airport IATA code (e.g., "LAX", "CDG")'),
16
+ departureDate: z.string().describe('Departure date in YYYY-MM-DD format'),
17
+ returnDate: z.string().optional().describe('Return date in YYYY-MM-DD format for round trip'),
18
+ adults: z.number().min(1).max(9).default(1).describe('Number of adult passengers (18+)'),
19
+ children: z.number().min(0).max(9).default(0).describe('Number of child passengers (2-17)'),
20
+ infants: z.number().min(0).max(9).default(0).describe('Number of infant passengers (under 2)'),
21
+ cabinClass: z.enum(['economy', 'premium_economy', 'business', 'first']).default('economy').describe('Preferred cabin class'),
22
+ maxConnections: z.number().min(0).max(3).optional().describe('Maximum number of connections (0 for direct flights only)'),
23
+ departureTimeFrom: z.string().optional().describe('Earliest departure time in HH:MM format'),
24
+ departureTimeTo: z.string().optional().describe('Latest departure time in HH:MM format')
25
+ }),
26
+ examples: {
27
+ request: {
28
+ origin: 'JFK',
29
+ destination: 'LAX',
30
+ departureDate: '2024-03-15',
31
+ returnDate: '2024-03-22',
32
+ adults: 2,
33
+ cabinClass: 'economy'
34
+ },
35
+ response: {
36
+ requestId: 'orq_123456',
37
+ searchParams: {
38
+ origin: 'JFK',
39
+ destination: 'LAX',
40
+ departureDate: '2024-03-15',
41
+ returnDate: '2024-03-22',
42
+ passengers: {
43
+ adults: 2,
44
+ children: 0,
45
+ infants: 0
46
+ },
47
+ cabinClass: 'economy'
48
+ },
49
+ totalOffers: 15,
50
+ offers: [
51
+ {
52
+ id: 'off_123456',
53
+ totalAmount: '450.00',
54
+ totalCurrency: 'USD',
55
+ expiresAt: '2024-03-01T12:00:00Z',
56
+ outbound: {
57
+ origin: 'JFK',
58
+ destination: 'LAX',
59
+ departureTime: '2024-03-15T08:00:00Z',
60
+ arrivalTime: '2024-03-15T14:30:00Z',
61
+ duration: 'PT6H30M',
62
+ stops: 0,
63
+ airline: 'American Airlines',
64
+ flightNumber: 'AA123',
65
+ segments: []
66
+ },
67
+ return: {
68
+ origin: 'LAX',
69
+ destination: 'JFK',
70
+ departureTime: '2024-03-22T16:00:00Z',
71
+ arrivalTime: '2024-03-23T00:30:00Z',
72
+ duration: 'PT5H30M',
73
+ stops: 0,
74
+ airline: 'American Airlines',
75
+ flightNumber: 'AA456',
76
+ segments: []
77
+ },
78
+ fareType: 'Domestic',
79
+ refundable: false,
80
+ changeable: true
81
+ }
82
+ ],
83
+ message: 'Found 15 flight options. Showing top 10 results.'
84
+ }
85
+ }
86
+ })
87
+ @UseGuards(OAuthGuard)
88
+ @Widget('flight-search-results')
89
+ async searchFlights(input: any, ctx: ExecutionContext) {
90
+ ctx.logger.info('Searching for flights', {
91
+ user: ctx.auth?.subject,
92
+ origin: input.origin,
93
+ destination: input.destination,
94
+ departureDate: input.departureDate
95
+ });
96
+
97
+ // Ensure we have passenger counts with defaults
98
+ const adults = input.adults || 1;
99
+ const children = input.children || 0;
100
+ const infants = input.infants || 0;
101
+
102
+ // Build passengers array
103
+ const passengers: any[] = [];
104
+ for (let i = 0; i < adults; i++) {
105
+ passengers.push({ type: 'adult' });
106
+ }
107
+ for (let i = 0; i < children; i++) {
108
+ passengers.push({ type: 'child', age: 12 }); // Default age for children
109
+ }
110
+ for (let i = 0; i < infants; i++) {
111
+ passengers.push({ type: 'infant_without_seat' });
112
+ }
113
+
114
+ // Build time filters if provided
115
+ const departureTime = input.departureTimeFrom && input.departureTimeTo
116
+ ? { from: input.departureTimeFrom, to: input.departureTimeTo }
117
+ : undefined;
118
+
119
+ ctx.logger.info('Calling Duffel service with passengers:', {
120
+ passengersCount: passengers.length,
121
+ passengers: passengers,
122
+ adults: adults,
123
+ children: children,
124
+ infants: infants
125
+ });
126
+
127
+ const result = await this.duffelService.searchFlights({
128
+ origin: input.origin.toUpperCase(),
129
+ destination: input.destination.toUpperCase(),
130
+ departureDate: input.departureDate,
131
+ returnDate: input.returnDate,
132
+ passengers,
133
+ cabinClass: input.cabinClass,
134
+ maxConnections: input.maxConnections,
135
+ departureTime
136
+ });
137
+
138
+ // Transform offers for better presentation
139
+ const offers = result.offers.map((offer: any) => {
140
+ const outboundSlice = offer.slices[0];
141
+ const returnSlice = offer.slices[1];
142
+
143
+ return {
144
+ id: offer.id,
145
+ totalAmount: offer.total_amount,
146
+ totalCurrency: offer.total_currency,
147
+ expiresAt: offer.expires_at,
148
+
149
+ // Outbound flight details
150
+ outbound: {
151
+ origin: outboundSlice.origin.iata_code,
152
+ destination: outboundSlice.destination.iata_code,
153
+ departureTime: outboundSlice.segments[0].departing_at,
154
+ arrivalTime: outboundSlice.segments[outboundSlice.segments.length - 1].arriving_at,
155
+ duration: outboundSlice.duration,
156
+ stops: outboundSlice.segments.length - 1,
157
+ airline: outboundSlice.segments[0].marketing_carrier.name,
158
+ flightNumber: outboundSlice.segments[0].marketing_carrier_flight_number,
159
+ segments: outboundSlice.segments.map((seg: any) => ({
160
+ origin: seg.origin.iata_code,
161
+ destination: seg.destination.iata_code,
162
+ departingAt: seg.departing_at,
163
+ arrivingAt: seg.arriving_at,
164
+ airline: seg.marketing_carrier.name,
165
+ flightNumber: seg.marketing_carrier_flight_number,
166
+ aircraft: seg.aircraft?.name
167
+ }))
168
+ },
169
+
170
+ // Return flight details (if round trip)
171
+ ...(returnSlice && {
172
+ return: {
173
+ origin: returnSlice.origin.iata_code,
174
+ destination: returnSlice.destination.iata_code,
175
+ departureTime: returnSlice.segments[0].departing_at,
176
+ arrivalTime: returnSlice.segments[returnSlice.segments.length - 1].arriving_at,
177
+ duration: returnSlice.duration,
178
+ stops: returnSlice.segments.length - 1,
179
+ airline: returnSlice.segments[0].marketing_carrier.name,
180
+ flightNumber: returnSlice.segments[0].marketing_carrier_flight_number,
181
+ segments: returnSlice.segments.map((seg: any) => ({
182
+ origin: seg.origin.iata_code,
183
+ destination: seg.destination.iata_code,
184
+ departingAt: seg.departing_at,
185
+ arrivingAt: seg.arriving_at,
186
+ airline: seg.marketing_carrier.name,
187
+ flightNumber: seg.marketing_carrier_flight_number,
188
+ aircraft: seg.aircraft?.name
189
+ }))
190
+ }
191
+ }),
192
+
193
+ // Fare details
194
+ fareType: offer.passenger_identity_documents_required ? 'International' : 'Domestic',
195
+ refundable: offer.conditions?.refund_before_departure?.allowed || false,
196
+ changeable: offer.conditions?.change_before_departure?.allowed || false
197
+ };
198
+ });
199
+
200
+ ctx.logger.info('Flight search completed', {
201
+ user: ctx.auth?.subject,
202
+ offersFound: offers.length
203
+ });
204
+
205
+ return {
206
+ requestId: result.id,
207
+ searchParams: {
208
+ origin: input.origin.toUpperCase(),
209
+ destination: input.destination.toUpperCase(),
210
+ departureDate: input.departureDate,
211
+ returnDate: input.returnDate,
212
+ passengers: {
213
+ adults: adults,
214
+ children: children,
215
+ infants: infants
216
+ },
217
+ cabinClass: input.cabinClass
218
+ },
219
+ totalOffers: offers.length,
220
+ offers: offers.slice(0, 10), // Return top 10 offers
221
+ message: `Found ${offers.length} flight options. Showing top 10 results.`
222
+ };
223
+ }
224
+
225
+ @Tool({
226
+ name: 'get_flight_details',
227
+ description: 'Get detailed information about a specific flight offer including baggage allowance, fare conditions, and seat availability.',
228
+ inputSchema: z.object({
229
+ offerId: z.string().describe('The offer ID from search results')
230
+ }),
231
+ examples: {
232
+ request: {
233
+ offerId: 'off_123456'
234
+ },
235
+ response: {
236
+ id: 'off_123456',
237
+ totalAmount: '450.00',
238
+ totalCurrency: 'USD',
239
+ expiresAt: '2024-03-01T12:00:00Z',
240
+ slices: [
241
+ {
242
+ origin: {
243
+ code: 'JFK',
244
+ name: 'John F. Kennedy International Airport',
245
+ city: 'New York'
246
+ },
247
+ destination: {
248
+ code: 'LAX',
249
+ name: 'Los Angeles International Airport',
250
+ city: 'Los Angeles'
251
+ },
252
+ duration: 'PT6H30M',
253
+ segments: [
254
+ {
255
+ id: 'seg_123',
256
+ origin: 'JFK',
257
+ destination: 'LAX',
258
+ departingAt: '2024-03-15T08:00:00Z',
259
+ arrivingAt: '2024-03-15T14:30:00Z',
260
+ duration: 'PT6H30M',
261
+ airline: {
262
+ name: 'American Airlines',
263
+ code: 'AA',
264
+ flightNumber: '123'
265
+ },
266
+ aircraft: 'Boeing 777-300ER'
267
+ }
268
+ ]
269
+ }
270
+ ],
271
+ passengers: [
272
+ {
273
+ id: 'pas_123',
274
+ type: 'adult',
275
+ fareType: 'economy',
276
+ baggageAllowance: [
277
+ {
278
+ type: 'checked',
279
+ quantity: 1
280
+ },
281
+ {
282
+ type: 'carry_on',
283
+ quantity: 1
284
+ }
285
+ ]
286
+ }
287
+ ],
288
+ conditions: {
289
+ refundBeforeDeparture: {
290
+ allowed: false
291
+ },
292
+ changeBeforeDeparture: {
293
+ allowed: true,
294
+ penaltyAmount: '75.00',
295
+ penaltyCurrency: 'USD'
296
+ }
297
+ },
298
+ paymentRequirements: {
299
+ requiresInstantPayment: true,
300
+ priceGuaranteeExpiresAt: '2024-03-01T12:00:00Z'
301
+ }
302
+ }
303
+ }
304
+ })
305
+ @UseGuards(OAuthGuard)
306
+ @Widget('flight-details')
307
+ async getFlightDetails(input: any, ctx: ExecutionContext) {
308
+ ctx.logger.info('Getting flight details', {
309
+ user: ctx.auth?.subject,
310
+ offerId: input.offerId
311
+ });
312
+
313
+ const offer = await this.duffelService.getOffer(input.offerId);
314
+
315
+ return {
316
+ id: offer.id,
317
+ totalAmount: offer.total_amount,
318
+ totalCurrency: offer.total_currency,
319
+ expiresAt: offer.expires_at,
320
+
321
+ slices: offer.slices.map((slice: any) => ({
322
+ origin: {
323
+ code: slice.origin.iata_code,
324
+ name: slice.origin.name,
325
+ city: slice.origin.city_name
326
+ },
327
+ destination: {
328
+ code: slice.destination.iata_code,
329
+ name: slice.destination.name,
330
+ city: slice.destination.city_name
331
+ },
332
+ duration: slice.duration,
333
+ segments: slice.segments.map((seg: any) => ({
334
+ id: seg.id,
335
+ origin: seg.origin.iata_code,
336
+ destination: seg.destination.iata_code,
337
+ departingAt: seg.departing_at,
338
+ arrivingAt: seg.arriving_at,
339
+ duration: seg.duration,
340
+ airline: {
341
+ name: seg.marketing_carrier.name,
342
+ code: seg.marketing_carrier.iata_code,
343
+ flightNumber: seg.marketing_carrier_flight_number
344
+ },
345
+ aircraft: seg.aircraft?.name,
346
+ operatingCarrier: seg.operating_carrier?.name,
347
+ distance: seg.distance
348
+ }))
349
+ })),
350
+
351
+ passengers: offer.passengers.map((pax: any) => ({
352
+ id: pax.id,
353
+ type: pax.type,
354
+ fareType: pax.fare_type,
355
+ baggageAllowance: pax.baggages?.map((bag: any) => ({
356
+ type: bag.type,
357
+ quantity: bag.quantity
358
+ }))
359
+ })),
360
+
361
+ conditions: {
362
+ refundBeforeDeparture: {
363
+ allowed: offer.conditions?.refund_before_departure?.allowed || false,
364
+ penaltyAmount: offer.conditions?.refund_before_departure?.penalty_amount,
365
+ penaltyCurrency: offer.conditions?.refund_before_departure?.penalty_currency
366
+ },
367
+ changeBeforeDeparture: {
368
+ allowed: offer.conditions?.change_before_departure?.allowed || false,
369
+ penaltyAmount: offer.conditions?.change_before_departure?.penalty_amount,
370
+ penaltyCurrency: offer.conditions?.change_before_departure?.penalty_currency
371
+ }
372
+ },
373
+
374
+ paymentRequirements: {
375
+ requiresInstantPayment: offer.payment_requirements?.requires_instant_payment,
376
+ priceGuaranteeExpiresAt: offer.payment_requirements?.price_guarantee_expires_at,
377
+ paymentRequiredBy: offer.payment_requirements?.payment_required_by
378
+ }
379
+ };
380
+ }
381
+
382
+ @Tool({
383
+ name: 'search_airports',
384
+ description: 'Search for airports by city name or airport code. Useful for finding IATA codes.',
385
+ inputSchema: z.object({
386
+ query: z.string().min(2).describe('City name or airport code to search for')
387
+ }),
388
+ examples: {
389
+ request: {
390
+ query: 'London'
391
+ },
392
+ response: {
393
+ query: 'London',
394
+ results: [
395
+ {
396
+ id: 'arp_lhr_gb',
397
+ name: 'Heathrow Airport',
398
+ iataCode: 'LHR',
399
+ icaoCode: 'EGLL',
400
+ cityName: 'London',
401
+ type: 'airport',
402
+ latitude: 51.4700,
403
+ longitude: -0.4543,
404
+ timeZone: 'Europe/London'
405
+ },
406
+ {
407
+ id: 'arp_lgw_gb',
408
+ name: 'Gatwick Airport',
409
+ iataCode: 'LGW',
410
+ icaoCode: 'EGKK',
411
+ cityName: 'London',
412
+ type: 'airport',
413
+ latitude: 51.1537,
414
+ longitude: -0.1821,
415
+ timeZone: 'Europe/London'
416
+ },
417
+ {
418
+ id: 'arp_stn_gb',
419
+ name: 'Stansted Airport',
420
+ iataCode: 'STN',
421
+ icaoCode: 'EGSS',
422
+ cityName: 'London',
423
+ type: 'airport',
424
+ latitude: 51.8860,
425
+ longitude: 0.2389,
426
+ timeZone: 'Europe/London'
427
+ }
428
+ ]
429
+ }
430
+ }
431
+ })
432
+ @UseGuards(OAuthGuard)
433
+ @Widget('airport-search')
434
+ async searchAirports(input: any, ctx: ExecutionContext) {
435
+ ctx.logger.info('Searching airports', {
436
+ user: ctx.auth?.subject,
437
+ query: input.query
438
+ });
439
+
440
+ const places = await this.duffelService.searchAirports(input.query);
441
+
442
+ return {
443
+ query: input.query,
444
+ results: places.slice(0, 10).map((place: any) => ({
445
+ id: place.id,
446
+ name: place.name,
447
+ iataCode: place.iata_code,
448
+ icaoCode: place.icao_code,
449
+ cityName: place.city_name,
450
+ type: place.type,
451
+ latitude: place.latitude,
452
+ longitude: place.longitude,
453
+ timeZone: place.time_zone
454
+ }))
455
+ };
456
+ }
457
+ }