@noodleseed/agent-kit 0.14.0 → 0.16.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 (118) hide show
  1. package/manifest.json +549 -9
  2. package/package.json +1 -1
  3. package/skills/claude-code/SKILL.md +6 -4
  4. package/skills/claude-code/examples/acme-bistro/README.md +51 -0
  5. package/skills/claude-code/examples/acme-bistro/design/UX-Document.md +435 -0
  6. package/skills/claude-code/examples/acme-bistro/design/api-contract.md +161 -0
  7. package/skills/claude-code/examples/acme-bistro/design/wireframe.html +573 -0
  8. package/skills/claude-code/examples/acme-bistro/noodle.json +5 -0
  9. package/skills/claude-code/examples/acme-bistro/package.json +20 -0
  10. package/skills/claude-code/examples/acme-bistro/src/helpers.ts +7 -0
  11. package/skills/claude-code/examples/acme-bistro/src/server.ts +149 -0
  12. package/skills/claude-code/examples/acme-bistro/src/views/menu-cart.tsx +159 -0
  13. package/skills/claude-code/examples/acme-bistro/src/views/widget-style.css +228 -0
  14. package/skills/claude-code/examples/acme-bistro/test/server.test.ts +21 -0
  15. package/skills/claude-code/examples/acme-bistro/vitest.config.ts +7 -0
  16. package/skills/claude-code/examples/acme-discovery/README.md +85 -0
  17. package/skills/claude-code/examples/acme-discovery/design/UX-Document.md +441 -0
  18. package/skills/claude-code/examples/acme-discovery/design/wireframe.html +670 -0
  19. package/skills/claude-code/examples/acme-discovery/noodle.json +5 -0
  20. package/skills/claude-code/examples/acme-discovery/package.json +20 -0
  21. package/skills/claude-code/examples/acme-discovery/src/helpers.ts +7 -0
  22. package/skills/claude-code/examples/acme-discovery/src/server.ts +202 -0
  23. package/skills/claude-code/examples/acme-discovery/src/views/discovery-carousel.tsx +155 -0
  24. package/skills/claude-code/examples/acme-discovery/src/views/widget-style.css +207 -0
  25. package/skills/claude-code/examples/acme-discovery/test/server.test.ts +24 -0
  26. package/skills/claude-code/examples/acme-discovery/vitest.config.ts +7 -0
  27. package/skills/claude-code/examples/acme-tasks/README.md +80 -0
  28. package/skills/claude-code/examples/acme-tasks/design/UX-Document.md +277 -0
  29. package/skills/claude-code/examples/acme-tasks/design/wireframe.html +603 -0
  30. package/skills/claude-code/examples/acme-tasks/noodle.json +5 -0
  31. package/skills/claude-code/examples/acme-tasks/package.json +20 -0
  32. package/skills/claude-code/examples/acme-tasks/src/helpers.ts +6 -0
  33. package/skills/claude-code/examples/acme-tasks/src/server.ts +141 -0
  34. package/skills/claude-code/examples/acme-tasks/src/views/task-list.tsx +177 -0
  35. package/skills/claude-code/examples/acme-tasks/src/views/widget-style.css +227 -0
  36. package/skills/claude-code/examples/acme-tasks/test/server.test.ts +22 -0
  37. package/skills/claude-code/examples/acme-tasks/vitest.config.ts +7 -0
  38. package/skills/claude-code/examples/food-ordering/README.md +82 -0
  39. package/skills/claude-code/examples/food-ordering/noodle.json +5 -0
  40. package/skills/claude-code/examples/food-ordering/package.json +22 -0
  41. package/skills/claude-code/examples/food-ordering/src/helpers.ts +34 -0
  42. package/skills/claude-code/examples/food-ordering/src/server.ts +412 -0
  43. package/skills/claude-code/examples/food-ordering/src/views/capabilities-card.tsx +155 -0
  44. package/skills/claude-code/examples/food-ordering/src/views/ordering-flow.tsx +659 -0
  45. package/skills/claude-code/examples/food-ordering/src/views/widget-style.css +682 -0
  46. package/skills/claude-code/examples/food-ordering/test/server.test.ts +58 -0
  47. package/skills/claude-code/examples/food-ordering/vitest.config.ts +30 -0
  48. package/skills/claude-code/examples/hello/README.md +13 -0
  49. package/skills/claude-code/examples/hello/noodle.json +5 -0
  50. package/skills/claude-code/examples/hello/package.json +16 -0
  51. package/skills/claude-code/examples/hello/src/server.ts +29 -0
  52. package/skills/claude-code/examples/hello/test/server.test.ts +8 -0
  53. package/skills/claude-code/examples/weather/README.md +85 -0
  54. package/skills/claude-code/examples/weather/noodle.json +4 -0
  55. package/skills/claude-code/examples/weather/package.json +16 -0
  56. package/skills/claude-code/examples/weather/src/server.ts +261 -0
  57. package/skills/claude-code/examples/weather/test/server.test.ts +29 -0
  58. package/skills/claude-code/references/connect-an-api.md +63 -1
  59. package/skills/claude-code/references/examples.md +16 -7
  60. package/skills/claude-code/references/troubleshooting.md +2 -0
  61. package/skills/codex/SKILL.md +6 -4
  62. package/skills/codex/examples/acme-bistro/README.md +51 -0
  63. package/skills/codex/examples/acme-bistro/design/UX-Document.md +435 -0
  64. package/skills/codex/examples/acme-bistro/design/api-contract.md +161 -0
  65. package/skills/codex/examples/acme-bistro/design/wireframe.html +573 -0
  66. package/skills/codex/examples/acme-bistro/noodle.json +5 -0
  67. package/skills/codex/examples/acme-bistro/package.json +20 -0
  68. package/skills/codex/examples/acme-bistro/src/helpers.ts +7 -0
  69. package/skills/codex/examples/acme-bistro/src/server.ts +149 -0
  70. package/skills/codex/examples/acme-bistro/src/views/menu-cart.tsx +159 -0
  71. package/skills/codex/examples/acme-bistro/src/views/widget-style.css +228 -0
  72. package/skills/codex/examples/acme-bistro/test/server.test.ts +21 -0
  73. package/skills/codex/examples/acme-bistro/vitest.config.ts +7 -0
  74. package/skills/codex/examples/acme-discovery/README.md +85 -0
  75. package/skills/codex/examples/acme-discovery/design/UX-Document.md +441 -0
  76. package/skills/codex/examples/acme-discovery/design/wireframe.html +670 -0
  77. package/skills/codex/examples/acme-discovery/noodle.json +5 -0
  78. package/skills/codex/examples/acme-discovery/package.json +20 -0
  79. package/skills/codex/examples/acme-discovery/src/helpers.ts +7 -0
  80. package/skills/codex/examples/acme-discovery/src/server.ts +202 -0
  81. package/skills/codex/examples/acme-discovery/src/views/discovery-carousel.tsx +155 -0
  82. package/skills/codex/examples/acme-discovery/src/views/widget-style.css +207 -0
  83. package/skills/codex/examples/acme-discovery/test/server.test.ts +24 -0
  84. package/skills/codex/examples/acme-discovery/vitest.config.ts +7 -0
  85. package/skills/codex/examples/acme-tasks/README.md +80 -0
  86. package/skills/codex/examples/acme-tasks/design/UX-Document.md +277 -0
  87. package/skills/codex/examples/acme-tasks/design/wireframe.html +603 -0
  88. package/skills/codex/examples/acme-tasks/noodle.json +5 -0
  89. package/skills/codex/examples/acme-tasks/package.json +20 -0
  90. package/skills/codex/examples/acme-tasks/src/helpers.ts +6 -0
  91. package/skills/codex/examples/acme-tasks/src/server.ts +141 -0
  92. package/skills/codex/examples/acme-tasks/src/views/task-list.tsx +177 -0
  93. package/skills/codex/examples/acme-tasks/src/views/widget-style.css +227 -0
  94. package/skills/codex/examples/acme-tasks/test/server.test.ts +22 -0
  95. package/skills/codex/examples/acme-tasks/vitest.config.ts +7 -0
  96. package/skills/codex/examples/food-ordering/README.md +82 -0
  97. package/skills/codex/examples/food-ordering/noodle.json +5 -0
  98. package/skills/codex/examples/food-ordering/package.json +22 -0
  99. package/skills/codex/examples/food-ordering/src/helpers.ts +34 -0
  100. package/skills/codex/examples/food-ordering/src/server.ts +412 -0
  101. package/skills/codex/examples/food-ordering/src/views/capabilities-card.tsx +155 -0
  102. package/skills/codex/examples/food-ordering/src/views/ordering-flow.tsx +659 -0
  103. package/skills/codex/examples/food-ordering/src/views/widget-style.css +682 -0
  104. package/skills/codex/examples/food-ordering/test/server.test.ts +58 -0
  105. package/skills/codex/examples/food-ordering/vitest.config.ts +30 -0
  106. package/skills/codex/examples/hello/README.md +13 -0
  107. package/skills/codex/examples/hello/noodle.json +5 -0
  108. package/skills/codex/examples/hello/package.json +16 -0
  109. package/skills/codex/examples/hello/src/server.ts +29 -0
  110. package/skills/codex/examples/hello/test/server.test.ts +8 -0
  111. package/skills/codex/examples/weather/README.md +85 -0
  112. package/skills/codex/examples/weather/noodle.json +4 -0
  113. package/skills/codex/examples/weather/package.json +16 -0
  114. package/skills/codex/examples/weather/src/server.ts +261 -0
  115. package/skills/codex/examples/weather/test/server.test.ts +29 -0
  116. package/skills/codex/references/connect-an-api.md +63 -1
  117. package/skills/codex/references/examples.md +16 -7
  118. package/skills/codex/references/troubleshooting.md +2 -0
@@ -0,0 +1,51 @@
1
+ # Acme Bistro — end-to-end ordering with payment-only handoff
2
+
3
+ A Noodle MCP App for **Acme Bistro**, a fictional restaurant. It is the flagship for the **end-to-end
4
+ transaction** pattern: the customer browses the menu and builds the order *in chat*, and the order
5
+ completes in chat — only **payment** hands off, via a signed checkout deep link (the card never touches
6
+ the app). It pairs a `toolWithWidget` menu/cart with `toolForWidget` cart helpers and a model-visible
7
+ `create_checkout` tool backed by `handoff.allowedDomains`.
8
+
9
+ Capability slot: **end-to-end in-chat transaction + payment-only handoff**, plus a worked **design-first**
10
+ deliverable set (`design/` — a UX Document, a single-file HTML wireframe with an embedded OpenAI Apps SDK
11
+ compliance audit, and a Recommended API contract). It sets the quality bar the `noodle-seed` skill's
12
+ `references/experience-design.md` and `references/chatgpt-compliance.md` teach. (Distinct from
13
+ `food-ordering`, which is the broad widget-composition proof; this one owns the design-first end-to-end +
14
+ compliance exemplar.)
15
+
16
+ ## Design deliverables (the standard to match)
17
+
18
+ - [`design/UX-Document.md`](design/UX-Document.md) — the house-style UX Document.
19
+ - [`design/wireframe.html`](design/wireframe.html) — the single-file wireframe (open in a browser) with the
20
+ embedded OpenAI Apps SDK compliance audit.
21
+ - [`design/api-contract.md`](design/api-contract.md) — the Recommended API shapes for the partner's kitchen/
22
+ ordering backend.
23
+
24
+ ## Local author loop
25
+
26
+ ```sh
27
+ noodle validate
28
+ noodle test
29
+ noodle dev
30
+ ```
31
+
32
+ In another terminal:
33
+
34
+ ```sh
35
+ noodle tools list
36
+ noodle tools call show_menu --args '{"customer":"Asha"}'
37
+ noodle tools call create_checkout --args '{"customer":"Asha","cartToken":"stone_pizzax2-lemon_tartx1","total":36}'
38
+ noodle check --target chatgpt
39
+ ```
40
+
41
+ ## Deploy
42
+
43
+ ```sh
44
+ noodle link --org demo --app acme-bistro
45
+ noodle deploy --access owner-only
46
+ noodle open
47
+ ```
48
+
49
+ This example has no connector secrets and does not include tokens, caller-key mechanisms, or
50
+ `.env.noodle` values. The menu, prices, and URLs are fictional; payment is handled off-app on
51
+ `acme.example`, never in chat.
@@ -0,0 +1,435 @@
1
+ # Acme Bistro ChatGPT App — User Flow & Experience Document
2
+
3
+ **Prepared by:** Noodle Seed
4
+ **Scope:** Diners browse the Acme Bistro menu, build and confirm an order inside ChatGPT, then hand off once to a signed checkout link to pay — the card never touches the app.
5
+ **Status:** Design specification (v1)
6
+ **Funnel boundary:** IN the app — menu, order building, order confirmation, and the checkout hand-off, all in-chat. OFF-app — **payment only**, on Acme Bistro's PCI-scoped checkout at `pay.acme.example`. **No per-user OAuth in this app**; the connector runs on Acme's own service credentials, and the diner authenticates (if at all) only on the payment page.
7
+
8
+ ---
9
+
10
+ ## 0. The One-Paragraph Thesis
11
+
12
+ A hungry diner opens ChatGPT and types *"order me two margheritas and a lemon tart from Acme Bistro."* Today that intent scatters across a search, a delivery-app download, a menu scroll, and a checkout form. Acme Bistro collapses it into one conversation: the model reads the live menu, parses the order out of plain language, renders a running cart the diner can nudge with a tap or a sentence, and — only when the order is right — mints a **signed, expiring payment link** that opens Acme's own checkout. We own the entire pre-payment experience; Acme owns the money. That split is deliberate and it is the product: the app never sees a card number, so Acme's PCI scope never grows, yet the diner completes a real, paid-intent order without leaving the chat. For a single restaurant, this is the cheapest possible storefront on the fastest-growing surface — one `server.ts`, no app to install, and every order arrives at Acme's checkout already built. If you can convincingly finish this order in a sentence, you have out-competed every tap-driven ordering app on the one axis they cannot copy: language.
13
+
14
+ ---
15
+
16
+ ## 1. Acme Bistro Product Overview (Knowledge Base)
17
+
18
+ **Acme Bistro is a single fictional neighbourhood restaurant** offering a short, curated menu for pickup ordering. Unlike a marketplace aggregator, there is one kitchen, one menu, and one checkout — which makes the conversational surface tight and the guardrails simple. The ChatGPT App is Acme's storefront on ChatGPT: it shows the menu, builds the order, and passes a ready cart to Acme's payment page.
19
+
20
+ ### 1.1 The Menu (authoritative — the app must know this exactly)
21
+
22
+ | Item | ID | Price (USD) | Course |
23
+ |------|-----|-------------|--------|
24
+ | Stone-baked Margherita | `stone_pizza` | $14 | Mains |
25
+ | Harvest Roast Bowl | `roast_bowl` | $13 | Mains |
26
+ | House Garden Salad | `house_salad` | $11 | Starters |
27
+ | Lemon Tart | `lemon_tart` | $8 | Desserts |
28
+ | Sparkling Water | `sparkling` | $4 | Drinks |
29
+
30
+ Prices are whole-dollar and fixed for v1. The **backend owns pricing** — the widget sums line items for display, but the amount that reaches checkout is recomputed and re-validated by Acme at `pay.acme.example`. The menu is small enough to render in a single inline widget with no pagination.
31
+
32
+ ### 1.2 The End-to-End Model (the defining choice)
33
+
34
+ Every other decision follows from one line: **the order is built and confirmed in chat; only payment hands off.** There is no in-chat card capture, no wallet, no stored payment method. When the diner is ready, the app calls `create_checkout`, which returns a **signed deep link** carrying a url-safe cart token and the numeric total; ChatGPT opens it, and Acme's checkout takes the card. The MCP server is never in the payment path.
35
+
36
+ ### 1.3 Business Model & Why Acme Wants This
37
+
38
+ Acme's bottleneck is reach, not kitchen capacity: a neighbourhood restaurant has no realistic way onto a conversational surface without building an app. The ChatGPT App removes that bottleneck for the cost of one authored server. **Attribution is built in** — every checkout link carries `src=chatgpt`, so Acme can measure exactly how much revenue the conversational storefront drives against their existing web orders.
39
+
40
+ ---
41
+
42
+ ## 2. Competitive Landscape — Food Ordering on ChatGPT
43
+
44
+ | Pattern | Examples | Strength | Gap Acme fills |
45
+ |---------|----------|----------|----------------|
46
+ | **Marketplace aggregators** | Large delivery apps | Vast networks, delivery logistics | Menu markups, no single-restaurant intimacy, heavy handoff to a separate app |
47
+ | **Reservation / discovery** | Booking + reviews apps | Strong discovery inventory | No ordering, no checkout |
48
+ | **Acme Bistro (this app)** | — | One kitchen, honest single-menu pricing, full order built in chat, payment on Acme's own PCI checkout | — |
49
+
50
+ **Acme's position:** Acme is not trying to be a marketplace. Its advantage inside ChatGPT is **directness** — a diner who already wants Acme's food gets from craving to a paid-ready cart in one conversation, with the restaurant's own prices and the restaurant's own checkout. The single-restaurant scope is a feature: no ranking to game, no cross-restaurant carts, no ambiguity about whose menu the model is grounding on.
51
+
52
+ ---
53
+
54
+ ## 3. Target User Personas
55
+
56
+ ### Persona A — "The Regular"
57
+ Orders from Acme every week and knows the menu. Wants the shortest possible path: *"the usual — two margheritas and a sparkling water."* Values speed and an accurate cart over discovery.
58
+
59
+ ### Persona B — "The Craver"
60
+ Arrives with an appetite, not a specific dish: *"something light from Acme"* or *"what mains do you have?"* Needs the menu surfaced fast and an opinionated nudge toward the roast bowl or the salad.
61
+
62
+ ### Persona C — "The Careful Orderer"
63
+ Has a dietary constraint and asks before adding: *"is the garden salad vegetarian?"* Needs honest, non-guessing answers grounded only in what the menu data actually states — and a clear defer-to-restaurant when it doesn't.
64
+
65
+ ### Persona D — "The Group Coordinator"
66
+ Ordering for two or three people with a running budget: *"add a margherita, a roast bowl, a salad, and a lemon tart — what's the total?"* Needs a live, legible cart total and easy quantity edits before committing to pay.
67
+
68
+ ---
69
+
70
+ ## 4. Conversational User Flow
71
+
72
+ ### 4.1 Entry Points
73
+
74
+ Natural phrases that should trigger the app:
75
+
76
+ ```
77
+ "Show me the Acme Bistro menu"
78
+ "Order two margheritas and a lemon tart from Acme"
79
+ "I want something light from Acme Bistro"
80
+ "What mains does Acme have?"
81
+ "Add a sparkling water to my Acme order"
82
+ "What's my Acme total?"
83
+ "Check out and pay for my Acme order"
84
+ ```
85
+
86
+ ### 4.2 Flow Architecture
87
+
88
+ ```
89
+ ┌──────────────────────────────────────────────┐
90
+ │ USER ENTERS CHAT │
91
+ │ (natural-language prompt) │
92
+ └───────────────────────┬────────────────────────┘
93
+
94
+
95
+ ┌─────────────────────────────┐
96
+ │ show_menu (widget) │
97
+ │ MenuCart renders: 5 items, │
98
+ │ steppers, live total, CTA │
99
+ └──────────────┬───────────────┘
100
+
101
+ ┌───────────────┼────────────────┐
102
+ ▼ ▼ ▼
103
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
104
+ │ add_to_cart │ │remove_from_ │ │ taps in │
105
+ │ (NL: "two │ │cart (NL or │ │ the widget │
106
+ │ margheritas")│ │ − button) │ │ (+ / −) │
107
+ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
108
+ └────────────────┼────────────────┘
109
+
110
+ ┌─────────────────────────────┐
111
+ │ Live cart + running total │
112
+ │ (summed in the widget) │
113
+ └──────────────┬───────────────┘
114
+ │ "check out & pay"
115
+
116
+ ┌─────────────────────────────┐
117
+ │ create_checkout │
118
+ │ → signed, expiring link to │
119
+ │ pay.acme.example │
120
+ └──────────────┬───────────────┘
121
+ ▼ (OFF-APP)
122
+ ┌─────────────────────────────┐
123
+ │ Acme PCI checkout · card │
124
+ │ entered here, never in chat │
125
+ └─────────────────────────────┘
126
+ ```
127
+
128
+ ### 4.3 Detailed Conversational Scenarios
129
+
130
+ #### Scenario 1 — Natural-language order (Persona A/D)
131
+
132
+ ```
133
+ USER: Order me two margheritas and a lemon tart from Acme Bistro.
134
+
135
+ APP: [tool: show_menu {customer:"Guest"}] → renders MenuCart
136
+ [tool: add_to_cart {item:"stone_pizza", quantity:2}]
137
+ [tool: add_to_cart {item:"lemon_tart", quantity:1}]
138
+
139
+ Here's your Acme Bistro order — two Stone-baked Margheritas
140
+ and a Lemon Tart. That's $36 so far. Want a drink or a side,
141
+ or shall I take you to checkout to pay?
142
+
143
+ [MenuCart widget]
144
+ ┌────────────────────────────────────────────┐
145
+ │ 🍽 Acme Bistro 2 in cart │
146
+ │ Stone-baked Margherita Mains $14 [−]2[+]│
147
+ │ Harvest Roast Bowl Mains $13 [−]0[+]│
148
+ │ House Garden Salad Starters$11 [−]0[+]│
149
+ │ Lemon Tart Desserts $8 [−]1[+]│
150
+ │ Sparkling Water Drinks $4 [−]0[+]│
151
+ │ ────────────────────────────────────────── │
152
+ │ Total $36 [ 💳 Check out & pay ] │
153
+ │ Payment happens on acme.example — your card │
154
+ │ is never entered in chat. │
155
+ └────────────────────────────────────────────┘
156
+ ```
157
+
158
+ #### Scenario 2 — Add and edit (Persona D)
159
+
160
+ ```
161
+ USER: Add a sparkling water too.
162
+
163
+ APP: [tool: add_to_cart {item:"sparkling", quantity:1}]
164
+ Added a Sparkling Water — your total is $40 for four items.
165
+
166
+ USER: Actually drop one margherita.
167
+
168
+ APP: [tool: remove_from_cart {item:"stone_pizza"}]
169
+ Done — one Margherita, one Lemon Tart, one Sparkling Water.
170
+ New total $26. Ready to pay whenever you are.
171
+ ```
172
+
173
+ #### Scenario 3 — Dietary question, honest answer (Persona C)
174
+
175
+ ```
176
+ USER: Is the garden salad vegetarian?
177
+
178
+ APP: The House Garden Salad is on our Starters menu at $11. I can
179
+ tell you what's on the menu, but I can't confirm ingredient-
180
+ level details like preparation or cross-contact — for
181
+ allergy or dietary certainty, please check with Acme Bistro
182
+ directly before ordering. Want me to add it anyway, or show
183
+ you the other starters and mains?
184
+ ```
185
+
186
+ #### Scenario 4 — Checkout handoff (all personas)
187
+
188
+ ```
189
+ USER: Check out and pay.
190
+
191
+ APP: [tool: create_checkout {customer:"Guest",
192
+ cartToken:"stone_pizzax2-lemon_tartx1-sparklingx1", total:40}]
193
+
194
+ Your order's ready — two Margheritas, a Lemon Tart, and a
195
+ Sparkling Water, $40 total. I've opened Acme's secure
196
+ checkout to take payment. Your card is entered on
197
+ acme.example, never here in chat.
198
+
199
+ [opens https://pay.acme.example/checkout?cart=
200
+ stone_pizzax2-lemon_tartx1-sparklingx1&total=40&src=chatgpt]
201
+ ```
202
+
203
+ ---
204
+
205
+ ## 5. UI Widget Specifications
206
+
207
+ > The widget is authored as a Noodle Seed React `view` (`toolWithWidget` → `MenuCart`), styled with **vanilla CSS cascade layers** so it inherits the host's light/dark theme and adapts to ChatGPT's surface. Compliance is verified with `noodle check --target chatgpt`.
208
+
209
+ ### 5.1 Design System Compliance
210
+
211
+ Acme authors **one** brand surface through the server `branding` tokens; everything else defers to host-provided semantic tokens (text, background, border, success/warning), so the widget looks native in ChatGPT.
212
+
213
+ | Category | Source | Value |
214
+ |----------|--------|-------|
215
+ | Text / background / border | Host semantic tokens (via cascade layers) | Host-provided, theme-aware |
216
+ | Brand accent | `branding.accent` | `#B91C1C` (Acme red) — **primary CTA + logo mark only** |
217
+ | Surface (light) | `branding.surface` | `#FEF3F2` |
218
+ | Surface (dark) | `branding.surfaceDark` | `#1A1211` |
219
+ | Radius / density | `branding.radius` / `branding.density` | `lg` / `comfortable` |
220
+
221
+ **Enforced rules:** system font stack; monochromatic outlined icons (the plate mark, the card glyph); WCAG AA contrast on all text/surface pairs (Acme red is used only as a fill behind light text or as a 1px mark, never as body text on white); no nested scroll (the 5-item menu fits without an inner scroller); brand accent restricted to the primary **Check out & pay** button and the header mark.
222
+
223
+ ### 5.2 Display Mode Strategy
224
+
225
+ | User intent | Display mode | Rationale |
226
+ |-------------|--------------|-----------|
227
+ | Browse the menu / build an order | **Inline Card** (`MenuCart`) | Five items + steppers + total fit an inline card; no drill-in, no pagination |
228
+ | Confirm total & pay | **Inline Card** (same widget, primary CTA) | The CTA opens the off-app checkout; no in-chat payment surface |
229
+ | Payment | **None (off-app browser)** | Deliberately not a widget — card capture stays on Acme's PCI page |
230
+
231
+ Modes deliberately **not** used: no Carousel (a single flat menu doesn't need one), no Fullscreen (five items don't warrant it), no Picture-in-Picture (there is no live-tracking phase in v1 — fulfilment happens after payment on Acme's side).
232
+
233
+ ### 5.3 Widget Specifications
234
+
235
+ #### ★ `MenuCart` — Inline Card
236
+ **Purpose:** the entire in-chat experience — menu, order building, live total, and the checkout hand-off — in one widget.
237
+
238
+ | Spec | Value |
239
+ |------|-------|
240
+ | Header | Plate mark, "Acme Bistro" title, status subtitle, cart chip (`N in cart` / `Fullscreen`) |
241
+ | Menu rows | One per item: name, course, price, and a `[− qty +]` stepper |
242
+ | Total | Live subtotal summed in React from the session-local cart |
243
+ | Primary action | **Check out & pay** (brand red) — disabled while the cart is empty or checkout is pending; opens the signed link via `openExternal` |
244
+ | Reassurance | Fine-print note: "Payment happens on acme.example — your card is never entered in chat." |
245
+ | Edge states | Empty cart (CTA disabled), pending checkout ("Opening checkout…"), dark theme variant |
246
+
247
+ **Two-users note:** every row is model-fillable — the model reflects *"two margheritas"* into `add_to_cart {item:"stone_pizza", quantity:2}`, and the same widget a human taps updates identically.
248
+
249
+ ---
250
+
251
+ ## 6. Tool Definitions (App Backend)
252
+
253
+ ### ★ Tool 1: `show_menu` — `toolWithWidget`
254
+ **Input:** `{ customer?: string = "Guest" }`
255
+ **Output:** `{ status, customer, items[] }` where each item is `{ id, name, price, kind }`.
256
+ **Renders:** the `MenuCart` widget.
257
+ **Annotations:** read-only.
258
+ **Triggers:** any menu / ordering intent ("show me Acme's menu", "order from Acme").
259
+
260
+ ### Tool 2: `add_to_cart` — `toolForWidget`
261
+ **Input:** `{ customer?, item: <menu id> = "stone_pizza", quantity?: int ≥1 = 1, notes?: string }`
262
+ **Output:** `{ status, item, quantity, notes }`.
263
+ **Annotations:** local write (non-destructive).
264
+ **Triggers:** natural-language additions ("add two margheritas", "and a lemon tart"). Widget-facing helper — reflects NL selections into the visible cart.
265
+
266
+ ### Tool 3: `remove_from_cart` — `toolForWidget`
267
+ **Input:** `{ customer?, item: <menu id> = "stone_pizza" }`
268
+ **Output:** `{ status, item }`.
269
+ **Annotations:** local write (non-destructive).
270
+ **Triggers:** "drop one margherita", "remove the salad", or the widget's `−` button.
271
+
272
+ ### ★ Tool 4: `create_checkout` — model-visible `tool`
273
+ **Input:** `{ customer?, cartToken: string = "cart", total: number ≥0 = 0 }`
274
+ **Output:** `{ status, summary, checkoutUrl }`.
275
+ **Annotations:** open-link (external action).
276
+ **Behaviour:** returns a signed deep link — `https://pay.acme.example/checkout?cart=<cartToken>&total=<total>&src=chatgpt`. The card never reaches this app. `handoff.allowedDomains` includes `pay.acme.example`, so the compiler derives the ChatGPT redirect domain and the link opens without a safe-link warning.
277
+ **Triggers:** "check out", "pay", "I'm done".
278
+
279
+ Tools are atomic and model-friendly: `show_menu` reads, the two cart tools write locally, `create_checkout` opens the one external link. There is no `submit_order` or `capture_payment` tool by design — order fulfilment and payment are Acme's, past the boundary.
280
+
281
+ ---
282
+
283
+ ## 7. Conversation Design Principles
284
+
285
+ ### 7.1 Tone of Voice
286
+ Warm, concise, and restaurant-first — like a counter host who knows the menu. State prices and totals as plain facts ("that's $36 so far"), recommend when it helps ("the roast bowl is the heartier main"), and never oversell.
287
+
288
+ ### 7.2 Guardrails (non-negotiable)
289
+ - **Never invent menu items or prices.** Ground every dish and amount in the `show_menu` data — only the five items, only their listed prices.
290
+ - **Never confirm allergen or dietary safety.** State what the menu says (course, name, price); for ingredient-level or cross-contact questions, defer to Acme Bistro directly. Never assert "this is vegetarian/gluten-free" without a menu flag that says so.
291
+ - **Never take payment in chat.** No card numbers, no CVV, no wallet. Payment is the one off-app step; if a user pastes card details, decline and point them to the checkout link.
292
+ - **Never promise fulfilment the app can't see.** The app builds and hands off the order; pickup timing and order status live on Acme's side after payment.
293
+ - **Always show the honest total before checkout**, and restate that payment happens on `acme.example`.
294
+
295
+ ### 7.3 Memory Strategy
296
+ Remember the diner's in-session cart and name. There is no cross-session account (no per-user auth) — a returning diner starts a fresh order, though the model may recall a prior order *within the same conversation* to speed a reorder.
297
+
298
+ ### 7.4 Multi-Turn Intelligence
299
+ The model infers item + quantity from language ("a couple of margheritas" → `quantity:2`), keeps a running total in view, and asks only when genuinely ambiguous ("did you mean the margherita or the roast bowl?"). It never asks for a field it can default.
300
+
301
+ ---
302
+
303
+ ## 8. End-to-End User Journey Map
304
+
305
+ **Phase 1 — Menu (first 5–10s):** user names Acme or asks for the menu → `show_menu` renders `MenuCart`.
306
+ **Phase 2 — Build (10–40s):** natural-language adds/removes (`add_to_cart` / `remove_from_cart`) and/or widget steppers; the total updates live.
307
+ **Phase 3 — Confirm (5–10s):** the app restates the cart and total in plain language; user says "pay".
308
+ **Phase 4 — Hand off (2–5s):** `create_checkout` mints the signed link; ChatGPT opens it.
309
+ **Phase 5 — Pay (off-app):** the diner enters their card on `pay.acme.example`; the app's job is done. Fulfilment is Acme's.
310
+
311
+ ---
312
+
313
+ ## 9. Handoff Architecture (Deep Dive)
314
+
315
+ **What must be true of the handoff:**
316
+ 1. **Context survives the jump.** The cart token encodes every line (`stone_pizzax2-lemon_tartx1-sparklingx1`) plus the total, so Acme's checkout rehydrates the exact order without a second round-trip.
317
+ 2. **The link is signed and attributable.** Acme signs the checkout URL server-side and carries `src=chatgpt` for attribution. `handoff.allowedDomains: ['https://pay.acme.example', 'https://acme.example']` lets the compiler emit the redirect domain so the link opens cleanly.
318
+ 3. **State is re-validated past the boundary.** Acme recomputes pricing, checks inventory, and enforces the total at checkout — the widget's sum is display-only and never authoritative.
319
+ 4. **The link expires.** Checkout URLs carry an `expires_at`; a stale link lands on a "cart expired — start again" page rather than charging an out-of-date total.
320
+
321
+ **URL pattern:** `https://pay.acme.example/checkout?cart={cartToken}&total={total}&src=chatgpt`
322
+
323
+ **Why payment is the only handoff:** keeping card capture on Acme's PCI-scoped checkout means the MCP server never enters payment scope — no card data, no stored methods, no compliance burden added by the ChatGPT surface. The app is the storefront; Acme is the register.
324
+
325
+ **Open questions for Acme engineering:**
326
+ - Signing scheme + default expiry window (proposed: 15 minutes)?
327
+ - Should the cart token be opaque (server-minted) instead of the human-readable `idxN-idxN` form, to prevent client-side total tampering before re-validation?
328
+ - Post-payment visibility (webhook / polling) so a later chat turn can confirm "your order is ready" — out of scope for v1?
329
+
330
+ ---
331
+
332
+ ## 10. Demo Scope Recommendation
333
+
334
+ **MVP (build in this order):**
335
+ 1. `show_menu` + `MenuCart` — menu renders, steppers work, total sums live.
336
+ 2. `add_to_cart` / `remove_from_cart` — natural-language and button edits both reflect in the cart.
337
+ 3. `create_checkout` — signed link opens Acme's checkout with the cart pre-loaded.
338
+
339
+ **2-minute demo script:**
340
+ ```
341
+ NARRATOR: "A diner wants dinner from their neighbourhood spot,
342
+ Acme Bistro, without leaving ChatGPT."
343
+
344
+ USER: "Order two margheritas and a lemon tart from Acme Bistro."
345
+ [show_menu renders MenuCart; add_to_cart ×2 fills the cart — $36]
346
+
347
+ USER: "Add a sparkling water."
348
+ [add_to_cart — total ticks to $40]
349
+
350
+ USER: "What's my total?"
351
+ [MenuCart shows Total $40, four items]
352
+
353
+ USER: "Check out and pay."
354
+ [create_checkout mints the signed link; ChatGPT opens
355
+ pay.acme.example — card entered there, never in chat]
356
+
357
+ NARRATOR: "Built and confirmed in one conversation; paid on Acme's
358
+ own secure checkout. The app never saw a card number."
359
+ ```
360
+
361
+ ---
362
+
363
+ ## 11. Technical Architecture (High Level)
364
+
365
+ ```
366
+ ┌───────────────────────────────────────────────┐
367
+ │ ChatGPT Client │
368
+ │ MenuCart widget (Noodle Seed React view) │
369
+ │ cascade-layer CSS · host theme tokens │
370
+ └───────────────────────┬─────────────────────────┘
371
+ │ tool calls
372
+
373
+ ┌───────────────────────────────────────────────┐
374
+ │ Acme Bistro MCP server (Noodle Seed) │
375
+ │ show_menu · add_to_cart · remove_from_cart │
376
+ │ create_checkout │
377
+ │ branding tokens · handoff.allowedDomains │
378
+ │ (static menu data; no per-user auth) │
379
+ └───────────────────────┬─────────────────────────┘
380
+ │ signed checkout link (no card data)
381
+
382
+ ┌───────────────────────────────────────────────┐
383
+ │ Acme Bistro checkout — pay.acme.example │
384
+ │ PCI-scoped card capture · pricing/inventory │
385
+ │ validation · order fulfilment │
386
+ └───────────────────────────────────────────────┘
387
+ ```
388
+
389
+ Menu data is static in v1 (authored in `server.ts`). Session cart state lives in the widget (React). No database, no per-user credentials, no card data in the MCP server — the smallest possible surface for a single-restaurant storefront.
390
+
391
+ ---
392
+
393
+ ## 12. Success Metrics
394
+
395
+ | Metric | Target | Measurement |
396
+ |--------|--------|-------------|
397
+ | Menu render → first add | 55%+ of `show_menu` sessions add ≥1 item | `add_to_cart` call rate |
398
+ | Cart with ≥1 item → `create_checkout` | 60%+ | Tool-call funnel |
399
+ | Checkout link opened → paid (on Acme) | Acme-side; joined via `src=chatgpt` | Acme checkout analytics |
400
+ | End-to-end (entry → paid order) | 15%+ | Funnel + Acme attribution |
401
+ | Average order value | $30+ | Cart totals at `create_checkout` |
402
+ | Time to checkout hand-off | Under 60s | Session duration |
403
+
404
+ **Attribution mechanics:** every `create_checkout` link carries `src=chatgpt`, so Acme can attribute paid revenue to the ChatGPT storefront and compare it against their existing web channel.
405
+
406
+ ---
407
+
408
+ ## 13. Future Enhancements (Post-Launch)
409
+
410
+ - **Item notes at scale** — surface the `notes` field in the widget for per-item requests ("no basil").
411
+ - **Modifier groups** — sizes / add-ons if the menu grows beyond flat items (would introduce an item-detail widget).
412
+ - **Live inventory** — mark sold-out items unavailable from Acme's kitchen system.
413
+ - **Post-payment confirmation** — an Acme webhook so a later chat turn can confirm "your order is ready for pickup."
414
+ - **Returning-diner reorder** — opt-in, if Acme later adds per-user accounts (would move this app off the no-auth model deliberately).
415
+ - **Scheduled pickup** — choose a pickup window before the checkout hand-off.
416
+ - **Second location** — a lightweight location picker if Acme opens another kitchen (keeps single-menu simplicity per location).
417
+
418
+ ---
419
+
420
+ ### Appendix A — Funnel Boundary Cheat-Sheet
421
+
422
+ | User request | Handled in the app? | Where it lands |
423
+ |--------------|---------------------|----------------|
424
+ | "Show me the menu" | ✅ In-chat | `show_menu` → `MenuCart` |
425
+ | "Add two margheritas" | ✅ In-chat | `add_to_cart` |
426
+ | "Drop the salad" | ✅ In-chat | `remove_from_cart` |
427
+ | "What's my total?" | ✅ In-chat | Live widget total |
428
+ | "Check out / pay" | ✅ In-chat → hand-off | `create_checkout` mints signed link |
429
+ | Enter card & pay | ❌ OFF-APP | `pay.acme.example` (Acme PCI checkout) |
430
+ | Pickup timing / order status | ❌ OFF-APP | Acme's side, post-payment |
431
+ | Account / saved cards | ❌ Not in v1 | No per-user auth by design |
432
+
433
+ ---
434
+
435
+ *This document is the master spec for the Acme Bistro ChatGPT App. Acme Bistro, its menu, and its domains are illustrative. All tool names, widget names, menu items, and prices match the runnable Noodle Seed app exactly; the checkout link shape and pricing/inventory validation are owned by Acme's backend past the funnel boundary.*
@@ -0,0 +1,161 @@
1
+ # Acme Bistro — Recommended API Shapes
2
+
3
+ **For Acme Bistro Engineering.** Concrete request/response JSON for each tool the ChatGPT App calls, so your backend can implement exactly what the app needs. These are a starting point for the contract, not a final spec — field names and envelopes can shift to match Acme's platform conventions, as long as the semantics below are preserved.
4
+
5
+ > **The backend owns pricing, inventory, and payment.** The widget sums line items for *display only*; the amount that reaches checkout is recomputed and enforced by Acme at `pay.acme.example`. The MCP server never sees a card number, a CVV, or a stored payment method — payment is the single off-app step. Keep pricing, availability, and the signed checkout link server-side.
6
+
7
+ The app maps to four tools:
8
+
9
+ | Tool | Kind | Job |
10
+ |------|------|-----|
11
+ | `show_menu` | `toolWithWidget` (read-only) | Return the menu + render the `MenuCart` widget |
12
+ | `add_to_cart` | `toolForWidget` (local write) | Reflect a natural-language addition into the visible cart |
13
+ | `remove_from_cart` | `toolForWidget` (local write) | Remove one unit of an item |
14
+ | `create_checkout` | model-visible `tool` (open-link) | Mint the signed, expiring payment link |
15
+
16
+ Menu item IDs are the stable enum: `stone_pizza`, `roast_bowl`, `house_salad`, `lemon_tart`, `sparkling`.
17
+
18
+ ---
19
+
20
+ ## 1. `show_menu` — menu + widget
21
+
22
+ The one read. Returns the full menu (small enough to render without pagination) plus a status line the model can speak.
23
+
24
+ **Request**
25
+ ```json
26
+ {
27
+ "customer": "Guest"
28
+ }
29
+ ```
30
+
31
+ **Response**
32
+ ```json
33
+ {
34
+ "status": "Acme Bistro menu is ready for Guest. Build the order here; pay at checkout.",
35
+ "customer": "Guest",
36
+ "items": [
37
+ { "id": "stone_pizza", "name": "Stone-baked Margherita", "price": 14, "kind": "Mains" },
38
+ { "id": "roast_bowl", "name": "Harvest Roast Bowl", "price": 13, "kind": "Mains" },
39
+ { "id": "house_salad", "name": "House Garden Salad", "price": 11, "kind": "Starters" },
40
+ { "id": "lemon_tart", "name": "Lemon Tart", "price": 8, "kind": "Desserts" },
41
+ { "id": "sparkling", "name": "Sparkling Water", "price": 4, "kind": "Drinks" }
42
+ ]
43
+ }
44
+ ```
45
+
46
+ **Notes.**
47
+ - `price` is a whole-dollar USD number in v1. If Acme moves to cents or a currency field, keep one canonical numeric price per item so the widget's sum and the checkout total agree.
48
+ - `items[]` is the authoritative menu — the model must not invent dishes or prices outside this list.
49
+ - **Extensibility:** Acme may add fields (`description`, `available`, `dietary_tags`, `image_url`) without breaking the app, as long as `id`, `name`, `price`, and `kind` remain. If `available: false` is added, the widget should disable that row's `+` button.
50
+
51
+ ---
52
+
53
+ ## 2. `add_to_cart` — reflect a natural-language addition
54
+
55
+ Called when the diner says *"add two margheritas"* — the model fills `item` and `quantity` from language. The cart is session-local in the widget; this tool echoes the resolved selection so the model can speak it back.
56
+
57
+ **Request**
58
+ ```json
59
+ {
60
+ "customer": "Guest",
61
+ "item": "stone_pizza",
62
+ "quantity": 2,
63
+ "notes": ""
64
+ }
65
+ ```
66
+
67
+ **Response**
68
+ ```json
69
+ {
70
+ "status": "Added 2 × stone_pizza for Guest.",
71
+ "item": "stone_pizza",
72
+ "quantity": 2,
73
+ "notes": ""
74
+ }
75
+ ```
76
+
77
+ **Notes.**
78
+ - `quantity` is an integer ≥ 1 (defaults to 1). `item` must be one of the five menu IDs; reject unknown IDs.
79
+ - `notes` is a free-text per-item request ("no basil"); optional, defaults to empty.
80
+ - **If Acme makes this server-authoritative** (rather than widget-local), return the updated line and a running subtotal so the frontend can render without a second call — e.g. add `line_total` and `cart_subtotal`. For v1 the widget owns the running total, so the minimal echo above is sufficient.
81
+
82
+ ---
83
+
84
+ ## 3. `remove_from_cart` — remove one unit
85
+
86
+ Called by the widget's `−` button or by language ("drop a margherita"). Removes one unit of the item.
87
+
88
+ **Request**
89
+ ```json
90
+ {
91
+ "customer": "Guest",
92
+ "item": "stone_pizza"
93
+ }
94
+ ```
95
+
96
+ **Response**
97
+ ```json
98
+ {
99
+ "status": "Removed stone_pizza for Guest.",
100
+ "item": "stone_pizza"
101
+ }
102
+ ```
103
+
104
+ **Notes.**
105
+ - Removing decrements by one; the widget deletes the line when its quantity reaches zero.
106
+ - No error if the item isn't in the cart — the operation is idempotent from the model's view (the widget guards the `−` button when quantity is 0).
107
+
108
+ ---
109
+
110
+ ## 4. `create_checkout` — mint the signed payment link
111
+
112
+ The one handoff. The widget computes the total (live React) and passes a url-safe cart token plus the numeric total; the tool returns a **signed, expiring** deep link to Acme's PCI-scoped checkout. **No card data is exchanged here** — the diner enters their card on `pay.acme.example`.
113
+
114
+ **Request**
115
+ ```json
116
+ {
117
+ "customer": "Guest",
118
+ "cartToken": "stone_pizzax2-lemon_tartx1-sparklingx1",
119
+ "total": 40
120
+ }
121
+ ```
122
+
123
+ **Response**
124
+ ```json
125
+ {
126
+ "status": "Ready to pay for Guest's order.",
127
+ "summary": "Guest's Acme Bistro order · 40 USD",
128
+ "checkoutUrl": "https://pay.acme.example/checkout?cart=stone_pizzax2-lemon_tartx1-sparklingx1&total=40&src=chatgpt",
129
+ "expires_at": "2026-07-09T18:15:00-07:00"
130
+ }
131
+ ```
132
+
133
+ **Notes.**
134
+ - **`checkoutUrl` must be signed server-side.** The `cart` and `total` query params are a convenience for rehydration and display — Acme's checkout must **recompute pricing from the cart token and enforce its own total**, never trusting the client-supplied `total`. Treat the incoming `total` as a display hint to reconcile, not as the charge amount.
135
+ - **`expires_at`** bounds the link (proposed 15-minute window). An expired link should land on a "cart expired — start again" page, not charge a stale total. The runnable app returns `status`, `summary`, and `checkoutUrl`; adding `expires_at` is the recommended production extension so the model can tell the diner how long the link is good for.
136
+ - **`src=chatgpt`** is the attribution parameter — carry it through to Acme's order record so ChatGPT-sourced revenue is measurable against the web channel.
137
+ - **Cart token format.** The app emits a human-readable `<id>x<qty>-<id>x<qty>` token. For production, consider an **opaque server-minted token** (the client passes a cart handle; Acme resolves it to the authoritative lines) to remove any incentive to tamper with the token or `total` before re-validation.
138
+ - **`handoff.allowedDomains`** in the server (`https://pay.acme.example`, `https://acme.example`) is what lets the compiler derive the ChatGPT redirect domain, so the link opens without a safe-link warning. Any new payment domain must be added there.
139
+
140
+ ---
141
+
142
+ ## Validation & ownership summary
143
+
144
+ The backend must own and enforce:
145
+
146
+ 1. **Pricing** — the authoritative per-item price and the order total; the widget sum is display-only.
147
+ 2. **Inventory** — item availability at menu-read and at checkout; a sold-out item should not reach a paid order.
148
+ 3. **Payment** — all card capture on `pay.acme.example`, inside Acme's PCI scope. The MCP server is never in the payment path.
149
+ 4. **Link integrity** — server-side signing of `checkoutUrl`, an enforced `expires_at`, and recomputation of the total from the cart token before charging.
150
+
151
+ Everything before payment — menu, cart, total, and minting the link — is the ChatGPT App's job. Everything at and after payment is Acme's.
152
+
153
+ ---
154
+
155
+ ## Open questions for Acme engineering
156
+
157
+ 1. **Cart token shape** — keep the readable `idxN-idxN` form, or move to an opaque server handle to prevent client-side tampering?
158
+ 2. **Signing scheme & expiry** — HMAC, JWT, or signed query params, and what default expiry window (proposed 15 min)?
159
+ 3. **Currency & precision** — stay whole-dollar USD, or introduce cents / a `currency` field? The widget and checkout total must agree.
160
+ 4. **Server-authoritative cart** — should `add_to_cart` / `remove_from_cart` become backend-owned (returning subtotals), or stay widget-local for v1?
161
+ 5. **Post-payment visibility** — expose a webhook or polling endpoint so a later chat turn can confirm order status? Out of scope for v1.