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.
Files changed (45) hide show
  1. package/README.md +61 -45
  2. package/bin/demoData.js +3 -3
  3. package/bin/mockpit.js +327 -52
  4. package/dist/mcp/server.js +35 -0
  5. package/dist/server/apiViews.d.ts +308 -23
  6. package/dist/server/apiViews.js +60 -19
  7. package/dist/server/app.d.ts +4 -1
  8. package/dist/server/app.js +608 -119
  9. package/dist/server/codeWorker.js +2 -1
  10. package/dist/server/decisions.d.ts +5 -5
  11. package/dist/server/decisions.js +10 -9
  12. package/dist/server/designGuide.d.ts +6 -2
  13. package/dist/server/designGuide.js +77 -43
  14. package/dist/server/embeds.d.ts +1 -0
  15. package/dist/server/embeds.js +30 -4
  16. package/dist/server/feedbackBatch.d.ts +6 -6
  17. package/dist/server/feedbackBatch.js +18 -11
  18. package/dist/server/guidance.d.ts +23 -0
  19. package/dist/server/guidance.js +71 -0
  20. package/dist/server/index.js +10 -1
  21. package/dist/server/mcpHttp.d.ts +3 -0
  22. package/dist/server/mcpHttp.js +32 -0
  23. package/dist/server/mcpSpec.d.ts +2 -1
  24. package/dist/server/mcpSpec.js +74 -24
  25. package/dist/server/postSurfaces.js +3 -0
  26. package/dist/server/run.js +6 -1
  27. package/dist/server/runApi.d.ts +3 -3
  28. package/dist/server/runApi.js +16 -12
  29. package/dist/server/sqlStore.d.ts +10 -3
  30. package/dist/server/sqlStore.js +230 -16
  31. package/dist/server/types.d.ts +46 -17
  32. package/dist/server/types.js +34 -11
  33. package/extensions/mockpit.js +179 -16
  34. package/guide/packs/design-craft.md +35 -0
  35. package/guide/topics/asks.md +60 -56
  36. package/guide/topics/feedback.md +104 -74
  37. package/guide/topics/html.md +5 -11
  38. package/guide/topics/http.md +6 -3
  39. package/guide/topics/knobs.md +2 -2
  40. package/guide/topics/scripts.md +2 -2
  41. package/guide/topics/surfaces.md +28 -10
  42. package/package.json +1 -4
  43. package/skills/launch-video/SKILL.md +1 -1
  44. package/skills/mockpit/SKILL.md +18 -10
  45. package/viewer/dist/index.html +8 -7
@@ -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`");
@@ -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
  };
@@ -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; mark parts with data-part); publish again to " +
14
- "revise. A choice is several variants plus one ask that binds them. One render plus a " +
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
- flow: "Flow this screen is a step of; alone, only places it",
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 ask"),
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", "page"] },
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 marked parts' },
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 slot, or adds it after the others',
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 ask that binds them; options may also set knob values. Reusing an ask id replaces it.",
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 asks), flows, plus pending. With mock: its states, variants, asks with answers, parts, knobs and settled values (every answer's set and tuning sent so far), plus pending. Never consumes feedback.",
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 uses and is used in)",
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, mix, tuned knobs, part comments, comments, accepted/archived/dropped, or resolved with the asks it dismissed), delivered once, plus pending (viewer open, draft progress).",
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
- mix: bag,
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 comments"),
282
- comments: z.array(o({ seq: num, text: str })),
283
- accepted: z.array(inUseRef).describe("Accepted, each at the version put in use"),
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
- dropped: z.array(variantRef),
286
- resolved: z
319
+ closed: z
287
320
  .literal(true)
288
321
  .optional()
289
- .describe("The user resolved the mock: its open asks were closed unanswered"),
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 asks the resolution closed; don't ask them again"),
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
- resolved: z.literal(true).optional(),
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: str,
400
+ status: z.enum(["open", "inUse", "archived"]),
354
401
  version: num,
355
- acceptedVersion: num.optional().describe("Set when accepted: the version in use"),
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
- : zodParam(p);
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
@@ -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");
@@ -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 it 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 }[]; mix: Knobs; tuned: Knobs; comments: { part: string | null; text: string }[] };\n comments: { text: string; state: string | null; variant: string | null }[];\n accepted: { state: string | null; variant: string }[]; archived: { state: string | null; variant: string }[];\n};\ntype Pending = { mock: string; 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 alternative of a slot. */\n publish(v: Ref & { title?: string; kind?: \"component\" | \"page\"; html?: 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 export(v: Ref): Promise<{ states: { state: string | null; variant: string; version: number; html: 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", "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: \"look\", text: \"Which look?\",\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;";
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;
@@ -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. test/run.test.ts checks that every
6
- // host function named here exists and vice versa.
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 it with ask when nudged
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 }[]; mix: Knobs; tuned: Knobs; comments: { part: string | null; text: string }[] };
35
- comments: { text: string; state: string | null; variant: string | null }[];
36
- accepted: { state: string | null; variant: string }[]; archived: { state: string | null; variant: string }[];
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 alternative of a slot. */
46
- publish(v: Ref & { title?: string; kind?: "component" | "page"; html?: 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
+ * 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
- export(v: Ref): Promise<{ states: { state: string | null; variant: string; version: number; html: string }[]; reply: unknown }>;
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: "look", text: "Which look?",
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 MockKind, 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";
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
- resolvedAt: string | null;
71
+ closedAt: string | null;
65
72
  createdAt: string;
66
73
  updatedAt: string;
67
74
  } | null>;