@noodleseed/agent-kit 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/manifest.json +545 -5
- package/package.json +1 -1
- package/skills/claude-code/SKILL.md +1 -1
- package/skills/claude-code/examples/acme-bistro/README.md +51 -0
- package/skills/claude-code/examples/acme-bistro/design/UX-Document.md +435 -0
- package/skills/claude-code/examples/acme-bistro/design/api-contract.md +161 -0
- package/skills/claude-code/examples/acme-bistro/design/wireframe.html +573 -0
- package/skills/claude-code/examples/acme-bistro/noodle.json +5 -0
- package/skills/claude-code/examples/acme-bistro/package.json +20 -0
- package/skills/claude-code/examples/acme-bistro/src/helpers.ts +7 -0
- package/skills/claude-code/examples/acme-bistro/src/server.ts +149 -0
- package/skills/claude-code/examples/acme-bistro/src/views/menu-cart.tsx +159 -0
- package/skills/claude-code/examples/acme-bistro/src/views/widget-style.css +228 -0
- package/skills/claude-code/examples/acme-bistro/test/server.test.ts +21 -0
- package/skills/claude-code/examples/acme-bistro/vitest.config.ts +7 -0
- package/skills/claude-code/examples/acme-discovery/README.md +85 -0
- package/skills/claude-code/examples/acme-discovery/design/UX-Document.md +441 -0
- package/skills/claude-code/examples/acme-discovery/design/wireframe.html +670 -0
- package/skills/claude-code/examples/acme-discovery/noodle.json +5 -0
- package/skills/claude-code/examples/acme-discovery/package.json +20 -0
- package/skills/claude-code/examples/acme-discovery/src/helpers.ts +7 -0
- package/skills/claude-code/examples/acme-discovery/src/server.ts +202 -0
- package/skills/claude-code/examples/acme-discovery/src/views/discovery-carousel.tsx +155 -0
- package/skills/claude-code/examples/acme-discovery/src/views/widget-style.css +207 -0
- package/skills/claude-code/examples/acme-discovery/test/server.test.ts +24 -0
- package/skills/claude-code/examples/acme-discovery/vitest.config.ts +7 -0
- package/skills/claude-code/examples/acme-tasks/README.md +80 -0
- package/skills/claude-code/examples/acme-tasks/design/UX-Document.md +277 -0
- package/skills/claude-code/examples/acme-tasks/design/wireframe.html +603 -0
- package/skills/claude-code/examples/acme-tasks/noodle.json +5 -0
- package/skills/claude-code/examples/acme-tasks/package.json +20 -0
- package/skills/claude-code/examples/acme-tasks/src/helpers.ts +6 -0
- package/skills/claude-code/examples/acme-tasks/src/server.ts +141 -0
- package/skills/claude-code/examples/acme-tasks/src/views/task-list.tsx +177 -0
- package/skills/claude-code/examples/acme-tasks/src/views/widget-style.css +227 -0
- package/skills/claude-code/examples/acme-tasks/test/server.test.ts +22 -0
- package/skills/claude-code/examples/acme-tasks/vitest.config.ts +7 -0
- package/skills/claude-code/examples/food-ordering/README.md +82 -0
- package/skills/claude-code/examples/food-ordering/noodle.json +5 -0
- package/skills/claude-code/examples/food-ordering/package.json +22 -0
- package/skills/claude-code/examples/food-ordering/src/helpers.ts +34 -0
- package/skills/claude-code/examples/food-ordering/src/server.ts +412 -0
- package/skills/claude-code/examples/food-ordering/src/views/capabilities-card.tsx +155 -0
- package/skills/claude-code/examples/food-ordering/src/views/ordering-flow.tsx +659 -0
- package/skills/claude-code/examples/food-ordering/src/views/widget-style.css +682 -0
- package/skills/claude-code/examples/food-ordering/test/server.test.ts +58 -0
- package/skills/claude-code/examples/food-ordering/vitest.config.ts +30 -0
- package/skills/claude-code/examples/hello/README.md +13 -0
- package/skills/claude-code/examples/hello/noodle.json +5 -0
- package/skills/claude-code/examples/hello/package.json +16 -0
- package/skills/claude-code/examples/hello/src/server.ts +29 -0
- package/skills/claude-code/examples/hello/test/server.test.ts +8 -0
- package/skills/claude-code/examples/weather/README.md +54 -0
- package/skills/claude-code/examples/weather/noodle.json +4 -0
- package/skills/claude-code/examples/weather/package.json +16 -0
- package/skills/claude-code/examples/weather/src/server.ts +190 -0
- package/skills/claude-code/examples/weather/test/server.test.ts +8 -0
- package/skills/claude-code/references/examples.md +16 -7
- package/skills/codex/SKILL.md +1 -1
- package/skills/codex/examples/acme-bistro/README.md +51 -0
- package/skills/codex/examples/acme-bistro/design/UX-Document.md +435 -0
- package/skills/codex/examples/acme-bistro/design/api-contract.md +161 -0
- package/skills/codex/examples/acme-bistro/design/wireframe.html +573 -0
- package/skills/codex/examples/acme-bistro/noodle.json +5 -0
- package/skills/codex/examples/acme-bistro/package.json +20 -0
- package/skills/codex/examples/acme-bistro/src/helpers.ts +7 -0
- package/skills/codex/examples/acme-bistro/src/server.ts +149 -0
- package/skills/codex/examples/acme-bistro/src/views/menu-cart.tsx +159 -0
- package/skills/codex/examples/acme-bistro/src/views/widget-style.css +228 -0
- package/skills/codex/examples/acme-bistro/test/server.test.ts +21 -0
- package/skills/codex/examples/acme-bistro/vitest.config.ts +7 -0
- package/skills/codex/examples/acme-discovery/README.md +85 -0
- package/skills/codex/examples/acme-discovery/design/UX-Document.md +441 -0
- package/skills/codex/examples/acme-discovery/design/wireframe.html +670 -0
- package/skills/codex/examples/acme-discovery/noodle.json +5 -0
- package/skills/codex/examples/acme-discovery/package.json +20 -0
- package/skills/codex/examples/acme-discovery/src/helpers.ts +7 -0
- package/skills/codex/examples/acme-discovery/src/server.ts +202 -0
- package/skills/codex/examples/acme-discovery/src/views/discovery-carousel.tsx +155 -0
- package/skills/codex/examples/acme-discovery/src/views/widget-style.css +207 -0
- package/skills/codex/examples/acme-discovery/test/server.test.ts +24 -0
- package/skills/codex/examples/acme-discovery/vitest.config.ts +7 -0
- package/skills/codex/examples/acme-tasks/README.md +80 -0
- package/skills/codex/examples/acme-tasks/design/UX-Document.md +277 -0
- package/skills/codex/examples/acme-tasks/design/wireframe.html +603 -0
- package/skills/codex/examples/acme-tasks/noodle.json +5 -0
- package/skills/codex/examples/acme-tasks/package.json +20 -0
- package/skills/codex/examples/acme-tasks/src/helpers.ts +6 -0
- package/skills/codex/examples/acme-tasks/src/server.ts +141 -0
- package/skills/codex/examples/acme-tasks/src/views/task-list.tsx +177 -0
- package/skills/codex/examples/acme-tasks/src/views/widget-style.css +227 -0
- package/skills/codex/examples/acme-tasks/test/server.test.ts +22 -0
- package/skills/codex/examples/acme-tasks/vitest.config.ts +7 -0
- package/skills/codex/examples/food-ordering/README.md +82 -0
- package/skills/codex/examples/food-ordering/noodle.json +5 -0
- package/skills/codex/examples/food-ordering/package.json +22 -0
- package/skills/codex/examples/food-ordering/src/helpers.ts +34 -0
- package/skills/codex/examples/food-ordering/src/server.ts +412 -0
- package/skills/codex/examples/food-ordering/src/views/capabilities-card.tsx +155 -0
- package/skills/codex/examples/food-ordering/src/views/ordering-flow.tsx +659 -0
- package/skills/codex/examples/food-ordering/src/views/widget-style.css +682 -0
- package/skills/codex/examples/food-ordering/test/server.test.ts +58 -0
- package/skills/codex/examples/food-ordering/vitest.config.ts +30 -0
- package/skills/codex/examples/hello/README.md +13 -0
- package/skills/codex/examples/hello/noodle.json +5 -0
- package/skills/codex/examples/hello/package.json +16 -0
- package/skills/codex/examples/hello/src/server.ts +29 -0
- package/skills/codex/examples/hello/test/server.test.ts +8 -0
- package/skills/codex/examples/weather/README.md +54 -0
- package/skills/codex/examples/weather/noodle.json +4 -0
- package/skills/codex/examples/weather/package.json +16 -0
- package/skills/codex/examples/weather/src/server.ts +190 -0
- package/skills/codex/examples/weather/test/server.test.ts +8 -0
- package/skills/codex/references/examples.md +16 -7
|
@@ -0,0 +1,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
|
+
);
|
|
@@ -1,15 +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
|
-
| `acme-discovery` | Top-of-funnel discovery→handoff: a discovery carousel, a `create_handoff` deep link, and a design-first UX spec + wireframe. |
|
|
11
|
-
| `acme-tasks` | A two-way productivity app designed around its top-3 prioritized flows (capture/prioritize/complete), with a design-first flow spec + wireframe. |
|
|
12
|
-
| `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). |
|
|
13
22
|
| `customer-auth` | End-user (customer) auth via OIDC/Firebase bridge with delegated credentials. |
|
|
14
23
|
| `stateful-draft` | Durable, caller-scoped widget state handles with optimistic revisions. |
|
|
15
24
|
| `perplexity` | A real SaaS API with bearer auth and a managed `secret`. |
|
package/skills/codex/SKILL.md
CHANGED
|
@@ -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.
|