mockpit 1.2.0 → 1.3.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 +13 -8
- package/bin/mockpit.js +116 -16
- package/dist/mcp/server.js +10 -7
- package/dist/server/apiViews.d.ts +118 -17
- package/dist/server/apiViews.js +66 -8
- package/dist/server/app.d.ts +2 -1
- package/dist/server/app.js +917 -144
- package/dist/server/decisions.d.ts +23 -0
- package/dist/server/decisions.js +99 -0
- package/dist/server/designGuide.js +45 -10
- package/dist/server/embeds.d.ts +25 -0
- package/dist/server/embeds.js +118 -0
- package/dist/server/eventLog.js +2 -1
- package/dist/server/feedbackBatch.d.ts +12 -1
- package/dist/server/feedbackBatch.js +20 -3
- package/dist/server/fills.d.ts +41 -0
- package/dist/server/fills.js +328 -0
- package/dist/server/index.js +15 -1
- package/dist/server/knobs.d.ts +6 -2
- package/dist/server/knobs.js +11 -1
- package/dist/server/mcpHttp.d.ts +3 -0
- package/dist/server/mcpHttp.js +3 -0
- package/dist/server/mcpSpec.d.ts +13 -13
- package/dist/server/mcpSpec.js +73 -8
- package/dist/server/postSurfaces.js +3 -12
- package/dist/server/richRender.js +3 -1
- package/dist/server/run.js +4 -0
- package/dist/server/runApi.d.ts +1 -1
- package/dist/server/runApi.js +4 -3
- package/dist/server/sqlStore.d.ts +89 -4
- package/dist/server/sqlStore.js +149 -23
- 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 +73 -11
- package/dist/server/types.js +42 -2
- package/dist/server/viewerRequest.d.ts +2 -0
- package/dist/server/viewerRequest.js +14 -0
- package/guide/topics/asks.md +50 -3
- package/guide/topics/feedback.md +89 -11
- package/guide/topics/html.md +5 -1
- package/guide/topics/http.md +2 -1
- package/guide/topics/knobs.md +2 -1
- package/guide/topics/surfaces.md +24 -8
- package/package.json +4 -13
- package/skills/launch-video/SKILL.md +1 -1
- package/skills/mockpit/SKILL.md +12 -4
- package/viewer/dist/index.html +8 -8
- package/dist/server/slots.d.ts +0 -14
- package/dist/server/slots.js +0 -71
package/README.md
CHANGED
|
@@ -51,11 +51,14 @@ mockpit turns that into a design-decision loop:
|
|
|
51
51
|
panel goes, which of two layouts. The question lives in the mock, not in
|
|
52
52
|
chat; the agent tells you where to look and ends its turn. Options bound to
|
|
53
53
|
a variant or to knob values render as pictures; hovering one previews it on
|
|
54
|
-
the stage, clicking picks it
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
the stage, clicking picks it, and the part it changed is outlined. A
|
|
55
|
+
region the agent drew several ways (a slot: one `data-part`, a `data-fill`
|
|
56
|
+
per alternative) is asked about once; a slot inside a fill is asked only
|
|
57
|
+
while that fill is chosen. Variants the agent forgot to ask about get a
|
|
58
|
+
built-in **Which one?**.
|
|
57
59
|
3. **Tune.** Parts the agent marked are selectable on the stage. Tune lists
|
|
58
|
-
them with the knobs it declared for each,
|
|
60
|
+
them with the knobs it declared for each, a **Borrow** line for a part
|
|
61
|
+
another look draws differently, plus a comment field. Presets
|
|
59
62
|
save a set of tuned values in your browser.
|
|
60
63
|
4. **Send.** Everything above is a draft (it survives a reload) until one
|
|
61
64
|
**Send**. A mock with a single variant and no questions gets **Accept /
|
|
@@ -70,10 +73,12 @@ mockpit turns that into a design-decision loop:
|
|
|
70
73
|
|
|
71
74
|
**Home** lists the project's mocks with a thumbnail, their states and how many
|
|
72
75
|
questions are open, with "Answer next ›" to jump to the first one. Dark and
|
|
73
|
-
light themes, toggled from the top bar. On a phone or
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
light themes, toggled from the top bar. On a phone or an iPad in portrait the
|
|
77
|
+
design is a canvas you pan and pinch, at each width the agent names (or one you
|
|
78
|
+
pick: Phone 390, Tablet 744, Desktop 820, Wide 1280) side by side, and the
|
|
79
|
+
questions float over it in a card you can snap to a corner, a side or the
|
|
80
|
+
bottom, or tuck against an edge. On a desktop, Inspect opens the stage full
|
|
81
|
+
screen to pan and pinch-zoom it.
|
|
77
82
|
|
|
78
83
|
## Quick start
|
|
79
84
|
|
package/bin/mockpit.js
CHANGED
|
@@ -62,11 +62,17 @@ verbs:
|
|
|
62
62
|
alone keeps a surface, a missing id removes it
|
|
63
63
|
--parts <name=file|-> instead: replace just that data-part's element in
|
|
64
64
|
the latest version (repeatable; name#key targets one instance)
|
|
65
|
+
--fills <part#fill=file|-> instead: replace or add one fill of a slot in
|
|
66
|
+
the latest version (repeatable)
|
|
65
67
|
--title <t> mock title
|
|
66
68
|
--kind <k> component|page (default component)
|
|
67
69
|
--knobs <json|file> knobs in tunekit usePane shape, keyed by path
|
|
68
70
|
--from <N> branch from version N
|
|
69
71
|
--prompt <text> what prompted this version
|
|
72
|
+
--note <text> one line on what this variant is, shown beside it
|
|
73
|
+
--widths <list> device widths to show it at, e.g. 390,1280
|
|
74
|
+
--flow <name> [--step <N>] make this screen step N of a flow (0 takes
|
|
75
|
+
it out); with no html it only places the screen
|
|
70
76
|
--project <name> project (default: git remote, else directory name)
|
|
71
77
|
--kit <id> opt the html surface into a kit (repeatable; see "mockpit kits")
|
|
72
78
|
--md/--mermaid/--diff/--terminal/--data/--code/--image <file|->
|
|
@@ -75,7 +81,7 @@ verbs:
|
|
|
75
81
|
mockpit ask --mock <slug> "<question>" --option <label[=variant]> ...
|
|
76
82
|
ask the user; bind options to variants
|
|
77
83
|
--option <l[=v]> an option (repeatable); "=variant" binds it to a variant,
|
|
78
|
-
"=knob:path=value" sets one knob
|
|
84
|
+
"=variant@3" to its version 3, "=knob:path=value" sets one knob
|
|
79
85
|
--scope <s> mock|state|part (default mock)
|
|
80
86
|
--state <label> the state a state-scoped ask is about
|
|
81
87
|
--part <name> the part a part-scoped ask is about
|
|
@@ -156,6 +162,7 @@ mockpit icons remove <set> [<set>...]
|
|
|
156
162
|
publish: `mockpit publish --mock <slug> --html <file> [options]
|
|
157
163
|
mockpit publish --mock <slug> --surfaces <json|file> [options]
|
|
158
164
|
mockpit publish --mock <slug> --parts <name=file|-> ... [options]
|
|
165
|
+
mockpit publish --mock <slug> --fills <part#fill=file|-> ... [options]
|
|
159
166
|
Creates the mock, state or variant, or the next version of an existing one.
|
|
160
167
|
--state <label> state in the user's words (omit for a single-state mock)
|
|
161
168
|
--variant <name> variant label (default "default")
|
|
@@ -163,30 +170,48 @@ mockpit publish --mock <slug> --parts <name=file|-> ... [options]
|
|
|
163
170
|
a missing id removes it, order is the list order
|
|
164
171
|
--parts <name=file|-> replace only the element carrying data-part="name"
|
|
165
172
|
(name#key for one instance) in the latest version; repeatable
|
|
173
|
+
--fills <part#fill=file|-> replace the element carrying data-part="part"
|
|
174
|
+
data-fill="fill" in the latest version, or add it after
|
|
175
|
+
that slot's other fills; the marks are added if missing
|
|
166
176
|
--title <t> mock title
|
|
167
177
|
--kind <k> component|page (default component)
|
|
168
178
|
--knobs <json|file> knobs in tunekit usePane shape
|
|
169
179
|
--from <N> branch from version N
|
|
180
|
+
--fork <v[@N]> start this new variant from sibling v (its version N, else
|
|
181
|
+
its latest); alone it copies v, with --parts/--fills it
|
|
182
|
+
edits the copy
|
|
170
183
|
--prompt <text> what prompted this version
|
|
184
|
+
--note <text> one line on what this variant is, shown beside it (never
|
|
185
|
+
inside the design)
|
|
186
|
+
--widths <list> device widths the mock is judged at, side by side, e.g.
|
|
187
|
+
390,1280 (the ones the user ships to)
|
|
188
|
+
--flow <name> a flow this screen is a step of: the screens the user moves
|
|
189
|
+
between, in order, across mocks; without html it only
|
|
190
|
+
places the existing screen
|
|
191
|
+
--step <N> its 1-based place in the flow (moves it if it is there);
|
|
192
|
+
0 takes it out; omitted, it joins at the end
|
|
171
193
|
--project <name> project (default: git remote, else directory name)
|
|
172
194
|
--md/--mermaid/--diff/--terminal/--data/--code/--image <file> more surfaces
|
|
173
195
|
--json / --quiet
|
|
174
196
|
Prints the parts found per state, parts that vanished or were renamed,
|
|
175
197
|
nudges, a ready-to-run "mockpit ask" when variants have no ask, and feedback.`,
|
|
176
198
|
ask: `mockpit ask --mock <slug> "<question>" --option <label[=variant]> ... [--scope mock|state|part]
|
|
177
|
-
[--state s] [--part p] [--multi] [--id id]
|
|
199
|
+
[--state s] [--part p] [--multi] [--id id] [--after <ask id>]
|
|
178
200
|
mockpit ask --mock <slug> --asks <json|file>
|
|
179
201
|
Ask the user. A choice is several variants plus one ask that binds them.
|
|
180
202
|
Then tell the user where to look and end your turn.`,
|
|
181
|
-
read: `mockpit read [slug] [--project <name>] [--body] [--history]
|
|
182
|
-
No slug: one line per mock (states, variants, open asks) plus pending, what
|
|
203
|
+
read: `mockpit read [slug] [--project <name>] [--body] [--history] [--tree]
|
|
204
|
+
No slug: one line per mock (states, variants, open asks), one per flow, plus pending, what
|
|
183
205
|
the user is doing now. A slug: the mock's states, variants, asks with
|
|
184
|
-
answers, parts, knobs and
|
|
185
|
-
--body includes surfaces, --history version rows.
|
|
206
|
+
answers, parts, knobs and settled values, plus its pending, as JSON.
|
|
207
|
+
--body includes surfaces, --history version rows. --tree (no slug): the
|
|
208
|
+
project as a tree, each mock's variants with status and forkedFrom, and the
|
|
209
|
+
mocks it uses and is used in, as JSON. Never consumes feedback.`,
|
|
186
210
|
feedback: `mockpit feedback [--project <name>] [--session <id>]
|
|
187
211
|
Returns at once with {feedback, pending} as JSON: one batch per mock the user
|
|
188
212
|
sent since you last heard ({mock, reply: {asks, mix, tuned, comments},
|
|
189
|
-
comments, accepted, archived}
|
|
213
|
+
comments, accepted, archived, dropped}, or {mock, resolved: true, dismissed} when the
|
|
214
|
+
user closed it unanswered), delivered once across feedback, watch and
|
|
190
215
|
every write; and pending ({mock, viewerOpen, draft}) so you can tell the
|
|
191
216
|
user "take your time" or "2 of 3 answered". Run it when the user says they
|
|
192
217
|
answered.`,
|
|
@@ -203,7 +228,8 @@ mockpit publish --mock <slug> --parts <name=file|-> ... [options]
|
|
|
203
228
|
Claude Code, arm it under Monitor after asking, then end the turn).
|
|
204
229
|
Delivery is shared with feedback and every write: each Send arrives once.`,
|
|
205
230
|
export: `mockpit export --mock <slug> [--state s] [--variant v] [--out <dir>]
|
|
206
|
-
Write index.html + history.json per state to .mockpit/accepted/<mock>/<state
|
|
231
|
+
Write index.html + history.json per state to .mockpit/accepted/<mock>/<state>/;
|
|
232
|
+
with slots, index.html is the chosen screen, plus source.html and decisions.json.`,
|
|
207
233
|
upload: `mockpit upload <file> [--kind image|file] [--session <id>]
|
|
208
234
|
Upload an asset; prints its id and URL. Use the id as an image surface's assetId.`,
|
|
209
235
|
guide: `mockpit guide [--topic <id>] [--project <name>]
|
|
@@ -810,6 +836,7 @@ function printPublished(result, flags) {
|
|
|
810
836
|
console.log(`parts${state ? ` (${state})` : ""}: ${parts.map((p) => p.name).join(", ")}`);
|
|
811
837
|
}
|
|
812
838
|
if (result.applied?.length) console.log(`applied: ${result.applied.join(", ")}`);
|
|
839
|
+
if (result.flow) console.log(flowLine(result.flow));
|
|
813
840
|
const changes = result.partChanges;
|
|
814
841
|
if (changes?.renamed?.length) {
|
|
815
842
|
console.log(`renamed: ${changes.renamed.map((r) => `${r.from} → ${r.to}`).join(", ")}`);
|
|
@@ -822,6 +849,11 @@ function printPublished(result, flags) {
|
|
|
822
849
|
printFeedback(result.feedback);
|
|
823
850
|
}
|
|
824
851
|
|
|
852
|
+
function flowLine(flow) {
|
|
853
|
+
const steps = flow.steps.map((x) => (x.state === null ? x.mock : `${x.mock}/${x.state}`));
|
|
854
|
+
return `flow ${flow.name}: ${steps.length ? steps.join(" → ") : "empty, removed"}`;
|
|
855
|
+
}
|
|
856
|
+
|
|
825
857
|
// Only speak when there IS feedback: an empty line per write is pure token
|
|
826
858
|
// cost, and the cursor guarantees anything pending arrives on some write.
|
|
827
859
|
function printFeedback(feedback) {
|
|
@@ -852,9 +884,15 @@ function parsePublishFlags() {
|
|
|
852
884
|
html: { type: "string" },
|
|
853
885
|
surfaces: { type: "string" },
|
|
854
886
|
parts: { type: "string", multiple: true },
|
|
887
|
+
fills: { type: "string", multiple: true },
|
|
855
888
|
knobs: { type: "string" },
|
|
856
889
|
from: { type: "string" },
|
|
890
|
+
fork: { type: "string" },
|
|
857
891
|
prompt: { type: "string" },
|
|
892
|
+
note: { type: "string" },
|
|
893
|
+
widths: { type: "string" },
|
|
894
|
+
flow: { type: "string" },
|
|
895
|
+
step: { type: "string" },
|
|
858
896
|
kit: { type: "string", multiple: true },
|
|
859
897
|
layout: { type: "string" },
|
|
860
898
|
md: { type: "string", multiple: true },
|
|
@@ -873,18 +911,27 @@ function parsePublishFlags() {
|
|
|
873
911
|
|
|
874
912
|
// `--parts name=file` (repeatable; `name#key` targets one instance): the new
|
|
875
913
|
// outer html of one part, spliced server-side into the latest version.
|
|
876
|
-
|
|
914
|
+
// `--fills part#fill=file` is the same for one alternative of a slot.
|
|
915
|
+
function readPartFlags(values, slug, flag = "parts") {
|
|
877
916
|
if (!values?.length) return undefined;
|
|
878
|
-
const example =
|
|
917
|
+
const example =
|
|
918
|
+
flag === "parts"
|
|
919
|
+
? `mockpit publish --mock ${slug} --parts body=body.html`
|
|
920
|
+
: `mockpit publish --mock ${slug} --fills entry#skeleton=skeleton.html`;
|
|
879
921
|
const parts = {};
|
|
880
922
|
let stdin = false;
|
|
881
923
|
for (const raw of values) {
|
|
882
924
|
const at = raw.indexOf("=");
|
|
883
|
-
if (at <= 0 || at === raw.length - 1)
|
|
925
|
+
if (at <= 0 || at === raw.length - 1) {
|
|
926
|
+
die(
|
|
927
|
+
`--${flag} needs ${flag === "parts" ? "name" : "part#fill"}=file (got "${raw}")`,
|
|
928
|
+
example,
|
|
929
|
+
);
|
|
930
|
+
}
|
|
884
931
|
const name = raw.slice(0, at);
|
|
885
932
|
const file = raw.slice(at + 1);
|
|
886
933
|
if (file === "-") {
|
|
887
|
-
if (stdin) die(
|
|
934
|
+
if (stdin) die(`only one --${flag} can read stdin`, example);
|
|
888
935
|
stdin = true;
|
|
889
936
|
} else if (!existsSync(file)) die(`cannot read ${file}`, `ls ${file}`);
|
|
890
937
|
parts[name] = readContent(file);
|
|
@@ -900,6 +947,13 @@ async function publishMock() {
|
|
|
900
947
|
if (from !== undefined && !Number.isInteger(from)) {
|
|
901
948
|
die(`--from must be a version number (got "${flags.from}")`, `mockpit read ${slug} --history`);
|
|
902
949
|
}
|
|
950
|
+
const step = flags.step === undefined ? undefined : Number(flags.step);
|
|
951
|
+
if (step !== undefined && !(Number.isInteger(step) && step >= 0)) {
|
|
952
|
+
die(
|
|
953
|
+
`--step must be a place in the flow, 1 or more, or 0 (got "${flags.step}")`,
|
|
954
|
+
`mockpit read`,
|
|
955
|
+
);
|
|
956
|
+
}
|
|
903
957
|
const file = flags.html ?? positionals[0];
|
|
904
958
|
const composed =
|
|
905
959
|
file !== undefined || tokens.some((t) => t.kind === "option" && SURFACE_FLAGS.has(t.name));
|
|
@@ -907,16 +961,18 @@ async function publishMock() {
|
|
|
907
961
|
composed && "--html",
|
|
908
962
|
flags.surfaces !== undefined && "--surfaces",
|
|
909
963
|
flags.parts?.length && "--parts",
|
|
964
|
+
flags.fills?.length && "--fills",
|
|
910
965
|
].filter(Boolean);
|
|
911
966
|
if (given.length > 1) {
|
|
912
967
|
die(
|
|
913
|
-
`pass one of --html, --surfaces or --
|
|
968
|
+
`pass one of --html, --surfaces, --parts or --fills, not ${given.join(" and ")}`,
|
|
914
969
|
`mockpit publish --help`,
|
|
915
970
|
);
|
|
916
971
|
}
|
|
917
972
|
// Checked before the session exists so a typo'd path leaves nothing behind on the server.
|
|
918
973
|
if (file && file !== "-" && !existsSync(file)) die(`cannot read ${file}`, `ls ${file}`);
|
|
919
974
|
const parts = readPartFlags(flags.parts, slug);
|
|
975
|
+
const fills = readPartFlags(flags.fills, slug, "fills");
|
|
920
976
|
let surfaces =
|
|
921
977
|
flags.surfaces === undefined ? undefined : readJsonFlag("surfaces", flags.surfaces);
|
|
922
978
|
if (surfaces !== undefined && !Array.isArray(surfaces)) {
|
|
@@ -936,7 +992,7 @@ async function publishMock() {
|
|
|
936
992
|
}
|
|
937
993
|
surfaces.push(...(await surfacesFromFlags(flags, tokens, { session, layout: flags.layout })));
|
|
938
994
|
}
|
|
939
|
-
if (!surfaces && !parts)
|
|
995
|
+
if (!surfaces && !parts && !fills && !flags.fork && flags.flow === undefined)
|
|
940
996
|
die(`no html for ${slug}`, `mockpit publish --mock ${slug} --html <file>`);
|
|
941
997
|
const body = {
|
|
942
998
|
session,
|
|
@@ -948,9 +1004,20 @@ async function publishMock() {
|
|
|
948
1004
|
kind: flags.kind,
|
|
949
1005
|
...(flags.knobs !== undefined && { knobs: readJsonFlag("knobs", flags.knobs) }),
|
|
950
1006
|
...(from !== undefined && { from }),
|
|
1007
|
+
...(flags.fork !== undefined && { fork: flags.fork }),
|
|
951
1008
|
prompt: flags.prompt,
|
|
1009
|
+
note: flags.note,
|
|
1010
|
+
...(flags.widths !== undefined && {
|
|
1011
|
+
widths: flags.widths
|
|
1012
|
+
.split(",")
|
|
1013
|
+
.filter((w) => w.trim())
|
|
1014
|
+
.map((w) => Number(w)),
|
|
1015
|
+
}),
|
|
1016
|
+
...(flags.flow !== undefined && { flow: flags.flow }),
|
|
1017
|
+
...(step !== undefined && { step }),
|
|
952
1018
|
...(surfaces && { surfaces }),
|
|
953
1019
|
...(parts && { parts }),
|
|
1020
|
+
...(fills && { fills }),
|
|
954
1021
|
};
|
|
955
1022
|
const result = await api(
|
|
956
1023
|
"/api/mocks",
|
|
@@ -962,6 +1029,7 @@ async function publishMock() {
|
|
|
962
1029
|
}
|
|
963
1030
|
|
|
964
1031
|
// "Quiet" is an unbound option; "Quiet=quiet" binds it to the quiet variant;
|
|
1032
|
+
// "Before=quiet@2" to its version 2 ("=@2" names the state's only variant);
|
|
965
1033
|
// "Above=knob:trim.position=top" sets one knob. The value is JSON when it
|
|
966
1034
|
// parses (17, true), else a string, so slider and toggle values keep their type.
|
|
967
1035
|
function parseOption(raw) {
|
|
@@ -980,7 +1048,11 @@ function parseOption(raw) {
|
|
|
980
1048
|
}
|
|
981
1049
|
const at = raw.lastIndexOf("=");
|
|
982
1050
|
if (at <= 0) return { label: raw };
|
|
983
|
-
|
|
1051
|
+
const label = raw.slice(0, at);
|
|
1052
|
+
const bound = raw.slice(at + 1);
|
|
1053
|
+
const pin = /^(.*)@(\d+)$/.exec(bound);
|
|
1054
|
+
if (!pin) return { label, variant: bound };
|
|
1055
|
+
return { label, ...(pin[1] ? { variant: pin[1] } : {}), version: Number(pin[2]) };
|
|
984
1056
|
}
|
|
985
1057
|
|
|
986
1058
|
// One feedback batch, one line — for a background monitor's notifications.
|
|
@@ -1005,6 +1077,12 @@ function watchLines(batch) {
|
|
|
1005
1077
|
const lines = [];
|
|
1006
1078
|
const name = batch.mock ?? "a mock";
|
|
1007
1079
|
if (batch.handoff) lines.push(`mockpit handoff on ${name}: ${batch.handoff}`);
|
|
1080
|
+
if (batch.resolved) {
|
|
1081
|
+
const n = batch.dismissed?.length ?? 0;
|
|
1082
|
+
lines.push(
|
|
1083
|
+
`mockpit resolved ${name}: ${n ? `${n} question${n === 1 ? "" : "s"} dismissed unanswered (${batch.dismissed.map((d) => d.text || d.ask).join("; ")}); don't ask again` : "closed by the user"}`,
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1008
1086
|
if (batch.reply) {
|
|
1009
1087
|
const r = batch.reply;
|
|
1010
1088
|
const bits = [
|
|
@@ -1340,6 +1418,7 @@ const commands = {
|
|
|
1340
1418
|
part: { type: "string" },
|
|
1341
1419
|
multi: { type: "boolean" },
|
|
1342
1420
|
id: { type: "string" },
|
|
1421
|
+
after: { type: "string" },
|
|
1343
1422
|
asks: { type: "string" },
|
|
1344
1423
|
},
|
|
1345
1424
|
});
|
|
@@ -1377,6 +1456,7 @@ const commands = {
|
|
|
1377
1456
|
...(flags.state && { state: flags.state }),
|
|
1378
1457
|
...(flags.part && { part: flags.part }),
|
|
1379
1458
|
...(flags.multi && { multi: true }),
|
|
1459
|
+
...(flags.after && { after: flags.after }),
|
|
1380
1460
|
},
|
|
1381
1461
|
];
|
|
1382
1462
|
}
|
|
@@ -1414,10 +1494,20 @@ const commands = {
|
|
|
1414
1494
|
project: { type: "string" },
|
|
1415
1495
|
body: { type: "boolean" },
|
|
1416
1496
|
history: { type: "boolean" },
|
|
1497
|
+
tree: { type: "boolean" },
|
|
1417
1498
|
},
|
|
1418
1499
|
});
|
|
1419
1500
|
const project = resolveProject(flags).name;
|
|
1420
1501
|
if (positionals.length > 1) die("read takes one mock slug", "mockpit read writer");
|
|
1502
|
+
if (flags.tree) {
|
|
1503
|
+
return out(
|
|
1504
|
+
await api(
|
|
1505
|
+
`/api/projects/${encodeURIComponent(project)}/tree`,
|
|
1506
|
+
{},
|
|
1507
|
+
{ report: "die", fix: `mockpit read --project ${project}` },
|
|
1508
|
+
),
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1421
1511
|
if (positionals.length === 1) {
|
|
1422
1512
|
const slug = slugify(positionals[0]);
|
|
1423
1513
|
return out(
|
|
@@ -1445,9 +1535,10 @@ const commands = {
|
|
|
1445
1535
|
for (const m of mocks) {
|
|
1446
1536
|
const states = m.states.length ? m.states.join(" / ") : "single state";
|
|
1447
1537
|
console.log(
|
|
1448
|
-
` ${m.slug} · ${m.kind} · ${states} · ${m.variants} variant${m.variants === 1 ? "" : "s"}${m.open ? ` · ${m.open} open` : ""}`,
|
|
1538
|
+
` ${m.slug} · ${m.kind} · ${states} · ${m.variants} variant${m.variants === 1 ? "" : "s"}${m.resolvedAt ? " · resolved" : m.open ? ` · ${m.open} open` : ""}`,
|
|
1449
1539
|
);
|
|
1450
1540
|
}
|
|
1541
|
+
for (const flow of list.flows ?? []) console.log(` ${flowLine(flow)}`);
|
|
1451
1542
|
for (const line of pendingLines(list.pending)) console.log(line);
|
|
1452
1543
|
},
|
|
1453
1544
|
async export() {
|
|
@@ -1473,6 +1564,15 @@ const commands = {
|
|
|
1473
1564
|
const dir = join(root, entry.state === null ? "default" : slugify(entry.state));
|
|
1474
1565
|
mkdirSync(dir, { recursive: true });
|
|
1475
1566
|
writeFileSync(join(dir, "index.html"), entry.html ?? "");
|
|
1567
|
+
// The screen with every fill, when index.html is the composed choice.
|
|
1568
|
+
if (entry.source) writeFileSync(join(dir, "source.html"), entry.source);
|
|
1569
|
+
const decided = data.decisions?.states?.[entry.state ?? ""];
|
|
1570
|
+
if (decided) {
|
|
1571
|
+
writeFileSync(
|
|
1572
|
+
join(dir, "decisions.json"),
|
|
1573
|
+
JSON.stringify({ ...decided, knobs: data.decisions.knobs }, null, 2) + "\n",
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1476
1576
|
writeFileSync(
|
|
1477
1577
|
join(dir, "history.json"),
|
|
1478
1578
|
JSON.stringify(
|
package/dist/mcp/server.js
CHANGED
|
@@ -156,6 +156,7 @@ const handlers = {
|
|
|
156
156
|
...args,
|
|
157
157
|
html,
|
|
158
158
|
parts: readPartFiles(args.parts),
|
|
159
|
+
fills: readPartFiles(args.fills),
|
|
159
160
|
session,
|
|
160
161
|
agent: AGENT,
|
|
161
162
|
project: resolveProject(args.project),
|
|
@@ -169,13 +170,15 @@ const handlers = {
|
|
|
169
170
|
project: resolveProject(args.project),
|
|
170
171
|
});
|
|
171
172
|
},
|
|
172
|
-
read: (args) => json(args.mock === undefined
|
|
173
|
-
? `/api/
|
|
174
|
-
:
|
|
175
|
-
project: resolveProject(args.project)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
173
|
+
read: (args) => json(args.mock === undefined && args.tree === true
|
|
174
|
+
? `/api/projects/${enc(resolveProject(args.project))}/tree`
|
|
175
|
+
: args.mock === undefined
|
|
176
|
+
? `/api/mocks${query({ project: resolveProject(args.project) })}`
|
|
177
|
+
: `/api/mocks/${enc(args.mock)}${query({
|
|
178
|
+
project: resolveProject(args.project),
|
|
179
|
+
body: args.body,
|
|
180
|
+
history: args.history,
|
|
181
|
+
})}`),
|
|
179
182
|
// Never waits: the server reads from the session's cursor, shared with
|
|
180
183
|
// piggyback and `mockpit watch`, so each Send is delivered exactly once.
|
|
181
184
|
async feedback(args) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type PartInfo } from "./parts.ts";
|
|
2
|
-
import { type Mock, type Post, type PostVersion, type Session, type Surface } from "./types.ts";
|
|
2
|
+
import { type Embed, type KnobValue, type Mock, type Post, type PostVersion, type Session, type Surface } from "./types.ts";
|
|
3
3
|
export declare const surfaceRef: (surface: Pick<Surface, "id" | "kind">, index: number) => {
|
|
4
4
|
id: string | undefined;
|
|
5
5
|
kind: "html" | "diff" | "image" | "markdown" | "terminal" | "mermaid" | "json" | "code";
|
|
@@ -56,7 +56,7 @@ export declare const fullSurfaceView: (surface: Surface, index: number) => {
|
|
|
56
56
|
lineStart?: number;
|
|
57
57
|
id?: string;
|
|
58
58
|
};
|
|
59
|
-
export declare const hydratedSurfaceView: (surface: Surface, index: number) =>
|
|
59
|
+
export declare const hydratedSurfaceView: (surface: Surface, index: number) => {
|
|
60
60
|
index: number;
|
|
61
61
|
kind: "html";
|
|
62
62
|
html: string;
|
|
@@ -106,7 +106,7 @@ export declare const hydratedSurfaceView: (surface: Surface, index: number) => R
|
|
|
106
106
|
title?: string;
|
|
107
107
|
lineStart?: number;
|
|
108
108
|
id?: string;
|
|
109
|
-
}
|
|
109
|
+
} | Record<string, unknown>;
|
|
110
110
|
export declare const historyMetaView: (version: PostVersion) => {
|
|
111
111
|
surfaceKinds: ("html" | "diff" | "image" | "markdown" | "terminal" | "mermaid" | "json" | "code")[];
|
|
112
112
|
author?: string | undefined;
|
|
@@ -119,9 +119,17 @@ export declare const historyMetaView: (version: PostVersion) => {
|
|
|
119
119
|
export interface VariantViewOptions {
|
|
120
120
|
body?: boolean;
|
|
121
121
|
history?: boolean;
|
|
122
|
+
embeds?: Map<string, EmbedView>;
|
|
123
|
+
}
|
|
124
|
+
export interface EmbedView {
|
|
125
|
+
key: string;
|
|
126
|
+
list: (Embed & {
|
|
127
|
+
latest: number;
|
|
128
|
+
versions: number[];
|
|
129
|
+
})[];
|
|
122
130
|
}
|
|
123
131
|
export declare const variantView: (post: Post, opts?: VariantViewOptions) => {
|
|
124
|
-
history?: {
|
|
132
|
+
history?: ({
|
|
125
133
|
surfaceKinds: ("html" | "diff" | "image" | "markdown" | "terminal" | "mermaid" | "json" | "code")[];
|
|
126
134
|
author?: string | undefined;
|
|
127
135
|
prompt?: string | undefined;
|
|
@@ -129,9 +137,18 @@ export declare const variantView: (post: Post, opts?: VariantViewOptions) => {
|
|
|
129
137
|
version: number;
|
|
130
138
|
title: string;
|
|
131
139
|
at: string;
|
|
132
|
-
}
|
|
140
|
+
} | {
|
|
141
|
+
hidden: true;
|
|
142
|
+
surfaceKinds: ("html" | "diff" | "image" | "markdown" | "terminal" | "mermaid" | "json" | "code")[];
|
|
143
|
+
author?: string | undefined;
|
|
144
|
+
prompt?: string | undefined;
|
|
145
|
+
from?: number | undefined;
|
|
146
|
+
version: number;
|
|
147
|
+
title: string;
|
|
148
|
+
at: string;
|
|
149
|
+
})[] | undefined;
|
|
133
150
|
parts: string[];
|
|
134
|
-
surfaces: (
|
|
151
|
+
surfaces: ({
|
|
135
152
|
index: number;
|
|
136
153
|
kind: "html";
|
|
137
154
|
html: string;
|
|
@@ -181,9 +198,21 @@ export declare const variantView: (post: Post, opts?: VariantViewOptions) => {
|
|
|
181
198
|
title?: string;
|
|
182
199
|
lineStart?: number;
|
|
183
200
|
id?: string;
|
|
184
|
-
})[];
|
|
185
|
-
|
|
186
|
-
|
|
201
|
+
} | Record<string, unknown>)[];
|
|
202
|
+
acceptedVersion?: number | undefined;
|
|
203
|
+
starred?: boolean | undefined;
|
|
204
|
+
forkedFrom?: import("./types.ts").ForkRef | undefined;
|
|
205
|
+
publishedWith?: Embed[] | undefined;
|
|
206
|
+
embeds?: (Embed & {
|
|
207
|
+
latest: number;
|
|
208
|
+
versions: number[];
|
|
209
|
+
})[] | undefined;
|
|
210
|
+
embedsKey?: string | undefined;
|
|
211
|
+
note?: string | undefined;
|
|
212
|
+
slots?: import("./fills.ts").SlotInfo[] | undefined;
|
|
213
|
+
knobs?: {
|
|
214
|
+
[x: string]: import("./types.ts").KnobConfig;
|
|
215
|
+
} | undefined;
|
|
187
216
|
author?: string | undefined;
|
|
188
217
|
prompt?: string | undefined;
|
|
189
218
|
from?: number | undefined;
|
|
@@ -197,6 +226,11 @@ export declare const variantView: (post: Post, opts?: VariantViewOptions) => {
|
|
|
197
226
|
createdAt: string;
|
|
198
227
|
updatedAt: string;
|
|
199
228
|
};
|
|
229
|
+
export declare const inUseByState: (mock: Mock, posts: Post[]) => {
|
|
230
|
+
state: string | null;
|
|
231
|
+
variant: string;
|
|
232
|
+
version: number;
|
|
233
|
+
}[];
|
|
200
234
|
export interface StateParts {
|
|
201
235
|
state: string | null;
|
|
202
236
|
parts: PartInfo[];
|
|
@@ -204,6 +238,7 @@ export interface StateParts {
|
|
|
204
238
|
export declare function partsByState(mock: Mock, posts: Post[]): StateParts[];
|
|
205
239
|
export declare const mockSummaryView: (mock: Mock, posts: Post[]) => {
|
|
206
240
|
sessionId: string | null;
|
|
241
|
+
resolvedAt: string | null;
|
|
207
242
|
createdAt: string;
|
|
208
243
|
updatedAt: string;
|
|
209
244
|
thumbnail: {
|
|
@@ -223,7 +258,7 @@ export declare const mockSummaryView: (mock: Mock, posts: Post[]) => {
|
|
|
223
258
|
open: number;
|
|
224
259
|
};
|
|
225
260
|
export declare const mockDetailView: (mock: Mock, posts: Post[], opts?: VariantViewOptions & {
|
|
226
|
-
|
|
261
|
+
settled?: Record<string, KnobValue>;
|
|
227
262
|
}) => {
|
|
228
263
|
id: string;
|
|
229
264
|
project: string;
|
|
@@ -234,11 +269,22 @@ export declare const mockDetailView: (mock: Mock, posts: Post[], opts?: VariantV
|
|
|
234
269
|
asks: import("./types.ts").Ask[];
|
|
235
270
|
open: number;
|
|
236
271
|
knobs: import("./types.ts").Knobs;
|
|
272
|
+
widths: number[];
|
|
237
273
|
sessionId: string | null;
|
|
274
|
+
resolvedAt: string | null;
|
|
238
275
|
createdAt: string;
|
|
239
276
|
updatedAt: string;
|
|
240
277
|
variants: {
|
|
241
|
-
history?: {
|
|
278
|
+
history?: ({
|
|
279
|
+
surfaceKinds: ("html" | "diff" | "image" | "markdown" | "terminal" | "mermaid" | "json" | "code")[];
|
|
280
|
+
author?: string | undefined;
|
|
281
|
+
prompt?: string | undefined;
|
|
282
|
+
from?: number | undefined;
|
|
283
|
+
version: number;
|
|
284
|
+
title: string;
|
|
285
|
+
at: string;
|
|
286
|
+
} | {
|
|
287
|
+
hidden: true;
|
|
242
288
|
surfaceKinds: ("html" | "diff" | "image" | "markdown" | "terminal" | "mermaid" | "json" | "code")[];
|
|
243
289
|
author?: string | undefined;
|
|
244
290
|
prompt?: string | undefined;
|
|
@@ -246,9 +292,9 @@ export declare const mockDetailView: (mock: Mock, posts: Post[], opts?: VariantV
|
|
|
246
292
|
version: number;
|
|
247
293
|
title: string;
|
|
248
294
|
at: string;
|
|
249
|
-
}[] | undefined;
|
|
295
|
+
})[] | undefined;
|
|
250
296
|
parts: string[];
|
|
251
|
-
surfaces: (
|
|
297
|
+
surfaces: ({
|
|
252
298
|
index: number;
|
|
253
299
|
kind: "html";
|
|
254
300
|
html: string;
|
|
@@ -298,9 +344,21 @@ export declare const mockDetailView: (mock: Mock, posts: Post[], opts?: VariantV
|
|
|
298
344
|
title?: string;
|
|
299
345
|
lineStart?: number;
|
|
300
346
|
id?: string;
|
|
301
|
-
})[];
|
|
302
|
-
|
|
303
|
-
|
|
347
|
+
} | Record<string, unknown>)[];
|
|
348
|
+
acceptedVersion?: number | undefined;
|
|
349
|
+
starred?: boolean | undefined;
|
|
350
|
+
forkedFrom?: import("./types.ts").ForkRef | undefined;
|
|
351
|
+
publishedWith?: Embed[] | undefined;
|
|
352
|
+
embeds?: (Embed & {
|
|
353
|
+
latest: number;
|
|
354
|
+
versions: number[];
|
|
355
|
+
})[] | undefined;
|
|
356
|
+
embedsKey?: string | undefined;
|
|
357
|
+
note?: string | undefined;
|
|
358
|
+
slots?: import("./fills.ts").SlotInfo[] | undefined;
|
|
359
|
+
knobs?: {
|
|
360
|
+
[x: string]: import("./types.ts").KnobConfig;
|
|
361
|
+
} | undefined;
|
|
304
362
|
author?: string | undefined;
|
|
305
363
|
prompt?: string | undefined;
|
|
306
364
|
from?: number | undefined;
|
|
@@ -314,8 +372,13 @@ export declare const mockDetailView: (mock: Mock, posts: Post[], opts?: VariantV
|
|
|
314
372
|
createdAt: string;
|
|
315
373
|
updatedAt: string;
|
|
316
374
|
}[];
|
|
375
|
+
inUse: {
|
|
376
|
+
state: string | null;
|
|
377
|
+
variant: string;
|
|
378
|
+
version: number;
|
|
379
|
+
}[];
|
|
317
380
|
parts: StateParts[];
|
|
318
|
-
|
|
381
|
+
settled: Record<string, KnobValue>;
|
|
319
382
|
};
|
|
320
383
|
export declare const sessionRowView: (session: Session, postCount: number) => {
|
|
321
384
|
postCount: number;
|
|
@@ -330,3 +393,41 @@ export declare const sessionRowView: (session: Session, postCount: number) => {
|
|
|
330
393
|
agentActiveAt: string | null;
|
|
331
394
|
keyed: boolean;
|
|
332
395
|
};
|
|
396
|
+
export declare const treeNodeView: (mock: Mock, posts: Post[], extra: {
|
|
397
|
+
settled: Record<string, KnobValue>;
|
|
398
|
+
uses: string[];
|
|
399
|
+
embeds: Map<string, EmbedView>;
|
|
400
|
+
}) => {
|
|
401
|
+
id: string;
|
|
402
|
+
slug: string;
|
|
403
|
+
title: string;
|
|
404
|
+
kind: import("./types.ts").MockKind;
|
|
405
|
+
open: number;
|
|
406
|
+
states: string[] | null[];
|
|
407
|
+
widths: number[];
|
|
408
|
+
createdAt: string;
|
|
409
|
+
updatedAt: string;
|
|
410
|
+
resolvedAt: string | null;
|
|
411
|
+
settled: Record<string, KnobValue>;
|
|
412
|
+
uses: string[];
|
|
413
|
+
usedIn: string[];
|
|
414
|
+
inUse: {
|
|
415
|
+
state: string | null;
|
|
416
|
+
variant: string;
|
|
417
|
+
version: number;
|
|
418
|
+
}[];
|
|
419
|
+
variants: {
|
|
420
|
+
embedsKey?: string | undefined;
|
|
421
|
+
acceptedVersion?: number | undefined;
|
|
422
|
+
starred?: boolean | undefined;
|
|
423
|
+
forkedFrom?: import("./types.ts").ForkRef | undefined;
|
|
424
|
+
note?: string | undefined;
|
|
425
|
+
postId: string;
|
|
426
|
+
state: string | null;
|
|
427
|
+
variant: string;
|
|
428
|
+
status: import("./types.ts").PostStatus;
|
|
429
|
+
version: number;
|
|
430
|
+
updatedAt: string;
|
|
431
|
+
surface: number;
|
|
432
|
+
}[];
|
|
433
|
+
};
|