@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.
- package/README.md +3 -3
- package/manifest.json +575 -5
- package/package.json +1 -1
- package/skills/claude-code/SKILL.md +7 -2
- 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/chatgpt-compliance.md +63 -0
- package/skills/claude-code/references/connect-an-api.md +76 -0
- package/skills/claude-code/references/examples.md +16 -4
- package/skills/claude-code/references/experience-design.md +142 -0
- package/skills/codex/SKILL.md +7 -2
- 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/chatgpt-compliance.md +63 -0
- package/skills/codex/references/connect-an-api.md +76 -0
- package/skills/codex/references/examples.md +16 -4
- 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,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,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,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,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
|
|
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`. |
|