@noodleseed/agent-kit 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/manifest.json +545 -5
- package/package.json +1 -1
- package/skills/claude-code/SKILL.md +1 -1
- package/skills/claude-code/examples/acme-bistro/README.md +51 -0
- package/skills/claude-code/examples/acme-bistro/design/UX-Document.md +435 -0
- package/skills/claude-code/examples/acme-bistro/design/api-contract.md +161 -0
- package/skills/claude-code/examples/acme-bistro/design/wireframe.html +573 -0
- package/skills/claude-code/examples/acme-bistro/noodle.json +5 -0
- package/skills/claude-code/examples/acme-bistro/package.json +20 -0
- package/skills/claude-code/examples/acme-bistro/src/helpers.ts +7 -0
- package/skills/claude-code/examples/acme-bistro/src/server.ts +149 -0
- package/skills/claude-code/examples/acme-bistro/src/views/menu-cart.tsx +159 -0
- package/skills/claude-code/examples/acme-bistro/src/views/widget-style.css +228 -0
- package/skills/claude-code/examples/acme-bistro/test/server.test.ts +21 -0
- package/skills/claude-code/examples/acme-bistro/vitest.config.ts +7 -0
- package/skills/claude-code/examples/acme-discovery/README.md +85 -0
- package/skills/claude-code/examples/acme-discovery/design/UX-Document.md +441 -0
- package/skills/claude-code/examples/acme-discovery/design/wireframe.html +670 -0
- package/skills/claude-code/examples/acme-discovery/noodle.json +5 -0
- package/skills/claude-code/examples/acme-discovery/package.json +20 -0
- package/skills/claude-code/examples/acme-discovery/src/helpers.ts +7 -0
- package/skills/claude-code/examples/acme-discovery/src/server.ts +202 -0
- package/skills/claude-code/examples/acme-discovery/src/views/discovery-carousel.tsx +155 -0
- package/skills/claude-code/examples/acme-discovery/src/views/widget-style.css +207 -0
- package/skills/claude-code/examples/acme-discovery/test/server.test.ts +24 -0
- package/skills/claude-code/examples/acme-discovery/vitest.config.ts +7 -0
- package/skills/claude-code/examples/acme-tasks/README.md +80 -0
- package/skills/claude-code/examples/acme-tasks/design/UX-Document.md +277 -0
- package/skills/claude-code/examples/acme-tasks/design/wireframe.html +603 -0
- package/skills/claude-code/examples/acme-tasks/noodle.json +5 -0
- package/skills/claude-code/examples/acme-tasks/package.json +20 -0
- package/skills/claude-code/examples/acme-tasks/src/helpers.ts +6 -0
- package/skills/claude-code/examples/acme-tasks/src/server.ts +141 -0
- package/skills/claude-code/examples/acme-tasks/src/views/task-list.tsx +177 -0
- package/skills/claude-code/examples/acme-tasks/src/views/widget-style.css +227 -0
- package/skills/claude-code/examples/acme-tasks/test/server.test.ts +22 -0
- package/skills/claude-code/examples/acme-tasks/vitest.config.ts +7 -0
- package/skills/claude-code/examples/food-ordering/README.md +82 -0
- package/skills/claude-code/examples/food-ordering/noodle.json +5 -0
- package/skills/claude-code/examples/food-ordering/package.json +22 -0
- package/skills/claude-code/examples/food-ordering/src/helpers.ts +34 -0
- package/skills/claude-code/examples/food-ordering/src/server.ts +412 -0
- package/skills/claude-code/examples/food-ordering/src/views/capabilities-card.tsx +155 -0
- package/skills/claude-code/examples/food-ordering/src/views/ordering-flow.tsx +659 -0
- package/skills/claude-code/examples/food-ordering/src/views/widget-style.css +682 -0
- package/skills/claude-code/examples/food-ordering/test/server.test.ts +58 -0
- package/skills/claude-code/examples/food-ordering/vitest.config.ts +30 -0
- package/skills/claude-code/examples/hello/README.md +13 -0
- package/skills/claude-code/examples/hello/noodle.json +5 -0
- package/skills/claude-code/examples/hello/package.json +16 -0
- package/skills/claude-code/examples/hello/src/server.ts +29 -0
- package/skills/claude-code/examples/hello/test/server.test.ts +8 -0
- package/skills/claude-code/examples/weather/README.md +54 -0
- package/skills/claude-code/examples/weather/noodle.json +4 -0
- package/skills/claude-code/examples/weather/package.json +16 -0
- package/skills/claude-code/examples/weather/src/server.ts +190 -0
- package/skills/claude-code/examples/weather/test/server.test.ts +8 -0
- package/skills/claude-code/references/examples.md +16 -7
- package/skills/codex/SKILL.md +1 -1
- package/skills/codex/examples/acme-bistro/README.md +51 -0
- package/skills/codex/examples/acme-bistro/design/UX-Document.md +435 -0
- package/skills/codex/examples/acme-bistro/design/api-contract.md +161 -0
- package/skills/codex/examples/acme-bistro/design/wireframe.html +573 -0
- package/skills/codex/examples/acme-bistro/noodle.json +5 -0
- package/skills/codex/examples/acme-bistro/package.json +20 -0
- package/skills/codex/examples/acme-bistro/src/helpers.ts +7 -0
- package/skills/codex/examples/acme-bistro/src/server.ts +149 -0
- package/skills/codex/examples/acme-bistro/src/views/menu-cart.tsx +159 -0
- package/skills/codex/examples/acme-bistro/src/views/widget-style.css +228 -0
- package/skills/codex/examples/acme-bistro/test/server.test.ts +21 -0
- package/skills/codex/examples/acme-bistro/vitest.config.ts +7 -0
- package/skills/codex/examples/acme-discovery/README.md +85 -0
- package/skills/codex/examples/acme-discovery/design/UX-Document.md +441 -0
- package/skills/codex/examples/acme-discovery/design/wireframe.html +670 -0
- package/skills/codex/examples/acme-discovery/noodle.json +5 -0
- package/skills/codex/examples/acme-discovery/package.json +20 -0
- package/skills/codex/examples/acme-discovery/src/helpers.ts +7 -0
- package/skills/codex/examples/acme-discovery/src/server.ts +202 -0
- package/skills/codex/examples/acme-discovery/src/views/discovery-carousel.tsx +155 -0
- package/skills/codex/examples/acme-discovery/src/views/widget-style.css +207 -0
- package/skills/codex/examples/acme-discovery/test/server.test.ts +24 -0
- package/skills/codex/examples/acme-discovery/vitest.config.ts +7 -0
- package/skills/codex/examples/acme-tasks/README.md +80 -0
- package/skills/codex/examples/acme-tasks/design/UX-Document.md +277 -0
- package/skills/codex/examples/acme-tasks/design/wireframe.html +603 -0
- package/skills/codex/examples/acme-tasks/noodle.json +5 -0
- package/skills/codex/examples/acme-tasks/package.json +20 -0
- package/skills/codex/examples/acme-tasks/src/helpers.ts +6 -0
- package/skills/codex/examples/acme-tasks/src/server.ts +141 -0
- package/skills/codex/examples/acme-tasks/src/views/task-list.tsx +177 -0
- package/skills/codex/examples/acme-tasks/src/views/widget-style.css +227 -0
- package/skills/codex/examples/acme-tasks/test/server.test.ts +22 -0
- package/skills/codex/examples/acme-tasks/vitest.config.ts +7 -0
- package/skills/codex/examples/food-ordering/README.md +82 -0
- package/skills/codex/examples/food-ordering/noodle.json +5 -0
- package/skills/codex/examples/food-ordering/package.json +22 -0
- package/skills/codex/examples/food-ordering/src/helpers.ts +34 -0
- package/skills/codex/examples/food-ordering/src/server.ts +412 -0
- package/skills/codex/examples/food-ordering/src/views/capabilities-card.tsx +155 -0
- package/skills/codex/examples/food-ordering/src/views/ordering-flow.tsx +659 -0
- package/skills/codex/examples/food-ordering/src/views/widget-style.css +682 -0
- package/skills/codex/examples/food-ordering/test/server.test.ts +58 -0
- package/skills/codex/examples/food-ordering/vitest.config.ts +30 -0
- package/skills/codex/examples/hello/README.md +13 -0
- package/skills/codex/examples/hello/noodle.json +5 -0
- package/skills/codex/examples/hello/package.json +16 -0
- package/skills/codex/examples/hello/src/server.ts +29 -0
- package/skills/codex/examples/hello/test/server.test.ts +8 -0
- package/skills/codex/examples/weather/README.md +54 -0
- package/skills/codex/examples/weather/noodle.json +4 -0
- package/skills/codex/examples/weather/package.json +16 -0
- package/skills/codex/examples/weather/src/server.ts +190 -0
- package/skills/codex/examples/weather/test/server.test.ts +8 -0
- package/skills/codex/references/examples.md +16 -7
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "food-ordering",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "vitest run test/server.test.ts",
|
|
8
|
+
"validate": "noodle validate",
|
|
9
|
+
"dev": "noodle dev",
|
|
10
|
+
"deploy": "noodle deploy"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@vitejs/plugin-react": "latest",
|
|
14
|
+
"@noodleseed/one": "latest",
|
|
15
|
+
"@types/react": "latest",
|
|
16
|
+
"@types/react-dom": "latest",
|
|
17
|
+
"react": "latest",
|
|
18
|
+
"react-dom": "latest",
|
|
19
|
+
"vite": "latest",
|
|
20
|
+
"vitest": "latest"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ServerDefinition } from '@noodleseed/one';
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
ActionBar,
|
|
5
|
+
AppShell,
|
|
6
|
+
AsyncBoundary,
|
|
7
|
+
ChoiceGroup,
|
|
8
|
+
createViewStore,
|
|
9
|
+
DataCard,
|
|
10
|
+
DataList,
|
|
11
|
+
Field,
|
|
12
|
+
HandoffButton,
|
|
13
|
+
QuantityStepper,
|
|
14
|
+
ShellNav,
|
|
15
|
+
StatusBadge,
|
|
16
|
+
SubmitButton,
|
|
17
|
+
View,
|
|
18
|
+
ViewStack,
|
|
19
|
+
} from '@noodleseed/one/react';
|
|
20
|
+
|
|
21
|
+
import { generateHelpers } from '@noodleseed/one/react';
|
|
22
|
+
|
|
23
|
+
export type AppType = ServerDefinition;
|
|
24
|
+
|
|
25
|
+
export const {
|
|
26
|
+
useCallTool,
|
|
27
|
+
useAppFlow,
|
|
28
|
+
useHandoff,
|
|
29
|
+
useLayout,
|
|
30
|
+
useOpenExternal,
|
|
31
|
+
useSendFollowUpMessage,
|
|
32
|
+
useToolInfo,
|
|
33
|
+
useViewState,
|
|
34
|
+
} = generateHelpers<AppType>();
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import {
|
|
2
|
+
annotations,
|
|
3
|
+
asset,
|
|
4
|
+
connector,
|
|
5
|
+
resource,
|
|
6
|
+
server,
|
|
7
|
+
tool,
|
|
8
|
+
toolForWidget,
|
|
9
|
+
toolWithWidget,
|
|
10
|
+
widget,
|
|
11
|
+
z,
|
|
12
|
+
} from '@noodleseed/one';
|
|
13
|
+
|
|
14
|
+
const heroImage = asset('assets/noodle-bowl.jpg');
|
|
15
|
+
|
|
16
|
+
const state = connector('noodle_state')
|
|
17
|
+
.version('1.0.0')
|
|
18
|
+
.operation('read_state', {
|
|
19
|
+
type: 'read',
|
|
20
|
+
input: {
|
|
21
|
+
handle: { type: 'string', required: true },
|
|
22
|
+
key: { type: 'string' },
|
|
23
|
+
},
|
|
24
|
+
output: {
|
|
25
|
+
value: { type: 'object', required: true },
|
|
26
|
+
revision: { type: 'integer', required: true },
|
|
27
|
+
status: { type: 'string', required: true },
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
.operation('patch_state', {
|
|
31
|
+
type: 'action',
|
|
32
|
+
input: {
|
|
33
|
+
handle: { type: 'string', required: true },
|
|
34
|
+
expectedRevision: { type: 'integer', required: true },
|
|
35
|
+
value: { type: 'object', required: true },
|
|
36
|
+
},
|
|
37
|
+
output: {
|
|
38
|
+
value: { type: 'object', required: true },
|
|
39
|
+
revision: { type: 'integer', required: true },
|
|
40
|
+
status: { type: 'string', required: true },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const stores = [
|
|
45
|
+
{
|
|
46
|
+
id: 'harbor-noodles',
|
|
47
|
+
name: 'Harbor Noodles',
|
|
48
|
+
cuisine: 'Noodles',
|
|
49
|
+
address: '18 Pier Lane',
|
|
50
|
+
open: true,
|
|
51
|
+
etaMinutes: 24,
|
|
52
|
+
rating: 4.8,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'garden-wraps',
|
|
56
|
+
name: 'Garden Wraps',
|
|
57
|
+
cuisine: 'Vegetarian',
|
|
58
|
+
address: '44 Market Street',
|
|
59
|
+
open: true,
|
|
60
|
+
etaMinutes: 18,
|
|
61
|
+
rating: 4.6,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'midnight-tacos',
|
|
65
|
+
name: 'Midnight Tacos',
|
|
66
|
+
cuisine: 'Mexican',
|
|
67
|
+
address: '7 Station Road',
|
|
68
|
+
open: false,
|
|
69
|
+
etaMinutes: 35,
|
|
70
|
+
rating: 4.7,
|
|
71
|
+
},
|
|
72
|
+
] as const;
|
|
73
|
+
|
|
74
|
+
const menu = [
|
|
75
|
+
{
|
|
76
|
+
id: 'spicy_miso',
|
|
77
|
+
storeId: 'harbor-noodles',
|
|
78
|
+
category: 'Bowls',
|
|
79
|
+
name: 'Spicy Miso Bowl',
|
|
80
|
+
price: 16,
|
|
81
|
+
description: 'Miso broth, wheat noodles, chili crisp, egg, and greens.',
|
|
82
|
+
modifiers: ['extra_noodles', 'soft_egg', 'chili_crisp'],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'ginger_tofu',
|
|
86
|
+
storeId: 'harbor-noodles',
|
|
87
|
+
category: 'Bowls',
|
|
88
|
+
name: 'Ginger Tofu Bowl',
|
|
89
|
+
price: 15,
|
|
90
|
+
description: 'Tofu, ginger broth, mushrooms, and scallions.',
|
|
91
|
+
modifiers: ['extra_tofu', 'brown_rice', 'no_mushroom'],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'green_falafel',
|
|
95
|
+
storeId: 'garden-wraps',
|
|
96
|
+
category: 'Wraps',
|
|
97
|
+
name: 'Green Falafel Wrap',
|
|
98
|
+
price: 13,
|
|
99
|
+
description: 'Falafel, herbs, pickles, tahini, and crisp vegetables.',
|
|
100
|
+
modifiers: ['extra_tahini', 'add_fries', 'gluten_free_wrap'],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'sweet_potato',
|
|
104
|
+
storeId: 'garden-wraps',
|
|
105
|
+
category: 'Plates',
|
|
106
|
+
name: 'Sweet Potato Plate',
|
|
107
|
+
price: 14,
|
|
108
|
+
description: 'Roasted sweet potato, grains, greens, and lemon yogurt.',
|
|
109
|
+
modifiers: ['vegan_yogurt', 'extra_greens', 'hot_sauce'],
|
|
110
|
+
},
|
|
111
|
+
] as const;
|
|
112
|
+
|
|
113
|
+
const cartLine = z.object({
|
|
114
|
+
itemId: z.string(),
|
|
115
|
+
quantity: z.number().int().min(1),
|
|
116
|
+
modifiers: z.array(z.string()).default([]),
|
|
117
|
+
note: z.string().optional(),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const cartInput = z.object({
|
|
121
|
+
selectedStoreId: z.string().optional(),
|
|
122
|
+
lines: z.array(cartLine).default([]),
|
|
123
|
+
customer: z.string().default('Guest'),
|
|
124
|
+
notes: z.string().optional(),
|
|
125
|
+
subtotal: z.number().default(0),
|
|
126
|
+
expectedRevision: z.number().int().min(0).default(0),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const storeShape = z.object({
|
|
130
|
+
id: z.string(),
|
|
131
|
+
name: z.string(),
|
|
132
|
+
cuisine: z.string(),
|
|
133
|
+
address: z.string(),
|
|
134
|
+
open: z.boolean(),
|
|
135
|
+
etaMinutes: z.number(),
|
|
136
|
+
rating: z.number(),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const menuItemShape = z.object({
|
|
140
|
+
id: z.string(),
|
|
141
|
+
storeId: z.string(),
|
|
142
|
+
category: z.string(),
|
|
143
|
+
name: z.string(),
|
|
144
|
+
price: z.number(),
|
|
145
|
+
description: z.string(),
|
|
146
|
+
modifiers: z.array(z.string()),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const cartOutput = z.object({
|
|
150
|
+
selectedStoreId: z.string().optional(),
|
|
151
|
+
lines: z.array(cartLine),
|
|
152
|
+
customer: z.string(),
|
|
153
|
+
notes: z.string().optional(),
|
|
154
|
+
subtotal: z.number(),
|
|
155
|
+
status: z.enum(['draft', 'review', 'handoff']),
|
|
156
|
+
checkoutUrl: z.string().optional(),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const cartStateSchema = z.object({
|
|
160
|
+
selectedStoreId: z.string().optional(),
|
|
161
|
+
lines: z.array(cartLine),
|
|
162
|
+
customer: z.string(),
|
|
163
|
+
notes: z.string().optional(),
|
|
164
|
+
subtotal: z.number(),
|
|
165
|
+
status: z.enum(['draft', 'review', 'handoff']),
|
|
166
|
+
checkoutUrl: z.string().optional(),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
type CartLine = {
|
|
170
|
+
readonly itemId: string;
|
|
171
|
+
readonly quantity: number;
|
|
172
|
+
readonly modifiers: readonly string[];
|
|
173
|
+
readonly note?: string;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
type CartInput = {
|
|
177
|
+
readonly selectedStoreId?: string;
|
|
178
|
+
readonly lines: readonly CartLine[];
|
|
179
|
+
readonly customer: string;
|
|
180
|
+
readonly notes?: string;
|
|
181
|
+
readonly subtotal: number;
|
|
182
|
+
readonly expectedRevision: number;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const readOnly = annotations.readOnly();
|
|
186
|
+
const action = annotations.openAction({ destructive: false });
|
|
187
|
+
|
|
188
|
+
function checkoutUrl(customer: string): string {
|
|
189
|
+
return `https://orders.example.com/checkout?customer=${encodeURIComponent(customer)}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function cartValue(input: CartInput, status: 'draft' | 'review' | 'handoff') {
|
|
193
|
+
return {
|
|
194
|
+
selectedStoreId: input.selectedStoreId,
|
|
195
|
+
lines: input.lines,
|
|
196
|
+
customer: input.customer,
|
|
197
|
+
notes: input.notes,
|
|
198
|
+
subtotal: input.subtotal,
|
|
199
|
+
status,
|
|
200
|
+
...(status === 'handoff' ? { checkoutUrl: 'https://orders.example.com/checkout' } : {}),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export default server(
|
|
205
|
+
'food_ordering',
|
|
206
|
+
{
|
|
207
|
+
title: 'Food Ordering',
|
|
208
|
+
version: '1.0.0',
|
|
209
|
+
use: { state },
|
|
210
|
+
state: {
|
|
211
|
+
handles: {
|
|
212
|
+
cart: {
|
|
213
|
+
kind: 'cart',
|
|
214
|
+
version: 'v1',
|
|
215
|
+
scope: 'caller',
|
|
216
|
+
ttlSeconds: 7200,
|
|
217
|
+
schema: cartStateSchema,
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
branding: {
|
|
222
|
+
name: 'Food Ordering',
|
|
223
|
+
accent: '#0F8F5F',
|
|
224
|
+
surface: '#F7F7F5',
|
|
225
|
+
surfaceDark: '#111820',
|
|
226
|
+
logo: {
|
|
227
|
+
uri: heroImage,
|
|
228
|
+
alt: 'Food Ordering noodle bowl',
|
|
229
|
+
},
|
|
230
|
+
radius: 'lg',
|
|
231
|
+
density: 'comfortable',
|
|
232
|
+
},
|
|
233
|
+
handoff: {
|
|
234
|
+
allowedDomains: ['https://orders.example.com'],
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
[
|
|
238
|
+
toolWithWidget('open_ordering', {
|
|
239
|
+
description:
|
|
240
|
+
'Open a complete food-ordering widget with store discovery, menu browsing, cart review, and checkout handoff.',
|
|
241
|
+
annotations: readOnly,
|
|
242
|
+
input: z.object({
|
|
243
|
+
query: z.string().optional(),
|
|
244
|
+
customer: z.string().default('Guest'),
|
|
245
|
+
}),
|
|
246
|
+
output: z.object({
|
|
247
|
+
status: z.string(),
|
|
248
|
+
customer: z.string(),
|
|
249
|
+
stores: z.array(storeShape),
|
|
250
|
+
featuredItems: z.array(menuItemShape),
|
|
251
|
+
fallback: z.string(),
|
|
252
|
+
}),
|
|
253
|
+
fulfil: ({ input }) => ({
|
|
254
|
+
status: 'Ready to build a food order.',
|
|
255
|
+
customer: input.customer,
|
|
256
|
+
stores,
|
|
257
|
+
featuredItems: menu,
|
|
258
|
+
fallback: 'Open stores: Harbor Noodles (Noodles), Garden Wraps (Vegetarian).',
|
|
259
|
+
}),
|
|
260
|
+
widgetTitle: 'Food ordering',
|
|
261
|
+
domain: 'https://orders.example.com',
|
|
262
|
+
view: {
|
|
263
|
+
component: 'ordering-flow',
|
|
264
|
+
entry: './views/ordering-flow.tsx',
|
|
265
|
+
},
|
|
266
|
+
widgetDescription:
|
|
267
|
+
'A complete consumer ordering surface with app-only helper tools, cart state, and checkout handoff.',
|
|
268
|
+
csp: {
|
|
269
|
+
connectDomains: ['https://orders.example.com'],
|
|
270
|
+
resourceDomains: ['https://orders.example.com'],
|
|
271
|
+
frameDomains: ['https://orders.example.com'],
|
|
272
|
+
},
|
|
273
|
+
permissions: { clipboardWrite: {} },
|
|
274
|
+
}),
|
|
275
|
+
toolForWidget('search_stores', {
|
|
276
|
+
description: 'Filter synthetic restaurants for the ordering widget.',
|
|
277
|
+
annotations: readOnly,
|
|
278
|
+
input: z.object({
|
|
279
|
+
query: z.string().optional(),
|
|
280
|
+
openOnly: z.boolean().default(false),
|
|
281
|
+
}),
|
|
282
|
+
output: z.object({ stores: z.array(storeShape) }),
|
|
283
|
+
fulfil: () => ({ stores }),
|
|
284
|
+
}),
|
|
285
|
+
toolForWidget('load_menu', {
|
|
286
|
+
description: 'Load synthetic menu categories and items for one store.',
|
|
287
|
+
annotations: readOnly,
|
|
288
|
+
input: z.object({ storeId: z.string() }),
|
|
289
|
+
output: z.object({
|
|
290
|
+
storeId: z.string(),
|
|
291
|
+
stores: z.array(storeShape),
|
|
292
|
+
items: z.array(menuItemShape),
|
|
293
|
+
}),
|
|
294
|
+
fulfil: ({ input }) => ({ storeId: input.storeId, stores, items: menu }),
|
|
295
|
+
}),
|
|
296
|
+
toolForWidget('load_item', {
|
|
297
|
+
description: 'Load item details and modifier options for the ordering widget.',
|
|
298
|
+
annotations: readOnly,
|
|
299
|
+
input: z.object({ itemId: z.string() }),
|
|
300
|
+
output: z.object({ itemId: z.string(), items: z.array(menuItemShape) }),
|
|
301
|
+
fulfil: ({ input }) => ({ itemId: input.itemId, items: menu }),
|
|
302
|
+
}),
|
|
303
|
+
toolForWidget('read_cart', {
|
|
304
|
+
description: 'Read the caller-scoped ordering cart state.',
|
|
305
|
+
annotations: readOnly,
|
|
306
|
+
input: z.object({}),
|
|
307
|
+
output: z.object({
|
|
308
|
+
value: z.unknown(),
|
|
309
|
+
revision: z.number(),
|
|
310
|
+
status: z.string(),
|
|
311
|
+
}),
|
|
312
|
+
fulfil: ({ connectors }) => {
|
|
313
|
+
const state = connectors.state.readState({ handle: 'cart' });
|
|
314
|
+
return { value: state.value, revision: state.revision, status: state.status };
|
|
315
|
+
},
|
|
316
|
+
}),
|
|
317
|
+
toolForWidget('sync_cart', {
|
|
318
|
+
description: 'Patch the caller-scoped ordering cart with the widget cart mirror.',
|
|
319
|
+
annotations: action,
|
|
320
|
+
input: cartInput,
|
|
321
|
+
output: z.object({
|
|
322
|
+
cart: cartOutput,
|
|
323
|
+
revision: z.number(),
|
|
324
|
+
status: z.string(),
|
|
325
|
+
}),
|
|
326
|
+
fulfil: ({ input, connectors }) => {
|
|
327
|
+
const cart = cartValue(input, 'draft');
|
|
328
|
+
const state = connectors.state.patchState({
|
|
329
|
+
handle: 'cart',
|
|
330
|
+
expectedRevision: input.expectedRevision,
|
|
331
|
+
value: cart,
|
|
332
|
+
});
|
|
333
|
+
return { cart, revision: state.revision, status: state.status };
|
|
334
|
+
},
|
|
335
|
+
}),
|
|
336
|
+
toolForWidget('prepare_checkout', {
|
|
337
|
+
description: 'Prepare the caller-scoped cart for checkout handoff.',
|
|
338
|
+
annotations: action,
|
|
339
|
+
input: cartInput,
|
|
340
|
+
output: z.object({
|
|
341
|
+
cart: cartOutput,
|
|
342
|
+
revision: z.number(),
|
|
343
|
+
checkoutUrl: z.string(),
|
|
344
|
+
}),
|
|
345
|
+
fulfil: ({ input, connectors }) => {
|
|
346
|
+
const cart = cartValue(input, 'handoff');
|
|
347
|
+
const state = connectors.state.patchState({
|
|
348
|
+
handle: 'cart',
|
|
349
|
+
expectedRevision: input.expectedRevision,
|
|
350
|
+
value: cart,
|
|
351
|
+
});
|
|
352
|
+
return {
|
|
353
|
+
cart,
|
|
354
|
+
revision: state.revision,
|
|
355
|
+
checkoutUrl: cart.checkoutUrl ?? checkoutUrl(input.customer),
|
|
356
|
+
};
|
|
357
|
+
},
|
|
358
|
+
}),
|
|
359
|
+
tool('summarize_ordering_options', {
|
|
360
|
+
description: 'Summarize available stores and menu examples without opening the widget.',
|
|
361
|
+
annotations: readOnly,
|
|
362
|
+
input: z.object({}),
|
|
363
|
+
output: z.object({
|
|
364
|
+
stores: z.array(storeShape),
|
|
365
|
+
featuredItems: z.array(menuItemShape),
|
|
366
|
+
}),
|
|
367
|
+
fulfil: () => ({ stores, featuredItems: menu }),
|
|
368
|
+
}),
|
|
369
|
+
tool('show_capabilities', {
|
|
370
|
+
description: 'Return a concise summary for the standalone widget capability preview.',
|
|
371
|
+
annotations: readOnly,
|
|
372
|
+
input: z.object({}),
|
|
373
|
+
output: z.object({ status: z.string(), note: z.string() }),
|
|
374
|
+
fulfil: () => ({
|
|
375
|
+
status: 'Food Ordering widget capabilities are ready.',
|
|
376
|
+
note: 'Standalone preview covers React views, helper tools, cart state, handoff, CSP, and permissions.',
|
|
377
|
+
}),
|
|
378
|
+
}),
|
|
379
|
+
resource('food_ordering_guide', {
|
|
380
|
+
uri: 'docs://food-ordering',
|
|
381
|
+
title: 'Food Ordering widget guide',
|
|
382
|
+
description: 'Synthetic guide resource for the consumer ordering flagship.',
|
|
383
|
+
mimeType: 'text/markdown',
|
|
384
|
+
// Return the resource body directly; the runtime maps it into MCP `contents` using the
|
|
385
|
+
// resource's own uri + mimeType. Do not return a `{ contents: [...] }` wrapper — that double-wraps.
|
|
386
|
+
fulfil: () =>
|
|
387
|
+
[
|
|
388
|
+
'# Food Ordering Widget Guide',
|
|
389
|
+
'',
|
|
390
|
+
'- Demonstrates a multi-step ordering widget, app-only helper tools, typed cart state, and handoff.',
|
|
391
|
+
'- Store, menu, and checkout data are synthetic and contain no customer credentials.',
|
|
392
|
+
'- Checkout opens an allowlisted example URL; payment and final ordering remain out of scope.',
|
|
393
|
+
].join('\n'),
|
|
394
|
+
}),
|
|
395
|
+
widget('capabilities_card', {
|
|
396
|
+
tool: 'show_capabilities',
|
|
397
|
+
title: 'Food Ordering capabilities',
|
|
398
|
+
domain: 'https://orders.example.com',
|
|
399
|
+
description: 'Standalone widget resource for previewing the ordering capability surface.',
|
|
400
|
+
view: {
|
|
401
|
+
component: 'capabilities-card',
|
|
402
|
+
entry: './views/capabilities-card.tsx',
|
|
403
|
+
},
|
|
404
|
+
csp: {
|
|
405
|
+
connectDomains: ['https://orders.example.com'],
|
|
406
|
+
resourceDomains: ['https://orders.example.com'],
|
|
407
|
+
frameDomains: ['https://orders.example.com'],
|
|
408
|
+
},
|
|
409
|
+
permissions: { clipboardWrite: {} },
|
|
410
|
+
}),
|
|
411
|
+
],
|
|
412
|
+
);
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { useLayout, useOpenExternal, useSendFollowUpMessage, useToolInfo } from '../helpers.js';
|
|
2
|
+
import './widget-style.css';
|
|
3
|
+
|
|
4
|
+
const capabilities: readonly {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly description: string;
|
|
7
|
+
}[] = [
|
|
8
|
+
{
|
|
9
|
+
name: 'React view resource',
|
|
10
|
+
description: 'Compiled widget HTML with a hydrated React entrypoint.',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'Widget helper calls',
|
|
14
|
+
description: 'App-only tools route through the same host-mediated path.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'Durable cart state',
|
|
18
|
+
description: 'Caller-scoped cart records use Noodle state handles with revisions.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'External handoff',
|
|
22
|
+
description: 'Checkout opens through declared allowlisted domains.',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'Model context',
|
|
26
|
+
description: 'Relevant UI state is mirrored through data-llm.',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'CSP and permissions',
|
|
30
|
+
description: 'Resource metadata declares network and clipboard needs.',
|
|
31
|
+
},
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
export default function CapabilitiesCard() {
|
|
35
|
+
const { theme } = useLayout();
|
|
36
|
+
const toolInfo = useToolInfo('show_capabilities');
|
|
37
|
+
const openExternal = useOpenExternal();
|
|
38
|
+
const sendFollowUpMessage = useSendFollowUpMessage();
|
|
39
|
+
const structured = toolInfo.structuredContent as
|
|
40
|
+
| { readonly status?: string; readonly note?: string }
|
|
41
|
+
| undefined;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<main
|
|
45
|
+
className={`nw-shell${theme === 'dark' ? ' dark' : ''}`}
|
|
46
|
+
data-llm={`Food Ordering capabilities: ${capabilities.map((item) => item.name).join(', ')}`}
|
|
47
|
+
>
|
|
48
|
+
<section className="nw-card">
|
|
49
|
+
<header className="nw-header">
|
|
50
|
+
<span className="nw-icon" aria-hidden="true">
|
|
51
|
+
<PuzzleIcon />
|
|
52
|
+
</span>
|
|
53
|
+
<div className="nw-title-block">
|
|
54
|
+
<h1 className="nw-title">MCP capabilities</h1>
|
|
55
|
+
<p className="nw-subtitle">
|
|
56
|
+
{structured?.status ?? 'Food Ordering widget capabilities are ready.'}
|
|
57
|
+
</p>
|
|
58
|
+
</div>
|
|
59
|
+
<span className="nw-chip">Connected</span>
|
|
60
|
+
</header>
|
|
61
|
+
<div className="nw-body">
|
|
62
|
+
<p className="nw-section-title">Available features</p>
|
|
63
|
+
<ul className="nw-feature-list">
|
|
64
|
+
{capabilities.map((capability) => (
|
|
65
|
+
<li className="nw-feature" key={capability.name}>
|
|
66
|
+
<span className="nw-check" aria-hidden="true">
|
|
67
|
+
<CheckIcon />
|
|
68
|
+
</span>
|
|
69
|
+
<span>
|
|
70
|
+
<span className="nw-feature-name">{capability.name}</span>
|
|
71
|
+
<span className="nw-feature-desc">{capability.description}</span>
|
|
72
|
+
</span>
|
|
73
|
+
</li>
|
|
74
|
+
))}
|
|
75
|
+
</ul>
|
|
76
|
+
<div className="nw-actions">
|
|
77
|
+
<button
|
|
78
|
+
className="nw-button nw-button-primary"
|
|
79
|
+
type="button"
|
|
80
|
+
onClick={() =>
|
|
81
|
+
sendFollowUpMessage({
|
|
82
|
+
prompt: 'Summarize what the Food Ordering MCP App widget demonstrates.',
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
>
|
|
86
|
+
<SparkIcon />
|
|
87
|
+
Ask assistant
|
|
88
|
+
</button>
|
|
89
|
+
<button
|
|
90
|
+
className="nw-button"
|
|
91
|
+
type="button"
|
|
92
|
+
onClick={() => openExternal('https://example.com/noodle-widget-docs')}
|
|
93
|
+
>
|
|
94
|
+
<ExternalIcon />
|
|
95
|
+
Open docs
|
|
96
|
+
</button>
|
|
97
|
+
</div>
|
|
98
|
+
{structured?.note ? <p className="nw-note">{structured.note}</p> : null}
|
|
99
|
+
</div>
|
|
100
|
+
<footer className="nw-footer">
|
|
101
|
+
<span className="nw-meta">
|
|
102
|
+
<ShieldIcon />
|
|
103
|
+
Secure widget surface
|
|
104
|
+
</span>
|
|
105
|
+
<span>v1.0.0</span>
|
|
106
|
+
</footer>
|
|
107
|
+
</section>
|
|
108
|
+
</main>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function PuzzleIcon() {
|
|
113
|
+
return (
|
|
114
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
115
|
+
<path d="M8 4h5v4h3a2 2 0 0 1 0 4h-3v3H9v-3H6a2 2 0 0 1 0-4h2V4Z" />
|
|
116
|
+
<path d="M13 15v5H4v-5" />
|
|
117
|
+
<path d="M13 20h7v-8" />
|
|
118
|
+
</svg>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function CheckIcon() {
|
|
123
|
+
return (
|
|
124
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
125
|
+
<path d="m6 12 4 4 8-8" />
|
|
126
|
+
</svg>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function SparkIcon() {
|
|
131
|
+
return (
|
|
132
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
133
|
+
<path d="m12 3 1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8L12 3Z" />
|
|
134
|
+
</svg>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function ExternalIcon() {
|
|
139
|
+
return (
|
|
140
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
141
|
+
<path d="M14 4h6v6" />
|
|
142
|
+
<path d="m20 4-9 9" />
|
|
143
|
+
<path d="M20 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5" />
|
|
144
|
+
</svg>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function ShieldIcon() {
|
|
149
|
+
return (
|
|
150
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
151
|
+
<path d="M12 3 5 6v5c0 4.4 2.8 8.3 7 10 4.2-1.7 7-5.6 7-10V6l-7-3Z" />
|
|
152
|
+
<path d="m9 12 2 2 4-4" />
|
|
153
|
+
</svg>
|
|
154
|
+
);
|
|
155
|
+
}
|