mockpit 2.0.0 → 3.0.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 +60 -212
- package/bin/demoData.js +2 -2
- package/bin/initDesign.js +6 -6
- package/bin/mockpit.js +407 -187
- package/bin/poster.js +377 -0
- package/dist/mcp/server.js +80 -24
- package/dist/server/answerAdditions.d.ts +7 -0
- package/dist/server/answerAdditions.js +76 -0
- package/dist/server/apiViews.d.ts +289 -52
- package/dist/server/apiViews.js +189 -81
- package/dist/server/app.d.ts +7 -2
- package/dist/server/app.js +2845 -832
- package/dist/server/appIcon.js +15 -5
- package/dist/server/appIconPng.js +3 -3
- package/dist/server/askNudges.js +1 -1
- package/dist/server/cdnInline.d.ts +8 -0
- package/dist/server/cdnInline.js +638 -0
- package/dist/server/codeWorker.js +1 -1
- package/dist/server/decisionList.d.ts +45 -0
- package/dist/server/decisionList.js +302 -0
- package/dist/server/decisions.d.ts +4 -4
- package/dist/server/decisions.js +9 -9
- package/dist/server/designGuide.d.ts +5 -5
- package/dist/server/designGuide.js +128 -67
- package/dist/server/embeds.d.ts +3 -1
- package/dist/server/embeds.js +31 -6
- package/dist/server/eventLog.d.ts +2 -2
- package/dist/server/eventLog.js +11 -11
- package/dist/server/events.d.ts +9 -6
- package/dist/server/feedbackBatch.d.ts +35 -13
- package/dist/server/feedbackBatch.js +111 -47
- package/dist/server/fills.d.ts +4 -0
- package/dist/server/fills.js +55 -1
- package/dist/server/handoff.d.ts +4 -1
- package/dist/server/handoff.js +8 -2
- package/dist/server/icons.js +1 -1
- package/dist/server/index.js +39 -10
- package/dist/server/jump.d.ts +21 -0
- package/dist/server/jump.js +175 -0
- package/dist/server/knobs.d.ts +3 -3
- package/dist/server/knobs.js +6 -6
- package/dist/server/mcpHttp.d.ts +5 -1
- package/dist/server/mcpHttp.js +16 -11
- package/dist/server/mcpSpec.js +118 -46
- package/dist/server/parts.js +9 -1
- package/dist/server/run.d.ts +1 -1
- package/dist/server/run.js +25 -16
- package/dist/server/runApi.d.ts +3 -3
- package/dist/server/runApi.js +25 -19
- package/dist/server/sqlStore.d.ts +112 -96
- package/dist/server/sqlStore.js +906 -172
- package/dist/server/standings.d.ts +25 -0
- package/dist/server/standings.js +113 -0
- package/dist/server/surfacePage.d.ts +6 -9
- package/dist/server/surfacePage.js +361 -86
- package/dist/server/types.d.ts +226 -72
- package/dist/server/types.js +73 -78
- package/extensions/mockpit.js +89 -72
- package/guide/AGENT_SETUP.md +13 -1
- package/guide/packs/design-craft.md +2 -3
- package/guide/topics/asks.md +106 -62
- package/guide/topics/feedback.md +126 -119
- package/guide/topics/html.md +17 -24
- package/guide/topics/http.md +41 -41
- package/guide/topics/knobs.md +7 -10
- package/guide/topics/scripts.md +22 -33
- package/guide/topics/surfaces.md +39 -48
- package/opencode/mockpit.js +120 -0
- package/opencode/package.json +8 -0
- package/package.json +4 -3
- package/skills/launch-video/SKILL.md +11 -11
- package/skills/mockpit/SKILL.md +26 -22
- package/viewer/dist/index.html +9 -9
package/dist/server/mcpSpec.js
CHANGED
|
@@ -4,24 +4,23 @@ import { GUIDE_TOPICS } from "./designGuide.js";
|
|
|
4
4
|
import { feedbackImages } from "./feedbackBatch.js";
|
|
5
5
|
import { KIT_IDS } from "./kits.js";
|
|
6
6
|
import { RUN_DESCRIPTION } from "./runApi.js";
|
|
7
|
-
import { SURFACE_KINDS } from "./types.js";
|
|
7
|
+
import { MAX_NOTES, SURFACE_KINDS } from "./types.js";
|
|
8
8
|
export const MCP_SERVER_INFO = { name: "mockpit", version: "0.2.0" };
|
|
9
9
|
// The `kind` enum both MCP transports advertise — derived from the one canonical
|
|
10
10
|
// list (types.ts) so the MCP tier can never fall behind what REST/CLI accept.
|
|
11
11
|
const SURFACE_KIND_ENUM = [...SURFACE_KINDS];
|
|
12
|
-
export const MCP_INSTRUCTIONS = "Mockpit shows design work to the user: project >
|
|
12
|
+
export const MCP_INSTRUCTIONS = "Mockpit shows design work to the user: project > design > state > variant > version. publish " +
|
|
13
13
|
"each variant (name states in the user's words; declare parts with data-part); publish again to " +
|
|
14
14
|
"revise. A choice is several variants plus one question that binds them (ask). One render plus a " +
|
|
15
15
|
"control: declare a knob. Say where to look, then end your turn; nothing waits. When the " +
|
|
16
16
|
"user says they answered, call feedback. Writes return feedback too; it is delivered once. " +
|
|
17
17
|
"Fetch guide before html.";
|
|
18
18
|
const field = {
|
|
19
|
-
|
|
19
|
+
design: "Design slug, title or id",
|
|
20
20
|
state: "State label; omit if single-state",
|
|
21
|
-
situation: 'Another render of this state ("Couldn\'t save", "Loading"); publish the state itself first',
|
|
22
21
|
variant: 'Variant label; default "default"',
|
|
23
22
|
project: "Default: the session's project",
|
|
24
|
-
title: "
|
|
23
|
+
title: "Design title",
|
|
25
24
|
session: "Session id from an earlier publish",
|
|
26
25
|
sessionTitle: "Task name; used only when creating the session",
|
|
27
26
|
agent: "Agent name for a new session",
|
|
@@ -29,11 +28,13 @@ const field = {
|
|
|
29
28
|
knobs: 'Global knobs (tunekit usePane shape) keyed by path: [default,min,max,step], boolean, {type:"select",options}, "#hex"',
|
|
30
29
|
variantKnobs: "Knobs this variant alone has",
|
|
31
30
|
from: "Branch from this version",
|
|
31
|
+
amend: "Replace the latest version in place: a fix before the user acts on it",
|
|
32
|
+
withdraw: "Take the latest version back, before the user acts on it",
|
|
32
33
|
fork: 'Start this new variant from a sibling: "variant" or "variant@N"; alone it copies it, with parts/fills it edits the copy',
|
|
33
34
|
prompt: "What prompted this version; usually omit",
|
|
34
|
-
|
|
35
|
+
description: "What this variant is; shown beside it, never in the html",
|
|
35
36
|
widths: "Widths it ships at, side by side: [390,1280]",
|
|
36
|
-
kind: "Only when building the product's screens:
|
|
37
|
+
kind: "Only when building the product's screens: screen, component (a reusable design screens embed). With no html it plans one, drawn by a later publish. Omit for ideas, diagrams and reviews",
|
|
37
38
|
area: 'Product area in the project\'s words; Home groups by it. "" clears',
|
|
38
39
|
source: 'Code path implementing it. "" clears',
|
|
39
40
|
flow: "Flow this state is a step of; alone, only places it",
|
|
@@ -82,8 +83,12 @@ export const mcpSurfaceSchema = z
|
|
|
82
83
|
const askSchema = z.object({
|
|
83
84
|
id: z.string().optional().describe("Stable id; reusing one replaces that question"),
|
|
84
85
|
text: z.string().describe("The question, one sentence"),
|
|
85
|
-
scope: z.enum(["
|
|
86
|
+
scope: z.enum(["design", "state", "part"]).optional(),
|
|
86
87
|
state: z.string().optional(),
|
|
88
|
+
states: z
|
|
89
|
+
.array(z.string())
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("Design-wide question: the states its answers can be seen on, best first"),
|
|
87
92
|
part: z.string().optional().describe("data-part name, for scope part"),
|
|
88
93
|
multi: z.boolean().optional(),
|
|
89
94
|
after: z
|
|
@@ -96,6 +101,9 @@ const askSchema = z.object({
|
|
|
96
101
|
variant: z.string().optional().describe("Bind to a published variant"),
|
|
97
102
|
version: z.number().int().optional().describe("And one version of it"),
|
|
98
103
|
set: z.record(z.string(), z.unknown()).optional().describe("Or to knob values {path: value}"),
|
|
104
|
+
style: z.string().optional().describe("With a set of a new value: its CSS"),
|
|
105
|
+
fill: z.string().optional().describe("With a set of {part: new fill}: its html"),
|
|
106
|
+
html: z.string().optional().describe("Or a whole page"),
|
|
99
107
|
recommended: z.boolean().optional().describe("The one you would pick; one per ask"),
|
|
100
108
|
})),
|
|
101
109
|
});
|
|
@@ -131,22 +139,21 @@ function zodParam(p) {
|
|
|
131
139
|
return p.req ? s : s.optional();
|
|
132
140
|
}
|
|
133
141
|
const P = {
|
|
134
|
-
|
|
142
|
+
design: { t: "string", d: field.design, req: true },
|
|
135
143
|
state: { t: "string", d: field.state },
|
|
136
144
|
variant: { t: "string", d: field.variant },
|
|
137
145
|
project: { t: "string", d: field.project },
|
|
138
146
|
session: { t: "string", d: field.session },
|
|
139
147
|
};
|
|
140
|
-
const VARIANT_REF = {
|
|
148
|
+
const VARIANT_REF = { design: P.design, state: P.state, variant: P.variant, project: P.project };
|
|
141
149
|
export const MCP_TOOL_DEFS = [
|
|
142
150
|
{
|
|
143
151
|
name: "publish",
|
|
144
|
-
description: "Create or version one variant of one state of a
|
|
152
|
+
description: "Create or version one variant of one state of a design. One of html, surfaces (the full ordered list), parts or fills (splice the latest version); or kind screen or component and none of them, to plan it. Returns parts per state, nudges, feedback.",
|
|
145
153
|
params: {
|
|
146
154
|
...VARIANT_REF,
|
|
147
|
-
situation: { t: "string", d: field.situation },
|
|
148
155
|
title: { t: "string", d: field.title },
|
|
149
|
-
kind: { t: "enum", values: ["
|
|
156
|
+
kind: { t: "enum", values: ["screen", "component", "idea"], d: field.kind },
|
|
150
157
|
area: { t: "string", d: field.area },
|
|
151
158
|
source: { t: "string", d: field.source },
|
|
152
159
|
html: { t: "string", d: field.html },
|
|
@@ -164,8 +171,10 @@ export const MCP_TOOL_DEFS = [
|
|
|
164
171
|
variantKnobs: { t: "object", d: field.variantKnobs },
|
|
165
172
|
from: { t: "integer", d: field.from },
|
|
166
173
|
fork: { t: "string", d: field.fork },
|
|
174
|
+
amend: { t: "boolean", d: field.amend },
|
|
175
|
+
withdraw: { t: "boolean", d: field.withdraw },
|
|
167
176
|
prompt: { t: "string", d: field.prompt },
|
|
168
|
-
|
|
177
|
+
description: { t: "string", d: field.description },
|
|
169
178
|
widths: { t: "integers", d: field.widths },
|
|
170
179
|
flow: { t: "string", d: field.flow },
|
|
171
180
|
step: { t: "integer", d: field.step },
|
|
@@ -177,9 +186,9 @@ export const MCP_TOOL_DEFS = [
|
|
|
177
186
|
},
|
|
178
187
|
{
|
|
179
188
|
name: "ask",
|
|
180
|
-
description: "Ask structured questions on a
|
|
189
|
+
description: "Ask structured questions on a design. A choice is several variants plus one question that binds them; options may also set knob values. Reusing a question id replaces it.",
|
|
181
190
|
params: {
|
|
182
|
-
|
|
191
|
+
design: P.design,
|
|
183
192
|
project: P.project,
|
|
184
193
|
asks: { t: "asks", d: "Questions with options", req: true },
|
|
185
194
|
session: P.session,
|
|
@@ -188,21 +197,25 @@ export const MCP_TOOL_DEFS = [
|
|
|
188
197
|
},
|
|
189
198
|
{
|
|
190
199
|
name: "read",
|
|
191
|
-
description: "Without
|
|
200
|
+
description: "Read this first. Both forms carry decisionList: what the user already settled in this project; it stands unless they say otherwise. Without design: every design (slug, states, variants, open questions), flows, plus pending. With design: its notes (a spec; the user's edits stand), states, variants, questions with answers, parts, knobs and settled values (every answer's set and tuning sent so far), plus pending. Never consumes feedback.",
|
|
192
201
|
params: {
|
|
193
|
-
|
|
202
|
+
design: { t: "string", d: field.design },
|
|
194
203
|
project: P.project,
|
|
195
204
|
body: { t: "boolean", d: "Include each variant's surfaces" },
|
|
196
205
|
history: { t: "boolean", d: "Include version rows" },
|
|
197
206
|
tree: {
|
|
198
207
|
t: "boolean",
|
|
199
|
-
d: "Without
|
|
208
|
+
d: "Without design: the project as a tree (each design's variants with status and forkedFrom, the designs it embeds and is embedded in)",
|
|
209
|
+
},
|
|
210
|
+
decisions: {
|
|
211
|
+
t: "boolean",
|
|
212
|
+
d: "Only the whole decision list, when decisionList says it was cut",
|
|
200
213
|
},
|
|
201
214
|
},
|
|
202
215
|
},
|
|
203
216
|
{
|
|
204
217
|
name: "feedback",
|
|
205
|
-
description: "Returns at once: what the user sent since you last heard (one batch per
|
|
218
|
+
description: "Returns at once: what the user sent since you last heard (one batch per design: reply answers, borrowed parts, tuned knobs, part comments, verdict; thread messages; inUse/archived), delivered once, plus pending (viewer open, draft progress).",
|
|
206
219
|
params: {
|
|
207
220
|
project: P.project,
|
|
208
221
|
session: { t: "string", d: "Session id returned by publish", req: true },
|
|
@@ -211,7 +224,7 @@ export const MCP_TOOL_DEFS = [
|
|
|
211
224
|
},
|
|
212
225
|
{
|
|
213
226
|
name: "say",
|
|
214
|
-
description: "Post a short plain-text message in a
|
|
227
|
+
description: "Post a short plain-text message in a design's thread. Returns feedback.",
|
|
215
228
|
params: {
|
|
216
229
|
...VARIANT_REF,
|
|
217
230
|
message: { t: "string", d: "Plain text", req: true },
|
|
@@ -219,6 +232,24 @@ export const MCP_TOOL_DEFS = [
|
|
|
219
232
|
},
|
|
220
233
|
httpOnly: ["session"],
|
|
221
234
|
},
|
|
235
|
+
{
|
|
236
|
+
name: "move",
|
|
237
|
+
description: "Tidy up variants: to or toState moves them there and out of here; archive takes them out of view; restore brings them back; a group goes along; one in use stays. Only when the user asks: hideOlder hides each variant's older versions (variants optional: all of them), and deleteOlder deletes them for good, skipping any that a reply, comment, question, embed, copy or fork still points at, or that is in use. Confirm a delete first: it can't be undone. Returns feedback.",
|
|
238
|
+
params: {
|
|
239
|
+
design: P.design,
|
|
240
|
+
state: P.state,
|
|
241
|
+
variants: { t: "strings", d: "Variant names; required except with hideOlder or deleteOlder" },
|
|
242
|
+
to: { t: "string", d: "Target design; default this one" },
|
|
243
|
+
toState: { t: "string", d: "Target state; created if new" },
|
|
244
|
+
archive: { t: "boolean" },
|
|
245
|
+
restore: { t: "boolean" },
|
|
246
|
+
hideOlder: { t: "boolean", d: "Hide all but each variant's newest visible version" },
|
|
247
|
+
deleteOlder: { t: "boolean", d: "Delete them instead; permanent, skips what's referenced" },
|
|
248
|
+
project: P.project,
|
|
249
|
+
session: P.session,
|
|
250
|
+
},
|
|
251
|
+
httpOnly: ["session"],
|
|
252
|
+
},
|
|
222
253
|
{
|
|
223
254
|
name: "export",
|
|
224
255
|
description: "The html in use (else current) per state, version history, screenshot URL, knobs, the settled values and the last reply.",
|
|
@@ -226,17 +257,22 @@ export const MCP_TOOL_DEFS = [
|
|
|
226
257
|
},
|
|
227
258
|
{
|
|
228
259
|
name: "edit",
|
|
229
|
-
description: "Change a
|
|
260
|
+
description: "Change a design without a new version; a new slug rewrites every reference to the old one. Without design, rename renames the project and areaOrder orders its areas. Returns feedback.",
|
|
230
261
|
params: {
|
|
231
|
-
|
|
262
|
+
design: { t: "string", d: field.design },
|
|
232
263
|
project: P.project,
|
|
233
264
|
slug: { t: "string" },
|
|
234
265
|
title: { t: "string" },
|
|
235
|
-
kind: { t: "enum", values: ["
|
|
266
|
+
kind: { t: "enum", values: ["screen", "component", "idea"] },
|
|
236
267
|
area: { t: "string" },
|
|
237
268
|
source: { t: "string" },
|
|
269
|
+
firstState: { t: "string", d: "Names a design's one unnamed state, so it can have more" },
|
|
270
|
+
notes: {
|
|
271
|
+
t: "string",
|
|
272
|
+
d: `A short plain-text spec, at most ${MAX_NOTES} characters; "" clears. Not applied over the user's own edit, and the result says so`,
|
|
273
|
+
},
|
|
238
274
|
rename: { t: "string", d: "New project name" },
|
|
239
|
-
areaOrder: { t: "strings", d: "Without
|
|
275
|
+
areaOrder: { t: "strings", d: "Without design: Home's area order" },
|
|
240
276
|
session: P.session,
|
|
241
277
|
},
|
|
242
278
|
httpOnly: ["session"],
|
|
@@ -294,14 +330,14 @@ const bag = z.record(z.string(), z.unknown());
|
|
|
294
330
|
const variantRef = o({ state: nullStr, variant: str });
|
|
295
331
|
const inUseRef = o({ state: nullStr, variant: str, version: num });
|
|
296
332
|
const partsByState = z.array(o({ state: nullStr, parts: z.array(o({ name: str })) }));
|
|
297
|
-
const flowSchema = o({ name: str, steps: z.array(o({
|
|
333
|
+
const flowSchema = o({ name: str, steps: z.array(o({ design: str, state: nullStr })) });
|
|
298
334
|
const feedbackSchema = o({
|
|
299
|
-
|
|
335
|
+
design: nullStr,
|
|
300
336
|
reply: o({
|
|
301
337
|
asks: z.array(o({
|
|
302
338
|
ask: str,
|
|
303
339
|
chosen: z.array(o({ label: str, other: z.literal(true).optional() })),
|
|
304
|
-
|
|
340
|
+
description: str.optional(),
|
|
305
341
|
})),
|
|
306
342
|
borrowed: bag.describe("Part → the variant to take it from"),
|
|
307
343
|
tuned: bag,
|
|
@@ -309,39 +345,47 @@ const feedbackSchema = o({
|
|
|
309
345
|
reactions: strs.optional().describe('One-tap reactions, e.g. 👍 part "hero" (variant B)'),
|
|
310
346
|
verdict: o({ kind: z.enum(["use", "revise"]), state: nullStr, variant: str })
|
|
311
347
|
.optional()
|
|
312
|
-
.describe("Use this or
|
|
348
|
+
.describe("Use this or New version on one variant"),
|
|
349
|
+
more: o({
|
|
350
|
+
ask: str,
|
|
351
|
+
text: str,
|
|
352
|
+
existing: strs,
|
|
353
|
+
note: str.optional(),
|
|
354
|
+
answered: z.array(o({ ask: str, text: str, chosen: strs, comment: str.optional() })),
|
|
355
|
+
})
|
|
356
|
+
.optional()
|
|
357
|
+
.describe("The user wants more answers to this question: ask it again, same id, with them"),
|
|
313
358
|
})
|
|
314
359
|
.nullable()
|
|
315
360
|
.describe("The user's Send; null if only messages"),
|
|
316
361
|
messages: z.array(o({ seq: num, text: str })).describe("What the user wrote in the thread"),
|
|
317
362
|
inUse: z.array(inUseRef).describe("In use, each at the version the user put in use"),
|
|
318
363
|
archived: z.array(variantRef),
|
|
319
|
-
|
|
320
|
-
.
|
|
321
|
-
.optional()
|
|
322
|
-
.
|
|
323
|
-
|
|
324
|
-
.array(o({ ask: str, text: str }))
|
|
325
|
-
.optional()
|
|
326
|
-
.describe("The questions the close dismissed; don't ask them again"),
|
|
327
|
-
edited: o({ kind: str.optional(), area: nullStr.optional() })
|
|
364
|
+
edited: o({
|
|
365
|
+
kind: str.optional(),
|
|
366
|
+
area: nullStr.optional(),
|
|
367
|
+
notes: nullStr.optional(),
|
|
368
|
+
})
|
|
328
369
|
.optional()
|
|
329
|
-
.describe("The user moved the
|
|
370
|
+
.describe("The user moved the design from Home (its kind or area now) or edited its notes"),
|
|
330
371
|
}).meta({ id: "Feedback" });
|
|
331
372
|
// Typed once, on feedback: repeating it on every write would cost more than
|
|
332
373
|
// the rest of the write's output schema.
|
|
333
374
|
const feedbackRide = z.array(bag).describe("Feedback batches as the feedback tool returns");
|
|
334
375
|
const pendingSchema = o({
|
|
335
|
-
|
|
336
|
-
|
|
376
|
+
design: str,
|
|
377
|
+
archived: z.literal(true).optional(),
|
|
337
378
|
viewerOpen: z.boolean(),
|
|
338
379
|
draft: o({ answered: num, of: num, comments: num, touchedAt: str }).nullable(),
|
|
339
380
|
}).meta({ id: "Pending" });
|
|
340
381
|
const writeOutput = {
|
|
341
|
-
post: o({ state: nullStr, variant: str, version: num })
|
|
382
|
+
post: o({ state: nullStr, variant: str, version: num })
|
|
383
|
+
.optional()
|
|
384
|
+
.describe("Absent when planned: nothing is drawn yet"),
|
|
385
|
+
planned: z.boolean().optional(),
|
|
342
386
|
sessionId: str,
|
|
343
387
|
url: str.describe("Viewer link for the user"),
|
|
344
|
-
parts: partsByState,
|
|
388
|
+
parts: partsByState.optional(),
|
|
345
389
|
partChanges: o({ vanished: strs, renamed: z.array(o({ from: str, to: str })) }).optional(),
|
|
346
390
|
nudges: strs.optional(),
|
|
347
391
|
suggestedAsk: bag.optional().describe("Send with ask when a nudge names it"),
|
|
@@ -378,10 +422,25 @@ const OUTPUT_SCHEMAS = {
|
|
|
378
422
|
feedback: feedbackRide,
|
|
379
423
|
},
|
|
380
424
|
feedback: {
|
|
381
|
-
feedback: z.array(feedbackSchema).describe("One batch per
|
|
425
|
+
feedback: z.array(feedbackSchema).describe("One batch per design; empty when nothing is new"),
|
|
382
426
|
pending: z.array(pendingSchema),
|
|
383
427
|
},
|
|
384
428
|
say: { feedback: feedbackRide },
|
|
429
|
+
move: {
|
|
430
|
+
variants: z.array(o({ state: str.nullable(), variant: str, status: str })),
|
|
431
|
+
versions: z
|
|
432
|
+
.array(o({ state: str.nullable(), variant: str, version: num }))
|
|
433
|
+
.optional()
|
|
434
|
+
.describe("With hideOlder or deleteOlder: what was hidden or deleted"),
|
|
435
|
+
skipped: z.array(o({ state: str.nullable().optional(), variant: str, version: num.optional(), why: str })),
|
|
436
|
+
to: o({ design: str, title: str, state: str.nullable() }).optional(),
|
|
437
|
+
saved: z
|
|
438
|
+
.array(o({ state: str.nullable(), variant: str, already: z.boolean() }))
|
|
439
|
+
.optional()
|
|
440
|
+
.describe("What each is called there"),
|
|
441
|
+
url: str,
|
|
442
|
+
feedback: feedbackRide,
|
|
443
|
+
},
|
|
385
444
|
edit: {
|
|
386
445
|
renamed: o({ from: str, to: str }).optional(),
|
|
387
446
|
areaOrder: strs.optional(),
|
|
@@ -390,7 +449,7 @@ const OUTPUT_SCHEMAS = {
|
|
|
390
449
|
feedback: feedbackRide,
|
|
391
450
|
},
|
|
392
451
|
read: {
|
|
393
|
-
|
|
452
|
+
designs: z.array(o({ slug: str, states: strs, variants: num, open: num })).optional(),
|
|
394
453
|
states: strs.optional(),
|
|
395
454
|
asks: z.array(o({ id: str, text: str })).optional(),
|
|
396
455
|
variants: z
|
|
@@ -414,7 +473,20 @@ const OUTPUT_SCHEMAS = {
|
|
|
414
473
|
settled: bag.optional().describe("Every knob value the user has sent, later replies winning"),
|
|
415
474
|
tree: z.array(bag).optional(),
|
|
416
475
|
flows: z.array(flowSchema).optional(),
|
|
417
|
-
|
|
476
|
+
decisionList: o({
|
|
477
|
+
total: num,
|
|
478
|
+
lines: z.array(o({
|
|
479
|
+
design: str,
|
|
480
|
+
text: str,
|
|
481
|
+
here: z.boolean().optional().describe("From the design read"),
|
|
482
|
+
edited: z.boolean().optional(),
|
|
483
|
+
archived: z.boolean().optional(),
|
|
484
|
+
})),
|
|
485
|
+
more: str.optional(),
|
|
486
|
+
}).optional(),
|
|
487
|
+
total: num.optional(),
|
|
488
|
+
lines: z.array(bag).optional(),
|
|
489
|
+
pending: z.union([z.array(pendingSchema), pendingSchema]).optional(),
|
|
418
490
|
},
|
|
419
491
|
export: {
|
|
420
492
|
states: z.array(o({
|
package/dist/server/parts.js
CHANGED
|
@@ -48,8 +48,16 @@ export function partsInHtml(html) {
|
|
|
48
48
|
}
|
|
49
49
|
return [...byName.values()];
|
|
50
50
|
}
|
|
51
|
+
// Cached per surfaces array, which a stored post never mutates: a design's view
|
|
52
|
+
// asks for every variant's parts on every read. Callers must not change the result.
|
|
53
|
+
const bySurfaces = new WeakMap();
|
|
51
54
|
export function partsInSurfaces(surfaces) {
|
|
52
|
-
|
|
55
|
+
const hit = bySurfaces.get(surfaces);
|
|
56
|
+
if (hit)
|
|
57
|
+
return hit;
|
|
58
|
+
const parts = mergeParts(surfaces.flatMap((s) => (s.kind === "html" ? [partsInHtml(s.html)] : [])));
|
|
59
|
+
bySurfaces.set(surfaces, parts);
|
|
60
|
+
return parts;
|
|
53
61
|
}
|
|
54
62
|
// Union several part lists by name, first occurrence first.
|
|
55
63
|
export function mergeParts(lists) {
|
package/dist/server/run.d.ts
CHANGED
|
@@ -77,6 +77,6 @@ export interface RunDeps {
|
|
|
77
77
|
status: number;
|
|
78
78
|
}>;
|
|
79
79
|
}
|
|
80
|
-
export declare const RUN_UNAVAILABLE = "run is not available on this deployment: it has no script sandbox. Use the
|
|
80
|
+
export declare const RUN_UNAVAILABLE = "run is not available on this deployment: it has no script sandbox. Use the design tools (or REST) instead";
|
|
81
81
|
export declare function cut(s: string, max: number): string;
|
|
82
82
|
export declare function createRunFlow(deps: RunDeps): (body: any, ctx: FlowContext) => Promise<FlowResult>;
|
package/dist/server/run.js
CHANGED
|
@@ -26,7 +26,7 @@ export const DEFAULT_RUN_LIMITS = {
|
|
|
26
26
|
maxInflight: 4,
|
|
27
27
|
maxRuns: 4,
|
|
28
28
|
};
|
|
29
|
-
export const RUN_UNAVAILABLE = "run is not available on this deployment: it has no script sandbox. Use the
|
|
29
|
+
export const RUN_UNAVAILABLE = "run is not available on this deployment: it has no script sandbox. Use the design tools (or REST) instead";
|
|
30
30
|
const text = (v, max) => typeof v === "string" && v.trim() ? v.trim().slice(0, max) : undefined;
|
|
31
31
|
const plain = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
32
32
|
// Keep the head and the tail: the start says what a value is, the end is where
|
|
@@ -40,7 +40,7 @@ export function cut(s, max) {
|
|
|
40
40
|
}
|
|
41
41
|
function writeSummary(body) {
|
|
42
42
|
const post = body?.post;
|
|
43
|
-
const slug = body?.
|
|
43
|
+
const slug = body?.design?.slug ?? "?";
|
|
44
44
|
if (!post)
|
|
45
45
|
return slug;
|
|
46
46
|
return `${slug}/${post.state ? `${post.state}/` : ""}${post.variant} v${post.version}`;
|
|
@@ -152,26 +152,30 @@ export function createRunFlow(deps) {
|
|
|
152
152
|
throw new Error(unknownTopicMessage(String(topic)));
|
|
153
153
|
return [await deps.guide("", topic), String(topic)];
|
|
154
154
|
},
|
|
155
|
-
async read([
|
|
156
|
-
if (
|
|
155
|
+
async read([design, opts]) {
|
|
156
|
+
if (plain(opts).decisions === true) {
|
|
157
|
+
const value = await take(flows.decisions({ project: await projectOf() }));
|
|
158
|
+
return [value, `${value.total} decisions`];
|
|
159
|
+
}
|
|
160
|
+
if ((design === undefined || design === null) && plain(opts).tree === true) {
|
|
157
161
|
const value = await take(flows.tree({ project: await projectOf() }));
|
|
158
|
-
return [value, `${value.tree.length}
|
|
162
|
+
return [value, `${value.tree.length} designs`];
|
|
159
163
|
}
|
|
160
|
-
if (
|
|
164
|
+
if (design === undefined || design === null) {
|
|
161
165
|
const value = await take(flows.list({ project: await projectOf() }));
|
|
162
|
-
return [value, `${value.
|
|
166
|
+
return [value, `${value.designs.length} designs`];
|
|
163
167
|
}
|
|
164
168
|
const o = plain(opts);
|
|
165
|
-
const value = await take(flows.get(
|
|
166
|
-
return [value, String(value?.slug ??
|
|
169
|
+
const value = await take(flows.get(design, { project, body: o.body === true, history: o.history === true }));
|
|
170
|
+
return [value, String(value?.slug ?? design)];
|
|
167
171
|
},
|
|
168
172
|
async publish([v]) {
|
|
169
173
|
const value = await take(flows.publish({ ...(await mine(plain(v))), agent }, flowCtx));
|
|
170
174
|
return [value, writeSummary(value)];
|
|
171
175
|
},
|
|
172
|
-
async ask([
|
|
173
|
-
const value = await take(flows.ask(
|
|
174
|
-
return [value, `${value.
|
|
176
|
+
async ask([design, asks]) {
|
|
177
|
+
const value = await take(flows.ask(design, await mine({ asks }), flowCtx));
|
|
178
|
+
return [value, `${value.design} ${value.asks.length} ask(s)`];
|
|
175
179
|
},
|
|
176
180
|
async feedback() {
|
|
177
181
|
const value = await take(flows.feedback(await mine({}), flowCtx));
|
|
@@ -180,8 +184,13 @@ export function createRunFlow(deps) {
|
|
|
180
184
|
},
|
|
181
185
|
async say([v, message]) {
|
|
182
186
|
const r = plain(v);
|
|
183
|
-
await take(flows.say(r.
|
|
184
|
-
return [null, String(r.
|
|
187
|
+
await take(flows.say(r.design, { ...(await mine(r)), message }, flowCtx));
|
|
188
|
+
return [null, String(r.design ?? "?")];
|
|
189
|
+
},
|
|
190
|
+
async move([v]) {
|
|
191
|
+
const r = plain(v);
|
|
192
|
+
const value = await take(flows.move(r.design, await mine(r), flowCtx));
|
|
193
|
+
return [value, `${value.variants.length} moved`];
|
|
185
194
|
},
|
|
186
195
|
async edit([v]) {
|
|
187
196
|
const r = await mine(plain(v));
|
|
@@ -206,8 +215,8 @@ export function createRunFlow(deps) {
|
|
|
206
215
|
},
|
|
207
216
|
async export([v]) {
|
|
208
217
|
const r = plain(v);
|
|
209
|
-
const value = await take(flows.
|
|
210
|
-
return [value, `${value.
|
|
218
|
+
const value = await take(flows.exportDesign(r.design, { ...r, project }, flowCtx));
|
|
219
|
+
return [value, `${value.design} ${value.states.length} state(s)`];
|
|
211
220
|
},
|
|
212
221
|
};
|
|
213
222
|
const call = (name, argsJson) => {
|
package/dist/server/runApi.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export declare const RUN_API = "// The body of an async function, in plain JavaScript (no TypeScript syntax).\n// `await mockpit.*`, `return` a JSON value, print() adds an output line.\n// No network, timers, imports or process. Session, project and agent are fixed\n// for the run; pass the returned session to the next run.\ntype Ref = {
|
|
2
|
-
export declare const RUN_FUNCTIONS: readonly ["guide", "read", "publish", "ask", "feedback", "say", "edit", "export", "upload"];
|
|
1
|
+
export declare const RUN_API = "// The body of an async function, in plain JavaScript (no TypeScript syntax).\n// `await mockpit.*`, `return` a JSON value, print() adds an output line.\n// No network, timers, imports or process. Session, project and agent are fixed\n// for the run; pass the returned session to the next run.\ntype Ref = { design: string; state?: string; variant?: string };\ntype Knobs = Record<string, unknown>; // path \u2192 [default,min,max,step] | boolean | {type:\"select\",options} | \"#hex\"\ntype Surface =\n | { kind: \"html\"; html: string; kits?: string[] }\n | { kind: \"markdown\"; markdown: string }\n | { kind: \"diff\"; patch?: string; files?: { filename: string; before: string; after: string }[]; layout?: \"unified\" | \"split\" }\n | { kind: \"image\"; assetId: string; alt?: string; caption?: string }\n | { kind: \"terminal\"; text: string; cols?: number; title?: string }\n | { kind: \"mermaid\"; mermaid: string }\n | { kind: \"json\"; data: unknown; title?: string }\n | { kind: \"code\"; code: string; language?: string; lineStart?: number };\ntype Ask = {\n id?: string; text: string; scope?: \"design\" | \"state\" | \"part\"; state?: string; part?: string; multi?: boolean;\n options: { id?: string; label: string; variant?: string; set?: Knobs; recommended?: boolean }[];\n};\ntype Written = {\n post?: { state: string | null; variant: string; version: number }; planned?: true; url: string;\n parts?: { state: string | null; parts: { name: string }[] }[];\n partChanges?: { vanished: string[]; renamed: { from: string; to: string }[] };\n nudges?: string[]; suggestedAsk?: Ask; suggestedFillAsks?: Ask[]; // send with ask when nudged\n};\ntype Feedback = {\n design: string | null;\n reply: null | { asks: { ask: string; chosen: { id: string; label: string; other?: true }[]; comment?: string }[]; borrowed: Record<string, string>; tuned: Knobs; comments: { part: string | null; text: string }[]; verdict?: { kind: \"use\" | \"revise\"; state: string | null; variant: string; version?: number } };\n messages: { text: string; state: string | null; variant: string | null }[];\n inUse: { state: string | null; variant: string; version: number }[]; archived: { state: string | null; variant: string }[];\n};\ntype Pending = { design: string; archived?: true; viewerOpen: boolean; draft: null | { answered: number; of: number; comments: number; touchedAt: string } };\ndeclare const mockpit: {\n guide(topic?: \"knobs\" | \"asks\" | \"surfaces\" | \"html\" | \"feedback\" | \"http\" | \"scripts\"): Promise<string>;\n /** Read first: decisionList is what the user already settled; it stands unless they say otherwise.\n * No design: every design, the flows, plus pending. With one: its states, variants, asks, parts, knobs.\n * opts.decisions: only the whole list, when a read cut it. */\n read(design?: string, opts?: { body?: boolean; history?: boolean; decisions?: boolean }): Promise<unknown>;\n /** Creates or versions (design, state, variant). One of: html; surfaces, the full ordered list\n * ({id} alone keeps that surface, a missing id is removed); parts {\"name\"|\"name#key\": outer html};\n * fills {\"part#fill\": outer html}, replacing or adding one fill of a part.\n * None of them, with kind screen or component: plans it, for a later publish to draw. */\n publish(v: Ref & { title?: string; kind?: \"screen\" | \"component\" | \"idea\"; area?: string; source?: string; html?: string; poster?: string; surfaces?: (Surface | { id: string })[]; parts?: Record<string, string>; fills?: Record<string, string>; knobs?: Knobs; variantKnobs?: Knobs; from?: number; prompt?: string; description?: string; widths?: number[]; flow?: string; step?: number }): Promise<Written>;\n ask(design: string, asks: Ask[]): Promise<{ asks: { id: string; text: string }[]; url: string; nudges?: string[] }>;\n /** Never waits: what the user sent since you last heard, and what they are doing now. */\n feedback(): Promise<{ feedback: Feedback[]; pending: Pending[] }>;\n say(v: Ref, message: string): Promise<void>;\n /** to or toState moves variants there; archive hides them; restore brings them back. */\n move(v: Ref & { variants: string[]; to?: string; toState?: string; archive?: boolean; restore?: boolean }): Promise<{ variants: { state: string | null; variant: string; status: string }[]; skipped: { variant: string; why: string }[]; url: string }>;\n /** No new version; \"\" clears. { rename } or { areaOrder } without design edits the project.\n * notes: a short plain-text spec; not applied over the user's own edit (notes.applied says). */\n edit(v: { design?: string; slug?: string; title?: string; kind?: string; area?: string; source?: string; firstState?: string; notes?: string; rename?: string; areaOrder?: string[] }): Promise<{ url: string; notes?: { applied: boolean; text?: string } }>;\n export(v: Ref): Promise<{ states: { state: string | null; variant: string; version: number; html: string; allFills?: string }[]; reply: unknown }>;\n /** data: base64 bytes. Use id as an image surface's assetId. */\n upload(data: string, opts: { contentType: string; filename?: string; kind?: \"image\" | \"file\" }): Promise<{ id: string; url: string }>;\n};\ndeclare function print(...values: unknown[]): void;";
|
|
2
|
+
export declare const RUN_FUNCTIONS: readonly ["guide", "read", "publish", "ask", "feedback", "say", "move", "edit", "export", "upload"];
|
|
3
3
|
export type RunFunction = (typeof RUN_FUNCTIONS)[number];
|
|
4
|
-
export declare const RUN_EXAMPLE = "const html = (tone) => `<section data-part=\"hero\" class=\"${tone}\"><h1>Writer</h1></section>`;\nawait mockpit.publish({
|
|
4
|
+
export declare const RUN_EXAMPLE = "const html = (tone) => `<section data-part=\"hero\" class=\"${tone}\"><h1>Writer</h1></section>`;\nawait mockpit.publish({ design: \"writer\", variant: \"calm\", html: html(\"calm\") });\nawait mockpit.publish({ design: \"writer\", variant: \"bold\", html: html(\"bold\") });\nconst { url } = await mockpit.ask(\"writer\", [{ id: \"tone\", text: \"Calm or bold?\",\n options: [{ label: \"Calm\", variant: \"calm\" }, { label: \"Bold\", variant: \"bold\" }] }]);\n// Tell the user where to look and end the turn; a later run reads mockpit.feedback().\nreturn url;";
|
|
5
5
|
export declare const RUN_DESCRIPTION: string;
|
|
6
6
|
export declare const RUN_INSTRUCTIONS: string;
|
package/dist/server/runApi.js
CHANGED
|
@@ -8,7 +8,7 @@ export const RUN_API = `// The body of an async function, in plain JavaScript (n
|
|
|
8
8
|
// \`await mockpit.*\`, \`return\` a JSON value, print() adds an output line.
|
|
9
9
|
// No network, timers, imports or process. Session, project and agent are fixed
|
|
10
10
|
// for the run; pass the returned session to the next run.
|
|
11
|
-
type Ref = {
|
|
11
|
+
type Ref = { design: string; state?: string; variant?: string };
|
|
12
12
|
type Knobs = Record<string, unknown>; // path → [default,min,max,step] | boolean | {type:"select",options} | "#hex"
|
|
13
13
|
type Surface =
|
|
14
14
|
| { kind: "html"; html: string; kits?: string[] }
|
|
@@ -20,37 +20,42 @@ type Surface =
|
|
|
20
20
|
| { kind: "json"; data: unknown; title?: string }
|
|
21
21
|
| { kind: "code"; code: string; language?: string; lineStart?: number };
|
|
22
22
|
type Ask = {
|
|
23
|
-
id?: string; text: string; scope?: "
|
|
23
|
+
id?: string; text: string; scope?: "design" | "state" | "part"; state?: string; part?: string; multi?: boolean;
|
|
24
24
|
options: { id?: string; label: string; variant?: string; set?: Knobs; recommended?: boolean }[];
|
|
25
25
|
};
|
|
26
26
|
type Written = {
|
|
27
|
-
post
|
|
28
|
-
parts
|
|
27
|
+
post?: { state: string | null; variant: string; version: number }; planned?: true; url: string;
|
|
28
|
+
parts?: { state: string | null; parts: { name: string }[] }[];
|
|
29
29
|
partChanges?: { vanished: string[]; renamed: { from: string; to: string }[] };
|
|
30
30
|
nudges?: string[]; suggestedAsk?: Ask; suggestedFillAsks?: Ask[]; // send with ask when nudged
|
|
31
31
|
};
|
|
32
32
|
type Feedback = {
|
|
33
|
-
|
|
34
|
-
reply: null | { asks: { ask: string; chosen: { id: string; label: string; other?: true }[];
|
|
33
|
+
design: string | null;
|
|
34
|
+
reply: null | { asks: { ask: string; chosen: { id: string; label: string; other?: true }[]; comment?: string }[]; borrowed: Record<string, string>; tuned: Knobs; comments: { part: string | null; text: string }[]; verdict?: { kind: "use" | "revise"; state: string | null; variant: string; version?: number } };
|
|
35
35
|
messages: { text: string; state: string | null; variant: string | null }[];
|
|
36
36
|
inUse: { state: string | null; variant: string; version: number }[]; archived: { state: string | null; variant: string }[];
|
|
37
|
-
closed?: true; dismissed?: { ask: string; text: string }[];
|
|
38
37
|
};
|
|
39
|
-
type Pending = {
|
|
38
|
+
type Pending = { design: string; archived?: true; viewerOpen: boolean; draft: null | { answered: number; of: number; comments: number; touchedAt: string } };
|
|
40
39
|
declare const mockpit: {
|
|
41
40
|
guide(topic?: "knobs" | "asks" | "surfaces" | "html" | "feedback" | "http" | "scripts"): Promise<string>;
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
/** Read first: decisionList is what the user already settled; it stands unless they say otherwise.
|
|
42
|
+
* No design: every design, the flows, plus pending. With one: its states, variants, asks, parts, knobs.
|
|
43
|
+
* opts.decisions: only the whole list, when a read cut it. */
|
|
44
|
+
read(design?: string, opts?: { body?: boolean; history?: boolean; decisions?: boolean }): Promise<unknown>;
|
|
45
|
+
/** Creates or versions (design, state, variant). One of: html; surfaces, the full ordered list
|
|
45
46
|
* ({id} alone keeps that surface, a missing id is removed); parts {"name"|"name#key": outer html};
|
|
46
|
-
* fills {"part#fill": outer html}, replacing or adding one fill of a part.
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
* fills {"part#fill": outer html}, replacing or adding one fill of a part.
|
|
48
|
+
* None of them, with kind screen or component: plans it, for a later publish to draw. */
|
|
49
|
+
publish(v: Ref & { title?: string; kind?: "screen" | "component" | "idea"; area?: string; source?: string; html?: string; poster?: string; surfaces?: (Surface | { id: string })[]; parts?: Record<string, string>; fills?: Record<string, string>; knobs?: Knobs; variantKnobs?: Knobs; from?: number; prompt?: string; description?: string; widths?: number[]; flow?: string; step?: number }): Promise<Written>;
|
|
50
|
+
ask(design: string, asks: Ask[]): Promise<{ asks: { id: string; text: string }[]; url: string; nudges?: string[] }>;
|
|
49
51
|
/** Never waits: what the user sent since you last heard, and what they are doing now. */
|
|
50
52
|
feedback(): Promise<{ feedback: Feedback[]; pending: Pending[] }>;
|
|
51
53
|
say(v: Ref, message: string): Promise<void>;
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
+
/** to or toState moves variants there; archive hides them; restore brings them back. */
|
|
55
|
+
move(v: Ref & { variants: string[]; to?: string; toState?: string; archive?: boolean; restore?: boolean }): Promise<{ variants: { state: string | null; variant: string; status: string }[]; skipped: { variant: string; why: string }[]; url: string }>;
|
|
56
|
+
/** No new version; "" clears. { rename } or { areaOrder } without design edits the project.
|
|
57
|
+
* notes: a short plain-text spec; not applied over the user's own edit (notes.applied says). */
|
|
58
|
+
edit(v: { design?: string; slug?: string; title?: string; kind?: string; area?: string; source?: string; firstState?: string; notes?: string; rename?: string; areaOrder?: string[] }): Promise<{ url: string; notes?: { applied: boolean; text?: string } }>;
|
|
54
59
|
export(v: Ref): Promise<{ states: { state: string | null; variant: string; version: number; html: string; allFills?: string }[]; reply: unknown }>;
|
|
55
60
|
/** data: base64 bytes. Use id as an image surface's assetId. */
|
|
56
61
|
upload(data: string, opts: { contentType: string; filename?: string; kind?: "image" | "file" }): Promise<{ id: string; url: string }>;
|
|
@@ -64,18 +69,19 @@ export const RUN_FUNCTIONS = [
|
|
|
64
69
|
"ask",
|
|
65
70
|
"feedback",
|
|
66
71
|
"say",
|
|
72
|
+
"move",
|
|
67
73
|
"edit",
|
|
68
74
|
"export",
|
|
69
75
|
"upload",
|
|
70
76
|
];
|
|
71
77
|
export const RUN_EXAMPLE = `const html = (tone) => \`<section data-part="hero" class="\${tone}"><h1>Writer</h1></section>\`;
|
|
72
|
-
await mockpit.publish({
|
|
73
|
-
await mockpit.publish({
|
|
78
|
+
await mockpit.publish({ design: "writer", variant: "calm", html: html("calm") });
|
|
79
|
+
await mockpit.publish({ design: "writer", variant: "bold", html: html("bold") });
|
|
74
80
|
const { url } = await mockpit.ask("writer", [{ id: "tone", text: "Calm or bold?",
|
|
75
81
|
options: [{ label: "Calm", variant: "calm" }, { label: "Bold", variant: "bold" }] }]);
|
|
76
82
|
// Tell the user where to look and end the turn; a later run reads mockpit.feedback().
|
|
77
83
|
return url;`;
|
|
78
|
-
export const RUN_DESCRIPTION = "Run a script against mockpit, which shows design work to the user (project >
|
|
84
|
+
export const RUN_DESCRIPTION = "Run a script against mockpit, which shows design work to the user (project > design > state > " +
|
|
79
85
|
"variant > version). Publish variants and ask in one call; nothing waits for the user. Returns " +
|
|
80
86
|
"value, prints, calls (every host call in order: which writes landed), error {kind, message, " +
|
|
81
87
|
"line} and feedback (every batch received, even if the script failed). Read mockpit.guide() " +
|