@nitrostack/cli 1.0.6 β†’ 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.
@@ -1,252 +1,77 @@
1
1
  # πŸ• NitroStack Pizza Shop Finder
2
2
 
3
- A comprehensive NitroStack template showcasing interactive pizza shop discovery with maps, lists, and detailed views. This template demonstrates the NitroStack Widget SDK for building beautiful, interactive widgets.
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
- ### 🎨 **Widget SDK Features**
8
- - βœ… `useTheme()` - Automatic dark mode support
9
- - βœ… `useWidgetState()` - Persistent favorites and view preferences
10
- - βœ… `useMaxHeight()` - Responsive height-aware layouts
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
- ### πŸ—ΊοΈ **Three Interactive Widgets**
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
- ### Prerequisites
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
- # Create a new project
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
- ### Configure Mapbox (Optional but Recommended)
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
- NEXT_PUBLIC_MAPBOX_TOKEN=your_mapbox_token_here
26
+ npm run install:all
57
27
  ```
58
28
 
59
- > **Note**: The template works without Mapbox, but the map widget will show a placeholder. You can still use the list and shop detail widgets.
60
-
61
- ### Run Development Server
29
+ ### 3. Step Up NitroStudio
62
30
 
63
- ```bash
64
- npm run dev
65
- ```
31
+ NitroStudio provides the best experience for visual testing of widgets and maps.
66
32
 
67
- This starts:
68
- - **MCP Server** - Hot reloads on code changes
69
- - **Studio** on http://localhost:3000 - Visual testing environment
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
- ### Test in Studio
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
- ## πŸ“ Project Structure
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
- ## 🎨 Widget Features
43
+ To enable the interactive map, add your Mapbox token:
105
44
 
106
- ### Pizza Map Widget
107
- - **Interactive Mapbox map** with custom markers
108
- - **Shop sidebar** with quick selection
109
- - **Fullscreen mode** for better exploration
110
- - **Persistent favorites** using `useWidgetState()`
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
- ### Pizza List Widget
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
- ## πŸ”§ Commands
53
+ ## πŸ› οΈ Commands
128
54
 
129
55
  ```bash
130
- # Installation
131
- npm install # Install all dependencies (root + widgets)
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
- ```bash
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
- ## πŸ“š Next Steps
240
-
241
- - Try the **Starter Template** - Learn the basics
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
- ## License
66
+ ## πŸ“ Structure
247
67
 
248
- MIT
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
- **Built with ❀️ using NitroStack**
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
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
+ }
@@ -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