quiver-cli 0.6.0 → 0.8.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/LICENSE +21 -0
- package/dist/cli.js +314 -86
- package/package.json +1 -1
- package/template/.agents/skills/agent-browser/SKILL.md +4 -9
- package/template/.agents/skills/apps/skybridge/SKILL.md +1 -1
- package/template/.agents/skills/code/improve/SKILL.md +9 -5
- package/template/.agents/skills/code/improve/references/audit-playbook.md +10 -10
- package/template/.agents/skills/code/improve/references/closing-the-loop.md +4 -3
- package/template/.agents/skills/code/improve/references/plan-template.md +5 -0
- package/template/.agents/skills/design/impeccable/SKILL.md +14 -24
- package/template/.agents/skills/design/impeccable/reference/animate.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/bolder.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/brand.md +2 -2
- package/template/.agents/skills/design/impeccable/reference/colorize.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/critique.md +6 -6
- package/template/.agents/skills/design/impeccable/reference/delight.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/distill.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/document.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/extract.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/hooks.md +90 -0
- package/template/.agents/skills/design/impeccable/reference/init.md +5 -5
- package/template/.agents/skills/design/impeccable/reference/live.md +16 -16
- package/template/.agents/skills/design/impeccable/reference/overdrive.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/polish.md +2 -2
- package/template/.agents/skills/design/impeccable/reference/quieter.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/shape.md +2 -2
- package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +1 -1
- package/template/.agents/skills/design/impeccable/scripts/context.mjs +724 -33
- package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +1 -1
- package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +204 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +57 -11
- package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +750 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +633 -46
- package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns.mjs +7 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +29 -4
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +43 -10
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +29 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +27 -1
- package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +1 -1
- package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +29 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +401 -46
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
- package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +661 -0
- package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +476 -0
- package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +1632 -0
- package/template/.agents/skills/design/impeccable/scripts/hook.mjs +61 -0
- package/template/.agents/skills/design/impeccable/scripts/{design-parser.mjs → lib/design-parser.mjs} +8 -1
- package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +638 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/target-args.mjs +42 -0
- package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +49 -0
- package/template/.agents/skills/design/impeccable/scripts/{live-event-validation.mjs → live/event-validation.mjs} +6 -5
- package/template/.agents/skills/design/impeccable/scripts/live/manual-apply.mjs +939 -0
- package/template/.agents/skills/design/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
- package/template/.agents/skills/design/impeccable/scripts/{live-manual-edits-buffer.mjs → live/manual-edits-buffer.mjs} +1 -1
- package/template/.agents/skills/design/impeccable/scripts/{live-session-store.mjs → live/session-store.mjs} +1 -1
- package/template/.agents/skills/design/impeccable/scripts/{live-ui-core.mjs → live/ui-core.mjs} +2 -1
- package/template/.agents/skills/design/impeccable/scripts/live/vocabulary.mjs +36 -0
- package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +3 -3
- package/template/.agents/skills/design/impeccable/scripts/live-browser-dom.js +146 -0
- package/template/.agents/skills/design/impeccable/scripts/live-browser.js +1456 -599
- package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +2 -2
- package/template/.agents/skills/design/impeccable/scripts/live-complete.mjs +2 -2
- package/template/.agents/skills/design/impeccable/scripts/live-discard-manual-edits.mjs +1 -1
- package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +35 -9
- package/template/.agents/skills/design/impeccable/scripts/live-insert.mjs +2 -2
- package/template/.agents/skills/design/impeccable/scripts/live-manual-edit-evidence.mjs +2 -2
- package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +18 -13
- package/template/.agents/skills/design/impeccable/scripts/live-resume.mjs +1 -1
- package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +77 -1264
- package/template/.agents/skills/design/impeccable/scripts/live-status.mjs +2 -2
- package/template/.agents/skills/design/impeccable/scripts/live-target.mjs +30 -0
- package/template/.agents/skills/design/impeccable/scripts/live-wrap.mjs +4 -4
- package/template/.agents/skills/design/impeccable/scripts/live.mjs +73 -22
- package/template/.agents/skills/writing/humanizer/SKILL.md +621 -0
- package/template/.agents/upstreams.json +17 -8
- package/template/.agents/skills/apps/skybridge/references/architecture.md +0 -175
- package/template/.agents/skills/apps/skybridge/references/copy-template.md +0 -24
- package/template/.agents/skills/apps/skybridge/references/csp.md +0 -33
- package/template/.agents/skills/apps/skybridge/references/deploy.md +0 -33
- package/template/.agents/skills/apps/skybridge/references/discover.md +0 -84
- package/template/.agents/skills/apps/skybridge/references/download-file.md +0 -77
- package/template/.agents/skills/apps/skybridge/references/fetch-and-render-data.md +0 -151
- package/template/.agents/skills/apps/skybridge/references/oauth.md +0 -115
- package/template/.agents/skills/apps/skybridge/references/open-external-links.md +0 -71
- package/template/.agents/skills/apps/skybridge/references/prompt-llm.md +0 -20
- package/template/.agents/skills/apps/skybridge/references/publish.md +0 -19
- package/template/.agents/skills/apps/skybridge/references/run-locally.md +0 -51
- package/template/.agents/skills/apps/skybridge/references/state-and-context.md +0 -151
- package/template/.agents/skills/apps/skybridge/references/ui-guidelines.md +0 -205
- package/template/.agents/skills/design/impeccable/scripts/cleanup-deprecated.mjs +0 -284
- package/template/.agents/skills/design/impeccable/scripts/impeccable-paths.mjs +0 -126
- /package/template/.agents/skills/design/impeccable/scripts/{is-generated.mjs → lib/is-generated.mjs} +0 -0
- /package/template/.agents/skills/design/impeccable/scripts/{live-completion.mjs → live/completion.mjs} +0 -0
- /package/template/.agents/skills/design/impeccable/scripts/{live-insert-ui.mjs → live/insert-ui.mjs} +0 -0
- /package/template/.agents/skills/design/impeccable/scripts/{live-svelte-component.mjs → live/svelte-component.mjs} +0 -0
- /package/template/.agents/skills/design/impeccable/scripts/{live-sveltekit-adapter.mjs → live/sveltekit-adapter.mjs} +0 -0
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
# Architecture Workflow
|
|
2
|
-
|
|
3
|
-
## Concepts
|
|
4
|
-
|
|
5
|
-
A **tool** is a backend action with no UI. It takes input and returns structured output. It can CRUD data and perform operations (checkout, submit, etc.).
|
|
6
|
-
|
|
7
|
-
A **view** is a tool with a UI. It renders the tool output visually. The UI is a React app that can:
|
|
8
|
-
- navigate multiple views (search → detail → confirmation)
|
|
9
|
-
- manage its own state
|
|
10
|
-
- call other tools to fetch data absent from the view output schema or trigger actions.
|
|
11
|
-
|
|
12
|
-
## Step 1: Identify the UX Flows
|
|
13
|
-
|
|
14
|
-
A **flow** is an end-to-end user journey that accomplishes one goal (e.g., "book a flight" = search → select → checkout).
|
|
15
|
-
Extract flows from the SPEC's value proposition. **Stick to the spec**: don't invent flows or infer intermediate steps.
|
|
16
|
-
|
|
17
|
-
**Example:**
|
|
18
|
-
|
|
19
|
-
Input (SPEC):
|
|
20
|
-
> Book flights by destination and dates, and cancel existing bookings by booking ID.
|
|
21
|
-
|
|
22
|
-
✅ Good output:
|
|
23
|
-
```
|
|
24
|
-
Book flight:
|
|
25
|
-
1. Search flights
|
|
26
|
-
2. Select flight
|
|
27
|
-
3. Checkout
|
|
28
|
-
Cancel booking:
|
|
29
|
-
1. Cancel booking
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
❌ Bad output:
|
|
33
|
-
```
|
|
34
|
-
Search flights:
|
|
35
|
-
1. Search flights
|
|
36
|
-
2. View results
|
|
37
|
-
Book flight: ← wrong: split booking into separate flow
|
|
38
|
-
1. Select flight
|
|
39
|
-
2. Enter passenger details
|
|
40
|
-
3. Checkout
|
|
41
|
-
Cancel booking:
|
|
42
|
-
1. List bookings ← wrong: invented step
|
|
43
|
-
2. Cancel booking
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**Do not proceed to Step 2 yet**: validate with user, adjust based on feedback.
|
|
47
|
-
|
|
48
|
-
## Step 2: Does the flow need UI?
|
|
49
|
-
|
|
50
|
-
Based on the UX flow:
|
|
51
|
-
|
|
52
|
-
**YES if:**
|
|
53
|
-
- Browsing/comparing multiple items
|
|
54
|
-
- Visual data improves understanding (maps, charts, images)
|
|
55
|
-
- Selections are easier in a visual layout
|
|
56
|
-
|
|
57
|
-
**NO if:**
|
|
58
|
-
- Inputs are naturally conversational (amounts, dates, descriptions)
|
|
59
|
-
- Output is simple enough as text
|
|
60
|
-
- No visual element would meaningfully improve the experience
|
|
61
|
-
|
|
62
|
-
## Step 3: Design the API
|
|
63
|
-
|
|
64
|
-
### Best Practices
|
|
65
|
-
|
|
66
|
-
**Naming:** Both views and tools start with a verb: `search_flights`, `get_details`, `create_checkout`.
|
|
67
|
-
|
|
68
|
-
**One view per flow/intent:** Different flows can have separate views
|
|
69
|
-
❌ `search_flights` view + `view_flight` view (same flow → merge into one view)
|
|
70
|
-
✅ `search_flights` view + `manage_bookings` view (different flows)
|
|
71
|
-
|
|
72
|
-
**Don't duplicate:** View output is returned to the LLM for conversation. View can be re-invoked. Don't create a tool that duplicates what the view fetches.
|
|
73
|
-
❌ `search_flights` view + `get_flights` tool (same data → view already fetches this)
|
|
74
|
-
✅ unique `search_flights` view that can be re-invoked by LLM or user
|
|
75
|
-
|
|
76
|
-
**View UI handles its own state:** Cart, selections, and form inputs live in the view - not as tools.
|
|
77
|
-
❌ `add_to_cart` tool (cart is view state)
|
|
78
|
-
❌ `select_seat` tool (selection is view state)
|
|
79
|
-
❌ `update_quantity` tool (form input is view state)
|
|
80
|
-
✅ Tools are for backend operations only: `create_checkout`, `submit_order`, `make_reservation`
|
|
81
|
-
|
|
82
|
-
**Don't lazy-load:** Tool calls are expensive. Return all needed data upfront.
|
|
83
|
-
❌ `search_flights` view + `get_flight_details` tool (lazy-loading details)
|
|
84
|
-
✅ `search_flights` view returns full flight data including details
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
For each identified flow:
|
|
89
|
-
|
|
90
|
-
### If NEEDS UI → View + Optional Tool(s)
|
|
91
|
-
|
|
92
|
-
**Example: Flight Booking**
|
|
93
|
-
|
|
94
|
-
UX Flow:
|
|
95
|
-
1. Search flights by dates, destination
|
|
96
|
-
2. Browse results, select flight
|
|
97
|
-
3. View flight details
|
|
98
|
-
4. Click checkout → redirect to payment
|
|
99
|
-
|
|
100
|
-
API:
|
|
101
|
-
|
|
102
|
-
**View: search_flights**
|
|
103
|
-
- Input: `{ dates, destination }`
|
|
104
|
-
- Output: `{ flights }` → rendered as list
|
|
105
|
-
- Views: search results, flight detail + passenger form
|
|
106
|
-
- Calls `create_checkout` tool → redirects to payment
|
|
107
|
-
|
|
108
|
-
**Tool: create_checkout**
|
|
109
|
-
- Input: `{ flightId, passengers[] }`
|
|
110
|
-
- Output: `{ checkoutUrl }` → view redirects to Stripe
|
|
111
|
-
|
|
112
|
-
### If DOES NOT NEED UI → Tool(s) Only
|
|
113
|
-
|
|
114
|
-
**Example: Manage Bookings**
|
|
115
|
-
|
|
116
|
-
UX Flow:
|
|
117
|
-
1. User: "Cancel my flight to Paris"
|
|
118
|
-
2. LLM asks for email, fetches bookings, asks clarifying questions if needed
|
|
119
|
-
3. LLM confirms and cancels
|
|
120
|
-
|
|
121
|
-
API:
|
|
122
|
-
|
|
123
|
-
**Tool: list_bookings**
|
|
124
|
-
- Input: `{ email }`
|
|
125
|
-
- Output: `{ booking[] }` → LLM says "You have two upcoming flights for Paris, which one do you want to cancel?"
|
|
126
|
-
|
|
127
|
-
**Tool: cancel_booking**
|
|
128
|
-
- Input: `{ bookingId }`
|
|
129
|
-
- Output: `{ booking }` → LLM summarizes: "Your booking for Paris on Jan 1 has been canceled."
|
|
130
|
-
|
|
131
|
-
## Step 4: Review
|
|
132
|
-
|
|
133
|
-
Present the final architecture to the user, adjust based on feedback.
|
|
134
|
-
|
|
135
|
-
## Step 5: Update SPEC.md
|
|
136
|
-
|
|
137
|
-
Update SPEC.md with the UX flows and API design.
|
|
138
|
-
|
|
139
|
-
**Example:**
|
|
140
|
-
|
|
141
|
-
```markdown
|
|
142
|
-
...
|
|
143
|
-
|
|
144
|
-
## UX Flows
|
|
145
|
-
|
|
146
|
-
Book a flight:
|
|
147
|
-
1. Search flights by destination and dates
|
|
148
|
-
2. Browse results, select flight
|
|
149
|
-
3. Enter passenger details
|
|
150
|
-
4. Checkout (redirect to Stripe)
|
|
151
|
-
|
|
152
|
-
Cancel booking:
|
|
153
|
-
1. Provide email
|
|
154
|
-
2. Select booking to cancel
|
|
155
|
-
|
|
156
|
-
## Tools and Views
|
|
157
|
-
|
|
158
|
-
**View: search_flights**
|
|
159
|
-
- **Input**: `{ destination, dates }`
|
|
160
|
-
- **Output**: `{ flights[] }`
|
|
161
|
-
- **Views**: results list, flight detail, passenger form
|
|
162
|
-
- **Behavior**: manages passenger state locally, calls `create_checkout` tool
|
|
163
|
-
|
|
164
|
-
**Tool: create_checkout**
|
|
165
|
-
- **Input**: `{ flightId, passengers[] }`
|
|
166
|
-
- **Output**: `{ checkoutUrl }`
|
|
167
|
-
|
|
168
|
-
**Tool: list_bookings**
|
|
169
|
-
- **Input**: `{ email }`
|
|
170
|
-
- **Output**: `{ bookings[] }`
|
|
171
|
-
|
|
172
|
-
**Tool: cancel_booking**
|
|
173
|
-
- **Input**: `{ bookingId }`
|
|
174
|
-
- **Output**: `{ success, booking }`
|
|
175
|
-
```
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# Start From Template
|
|
2
|
-
|
|
3
|
-
Scaffold a project by setting up the Skybridge template starter. Skybridge is a TypeScript framework for building MCP servers with type-safe APIs and React views.
|
|
4
|
-
|
|
5
|
-
## Workflow
|
|
6
|
-
|
|
7
|
-
1. Ask: "Which package manager?" (npm / pnpm / yarn / bun / deno)
|
|
8
|
-
|
|
9
|
-
2. Run (do not `rm` beforehand—create handles conflicts):
|
|
10
|
-
```bash
|
|
11
|
-
{pm} create skybridge@latest {target-dir}
|
|
12
|
-
|
|
13
|
-
# deno
|
|
14
|
-
deno init --npm skybridge {target-dir}
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
3. [Start the dev server](run-locally.md). Read logs to assess readiness/health; fix any errors (TypeScript, etc.) before proceeding.
|
|
18
|
-
|
|
19
|
-
4. Start implementing your app using these core concepts:
|
|
20
|
-
- Server handlers and view components → [fetch-and-render-data.md](fetch-and-render-data.md)
|
|
21
|
-
- View state and LLM context → [state-and-context.md](state-and-context.md)
|
|
22
|
-
- Display modes → [ui-guidelines.md](ui-guidelines.md)
|
|
23
|
-
|
|
24
|
-
5. Delete unused views files and leftover code.
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# Content Security Policy
|
|
2
|
-
|
|
3
|
-
Views run in sandboxed iframes with strict CSP. Whitelist external domains under the tool's `view.csp`:
|
|
4
|
-
|
|
5
|
-
| Property | Purpose |
|
|
6
|
-
|----------|---------|
|
|
7
|
-
| `connectDomains` | Fetch/XHR requests to external APIs |
|
|
8
|
-
| `resourceDomains` | Static assets (images, fonts, scripts, styles) |
|
|
9
|
-
| `redirectDomains` | (optional) `openExternal` destinations without safe-link modal |
|
|
10
|
-
| `frameDomains` | (optional) Iframe embeds — triggers stricter review |
|
|
11
|
-
|
|
12
|
-
```typescript
|
|
13
|
-
server.registerTool(
|
|
14
|
-
{
|
|
15
|
-
name: "search-flights",
|
|
16
|
-
description: "Search flights",
|
|
17
|
-
inputSchema: { ... },
|
|
18
|
-
view: {
|
|
19
|
-
component: "search-flights",
|
|
20
|
-
description: "Flight results",
|
|
21
|
-
csp: {
|
|
22
|
-
connectDomains: ["https://api.example.com"],
|
|
23
|
-
resourceDomains: ["https://cdn.example.com"],
|
|
24
|
-
frameDomains: ["https://maps.example.com"],
|
|
25
|
-
redirectDomains: ["https://checkout.example.com"],
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
async (input) => ({ ... })
|
|
30
|
-
);
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Skybridge auto-includes the server's domain. Only add external domains.
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# Deploy
|
|
2
|
-
|
|
3
|
-
Deploy to Alpic using Alpic CLI.
|
|
4
|
-
|
|
5
|
-
## Parameters
|
|
6
|
-
|
|
7
|
-
- {path-to-project} is the path to the project directory. It is relative to the current working directory.
|
|
8
|
-
- When executing a command requiring `{path-to-project}`, check that you provided the correct path to the project.
|
|
9
|
-
|
|
10
|
-
## Steps
|
|
11
|
-
|
|
12
|
-
1. **Make sure the user is logged in to Alpic**
|
|
13
|
-
|
|
14
|
-
Execute `npx alpic@latest login` to login to Alpic.
|
|
15
|
-
|
|
16
|
-
2. **Deploy to Alpic**
|
|
17
|
-
|
|
18
|
-
If it's a first time deployment (absence of `.alpic/` folder in the project directory), **ask the user for the project name**.
|
|
19
|
-
Then, execute `npx alpic@latest deploy --yes --project-name {project-name} {path-to-project}`.
|
|
20
|
-
|
|
21
|
-
3. **Subsequent deployments**
|
|
22
|
-
|
|
23
|
-
For subsequent deployments (presence of `.alpic/` folder in the project directory), execute `npx alpic@latest deploy --yes {path-to-project}`.
|
|
24
|
-
|
|
25
|
-
4. **Setup GitHub integration**
|
|
26
|
-
|
|
27
|
-
If it's a new project, ask the user first if they want to setup git.
|
|
28
|
-
If yes:
|
|
29
|
-
|
|
30
|
-
- **Push to GitHub** — Commit and push code
|
|
31
|
-
- **Link to Alpic project** - Use `npx alpic@latest git connect --yes {path-to-project}`
|
|
32
|
-
|
|
33
|
-
Full docs: [docs.alpic.ai/quickstart](https://docs.alpic.ai/quickstart)
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# Discovery Workflow
|
|
2
|
-
|
|
3
|
-
**Goal: Idea maturation, not speed.**
|
|
4
|
-
|
|
5
|
-
**Proceed in phases.** Even if the user provides details, complete each phase through conversation. Do not infer or assume but discuss and validate with user. Proceed one phase at a time—do not write SPEC.md nor proceed to implementation until all phases are validated.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Phase 1: Value Proposition
|
|
10
|
-
|
|
11
|
-
1. **Problem + User**: What problem? For whom?
|
|
12
|
-
2. **Pain**: How solved today? What's painful?
|
|
13
|
-
3. **Core actions**: 1-3 focused actions (not a full app port)
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Phase 2: Why LLM?
|
|
18
|
-
|
|
19
|
-
1. **Conversational win**: Where does "just say it" beat clicking?
|
|
20
|
-
2. **LLM adds**: What does the LLM contribute? (intent, generation, reasoning)
|
|
21
|
-
3. **What LLM lacks**: Your data? APIs? Ability to take real actions?
|
|
22
|
-
|
|
23
|
-
**Fail patterns** (stop if any match):
|
|
24
|
-
- Long-form or static content better suited for a website
|
|
25
|
-
- Complex multi-step workflows that exceed display modes
|
|
26
|
-
- Dashboards (use tables, lists, or short paragraphs instead)
|
|
27
|
-
- Full app ports instead of focused atomic actions
|
|
28
|
-
- No clear answer to "why inside an AI assistant vs standalone?"
|
|
29
|
-
|
|
30
|
-
→ If fails: explain gap, suggest different interface or narrower scope.
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Phase 3: UI Overview
|
|
35
|
-
|
|
36
|
-
Describe the user journey through core actions:
|
|
37
|
-
|
|
38
|
-
1. **First view**: What does the user see when they start?
|
|
39
|
-
2. **Key interactions**: What happens at each core action?
|
|
40
|
-
3. **End state**: How does the experience conclude?
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Phase 4: Product Context
|
|
45
|
-
|
|
46
|
-
Gather: existing products, APIs/data, auth method, constraints.
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## Phase 5: Create SPEC.md
|
|
51
|
-
|
|
52
|
-
**Only after phases 1-4 are discussed and validated with the user.** Do not write SPEC.md from the initial query alone.
|
|
53
|
-
|
|
54
|
-
Assemble from phases. Target: cwd if empty, else `{app-name}/`.
|
|
55
|
-
|
|
56
|
-
### Example
|
|
57
|
-
|
|
58
|
-
```markdown
|
|
59
|
-
# Pizza Ordering App
|
|
60
|
-
|
|
61
|
-
## Value Proposition
|
|
62
|
-
Order pizza through conversation. Target: PizzaCo customers wanting quick orders. Pain: navigating menus is slower than describing what you want.
|
|
63
|
-
|
|
64
|
-
**Core actions**: Browse menu, customize order, track delivery.
|
|
65
|
-
|
|
66
|
-
## Why LLM?
|
|
67
|
-
**Conversational win**: "My usual but with mushrooms" = one sentence vs. multiple screens.
|
|
68
|
-
**LLM adds**: Intent from natural descriptions, handles modifications.
|
|
69
|
-
**What LLM lacks**: Real menu and pricing data, order placement.
|
|
70
|
-
|
|
71
|
-
## UI Overview
|
|
72
|
-
**First view**: Popular pizzas with quick "reorder last" option.
|
|
73
|
-
**Browsing**: Menu with categories, filters, and customization options.
|
|
74
|
-
**Checkout**: Order summary, confirm, and place order.
|
|
75
|
-
**Tracking**: Live delivery status with ETA and map.
|
|
76
|
-
|
|
77
|
-
## Product Context
|
|
78
|
-
- **Existing products**: Mobile app, website
|
|
79
|
-
- **API**: REST at api.pizzaco.com (OAuth2, 100 req/min)
|
|
80
|
-
- **Auth**: PizzaCo account (OAuth2)
|
|
81
|
-
- **Constraints**: Payment via existing account only
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
After SPEC.md is created, confirm with user before proceeding to implementation.
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# Download file
|
|
2
|
-
|
|
3
|
-
Save content to the user's filesystem → `useDownload`
|
|
4
|
-
|
|
5
|
-
Views run in sandboxed iframes where `<a download>` and `URL.createObjectURL` are blocked. `useDownload` asks the host to perform the save; the host shows a confirmation dialog first.
|
|
6
|
-
|
|
7
|
-
> MCP Apps only. On ChatGPT (Apps SDK), use `useFiles` to work with attachments instead.
|
|
8
|
-
|
|
9
|
-
## Inline text (CSV, JSON, markdown)
|
|
10
|
-
|
|
11
|
-
```tsx
|
|
12
|
-
import { useDownload } from "skybridge/web";
|
|
13
|
-
|
|
14
|
-
function ExportButton({ rows }: { rows: Row[] }) {
|
|
15
|
-
const { download } = useDownload();
|
|
16
|
-
|
|
17
|
-
const handleClick = async () => {
|
|
18
|
-
const csv = rows.map((r) => `${r.id},${r.name}`).join("\n");
|
|
19
|
-
const { isError } = await download({
|
|
20
|
-
contents: [
|
|
21
|
-
{
|
|
22
|
-
type: "resource",
|
|
23
|
-
resource: {
|
|
24
|
-
uri: "file:///orders.csv", // filename hint
|
|
25
|
-
mimeType: "text/csv",
|
|
26
|
-
text: csv,
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
});
|
|
31
|
-
if (isError) {
|
|
32
|
-
// user cancelled or host denied — soft fail, not an exception
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
return <button onClick={handleClick}>Export CSV</button>;
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Inline binary
|
|
41
|
-
|
|
42
|
-
```tsx
|
|
43
|
-
await download({
|
|
44
|
-
contents: [
|
|
45
|
-
{
|
|
46
|
-
type: "resource",
|
|
47
|
-
resource: {
|
|
48
|
-
uri: "file:///chart.png",
|
|
49
|
-
mimeType: "image/png",
|
|
50
|
-
blob: base64EncodedPng,
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
|
-
});
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Resource link (host fetches)
|
|
58
|
-
|
|
59
|
-
```tsx
|
|
60
|
-
await download({
|
|
61
|
-
contents: [
|
|
62
|
-
{
|
|
63
|
-
type: "resource_link",
|
|
64
|
-
uri: "https://api.example.com/reports/q4.pdf",
|
|
65
|
-
name: "Q4 Report",
|
|
66
|
-
mimeType: "application/pdf",
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
});
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Notes
|
|
73
|
-
|
|
74
|
-
- Must be user-initiated (button/menu click). Calls from mount effects will be rejected.
|
|
75
|
-
- The `uri` is a filename hint; the host derives the suggested save name from the last path segment.
|
|
76
|
-
- `isError: true` is a soft signal (user cancelled / host denied). Transport errors throw.
|
|
77
|
-
- For binary content above a few hundred KB, prefer `resource_link` over inline base64.
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
# Fetch and render data
|
|
2
|
-
|
|
3
|
-
- Fetch structured data and render with custom UI → `view`
|
|
4
|
-
- Fetch textual data or trigger actions → `tool`
|
|
5
|
-
- Tool can be triggered by user interaction within a view UI
|
|
6
|
-
|
|
7
|
-
## Project Structure
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
my-app/
|
|
11
|
-
├── src/
|
|
12
|
-
│ ├── server.ts # McpServer with tool + view registration
|
|
13
|
-
│ ├── helpers.ts # Type-safe hooks via generateHelpers
|
|
14
|
-
│ ├── index.css # Global CSS, must be imported in every view
|
|
15
|
-
│ └── views/ # React components (filename = view component name)
|
|
16
|
-
│ └── search-flights.tsx
|
|
17
|
-
└── package.json
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
**Naming convention**: View filename must match the `view.component` name using kebab-case.
|
|
21
|
-
`search_flights` → register with `view.component: "search-flights"` → file `views/search-flights.tsx`
|
|
22
|
-
|
|
23
|
-
## Server Handlers
|
|
24
|
-
|
|
25
|
-
Output:
|
|
26
|
-
- **`structuredContent`**: concise JSON the view uses and the model reads. Include only what the model should see.
|
|
27
|
-
- **`content`** (optional): narration (Markdown or plaintext) shown to LLM
|
|
28
|
-
- **`_meta`** (optional): large or sensitive data exclusively for the view. _meta never reaches the model.
|
|
29
|
-
|
|
30
|
-
Annotations (set `true` when):
|
|
31
|
-
- **`readOnlyHint`**: only reads data, no side effects
|
|
32
|
-
- **`openWorldHint`**: publishes content or reaches outside user's account
|
|
33
|
-
- **`destructiveHint`**: deletes or overwrites user data
|
|
34
|
-
|
|
35
|
-
**Example**:
|
|
36
|
-
|
|
37
|
-
- src/server.ts
|
|
38
|
-
```typescript
|
|
39
|
-
import { McpServer } from "skybridge/server";
|
|
40
|
-
import { z } from "zod";
|
|
41
|
-
|
|
42
|
-
const server = new McpServer(
|
|
43
|
-
{ name: "my-app", version: "0.0.1" },
|
|
44
|
-
{ capabilities: {} },
|
|
45
|
-
)
|
|
46
|
-
.registerTool(
|
|
47
|
-
{
|
|
48
|
-
name: "search-flights",
|
|
49
|
-
description: "Search for flights",
|
|
50
|
-
inputSchema: { destination: z.string(), dates: z.string() },
|
|
51
|
-
annotations: { readOnlyHint: true, openWorldHint: false, destructiveHint: false },
|
|
52
|
-
view: {
|
|
53
|
-
component: "search-flights",
|
|
54
|
-
description: "Flight results",
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
async ({ destination, dates }) => {
|
|
58
|
-
const flights = await fetchFlights(destination, dates);
|
|
59
|
-
const structuredContent = { flights: [] };
|
|
60
|
-
const _meta = { images: [] }
|
|
61
|
-
for (const { id, departureTime, price, airlineLogo } of flights) {
|
|
62
|
-
structuredContent.flights.push({ id, departureTime, price });
|
|
63
|
-
_meta.images.push(airlineLogo);
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
structuredContent,
|
|
67
|
-
content: [{ type: "text", text: `Found ${flights.length} flights.` }],
|
|
68
|
-
_meta // mind the underscore prefix
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
)
|
|
72
|
-
.registerTool(
|
|
73
|
-
{
|
|
74
|
-
name: "book-flight",
|
|
75
|
-
description: "Book a flight",
|
|
76
|
-
inputSchema: { flightId: z.string() },
|
|
77
|
-
annotations: { readOnlyHint: false, openWorldHint: false, destructiveHint: false },
|
|
78
|
-
},
|
|
79
|
-
async ({ flightId }) => {
|
|
80
|
-
const confirmationId = await bookFlight(flightId);
|
|
81
|
-
return {
|
|
82
|
-
structuredContent: { confirmationId },
|
|
83
|
-
content: [{ type: "text", text: `Flight booked. Confirmation: ${confirmationId}` }],
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
server.run();
|
|
89
|
-
|
|
90
|
-
export type AppType = typeof server;
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## UI Components
|
|
94
|
-
|
|
95
|
-
- generate type-safe hooks with `generateHelpers`
|
|
96
|
-
- `useToolInfo`: access view input/output
|
|
97
|
-
- `useCallTool`: trigger tool from UI
|
|
98
|
-
|
|
99
|
-
**Example**:
|
|
100
|
-
|
|
101
|
-
- src/helpers.ts
|
|
102
|
-
```typescript
|
|
103
|
-
import { generateHelpers } from "skybridge/web";
|
|
104
|
-
import type { AppType } from "./server.js";
|
|
105
|
-
|
|
106
|
-
export const { useToolInfo, useCallTool } = generateHelpers<AppType>();
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
- src/views/search-flights.tsx
|
|
110
|
-
```tsx
|
|
111
|
-
import "@/index.css";
|
|
112
|
-
import { useToolInfo, useCallTool } from "../helpers.js";
|
|
113
|
-
|
|
114
|
-
export default function SearchFlights() {
|
|
115
|
-
const { input, output, isPending, responseMetadata } = useToolInfo<"search-flights">();
|
|
116
|
-
const {
|
|
117
|
-
callTool, // returns void, use `data` to get the actual output
|
|
118
|
-
data: bookFlightOutput,
|
|
119
|
-
isPending: isBooking,
|
|
120
|
-
isSuccess: isBooked,
|
|
121
|
-
} = useCallTool("book-flight");
|
|
122
|
-
|
|
123
|
-
if (isPending) {
|
|
124
|
-
return <div>Searching flights to {input?.destination}...</div>;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (isBooked) {
|
|
128
|
-
return <div>Booked! Confirmation: {bookFlightOutput.structuredContent.confirmationId}</div>;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<div>
|
|
133
|
-
<h2>Flights to {input.destination}</h2>
|
|
134
|
-
<ul>
|
|
135
|
-
{output.flights.map((flight, i) => (
|
|
136
|
-
<li key={i}>
|
|
137
|
-
<img src={responseMetadata.images[i]} />
|
|
138
|
-
{flight.departureTime} - ${flight.price}
|
|
139
|
-
<button
|
|
140
|
-
onClick={() => callTool({ flightId: flight.id })}
|
|
141
|
-
disabled={isBooking}
|
|
142
|
-
>
|
|
143
|
-
{isBooking ? "Booking..." : "Book"}
|
|
144
|
-
</button>
|
|
145
|
-
</li>
|
|
146
|
-
))}
|
|
147
|
-
</ul>
|
|
148
|
-
</div>
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
```
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
# OAuth Authentication
|
|
2
|
-
|
|
3
|
-
Enable user authentication so tools can access user-specific data.
|
|
4
|
-
|
|
5
|
-
## How it works
|
|
6
|
-
|
|
7
|
-
1. MCP server exposes OAuth discovery endpoints
|
|
8
|
-
2. Host reads them, walks the user through OAuth, refreshes tokens
|
|
9
|
-
3. Host calls `/mcp` with `Authorization: Bearer <token>`
|
|
10
|
-
4. `requireBearerAuth` middleware verifies the token and rejects with HTTP 401 if invalid — tool handlers never run unauthenticated
|
|
11
|
-
5. Tool handlers read user identity from `extra.authInfo`
|
|
12
|
-
|
|
13
|
-
## 1. Discovery endpoints
|
|
14
|
-
|
|
15
|
-
Mount OAuth metadata so MCP clients can discover the authorization server:
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
// src/server.ts
|
|
19
|
-
import { mcpAuthMetadataRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
20
|
-
import { McpServer } from "skybridge/server";
|
|
21
|
-
|
|
22
|
-
const server = new McpServer(
|
|
23
|
-
{ name: "my-app", version: "0.0.1" },
|
|
24
|
-
{ capabilities: {} },
|
|
25
|
-
).use(
|
|
26
|
-
mcpAuthMetadataRouter({
|
|
27
|
-
oauthMetadata: {
|
|
28
|
-
issuer: "https://your-oauth-provider.com",
|
|
29
|
-
authorization_endpoint: "https://your-oauth-provider.com/oauth2/authorize",
|
|
30
|
-
token_endpoint: "https://your-oauth-provider.com/oauth2/token",
|
|
31
|
-
response_types_supported: ["code"],
|
|
32
|
-
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
33
|
-
code_challenge_methods_supported: ["S256"],
|
|
34
|
-
},
|
|
35
|
-
// SERVER_URL: this server's public URL (localhost:3000, Alpic tunnel, or prod)
|
|
36
|
-
resourceServerUrl: new URL(process.env.SERVER_URL),
|
|
37
|
-
}),
|
|
38
|
-
);
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
This serves `/.well-known/oauth-authorization-server` and `/.well-known/oauth-protected-resource`.
|
|
42
|
-
|
|
43
|
-
## 2. Write a token verifier
|
|
44
|
-
|
|
45
|
-
`requireBearerAuth` takes a `verifier` with `verifyAccessToken(token): Promise<AuthInfo>`. Verify the provider's JWT against its JWKS:
|
|
46
|
-
|
|
47
|
-
⚠️ Fetch your provider's docs for the exact JWKS URL and issuer.
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
// src/auth.ts
|
|
51
|
-
import { InvalidTokenError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
|
|
52
|
-
import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
|
|
53
|
-
import * as jose from "jose";
|
|
54
|
-
|
|
55
|
-
const jwks = jose.createRemoteJWKSet(
|
|
56
|
-
new URL("https://your-oauth-provider.com/oauth2/jwks"),
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
export async function verifyAccessToken(token: string): Promise<AuthInfo> {
|
|
60
|
-
const { payload } = await jose.jwtVerify(token, jwks, {
|
|
61
|
-
issuer: "https://your-oauth-provider.com",
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
if (!payload.sub || typeof payload.sub !== "string") {
|
|
65
|
-
throw new InvalidTokenError("missing sub claim");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
token,
|
|
70
|
-
clientId: (payload.client_id ?? payload.azp ?? "") as string,
|
|
71
|
-
scopes: typeof payload.scope === "string" ? payload.scope.split(" ") : [],
|
|
72
|
-
expiresAt: payload.exp,
|
|
73
|
-
extra: { sub: payload.sub },
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## 3. Enforce auth on /mcp
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
// src/server.ts (continued)
|
|
82
|
-
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
|
|
83
|
-
import { verifyAccessToken } from "./auth.js";
|
|
84
|
-
|
|
85
|
-
server.use(
|
|
86
|
-
"/mcp",
|
|
87
|
-
requireBearerAuth({
|
|
88
|
-
verifier: { verifyAccessToken },
|
|
89
|
-
requiredScopes: ["openid", "email", "profile"], // optional
|
|
90
|
-
}),
|
|
91
|
-
);
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Unauthenticated requests get HTTP 401 before any tool handler runs.
|
|
95
|
-
|
|
96
|
-
## 4. Read auth in handlers
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
|
|
100
|
-
|
|
101
|
-
server.registerTool(
|
|
102
|
-
{
|
|
103
|
-
name: "get-orders",
|
|
104
|
-
description: "Get user orders",
|
|
105
|
-
},
|
|
106
|
-
async (_input, extra) => {
|
|
107
|
-
const auth = extra.authInfo as AuthInfo;
|
|
108
|
-
const orders = await fetchOrders(auth.extra?.sub as string);
|
|
109
|
-
return {
|
|
110
|
-
structuredContent: { orders },
|
|
111
|
-
content: [{ type: "text", text: `Found ${orders.length} orders` }],
|
|
112
|
-
};
|
|
113
|
-
},
|
|
114
|
-
);
|
|
115
|
-
```
|