@nitrostack/cli 1.0.4 β 1.0.7
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/LICENSE +201 -0
- package/README.md +39 -96
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +4 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +24 -52
- package/dist/commands/generate-types.d.ts +0 -1
- package/dist/commands/generate-types.d.ts.map +1 -1
- package/dist/commands/generate-types.js +18 -39
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +23 -15
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +18 -17
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +3 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +4 -4
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +92 -77
- package/dist/ui/branding.d.ts +21 -4
- package/dist/ui/branding.d.ts.map +1 -1
- package/dist/ui/branding.js +121 -52
- package/package.json +6 -7
- package/templates/typescript-oauth/.env.example +5 -5
- package/templates/typescript-oauth/README.md +36 -231
- package/templates/typescript-oauth/package.json +1 -1
- package/templates/typescript-oauth/src/app.module.ts +1 -1
- package/templates/typescript-oauth/src/guards/oauth.guard.ts +1 -1
- package/templates/typescript-oauth/src/health/system.health.ts +1 -1
- package/templates/typescript-oauth/src/index.ts +1 -1
- package/templates/typescript-oauth/src/modules/flights/booking.tools.ts +1 -1
- package/templates/typescript-oauth/src/modules/flights/flights.module.ts +1 -1
- package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +1 -1
- package/templates/typescript-oauth/src/modules/flights/flights.resources.ts +1 -1
- package/templates/typescript-oauth/src/modules/flights/flights.tools.ts +1 -1
- package/templates/typescript-oauth/src/services/duffel.service.ts +1 -1
- package/templates/typescript-pizzaz/.env.example +8 -0
- package/templates/typescript-pizzaz/README.md +42 -217
- package/templates/typescript-pizzaz/package.json +1 -1
- package/templates/typescript-pizzaz/src/app.module.ts +1 -1
- package/templates/typescript-pizzaz/src/index.ts +1 -1
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.module.ts +3 -2
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.service.ts +1 -1
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.tasks.ts +294 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.tools.ts +1 -1
- package/templates/typescript-starter/.env.example +7 -0
- package/templates/typescript-starter/README.md +51 -284
- package/templates/typescript-starter/package.json +1 -1
- package/templates/typescript-starter/src/app.module.ts +1 -1
- package/templates/typescript-starter/src/health/system.health.ts +1 -1
- package/templates/typescript-starter/src/index.ts +1 -1
- package/templates/typescript-starter/src/modules/calculator/calculator.module.ts +1 -1
- package/templates/typescript-starter/src/modules/calculator/calculator.prompts.ts +1 -1
- package/templates/typescript-starter/src/modules/calculator/calculator.resources.ts +1 -1
- package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +1 -1
|
@@ -1,252 +1,77 @@
|
|
|
1
1
|
# π NitroStack Pizza Shop Finder
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A high-performance interactive template showcasing discovery with maps, lists, and detailed views. This template demonstrates the **NitroStack Widget SDK** for building beautiful, tool-driven visual experiences.
|
|
4
4
|
|
|
5
|
-
## Features
|
|
5
|
+
## β¨ Features
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
- β
`useDisplayMode()` - Fullscreen mode adaptation
|
|
12
|
-
- β
`useWidgetSDK()` - Tool calling, navigation, and external links
|
|
13
|
-
- β
`<WidgetLayout>` - Automatic RPC setup
|
|
7
|
+
- **Mapbox Integration** β Interactive maps with custom markers.
|
|
8
|
+
- **Real-time State** β Shared state between server and widgets.
|
|
9
|
+
- **Responsive Layouts** β Auto-adapting heights and display modes (Inline, PiP, Fullscreen).
|
|
10
|
+
- **Interactive UI** β Sorting, filtering, and favorites persistence.
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
1. **Pizza Map** - Interactive Mapbox map with markers and shop selection
|
|
17
|
-
2. **Pizza List** - Grid/list view with sorting, filtering, and favorites
|
|
18
|
-
3. **Pizza Shop** - Detailed shop information with contact actions
|
|
19
|
-
|
|
20
|
-
### π **MCP Tools**
|
|
21
|
-
- `show_pizza_map` - Display shops on an interactive map
|
|
22
|
-
- `show_pizza_list` - Show filterable list of shops
|
|
23
|
-
- `show_pizza_shop` - Display detailed shop information
|
|
12
|
+
---
|
|
24
13
|
|
|
25
14
|
## π Quick Start
|
|
26
15
|
|
|
27
|
-
###
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
# Install NitroStack CLI globally
|
|
31
|
-
npm install -g nitrostack
|
|
32
|
-
|
|
33
|
-
# Or use npx
|
|
34
|
-
npx nitrostack --version
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Setup Your Project
|
|
16
|
+
### 1. Initialize Your Project
|
|
38
17
|
|
|
39
18
|
```bash
|
|
40
|
-
|
|
41
|
-
nitrostack init my-pizza-app --template typescript-pizzaz
|
|
19
|
+
npx nitrostack init my-pizza-app --template typescript-pizzaz
|
|
42
20
|
cd my-pizza-app
|
|
43
|
-
|
|
44
|
-
# Install all dependencies (root + widgets)
|
|
45
|
-
nitrostack install
|
|
46
21
|
```
|
|
47
22
|
|
|
48
|
-
###
|
|
49
|
-
|
|
50
|
-
The map widget uses Mapbox GL for beautiful interactive maps:
|
|
51
|
-
|
|
52
|
-
1. Get a **free** API key from [Mapbox](https://www.mapbox.com/) (sign up takes 1 minute)
|
|
53
|
-
2. Create `src/widgets/.env` file:
|
|
23
|
+
### 2. Install Dependencies
|
|
54
24
|
|
|
55
25
|
```bash
|
|
56
|
-
|
|
26
|
+
npm run install:all
|
|
57
27
|
```
|
|
58
28
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
### Run Development Server
|
|
29
|
+
### 3. Step Up NitroStudio
|
|
62
30
|
|
|
63
|
-
|
|
64
|
-
npm run dev
|
|
65
|
-
```
|
|
31
|
+
NitroStudio provides the best experience for visual testing of widgets and maps.
|
|
66
32
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- **Widget Dev Server** on http://localhost:3001 - Hot module replacement
|
|
33
|
+
1. **Download NitroStudio**: [nitrostack.ai/studio](https://nitrostack.ai/studio)
|
|
34
|
+
2. **Open Project**: Launch NitroStudio and select your project folder.
|
|
35
|
+
3. **View Maps**: Use the chat or visual tools to explore your pizza shops.
|
|
71
36
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
Try these prompts in Studio chat:
|
|
75
|
-
- "Show me pizza shops on a map"
|
|
76
|
-
- "List all pizza shops"
|
|
77
|
-
- "Show me details for Tony's New York Pizza"
|
|
78
|
-
- "Find pizza shops with high ratings"
|
|
37
|
+
---
|
|
79
38
|
|
|
80
|
-
##
|
|
39
|
+
## βοΈ Configuration
|
|
81
40
|
|
|
82
|
-
|
|
83
|
-
typescript-pizzaz/
|
|
84
|
-
βββ src/
|
|
85
|
-
β βββ index.ts # Main server entry
|
|
86
|
-
β βββ app.module.ts # App module
|
|
87
|
-
β βββ modules/
|
|
88
|
-
β βββ pizzaz/
|
|
89
|
-
β βββ pizzaz.data.ts # Pizza shop data
|
|
90
|
-
β βββ pizzaz.service.ts # Business logic
|
|
91
|
-
β βββ pizzaz.tools.ts # MCP tools
|
|
92
|
-
β βββ pizzaz.module.ts # Module definition
|
|
93
|
-
β βββ widgets/
|
|
94
|
-
β βββ app/
|
|
95
|
-
β β βββ pizza-map/ # Map widget
|
|
96
|
-
β β βββ pizza-list/ # List widget
|
|
97
|
-
β β βββ pizza-shop/ # Shop detail widget
|
|
98
|
-
β βββ components/
|
|
99
|
-
β βββ PizzaCard.tsx # Reusable card component
|
|
100
|
-
βββ package.json
|
|
101
|
-
βββ README.md
|
|
102
|
-
```
|
|
41
|
+
### Mapbox (Optional)
|
|
103
42
|
|
|
104
|
-
|
|
43
|
+
To enable the interactive map, add your Mapbox token:
|
|
105
44
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
- **Theme-aware** map styles (light/dark)
|
|
45
|
+
1. Get a free token at [mapbox.com](https://www.mapbox.com/).
|
|
46
|
+
2. Create `src/widgets/.env` and add:
|
|
47
|
+
```env
|
|
48
|
+
NEXT_PUBLIC_MAPBOX_TOKEN=pk.your_token_here
|
|
49
|
+
```
|
|
112
50
|
|
|
113
|
-
|
|
114
|
-
- **Grid/List view toggle** with state persistence
|
|
115
|
-
- **Sorting** by rating, name, or price
|
|
116
|
-
- **Favorites tracking** across sessions
|
|
117
|
-
- **Responsive layout** using `useMaxHeight()`
|
|
118
|
-
- **Filter panel** for advanced search
|
|
119
|
-
|
|
120
|
-
### Pizza Shop Widget
|
|
121
|
-
- **Hero image** with overlay information
|
|
122
|
-
- **Contact actions** (call, directions, website)
|
|
123
|
-
- **Specialties showcase**
|
|
124
|
-
- **Related shops** recommendations
|
|
125
|
-
- **External link handling** via `useWidgetSDK()`
|
|
51
|
+
---
|
|
126
52
|
|
|
127
|
-
##
|
|
53
|
+
## π οΈ Commands
|
|
128
54
|
|
|
129
55
|
```bash
|
|
130
|
-
#
|
|
131
|
-
npm
|
|
132
|
-
nitrostack install # Same as above
|
|
133
|
-
|
|
134
|
-
# Development
|
|
135
|
-
npm run dev # Start dev server with Studio
|
|
136
|
-
npm run build # Build TypeScript and widgets for production
|
|
137
|
-
npm start # Run production server
|
|
138
|
-
|
|
139
|
-
# Upgrade
|
|
140
|
-
npm run upgrade # Upgrade nitrostack to latest version
|
|
141
|
-
|
|
142
|
-
# Widget Management
|
|
143
|
-
npm run widget <command> # Run npm command in widgets directory
|
|
144
|
-
npm run widget add <pkg> # Add a widget dependency
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## π οΈ Customization
|
|
148
|
-
|
|
149
|
-
### Adding More Shops
|
|
150
|
-
|
|
151
|
-
Edit `src/modules/pizzaz/pizzaz.data.ts`:
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
export const PIZZA_SHOPS: PizzaShop[] = [
|
|
155
|
-
{
|
|
156
|
-
id: 'my-pizza-shop',
|
|
157
|
-
name: 'My Pizza Shop',
|
|
158
|
-
description: 'Amazing pizza!',
|
|
159
|
-
address: '123 Main St, City, State 12345',
|
|
160
|
-
coords: [-122.4194, 37.7749], // [lng, lat]
|
|
161
|
-
rating: 4.5,
|
|
162
|
-
reviews: 100,
|
|
163
|
-
priceLevel: 2,
|
|
164
|
-
cuisine: ['Italian', 'Pizza'],
|
|
165
|
-
hours: { open: '11:00 AM', close: '10:00 PM' },
|
|
166
|
-
phone: '(555) 123-4567',
|
|
167
|
-
website: 'https://example.com',
|
|
168
|
-
image: 'https://images.unsplash.com/photo-...',
|
|
169
|
-
specialties: ['Margherita', 'Pepperoni'],
|
|
170
|
-
openNow: true,
|
|
171
|
-
},
|
|
172
|
-
// ... more shops
|
|
173
|
-
];
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### Changing Map Style
|
|
177
|
-
|
|
178
|
-
Edit `src/widgets/app/pizza-map/page.tsx`:
|
|
179
|
-
|
|
180
|
-
```typescript
|
|
181
|
-
style: isDark
|
|
182
|
-
? 'mapbox://styles/mapbox/dark-v11' // Dark mode style
|
|
183
|
-
: 'mapbox://styles/mapbox/streets-v12' // Light mode style
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### Adding New Filters
|
|
187
|
-
|
|
188
|
-
Edit `src/modules/pizzaz/pizzaz.service.ts` to add more filter options.
|
|
189
|
-
|
|
190
|
-
## π SDK Features Demonstrated
|
|
191
|
-
|
|
192
|
-
### Theme Awareness
|
|
193
|
-
```typescript
|
|
194
|
-
const theme = useTheme(); // 'light' | 'dark'
|
|
195
|
-
const bgColor = theme === 'dark' ? '#000' : '#fff';
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### State Persistence
|
|
199
|
-
```typescript
|
|
200
|
-
const [state, setState] = useWidgetState(() => ({
|
|
201
|
-
favorites: [],
|
|
202
|
-
viewMode: 'grid',
|
|
203
|
-
}));
|
|
204
|
-
|
|
205
|
-
// State persists across widget reloads
|
|
206
|
-
setState({ ...state, favorites: [...state.favorites, shopId] });
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### Responsive Layouts
|
|
210
|
-
```typescript
|
|
211
|
-
const maxHeight = useMaxHeight();
|
|
212
|
-
return <div style={{ maxHeight }}>{content}</div>;
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### Display Mode Adaptation
|
|
216
|
-
```typescript
|
|
217
|
-
const displayMode = useDisplayMode(); // 'inline' | 'pip' | 'fullscreen'
|
|
218
|
-
const showSidebar = displayMode === 'fullscreen';
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### External Links
|
|
222
|
-
```typescript
|
|
223
|
-
const { openExternal } = useWidgetSDK();
|
|
224
|
-
openExternal('https://example.com');
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
## π Deployment
|
|
228
|
-
|
|
229
|
-
### Build for Production
|
|
56
|
+
# Start dev server with Studio integration
|
|
57
|
+
npm run dev
|
|
230
58
|
|
|
231
|
-
|
|
59
|
+
# Build for production
|
|
232
60
|
npm run build
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
### Deploy Widgets
|
|
236
|
-
|
|
237
|
-
Widget HTML files will be generated in `src/widgets/out/` - these work identically in any MCP-compatible client including OpenAI ChatGPT.
|
|
238
61
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
- Try the **Flight Booking Template** - API integration with Duffel
|
|
243
|
-
- Read the [NitroStack Documentation](https://nitrostack.ai/docs)
|
|
244
|
-
- Check out [Mapbox GL JS Documentation](https://docs.mapbox.com/mapbox-gl-js/)
|
|
62
|
+
# Manage widgets directly
|
|
63
|
+
npm run widget <command>
|
|
64
|
+
```
|
|
245
65
|
|
|
246
|
-
##
|
|
66
|
+
## π Structure
|
|
247
67
|
|
|
248
|
-
|
|
68
|
+
- `src/modules/pizzaz/` β Core logic and pizza shop data.
|
|
69
|
+
- `src/widgets/app/pizza-map/` β Next.js Map widget.
|
|
70
|
+
- `src/widgets/app/pizza-list/` β Filterable shop list.
|
|
71
|
+
- `src/widgets/app/pizza-shop/` β Detail view with actions.
|
|
249
72
|
|
|
250
73
|
---
|
|
251
|
-
|
|
252
|
-
|
|
74
|
+
**Official Resources**
|
|
75
|
+
- [Website](https://nitrostack.ai)
|
|
76
|
+
- [Docs](https://docs.nitrostack.ai)
|
|
77
|
+
- [Download Studio](https://nitrostack.ai/studio)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { Module } from 'nitrostack';
|
|
1
|
+
import { Module } from '@nitrostack/core';
|
|
2
2
|
import { PizzazService } from './pizzaz.service.js';
|
|
3
3
|
import { PizzazTools } from './pizzaz.tools.js';
|
|
4
|
+
import { PizzazTaskTools } from './pizzaz.tasks.js';
|
|
4
5
|
|
|
5
6
|
@Module({
|
|
6
7
|
name: 'pizzaz',
|
|
7
8
|
description: 'Pizza shop finder module',
|
|
8
|
-
controllers: [PizzazTools],
|
|
9
|
+
controllers: [PizzazTools, PizzazTaskTools],
|
|
9
10
|
providers: [PizzazService],
|
|
10
11
|
})
|
|
11
12
|
export class PizzazModule { }
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pizzaz Task Tools
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the MCP Tasks feature β long-running, async tool execution
|
|
5
|
+
* with progress reporting and cancellation support.
|
|
6
|
+
*
|
|
7
|
+
* To test via MCP Inspector (or any MCP client that supports tasks):
|
|
8
|
+
*
|
|
9
|
+
* tools/call name="audit_pizza_shops" task={}
|
|
10
|
+
* β returns { task: { taskId, status: "working", ... } }
|
|
11
|
+
*
|
|
12
|
+
* tasks/get taskId=<id>
|
|
13
|
+
* β returns current status & progress message
|
|
14
|
+
*
|
|
15
|
+
* tasks/result taskId=<id>
|
|
16
|
+
* β blocks until done, then returns the full audit report
|
|
17
|
+
*
|
|
18
|
+
* tasks/cancel taskId=<id>
|
|
19
|
+
* β cancels the running audit
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { ToolDecorator as Tool, ExecutionContext, Injectable, z } from '@nitrostack/core';
|
|
23
|
+
import { PizzazService } from './pizzaz.service.js';
|
|
24
|
+
|
|
25
|
+
// βββ Input Schemas βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
26
|
+
|
|
27
|
+
const AuditSchema = z.object({
|
|
28
|
+
/** Number of shops to audit. Defaults to all shops. */
|
|
29
|
+
maxShops: z
|
|
30
|
+
.number()
|
|
31
|
+
.int()
|
|
32
|
+
.min(1)
|
|
33
|
+
.max(20)
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('Maximum number of pizza shops to audit (default: all)'),
|
|
36
|
+
/** Simulate a slow audit by adding a delay per shop in ms. */
|
|
37
|
+
delayPerShopMs: z
|
|
38
|
+
.number()
|
|
39
|
+
.int()
|
|
40
|
+
.min(0)
|
|
41
|
+
.max(5000)
|
|
42
|
+
.optional()
|
|
43
|
+
.default(800)
|
|
44
|
+
.describe('Simulated processing time per shop in milliseconds (default: 800ms)'),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const OrderPizzaSchema = z.object({
|
|
48
|
+
shopId: z.string().describe('ID of the pizza shop to order from'),
|
|
49
|
+
pizzaName: z.string().describe('Name of the pizza to order'),
|
|
50
|
+
quantity: z.number().int().min(1).max(10).default(1).describe('Number of pizzas to order'),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// βββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
54
|
+
|
|
55
|
+
function sleep(ms: number): Promise<void> {
|
|
56
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Score a shop 0β100 based on rating and review count */
|
|
60
|
+
function scoreShop(shop: { rating: number; reviews: number; openNow: boolean }): number {
|
|
61
|
+
const ratingScore = (shop.rating / 5) * 60; // 60% weight
|
|
62
|
+
const reviewScore = Math.min(shop.reviews / 500, 1) * 30; // 30% weight
|
|
63
|
+
const openBonus = shop.openNow ? 10 : 0; // 10% bonus
|
|
64
|
+
return Math.round(ratingScore + reviewScore + openBonus);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function grade(score: number): string {
|
|
68
|
+
if (score >= 90) return 'A+';
|
|
69
|
+
if (score >= 80) return 'A';
|
|
70
|
+
if (score >= 70) return 'B';
|
|
71
|
+
if (score >= 60) return 'C';
|
|
72
|
+
return 'D';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// βββ Task Tools Controller βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
76
|
+
|
|
77
|
+
@Injectable({ deps: [PizzazService] })
|
|
78
|
+
export class PizzazTaskTools {
|
|
79
|
+
constructor(private readonly pizzazService: PizzazService) { }
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* audit_pizza_shops
|
|
83
|
+
*
|
|
84
|
+
* A long-running tool that audits every pizza shop in the database.
|
|
85
|
+
* It processes each shop one-by-one (with a configurable delay) so you
|
|
86
|
+
* can watch the progress messages update via tasks/get, then fetch the
|
|
87
|
+
* full report via tasks/result.
|
|
88
|
+
*
|
|
89
|
+
* taskSupport: 'optional' β works normally (sync) OR as a task.
|
|
90
|
+
* When invoked without `task: {}`, returns the audit immediately.
|
|
91
|
+
* When invoked with `task: {}`, returns a task handle immediately and
|
|
92
|
+
* runs the audit in the background.
|
|
93
|
+
*/
|
|
94
|
+
@Tool({
|
|
95
|
+
name: 'audit_pizza_shops',
|
|
96
|
+
description:
|
|
97
|
+
'Runs a quality audit across all pizza shops. ' +
|
|
98
|
+
'This is a long-running operation β use task augmentation to run it asynchronously. ' +
|
|
99
|
+
'Pass `task: {}` in your tools/call request to get a task handle, ' +
|
|
100
|
+
'then poll with tasks/get and retrieve the report with tasks/result.',
|
|
101
|
+
inputSchema: AuditSchema,
|
|
102
|
+
taskSupport: 'optional',
|
|
103
|
+
examples: {
|
|
104
|
+
request: { maxShops: 3, delayPerShopMs: 0 },
|
|
105
|
+
response: {
|
|
106
|
+
summary: {
|
|
107
|
+
totalAudited: 3,
|
|
108
|
+
averageScore: 82,
|
|
109
|
+
topShop: 'Bella Napoli',
|
|
110
|
+
completedAt: '2025-01-01T00:00:00.000Z',
|
|
111
|
+
},
|
|
112
|
+
results: [
|
|
113
|
+
{
|
|
114
|
+
shopId: 'bella-napoli',
|
|
115
|
+
shopName: 'Bella Napoli',
|
|
116
|
+
score: 92,
|
|
117
|
+
grade: 'A+',
|
|
118
|
+
notes: 'Exceptional rating and review volume. Currently open.',
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
|
+
async auditPizzaShops(
|
|
125
|
+
args: z.infer<typeof AuditSchema>,
|
|
126
|
+
ctx: ExecutionContext,
|
|
127
|
+
) {
|
|
128
|
+
const allShops = this.pizzazService.getAllShops();
|
|
129
|
+
const shops = args.maxShops ? allShops.slice(0, args.maxShops) : allShops;
|
|
130
|
+
const delayMs = args.delayPerShopMs ?? 800;
|
|
131
|
+
|
|
132
|
+
ctx.logger.info('Starting pizza shop audit', {
|
|
133
|
+
totalShops: shops.length,
|
|
134
|
+
delayPerShopMs: delayMs,
|
|
135
|
+
isTask: !!ctx.task,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const results: Array<{
|
|
139
|
+
shopId: string;
|
|
140
|
+
shopName: string;
|
|
141
|
+
score: number;
|
|
142
|
+
grade: string;
|
|
143
|
+
notes: string;
|
|
144
|
+
}> = [];
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < shops.length; i++) {
|
|
147
|
+
const shop = shops[i];
|
|
148
|
+
|
|
149
|
+
// ββ Cooperative cancellation check ββββββββββββββββββββββββββββββ
|
|
150
|
+
// If the client called tasks/cancel, we stop gracefully here
|
|
151
|
+
// instead of wasting time completing the rest of the audit.
|
|
152
|
+
if (ctx.task) {
|
|
153
|
+
ctx.task.throwIfCancelled();
|
|
154
|
+
ctx.task.updateProgress(
|
|
155
|
+
`π Auditing "${shop.name}" (${i + 1}/${shops.length})β¦`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Simulate I/O-bound work (DB queries, external API calls, etc.)
|
|
160
|
+
await sleep(delayMs);
|
|
161
|
+
|
|
162
|
+
// Check again after the async work β client may have cancelled
|
|
163
|
+
if (ctx.task?.isCancelled) {
|
|
164
|
+
ctx.task.throwIfCancelled();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const score = scoreShop(shop);
|
|
168
|
+
const shopGrade = grade(score);
|
|
169
|
+
|
|
170
|
+
const notes: string[] = [];
|
|
171
|
+
if (score >= 90) notes.push('Exceptional rating and review volume.');
|
|
172
|
+
if (!shop.openNow) notes.push('Currently closed β may affect customer reach.');
|
|
173
|
+
if (shop.rating >= 4.7) notes.push('One of the highest-rated shops.');
|
|
174
|
+
if (shop.reviews < 100) notes.push('Low review count β still building reputation.');
|
|
175
|
+
|
|
176
|
+
results.push({
|
|
177
|
+
shopId: shop.id,
|
|
178
|
+
shopName: shop.name,
|
|
179
|
+
score,
|
|
180
|
+
grade: shopGrade,
|
|
181
|
+
notes: notes.join(' ') || 'Solid performer.',
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
ctx.logger.info(`Audited ${shop.name}: score=${score} (${shopGrade})`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Final progress update before completing
|
|
188
|
+
if (ctx.task) {
|
|
189
|
+
ctx.task.updateProgress(`β
Audit complete! Compiling report for ${results.length} shopsβ¦`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const averageScore =
|
|
193
|
+
results.length > 0
|
|
194
|
+
? Math.round(results.reduce((sum, r) => sum + r.score, 0) / results.length)
|
|
195
|
+
: 0;
|
|
196
|
+
|
|
197
|
+
const topShop = results.sort((a, b) => b.score - a.score)[0];
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
summary: {
|
|
201
|
+
totalAudited: results.length,
|
|
202
|
+
averageScore,
|
|
203
|
+
topShop: topShop?.shopName ?? 'N/A',
|
|
204
|
+
completedAt: new Date().toISOString(),
|
|
205
|
+
},
|
|
206
|
+
results,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* order_pizza
|
|
212
|
+
*
|
|
213
|
+
* A task-REQUIRED tool β it MUST always be called with task: {} because
|
|
214
|
+
* orders involve payment processing and confirmation steps that take time.
|
|
215
|
+
* This demonstrates the 'required' task support level.
|
|
216
|
+
*
|
|
217
|
+
* Invoke: tools/call name="order_pizza" task={}
|
|
218
|
+
* arguments: { shopId: "bella-napoli", pizzaName: "Margherita", quantity: 2 }
|
|
219
|
+
*/
|
|
220
|
+
@Tool({
|
|
221
|
+
name: 'order_pizza',
|
|
222
|
+
description:
|
|
223
|
+
'Places an order at a pizza shop. This tool REQUIRES task augmentation β ' +
|
|
224
|
+
'you must pass `task: {}` in your tools/call request. ' +
|
|
225
|
+
'Poll with tasks/get and retrieve your order confirmation via tasks/result.',
|
|
226
|
+
inputSchema: OrderPizzaSchema,
|
|
227
|
+
taskSupport: 'required',
|
|
228
|
+
examples: {
|
|
229
|
+
request: { shopId: 'bella-napoli', pizzaName: 'Margherita', quantity: 1 },
|
|
230
|
+
response: {
|
|
231
|
+
orderId: 'ORD-12345',
|
|
232
|
+
status: 'confirmed',
|
|
233
|
+
estimatedMinutes: 30,
|
|
234
|
+
total: '$18.00',
|
|
235
|
+
items: [{ name: 'Margherita', quantity: 1, price: '$18.00' }],
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
})
|
|
239
|
+
async orderPizza(
|
|
240
|
+
args: z.infer<typeof OrderPizzaSchema>,
|
|
241
|
+
ctx: ExecutionContext,
|
|
242
|
+
) {
|
|
243
|
+
const shop = this.pizzazService.getShopById(args.shopId);
|
|
244
|
+
if (!shop) {
|
|
245
|
+
throw new Error(`Pizza shop not found: ${args.shopId}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
ctx.logger.info('Processing pizza order', {
|
|
249
|
+
shopId: args.shopId,
|
|
250
|
+
pizza: args.pizzaName,
|
|
251
|
+
quantity: args.quantity,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Step 1 β validate stock
|
|
255
|
+
ctx.task?.updateProgress(`π Checking availability of "${args.pizzaName}" at ${shop.name}β¦`);
|
|
256
|
+
await sleep(600);
|
|
257
|
+
ctx.task?.throwIfCancelled();
|
|
258
|
+
|
|
259
|
+
// Step 2 β process payment
|
|
260
|
+
ctx.task?.updateProgress(`π³ Processing paymentβ¦`);
|
|
261
|
+
await sleep(1000);
|
|
262
|
+
ctx.task?.throwIfCancelled();
|
|
263
|
+
|
|
264
|
+
// Step 3 β confirm with kitchen
|
|
265
|
+
ctx.task?.updateProgress(`π Confirming order with kitchenβ¦`);
|
|
266
|
+
await sleep(700);
|
|
267
|
+
ctx.task?.throwIfCancelled();
|
|
268
|
+
|
|
269
|
+
// Step 4 β dispatch
|
|
270
|
+
ctx.task?.updateProgress(`π΄ Order dispatched! Generating confirmationβ¦`);
|
|
271
|
+
await sleep(300);
|
|
272
|
+
|
|
273
|
+
const pricePerPizza = shop.priceLevel * 9;
|
|
274
|
+
const total = pricePerPizza * args.quantity;
|
|
275
|
+
|
|
276
|
+
ctx.logger.info('Order confirmed', { shopId: args.shopId, total });
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
orderId: `ORD-${Date.now().toString(36).toUpperCase()}`,
|
|
280
|
+
status: 'confirmed',
|
|
281
|
+
shop: shop.name,
|
|
282
|
+
estimatedMinutes: 25 + Math.floor(Math.random() * 15),
|
|
283
|
+
total: `$${total.toFixed(2)}`,
|
|
284
|
+
items: [
|
|
285
|
+
{
|
|
286
|
+
name: args.pizzaName,
|
|
287
|
+
quantity: args.quantity,
|
|
288
|
+
price: `$${(pricePerPizza * args.quantity).toFixed(2)}`,
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
placedAt: new Date().toISOString(),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ToolDecorator as Tool, Widget, ExecutionContext, Injectable, z } from 'nitrostack';
|
|
1
|
+
import { ToolDecorator as Tool, Widget, ExecutionContext, Injectable, z } from '@nitrostack/core';
|
|
2
2
|
import { PizzazService } from './pizzaz.service.js';
|
|
3
3
|
|
|
4
4
|
const ShowMapSchema = z.object({
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# NitroStack Starter Environment
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# Add your environment variables here.
|
|
4
|
+
# =============================================================================
|
|
5
|
+
|
|
6
|
+
# Example:
|
|
7
|
+
# API_KEY=your_api_key_here
|