mockpit 1.0.0 → 1.2.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 +12 -3
  2. package/bin/mockpit.js +47 -21
  3. package/dist/mcp/server.js +43 -4
  4. package/dist/server/apiViews.d.ts +11 -8
  5. package/dist/server/apiViews.js +21 -16
  6. package/dist/server/app.d.ts +3 -1
  7. package/dist/server/app.js +374 -67
  8. package/dist/server/appIcon.d.ts +22 -0
  9. package/dist/server/appIcon.js +43 -0
  10. package/dist/server/appIconPng.d.ts +1 -0
  11. package/dist/server/appIconPng.js +6 -0
  12. package/dist/server/askNudges.d.ts +3 -0
  13. package/dist/server/askNudges.js +40 -0
  14. package/dist/server/base64.d.ts +1 -0
  15. package/dist/server/base64.js +8 -0
  16. package/dist/server/designGuide.js +15 -13
  17. package/dist/server/eventLog.d.ts +27 -0
  18. package/dist/server/eventLog.js +187 -0
  19. package/dist/server/feedbackBatch.d.ts +16 -4
  20. package/dist/server/feedbackBatch.js +59 -9
  21. package/dist/server/handoff.d.ts +6 -0
  22. package/dist/server/handoff.js +38 -0
  23. package/dist/server/index.js +12 -0
  24. package/dist/server/knobs.js +2 -3
  25. package/dist/server/logFile.d.ts +2 -0
  26. package/dist/server/logFile.js +27 -0
  27. package/dist/server/mcpHttp.d.ts +2 -2
  28. package/dist/server/mcpHttp.js +11 -5
  29. package/dist/server/mcpSpec.d.ts +8 -0
  30. package/dist/server/mcpSpec.js +26 -1
  31. package/dist/server/run.js +2 -2
  32. package/dist/server/runApi.d.ts +1 -1
  33. package/dist/server/runApi.js +1 -1
  34. package/dist/server/sqlStore.d.ts +3 -1
  35. package/dist/server/sqlStore.js +40 -8
  36. package/dist/server/types.d.ts +25 -2
  37. package/dist/server/types.js +47 -4
  38. package/extensions/mockpit.js +24 -9
  39. package/guide/topics/asks.md +19 -7
  40. package/guide/topics/feedback.md +17 -7
  41. package/guide/topics/knobs.md +3 -3
  42. package/guide/topics/surfaces.md +8 -8
  43. package/package.json +2 -2
  44. package/skills/mockpit/SKILL.md +4 -2
  45. package/viewer/dist/index.html +9 -9
@@ -1,4 +1,5 @@
1
1
  import { execFileSync } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
2
3
  import { readFile } from "node:fs/promises";
3
4
  import { basename, resolve } from "node:path";
4
5
 
@@ -127,8 +128,19 @@ function resolveProjectName(cwd) {
127
128
  );
128
129
  }
129
130
 
131
+ // Names this client in the server's event log, with the version that would
132
+ // show it running behind the server.
133
+ const CLIENT = `pi/${(() => {
134
+ try {
135
+ return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
136
+ } catch {
137
+ return "unknown";
138
+ }
139
+ })()}`;
140
+
130
141
  function authHeaders(extra = {}) {
131
142
  return {
143
+ "x-mockpit-client": CLIENT,
132
144
  ...(process.env.MOCKPIT_TOKEN ? { authorization: `Bearer ${process.env.MOCKPIT_TOKEN}` } : {}),
133
145
  ...extra,
134
146
  };
@@ -208,6 +220,7 @@ function feedbackLines(feedback) {
208
220
  const lines = [];
209
221
  for (const batch of feedback ?? []) {
210
222
  const prefix = batch.mock ? `${batch.mock}: ` : "";
223
+ if (batch.handoff) lines.push(`- ${prefix}${batch.handoff}`);
211
224
  const reply = batch.reply;
212
225
  if (reply) {
213
226
  for (const a of reply.asks ?? []) {
@@ -234,17 +247,19 @@ function feedbackLines(feedback) {
234
247
  return lines;
235
248
  }
236
249
 
237
- // What the user is doing right now, so the agent can say "take your time".
238
- // viewerOpen is workspace-wide, so it is said once rather than per mock.
250
+ // What the user is doing right now, per mock, so the agent can say "take your time".
239
251
  function pendingLines(pending) {
240
- const rows = [pending ?? []].flat();
241
- const lines = rows
242
- .filter((p) => p.draft)
243
- .map(
244
- (p) =>
245
- `- ${p.mock}: the user is answering (${p.draft.answered} of ${p.draft.of} answered, ${p.draft.comments} comments)`,
252
+ const lines = [pending ?? []]
253
+ .flat()
254
+ .flatMap((p) =>
255
+ p.draft
256
+ ? [
257
+ `- ${p.mock}: the user is answering (${p.draft.answered} of ${p.draft.of} answered, ${p.draft.comments} comments)`,
258
+ ]
259
+ : p.viewerOpen
260
+ ? [`- ${p.mock}: open in the viewer`]
261
+ : [],
246
262
  );
247
- if (lines.length === 0 && rows.some((p) => p.viewerOpen)) lines.push("- the viewer is open");
248
263
  return lines.length ? ["Pending:", ...lines] : [];
249
264
  }
250
265
 
@@ -7,9 +7,12 @@ where to look and end your turn.
7
7
 
8
8
  ```sh
9
9
  mockpit ask --mock writer "Which look?" --option Quiet=quiet --option Dark=dark --id look
10
+ mockpit ask --mock writer "Trim?" --option Above=knob:trim.position=top --option Below=knob:trim.position=bottom
10
11
  mockpit ask --mock writer --asks asks.json
11
12
  ```
12
13
 
14
+ `=knob:` sets one knob; for an option that sets several, use `--asks` JSON.
15
+
13
16
  ## Shape
14
17
 
15
18
  The same JSON goes to `--asks`, MCP `ask` and `POST /api/mocks/:id/asks`:
@@ -60,10 +63,14 @@ An option binds to one of:
60
63
  - a knob `set`: picking it applies those values live.
61
64
  - nothing: a plain pill.
62
65
 
66
+ Options that change how something looks carry a `variant` or a knob `set`,
67
+ so the user sees them live. Plain pills only for what can't be seen.
68
+
63
69
  The viewer shows a picture of each option it can render. `multi: true` allows
64
70
  several answers. Option ids default to the slugged label. Reusing an ask `id`
65
71
  replaces that ask. The viewer adds an "Other…" write-in and a note to every
66
- ask, so don't add your own; the option id `other` is reserved.
72
+ ask (both take attached images), so don't add your own; the option id `other`
73
+ is reserved.
67
74
 
68
75
  ## The Look ask
69
76
 
@@ -72,16 +79,21 @@ ask it first. Questions appear in the order you ask them. This one is titled
72
79
  "Look" and unlocks Mix, which lets the user borrow a part from another look.
73
80
  Variants without a Look ask get a plain switcher in the frame header.
74
81
 
82
+ A variant decides everything it differs on; don't re-ask it. Ask other axes as
83
+ knob `set`s.
84
+
75
85
  ## When you forget to ask
76
86
 
77
87
  A publish that leaves a state with two or more open variants and no ask
78
88
  binding them returns a nudge and a ready `suggestedAsk` (mock-wide when the
79
- variant names line up across states, else for that state). Send it as is with
80
- `ask`, or write your own question with options bound to the variants. The CLI
81
- prints the `mockpit ask` command for you.
82
-
83
- Until an ask binds them, the viewer shows a built-in "Which one?" with a
84
- picture of each variant, so the user can still choose in one Send. Its answer
89
+ variant names line up across states, else for that state), or your existing
90
+ ask plus the new variants. Send it as is with `ask`, or write your own question
91
+ with options bound to the variants. The CLI prints the `mockpit ask` command
92
+ for you. Sending it stops the nudge and keeps the answered Look ask on the
93
+ mock; the built-in one is gone once answered.
94
+
95
+ Until an ask binds them, the viewer shows a built-in Look ask with a picture
96
+ of each variant, so the user can still choose in one Send. Its answer
85
97
  reads like any other ask under the reserved id `variant` (per state:
86
98
  `variant:<state>`), and accepts and archives per state. Your own ask over
87
99
  those variants replaces it.
@@ -24,7 +24,8 @@ It returns at once:
24
24
  "ask": "trim",
25
25
  "text": "Trim above or below?",
26
26
  "chosen": [{ "id": "below", "label": "Below", "set": { "trim.position": "bottom" } }],
27
- "note": "below on mobile only"
27
+ "note": "below on mobile only",
28
+ "noteImages": [{ "id": "k3Jf9xQ2aLw", "url": "http://localhost:8228/a/k3Jf9xQ2aLw" }]
28
29
  },
29
30
  {
30
31
  "ask": "lang",
@@ -35,6 +36,7 @@ It returns at once:
35
36
  "mix": { "body": "quiet" },
36
37
  "tuned": { "body.size": 19 },
37
38
  "comments": [{ "part": "title", "state": "Writing", "text": "bigger" }],
39
+ "reactions": ["👍 part \"title\" (variant dark, state Writing)"],
38
40
  "text": "close, go dark"
39
41
  },
40
42
  "comments": [],
@@ -53,8 +55,8 @@ It returns at once:
53
55
  ```
54
56
 
55
57
  `feedback` holds one batch per mock, in delivery order. `pending` is what the
56
- user is doing right now, per mock: `viewerOpen` says a browser has the
57
- workspace open, `draft` is their unsent progress (`null` when there is none).
58
+ user is doing right now, per mock: `viewerOpen` says a browser has that
59
+ mock on screen, `draft` is their unsent progress (`null` when there is none).
58
60
  An empty `feedback` with a draft means "still answering": tell the user to take
59
61
  their time, don't ask again. `mockpit read` returns `pending` too, without
60
62
  taking any feedback.
@@ -64,18 +66,26 @@ taking any feedback.
64
66
  1. `answers` and `asks` decide structure. A variant-bound answer has already
65
67
  accepted that variant and archived its siblings (`accepted`, `archived`).
66
68
  Stop iterating on the archived ones. The ask `variant` (per state
67
- `variant:<state>`) is the viewer's built-in "Which one?", read like any
69
+ `variant:<state>`) is the viewer's built-in Look ask, read like any
68
70
  other. A `chosen` entry with `other: true` is
69
71
  the user's own answer (its `label`), not one of your options; it flips no
70
72
  variant and sets no knob. `note` qualifies the answer and may come with
71
- nothing chosen.
73
+ nothing chosen. The user can attach images (screenshots, references) to
74
+ either: `images` on the `other` choice, `noteImages` on the ask, each
75
+ `{id, url}`; the text may then be `""`. Look at every one. Over MCP they
76
+ also arrive as image content in the same result; elsewhere fetch the `url`
77
+ (with your token, if the server has one) and open the file.
72
78
  2. `tuned` holds knob values. Write them back into the source as the new
73
79
  defaults. tunekit's skill (`skills/tunekit` in the tunekit package) covers
74
80
  applying copied values.
75
81
  3. `mix` maps a part to the variant it should come from. Compose that part from
76
- the other look into the chosen one.
82
+ the other look into the chosen one. If `mix` conflicts with an answer, the
83
+ answer wins.
77
84
  4. `comments` are anchored on a part in a state (`part: null` means no part).
78
- Address each one.
85
+ Address each one. `reactions` are one-tap verdicts, one line each:
86
+ 👍 keep it, 👎 rework it, "more like this" go further that way. A reaction
87
+ without a part is about the whole variant; one may end with the user's
88
+ note in quotes. Reactions never also appear in `comments`.
79
89
  5. `decision` (`{kind: accept|revise|drop, state, variant}`) comes instead of
80
90
  answers when the mock has no asks and the user pressed Accept, Revise or
81
91
  Drop.
@@ -2,9 +2,9 @@
2
2
 
3
3
  A knob is a value the user tunes live on one render. Use one when one render
4
4
  plus a control shows the choice. A choice is several variants plus one ask
5
- that binds them; for that, publish variants and ask instead (topic `asks`). A knob with three or fewer discrete options (a
6
- toggle, a short select) gets a nudge in the publish response; keep it only if
7
- one render plus that control really shows the choice.
5
+ that binds them; for that, publish variants and ask instead (topic `asks`).
6
+ A 2–3 option knob is a choice: ask with options that `set` it. Publishing
7
+ one returns that nudge.
8
8
 
9
9
  ## Declaring
10
10
 
@@ -90,11 +90,11 @@ bytes dedupe. An asset lives as long as something references it. The limit is
90
90
 
91
91
  Each thing you add lights up its part of the viewer.
92
92
 
93
- | you publish | the user gets |
94
- | ---------------------- | ------------------------------------------------------ |
95
- | a plain mock, any kind | the stage and Thread, with Accept / Revise / Drop |
96
- | several variants | a switcher, plus a built-in "Which one?" until you ask |
97
- | asks | Questions: picture options on the stage, then Send |
98
- | `data-part` | Tune's component list and comments anchored on parts |
99
- | `knobs` | Tune's controls, live on the stage |
100
- | several states | the state strip under the stage |
93
+ | you publish | the user gets |
94
+ | ---------------------- | ---------------------------------------------------- |
95
+ | a plain mock, any kind | the stage and Thread, with Accept / Revise / Drop |
96
+ | several variants | a switcher, plus a built-in Look ask until you ask |
97
+ | asks | Questions: picture options on the stage, then Send |
98
+ | `data-part` | Tune's component list and comments anchored on parts |
99
+ | `knobs` | Tune's controls, live on the stage |
100
+ | several states | the state strip under the stage |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mockpit",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "A design-decision loop for terminal coding agents — agents publish mocks with states and variants, ask questions and expose knobs; you answer, tune and comment, then send one reply.",
5
5
  "keywords": [
6
6
  "agent-tools",
@@ -82,7 +82,7 @@
82
82
  "lint:fix": "oxlint . --fix",
83
83
  "changeset": "changeset",
84
84
  "changeset:status": "changeset status",
85
- "release:version": "changeset version",
85
+ "release:version": "changeset version && npm install --package-lock-only --ignore-scripts",
86
86
  "typecheck": "tsc --noEmit && tsc -p tsconfig.workers.json --noEmit && tsc -p tsconfig.viewer.json --noEmit",
87
87
  "clean:dist": "node scripts/clean-dist.ts",
88
88
  "build": "npm run clean:dist && tsc -p tsconfig.build.json && npm run build:viewer",
@@ -19,8 +19,10 @@ The loop never waits for the user:
19
19
  2. `mockpit publish --mock <slug> --state "<user's words>" --variant <v> --html f.html`,
20
20
  once per variant; mark parts with `data-part`.
21
21
  3. A choice is several variants plus one ask that binds them: `mockpit ask`
22
- with options bound to variants. One render plus a control: `--knobs`. When a
23
- publish result nudges, send its `suggestedAsk`.
22
+ with options bound to variants. A variant decides everything it differs on;
23
+ don't re-ask it. Visual options carry a `variant` or knob `set`. One render
24
+ plus a control: `--knobs`. When a publish result nudges, send its
25
+ `suggestedAsk`.
24
26
  4. Tell the user in one line where to look, then end your turn. The question
25
27
  itself lives in the mock (`ask`), never in chat. The user answers in the
26
28
  browser at their own pace. Never poll.