@womp/mcp-ui 0.1.11 → 0.1.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@womp/mcp-ui",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "MCP Apps widgets for Womp's MCP server — interactive cards rendered inline in Claude and other MCP hosts.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -15,11 +15,13 @@
15
15
  },
16
16
  "scripts": {
17
17
  "build": "yarn build:ui && yarn build:bake",
18
- "build:ui": "yarn build:ui:image && yarn build:ui:mesh && yarn build:ui:print && yarn build:ui:cart && yarn build:ui:order && yarn build:ui:upload",
18
+ "build:ui": "yarn build:ui:image && yarn build:ui:mesh && yarn build:ui:print && yarn build:ui:cart && yarn build:ui:order && yarn build:ui:upload && yarn build:ui:openai-camera",
19
19
  "build:bake": "node scripts/bake-html.mjs",
20
20
  "dev": "vite",
21
21
  "dev:host": "vite",
22
22
  "dev:mcp": "node dev/server.mjs",
23
+ "dev:openai-mcp": "node dev/openai-mcp/index.mjs",
24
+ "test:openai-mcp": "node --test \"dev/openai-mcp/*.test.mjs\"",
23
25
  "lint": "tsc --noEmit",
24
26
  "clean": "rm -rf dist",
25
27
  "prepublishOnly": "yarn clean && yarn build",
@@ -28,7 +30,8 @@
28
30
  "build:ui:print": "CARD=print vite build",
29
31
  "build:ui:cart": "CARD=cart vite build",
30
32
  "build:ui:order": "CARD=order vite build",
31
- "build:ui:upload": "CARD=upload vite build"
33
+ "build:ui:upload": "CARD=upload vite build",
34
+ "build:ui:openai-camera": "CARD=openai-camera vite build"
32
35
  },
33
36
  "devDependencies": {
34
37
  "@modelcontextprotocol/ext-apps": "^1.7.5",
@@ -0,0 +1,384 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Camera</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: light dark;
10
+ }
11
+ body {
12
+ margin: 0;
13
+ font-family: var(--font-sans, system-ui, -apple-system, "Segoe UI", sans-serif);
14
+ color: var(--color-text-primary, #1b1b1a);
15
+ }
16
+
17
+ .card {
18
+ display: flex;
19
+ flex-direction: column;
20
+ max-width: 680px;
21
+ border-radius: var(--border-radius-lg, 14px);
22
+ overflow: hidden;
23
+ background: var(--color-background-secondary, #f6f6f4);
24
+ border: 1px solid var(--color-border-primary, rgba(0, 0, 0, 0.1));
25
+ box-sizing: border-box;
26
+ }
27
+ /* Fullscreen hands us the viewport: the viewer takes whatever the controls leave. */
28
+ :root[data-display-mode="fullscreen"] .card {
29
+ max-width: none;
30
+ height: 100vh;
31
+ border-radius: 0;
32
+ border: 0;
33
+ }
34
+
35
+ .stage {
36
+ position: relative;
37
+ height: 360px;
38
+ background: #d9d9d9;
39
+ }
40
+ :root[data-display-mode="fullscreen"] .stage {
41
+ flex: 1;
42
+ height: auto;
43
+ min-height: 0;
44
+ }
45
+ /* While ordering, the design stays in view but gives the form the room. */
46
+ .card[data-view="order"] .stage,
47
+ .card[data-view="done"] .stage {
48
+ height: 220px;
49
+ flex: none;
50
+ }
51
+ .stage iframe {
52
+ display: block;
53
+ width: 100%;
54
+ height: 100%;
55
+ border: 0;
56
+ }
57
+ .stage iframe[hidden] { display: none; }
58
+
59
+ .fallback {
60
+ position: absolute;
61
+ inset: 0;
62
+ overflow: auto;
63
+ padding: 16px;
64
+ box-sizing: border-box;
65
+ background: var(--color-background-tertiary, #ecebe6);
66
+ color: var(--color-text-primary, #1b1b1a);
67
+ font-size: var(--font-text-sm-size, 13px);
68
+ }
69
+ .fallback[hidden] { display: none; }
70
+ .fallback p { margin: 0 0 10px; }
71
+ .fallback dl {
72
+ display: grid;
73
+ grid-template-columns: minmax(0, 1fr) auto;
74
+ gap: 2px 12px;
75
+ margin: 0;
76
+ font-size: var(--font-text-xs-size, 12px);
77
+ }
78
+ .fallback dt { color: var(--color-text-secondary, rgba(27, 27, 26, 0.62)); }
79
+ .fallback dd { margin: 0; font-variant-numeric: tabular-nums; }
80
+
81
+ .view[hidden] { display: none; }
82
+
83
+ .controls {
84
+ display: grid;
85
+ gap: 8px;
86
+ padding: 12px 14px 0;
87
+ }
88
+ .row {
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 10px;
92
+ min-width: 0;
93
+ }
94
+ .row > span {
95
+ flex: none;
96
+ width: 48px;
97
+ font-size: var(--font-text-xs-size, 12px);
98
+ color: var(--color-text-secondary, rgba(27, 27, 26, 0.62));
99
+ }
100
+ .swatches {
101
+ display: flex;
102
+ flex-wrap: wrap;
103
+ gap: 6px;
104
+ min-width: 0;
105
+ }
106
+ .swatch {
107
+ width: 22px;
108
+ height: 22px;
109
+ padding: 0;
110
+ border-radius: 50%;
111
+ border: 1px solid rgba(0, 0, 0, 0.18);
112
+ cursor: pointer;
113
+ }
114
+ /* The clear resin gets a hint of glass rather than a flat swatch. */
115
+ .swatch.clear {
116
+ background-image: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0) 55%) !important;
117
+ }
118
+ .swatch[aria-pressed="true"] {
119
+ outline: 2px solid var(--color-text-primary, #1b1b1a);
120
+ outline-offset: 2px;
121
+ }
122
+
123
+ /* Any colour: the native picker (Chromium's has an eyedropper of its own). */
124
+ .pick {
125
+ position: relative;
126
+ flex: none;
127
+ width: 22px;
128
+ height: 22px;
129
+ border-radius: 50%;
130
+ border: 1px solid rgba(0, 0, 0, 0.18);
131
+ background: conic-gradient(#ff5e5e, #ffd93d, #6bdc6b, #4fc3f7, #7b6cf6, #f06bd8, #ff5e5e);
132
+ cursor: pointer;
133
+ box-sizing: border-box;
134
+ }
135
+ .pick input {
136
+ position: absolute;
137
+ inset: 0;
138
+ width: 100%;
139
+ height: 100%;
140
+ opacity: 0;
141
+ padding: 0;
142
+ border: 0;
143
+ cursor: pointer;
144
+ }
145
+ .icon-button {
146
+ flex: none;
147
+ display: inline-grid;
148
+ place-items: center;
149
+ width: 24px;
150
+ height: 24px;
151
+ padding: 0;
152
+ border-radius: 50%;
153
+ border: 1px solid var(--color-border-primary, rgba(0, 0, 0, 0.14));
154
+ background: transparent;
155
+ color: var(--color-text-primary, #1b1b1a);
156
+ font: inherit;
157
+ font-size: 14px;
158
+ line-height: 1;
159
+ cursor: pointer;
160
+ }
161
+ .icon-button[hidden] { display: none; }
162
+ .icon-button:disabled { opacity: 0.4; cursor: default; }
163
+
164
+ .mode {
165
+ flex: none;
166
+ display: inline-flex;
167
+ gap: 2px;
168
+ padding: 2px;
169
+ border-radius: var(--border-radius-full, 999px);
170
+ border: 1px solid var(--color-border-primary, rgba(0, 0, 0, 0.14));
171
+ }
172
+ .mode button {
173
+ font: inherit;
174
+ font-size: var(--font-text-xs-size, 12px);
175
+ padding: 3px 10px;
176
+ border: 0;
177
+ border-radius: var(--border-radius-full, 999px);
178
+ background: transparent;
179
+ color: var(--color-text-secondary, rgba(27, 27, 26, 0.62));
180
+ cursor: pointer;
181
+ }
182
+ .mode button[aria-pressed="true"] {
183
+ background: var(--color-text-primary, #1b1b1a);
184
+ color: var(--color-background-primary, #ffffff);
185
+ }
186
+
187
+ /* A gradient: its colours on a track, the one being edited ringed. */
188
+ .stops {
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 6px;
192
+ min-width: 0;
193
+ }
194
+ .stops[hidden] { display: none; }
195
+ .track {
196
+ position: relative;
197
+ width: 132px;
198
+ height: 22px;
199
+ border-radius: var(--border-radius-full, 999px);
200
+ border: 1px solid rgba(0, 0, 0, 0.18);
201
+ box-sizing: border-box;
202
+ }
203
+ .stop {
204
+ position: absolute;
205
+ top: 50%;
206
+ width: 18px;
207
+ height: 18px;
208
+ margin: -9px 0 0 -9px;
209
+ padding: 0;
210
+ border-radius: 50%;
211
+ border: 2px solid #ffffff;
212
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35);
213
+ cursor: pointer;
214
+ }
215
+ .stop[aria-pressed="true"] {
216
+ box-shadow: 0 0 0 2px var(--color-text-primary, #1b1b1a);
217
+ }
218
+
219
+ .foot {
220
+ display: flex;
221
+ align-items: center;
222
+ gap: 8px;
223
+ padding: 12px 14px;
224
+ }
225
+ .price {
226
+ flex: 1;
227
+ min-width: 0;
228
+ font-size: var(--font-text-sm-size, 13px);
229
+ color: var(--color-text-secondary, rgba(27, 27, 26, 0.62));
230
+ }
231
+ button.action {
232
+ font: inherit;
233
+ font-size: var(--font-text-sm-size, 13px);
234
+ font-weight: 600;
235
+ border-radius: var(--border-radius-full, 999px);
236
+ padding: 8px 14px;
237
+ cursor: pointer;
238
+ border: 1px solid var(--color-border-primary, rgba(0, 0, 0, 0.14));
239
+ background: transparent;
240
+ color: var(--color-text-primary, #1b1b1a);
241
+ }
242
+ button.action.primary {
243
+ border-color: transparent;
244
+ background: var(--color-text-primary, #1b1b1a);
245
+ color: var(--color-background-primary, #ffffff);
246
+ }
247
+ button.action:disabled { opacity: 0.5; cursor: default; }
248
+
249
+ form {
250
+ display: grid;
251
+ gap: 8px;
252
+ padding: 12px 14px 0;
253
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
254
+ }
255
+ form .wide { grid-column: 1 / -1; }
256
+ form h2 {
257
+ margin: 0;
258
+ font-size: var(--font-text-sm-size, 14px);
259
+ }
260
+ form .note {
261
+ margin: 0;
262
+ font-size: var(--font-text-xs-size, 12px);
263
+ color: var(--color-text-secondary, rgba(27, 27, 26, 0.62));
264
+ }
265
+ label {
266
+ display: grid;
267
+ gap: 3px;
268
+ min-width: 0;
269
+ font-size: var(--font-text-xs-size, 12px);
270
+ color: var(--color-text-secondary, rgba(27, 27, 26, 0.62));
271
+ }
272
+ input {
273
+ width: 100%;
274
+ box-sizing: border-box;
275
+ font: inherit;
276
+ font-size: var(--font-text-sm-size, 13px);
277
+ color: var(--color-text-primary, #1b1b1a);
278
+ background: var(--color-background-primary, #ffffff);
279
+ border: 1px solid var(--color-border-primary, rgba(0, 0, 0, 0.14));
280
+ border-radius: 8px;
281
+ padding: 7px 9px;
282
+ }
283
+ input[aria-invalid="true"] { border-color: #c2410c; }
284
+
285
+ .done {
286
+ padding: 14px;
287
+ display: grid;
288
+ gap: 6px;
289
+ font-size: var(--font-text-sm-size, 13px);
290
+ }
291
+ .done h2 { margin: 0; font-size: var(--font-text-md-size, 15px); }
292
+ .done p { margin: 0; color: var(--color-text-secondary, rgba(27, 27, 26, 0.62)); }
293
+
294
+ .status {
295
+ padding: 0 14px 10px;
296
+ font-size: var(--font-text-xs-size, 11px);
297
+ color: var(--color-text-secondary, rgba(27, 27, 26, 0.62));
298
+ }
299
+ .status:empty { display: none; }
300
+ </style>
301
+ </head>
302
+ <body>
303
+ <div class="card" id="card" data-view="design">
304
+ <div class="stage">
305
+ <iframe id="viewer" title="3D preview of your camera" allow="fullscreen" hidden></iframe>
306
+ <div class="fallback" id="fallback" hidden></div>
307
+ </div>
308
+
309
+ <div class="view" id="view-design">
310
+ <div class="controls">
311
+ <div class="row">
312
+ <span>Shell</span>
313
+ <div class="mode" role="group" aria-label="Shell finish">
314
+ <button type="button" id="solid-shell" aria-pressed="true">Solid</button>
315
+ <button type="button" id="gradient-shell" aria-pressed="false">Gradient</button>
316
+ </div>
317
+ <div class="stops" id="stops-shell" hidden></div>
318
+ </div>
319
+ <div class="row">
320
+ <span></span>
321
+ <div class="swatches" id="sw-shell"></div>
322
+ <label class="pick" title="Any colour"><input type="color" id="pick-shell" aria-label="Shell: any colour" /></label>
323
+ <button class="icon-button" type="button" id="dropper-shell" title="Pick a colour from the screen" aria-label="Shell: pick a colour from the screen" hidden>
324
+ <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m2 22 1-1h3l9-9"/><path d="M3 21v-3l9-9"/><path d="m15 6 3.4-3.4a2.1 2.1 0 1 1 3 3L18 9l.4.4a2.1 2.1 0 1 1-3 3l-3.8-3.8a2.1 2.1 0 1 1 3-3l.4.4Z"/></svg>
325
+ </button>
326
+ </div>
327
+ <div class="row">
328
+ <span>Button</span>
329
+ <div class="mode" role="group" aria-label="Button finish">
330
+ <button type="button" id="solid-button" aria-pressed="true">Solid</button>
331
+ <button type="button" id="gradient-button" aria-pressed="false">Gradient</button>
332
+ </div>
333
+ <div class="stops" id="stops-button" hidden></div>
334
+ </div>
335
+ <div class="row">
336
+ <span></span>
337
+ <div class="swatches" id="sw-button"></div>
338
+ <label class="pick" title="Any colour"><input type="color" id="pick-button" aria-label="Button: any colour" /></label>
339
+ <button class="icon-button" type="button" id="dropper-button" title="Pick a colour from the screen" aria-label="Button: pick a colour from the screen" hidden>
340
+ <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m2 22 1-1h3l9-9"/><path d="M3 21v-3l9-9"/><path d="m15 6 3.4-3.4a2.1 2.1 0 1 1 3 3L18 9l.4.4a2.1 2.1 0 1 1-3 3l-3.8-3.8a2.1 2.1 0 1 1 3-3l.4.4Z"/></svg>
341
+ </button>
342
+ </div>
343
+ </div>
344
+ <div class="foot">
345
+ <div class="price" id="price"></div>
346
+ <button class="action" id="customize" type="button">Customize</button>
347
+ <button class="action primary" id="order" type="button">Order</button>
348
+ </div>
349
+ </div>
350
+
351
+ <div class="view" id="view-order" hidden>
352
+ <form id="order-form" novalidate>
353
+ <h2 class="wide">Where should we send it?</h2>
354
+ <p class="note wide" id="order-summary"></p>
355
+ <label class="wide">Full name<input name="name" autocomplete="name" maxlength="200" /></label>
356
+ <label class="wide">Email<input name="email" type="email" autocomplete="email" maxlength="200" /></label>
357
+ <label class="wide">Address<input name="address" autocomplete="street-address" maxlength="200" /></label>
358
+ <label>City<input name="city" autocomplete="address-level2" maxlength="200" /></label>
359
+ <label>Postcode / ZIP<input name="postcode" autocomplete="postal-code" maxlength="40" /></label>
360
+ <label class="wide">Country<input name="country" autocomplete="country-name" maxlength="100" /></label>
361
+ </form>
362
+ <div class="foot">
363
+ <div class="price">Test mode — nothing is charged.</div>
364
+ <button class="action" id="order-back" type="button">Back</button>
365
+ <button class="action primary" id="order-place" type="button">Place test order</button>
366
+ </div>
367
+ </div>
368
+
369
+ <div class="view" id="view-done" hidden>
370
+ <div class="done">
371
+ <h2>Order received</h2>
372
+ <p id="done-text"></p>
373
+ </div>
374
+ <div class="foot">
375
+ <div class="price"></div>
376
+ <button class="action" id="done-back" type="button">Back to design</button>
377
+ </div>
378
+ </div>
379
+
380
+ <div class="status" id="status">Connecting&hellip;</div>
381
+ </div>
382
+ <script type="module" src="./main.ts"></script>
383
+ </body>
384
+ </html>