mockpit 1.2.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 +69 -48
- package/bin/demoData.js +3 -3
- package/bin/mockpit.js +426 -51
- package/dist/mcp/server.js +45 -7
- package/dist/server/apiViews.d.ts +407 -21
- package/dist/server/apiViews.js +110 -11
- package/dist/server/app.d.ts +5 -1
- package/dist/server/app.js +1454 -192
- package/dist/server/codeWorker.js +2 -1
- package/dist/server/decisions.d.ts +23 -0
- package/dist/server/decisions.js +100 -0
- package/dist/server/designGuide.d.ts +6 -2
- package/dist/server/designGuide.js +96 -27
- package/dist/server/embeds.d.ts +26 -0
- package/dist/server/embeds.js +144 -0
- package/dist/server/eventLog.js +2 -1
- package/dist/server/feedbackBatch.d.ts +15 -4
- package/dist/server/feedbackBatch.js +31 -7
- package/dist/server/fills.d.ts +41 -0
- package/dist/server/fills.js +328 -0
- package/dist/server/guidance.d.ts +23 -0
- package/dist/server/guidance.js +71 -0
- package/dist/server/index.js +25 -2
- package/dist/server/knobs.d.ts +6 -2
- package/dist/server/knobs.js +11 -1
- package/dist/server/mcpHttp.d.ts +6 -0
- package/dist/server/mcpHttp.js +35 -0
- package/dist/server/mcpSpec.d.ts +14 -13
- package/dist/server/mcpSpec.js +133 -18
- package/dist/server/postSurfaces.js +6 -12
- package/dist/server/richRender.js +3 -1
- package/dist/server/run.js +10 -1
- package/dist/server/runApi.d.ts +3 -3
- package/dist/server/runApi.js +18 -13
- package/dist/server/sqlStore.d.ts +97 -5
- package/dist/server/sqlStore.js +367 -27
- package/dist/server/surfacePage.d.ts +6 -2
- package/dist/server/surfacePage.js +142 -10
- package/dist/server/themes.d.ts +2 -0
- package/dist/server/themes.js +23 -3
- package/dist/server/types.d.ts +107 -16
- package/dist/server/types.js +73 -10
- package/dist/server/viewerRequest.d.ts +2 -0
- package/dist/server/viewerRequest.js +14 -0
- package/extensions/mockpit.js +179 -16
- package/guide/packs/design-craft.md +35 -0
- package/guide/topics/asks.md +82 -31
- package/guide/topics/feedback.md +151 -43
- package/guide/topics/html.md +8 -10
- package/guide/topics/http.md +8 -4
- package/guide/topics/knobs.md +4 -3
- package/guide/topics/scripts.md +2 -2
- package/guide/topics/surfaces.md +45 -11
- package/package.json +3 -15
- package/skills/launch-video/SKILL.md +2 -2
- package/skills/mockpit/SKILL.md +23 -7
- package/viewer/dist/index.html +9 -8
- package/dist/server/slots.d.ts +0 -14
- package/dist/server/slots.js +0 -71
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";
|
|
57
|
+
t: "surfaces" | "asks" | "object" | "integers" | "strings";
|
|
57
58
|
d?: string;
|
|
58
59
|
req?: boolean;
|
|
59
60
|
};
|
|
@@ -131,9 +132,9 @@ export declare const STDIO_MCP_TOOLS: ({
|
|
|
131
132
|
_meta: {
|
|
132
133
|
"anthropic/alwaysLoad": boolean;
|
|
133
134
|
};
|
|
134
|
-
outputSchema?: z.ZodObject<
|
|
135
|
-
[
|
|
136
|
-
}
|
|
135
|
+
outputSchema?: z.ZodObject<{
|
|
136
|
+
[x: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
137
|
+
}, z.core.$loose> | undefined;
|
|
137
138
|
name: string;
|
|
138
139
|
description: string;
|
|
139
140
|
inputSchema: Readonly<{
|
|
@@ -141,9 +142,9 @@ export declare const STDIO_MCP_TOOLS: ({
|
|
|
141
142
|
}>;
|
|
142
143
|
} | {
|
|
143
144
|
_meta?: undefined;
|
|
144
|
-
outputSchema?: z.ZodObject<
|
|
145
|
-
[
|
|
146
|
-
}
|
|
145
|
+
outputSchema?: z.ZodObject<{
|
|
146
|
+
[x: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
147
|
+
}, z.core.$loose> | undefined;
|
|
147
148
|
name: string;
|
|
148
149
|
description: string;
|
|
149
150
|
inputSchema: Readonly<{
|
|
@@ -154,9 +155,9 @@ export declare const STDIO_RUN_TOOLS: ({
|
|
|
154
155
|
_meta: {
|
|
155
156
|
"anthropic/alwaysLoad": boolean;
|
|
156
157
|
};
|
|
157
|
-
outputSchema?: z.ZodObject<
|
|
158
|
-
[
|
|
159
|
-
}
|
|
158
|
+
outputSchema?: z.ZodObject<{
|
|
159
|
+
[x: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
160
|
+
}, z.core.$loose> | undefined;
|
|
160
161
|
name: string;
|
|
161
162
|
description: string;
|
|
162
163
|
inputSchema: Readonly<{
|
|
@@ -164,9 +165,9 @@ export declare const STDIO_RUN_TOOLS: ({
|
|
|
164
165
|
}>;
|
|
165
166
|
} | {
|
|
166
167
|
_meta?: undefined;
|
|
167
|
-
outputSchema?: z.ZodObject<
|
|
168
|
-
[
|
|
169
|
-
}
|
|
168
|
+
outputSchema?: z.ZodObject<{
|
|
169
|
+
[x: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
170
|
+
}, z.core.$loose> | undefined;
|
|
170
171
|
name: string;
|
|
171
172
|
description: string;
|
|
172
173
|
inputSchema: Readonly<{
|
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",
|
|
@@ -28,8 +29,17 @@ const field = {
|
|
|
28
29
|
knobs: 'Global knobs (tunekit usePane shape) keyed by path: [default,min,max,step], boolean, {type:"select",options}, "#hex"',
|
|
29
30
|
variantKnobs: "Knobs this variant alone has",
|
|
30
31
|
from: "Branch from this version",
|
|
32
|
+
fork: 'Start this new variant from a sibling: "variant" or "variant@N"; alone it copies it, with parts/fills it edits the copy',
|
|
31
33
|
prompt: "What prompted this version; usually omit",
|
|
34
|
+
note: "What this variant is; shown beside it, never in the html",
|
|
35
|
+
widths: "Widths it ships at, side by side: [390,1280]",
|
|
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",
|
|
40
|
+
step: "1-based place; 0 removes",
|
|
32
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",
|
|
33
43
|
};
|
|
34
44
|
const diffFileSchema = z.object({
|
|
35
45
|
filename: z.string(),
|
|
@@ -48,6 +58,7 @@ export const mcpSurfaceSchema = z
|
|
|
48
58
|
kind: z.enum(SURFACE_KIND_ENUM).optional(),
|
|
49
59
|
html: z.string().optional().describe(field.html),
|
|
50
60
|
kits: z.array(z.string()).optional().describe(field.kits),
|
|
61
|
+
poster: z.string().optional().describe(field.poster),
|
|
51
62
|
markdown: z.string().optional().describe("Prose; raw HTML is escaped"),
|
|
52
63
|
mermaid: z.string().optional().describe("Diagram source; do not set colors"),
|
|
53
64
|
patch: z.string().optional().describe("Preferred over files"),
|
|
@@ -69,17 +80,23 @@ export const mcpSurfaceSchema = z
|
|
|
69
80
|
description: "Set kind and its field: html, markdown, mermaid, patch|files (diff), assetId (image), text (terminal), data (json), code",
|
|
70
81
|
});
|
|
71
82
|
const askSchema = z.object({
|
|
72
|
-
id: z.string().optional().describe("Stable id; reusing one replaces that
|
|
83
|
+
id: z.string().optional().describe("Stable id; reusing one replaces that question"),
|
|
73
84
|
text: z.string().describe("The question, one sentence"),
|
|
74
85
|
scope: z.enum(["mock", "state", "part"]).optional(),
|
|
75
86
|
state: z.string().optional(),
|
|
76
87
|
part: z.string().optional().describe("data-part name, for scope part"),
|
|
77
88
|
multi: z.boolean().optional(),
|
|
89
|
+
after: z
|
|
90
|
+
.string()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("Id of an earlier ask this depends on; it opens once that one is answered"),
|
|
78
93
|
options: z.array(z.object({
|
|
79
94
|
id: z.string().optional(),
|
|
80
95
|
label: z.string(),
|
|
81
96
|
variant: z.string().optional().describe("Bind to a published variant"),
|
|
97
|
+
version: z.number().int().optional().describe("And one version of it"),
|
|
82
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"),
|
|
83
100
|
})),
|
|
84
101
|
});
|
|
85
102
|
function zodParam(p) {
|
|
@@ -100,6 +117,12 @@ function zodParam(p) {
|
|
|
100
117
|
case "integer":
|
|
101
118
|
s = z.number().int();
|
|
102
119
|
break;
|
|
120
|
+
case "integers":
|
|
121
|
+
s = z.array(z.number().int());
|
|
122
|
+
break;
|
|
123
|
+
case "strings":
|
|
124
|
+
s = z.array(z.string());
|
|
125
|
+
break;
|
|
103
126
|
default:
|
|
104
127
|
s = p.t === "string" ? z.string() : p.t === "number" ? z.number() : z.boolean();
|
|
105
128
|
}
|
|
@@ -118,21 +141,34 @@ const VARIANT_REF = { mock: P.mock, state: P.state, variant: P.variant, project:
|
|
|
118
141
|
export const MCP_TOOL_DEFS = [
|
|
119
142
|
{
|
|
120
143
|
name: "publish",
|
|
121
|
-
description: "Create or version one variant of one state of a mock. One of html, surfaces (the full ordered list) or
|
|
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.",
|
|
122
145
|
params: {
|
|
123
146
|
...VARIANT_REF,
|
|
147
|
+
situation: { t: "string", d: field.situation },
|
|
124
148
|
title: { t: "string", d: field.title },
|
|
125
|
-
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 },
|
|
126
152
|
html: { t: "string", d: field.html },
|
|
153
|
+
poster: { t: "string", d: field.poster },
|
|
127
154
|
surfaces: {
|
|
128
155
|
t: "surfaces",
|
|
129
156
|
d: "Full ordered list: {id} keeps a surface, a missing id removes it",
|
|
130
157
|
},
|
|
131
|
-
parts: { t: "object", d: '{"name"|"name#key": outer html}; splices
|
|
158
|
+
parts: { t: "object", d: '{"name"|"name#key": outer html}; splices declared parts' },
|
|
159
|
+
fills: {
|
|
160
|
+
t: "object",
|
|
161
|
+
d: '{"part#fill": outer html}; replaces that fill of a part, or adds it after its others',
|
|
162
|
+
},
|
|
132
163
|
knobs: { t: "object", d: field.knobs },
|
|
133
164
|
variantKnobs: { t: "object", d: field.variantKnobs },
|
|
134
165
|
from: { t: "integer", d: field.from },
|
|
166
|
+
fork: { t: "string", d: field.fork },
|
|
135
167
|
prompt: { t: "string", d: field.prompt },
|
|
168
|
+
note: { t: "string", d: field.note },
|
|
169
|
+
widths: { t: "integers", d: field.widths },
|
|
170
|
+
flow: { t: "string", d: field.flow },
|
|
171
|
+
step: { t: "integer", d: field.step },
|
|
136
172
|
session: P.session,
|
|
137
173
|
sessionTitle: { t: "string", d: field.sessionTitle },
|
|
138
174
|
agent: { t: "string", d: field.agent },
|
|
@@ -141,7 +177,7 @@ export const MCP_TOOL_DEFS = [
|
|
|
141
177
|
},
|
|
142
178
|
{
|
|
143
179
|
name: "ask",
|
|
144
|
-
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.",
|
|
145
181
|
params: {
|
|
146
182
|
mock: P.mock,
|
|
147
183
|
project: P.project,
|
|
@@ -152,17 +188,21 @@ export const MCP_TOOL_DEFS = [
|
|
|
152
188
|
},
|
|
153
189
|
{
|
|
154
190
|
name: "read",
|
|
155
|
-
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.",
|
|
156
192
|
params: {
|
|
157
193
|
mock: { t: "string", d: field.mock },
|
|
158
194
|
project: P.project,
|
|
159
195
|
body: { t: "boolean", d: "Include each variant's surfaces" },
|
|
160
196
|
history: { t: "boolean", d: "Include version rows" },
|
|
197
|
+
tree: {
|
|
198
|
+
t: "boolean",
|
|
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)",
|
|
200
|
+
},
|
|
161
201
|
},
|
|
162
202
|
},
|
|
163
203
|
{
|
|
164
204
|
name: "feedback",
|
|
165
|
-
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).",
|
|
166
206
|
params: {
|
|
167
207
|
project: P.project,
|
|
168
208
|
session: { t: "string", d: "Session id returned by publish", req: true },
|
|
@@ -181,9 +221,26 @@ export const MCP_TOOL_DEFS = [
|
|
|
181
221
|
},
|
|
182
222
|
{
|
|
183
223
|
name: "export",
|
|
184
|
-
description: "The
|
|
224
|
+
description: "The html in use (else current) per state, version history, screenshot URL, knobs, the settled values and the last reply.",
|
|
185
225
|
params: VARIANT_REF,
|
|
186
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
|
+
},
|
|
187
244
|
{
|
|
188
245
|
name: "upload",
|
|
189
246
|
description: "Upload bytes and return id and URL. Reference id as an image surface's assetId.",
|
|
@@ -235,7 +292,9 @@ const nullStr = z.string().nullable();
|
|
|
235
292
|
const strs = z.array(z.string());
|
|
236
293
|
const bag = z.record(z.string(), z.unknown());
|
|
237
294
|
const variantRef = o({ state: nullStr, variant: str });
|
|
295
|
+
const inUseRef = o({ state: nullStr, variant: str, version: num });
|
|
238
296
|
const partsByState = z.array(o({ state: nullStr, parts: z.array(o({ name: str })) }));
|
|
297
|
+
const flowSchema = o({ name: str, steps: z.array(o({ mock: str, state: nullStr })) });
|
|
239
298
|
const feedbackSchema = o({
|
|
240
299
|
mock: nullStr,
|
|
241
300
|
reply: o({
|
|
@@ -244,22 +303,37 @@ const feedbackSchema = o({
|
|
|
244
303
|
chosen: z.array(o({ label: str, other: z.literal(true).optional() })),
|
|
245
304
|
note: str.optional(),
|
|
246
305
|
})),
|
|
247
|
-
|
|
306
|
+
borrowed: bag.describe("Part → the variant to take it from"),
|
|
248
307
|
tuned: bag,
|
|
249
308
|
comments: z.array(o({ part: nullStr, text: str })),
|
|
250
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"),
|
|
251
313
|
})
|
|
252
314
|
.nullable()
|
|
253
|
-
.describe("The user's Send; null if only
|
|
254
|
-
|
|
255
|
-
|
|
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"),
|
|
256
318
|
archived: z.array(variantRef),
|
|
319
|
+
closed: z
|
|
320
|
+
.literal(true)
|
|
321
|
+
.optional()
|
|
322
|
+
.describe("The user closed the mock: its open questions were closed unanswered"),
|
|
323
|
+
dismissed: z
|
|
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() })
|
|
328
|
+
.optional()
|
|
329
|
+
.describe("The user moved the mock from Home: its kind or area now"),
|
|
257
330
|
}).meta({ id: "Feedback" });
|
|
258
331
|
// Typed once, on feedback: repeating it on every write would cost more than
|
|
259
332
|
// the rest of the write's output schema.
|
|
260
333
|
const feedbackRide = z.array(bag).describe("Feedback batches as the feedback tool returns");
|
|
261
334
|
const pendingSchema = o({
|
|
262
335
|
mock: str,
|
|
336
|
+
closed: z.literal(true).optional(),
|
|
263
337
|
viewerOpen: z.boolean(),
|
|
264
338
|
draft: o({ answered: num, of: num, comments: num, touchedAt: str }).nullable(),
|
|
265
339
|
}).meta({ id: "Pending" });
|
|
@@ -271,6 +345,11 @@ const writeOutput = {
|
|
|
271
345
|
partChanges: o({ vanished: strs, renamed: z.array(o({ from: str, to: str })) }).optional(),
|
|
272
346
|
nudges: strs.optional(),
|
|
273
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"),
|
|
352
|
+
flow: flowSchema.optional(),
|
|
274
353
|
feedback: feedbackRide,
|
|
275
354
|
};
|
|
276
355
|
const OUTPUT_SCHEMAS = {
|
|
@@ -303,17 +382,51 @@ const OUTPUT_SCHEMAS = {
|
|
|
303
382
|
pending: z.array(pendingSchema),
|
|
304
383
|
},
|
|
305
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
|
+
},
|
|
306
392
|
read: {
|
|
307
393
|
mocks: z.array(o({ slug: str, states: strs, variants: num, open: num })).optional(),
|
|
308
394
|
states: strs.optional(),
|
|
309
395
|
asks: z.array(o({ id: str, text: str })).optional(),
|
|
310
|
-
variants: z
|
|
396
|
+
variants: z
|
|
397
|
+
.array(o({
|
|
398
|
+
state: nullStr,
|
|
399
|
+
variant: str,
|
|
400
|
+
status: z.enum(["open", "inUse", "archived"]),
|
|
401
|
+
version: num,
|
|
402
|
+
inUseVersion: num.optional().describe("Set while in use: the version in use"),
|
|
403
|
+
history: z
|
|
404
|
+
.array(o({ version: num, hidden: z.boolean().optional() }))
|
|
405
|
+
.optional()
|
|
406
|
+
.describe("With history: version rows; hidden ones the user put away are flagged"),
|
|
407
|
+
}))
|
|
408
|
+
.optional(),
|
|
409
|
+
inUse: z
|
|
410
|
+
.array(inUseRef)
|
|
411
|
+
.optional()
|
|
412
|
+
.describe("Per state, the version the user put in use; export and embeds resolve to it"),
|
|
311
413
|
knobs: bag.optional(),
|
|
312
|
-
|
|
414
|
+
settled: bag.optional().describe("Every knob value the user has sent, later replies winning"),
|
|
415
|
+
tree: z.array(bag).optional(),
|
|
416
|
+
flows: z.array(flowSchema).optional(),
|
|
313
417
|
pending: z.union([z.array(pendingSchema), pendingSchema]),
|
|
314
418
|
},
|
|
315
419
|
export: {
|
|
316
|
-
states: z.array(o({
|
|
420
|
+
states: z.array(o({
|
|
421
|
+
state: nullStr,
|
|
422
|
+
variant: str,
|
|
423
|
+
version: num,
|
|
424
|
+
latest: num.optional().describe("Your newer version, not in use yet"),
|
|
425
|
+
html: str.describe("With fills: only the chosen ones"),
|
|
426
|
+
allFills: str.optional().describe("With fills: the html with every fill"),
|
|
427
|
+
markdown: str,
|
|
428
|
+
})),
|
|
429
|
+
settled: bag,
|
|
317
430
|
reply: bag.nullable(),
|
|
318
431
|
},
|
|
319
432
|
};
|
|
@@ -330,7 +443,9 @@ function shapeFor(tool, transport) {
|
|
|
330
443
|
shape[key] =
|
|
331
444
|
transport === "stdio" && key === "html" && tool.name === "publish"
|
|
332
445
|
? z.string().optional().describe(`${field.html}; or a file path`)
|
|
333
|
-
:
|
|
446
|
+
: transport === "stdio" && key === "poster" && tool.name === "publish"
|
|
447
|
+
? z.string().optional().describe(`${p.d}; or an image file path`)
|
|
448
|
+
: zodParam(p);
|
|
334
449
|
}
|
|
335
450
|
return shape;
|
|
336
451
|
}
|
|
@@ -6,23 +6,14 @@ const MERMAID_PARSE_NEXT_STEPS = [
|
|
|
6
6
|
'Keep the diagram declaration on the first non-comment line (for example, "flowchart TD").',
|
|
7
7
|
"Reduce the diagram to a minimal valid form, then add statements back until the failing line is isolated.",
|
|
8
8
|
];
|
|
9
|
-
const requiredString = (name) => z.string({
|
|
10
|
-
required_error: `requires string "${name}"`,
|
|
11
|
-
invalid_type_error: `requires string "${name}"`,
|
|
12
|
-
});
|
|
9
|
+
const requiredString = (name) => z.string({ error: `requires string "${name}"` });
|
|
13
10
|
const optionalLooseString = z.preprocess((v) => (typeof v === "string" ? v : undefined), z.string().optional());
|
|
14
11
|
const looseLayout = z.preprocess((v) => (v === "unified" || v === "split" ? v : undefined), z.enum(["unified", "split"]).optional());
|
|
15
12
|
const optionalLooseNumber = z.preprocess((v) => (typeof v === "number" && Number.isFinite(v) ? v : undefined), z.number().optional());
|
|
16
13
|
const strictDiffFile = z.object({
|
|
17
14
|
filename: requiredString("filename"),
|
|
18
|
-
before: z.string({
|
|
19
|
-
|
|
20
|
-
invalid_type_error: 'requires string "before" and "after"',
|
|
21
|
-
}),
|
|
22
|
-
after: z.string({
|
|
23
|
-
required_error: 'requires string "before" and "after"',
|
|
24
|
-
invalid_type_error: 'requires string "before" and "after"',
|
|
25
|
-
}),
|
|
15
|
+
before: z.string({ error: 'requires string "before" and "after"' }),
|
|
16
|
+
after: z.string({ error: 'requires string "before" and "after"' }),
|
|
26
17
|
language: z.string().optional(),
|
|
27
18
|
});
|
|
28
19
|
const looseDiffFile = z
|
|
@@ -53,17 +44,20 @@ const strictHtmlSurface = z.object({
|
|
|
53
44
|
kind: z.literal("html"),
|
|
54
45
|
html: requiredString("html"),
|
|
55
46
|
kits: z.array(z.string()).optional(),
|
|
47
|
+
poster: z.string().min(1).optional(),
|
|
56
48
|
});
|
|
57
49
|
const looseHtmlSurface = z
|
|
58
50
|
.object({
|
|
59
51
|
kind: z.literal("html"),
|
|
60
52
|
html: requiredString("html"),
|
|
61
53
|
kits: z.unknown().optional(),
|
|
54
|
+
poster: optionalLooseString,
|
|
62
55
|
})
|
|
63
56
|
.transform((p) => ({
|
|
64
57
|
kind: "html",
|
|
65
58
|
html: p.html,
|
|
66
59
|
...(Array.isArray(p.kits) ? { kits: p.kits.filter((k) => typeof k === "string") } : {}),
|
|
60
|
+
...(p.poster ? { poster: p.poster } : {}),
|
|
67
61
|
}));
|
|
68
62
|
// Strict mode rejects an unknown id with the valid set, so a CLI/REST typo is a
|
|
69
63
|
// clean 400; loose mode filters unknown ids out rather than dropping the whole
|
|
@@ -139,7 +139,9 @@ blockquote {
|
|
|
139
139
|
color: var(--muted);
|
|
140
140
|
}
|
|
141
141
|
table { border-collapse: collapse; font-size: 13px; }
|
|
142
|
-
|
|
142
|
+
/* break-word, not the body's anywhere: anywhere lets a column shrink to one
|
|
143
|
+
letter, so a long cell beside it squeezes "Topic" into "Top/ic". */
|
|
144
|
+
th, td { border: 0.5px solid var(--border); padding: 4px 8px; text-align: left; overflow-wrap: break-word; }
|
|
143
145
|
th { background: var(--hover); }
|
|
144
146
|
img { max-width: 100%; height: auto; border-radius: 6px; }
|
|
145
147
|
hr { border: none; border-top: 0.5px solid var(--border); margin: 1em 0; }
|
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
|
|
@@ -153,6 +153,10 @@ export function createRunFlow(deps) {
|
|
|
153
153
|
return [await deps.guide("", topic), String(topic)];
|
|
154
154
|
},
|
|
155
155
|
async read([mock, opts]) {
|
|
156
|
+
if ((mock === undefined || mock === null) && plain(opts).tree === true) {
|
|
157
|
+
const value = await take(flows.tree({ project: await projectOf() }));
|
|
158
|
+
return [value, `${value.tree.length} mocks`];
|
|
159
|
+
}
|
|
156
160
|
if (mock === undefined || mock === null) {
|
|
157
161
|
const value = await take(flows.list({ project: await projectOf() }));
|
|
158
162
|
return [value, `${value.mocks.length} mocks`];
|
|
@@ -179,6 +183,11 @@ export function createRunFlow(deps) {
|
|
|
179
183
|
await take(flows.say(r.mock, { ...(await mine(r)), message }, flowCtx));
|
|
180
184
|
return [null, String(r.mock ?? "?")];
|
|
181
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
|
+
},
|
|
182
191
|
async upload([data, opts]) {
|
|
183
192
|
if (typeof data !== "string" || !data)
|
|
184
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,33 +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
|
-
/** No mock: every mock plus pending. With one: its states, variants, asks, parts, knobs. */
|
|
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
|
-
* ({id} alone keeps that surface, a missing id is removed); parts {"name"|"name#key": outer html}
|
|
45
|
-
|
|
45
|
+
* ({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
|
+
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>;
|
|
46
48
|
ask(mock: string, asks: Ask[]): Promise<{ asks: { id: string; text: string }[]; url: string; nudges?: string[] }>;
|
|
47
49
|
/** Never waits: what the user sent since you last heard, and what they are doing now. */
|
|
48
50
|
feedback(): Promise<{ feedback: Feedback[]; pending: Pending[] }>;
|
|
49
51
|
say(v: Ref, message: string): Promise<void>;
|
|
50
|
-
|
|
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 }>;
|
|
51
55
|
/** data: base64 bytes. Use id as an image surface's assetId. */
|
|
52
56
|
upload(data: string, opts: { contentType: string; filename?: string; kind?: "image" | "file" }): Promise<{ id: string; url: string }>;
|
|
53
57
|
};
|
|
@@ -60,13 +64,14 @@ export const RUN_FUNCTIONS = [
|
|
|
60
64
|
"ask",
|
|
61
65
|
"feedback",
|
|
62
66
|
"say",
|
|
67
|
+
"edit",
|
|
63
68
|
"export",
|
|
64
69
|
"upload",
|
|
65
70
|
];
|
|
66
71
|
export const RUN_EXAMPLE = `const html = (tone) => \`<section data-part="hero" class="\${tone}"><h1>Writer</h1></section>\`;
|
|
67
72
|
await mockpit.publish({ mock: "writer", variant: "calm", html: html("calm") });
|
|
68
73
|
await mockpit.publish({ mock: "writer", variant: "bold", html: html("bold") });
|
|
69
|
-
const { url } = await mockpit.ask("writer", [{ id: "
|
|
74
|
+
const { url } = await mockpit.ask("writer", [{ id: "tone", text: "Calm or bold?",
|
|
70
75
|
options: [{ label: "Calm", variant: "calm" }, { label: "Bold", variant: "bold" }] }]);
|
|
71
76
|
// Tell the user where to look and end the turn; a later run reads mockpit.feedback().
|
|
72
77
|
return url;`;
|