mockpit 1.3.0 → 2.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 +61 -45
- package/bin/demoData.js +3 -3
- package/bin/mockpit.js +327 -52
- package/dist/mcp/server.js +35 -0
- package/dist/server/apiViews.d.ts +308 -23
- package/dist/server/apiViews.js +60 -19
- package/dist/server/app.d.ts +4 -1
- package/dist/server/app.js +608 -119
- package/dist/server/codeWorker.js +2 -1
- package/dist/server/decisions.d.ts +5 -5
- package/dist/server/decisions.js +10 -9
- package/dist/server/designGuide.d.ts +6 -2
- package/dist/server/designGuide.js +77 -43
- package/dist/server/embeds.d.ts +1 -0
- package/dist/server/embeds.js +30 -4
- package/dist/server/feedbackBatch.d.ts +6 -6
- package/dist/server/feedbackBatch.js +18 -11
- package/dist/server/guidance.d.ts +23 -0
- package/dist/server/guidance.js +71 -0
- package/dist/server/index.js +10 -1
- package/dist/server/mcpHttp.d.ts +3 -0
- package/dist/server/mcpHttp.js +32 -0
- package/dist/server/mcpSpec.d.ts +2 -1
- package/dist/server/mcpSpec.js +74 -24
- package/dist/server/postSurfaces.js +3 -0
- package/dist/server/run.js +6 -1
- package/dist/server/runApi.d.ts +3 -3
- package/dist/server/runApi.js +16 -12
- package/dist/server/sqlStore.d.ts +10 -3
- package/dist/server/sqlStore.js +230 -16
- package/dist/server/types.d.ts +46 -17
- package/dist/server/types.js +34 -11
- package/extensions/mockpit.js +179 -16
- package/guide/packs/design-craft.md +35 -0
- package/guide/topics/asks.md +60 -56
- package/guide/topics/feedback.md +104 -74
- package/guide/topics/html.md +5 -11
- package/guide/topics/http.md +6 -3
- package/guide/topics/knobs.md +2 -2
- package/guide/topics/scripts.md +2 -2
- package/guide/topics/surfaces.md +28 -10
- package/package.json +1 -4
- package/skills/launch-video/SKILL.md +1 -1
- package/skills/mockpit/SKILL.md +18 -10
- package/viewer/dist/index.html +8 -7
package/dist/server/mcpHttp.js
CHANGED
|
@@ -11,6 +11,27 @@ export function unwrap(result) {
|
|
|
11
11
|
}
|
|
12
12
|
return result.body;
|
|
13
13
|
}
|
|
14
|
+
// `edit` is one verb with two shapes: `rename` or `areaOrder` edits the
|
|
15
|
+
// project, anything else edits the mock. Shared by both MCP transports' HTTP
|
|
16
|
+
// side and `run`.
|
|
17
|
+
export function editCall(flows, args, ctx, project) {
|
|
18
|
+
if (args.rename === undefined && args.areaOrder === undefined) {
|
|
19
|
+
return flows.edit(args.mock, args, ctx);
|
|
20
|
+
}
|
|
21
|
+
if (args.mock !== undefined) {
|
|
22
|
+
return Promise.resolve({
|
|
23
|
+
status: 400,
|
|
24
|
+
body: { error: '"rename" and "areaOrder" edit the project; edit the mock without them' },
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (!project) {
|
|
28
|
+
return Promise.resolve({
|
|
29
|
+
status: 400,
|
|
30
|
+
body: { error: 'provide "project": the project to edit' },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return flows.editProject(project, { name: args.rename, areaOrder: args.areaOrder, session: args.session }, ctx);
|
|
34
|
+
}
|
|
14
35
|
export function registerMcp(app, deps) {
|
|
15
36
|
const { flows, store } = deps;
|
|
16
37
|
async function projectOf(args) {
|
|
@@ -24,6 +45,15 @@ export function registerMcp(app, deps) {
|
|
|
24
45
|
const projects = await store.listProjects();
|
|
25
46
|
return projects[0]?.name ?? "workspace";
|
|
26
47
|
}
|
|
48
|
+
// Only a project the call names, or its session's: a rename never falls back
|
|
49
|
+
// to whichever project happens to be first.
|
|
50
|
+
async function namedProject(args) {
|
|
51
|
+
if (typeof args.project === "string" && args.project)
|
|
52
|
+
return args.project;
|
|
53
|
+
if (typeof args.session !== "string")
|
|
54
|
+
return undefined;
|
|
55
|
+
return (await store.getSession(args.session))?.project ?? undefined;
|
|
56
|
+
}
|
|
27
57
|
async function loadImage(ref) {
|
|
28
58
|
const asset = await store.getAsset(ref.id);
|
|
29
59
|
return asset ? { data: asset.data, mimeType: asset.contentType } : null;
|
|
@@ -51,6 +81,8 @@ export function registerMcp(app, deps) {
|
|
|
51
81
|
return unwrap(await flows.say(args.mock, args, ctx));
|
|
52
82
|
case "export":
|
|
53
83
|
return unwrap(await flows.exportMock(args.mock, args, ctx));
|
|
84
|
+
case "edit":
|
|
85
|
+
return unwrap(await editCall(flows, args, ctx, await namedProject(args)));
|
|
54
86
|
case "upload": {
|
|
55
87
|
if (typeof args.data !== "string" || args.data.length === 0) {
|
|
56
88
|
throw new Error("upload needs base64 `data`");
|
package/dist/server/mcpSpec.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare const mcpSurfaceSchema: z.ZodObject<{
|
|
|
19
19
|
}>>;
|
|
20
20
|
html: z.ZodOptional<z.ZodString>;
|
|
21
21
|
kits: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
22
|
+
poster: z.ZodOptional<z.ZodString>;
|
|
22
23
|
markdown: z.ZodOptional<z.ZodString>;
|
|
23
24
|
mermaid: z.ZodOptional<z.ZodString>;
|
|
24
25
|
patch: z.ZodOptional<z.ZodString>;
|
|
@@ -53,7 +54,7 @@ type Param = {
|
|
|
53
54
|
d?: string;
|
|
54
55
|
req?: boolean;
|
|
55
56
|
} | {
|
|
56
|
-
t: "surfaces" | "asks" | "object" | "integers";
|
|
57
|
+
t: "surfaces" | "asks" | "object" | "integers" | "strings";
|
|
57
58
|
d?: string;
|
|
58
59
|
req?: boolean;
|
|
59
60
|
};
|
package/dist/server/mcpSpec.js
CHANGED
|
@@ -10,14 +10,15 @@ export const MCP_SERVER_INFO = { name: "mockpit", version: "0.2.0" };
|
|
|
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
12
|
export const MCP_INSTRUCTIONS = "Mockpit shows design work to the user: project > mock > state > variant > version. publish " +
|
|
13
|
-
"each variant (name states in the user's words;
|
|
14
|
-
"revise. A choice is several variants plus one
|
|
13
|
+
"each variant (name states in the user's words; declare parts with data-part); publish again to " +
|
|
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
|
mock: "Mock slug 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',
|
|
21
22
|
variant: 'Variant label; default "default"',
|
|
22
23
|
project: "Default: the session's project",
|
|
23
24
|
title: "Mock title",
|
|
@@ -32,9 +33,13 @@ const field = {
|
|
|
32
33
|
prompt: "What prompted this version; usually omit",
|
|
33
34
|
note: "What this variant is; shown beside it, never in the html",
|
|
34
35
|
widths: "Widths it ships at, side by side: [390,1280]",
|
|
35
|
-
|
|
36
|
+
kind: "Only when building the product's screens: page (a screen), component (a piece pages embed). Omit for sketches, diagrams and reviews",
|
|
37
|
+
area: 'Product area in the project\'s words; Home groups by it. "" clears',
|
|
38
|
+
source: 'Code path implementing it. "" clears',
|
|
39
|
+
flow: "Flow this state is a step of; alone, only places it",
|
|
36
40
|
step: "1-based place; 0 removes",
|
|
37
41
|
kits: `Opt-in bundles: ${KIT_IDS.join("|")}`,
|
|
42
|
+
poster: "Still of the page (asset id) shown instead of running it; resend when the page changes",
|
|
38
43
|
};
|
|
39
44
|
const diffFileSchema = z.object({
|
|
40
45
|
filename: z.string(),
|
|
@@ -53,6 +58,7 @@ export const mcpSurfaceSchema = z
|
|
|
53
58
|
kind: z.enum(SURFACE_KIND_ENUM).optional(),
|
|
54
59
|
html: z.string().optional().describe(field.html),
|
|
55
60
|
kits: z.array(z.string()).optional().describe(field.kits),
|
|
61
|
+
poster: z.string().optional().describe(field.poster),
|
|
56
62
|
markdown: z.string().optional().describe("Prose; raw HTML is escaped"),
|
|
57
63
|
mermaid: z.string().optional().describe("Diagram source; do not set colors"),
|
|
58
64
|
patch: z.string().optional().describe("Preferred over files"),
|
|
@@ -74,7 +80,7 @@ export const mcpSurfaceSchema = z
|
|
|
74
80
|
description: "Set kind and its field: html, markdown, mermaid, patch|files (diff), assetId (image), text (terminal), data (json), code",
|
|
75
81
|
});
|
|
76
82
|
const askSchema = z.object({
|
|
77
|
-
id: z.string().optional().describe("Stable id; reusing one replaces that
|
|
83
|
+
id: z.string().optional().describe("Stable id; reusing one replaces that question"),
|
|
78
84
|
text: z.string().describe("The question, one sentence"),
|
|
79
85
|
scope: z.enum(["mock", "state", "part"]).optional(),
|
|
80
86
|
state: z.string().optional(),
|
|
@@ -90,6 +96,7 @@ const askSchema = z.object({
|
|
|
90
96
|
variant: z.string().optional().describe("Bind to a published variant"),
|
|
91
97
|
version: z.number().int().optional().describe("And one version of it"),
|
|
92
98
|
set: z.record(z.string(), z.unknown()).optional().describe("Or to knob values {path: value}"),
|
|
99
|
+
recommended: z.boolean().optional().describe("The one you would pick; one per ask"),
|
|
93
100
|
})),
|
|
94
101
|
});
|
|
95
102
|
function zodParam(p) {
|
|
@@ -113,6 +120,9 @@ function zodParam(p) {
|
|
|
113
120
|
case "integers":
|
|
114
121
|
s = z.array(z.number().int());
|
|
115
122
|
break;
|
|
123
|
+
case "strings":
|
|
124
|
+
s = z.array(z.string());
|
|
125
|
+
break;
|
|
116
126
|
default:
|
|
117
127
|
s = p.t === "string" ? z.string() : p.t === "number" ? z.number() : z.boolean();
|
|
118
128
|
}
|
|
@@ -134,17 +144,21 @@ export const MCP_TOOL_DEFS = [
|
|
|
134
144
|
description: "Create or version one variant of one state of a mock. One of html, surfaces (the full ordered list), parts or fills (splice the latest version). Returns parts per state, nudges, feedback.",
|
|
135
145
|
params: {
|
|
136
146
|
...VARIANT_REF,
|
|
147
|
+
situation: { t: "string", d: field.situation },
|
|
137
148
|
title: { t: "string", d: field.title },
|
|
138
|
-
kind: { t: "enum", values: ["component", "
|
|
149
|
+
kind: { t: "enum", values: ["page", "component", "sketch"], d: field.kind },
|
|
150
|
+
area: { t: "string", d: field.area },
|
|
151
|
+
source: { t: "string", d: field.source },
|
|
139
152
|
html: { t: "string", d: field.html },
|
|
153
|
+
poster: { t: "string", d: field.poster },
|
|
140
154
|
surfaces: {
|
|
141
155
|
t: "surfaces",
|
|
142
156
|
d: "Full ordered list: {id} keeps a surface, a missing id removes it",
|
|
143
157
|
},
|
|
144
|
-
parts: { t: "object", d: '{"name"|"name#key": outer html}; splices
|
|
158
|
+
parts: { t: "object", d: '{"name"|"name#key": outer html}; splices declared parts' },
|
|
145
159
|
fills: {
|
|
146
160
|
t: "object",
|
|
147
|
-
d: '{"part#fill": outer html}; replaces that fill of a
|
|
161
|
+
d: '{"part#fill": outer html}; replaces that fill of a part, or adds it after its others',
|
|
148
162
|
},
|
|
149
163
|
knobs: { t: "object", d: field.knobs },
|
|
150
164
|
variantKnobs: { t: "object", d: field.variantKnobs },
|
|
@@ -163,7 +177,7 @@ export const MCP_TOOL_DEFS = [
|
|
|
163
177
|
},
|
|
164
178
|
{
|
|
165
179
|
name: "ask",
|
|
166
|
-
description: "Ask structured questions on a mock. A choice is several variants plus one
|
|
180
|
+
description: "Ask structured questions on a mock. A choice is several variants plus one question that binds them; options may also set knob values. Reusing a question id replaces it.",
|
|
167
181
|
params: {
|
|
168
182
|
mock: P.mock,
|
|
169
183
|
project: P.project,
|
|
@@ -174,7 +188,7 @@ export const MCP_TOOL_DEFS = [
|
|
|
174
188
|
},
|
|
175
189
|
{
|
|
176
190
|
name: "read",
|
|
177
|
-
description: "Without mock: every mock (slug, states, variants, open
|
|
191
|
+
description: "Without mock: every mock (slug, states, variants, open questions), flows, plus pending. With mock: its states, variants, questions with answers, parts, knobs and settled values (every answer's set and tuning sent so far), plus pending. Never consumes feedback.",
|
|
178
192
|
params: {
|
|
179
193
|
mock: { t: "string", d: field.mock },
|
|
180
194
|
project: P.project,
|
|
@@ -182,13 +196,13 @@ export const MCP_TOOL_DEFS = [
|
|
|
182
196
|
history: { t: "boolean", d: "Include version rows" },
|
|
183
197
|
tree: {
|
|
184
198
|
t: "boolean",
|
|
185
|
-
d: "Without mock: the project as a tree (each mock's variants with status and forkedFrom, the mocks it
|
|
199
|
+
d: "Without mock: the project as a tree (each mock's variants with status and forkedFrom, the mocks it embeds and is embedded in)",
|
|
186
200
|
},
|
|
187
201
|
},
|
|
188
202
|
},
|
|
189
203
|
{
|
|
190
204
|
name: "feedback",
|
|
191
|
-
description: "Returns at once: what the user sent since you last heard (one batch per mock: reply answers,
|
|
205
|
+
description: "Returns at once: what the user sent since you last heard (one batch per mock: reply answers, borrowed parts, tuned knobs, part comments, verdict; thread messages; inUse/archived; or closed with the questions it dismissed), delivered once, plus pending (viewer open, draft progress).",
|
|
192
206
|
params: {
|
|
193
207
|
project: P.project,
|
|
194
208
|
session: { t: "string", d: "Session id returned by publish", req: true },
|
|
@@ -210,6 +224,23 @@ export const MCP_TOOL_DEFS = [
|
|
|
210
224
|
description: "The html in use (else current) per state, version history, screenshot URL, knobs, the settled values and the last reply.",
|
|
211
225
|
params: VARIANT_REF,
|
|
212
226
|
},
|
|
227
|
+
{
|
|
228
|
+
name: "edit",
|
|
229
|
+
description: "Change a mock without a new version; a new slug rewrites every reference to the old one. Without mock, rename renames the project and areaOrder orders its areas. Returns feedback.",
|
|
230
|
+
params: {
|
|
231
|
+
mock: { t: "string", d: field.mock },
|
|
232
|
+
project: P.project,
|
|
233
|
+
slug: { t: "string" },
|
|
234
|
+
title: { t: "string" },
|
|
235
|
+
kind: { t: "enum", values: ["page", "component", "sketch"] },
|
|
236
|
+
area: { t: "string" },
|
|
237
|
+
source: { t: "string" },
|
|
238
|
+
rename: { t: "string", d: "New project name" },
|
|
239
|
+
areaOrder: { t: "strings", d: "Without mock: Home's area order" },
|
|
240
|
+
session: P.session,
|
|
241
|
+
},
|
|
242
|
+
httpOnly: ["session"],
|
|
243
|
+
},
|
|
213
244
|
{
|
|
214
245
|
name: "upload",
|
|
215
246
|
description: "Upload bytes and return id and URL. Reference id as an image surface's assetId.",
|
|
@@ -272,32 +303,37 @@ const feedbackSchema = o({
|
|
|
272
303
|
chosen: z.array(o({ label: str, other: z.literal(true).optional() })),
|
|
273
304
|
note: str.optional(),
|
|
274
305
|
})),
|
|
275
|
-
|
|
306
|
+
borrowed: bag.describe("Part → the variant to take it from"),
|
|
276
307
|
tuned: bag,
|
|
277
308
|
comments: z.array(o({ part: nullStr, text: str })),
|
|
278
309
|
reactions: strs.optional().describe('One-tap reactions, e.g. 👍 part "hero" (variant B)'),
|
|
310
|
+
verdict: o({ kind: z.enum(["use", "revise"]), state: nullStr, variant: str })
|
|
311
|
+
.optional()
|
|
312
|
+
.describe("Use this or Revise on one variant"),
|
|
279
313
|
})
|
|
280
314
|
.nullable()
|
|
281
|
-
.describe("The user's Send; null if only
|
|
282
|
-
|
|
283
|
-
|
|
315
|
+
.describe("The user's Send; null if only messages"),
|
|
316
|
+
messages: z.array(o({ seq: num, text: str })).describe("What the user wrote in the thread"),
|
|
317
|
+
inUse: z.array(inUseRef).describe("In use, each at the version the user put in use"),
|
|
284
318
|
archived: z.array(variantRef),
|
|
285
|
-
|
|
286
|
-
resolved: z
|
|
319
|
+
closed: z
|
|
287
320
|
.literal(true)
|
|
288
321
|
.optional()
|
|
289
|
-
.describe("The user
|
|
322
|
+
.describe("The user closed the mock: its open questions were closed unanswered"),
|
|
290
323
|
dismissed: z
|
|
291
324
|
.array(o({ ask: str, text: str }))
|
|
292
325
|
.optional()
|
|
293
|
-
.describe("The
|
|
326
|
+
.describe("The questions the close dismissed; don't ask them again"),
|
|
327
|
+
edited: o({ kind: str.optional(), area: nullStr.optional() })
|
|
328
|
+
.optional()
|
|
329
|
+
.describe("The user moved the mock from Home: its kind or area now"),
|
|
294
330
|
}).meta({ id: "Feedback" });
|
|
295
331
|
// Typed once, on feedback: repeating it on every write would cost more than
|
|
296
332
|
// the rest of the write's output schema.
|
|
297
333
|
const feedbackRide = z.array(bag).describe("Feedback batches as the feedback tool returns");
|
|
298
334
|
const pendingSchema = o({
|
|
299
335
|
mock: str,
|
|
300
|
-
|
|
336
|
+
closed: z.literal(true).optional(),
|
|
301
337
|
viewerOpen: z.boolean(),
|
|
302
338
|
draft: o({ answered: num, of: num, comments: num, touchedAt: str }).nullable(),
|
|
303
339
|
}).meta({ id: "Pending" });
|
|
@@ -309,6 +345,10 @@ const writeOutput = {
|
|
|
309
345
|
partChanges: o({ vanished: strs, renamed: z.array(o({ from: str, to: str })) }).optional(),
|
|
310
346
|
nudges: strs.optional(),
|
|
311
347
|
suggestedAsk: bag.optional().describe("Send with ask when a nudge names it"),
|
|
348
|
+
suggestedFillAsks: z
|
|
349
|
+
.array(bag)
|
|
350
|
+
.optional()
|
|
351
|
+
.describe("A question per part with fills that none sets; send with ask"),
|
|
312
352
|
flow: flowSchema.optional(),
|
|
313
353
|
feedback: feedbackRide,
|
|
314
354
|
};
|
|
@@ -342,6 +382,13 @@ const OUTPUT_SCHEMAS = {
|
|
|
342
382
|
pending: z.array(pendingSchema),
|
|
343
383
|
},
|
|
344
384
|
say: { feedback: feedbackRide },
|
|
385
|
+
edit: {
|
|
386
|
+
renamed: o({ from: str, to: str }).optional(),
|
|
387
|
+
areaOrder: strs.optional(),
|
|
388
|
+
url: str,
|
|
389
|
+
nudges: strs.optional(),
|
|
390
|
+
feedback: feedbackRide,
|
|
391
|
+
},
|
|
345
392
|
read: {
|
|
346
393
|
mocks: z.array(o({ slug: str, states: strs, variants: num, open: num })).optional(),
|
|
347
394
|
states: strs.optional(),
|
|
@@ -350,9 +397,9 @@ const OUTPUT_SCHEMAS = {
|
|
|
350
397
|
.array(o({
|
|
351
398
|
state: nullStr,
|
|
352
399
|
variant: str,
|
|
353
|
-
status:
|
|
400
|
+
status: z.enum(["open", "inUse", "archived"]),
|
|
354
401
|
version: num,
|
|
355
|
-
|
|
402
|
+
inUseVersion: num.optional().describe("Set while in use: the version in use"),
|
|
356
403
|
history: z
|
|
357
404
|
.array(o({ version: num, hidden: z.boolean().optional() }))
|
|
358
405
|
.optional()
|
|
@@ -375,7 +422,8 @@ const OUTPUT_SCHEMAS = {
|
|
|
375
422
|
variant: str,
|
|
376
423
|
version: num,
|
|
377
424
|
latest: num.optional().describe("Your newer version, not in use yet"),
|
|
378
|
-
html: str,
|
|
425
|
+
html: str.describe("With fills: only the chosen ones"),
|
|
426
|
+
allFills: str.optional().describe("With fills: the html with every fill"),
|
|
379
427
|
markdown: str,
|
|
380
428
|
})),
|
|
381
429
|
settled: bag,
|
|
@@ -395,7 +443,9 @@ function shapeFor(tool, transport) {
|
|
|
395
443
|
shape[key] =
|
|
396
444
|
transport === "stdio" && key === "html" && tool.name === "publish"
|
|
397
445
|
? z.string().optional().describe(`${field.html}; or a file path`)
|
|
398
|
-
:
|
|
446
|
+
: transport === "stdio" && key === "poster" && tool.name === "publish"
|
|
447
|
+
? z.string().optional().describe(`${p.d}; or an image file path`)
|
|
448
|
+
: zodParam(p);
|
|
399
449
|
}
|
|
400
450
|
return shape;
|
|
401
451
|
}
|
|
@@ -44,17 +44,20 @@ const strictHtmlSurface = z.object({
|
|
|
44
44
|
kind: z.literal("html"),
|
|
45
45
|
html: requiredString("html"),
|
|
46
46
|
kits: z.array(z.string()).optional(),
|
|
47
|
+
poster: z.string().min(1).optional(),
|
|
47
48
|
});
|
|
48
49
|
const looseHtmlSurface = z
|
|
49
50
|
.object({
|
|
50
51
|
kind: z.literal("html"),
|
|
51
52
|
html: requiredString("html"),
|
|
52
53
|
kits: z.unknown().optional(),
|
|
54
|
+
poster: optionalLooseString,
|
|
53
55
|
})
|
|
54
56
|
.transform((p) => ({
|
|
55
57
|
kind: "html",
|
|
56
58
|
html: p.html,
|
|
57
59
|
...(Array.isArray(p.kits) ? { kits: p.kits.filter((k) => typeof k === "string") } : {}),
|
|
60
|
+
...(p.poster ? { poster: p.poster } : {}),
|
|
58
61
|
}));
|
|
59
62
|
// Strict mode rejects an unknown id with the valid set, so a CLI/REST typo is a
|
|
60
63
|
// clean 400; loose mode filters unknown ids out rather than dropping the whole
|
package/dist/server/run.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// that is counted, bounded and logged.
|
|
10
10
|
import { decodeBase64 } from "./base64.js";
|
|
11
11
|
import { isGuideTopic, unknownTopicMessage } from "./designGuide.js";
|
|
12
|
-
import { unwrap } from "./mcpHttp.js";
|
|
12
|
+
import { editCall, unwrap } from "./mcpHttp.js";
|
|
13
13
|
import { RUN_FUNCTIONS } from "./runApi.js";
|
|
14
14
|
// A script never waits for the user, so the wall clock is only a sandbox guard:
|
|
15
15
|
// 10 s clears a run of publishes with room to spare. 32 MiB and a 256 KiB stack are what the spike showed safe (512 KiB overflows the host
|
|
@@ -183,6 +183,11 @@ export function createRunFlow(deps) {
|
|
|
183
183
|
await take(flows.say(r.mock, { ...(await mine(r)), message }, flowCtx));
|
|
184
184
|
return [null, String(r.mock ?? "?")];
|
|
185
185
|
},
|
|
186
|
+
async edit([v]) {
|
|
187
|
+
const r = await mine(plain(v));
|
|
188
|
+
const value = await take(editCall(flows, r, flowCtx, project));
|
|
189
|
+
return [value, value.renamed ? `${value.renamed.from} → ${value.renamed.to}` : "edited"];
|
|
190
|
+
},
|
|
186
191
|
async upload([data, opts]) {
|
|
187
192
|
if (typeof data !== "string" || !data)
|
|
188
193
|
throw new Error("upload needs base64 data");
|
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 = { mock: 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?: \"mock\" | \"state\" | \"part\"; state?: string; part?: string; multi?: boolean;\n options: { id?: string; label: string; variant?: string; set?: Knobs }[];\n};\ntype Written = {\n post: { state: string | null; variant: string; version: number }; url: string;\n parts: { state: string | null; parts: { name: string }[] }[];\n partChanges?: { vanished: string[]; renamed: { from: string; to: string }[] };\n nudges?: string[]; suggestedAsk?: Ask; // send
|
|
2
|
-
export declare const RUN_FUNCTIONS: readonly ["guide", "read", "publish", "ask", "feedback", "say", "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 = { mock: 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?: \"mock\" | \"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 }; 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 mock: string | null;\n reply: null | { asks: { ask: string; chosen: { id: string; label: string; other?: true }[]; note?: 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 closed?: true; dismissed?: { ask: string; text: string }[];\n};\ntype Pending = { mock: string; closed?: 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 /** No mock: every mock, the flows, plus pending. With one: its states, variants, asks, parts, knobs. */\n read(mock?: string, opts?: { body?: boolean; history?: boolean }): Promise<unknown>;\n /** Creates or versions (mock, 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 publish(v: Ref & { situation?: string; title?: string; kind?: \"page\" | \"component\" | \"sketch\"; 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; note?: string; widths?: number[]; flow?: string; step?: number }): Promise<Written>;\n ask(mock: 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 /** No new version; \"\" clears. { rename } or { areaOrder } without mock edits the project. */\n edit(v: { mock?: string; slug?: string; title?: string; kind?: string; area?: string; source?: string; rename?: string; areaOrder?: string[] }): Promise<{ url: 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", "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({ mock: \"writer\", variant: \"calm\", html: html(\"calm\") });\nawait mockpit.publish({ mock: \"writer\", variant: \"bold\", html: html(\"bold\") });\nconst { url } = await mockpit.ask(\"writer\", [{ id: \"
|
|
4
|
+
export declare const RUN_EXAMPLE = "const html = (tone) => `<section data-part=\"hero\" class=\"${tone}\"><h1>Writer</h1></section>`;\nawait mockpit.publish({ mock: \"writer\", variant: \"calm\", html: html(\"calm\") });\nawait mockpit.publish({ mock: \"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
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// tool description, the scripts guide topic and `mockpit run --help`, so the
|
|
3
3
|
// tiers cannot describe different APIs. Hand-shaped rather than generated from
|
|
4
4
|
// mcpSpec: a generator repeats Surface per tool and the run-level params on
|
|
5
|
-
// every function, roughly tripling the size.
|
|
6
|
-
//
|
|
5
|
+
// every function, roughly tripling the size. Every host function named here
|
|
6
|
+
// must exist, and vice versa.
|
|
7
7
|
export const RUN_API = `// The body of an async function, in plain JavaScript (no TypeScript syntax).
|
|
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
|
|
@@ -21,34 +21,37 @@ type Surface =
|
|
|
21
21
|
| { kind: "code"; code: string; language?: string; lineStart?: number };
|
|
22
22
|
type Ask = {
|
|
23
23
|
id?: string; text: string; scope?: "mock" | "state" | "part"; state?: string; part?: string; multi?: boolean;
|
|
24
|
-
options: { id?: string; label: string; variant?: string; set?: Knobs }[];
|
|
24
|
+
options: { id?: string; label: string; variant?: string; set?: Knobs; recommended?: boolean }[];
|
|
25
25
|
};
|
|
26
26
|
type Written = {
|
|
27
27
|
post: { state: string | null; variant: string; version: number }; url: string;
|
|
28
28
|
parts: { state: string | null; parts: { name: string }[] }[];
|
|
29
29
|
partChanges?: { vanished: string[]; renamed: { from: string; to: string }[] };
|
|
30
|
-
nudges?: string[]; suggestedAsk?: Ask; // send
|
|
30
|
+
nudges?: string[]; suggestedAsk?: Ask; suggestedFillAsks?: Ask[]; // send with ask when nudged
|
|
31
31
|
};
|
|
32
32
|
type Feedback = {
|
|
33
33
|
mock: string | null;
|
|
34
|
-
reply: null | { asks: { ask: string; chosen: { id: string; label: string; other?: true }[]; note?: string }[];
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
reply: null | { asks: { ask: string; chosen: { id: string; label: string; other?: true }[]; note?: 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
|
+
messages: { text: string; state: string | null; variant: string | null }[];
|
|
36
|
+
inUse: { state: string | null; variant: string; version: number }[]; archived: { state: string | null; variant: string }[];
|
|
37
|
+
closed?: true; dismissed?: { ask: string; text: string }[];
|
|
37
38
|
};
|
|
38
|
-
type Pending = { mock: string; viewerOpen: boolean; draft: null | { answered: number; of: number; comments: number; touchedAt: string } };
|
|
39
|
+
type Pending = { mock: string; closed?: true; viewerOpen: boolean; draft: null | { answered: number; of: number; comments: number; touchedAt: string } };
|
|
39
40
|
declare const mockpit: {
|
|
40
41
|
guide(topic?: "knobs" | "asks" | "surfaces" | "html" | "feedback" | "http" | "scripts"): Promise<string>;
|
|
41
42
|
/** No mock: every mock, the flows, plus pending. With one: its states, variants, asks, parts, knobs. */
|
|
42
43
|
read(mock?: string, opts?: { body?: boolean; history?: boolean }): Promise<unknown>;
|
|
43
44
|
/** Creates or versions (mock, state, variant). One of: html; surfaces, the full ordered list
|
|
44
45
|
* ({id} alone keeps that surface, a missing id is removed); parts {"name"|"name#key": outer html};
|
|
45
|
-
* fills {"part#fill": outer html}, replacing or adding one
|
|
46
|
-
publish(v: Ref & { title?: string; kind?: "component" | "
|
|
46
|
+
* fills {"part#fill": outer html}, replacing or adding one fill of a part. */
|
|
47
|
+
publish(v: Ref & { situation?: string; title?: string; kind?: "page" | "component" | "sketch"; 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; note?: string; widths?: number[]; flow?: string; step?: number }): Promise<Written>;
|
|
47
48
|
ask(mock: string, asks: Ask[]): Promise<{ asks: { id: string; text: string }[]; url: string; nudges?: string[] }>;
|
|
48
49
|
/** Never waits: what the user sent since you last heard, and what they are doing now. */
|
|
49
50
|
feedback(): Promise<{ feedback: Feedback[]; pending: Pending[] }>;
|
|
50
51
|
say(v: Ref, message: string): Promise<void>;
|
|
51
|
-
|
|
52
|
+
/** No new version; "" clears. { rename } or { areaOrder } without mock edits the project. */
|
|
53
|
+
edit(v: { mock?: string; slug?: string; title?: string; kind?: string; area?: string; source?: string; rename?: string; areaOrder?: string[] }): Promise<{ url: string }>;
|
|
54
|
+
export(v: Ref): Promise<{ states: { state: string | null; variant: string; version: number; html: string; allFills?: string }[]; reply: unknown }>;
|
|
52
55
|
/** data: base64 bytes. Use id as an image surface's assetId. */
|
|
53
56
|
upload(data: string, opts: { contentType: string; filename?: string; kind?: "image" | "file" }): Promise<{ id: string; url: string }>;
|
|
54
57
|
};
|
|
@@ -61,13 +64,14 @@ export const RUN_FUNCTIONS = [
|
|
|
61
64
|
"ask",
|
|
62
65
|
"feedback",
|
|
63
66
|
"say",
|
|
67
|
+
"edit",
|
|
64
68
|
"export",
|
|
65
69
|
"upload",
|
|
66
70
|
];
|
|
67
71
|
export const RUN_EXAMPLE = `const html = (tone) => \`<section data-part="hero" class="\${tone}"><h1>Writer</h1></section>\`;
|
|
68
72
|
await mockpit.publish({ mock: "writer", variant: "calm", html: html("calm") });
|
|
69
73
|
await mockpit.publish({ mock: "writer", variant: "bold", html: html("bold") });
|
|
70
|
-
const { url } = await mockpit.ask("writer", [{ id: "
|
|
74
|
+
const { url } = await mockpit.ask("writer", [{ id: "tone", text: "Calm or bold?",
|
|
71
75
|
options: [{ label: "Calm", variant: "calm" }, { label: "Bold", variant: "bold" }] }]);
|
|
72
76
|
// Tell the user where to look and end the turn; a later run reads mockpit.feedback().
|
|
73
77
|
return url;`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Ask, type Asset, type Comment, type CommentQuery, type CommitReplyInput, type CreateAssetInput, type CreateCommentInput, type CreateMockInput, type CreatePostInput, type CreateSessionInput, type Draft, type Knobs, type Mock, type
|
|
1
|
+
import { type Ask, type Asset, type Comment, type CommentQuery, type CommitReplyInput, type CreateAssetInput, type CreateCommentInput, type CreateMockInput, type CreatePostInput, type CreateSessionInput, type Draft, type Knobs, type Mock, type Post, type PostQuery, type PostStatus, type PostVersion, type Session, type Embed, type Flow, type FlowStep, type ForkRef, type SqlStorage, type Store, type Surface, type UpdateMockInput, type UpdatePostInput } from "./types.ts";
|
|
2
2
|
export declare class SqlStore implements Store {
|
|
3
3
|
private sql;
|
|
4
4
|
private assetRefCache;
|
|
@@ -8,6 +8,7 @@ export declare class SqlStore implements Store {
|
|
|
8
8
|
private columns;
|
|
9
9
|
private addMissing;
|
|
10
10
|
private migrate;
|
|
11
|
+
private migrateToContractTerms;
|
|
11
12
|
private createIndexes;
|
|
12
13
|
private migrateLegacy;
|
|
13
14
|
private migrateToSurfaces;
|
|
@@ -16,6 +17,7 @@ export declare class SqlStore implements Store {
|
|
|
16
17
|
private migrateToItems;
|
|
17
18
|
private migrateToVersions;
|
|
18
19
|
private migrateToMocks;
|
|
20
|
+
private migrateToSketches;
|
|
19
21
|
private settingSync;
|
|
20
22
|
private insertVersion;
|
|
21
23
|
private rowToVersion;
|
|
@@ -45,6 +47,8 @@ export declare class SqlStore implements Store {
|
|
|
45
47
|
createMock(input: CreateMockInput): Promise<Mock>;
|
|
46
48
|
updateMock(id: string, patch: UpdateMockInput): Promise<Mock | null>;
|
|
47
49
|
removeMock(id: string): Promise<boolean>;
|
|
50
|
+
renameMock(id: string, slug: string): Promise<Mock | null>;
|
|
51
|
+
renameProject(from: string, to: string): Promise<boolean>;
|
|
48
52
|
private flowsOf;
|
|
49
53
|
private writeFlow;
|
|
50
54
|
listFlows(project: string): Promise<Flow[]>;
|
|
@@ -55,13 +59,16 @@ export declare class SqlStore implements Store {
|
|
|
55
59
|
project: string;
|
|
56
60
|
slug: string;
|
|
57
61
|
title: string;
|
|
58
|
-
kind: MockKind;
|
|
62
|
+
kind: import("./types.ts").MockKind;
|
|
63
|
+
area: string | null;
|
|
64
|
+
source: string | null;
|
|
59
65
|
states: string[];
|
|
66
|
+
situations: Record<string, string[]>;
|
|
60
67
|
asks: Ask[];
|
|
61
68
|
knobs: Knobs;
|
|
62
69
|
widths: number[];
|
|
63
70
|
sessionId: string | null;
|
|
64
|
-
|
|
71
|
+
closedAt: string | null;
|
|
65
72
|
createdAt: string;
|
|
66
73
|
updatedAt: string;
|
|
67
74
|
} | null>;
|