@noodleseed/agent-kit 0.13.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.
Files changed (121) hide show
  1. package/README.md +3 -3
  2. package/manifest.json +575 -5
  3. package/package.json +1 -1
  4. package/skills/claude-code/SKILL.md +7 -2
  5. package/skills/claude-code/examples/acme-bistro/README.md +51 -0
  6. package/skills/claude-code/examples/acme-bistro/design/UX-Document.md +435 -0
  7. package/skills/claude-code/examples/acme-bistro/design/api-contract.md +161 -0
  8. package/skills/claude-code/examples/acme-bistro/design/wireframe.html +573 -0
  9. package/skills/claude-code/examples/acme-bistro/noodle.json +5 -0
  10. package/skills/claude-code/examples/acme-bistro/package.json +20 -0
  11. package/skills/claude-code/examples/acme-bistro/src/helpers.ts +7 -0
  12. package/skills/claude-code/examples/acme-bistro/src/server.ts +149 -0
  13. package/skills/claude-code/examples/acme-bistro/src/views/menu-cart.tsx +159 -0
  14. package/skills/claude-code/examples/acme-bistro/src/views/widget-style.css +228 -0
  15. package/skills/claude-code/examples/acme-bistro/test/server.test.ts +21 -0
  16. package/skills/claude-code/examples/acme-bistro/vitest.config.ts +7 -0
  17. package/skills/claude-code/examples/acme-discovery/README.md +85 -0
  18. package/skills/claude-code/examples/acme-discovery/design/UX-Document.md +441 -0
  19. package/skills/claude-code/examples/acme-discovery/design/wireframe.html +670 -0
  20. package/skills/claude-code/examples/acme-discovery/noodle.json +5 -0
  21. package/skills/claude-code/examples/acme-discovery/package.json +20 -0
  22. package/skills/claude-code/examples/acme-discovery/src/helpers.ts +7 -0
  23. package/skills/claude-code/examples/acme-discovery/src/server.ts +202 -0
  24. package/skills/claude-code/examples/acme-discovery/src/views/discovery-carousel.tsx +155 -0
  25. package/skills/claude-code/examples/acme-discovery/src/views/widget-style.css +207 -0
  26. package/skills/claude-code/examples/acme-discovery/test/server.test.ts +24 -0
  27. package/skills/claude-code/examples/acme-discovery/vitest.config.ts +7 -0
  28. package/skills/claude-code/examples/acme-tasks/README.md +80 -0
  29. package/skills/claude-code/examples/acme-tasks/design/UX-Document.md +277 -0
  30. package/skills/claude-code/examples/acme-tasks/design/wireframe.html +603 -0
  31. package/skills/claude-code/examples/acme-tasks/noodle.json +5 -0
  32. package/skills/claude-code/examples/acme-tasks/package.json +20 -0
  33. package/skills/claude-code/examples/acme-tasks/src/helpers.ts +6 -0
  34. package/skills/claude-code/examples/acme-tasks/src/server.ts +141 -0
  35. package/skills/claude-code/examples/acme-tasks/src/views/task-list.tsx +177 -0
  36. package/skills/claude-code/examples/acme-tasks/src/views/widget-style.css +227 -0
  37. package/skills/claude-code/examples/acme-tasks/test/server.test.ts +22 -0
  38. package/skills/claude-code/examples/acme-tasks/vitest.config.ts +7 -0
  39. package/skills/claude-code/examples/food-ordering/README.md +82 -0
  40. package/skills/claude-code/examples/food-ordering/noodle.json +5 -0
  41. package/skills/claude-code/examples/food-ordering/package.json +22 -0
  42. package/skills/claude-code/examples/food-ordering/src/helpers.ts +34 -0
  43. package/skills/claude-code/examples/food-ordering/src/server.ts +412 -0
  44. package/skills/claude-code/examples/food-ordering/src/views/capabilities-card.tsx +155 -0
  45. package/skills/claude-code/examples/food-ordering/src/views/ordering-flow.tsx +659 -0
  46. package/skills/claude-code/examples/food-ordering/src/views/widget-style.css +682 -0
  47. package/skills/claude-code/examples/food-ordering/test/server.test.ts +58 -0
  48. package/skills/claude-code/examples/food-ordering/vitest.config.ts +30 -0
  49. package/skills/claude-code/examples/hello/README.md +13 -0
  50. package/skills/claude-code/examples/hello/noodle.json +5 -0
  51. package/skills/claude-code/examples/hello/package.json +16 -0
  52. package/skills/claude-code/examples/hello/src/server.ts +29 -0
  53. package/skills/claude-code/examples/hello/test/server.test.ts +8 -0
  54. package/skills/claude-code/examples/weather/README.md +54 -0
  55. package/skills/claude-code/examples/weather/noodle.json +4 -0
  56. package/skills/claude-code/examples/weather/package.json +16 -0
  57. package/skills/claude-code/examples/weather/src/server.ts +190 -0
  58. package/skills/claude-code/examples/weather/test/server.test.ts +8 -0
  59. package/skills/claude-code/references/chatgpt-compliance.md +63 -0
  60. package/skills/claude-code/references/connect-an-api.md +76 -0
  61. package/skills/claude-code/references/examples.md +16 -4
  62. package/skills/claude-code/references/experience-design.md +142 -0
  63. package/skills/codex/SKILL.md +7 -2
  64. package/skills/codex/examples/acme-bistro/README.md +51 -0
  65. package/skills/codex/examples/acme-bistro/design/UX-Document.md +435 -0
  66. package/skills/codex/examples/acme-bistro/design/api-contract.md +161 -0
  67. package/skills/codex/examples/acme-bistro/design/wireframe.html +573 -0
  68. package/skills/codex/examples/acme-bistro/noodle.json +5 -0
  69. package/skills/codex/examples/acme-bistro/package.json +20 -0
  70. package/skills/codex/examples/acme-bistro/src/helpers.ts +7 -0
  71. package/skills/codex/examples/acme-bistro/src/server.ts +149 -0
  72. package/skills/codex/examples/acme-bistro/src/views/menu-cart.tsx +159 -0
  73. package/skills/codex/examples/acme-bistro/src/views/widget-style.css +228 -0
  74. package/skills/codex/examples/acme-bistro/test/server.test.ts +21 -0
  75. package/skills/codex/examples/acme-bistro/vitest.config.ts +7 -0
  76. package/skills/codex/examples/acme-discovery/README.md +85 -0
  77. package/skills/codex/examples/acme-discovery/design/UX-Document.md +441 -0
  78. package/skills/codex/examples/acme-discovery/design/wireframe.html +670 -0
  79. package/skills/codex/examples/acme-discovery/noodle.json +5 -0
  80. package/skills/codex/examples/acme-discovery/package.json +20 -0
  81. package/skills/codex/examples/acme-discovery/src/helpers.ts +7 -0
  82. package/skills/codex/examples/acme-discovery/src/server.ts +202 -0
  83. package/skills/codex/examples/acme-discovery/src/views/discovery-carousel.tsx +155 -0
  84. package/skills/codex/examples/acme-discovery/src/views/widget-style.css +207 -0
  85. package/skills/codex/examples/acme-discovery/test/server.test.ts +24 -0
  86. package/skills/codex/examples/acme-discovery/vitest.config.ts +7 -0
  87. package/skills/codex/examples/acme-tasks/README.md +80 -0
  88. package/skills/codex/examples/acme-tasks/design/UX-Document.md +277 -0
  89. package/skills/codex/examples/acme-tasks/design/wireframe.html +603 -0
  90. package/skills/codex/examples/acme-tasks/noodle.json +5 -0
  91. package/skills/codex/examples/acme-tasks/package.json +20 -0
  92. package/skills/codex/examples/acme-tasks/src/helpers.ts +6 -0
  93. package/skills/codex/examples/acme-tasks/src/server.ts +141 -0
  94. package/skills/codex/examples/acme-tasks/src/views/task-list.tsx +177 -0
  95. package/skills/codex/examples/acme-tasks/src/views/widget-style.css +227 -0
  96. package/skills/codex/examples/acme-tasks/test/server.test.ts +22 -0
  97. package/skills/codex/examples/acme-tasks/vitest.config.ts +7 -0
  98. package/skills/codex/examples/food-ordering/README.md +82 -0
  99. package/skills/codex/examples/food-ordering/noodle.json +5 -0
  100. package/skills/codex/examples/food-ordering/package.json +22 -0
  101. package/skills/codex/examples/food-ordering/src/helpers.ts +34 -0
  102. package/skills/codex/examples/food-ordering/src/server.ts +412 -0
  103. package/skills/codex/examples/food-ordering/src/views/capabilities-card.tsx +155 -0
  104. package/skills/codex/examples/food-ordering/src/views/ordering-flow.tsx +659 -0
  105. package/skills/codex/examples/food-ordering/src/views/widget-style.css +682 -0
  106. package/skills/codex/examples/food-ordering/test/server.test.ts +58 -0
  107. package/skills/codex/examples/food-ordering/vitest.config.ts +30 -0
  108. package/skills/codex/examples/hello/README.md +13 -0
  109. package/skills/codex/examples/hello/noodle.json +5 -0
  110. package/skills/codex/examples/hello/package.json +16 -0
  111. package/skills/codex/examples/hello/src/server.ts +29 -0
  112. package/skills/codex/examples/hello/test/server.test.ts +8 -0
  113. package/skills/codex/examples/weather/README.md +54 -0
  114. package/skills/codex/examples/weather/noodle.json +4 -0
  115. package/skills/codex/examples/weather/package.json +16 -0
  116. package/skills/codex/examples/weather/src/server.ts +190 -0
  117. package/skills/codex/examples/weather/test/server.test.ts +8 -0
  118. package/skills/codex/references/chatgpt-compliance.md +63 -0
  119. package/skills/codex/references/connect-an-api.md +76 -0
  120. package/skills/codex/references/examples.md +16 -4
  121. package/skills/codex/references/experience-design.md +142 -0
@@ -0,0 +1,30 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ resolve: {
5
+ alias: {
6
+ '@noodleseed/one': new URL('../../packages/authoring/src/index.ts', import.meta.url).pathname,
7
+ '@noodle-borg/capabilities': new URL(
8
+ '../../packages/capabilities/src/index.ts',
9
+ import.meta.url,
10
+ ).pathname,
11
+ '@noodle-borg/compiler': new URL('../../packages/compiler/src/index.ts', import.meta.url)
12
+ .pathname,
13
+ '@noodle-borg/compute': new URL('../../packages/compute/src/index.ts', import.meta.url)
14
+ .pathname,
15
+ '@noodle-borg/connector-defs': new URL(
16
+ '../../packages/connector-defs/src/index.ts',
17
+ import.meta.url,
18
+ ).pathname,
19
+ '@noodle-borg/connector-http': new URL(
20
+ '../../packages/connector-http/src/index.ts',
21
+ import.meta.url,
22
+ ).pathname,
23
+ '@noodle-borg/runtime': new URL('../../packages/runtime/src/index.ts', import.meta.url)
24
+ .pathname,
25
+ },
26
+ },
27
+ test: {
28
+ include: ['test/**/*.test.ts'],
29
+ },
30
+ });
@@ -0,0 +1,13 @@
1
+ # hello — minimal TypeScript quickstart
2
+
3
+ The smallest deployable Noodle app: a single `greet` tool authored in TypeScript with no
4
+ connectors, secrets, flows, widgets, or handoff policy. It still uses the current server options form
5
+ so new authors see where server-level branding belongs. Use it to smoke the author loop
6
+ (`noodle validate` / `noodle dev`) or a first deploy.
7
+
8
+ ```sh
9
+ noodle dev examples/hello/src/server.ts --app hello
10
+ noodle deploy examples/hello/src/server.ts --org acme --app hello
11
+ ```
12
+
13
+ It is also the fixture for `pnpm smoke:dev` and the e2e harness, so keep its tool surface stable.
@@ -0,0 +1,5 @@
1
+ {
2
+ "entrypoint": "src/server.ts",
3
+ "name": "hello",
4
+ "template": "hello"
5
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "hello",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "test": "vitest run",
8
+ "validate": "noodle validate",
9
+ "dev": "noodle dev",
10
+ "deploy": "noodle deploy"
11
+ },
12
+ "devDependencies": {
13
+ "@noodleseed/one": "latest",
14
+ "vitest": "latest"
15
+ }
16
+ }
@@ -0,0 +1,29 @@
1
+ import { server, tool, z } from '@noodleseed/one';
2
+
3
+ export default server(
4
+ 'hello',
5
+ {
6
+ title: 'Hello',
7
+ version: '1.0.0',
8
+ branding: {
9
+ name: 'Hello',
10
+ accent: '#1D9E75',
11
+ radius: 'md',
12
+ density: 'comfortable',
13
+ },
14
+ },
15
+ [
16
+ tool('greet', {
17
+ description: 'Greet someone by name.',
18
+ input: z.object({
19
+ name: z.string(),
20
+ }),
21
+ output: z.object({
22
+ message: z.string(),
23
+ }),
24
+ fulfil: ({ input }) => {
25
+ return { message: `Hello, ${input.name}!` };
26
+ },
27
+ }),
28
+ ],
29
+ );
@@ -0,0 +1,8 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import app from '../src/server.js';
3
+
4
+ describe('hello example', () => {
5
+ it('exports a Noodle server definition', () => {
6
+ expect(typeof app.toManifest).toBe('function');
7
+ });
8
+ });
@@ -0,0 +1,54 @@
1
+ # Weather Briefing
2
+
3
+ A single declarative tool that shows the runtime's breadth working together, with **no auth and no API
4
+ keys**. The `weather_briefing` tool takes a city name and runs a **three-step flow**:
5
+
6
+ Capability slots: HTTP connector authoring, ordered fulfilment flows, query/response mapping, and sandboxed
7
+ compute.
8
+
9
+ 1. **`geo.search`** → geocode the city to coordinates (Open-Meteo Geocoding API)
10
+ 2. **`forecast.current`** → fetch current weather for those coordinates (Open-Meteo Forecast API)
11
+ 3. **`brief.summarize`** → derive a human-readable briefing in a **WASM/QuickJS compute sandbox**
12
+
13
+ It exercises, in one TypeScript-authored app:
14
+
15
+ - **Server-level branding** with semantic tokens carried through the runtime artifact for any generated
16
+ app surface.
17
+ - **Ordered flow execution** with outputs threaded between steps (`${steps.geo.latitude}` → next step).
18
+ - **Two HTTP connectors on two different hosts**, each with its own egress allowlist.
19
+ - **Query parameters** (`query: [...]`) and a constant query baked into the path (`?current_weather=true`).
20
+ - **Deep response mapping** with the `${...}` language (`${response.results[0].latitude}`,
21
+ `${response.current_weather.temperature}`).
22
+ - **Sandboxed compute** (no network/fs/env/clock) turning raw numbers into conditions + advice.
23
+ - **Typed input/output schemas** emitted as JSON Schema 2020-12.
24
+
25
+ ## Run it locally
26
+
27
+ From the repo root, with the workspace built (`pnpm build`):
28
+
29
+ ```bash
30
+ : # 1. boot the local loopback dev server
31
+ node packages/cli/dist/cli.js dev examples/weather/src/server.ts --app weather
32
+
33
+ : # 2. in another shell, call the printed local endpoint
34
+ URL=http://127.0.0.1:<port>/o/local/weather/dev/mcp
35
+ curl -s "$URL" \
36
+ -H 'content-type: application/json' \
37
+ -H 'accept: application/json, text/event-stream' \
38
+ -H 'mcp-protocol-version: 2025-11-25' \
39
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"weather_briefing","arguments":{"city":"Paris"}}}'
40
+ ```
41
+
42
+ Example result (live data, abbreviated):
43
+
44
+ ```json
45
+ {
46
+ "place": "Paris", "country": "France",
47
+ "temperature_c": 25.1, "windspeed_kmh": 8.3,
48
+ "conditions": "overcast",
49
+ "headline": "Paris, France: 25°C, overcast.",
50
+ "advice": "Comfortable conditions — no special prep needed."
51
+ }
52
+ ```
53
+
54
+ Try other cities (`Reykjavik`, `Singapore`, `Denver`) to see the conditions and advice change.
@@ -0,0 +1,4 @@
1
+ {
2
+ "entrypoint": "src/server.ts",
3
+ "name": "weather"
4
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "weather",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "test": "vitest run",
8
+ "validate": "noodle validate",
9
+ "dev": "noodle dev",
10
+ "deploy": "noodle deploy"
11
+ },
12
+ "devDependencies": {
13
+ "@noodleseed/one": "latest",
14
+ "vitest": "latest"
15
+ }
16
+ }
@@ -0,0 +1,190 @@
1
+ import { connector, server, tool, z } from '@noodleseed/one';
2
+
3
+ // The same Weather Briefing server, authored in TypeScript with the Noodle authoring SDK.
4
+ //
5
+ // The SDK owns the *manifest*: the tool, its Zod-typed input/output schemas, and the flow — which you
6
+ // write as ordinary code in `fulfil` and the SDK records symbolically into ordered steps.
7
+ //
8
+ // HTTP and compute connectors are authored here too, so the public developer entrypoint is one
9
+ // self-contained server.ts. The SDK still compiles this to internal manifest/catalog data for the runtime.
10
+
11
+ const geocoding = connector('open_meteo_geocoding')
12
+ .version('1.0.0')
13
+ .http({
14
+ baseUrl: 'https://geocoding-api.open-meteo.com',
15
+ allowedOrigins: ['https://geocoding-api.open-meteo.com'],
16
+ operations: {
17
+ search: {
18
+ type: 'read',
19
+ method: 'GET',
20
+ path: '/v1/search',
21
+ query: ['name'],
22
+ input: { name: { type: 'string', required: true } },
23
+ output: {
24
+ latitude: { type: 'number', required: true },
25
+ longitude: { type: 'number', required: true },
26
+ place: { type: 'string' },
27
+ country: { type: 'string' },
28
+ },
29
+ response: {
30
+ latitude: '${response.results[0].latitude}',
31
+ longitude: '${response.results[0].longitude}',
32
+ place: '${response.results[0].name}',
33
+ country: '${response.results[0].country}',
34
+ },
35
+ },
36
+ },
37
+ });
38
+
39
+ const forecast = connector('open_meteo_forecast')
40
+ .version('1.0.0')
41
+ .http({
42
+ baseUrl: 'https://api.open-meteo.com',
43
+ allowedOrigins: ['https://api.open-meteo.com'],
44
+ operations: {
45
+ current: {
46
+ type: 'read',
47
+ method: 'GET',
48
+ path: '/v1/forecast?current_weather=true',
49
+ query: ['latitude', 'longitude'],
50
+ input: {
51
+ latitude: { type: 'number', required: true },
52
+ longitude: { type: 'number', required: true },
53
+ },
54
+ output: {
55
+ temperature: { type: 'number' },
56
+ windspeed: { type: 'number' },
57
+ weathercode: { type: 'number' },
58
+ },
59
+ response: {
60
+ temperature: '${response.current_weather.temperature}',
61
+ windspeed: '${response.current_weather.windspeed}',
62
+ weathercode: '${response.current_weather.weathercode}',
63
+ },
64
+ },
65
+ },
66
+ });
67
+
68
+ const brief = connector('weather_brief')
69
+ .version('1.0.0')
70
+ .compute('summarize', {
71
+ type: 'read',
72
+ input: {
73
+ place: { type: 'string', required: true },
74
+ country: { type: 'string' },
75
+ temperature: { type: 'number', required: true },
76
+ windspeed: { type: 'number', required: true },
77
+ weathercode: { type: 'number', required: true },
78
+ },
79
+ output: {
80
+ conditions: { type: 'string', required: true },
81
+ headline: { type: 'string', required: true },
82
+ advice: { type: 'string', required: true },
83
+ },
84
+ // A real function — type-checked here, serialized to source and run in the sandbox. It must be
85
+ // self-contained: no imports, no closure over outer variables, synchronous.
86
+ run: (input) => {
87
+ const codes: Record<number, string> = {
88
+ 0: 'clear sky',
89
+ 1: 'mainly clear',
90
+ 2: 'partly cloudy',
91
+ 3: 'overcast',
92
+ 45: 'fog',
93
+ 48: 'depositing rime fog',
94
+ 51: 'light drizzle',
95
+ 53: 'moderate drizzle',
96
+ 55: 'dense drizzle',
97
+ 61: 'slight rain',
98
+ 63: 'moderate rain',
99
+ 65: 'heavy rain',
100
+ 71: 'slight snow',
101
+ 73: 'moderate snow',
102
+ 75: 'heavy snow',
103
+ 77: 'snow grains',
104
+ 80: 'slight rain showers',
105
+ 81: 'moderate rain showers',
106
+ 82: 'violent rain showers',
107
+ 85: 'slight snow showers',
108
+ 86: 'heavy snow showers',
109
+ 95: 'thunderstorm',
110
+ 96: 'thunderstorm with hail',
111
+ 99: 'thunderstorm with heavy hail',
112
+ };
113
+ const code = Number(input.weathercode);
114
+ const conditions = codes[code] || 'unknown conditions';
115
+ const temp = Math.round(Number(input.temperature));
116
+ const wind = Math.round(Number(input.windspeed));
117
+ const where = input.country ? `${input.place}, ${input.country}` : input.place;
118
+ const headline = `${where}: ${temp}°C, ${conditions}.`;
119
+ const tips: string[] = [];
120
+ if (temp <= 0) tips.push("bundle up, it's freezing");
121
+ else if (temp <= 10) tips.push('wear a warm coat');
122
+ else if (temp >= 28) tips.push("stay hydrated, it's hot");
123
+ if (code >= 95) tips.push('thunderstorms expected — seek shelter');
124
+ else if (code >= 71 && code <= 86 && code !== 80 && code !== 81 && code !== 82)
125
+ tips.push('snow — dress warm and tread carefully');
126
+ else if (code >= 51 && code <= 82) tips.push('bring an umbrella');
127
+ if (wind >= 30) tips.push('expect strong winds');
128
+ const advice = tips.length
129
+ ? `${tips.join('; ')}.`
130
+ : 'Comfortable conditions — no special prep needed.';
131
+ return { conditions, headline, advice };
132
+ },
133
+ });
134
+
135
+ export default server(
136
+ 'weather_briefing',
137
+ {
138
+ title: 'Weather Briefing',
139
+ version: '1.0.0',
140
+ use: { geo: geocoding, forecast, brief },
141
+ branding: {
142
+ name: 'Weather Briefing',
143
+ accent: '#0284C7',
144
+ radius: 'md',
145
+ density: 'comfortable',
146
+ },
147
+ },
148
+ [
149
+ tool('weather_briefing', {
150
+ description:
151
+ 'Look up a city, fetch its current weather, and return a human-readable briefing. Runs a ' +
152
+ 'three-step flow: geocode the city, fetch the forecast, then derive the briefing in a sandboxed compute step.',
153
+ input: z.object({
154
+ city: z.string(),
155
+ }),
156
+ output: z.object({
157
+ place: z.string(),
158
+ country: z.string(),
159
+ temperature_c: z.number(),
160
+ windspeed_kmh: z.number(),
161
+ conditions: z.string(),
162
+ headline: z.string(),
163
+ advice: z.string(),
164
+ }),
165
+ fulfil: ({ input, connectors }) => {
166
+ const located = connectors.geo.search({ name: input.city });
167
+ const weather = connectors.forecast.current({
168
+ latitude: located.latitude,
169
+ longitude: located.longitude,
170
+ });
171
+ const briefing = connectors.brief.summarize({
172
+ place: located.place,
173
+ country: located.country,
174
+ temperature: weather.temperature,
175
+ windspeed: weather.windspeed,
176
+ weathercode: weather.weathercode,
177
+ });
178
+ return {
179
+ place: located.place,
180
+ country: located.country,
181
+ temperature_c: weather.temperature,
182
+ windspeed_kmh: weather.windspeed,
183
+ conditions: briefing.conditions,
184
+ headline: briefing.headline,
185
+ advice: briefing.advice,
186
+ };
187
+ },
188
+ }),
189
+ ],
190
+ );
@@ -0,0 +1,8 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import app from '../src/server.js';
3
+
4
+ describe('weather example', () => {
5
+ it('exports a Noodle server definition', () => {
6
+ expect(typeof app.toManifest).toBe('function');
7
+ });
8
+ });
@@ -0,0 +1,63 @@
1
+ # ChatGPT App compliance (pre-submission)
2
+
3
+ `noodle check --target chatgpt` verifies the *metadata* prerequisites; app-store submission also faces a
4
+ human review against OpenAI’s Apps SDK UX principles. Run this checklist against the built app before
5
+ submitting, and render it as an audit table in the design wireframe (`design/wireframe.html` in the
6
+ `acme-*` examples) so partners and reviewers see it up front.
7
+
8
+ ## Contents
9
+
10
+ - Metadata gate vs review
11
+ - Pre-submission checklist
12
+ - UI guidelines
13
+ - Domain guardrails
14
+ - Privacy and data
15
+
16
+ ## Metadata gate vs review
17
+
18
+ `noodle check --target chatgpt --json` returning `ok:true` means the widget is *metadata-ready* (widget
19
+ `domain`, `openai/outputTemplate`, CSP, tool annotations, and `invoking`/`invoked` invocation copy are
20
+ present) — it does NOT prove host rendering, conversation UX, or submission acceptance. Validate real
21
+ rendering in ChatGPT Developer Mode / MCP Inspector, then run the checklist below.
22
+
23
+ ## Pre-submission checklist (what review looks for)
24
+
25
+ 1. **Conversational value** — at least one capability relies on ChatGPT’s strengths: natural-language
26
+ actions no tap-driven app can do (e.g. "two margheritas and a lemon tart" parses into a cart). Cite
27
+ concrete app behavior, not aspirations.
28
+ 2. **Beyond base ChatGPT** — new knowledge, actions, or presentation (grounded partner data, live
29
+ inventory, signed handoffs, real-world routing).
30
+ 3. **Atomic, model-friendly actions** — self-contained tools with explicit input/output schemas, and an
31
+ annotation on every tool (`annotations.readOnly()` / `.action()` / `.openAction()`).
32
+ 4. **Helpful UI only** — justify each widget (would plain text degrade UX?), and note what you
33
+ deliberately did NOT build a widget for (payment is off-app → no payment widget).
34
+ 5. **In-chat task completion** — the user finishes a meaningful task in chat. For a top-of-funnel app,
35
+ the task is the discovery/config loop completed in-chat with an intentional handoff.
36
+ 6. **Performance** — tool calls scoped per step; response-time targets stated.
37
+ 7. **Discoverability** — broad, natural trigger prompts listed; description keywords planned. Golden
38
+ prompt sets and metadata optimization are a launch workstream, not polish.
39
+ 8. **Platform fit** — multi-turn dialogue, conversation memory, and multimodality where genuinely useful.
40
+
41
+ ## UI guidelines
42
+
43
+ System fonts, monochrome outlined icons, WCAG AA contrast, at most two actions on inline cards, no nested
44
+ scroll, and the right display mode per intent (inline by default; fullscreen only where browsing needs
45
+ it; picture-in-picture only for live state). Brand only through `server` `branding` tokens — accent on
46
+ the primary CTA, logo, and badges, nothing else; the compiler derives the palette. Never inject raw
47
+ global CSS.
48
+
49
+ ## Domain guardrails
50
+
51
+ For regulated-adjacent apps, add app-specific trust behaviors and **show them in the rendered pixels**:
52
+ cite the source and its revision for consequential lookups; frame regulated content as "considerations,
53
+ not a ruling"; never invent compatibility, availability, or pricing; and always show the relevant
54
+ caution/disclaimer. These are what make a regulated-adjacent app approvable.
55
+
56
+ ## Privacy and data
57
+
58
+ Data flows through OpenAI; tool payloads and whatever the server stores must match the partner’s privacy
59
+ policy. No payment happens in chat (PCI stays off-app). Avoid per-user OAuth in a top-of-funnel v1 (use
60
+ service credentials via a `connector`); add end-user auth only for two-way apps (`customerAuth`). Keep
61
+ secrets out of tool output, widgets, and logs. If the partner’s published policy predates the app, flag a
62
+ privacy gap for their counsel before submission. Re-run this checklist against the *built* app before
63
+ every submission — not just the wireframe.
@@ -0,0 +1,76 @@
1
+ # Connect a live API (you were given a key)
2
+
3
+ When the user hands you an API key or credentials, don't infer the data from documentation — docs
4
+ drift. Probe the live API, learn the real shape, then encode it as a `connector`. The loop:
5
+
6
+ ## Contents
7
+
8
+ - Secure the key first
9
+ - Probe the live API
10
+ - Model the connector from the observed shape
11
+ - Design intent tools
12
+ - Prove real output
13
+ - Then build the app
14
+
15
+ ## Secure the key first
16
+
17
+ Never inline or log the key. Have the user put it in an environment variable, then store it as a
18
+ managed secret and reference it only as `secret(...)`:
19
+
20
+ ```sh
21
+ export SOME_API_KEY=… # the user sets this; it never appears in a file or prompt
22
+ noodle secrets set SOME_API_KEY --from-env SOME_API_KEY
23
+ ```
24
+
25
+ In `server.ts` the key is only ever `secret("SOME_API_KEY")` — keep the raw value out of code, tests,
26
+ prompts, logs, and generated files.
27
+
28
+ ## Probe the live API
29
+
30
+ Learn the actual response shape empirically. Two ways — capture one real example response per endpoint
31
+ you will use, and read its field names, nesting, array shapes, pagination, and id-vs-label fields:
32
+
33
+ - **With your own HTTP/shell tool** — call a representative read endpoint using the key **from the env
34
+ var**, never the literal (so it stays out of logs): `curl -H "Authorization: Bearer $SOME_API_KEY"
35
+ https://api.example.com/things`. Inspect the returned JSON.
36
+ - **Noodle-native** — author a minimal read operation that maps the whole body (`response: { raw:
37
+ '${response}' }`), `noodle secrets set` the key, then `noodle tools call` it to see the real payload
38
+ in-process.
39
+
40
+ ## Model the connector from the observed shape
41
+
42
+ Encode the API as an HTTP connector, mapping only the fields you actually saw into a small typed
43
+ `output`:
44
+
45
+ - `connector("id").version("1.0.0").http({ baseUrl, allowedOrigins, auth, operations })`.
46
+ - `auth: { kind: 'bearer', secret: secret('SOME_API_KEY') }` — or `{ kind: 'apiKey', header: 'X-API-Key',
47
+ secret: secret('SOME_API_KEY') }`. Never put the credential in operation `headers`.
48
+ - Per operation: `method`, `path` (with `{id}` templates), `query: ["arg"]` for URL params, `input`,
49
+ `output`, and a `response` mapping whose `${response.path}` matches the real JSON — the parsed body is
50
+ bound directly to `${response}` (no `.body` envelope); use bracket indices for arrays
51
+ (`${response.results[0].id}`).
52
+
53
+ The full connector shape, every `auth.kind`, and compute connectors are in
54
+ `references/authoring-workflow.md`.
55
+
56
+ ## Design intent tools
57
+
58
+ Shape tools around what the user says, not 1:1 around endpoints. Pair an id-taking action with a
59
+ find/search operation that returns `{ id, label }` summaries so the model resolves text → id itself,
60
+ and map each response to a few labelled fields the model can speak from. See the "Design tools for the
61
+ model" section of `references/authoring-workflow.md`.
62
+
63
+ ## Prove real output
64
+
65
+ `noodle validate` / `noodle test` prove a connector tool *compiles and registers* — not that its
66
+ mapping returns data. With the secret set, run a live read: `noodle tools call <read_tool> --args
67
+ '{…}'` executes the connector against the real API in-process. Confirm the mapped fields are populated,
68
+ not `undefined`; if they are empty, fix the `${response…}` paths against the real payload and re-run.
69
+ Only run a live write if it is safe or the user approved it.
70
+
71
+ ## Then build the app
72
+
73
+ With real data flowing, design the experience (`references/experience-design.md`), add widgets where a
74
+ UI genuinely helps (`references/widgets-and-apps.md`), and verify with `noodle check`. Deploy per
75
+ `references/deploy-and-ops.md`, and set the same secret in the hosted environment with `noodle secrets
76
+ set` before the first hosted call.
@@ -1,12 +1,24 @@
1
1
  # Examples
2
2
 
3
- Flagship examples (one per capability). Read the matching example for the pattern you need; extend an existing flagship rather than inventing a new shape.
3
+ Flagship examples, one per capability. The **bundled** rows ship inside this skill under `examples/<name>/` (the real `server.ts`, `src/views/*.tsx`, and — for the design-first flagships — a `design/` set); read them locally at the paths shown. The rest live only in the Noodle Seed repository under `examples/<name>/` on GitHub. Extend an existing flagship rather than inventing a new shape.
4
+
5
+ ## Bundled in this skill — read locally
6
+
7
+ Paths are relative to this skill directory. Assets (images/fonts) are omitted from the bundle; fetch the full runnable example from the repository if you need them. Verify any edit with `noodle validate --json` then `noodle test --json`.
8
+
9
+ | Example | Use when | Read |
10
+ | :-- | :-- | :-- |
11
+ | `hello` | Minimal TypeScript quickstart — a single tool, no connectors/widgets. | `examples/hello/src/server.ts` |
12
+ | `weather` | HTTP connectors, multi-step flows, and the sandboxed compute connector. | `examples/weather/src/server.ts` |
13
+ | `food-ordering` | Consumer ordering MCP App widgets, app-only helpers, cart state, assets, branding, and handoff. | `examples/food-ordering/src/server.ts` |
14
+ | `acme-discovery` | Top-of-funnel discovery→handoff: a discovery carousel, a `create_handoff` deep link, and a design-first UX spec + wireframe. | `examples/acme-discovery/src/server.ts` + `design/` |
15
+ | `acme-tasks` | A two-way productivity app designed around its top-3 prioritized flows (capture/prioritize/complete), with a design-first flow spec + wireframe. | `examples/acme-tasks/src/server.ts` + `design/` |
16
+ | `acme-bistro` | End-to-end ordering with a payment-only handoff; ships a gold-standard `design/` set (UX doc, wireframe with compliance audit, API contract). | `examples/acme-bistro/src/server.ts` + `design/` |
17
+
18
+ ## In the repository only — `examples/<name>/` on GitHub
4
19
 
5
20
  | Example | Use when |
6
21
  | :-- | :-- |
7
- | `hello` | Minimal TypeScript quickstart — a single tool, no connectors/widgets. |
8
- | `weather` | HTTP connectors, multi-step flows, and the sandboxed compute connector. |
9
- | `food-ordering` | Consumer ordering MCP App widgets, app-only helpers, cart state, assets, branding, and handoff. |
10
22
  | `customer-auth` | End-user (customer) auth via OIDC/Firebase bridge with delegated credentials. |
11
23
  | `stateful-draft` | Durable, caller-scoped widget state handles with optimistic revisions. |
12
24
  | `perplexity` | A real SaaS API with bearer auth and a managed `secret`. |