mockpit 2.0.0 → 3.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 +60 -212
- package/bin/demoData.js +2 -2
- package/bin/initDesign.js +6 -6
- package/bin/mockpit.js +407 -187
- package/bin/poster.js +377 -0
- package/dist/mcp/server.js +80 -24
- package/dist/server/answerAdditions.d.ts +7 -0
- package/dist/server/answerAdditions.js +76 -0
- package/dist/server/apiViews.d.ts +289 -52
- package/dist/server/apiViews.js +189 -81
- package/dist/server/app.d.ts +7 -2
- package/dist/server/app.js +2845 -832
- package/dist/server/appIcon.js +15 -5
- package/dist/server/appIconPng.js +3 -3
- package/dist/server/askNudges.js +1 -1
- package/dist/server/cdnInline.d.ts +8 -0
- package/dist/server/cdnInline.js +638 -0
- package/dist/server/codeWorker.js +1 -1
- package/dist/server/decisionList.d.ts +45 -0
- package/dist/server/decisionList.js +302 -0
- package/dist/server/decisions.d.ts +4 -4
- package/dist/server/decisions.js +9 -9
- package/dist/server/designGuide.d.ts +5 -5
- package/dist/server/designGuide.js +128 -67
- package/dist/server/embeds.d.ts +3 -1
- package/dist/server/embeds.js +31 -6
- package/dist/server/eventLog.d.ts +2 -2
- package/dist/server/eventLog.js +11 -11
- package/dist/server/events.d.ts +9 -6
- package/dist/server/feedbackBatch.d.ts +35 -13
- package/dist/server/feedbackBatch.js +111 -47
- package/dist/server/fills.d.ts +4 -0
- package/dist/server/fills.js +55 -1
- package/dist/server/handoff.d.ts +4 -1
- package/dist/server/handoff.js +8 -2
- package/dist/server/icons.js +1 -1
- package/dist/server/index.js +39 -10
- package/dist/server/jump.d.ts +21 -0
- package/dist/server/jump.js +175 -0
- package/dist/server/knobs.d.ts +3 -3
- package/dist/server/knobs.js +6 -6
- package/dist/server/mcpHttp.d.ts +5 -1
- package/dist/server/mcpHttp.js +16 -11
- package/dist/server/mcpSpec.js +118 -46
- package/dist/server/parts.js +9 -1
- package/dist/server/run.d.ts +1 -1
- package/dist/server/run.js +25 -16
- package/dist/server/runApi.d.ts +3 -3
- package/dist/server/runApi.js +25 -19
- package/dist/server/sqlStore.d.ts +112 -96
- package/dist/server/sqlStore.js +906 -172
- package/dist/server/standings.d.ts +25 -0
- package/dist/server/standings.js +113 -0
- package/dist/server/surfacePage.d.ts +6 -9
- package/dist/server/surfacePage.js +361 -86
- package/dist/server/types.d.ts +226 -72
- package/dist/server/types.js +73 -78
- package/extensions/mockpit.js +89 -72
- package/guide/AGENT_SETUP.md +13 -1
- package/guide/packs/design-craft.md +2 -3
- package/guide/topics/asks.md +106 -62
- package/guide/topics/feedback.md +126 -119
- package/guide/topics/html.md +17 -24
- package/guide/topics/http.md +41 -41
- package/guide/topics/knobs.md +7 -10
- package/guide/topics/scripts.md +22 -33
- package/guide/topics/surfaces.md +39 -48
- package/opencode/mockpit.js +120 -0
- package/opencode/package.json +8 -0
- package/package.json +4 -3
- package/skills/launch-video/SKILL.md +11 -11
- package/skills/mockpit/SKILL.md +26 -22
- package/viewer/dist/index.html +9 -9
package/bin/mockpit.js
CHANGED
|
@@ -13,6 +13,7 @@ import { homedir, tmpdir, userInfo } from "node:os";
|
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { parseArgs } from "node:util";
|
|
16
|
+
import { findBrowser, queuePoster } from "./poster.js";
|
|
16
17
|
import { serveUrl } from "./serveUrl.js";
|
|
17
18
|
|
|
18
19
|
const BASE = (process.env.MOCKPIT_URL ?? "http://localhost:8228").replace(/\/$/, "");
|
|
@@ -28,9 +29,9 @@ const HEADERS = {
|
|
|
28
29
|
|
|
29
30
|
const HELP = `mockpit — a live visual surface for terminal coding agents
|
|
30
31
|
|
|
31
|
-
vocabulary: project ›
|
|
32
|
-
|
|
33
|
-
open"); a variant is a parallel
|
|
32
|
+
vocabulary: project › design › state › variant › version. A project is a repo; a
|
|
33
|
+
design is an idea, screen or component, by slug; a state is one thing the user sees in it ("Lab
|
|
34
|
+
open"); a variant is a parallel take on a state; a version is its history.
|
|
34
35
|
Declare the parts you want feedback on with data-part="name" in the html.
|
|
35
36
|
|
|
36
37
|
the loop (never blocks on the user):
|
|
@@ -41,22 +42,22 @@ the loop (never blocks on the user):
|
|
|
41
42
|
on; don't re-ask it. Visual options carry a variant or knob set. One
|
|
42
43
|
render plus a control: --knobs.
|
|
43
44
|
4. Tell the user in one line where to look, then end your turn. The question
|
|
44
|
-
itself lives in the
|
|
45
|
+
itself lives in the design, never in chat. The user answers in the
|
|
45
46
|
browser at their own pace. Never poll.
|
|
46
47
|
5. When the user says they answered (or a mockpit watch line wakes you), run
|
|
47
48
|
mockpit feedback. Every write also returns feedback; read it.
|
|
48
49
|
6. mockpit publish the revision; mockpit export once a variant is in use.
|
|
49
50
|
In Claude Code, arm mockpit watch under Monitor after asking, then end the
|
|
50
|
-
turn
|
|
51
|
+
turn, unless you have the mockpit plugin, which wakes you itself; elsewhere
|
|
52
|
+
the user's next message is the wake-up.
|
|
51
53
|
|
|
52
54
|
verbs:
|
|
53
55
|
mockpit init [--project name] [--kit <id>] [--kit-url <url> --kit-doc <file|text>]
|
|
54
56
|
detect the repo's design system, store
|
|
55
57
|
palette/kit/icon sets, write .mockpit/starter.html
|
|
56
|
-
mockpit publish --
|
|
58
|
+
mockpit publish --design <slug> --html <file> [options]
|
|
57
59
|
create or version one variant of one state
|
|
58
|
-
--state <label> state, in the user's words (omit for a single-state
|
|
59
|
-
--situation <s> another render of that state ("Couldn't save", "Loading")
|
|
60
|
+
--state <label> state, in the user's words (omit for a single-state design)
|
|
60
61
|
--variant <name> variant label (default "default")
|
|
61
62
|
--html <file|-> the html body (or a positional file)
|
|
62
63
|
--surfaces <json|file> instead: the full ordered surface list; {"id"}
|
|
@@ -65,14 +66,18 @@ verbs:
|
|
|
65
66
|
the latest version (repeatable; name#key targets one instance)
|
|
66
67
|
--fills <part#fill=file|-> instead: replace or add one fill of a part in
|
|
67
68
|
the latest version (repeatable)
|
|
68
|
-
--title <t>
|
|
69
|
-
--kind <k>
|
|
69
|
+
--title <t> design title
|
|
70
|
+
--kind <k> screen|component, only for an app's screens (default idea);
|
|
71
|
+
with no html it plans one, drawn by a later publish
|
|
70
72
|
--area <name> where in the product it belongs ("Shell"); Home groups by it
|
|
71
73
|
--source <path> the code path that implements it
|
|
72
74
|
--knobs <json|file> knobs in tunekit usePane shape, keyed by path
|
|
73
75
|
--from <N> branch from version N
|
|
76
|
+
--amend replace the latest version in place: a fix before the
|
|
77
|
+
user has acted on it (one request, one version)
|
|
78
|
+
--withdraw take the latest version back, under the same rule
|
|
74
79
|
--prompt <text> what prompted this version
|
|
75
|
-
--
|
|
80
|
+
--description <text> one line on what this variant is, shown beside it
|
|
76
81
|
--widths <list> device widths to show it at, e.g. 390,1280
|
|
77
82
|
--flow <name> [--step <N>] make this state step N of a flow (0 takes
|
|
78
83
|
it out); with no html it only places the state
|
|
@@ -84,33 +89,38 @@ verbs:
|
|
|
84
89
|
--md/--mermaid/--diff/--terminal/--data/--code/--image <file|->
|
|
85
90
|
add a surface of that kind after the html (repeatable;
|
|
86
91
|
--data is a JSON tree)
|
|
87
|
-
mockpit ask --
|
|
92
|
+
mockpit ask --design <slug> "<question>" --option <label[=variant]> ...
|
|
88
93
|
ask the user; bind options to variants
|
|
89
94
|
--option <l[=v]> an option (repeatable); "=variant" binds it to a variant,
|
|
90
95
|
"=variant@3" to its version 3, "=knob:path=value" sets one knob
|
|
91
96
|
--recommend <l> the option you would pick, by label; a quiet mark on it
|
|
92
|
-
--scope <s>
|
|
97
|
+
--scope <s> design|state|part (default design)
|
|
93
98
|
--state <label> the state a state-scoped question is about
|
|
99
|
+
--states <label> a design-wide question: a state its answers can be seen
|
|
100
|
+
on (repeatable, best first)
|
|
94
101
|
--part <name> the part a part-scoped question is about
|
|
95
102
|
--multi allow several answers
|
|
96
103
|
--id <id> stable id; reusing one replaces that question
|
|
97
104
|
--asks <json|file> several questions at once, full shape
|
|
98
|
-
mockpit read [slug] [--body] [--history]
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
mockpit read [slug] [--body] [--history] [--decisions]
|
|
106
|
+
what the user already decided, then
|
|
107
|
+
no slug: every design plus what the user
|
|
108
|
+
is doing now; a slug: that design
|
|
101
109
|
mockpit feedback what the user sent since you last heard,
|
|
102
110
|
plus pending; returns at once
|
|
103
|
-
mockpit say "<text>" --
|
|
104
|
-
a plain-text message in a
|
|
105
|
-
mockpit
|
|
106
|
-
|
|
111
|
+
mockpit say "<text>" --design <slug> [--state s] [--variant v]
|
|
112
|
+
a plain-text message in a design's thread
|
|
113
|
+
mockpit move --design <slug> --variant <v>... (--to <design> [--to-state s] | --archive | --restore)
|
|
114
|
+
move variants elsewhere, archive or restore them
|
|
115
|
+
mockpit edit --design <slug> [--slug s] [--title t] [--kind k] [--area a] [--source p]
|
|
116
|
+
change a design without a new version ("" clears)
|
|
107
117
|
mockpit edit --project <name> --rename <new>
|
|
108
118
|
rename a project
|
|
109
119
|
mockpit edit --project <name> --area-order "Shell,Food"
|
|
110
120
|
the order Home lists areas in
|
|
111
|
-
mockpit export --
|
|
121
|
+
mockpit export --design <slug> [--state s] [--variant v] [--out <dir>]
|
|
112
122
|
write the html in use + history per state
|
|
113
|
-
to .mockpit/accepted/<
|
|
123
|
+
to .mockpit/accepted/<design>/<state>/
|
|
114
124
|
mockpit upload <file> [--kind image|file]
|
|
115
125
|
upload an asset, print its id and URL
|
|
116
126
|
mockpit guide [--topic <id>] the project-aware brief, or one reference topic
|
|
@@ -124,7 +134,7 @@ verbs:
|
|
|
124
134
|
other commands:
|
|
125
135
|
mockpit serve [--port N] [--host H] [--open]
|
|
126
136
|
start the server (API + viewer)
|
|
127
|
-
mockpit demo seed the Writer
|
|
137
|
+
mockpit demo seed the Writer design to explore the viewer
|
|
128
138
|
mockpit icons [add|remove <set>...] list the icon sets html surfaces can name
|
|
129
139
|
(icon="prefix:name"); add or remove an
|
|
130
140
|
Iconify set for this project
|
|
@@ -147,6 +157,7 @@ environment:
|
|
|
147
157
|
MOCKPIT_PROJECT project name; overrides the git-remote/directory default
|
|
148
158
|
MOCKPIT_URL server base URL (default http://localhost:8228)
|
|
149
159
|
MOCKPIT_TOKEN bearer token for a deployed instance
|
|
160
|
+
CHROME_PATH the browser for automatic posters; pointing nowhere turns them off
|
|
150
161
|
MOCKPIT_HOST address serve binds to (default: every interface)
|
|
151
162
|
MOCKPIT_LOG serve's event log file, or "off" (default ~/.mockpit/events.jsonl)
|
|
152
163
|
MOCKPIT_SESSION fixed session id (overrides auto-detection)
|
|
@@ -183,14 +194,21 @@ mockpit icons remove <set> [<set>...]
|
|
|
183
194
|
html surfaces write <i icon="lucide:check"></i>; the server inlines the svg.
|
|
184
195
|
lucide and mage are bundled. add finds @iconify-json/<set> in node_modules,
|
|
185
196
|
the global npm root or bun's cache, else fetches it from jsDelivr.`,
|
|
186
|
-
publish: `mockpit publish --
|
|
187
|
-
mockpit publish --
|
|
188
|
-
mockpit publish --
|
|
189
|
-
mockpit publish --
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
publish: `mockpit publish --design <slug> --html <file> [options]
|
|
198
|
+
mockpit publish --design <slug> --surfaces <json|file> [options]
|
|
199
|
+
mockpit publish --design <slug> --parts <name=file|-> ... [options]
|
|
200
|
+
mockpit publish --design <slug> --fills <part#fill=file|-> ... [options]
|
|
201
|
+
mockpit publish --design <slug> [--state s] [--variant v] --withdraw
|
|
202
|
+
Creates the design, state or variant, or the next version of an existing one.
|
|
203
|
+
One request gets one version: fixes to it before the user acts use --amend.
|
|
204
|
+
--amend replace the variant's latest version in place (same number)
|
|
205
|
+
instead of adding one. Only from the session that published
|
|
206
|
+
it, and only while the user hasn't acted on it (no Send,
|
|
207
|
+
comment, draft answer, not in use)
|
|
208
|
+
--withdraw take the variant's latest version back under the same
|
|
209
|
+
rule: the version before it is current again, and a
|
|
210
|
+
variant with no other version goes
|
|
211
|
+
--state <label> state in the user's words (omit for a single-state design)
|
|
194
212
|
--variant <name> variant label (default "default")
|
|
195
213
|
--surfaces <json|file> the full ordered list: {"id"} alone keeps a surface,
|
|
196
214
|
a missing id removes it, order is the list order
|
|
@@ -199,10 +217,10 @@ mockpit publish --mock <slug> --fills <part#fill=file|-> ... [options]
|
|
|
199
217
|
--fills <part#fill=file|-> replace the element carrying data-part="part"
|
|
200
218
|
data-fill="fill" in the latest version, or add it after
|
|
201
219
|
that part's other fills; the marks are added if missing
|
|
202
|
-
--title <t>
|
|
203
|
-
--kind <k>
|
|
204
|
-
component for a
|
|
205
|
-
omit for
|
|
220
|
+
--title <t> design title
|
|
221
|
+
--kind <k> screen|component|idea: screen for a screen of the product,
|
|
222
|
+
component for a reusable design screens embed (embedding makes one);
|
|
223
|
+
omit for ideas, diagrams, reviews (default idea)
|
|
206
224
|
--area <name> where in the product it belongs, in the project's words
|
|
207
225
|
("Shell", "Food"); Home groups each tab by it. "" clears
|
|
208
226
|
--source <path> the code path that implements it (apps/web/src/shell). "" clears
|
|
@@ -212,17 +230,18 @@ mockpit publish --mock <slug> --fills <part#fill=file|-> ... [options]
|
|
|
212
230
|
its latest); alone it copies v, with --parts/--fills it
|
|
213
231
|
edits the copy
|
|
214
232
|
--prompt <text> what prompted this version
|
|
215
|
-
--
|
|
233
|
+
--description <text> one line on what this variant is, shown beside it (never
|
|
216
234
|
inside the design)
|
|
217
|
-
--widths <list> device widths the
|
|
235
|
+
--widths <list> device widths the design is judged at, side by side, e.g.
|
|
218
236
|
390,1280 (the ones the user ships to)
|
|
219
237
|
--flow <name> a flow this state is a step of: the states the user moves
|
|
220
|
-
between, in order, across
|
|
238
|
+
between, in order, across designs; without html it only
|
|
221
239
|
places the existing state
|
|
222
240
|
--step <N> its 1-based place in the flow (moves it if it is there);
|
|
223
241
|
0 takes it out; omitted, it joins at the end
|
|
224
242
|
--project <name> project (default: git remote, else directory name)
|
|
225
|
-
--poster <file> a still of the page (png, jpg) that Studio and Home show
|
|
243
|
+
--poster <file> a still of the page (png, jpg) that Studio and Home show; without
|
|
244
|
+
one, a local Chrome or Chromium takes it just after the publish
|
|
226
245
|
instead of running it; with --parts or --fills it is the
|
|
227
246
|
still of the spliced page. A version that changes the page
|
|
228
247
|
without one drops the old still
|
|
@@ -230,37 +249,56 @@ mockpit publish --mock <slug> --fills <part#fill=file|-> ... [options]
|
|
|
230
249
|
--json / --quiet
|
|
231
250
|
Prints the parts found per state, parts that vanished or were renamed,
|
|
232
251
|
nudges, a ready-to-run "mockpit ask" when variants have no question, and feedback.`,
|
|
233
|
-
ask: `mockpit ask --
|
|
234
|
-
[--state s] [--part p] [--multi] [--id id] [--after <ask id>] [--recommend <label>]
|
|
235
|
-
mockpit ask --
|
|
252
|
+
ask: `mockpit ask --design <slug> "<question>" --option <label[=variant]> ... [--scope design|state|part]
|
|
253
|
+
[--state s] [--states s]... [--part p] [--multi] [--id id] [--after <ask id>] [--recommend <label>]
|
|
254
|
+
mockpit ask --design <slug> --asks <json|file>
|
|
236
255
|
Ask the user. A choice is several variants plus one question that binds them.
|
|
237
256
|
Then tell the user where to look and end your turn.`,
|
|
238
|
-
read: `mockpit read [slug] [--project <name>] [--body] [--history] [--tree]
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
257
|
+
read: `mockpit read [slug] [--project <name>] [--body] [--history] [--tree] [--decisions]
|
|
258
|
+
Read this first. Both forms carry the project's decision list, what the user
|
|
259
|
+
already settled, from their replies: treat each line as settled unless they say
|
|
260
|
+
otherwise. No slug: one line per design (states, variants, open questions), one per
|
|
261
|
+
flow, plus pending, what the user is doing now, then the decisions. A slug: the
|
|
262
|
+
design's states, variants, questions with answers, parts, knobs and settled values,
|
|
263
|
+
plus its pending and decisionList (its own lines marked here), as JSON.
|
|
242
264
|
--body includes surfaces, --history version rows. --tree (no slug): the
|
|
243
|
-
project as a tree, each
|
|
244
|
-
|
|
265
|
+
project as a tree, each design's variants with status and forkedFrom, and the
|
|
266
|
+
designs it embeds and is embedded in, as JSON. --decisions: the whole list, when
|
|
267
|
+
a read says lines were left out. Never consumes feedback.`,
|
|
245
268
|
feedback: `mockpit feedback [--project <name>] [--session <id>]
|
|
246
|
-
Returns at once with {feedback, pending} as JSON: one batch per
|
|
247
|
-
sent since you last heard ({
|
|
248
|
-
verdict}, messages, inUse, archived},
|
|
249
|
-
|
|
250
|
-
every write; and pending ({mock, viewerOpen, draft}) so you can tell the
|
|
269
|
+
Returns at once with {feedback, pending} as JSON: one batch per design the user
|
|
270
|
+
sent since you last heard ({design, reply: {asks, borrowed, tuned, comments,
|
|
271
|
+
verdict}, messages, inUse, archived}), delivered once across feedback, watch and
|
|
272
|
+
every write; and pending ({design, viewerOpen, draft}) so you can tell the
|
|
251
273
|
user "take your time" or "2 of 3 answered". Run it when the user says they
|
|
252
274
|
answered.`,
|
|
253
|
-
say: `mockpit say "<text>" --
|
|
254
|
-
A plain-text message in the
|
|
255
|
-
|
|
275
|
+
say: `mockpit say "<text>" --design <slug> [--state s] [--variant v]
|
|
276
|
+
A plain-text message in the design's thread, as your session's agent.`,
|
|
277
|
+
move: `mockpit move --design <slug> [--state s] --variant <v> [--variant <v>...]
|
|
278
|
+
--to <design> [--to-state <s>] | --to-state <s> | --archive | --restore
|
|
279
|
+
mockpit move --design <slug> [--state s] [--variant <v>...] --hide-older | --delete-older
|
|
280
|
+
Tidy up. --to or --to-state moves them to another design or tab (a new state is
|
|
281
|
+
created) and takes them out of here; --archive takes them out of sight; --restore
|
|
282
|
+
brings them back. Their group goes along; one in use stays and is reported. The
|
|
283
|
+
thread says what happened, so the user can undo it.
|
|
284
|
+
Only when the user asks: --hide-older hides all but each variant's newest visible
|
|
285
|
+
version (no --variant: every variant), and --delete-older deletes them for good,
|
|
286
|
+
skipping and listing any that a reply, comment, question, embed, copy or fork still
|
|
287
|
+
points at, or that is in use. It can't be undone: confirm first.`,
|
|
288
|
+
edit: `mockpit edit --design <slug> [--slug <new>] [--title <t>] [--kind screen|component|idea]
|
|
289
|
+
[--first-state <name>] [--notes <text>|""]
|
|
256
290
|
[--area <name>|""] [--source <path>|""] [--project <name>]
|
|
257
291
|
mockpit edit --project <name> --rename <new name>
|
|
258
292
|
mockpit edit --project <name> --area-order "Shell,Food,Chat"
|
|
259
|
-
Change a
|
|
293
|
+
Change a design without publishing a new version; "" clears area or source. A new
|
|
260
294
|
slug rewrites every embed, data-goto and flow step that names the old one, in
|
|
261
|
-
every version. A
|
|
262
|
-
--
|
|
263
|
-
|
|
295
|
+
every version. A design still embedded somewhere stays a component, not an idea.
|
|
296
|
+
--notes drafts the design's short plain-text spec, which read returns; the user's own
|
|
297
|
+
edit of it stands, so the draft is then not applied and the result says so.
|
|
298
|
+
--first-state names a design's one unnamed state, and every variant it holds goes
|
|
299
|
+
there, so the design can then have more states.
|
|
300
|
+
--rename moves the project's designs, sessions, flows and design settings to the
|
|
301
|
+
new name (refused if that project has designs); pass --project <new> or set
|
|
264
302
|
MOCKPIT_PROJECT afterwards if the repo still resolves to the old name.
|
|
265
303
|
--area-order lists the project's areas in the order Home shows them; areas it
|
|
266
304
|
leaves out follow alphabetically, "" goes back to alphabetical.`,
|
|
@@ -270,12 +308,15 @@ mockpit edit --project <name> --area-order "Shell,Food,Chat"
|
|
|
270
308
|
host call in order, the script's prints, its value or error, and any user
|
|
271
309
|
feedback the run received. --json prints the whole result. Exits 2 when the
|
|
272
310
|
script failed.`,
|
|
273
|
-
watch: `mockpit watch [--session <id>] [--after <seq>]
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
311
|
+
watch: `mockpit watch [--session <id>] [--after <seq>] [--once] [--exit-code <n>]
|
|
312
|
+
Print one line per Send, for a background monitor (in Claude Code, arm it
|
|
313
|
+
under Monitor after asking, then end the turn; with the mockpit plugin, don't,
|
|
314
|
+
since it wakes you itself). A line says a reply landed;
|
|
315
|
+
feedback or the next write delivers it, once.
|
|
316
|
+
--once stops after the first Send, for a harness whose wake is a process
|
|
317
|
+
exiting; --exit-code sets the code it stops with (default 0).`,
|
|
318
|
+
export: `mockpit export --design <slug> [--state s] [--variant v] [--out <dir>]
|
|
319
|
+
Write index.html + history.json per state to .mockpit/accepted/<design>/<state>/;
|
|
279
320
|
with fills, index.html is the chosen state, plus all-fills.html and decisions.json.`,
|
|
280
321
|
upload: `mockpit upload <file> [--kind image|file] [--session <id>]
|
|
281
322
|
Upload an asset; prints its id and URL. Use the id as an image surface's assetId.`,
|
|
@@ -314,7 +355,7 @@ function die(what, fix) {
|
|
|
314
355
|
}
|
|
315
356
|
|
|
316
357
|
// `report` decides how a failure reads: plumbing verbs keep the one-line
|
|
317
|
-
// `mockpit: …` (exit 1); the
|
|
358
|
+
// `mockpit: …` (exit 1); the design verbs pass `die` so the error / fix / exit 2
|
|
318
359
|
// format is what an agent parses.
|
|
319
360
|
async function api(path, init = {}, { report, fix } = {}) {
|
|
320
361
|
const bail = (what, hint) => (report === "die" ? die(what, hint ?? fix) : fail(what));
|
|
@@ -343,7 +384,11 @@ async function api(path, init = {}, { report, fix } = {}) {
|
|
|
343
384
|
(body.error ?? `${res.status} ${res.statusText}`) +
|
|
344
385
|
(Array.isArray(choices) ? ` (${choices.join(", ")})` : "");
|
|
345
386
|
const extra = issue?.message ?? issue?.code;
|
|
346
|
-
|
|
387
|
+
// The server's own fix, when it names one, beats the caller's generic hint.
|
|
388
|
+
bail(
|
|
389
|
+
extra && !main.includes(extra) ? `${main}: ${extra}` : main,
|
|
390
|
+
typeof body.fix === "string" ? body.fix : undefined,
|
|
391
|
+
);
|
|
347
392
|
}
|
|
348
393
|
return body;
|
|
349
394
|
}
|
|
@@ -437,8 +482,11 @@ function agentName(flags) {
|
|
|
437
482
|
// doesn't keep; Claude Code's session id does, so the server can hand back the
|
|
438
483
|
// same mockpit session (and its feedback cursor).
|
|
439
484
|
function harnessKey() {
|
|
440
|
-
|
|
441
|
-
|
|
485
|
+
if (process.env.MOCKPIT_SESSION_KEY) return process.env.MOCKPIT_SESSION_KEY;
|
|
486
|
+
if (process.env.CLAUDE_CODE_SESSION_ID)
|
|
487
|
+
return `claude-code:${process.env.CLAUDE_CODE_SESSION_ID}`;
|
|
488
|
+
if (process.env.CODEX_THREAD_ID) return `codex:${process.env.CODEX_THREAD_ID}`;
|
|
489
|
+
return undefined;
|
|
442
490
|
}
|
|
443
491
|
|
|
444
492
|
async function resolveSession(flags, { create = false } = {}) {
|
|
@@ -680,17 +728,17 @@ async function installIconSets(project, prefixes) {
|
|
|
680
728
|
errors.push({ prefix, message: err instanceof Error ? err.message : String(err) });
|
|
681
729
|
}
|
|
682
730
|
}
|
|
683
|
-
if (added.length === 0) return { added: [], errors,
|
|
684
|
-
const stored = (await api(`${projectPath(project)}/design`)) ?? {};
|
|
731
|
+
if (added.length === 0) return { added: [], errors, designSystem: null };
|
|
732
|
+
const stored = (await api(`${projectPath(project)}/design-system`)) ?? {};
|
|
685
733
|
const keep = (stored.iconSets ?? []).filter((s) => !added.some((a) => a.prefix === s.prefix));
|
|
686
|
-
const
|
|
734
|
+
const designSystem = await api(`${projectPath(project)}/design-system`, {
|
|
687
735
|
method: "PUT",
|
|
688
736
|
body: JSON.stringify({ ...stored, iconSets: [...keep, ...added] }),
|
|
689
737
|
});
|
|
690
738
|
return {
|
|
691
|
-
added:
|
|
739
|
+
added: designSystem.iconSets.filter((s) => added.some((a) => a.prefix === s.prefix)),
|
|
692
740
|
errors,
|
|
693
|
-
|
|
741
|
+
designSystem,
|
|
694
742
|
};
|
|
695
743
|
}
|
|
696
744
|
|
|
@@ -834,7 +882,7 @@ function slugify(text) {
|
|
|
834
882
|
|
|
835
883
|
const projectPath = (project) => `/api/projects/${encodeURIComponent(project)}`;
|
|
836
884
|
|
|
837
|
-
const
|
|
885
|
+
const designPath = (slug) => `/api/designs/${encodeURIComponent(slug)}`;
|
|
838
886
|
|
|
839
887
|
const query = (params) => {
|
|
840
888
|
const q = new URLSearchParams();
|
|
@@ -845,18 +893,18 @@ const query = (params) => {
|
|
|
845
893
|
return s ? `?${s}` : "";
|
|
846
894
|
};
|
|
847
895
|
|
|
848
|
-
// The flags every
|
|
849
|
-
const
|
|
850
|
-
|
|
896
|
+
// The flags every design verb shares: which design, which state, which variant.
|
|
897
|
+
const DESIGN_FLAGS = {
|
|
898
|
+
design: { type: "string" },
|
|
851
899
|
state: { type: "string" },
|
|
852
900
|
variant: { type: "string" },
|
|
853
901
|
project: { type: "string" },
|
|
854
902
|
session: { type: "string" },
|
|
855
903
|
};
|
|
856
904
|
|
|
857
|
-
function
|
|
858
|
-
const slug = flags.
|
|
859
|
-
if (!slug) die("--
|
|
905
|
+
function requireDesign(flags, example) {
|
|
906
|
+
const slug = flags.design ? slugify(flags.design) : "";
|
|
907
|
+
if (!slug) die("--design needs a slug", example);
|
|
860
908
|
return slug;
|
|
861
909
|
}
|
|
862
910
|
|
|
@@ -878,7 +926,7 @@ const shq = (s) => (/^[\w@%+=:,./-]+$/.test(s) ? s : `'${String(s).replace(/'/g,
|
|
|
878
926
|
// The server's suggested ask as the command that sends it: an agent that was
|
|
879
927
|
// nudged can run it as is. --asks carries ids and scope, which flags can't.
|
|
880
928
|
function suggestedAskLine(slug, ask) {
|
|
881
|
-
return `mockpit ask --
|
|
929
|
+
return `mockpit ask --design ${shq(slug)} --asks ${shq(JSON.stringify([ask]))}`;
|
|
882
930
|
}
|
|
883
931
|
|
|
884
932
|
// One line per write: what was published, its URL, the parts found per state,
|
|
@@ -886,10 +934,22 @@ function suggestedAskLine(slug, ask) {
|
|
|
886
934
|
function printPublished(result, flags) {
|
|
887
935
|
if (flags.json) return out(result);
|
|
888
936
|
if (flags.quiet) return;
|
|
889
|
-
const {
|
|
937
|
+
const { design, post } = result;
|
|
938
|
+
if (result.planned) {
|
|
939
|
+
console.log(`${design.slug}: planned, nothing drawn yet · ${result.url}`);
|
|
940
|
+
if (result.flow) console.log(flowLine(result.flow));
|
|
941
|
+
for (const nudge of result.nudges ?? []) console.log(`nudge: ${nudge}`);
|
|
942
|
+
printFeedback(result.feedback);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
890
945
|
const branch = post.from !== undefined && post.from !== post.version - 1;
|
|
946
|
+
const how = result.withdrawn
|
|
947
|
+
? ` (v${result.withdrawn.version} withdrawn)`
|
|
948
|
+
: flags.amend
|
|
949
|
+
? " (amended)"
|
|
950
|
+
: "";
|
|
891
951
|
console.log(
|
|
892
|
-
`${
|
|
952
|
+
`${design.slug}/${stateLabel(post.state)}${post.variant} v${post.version}${branch && post.from ? ` (from v${post.from})` : ""}${how} · ${result.url}`,
|
|
893
953
|
);
|
|
894
954
|
for (const { state, parts } of result.parts ?? []) {
|
|
895
955
|
if (parts.length === 0) continue;
|
|
@@ -904,13 +964,41 @@ function printPublished(result, flags) {
|
|
|
904
964
|
if (changes?.vanished?.length) console.log(`vanished: ${changes.vanished.join(", ")}`);
|
|
905
965
|
for (const nudge of result.nudges ?? []) console.log(`nudge: ${nudge}`);
|
|
906
966
|
if (result.suggestedAsk)
|
|
907
|
-
console.log(`suggested: ${suggestedAskLine(
|
|
967
|
+
console.log(`suggested: ${suggestedAskLine(design.slug, result.suggestedAsk)}`);
|
|
908
968
|
for (const warning of result.warnings ?? []) console.log(`warning: ${warning}`);
|
|
909
969
|
printFeedback(result.feedback);
|
|
910
970
|
}
|
|
911
971
|
|
|
972
|
+
async function withdrawVersion(flags, slug, project) {
|
|
973
|
+
const session = await resolveSession(flags);
|
|
974
|
+
if (!session) die(`no session has published ${slug} from here`, `mockpit read ${slug}`);
|
|
975
|
+
const result = await api(
|
|
976
|
+
"/api/designs",
|
|
977
|
+
{
|
|
978
|
+
method: "POST",
|
|
979
|
+
body: JSON.stringify({
|
|
980
|
+
session,
|
|
981
|
+
project,
|
|
982
|
+
design: slug,
|
|
983
|
+
state: flags.state,
|
|
984
|
+
variant: flags.variant,
|
|
985
|
+
withdraw: true,
|
|
986
|
+
}),
|
|
987
|
+
},
|
|
988
|
+
{ report: "die", fix: `mockpit read ${slug} --history` },
|
|
989
|
+
);
|
|
990
|
+
if (!result.removed) return printPublished(result, flags);
|
|
991
|
+
if (flags.json) return out(result);
|
|
992
|
+
if (flags.quiet) return;
|
|
993
|
+
const w = result.withdrawn;
|
|
994
|
+
console.log(
|
|
995
|
+
`${slug}/${stateLabel(w.state)}${w.variant} v${w.version} withdrawn, variant removed · ${result.url}`,
|
|
996
|
+
);
|
|
997
|
+
printFeedback(result.feedback);
|
|
998
|
+
}
|
|
999
|
+
|
|
912
1000
|
function flowLine(flow) {
|
|
913
|
-
const steps = flow.steps.map((x) => (x.state === null ? x.
|
|
1001
|
+
const steps = flow.steps.map((x) => (x.state === null ? x.design : `${x.design}/${x.state}`));
|
|
914
1002
|
return `flow ${flow.name}: ${steps.length ? steps.join(" → ") : "empty, removed"}`;
|
|
915
1003
|
}
|
|
916
1004
|
|
|
@@ -920,15 +1008,15 @@ function printFeedback(feedback) {
|
|
|
920
1008
|
if (feedback?.length) console.log(`feedback: ${JSON.stringify(feedback)}`);
|
|
921
1009
|
}
|
|
922
1010
|
|
|
923
|
-
// What the user is doing right now, per
|
|
1011
|
+
// What the user is doing right now, per design.
|
|
924
1012
|
function pendingLines(pending) {
|
|
925
1013
|
return (pending ?? []).flatMap((p) =>
|
|
926
1014
|
p.draft
|
|
927
1015
|
? [
|
|
928
|
-
`pending: ${p.
|
|
1016
|
+
`pending: ${p.design} — the user is answering (${p.draft.answered} of ${p.draft.of} answered, ${p.draft.comments} comment${p.draft.comments === 1 ? "" : "s"})`,
|
|
929
1017
|
]
|
|
930
1018
|
: p.viewerOpen
|
|
931
|
-
? [`pending: ${p.
|
|
1019
|
+
? [`pending: ${p.design} — open in the viewer`]
|
|
932
1020
|
: [],
|
|
933
1021
|
);
|
|
934
1022
|
}
|
|
@@ -938,8 +1026,7 @@ function parsePublishFlags() {
|
|
|
938
1026
|
tokens: true,
|
|
939
1027
|
allowPositionals: true,
|
|
940
1028
|
options: {
|
|
941
|
-
...
|
|
942
|
-
situation: { type: "string" },
|
|
1029
|
+
...DESIGN_FLAGS,
|
|
943
1030
|
title: { type: "string" },
|
|
944
1031
|
kind: { type: "string" },
|
|
945
1032
|
area: { type: "string" },
|
|
@@ -953,7 +1040,7 @@ function parsePublishFlags() {
|
|
|
953
1040
|
from: { type: "string" },
|
|
954
1041
|
fork: { type: "string" },
|
|
955
1042
|
prompt: { type: "string" },
|
|
956
|
-
|
|
1043
|
+
description: { type: "string" },
|
|
957
1044
|
widths: { type: "string" },
|
|
958
1045
|
flow: { type: "string" },
|
|
959
1046
|
step: { type: "string" },
|
|
@@ -969,6 +1056,8 @@ function parsePublishFlags() {
|
|
|
969
1056
|
"session-title": { type: "string" },
|
|
970
1057
|
agent: { type: "string" },
|
|
971
1058
|
"new-session": { type: "boolean" },
|
|
1059
|
+
amend: { type: "boolean" },
|
|
1060
|
+
withdraw: { type: "boolean" },
|
|
972
1061
|
},
|
|
973
1062
|
});
|
|
974
1063
|
}
|
|
@@ -980,8 +1069,8 @@ function readPartFlags(values, slug, flag = "parts") {
|
|
|
980
1069
|
if (!values?.length) return undefined;
|
|
981
1070
|
const example =
|
|
982
1071
|
flag === "parts"
|
|
983
|
-
? `mockpit publish --
|
|
984
|
-
: `mockpit publish --
|
|
1072
|
+
? `mockpit publish --design ${slug} --parts body=body.html`
|
|
1073
|
+
: `mockpit publish --design ${slug} --fills entry#skeleton=skeleton.html`;
|
|
985
1074
|
const parts = {};
|
|
986
1075
|
let stdin = false;
|
|
987
1076
|
for (const raw of values) {
|
|
@@ -1003,14 +1092,15 @@ function readPartFlags(values, slug, flag = "parts") {
|
|
|
1003
1092
|
return parts;
|
|
1004
1093
|
}
|
|
1005
1094
|
|
|
1006
|
-
async function
|
|
1095
|
+
async function publishDesign() {
|
|
1007
1096
|
const { values: flags, positionals, tokens } = parsePublishFlags();
|
|
1008
|
-
const slug =
|
|
1097
|
+
const slug = requireDesign(flags, "mockpit publish --design writer --html writer.html");
|
|
1009
1098
|
const project = resolveProject(flags).name;
|
|
1010
1099
|
const from = flags.from === undefined ? undefined : Number(flags.from);
|
|
1011
1100
|
if (from !== undefined && !Number.isInteger(from)) {
|
|
1012
1101
|
die(`--from must be a version number (got "${flags.from}")`, `mockpit read ${slug} --history`);
|
|
1013
1102
|
}
|
|
1103
|
+
if (flags.withdraw) return withdrawVersion(flags, slug, project);
|
|
1014
1104
|
const step = flags.step === undefined ? undefined : Number(flags.step);
|
|
1015
1105
|
if (step !== undefined && !(Number.isInteger(step) && step >= 0)) {
|
|
1016
1106
|
die(
|
|
@@ -1059,16 +1149,16 @@ async function publishMock() {
|
|
|
1059
1149
|
}
|
|
1060
1150
|
surfaces.push(...(await surfacesFromFlags(flags, tokens, { session, layout: flags.layout })));
|
|
1061
1151
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1152
|
+
const declaring = flags.kind === "screen" || flags.kind === "component";
|
|
1153
|
+
if (!surfaces && !parts && !fills && !flags.fork && flags.flow === undefined && !declaring)
|
|
1154
|
+
die(`no html for ${slug}`, `mockpit publish --design ${slug} --html <file>`);
|
|
1064
1155
|
const poster =
|
|
1065
1156
|
flags.poster === undefined ? undefined : (await uploadFile(flags.poster, { session })).id;
|
|
1066
1157
|
const body = {
|
|
1067
1158
|
session,
|
|
1068
1159
|
project,
|
|
1069
|
-
|
|
1160
|
+
design: slug,
|
|
1070
1161
|
state: flags.state,
|
|
1071
|
-
situation: flags.situation,
|
|
1072
1162
|
variant: flags.variant,
|
|
1073
1163
|
title: flags.title,
|
|
1074
1164
|
kind: flags.kind,
|
|
@@ -1078,7 +1168,7 @@ async function publishMock() {
|
|
|
1078
1168
|
...(from !== undefined && { from }),
|
|
1079
1169
|
...(flags.fork !== undefined && { fork: flags.fork }),
|
|
1080
1170
|
prompt: flags.prompt,
|
|
1081
|
-
|
|
1171
|
+
description: flags.description,
|
|
1082
1172
|
...(flags.widths !== undefined && {
|
|
1083
1173
|
widths: flags.widths
|
|
1084
1174
|
.split(",")
|
|
@@ -1091,13 +1181,26 @@ async function publishMock() {
|
|
|
1091
1181
|
...(parts && { parts }),
|
|
1092
1182
|
...(fills && { fills }),
|
|
1093
1183
|
...(poster && { poster }),
|
|
1184
|
+
...(!poster && findBrowser() && { posterComing: true }),
|
|
1185
|
+
...(flags.amend && { amend: true }),
|
|
1094
1186
|
};
|
|
1095
1187
|
const result = await api(
|
|
1096
|
-
"/api/
|
|
1188
|
+
"/api/designs",
|
|
1097
1189
|
{ method: "POST", body: JSON.stringify(body) },
|
|
1098
1190
|
{ report: "die", fix: `mockpit read ${slug}` },
|
|
1099
1191
|
);
|
|
1100
1192
|
printPublished(result, flags);
|
|
1193
|
+
if (!poster && result.post?.surfaces?.some((s) => s.kind === "html") && !result.withdrawn) {
|
|
1194
|
+
queuePoster({
|
|
1195
|
+
base: BASE,
|
|
1196
|
+
token: TOKEN,
|
|
1197
|
+
client: HEADERS["x-mockpit-client"],
|
|
1198
|
+
session,
|
|
1199
|
+
design: result.design.id,
|
|
1200
|
+
postId: result.post.id,
|
|
1201
|
+
version: result.post.version,
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1101
1204
|
return result;
|
|
1102
1205
|
}
|
|
1103
1206
|
|
|
@@ -1148,21 +1251,22 @@ function printRun(r) {
|
|
|
1148
1251
|
|
|
1149
1252
|
function watchLines(batch) {
|
|
1150
1253
|
const lines = [];
|
|
1151
|
-
const name = batch.
|
|
1254
|
+
const name = batch.design ?? "a design";
|
|
1152
1255
|
if (batch.handoff) lines.push(`mockpit handoff on ${name}: ${batch.handoff}`);
|
|
1153
|
-
if (batch.closed) {
|
|
1154
|
-
const n = batch.dismissed?.length ?? 0;
|
|
1155
|
-
lines.push(
|
|
1156
|
-
`mockpit closed ${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"}`,
|
|
1157
|
-
);
|
|
1158
|
-
}
|
|
1159
1256
|
if (batch.edited) {
|
|
1160
1257
|
const e = batch.edited;
|
|
1161
1258
|
const bits = [
|
|
1162
1259
|
e.kind ? `now a ${e.kind}` : "",
|
|
1163
1260
|
e.area !== undefined ? (e.area ? `area ${e.area}` : "no area") : "",
|
|
1164
1261
|
].filter(Boolean);
|
|
1165
|
-
lines.push(`mockpit edited ${name}: the user moved it (${bits.join(", ")})`);
|
|
1262
|
+
if (bits.length) lines.push(`mockpit edited ${name}: the user moved it (${bits.join(", ")})`);
|
|
1263
|
+
if (e.notes !== undefined) {
|
|
1264
|
+
lines.push(
|
|
1265
|
+
e.notes === null
|
|
1266
|
+
? `mockpit edited ${name}: the user cleared its notes`
|
|
1267
|
+
: `mockpit edited ${name}: the user edited its notes: ${e.notes}`,
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1166
1270
|
}
|
|
1167
1271
|
if (batch.reply) {
|
|
1168
1272
|
const r = batch.reply;
|
|
@@ -1171,7 +1275,7 @@ function watchLines(batch) {
|
|
|
1171
1275
|
? r.asks
|
|
1172
1276
|
.map(
|
|
1173
1277
|
(a) =>
|
|
1174
|
-
`${a.text || a.ask}: ${a.chosen.map((o) => (o.other ? `“${o.label}”` : o.label)).join("+")}${a.
|
|
1278
|
+
`${a.text || a.ask}: ${a.chosen.map((o) => (o.other ? `“${o.label}”` : o.label)).join("+")}${a.comment ? ` (“${a.comment}”)` : ""}`,
|
|
1175
1279
|
)
|
|
1176
1280
|
.join("; ")
|
|
1177
1281
|
: "",
|
|
@@ -1207,7 +1311,7 @@ function watchLines(batch) {
|
|
|
1207
1311
|
return lines;
|
|
1208
1312
|
}
|
|
1209
1313
|
|
|
1210
|
-
// GET where "not there yet" is an answer, not an exit — a project or
|
|
1314
|
+
// GET where "not there yet" is an answer, not an exit — a project or design that
|
|
1211
1315
|
// doesn't exist is the normal state before the first publish.
|
|
1212
1316
|
async function apiSoft(path) {
|
|
1213
1317
|
let res;
|
|
@@ -1443,28 +1547,32 @@ const commands = {
|
|
|
1443
1547
|
const say = (label, text) => {
|
|
1444
1548
|
if (!flags.quiet && !flags.json) console.log(`${label.padEnd(8)} ${text}`);
|
|
1445
1549
|
};
|
|
1446
|
-
const {
|
|
1550
|
+
const { detectDesignSystem, detectIconSets, renderStarter } = await import("./initDesign.js");
|
|
1447
1551
|
say("project:", `${project} (from ${source})`);
|
|
1448
1552
|
|
|
1449
|
-
const
|
|
1450
|
-
const d =
|
|
1553
|
+
const designSystem = await detectDesignSystem(process.cwd());
|
|
1554
|
+
const d = designSystem.detected;
|
|
1451
1555
|
// `detected` is always an object, so emptiness has to be read off the
|
|
1452
1556
|
// fields — otherwise a repo with no design system printed a blank line.
|
|
1453
|
-
const stripped =
|
|
1454
|
-
? ` (stripped: ${
|
|
1557
|
+
const stripped = designSystem.strippedImports?.length
|
|
1558
|
+
? ` (stripped: ${designSystem.strippedImports.join(", ")})`
|
|
1455
1559
|
: "";
|
|
1456
1560
|
const found = [
|
|
1457
1561
|
d?.tailwind &&
|
|
1458
|
-
(
|
|
1562
|
+
(designSystem.tailwindSource
|
|
1563
|
+
? `tailwind: ${designSystem.tailwindSource}${stripped}`
|
|
1564
|
+
: "tailwind"),
|
|
1459
1565
|
d?.shadcn && "shadcn",
|
|
1460
|
-
d?.cssVars
|
|
1566
|
+
d?.cssVars
|
|
1567
|
+
? `${d.cssVars} css vars${designSystem.source ? ` from ${designSystem.source}` : ""}`
|
|
1568
|
+
: null,
|
|
1461
1569
|
d?.fonts?.length ? `fonts ${d.fonts.join(", ")}` : null,
|
|
1462
1570
|
].filter(Boolean);
|
|
1463
1571
|
say(
|
|
1464
1572
|
"design:",
|
|
1465
1573
|
found.length ? found.join(" · ") : "nothing detected — using the built-in palette",
|
|
1466
1574
|
);
|
|
1467
|
-
let kit = flags.kit ??
|
|
1575
|
+
let kit = flags.kit ?? designSystem.kit ?? "builtin";
|
|
1468
1576
|
// The project kit is stored first so the design PUT can name it as default.
|
|
1469
1577
|
if (flags["kit-url"] !== undefined) {
|
|
1470
1578
|
kit = flags.kit ?? "project";
|
|
@@ -1475,7 +1583,7 @@ const commands = {
|
|
|
1475
1583
|
});
|
|
1476
1584
|
}
|
|
1477
1585
|
say("kit:", kit);
|
|
1478
|
-
const files =
|
|
1586
|
+
const files = designSystem.designFiles;
|
|
1479
1587
|
if (files) {
|
|
1480
1588
|
const sh = files.shadcn;
|
|
1481
1589
|
say(
|
|
@@ -1490,16 +1598,16 @@ const commands = {
|
|
|
1490
1598
|
);
|
|
1491
1599
|
}
|
|
1492
1600
|
|
|
1493
|
-
let stored = await api(`${projectPath(project)}/design`, {
|
|
1601
|
+
let stored = await api(`${projectPath(project)}/design-system`, {
|
|
1494
1602
|
method: "PUT",
|
|
1495
1603
|
body: JSON.stringify({
|
|
1496
|
-
detected:
|
|
1497
|
-
palette:
|
|
1498
|
-
cssVars:
|
|
1499
|
-
tailwindCss:
|
|
1500
|
-
strippedImports:
|
|
1604
|
+
detected: designSystem.detected ?? null,
|
|
1605
|
+
palette: designSystem.palette ?? null,
|
|
1606
|
+
cssVars: designSystem.cssVars ?? "",
|
|
1607
|
+
tailwindCss: designSystem.tailwindCss ?? "",
|
|
1608
|
+
strippedImports: designSystem.strippedImports ?? [],
|
|
1501
1609
|
kit,
|
|
1502
|
-
designFiles:
|
|
1610
|
+
designFiles: designSystem.designFiles ?? null,
|
|
1503
1611
|
}),
|
|
1504
1612
|
});
|
|
1505
1613
|
|
|
@@ -1522,7 +1630,7 @@ const commands = {
|
|
|
1522
1630
|
if (added) say("icons:", `${f.prefix} (${added.count} icons, from ${f.from})`);
|
|
1523
1631
|
else say("icons:", `${f.prefix} not added: ${error?.message ?? "unknown error"}`);
|
|
1524
1632
|
}
|
|
1525
|
-
if (result.
|
|
1633
|
+
if (result.designSystem) stored = result.designSystem;
|
|
1526
1634
|
}
|
|
1527
1635
|
const iconSets = [
|
|
1528
1636
|
...new Set([...(stored?.iconSets ?? []).map((s) => s.prefix), ...BUNDLED_ICON_SETS]),
|
|
@@ -1531,7 +1639,7 @@ const commands = {
|
|
|
1531
1639
|
|
|
1532
1640
|
const starter = join(process.cwd(), ".mockpit", "starter.html");
|
|
1533
1641
|
mkdirSync(dirname(starter), { recursive: true });
|
|
1534
|
-
writeFileSync(starter, renderStarter({ ...
|
|
1642
|
+
writeFileSync(starter, renderStarter({ ...designSystem, kit }, iconSets));
|
|
1535
1643
|
say("wrote:", ".mockpit/starter.html");
|
|
1536
1644
|
if (ignoreMockpitDir()) say("wrote:", ".gitignore (+ .mockpit/, keeping .mockpit/rules.md)");
|
|
1537
1645
|
const rulesFile = join(repoRoot(), ".mockpit", "rules.md");
|
|
@@ -1539,24 +1647,25 @@ const commands = {
|
|
|
1539
1647
|
say("rules:", rulesSummary(await sendRules(rulesFile, `${projectPath(project)}/rules`)));
|
|
1540
1648
|
}
|
|
1541
1649
|
say("next:", "mockpit guide");
|
|
1542
|
-
if (flags.json) out({ project,
|
|
1650
|
+
if (flags.json) out({ project, designSystem: stored, starter });
|
|
1543
1651
|
},
|
|
1544
1652
|
|
|
1545
1653
|
async publish() {
|
|
1546
|
-
await
|
|
1654
|
+
await publishDesign();
|
|
1547
1655
|
},
|
|
1548
1656
|
|
|
1549
1657
|
async ask() {
|
|
1550
1658
|
const { values: flags, positionals } = parse({
|
|
1551
1659
|
allowPositionals: true,
|
|
1552
1660
|
options: {
|
|
1553
|
-
|
|
1661
|
+
design: { type: "string" },
|
|
1554
1662
|
project: { type: "string" },
|
|
1555
1663
|
session: { type: "string" },
|
|
1556
1664
|
option: { type: "string", multiple: true },
|
|
1557
1665
|
recommend: { type: "string" },
|
|
1558
1666
|
scope: { type: "string" },
|
|
1559
1667
|
state: { type: "string" },
|
|
1668
|
+
states: { type: "string", multiple: true },
|
|
1560
1669
|
part: { type: "string" },
|
|
1561
1670
|
multi: { type: "boolean" },
|
|
1562
1671
|
id: { type: "string" },
|
|
@@ -1564,16 +1673,16 @@ const commands = {
|
|
|
1564
1673
|
asks: { type: "string" },
|
|
1565
1674
|
},
|
|
1566
1675
|
});
|
|
1567
|
-
const slug =
|
|
1676
|
+
const slug = requireDesign(
|
|
1568
1677
|
flags,
|
|
1569
|
-
'mockpit ask --
|
|
1678
|
+
'mockpit ask --design writer "Quiet or dark?" --option Quiet=quiet',
|
|
1570
1679
|
);
|
|
1571
1680
|
let asks;
|
|
1572
1681
|
if (flags.asks !== undefined) {
|
|
1573
1682
|
if (positionals.length > 0) {
|
|
1574
1683
|
die(
|
|
1575
1684
|
"use the question or --asks, not both",
|
|
1576
|
-
`mockpit ask --
|
|
1685
|
+
`mockpit ask --design ${slug} --asks <json|file>`,
|
|
1577
1686
|
);
|
|
1578
1687
|
}
|
|
1579
1688
|
asks = readJsonFlag("asks", flags.asks);
|
|
@@ -1583,13 +1692,13 @@ const commands = {
|
|
|
1583
1692
|
if (!text)
|
|
1584
1693
|
die(
|
|
1585
1694
|
"ask needs a question",
|
|
1586
|
-
`mockpit ask --
|
|
1695
|
+
`mockpit ask --design ${slug} "Quiet or dark?" --option Quiet=quiet`,
|
|
1587
1696
|
);
|
|
1588
1697
|
const options = (flags.option ?? []).map(parseOption);
|
|
1589
1698
|
if (options.length === 0) {
|
|
1590
1699
|
die(
|
|
1591
1700
|
"ask needs options",
|
|
1592
|
-
`mockpit ask --
|
|
1701
|
+
`mockpit ask --design ${slug} "${text}" --option Quiet=quiet --option Dark=dark`,
|
|
1593
1702
|
);
|
|
1594
1703
|
}
|
|
1595
1704
|
if (flags.recommend !== undefined) {
|
|
@@ -1599,7 +1708,7 @@ const commands = {
|
|
|
1599
1708
|
if (!pick)
|
|
1600
1709
|
die(
|
|
1601
1710
|
`--recommend "${flags.recommend}" names no option`,
|
|
1602
|
-
`mockpit ask --
|
|
1711
|
+
`mockpit ask --design ${slug} "${text}" --option Quiet=quiet --option Dark=dark --recommend Quiet`,
|
|
1603
1712
|
);
|
|
1604
1713
|
pick.recommended = true;
|
|
1605
1714
|
}
|
|
@@ -1610,6 +1719,7 @@ const commands = {
|
|
|
1610
1719
|
...(flags.id && { id: flags.id }),
|
|
1611
1720
|
...(flags.scope && { scope: flags.scope }),
|
|
1612
1721
|
...(flags.state && { state: flags.state }),
|
|
1722
|
+
...(flags.states && { states: flags.states }),
|
|
1613
1723
|
...(flags.part && { part: flags.part }),
|
|
1614
1724
|
...(flags.multi && { multi: true }),
|
|
1615
1725
|
...(flags.after && { after: flags.after }),
|
|
@@ -1618,7 +1728,7 @@ const commands = {
|
|
|
1618
1728
|
}
|
|
1619
1729
|
const session = await resolveSession(flags);
|
|
1620
1730
|
const result = await api(
|
|
1621
|
-
`${
|
|
1731
|
+
`${designPath(slug)}/asks`,
|
|
1622
1732
|
{
|
|
1623
1733
|
method: "POST",
|
|
1624
1734
|
body: JSON.stringify({
|
|
@@ -1633,7 +1743,7 @@ const commands = {
|
|
|
1633
1743
|
if (flags.quiet) return;
|
|
1634
1744
|
for (const ask of result.asks) {
|
|
1635
1745
|
console.log(
|
|
1636
|
-
`asked on ${result.
|
|
1746
|
+
`asked on ${result.design}: ${ask.text} [${ask.options.map((o) => o.label).join(" | ")}]`,
|
|
1637
1747
|
);
|
|
1638
1748
|
}
|
|
1639
1749
|
for (const nudge of result.nudges ?? []) console.log(`nudge: ${nudge}`);
|
|
@@ -1651,10 +1761,20 @@ const commands = {
|
|
|
1651
1761
|
body: { type: "boolean" },
|
|
1652
1762
|
history: { type: "boolean" },
|
|
1653
1763
|
tree: { type: "boolean" },
|
|
1764
|
+
decisions: { type: "boolean" },
|
|
1654
1765
|
},
|
|
1655
1766
|
});
|
|
1656
1767
|
const project = resolveProject(flags).name;
|
|
1657
|
-
if (positionals.length > 1) die("read takes one
|
|
1768
|
+
if (positionals.length > 1) die("read takes one design slug", "mockpit read writer");
|
|
1769
|
+
if (flags.decisions) {
|
|
1770
|
+
return out(
|
|
1771
|
+
await api(
|
|
1772
|
+
`/api/projects/${encodeURIComponent(project)}/decisions`,
|
|
1773
|
+
{},
|
|
1774
|
+
{ report: "die", fix: `mockpit read --project ${project}` },
|
|
1775
|
+
),
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1658
1778
|
if (flags.tree) {
|
|
1659
1779
|
return out(
|
|
1660
1780
|
await api(
|
|
@@ -1668,49 +1788,56 @@ const commands = {
|
|
|
1668
1788
|
const slug = slugify(positionals[0]);
|
|
1669
1789
|
return out(
|
|
1670
1790
|
await api(
|
|
1671
|
-
`${
|
|
1791
|
+
`${designPath(slug)}${query({ project, body: flags.body, history: flags.history })}`,
|
|
1672
1792
|
{},
|
|
1673
1793
|
{ report: "die", fix: `mockpit read --project ${project}` },
|
|
1674
1794
|
),
|
|
1675
1795
|
);
|
|
1676
1796
|
}
|
|
1677
|
-
const list = (await apiSoft(`/api/
|
|
1678
|
-
|
|
1797
|
+
const list = (await apiSoft(`/api/designs${query({ project })}`)) ?? {
|
|
1798
|
+
designs: [],
|
|
1679
1799
|
open: 0,
|
|
1680
1800
|
pending: [],
|
|
1681
1801
|
};
|
|
1682
1802
|
if (flags.json) return out(list);
|
|
1683
1803
|
if (flags.quiet) return;
|
|
1684
|
-
const
|
|
1804
|
+
const designs = list.designs ?? [];
|
|
1685
1805
|
console.log(
|
|
1686
|
-
`${project} · ${
|
|
1806
|
+
`${project} · ${designs.length} design${designs.length === 1 ? "" : "s"}` +
|
|
1687
1807
|
(list.open
|
|
1688
1808
|
? ` · ${list.open} open question${list.open === 1 ? "" : "s"}`
|
|
1689
1809
|
: " · nothing waiting on the user"),
|
|
1690
1810
|
);
|
|
1691
|
-
for (const m of
|
|
1811
|
+
for (const m of designs) {
|
|
1692
1812
|
const states = m.states.length ? m.states.join(" / ") : "single state";
|
|
1693
1813
|
console.log(
|
|
1694
|
-
` ${m.slug} · ${m.kind}${m.area ? ` · ${m.area}` : ""} · ${states} · ${m.variants} variant${m.variants === 1 ? "" : "s"}${m.
|
|
1814
|
+
` ${m.slug} · ${m.kind}${m.area ? ` · ${m.area}` : ""} · ${states} · ${m.variants} variant${m.variants === 1 ? "" : "s"}${m.planned ? " · planned" : ""}${m.archivedAt ? " · archived" : m.open ? ` · ${m.open} open` : ""}`,
|
|
1695
1815
|
);
|
|
1696
1816
|
}
|
|
1697
1817
|
for (const flow of list.flows ?? []) console.log(` ${flowLine(flow)}`);
|
|
1698
1818
|
for (const line of pendingLines(list.pending)) console.log(line);
|
|
1819
|
+
if (list.decisionList) {
|
|
1820
|
+
console.log(
|
|
1821
|
+
`Decided, ${list.decisionList.total}. Treat as settled unless the user says otherwise:`,
|
|
1822
|
+
);
|
|
1823
|
+
for (const d of list.decisionList.lines) console.log(` ${d.design}: ${d.text}`);
|
|
1824
|
+
if (list.decisionList.more) console.log(` ${list.decisionList.more} (--decisions)`);
|
|
1825
|
+
}
|
|
1699
1826
|
},
|
|
1700
1827
|
async export() {
|
|
1701
1828
|
const { values: flags } = parse({
|
|
1702
1829
|
options: {
|
|
1703
|
-
|
|
1830
|
+
design: { type: "string" },
|
|
1704
1831
|
state: { type: "string" },
|
|
1705
1832
|
variant: { type: "string" },
|
|
1706
1833
|
project: { type: "string" },
|
|
1707
1834
|
out: { type: "string" },
|
|
1708
1835
|
},
|
|
1709
1836
|
});
|
|
1710
|
-
const slug =
|
|
1837
|
+
const slug = requireDesign(flags, "mockpit export --design writer");
|
|
1711
1838
|
const project = resolveProject(flags).name;
|
|
1712
1839
|
const data = await api(
|
|
1713
|
-
`${
|
|
1840
|
+
`${designPath(slug)}/export${query({ project, state: flags.state, variant: flags.variant })}`,
|
|
1714
1841
|
{},
|
|
1715
1842
|
{ report: "die", fix: `mockpit read ${slug}` },
|
|
1716
1843
|
);
|
|
@@ -1734,7 +1861,7 @@ const commands = {
|
|
|
1734
1861
|
JSON.stringify(
|
|
1735
1862
|
{
|
|
1736
1863
|
project,
|
|
1737
|
-
|
|
1864
|
+
design: slug,
|
|
1738
1865
|
state: entry.state,
|
|
1739
1866
|
variant: entry.variant,
|
|
1740
1867
|
version: entry.version,
|
|
@@ -1802,24 +1929,44 @@ const commands = {
|
|
|
1802
1929
|
},
|
|
1803
1930
|
async watch() {
|
|
1804
1931
|
const { values: flags } = parse({
|
|
1805
|
-
options: {
|
|
1932
|
+
options: {
|
|
1933
|
+
session: { type: "string" },
|
|
1934
|
+
after: { type: "string" },
|
|
1935
|
+
once: { type: "boolean" },
|
|
1936
|
+
"exit-code": { type: "string" },
|
|
1937
|
+
},
|
|
1806
1938
|
});
|
|
1807
1939
|
if (flags.after !== undefined && !/^\d+$/.test(flags.after)) {
|
|
1808
1940
|
fail(`--after must be a number (got "${flags.after}")`);
|
|
1809
1941
|
}
|
|
1942
|
+
const exitCode = flags["exit-code"] ?? "0";
|
|
1943
|
+
if (!/^\d+$/.test(exitCode)) fail(`--exit-code must be a number (got "${exitCode}")`);
|
|
1944
|
+
// A waiter outliving its agent would take a Send and tell no one, and each
|
|
1945
|
+
// Send is delivered only once.
|
|
1946
|
+
if (flags.once) {
|
|
1947
|
+
const parent = process.ppid;
|
|
1948
|
+
setInterval(() => {
|
|
1949
|
+
if (process.ppid !== parent || process.ppid === 1) process.exit(0);
|
|
1950
|
+
}, 5000).unref();
|
|
1951
|
+
}
|
|
1810
1952
|
// A continuous long-poll that streams each piece of feedback as one line —
|
|
1811
1953
|
// one line is one monitor notification. It re-arms forever; a transient
|
|
1812
1954
|
// network error backs off and retries rather than exiting.
|
|
1813
1955
|
//
|
|
1814
|
-
//
|
|
1815
|
-
//
|
|
1816
|
-
//
|
|
1956
|
+
// It only says a reply landed (peek): the reply stays on the session's cursor
|
|
1957
|
+
// until the agent's feedback or next write takes it, so a harness dropping or
|
|
1958
|
+
// cutting this output can't lose it, and each Send is still delivered once.
|
|
1817
1959
|
let firstAfter = flags.after;
|
|
1818
1960
|
for (;;) {
|
|
1819
1961
|
const key = harnessKey();
|
|
1962
|
+
// The state file is per process and cwd, which several threads of one harness
|
|
1963
|
+
// can share, so a conversation's own key decides whenever there is one.
|
|
1820
1964
|
const session =
|
|
1821
|
-
|
|
1822
|
-
|
|
1965
|
+
flags.session ??
|
|
1966
|
+
process.env.MOCKPIT_SESSION ??
|
|
1967
|
+
(key
|
|
1968
|
+
? await resolveSessionByKey(key)
|
|
1969
|
+
: ((await resolveSession(flags)) ?? (await resolveSessionByCwd())));
|
|
1823
1970
|
if (!session) {
|
|
1824
1971
|
// A conversation that hasn't used mockpit yet may never; ask rarely.
|
|
1825
1972
|
await sleep(key ? 10_000 : 2000);
|
|
@@ -1828,7 +1975,7 @@ const commands = {
|
|
|
1828
1975
|
let result;
|
|
1829
1976
|
try {
|
|
1830
1977
|
const res = await fetch(
|
|
1831
|
-
`${BASE}/api/comments${query({ session, author: "user", after: firstAfter, wait: 60 })}`,
|
|
1978
|
+
`${BASE}/api/comments${query({ session, author: "user", after: firstAfter, wait: 60, peek: 1 })}`,
|
|
1832
1979
|
{ headers: HEADERS },
|
|
1833
1980
|
);
|
|
1834
1981
|
if (!res.ok) {
|
|
@@ -1841,20 +1988,26 @@ const commands = {
|
|
|
1841
1988
|
continue;
|
|
1842
1989
|
}
|
|
1843
1990
|
firstAfter = undefined;
|
|
1844
|
-
|
|
1845
|
-
|
|
1991
|
+
const lines = (result.feedback ?? []).flatMap(watchLines);
|
|
1992
|
+
for (const line of lines) console.log(line);
|
|
1993
|
+
if (flags.once && lines.length) {
|
|
1994
|
+
// A harness may frame this output as a hook failure, so it says what it is.
|
|
1995
|
+
console.log(
|
|
1996
|
+
"This is the user's reply from the mockpit viewer, not an error. Call mockpit's feedback for all of it.",
|
|
1997
|
+
);
|
|
1998
|
+
process.exit(Number(exitCode));
|
|
1846
1999
|
}
|
|
1847
2000
|
}
|
|
1848
2001
|
},
|
|
1849
2002
|
|
|
1850
2003
|
async say() {
|
|
1851
|
-
const { values: flags, positionals } = parse({ allowPositionals: true, options:
|
|
2004
|
+
const { values: flags, positionals } = parse({ allowPositionals: true, options: DESIGN_FLAGS });
|
|
1852
2005
|
const text = positionals.join(" ").trim();
|
|
1853
|
-
if (!text) die("say needs text", 'mockpit say "…" --
|
|
1854
|
-
const slug =
|
|
2006
|
+
if (!text) die("say needs text", 'mockpit say "…" --design writer');
|
|
2007
|
+
const slug = requireDesign(flags, `mockpit say "${text}" --design writer`);
|
|
1855
2008
|
const session = await resolveSession(flags);
|
|
1856
2009
|
const result = await api(
|
|
1857
|
-
`${
|
|
2010
|
+
`${designPath(slug)}/say`,
|
|
1858
2011
|
{
|
|
1859
2012
|
method: "POST",
|
|
1860
2013
|
body: JSON.stringify({
|
|
@@ -1872,10 +2025,69 @@ const commands = {
|
|
|
1872
2025
|
console.log(`said on ${slug}`);
|
|
1873
2026
|
printFeedback(result.feedback);
|
|
1874
2027
|
},
|
|
2028
|
+
async move() {
|
|
2029
|
+
const { values: flags } = parse({
|
|
2030
|
+
options: {
|
|
2031
|
+
...DESIGN_FLAGS,
|
|
2032
|
+
variant: { type: "string", multiple: true },
|
|
2033
|
+
to: { type: "string" },
|
|
2034
|
+
"to-state": { type: "string" },
|
|
2035
|
+
archive: { type: "boolean" },
|
|
2036
|
+
restore: { type: "boolean" },
|
|
2037
|
+
"hide-older": { type: "boolean" },
|
|
2038
|
+
"delete-older": { type: "boolean" },
|
|
2039
|
+
},
|
|
2040
|
+
});
|
|
2041
|
+
const example = "mockpit move --design writer --variant quiet --archive";
|
|
2042
|
+
const slug = requireDesign(flags, example);
|
|
2043
|
+
const tidying = flags["hide-older"] || flags["delete-older"];
|
|
2044
|
+
if (!flags.variant?.length && !tidying) die("move needs --variant", example);
|
|
2045
|
+
const session = await resolveSession(flags);
|
|
2046
|
+
const result = await api(
|
|
2047
|
+
`${designPath(slug)}/move`,
|
|
2048
|
+
{
|
|
2049
|
+
method: "POST",
|
|
2050
|
+
body: JSON.stringify({
|
|
2051
|
+
state: flags.state,
|
|
2052
|
+
variants: flags.variant,
|
|
2053
|
+
to: flags.to,
|
|
2054
|
+
toState: flags["to-state"],
|
|
2055
|
+
archive: flags.archive,
|
|
2056
|
+
restore: flags.restore,
|
|
2057
|
+
hideOlder: flags["hide-older"],
|
|
2058
|
+
deleteOlder: flags["delete-older"],
|
|
2059
|
+
project: resolveProject(flags).name,
|
|
2060
|
+
session: session ?? undefined,
|
|
2061
|
+
}),
|
|
2062
|
+
},
|
|
2063
|
+
{ report: "die", fix: `mockpit read ${slug}` },
|
|
2064
|
+
);
|
|
2065
|
+
if (flags.json) return out(result);
|
|
2066
|
+
if (flags.quiet) return;
|
|
2067
|
+
if (tidying) {
|
|
2068
|
+
const verb = flags["delete-older"] ? "deleted" : "hid";
|
|
2069
|
+
console.log(`${verb} ${result.versions.length} older versions`);
|
|
2070
|
+
for (const s of result.skipped) {
|
|
2071
|
+
const which = s.version ? ` v${s.version}` : "";
|
|
2072
|
+
console.log(`stayed: ${s.state ? `${s.state}/` : ""}${s.variant}${which} (${s.why})`);
|
|
2073
|
+
}
|
|
2074
|
+
printFeedback(result.feedback);
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
const names = result.variants.map((v) => v.variant).join(", ") || "nothing";
|
|
2078
|
+
const done = result.to
|
|
2079
|
+
? `moved to ${result.to.design}`
|
|
2080
|
+
: flags.restore
|
|
2081
|
+
? "restored"
|
|
2082
|
+
: "archived";
|
|
2083
|
+
console.log(`${done}: ${names}`);
|
|
2084
|
+
for (const s of result.skipped) console.log(`stayed: ${s.variant} (${s.why})`);
|
|
2085
|
+
printFeedback(result.feedback);
|
|
2086
|
+
},
|
|
1875
2087
|
async edit() {
|
|
1876
2088
|
const { values: flags } = parse({
|
|
1877
2089
|
options: {
|
|
1878
|
-
|
|
2090
|
+
design: { type: "string" },
|
|
1879
2091
|
project: { type: "string" },
|
|
1880
2092
|
session: { type: "string" },
|
|
1881
2093
|
slug: { type: "string" },
|
|
@@ -1883,6 +2095,8 @@ const commands = {
|
|
|
1883
2095
|
kind: { type: "string" },
|
|
1884
2096
|
area: { type: "string" },
|
|
1885
2097
|
source: { type: "string" },
|
|
2098
|
+
"first-state": { type: "string" },
|
|
2099
|
+
notes: { type: "string" },
|
|
1886
2100
|
rename: { type: "string" },
|
|
1887
2101
|
"area-order": { type: "string" },
|
|
1888
2102
|
},
|
|
@@ -1891,9 +2105,9 @@ const commands = {
|
|
|
1891
2105
|
const session = (await resolveSession(flags)) ?? undefined;
|
|
1892
2106
|
const areaOrder = flags["area-order"];
|
|
1893
2107
|
if (flags.rename !== undefined || areaOrder !== undefined) {
|
|
1894
|
-
if (flags.
|
|
2108
|
+
if (flags.design) {
|
|
1895
2109
|
die(
|
|
1896
|
-
"--rename and --area-order edit the project; edit the
|
|
2110
|
+
"--rename and --area-order edit the project; edit the design without them",
|
|
1897
2111
|
"mockpit edit --help",
|
|
1898
2112
|
);
|
|
1899
2113
|
}
|
|
@@ -1922,7 +2136,7 @@ const commands = {
|
|
|
1922
2136
|
for (const nudge of result.nudges ?? []) console.log(`nudge: ${nudge}`);
|
|
1923
2137
|
if (!result.renamed) return printFeedback(result.feedback);
|
|
1924
2138
|
console.log(
|
|
1925
|
-
`renamed project ${result.renamed.from} → ${result.renamed.to} · ${result.
|
|
2139
|
+
`renamed project ${result.renamed.from} → ${result.renamed.to} · ${result.designs} design${result.designs === 1 ? "" : "s"} · ${result.url}`,
|
|
1926
2140
|
);
|
|
1927
2141
|
if (!flags.project && !process.env.MOCKPIT_PROJECT) {
|
|
1928
2142
|
console.log(
|
|
@@ -1932,40 +2146,46 @@ const commands = {
|
|
|
1932
2146
|
printFeedback(result.feedback);
|
|
1933
2147
|
return;
|
|
1934
2148
|
}
|
|
1935
|
-
const slug =
|
|
1936
|
-
const fields = ["slug", "title", "kind", "area", "source"].filter(
|
|
2149
|
+
const slug = requireDesign(flags, "mockpit edit --design writer --area Shell");
|
|
2150
|
+
const fields = ["slug", "title", "kind", "area", "source", "notes"].filter(
|
|
1937
2151
|
(k) => flags[k] !== undefined,
|
|
1938
2152
|
);
|
|
1939
|
-
if (!fields.length) {
|
|
1940
|
-
die(
|
|
2153
|
+
if (!fields.length && flags["first-state"] === undefined) {
|
|
2154
|
+
die(
|
|
2155
|
+
"edit needs --slug, --title, --kind, --area, --source, --notes or --first-state",
|
|
2156
|
+
"mockpit edit --help",
|
|
2157
|
+
);
|
|
1941
2158
|
}
|
|
1942
2159
|
const result = await api(
|
|
1943
|
-
|
|
2160
|
+
designPath(slug),
|
|
1944
2161
|
{
|
|
1945
2162
|
method: "PATCH",
|
|
1946
2163
|
body: JSON.stringify({
|
|
1947
2164
|
project,
|
|
1948
2165
|
session,
|
|
1949
2166
|
...Object.fromEntries(fields.map((k) => [k, flags[k]])),
|
|
2167
|
+
firstState: flags["first-state"],
|
|
1950
2168
|
}),
|
|
1951
2169
|
},
|
|
1952
2170
|
{ report: "die", fix: `mockpit read ${slug}` },
|
|
1953
2171
|
);
|
|
1954
2172
|
if (flags.json) return out(result);
|
|
1955
2173
|
if (flags.quiet) return;
|
|
1956
|
-
const m = result.
|
|
2174
|
+
const m = result.design;
|
|
1957
2175
|
console.log(
|
|
1958
2176
|
[
|
|
1959
2177
|
result.renamed ? `${result.renamed.from} → ${m.slug}` : m.slug,
|
|
1960
2178
|
m.kind,
|
|
1961
2179
|
m.area && `area ${m.area}`,
|
|
1962
2180
|
m.source && `source ${m.source}`,
|
|
2181
|
+
result.notes && (result.notes.applied ? "notes saved" : "notes NOT applied"),
|
|
1963
2182
|
result.url,
|
|
1964
2183
|
]
|
|
1965
2184
|
.filter(Boolean)
|
|
1966
2185
|
.join(" · "),
|
|
1967
2186
|
);
|
|
1968
2187
|
for (const nudge of result.nudges ?? []) console.log(`nudge: ${nudge}`);
|
|
2188
|
+
if (result.notes && !result.notes.applied) console.log(`notes: ${result.notes.text}`);
|
|
1969
2189
|
printFeedback(result.feedback);
|
|
1970
2190
|
},
|
|
1971
2191
|
async demo() {
|
|
@@ -1979,13 +2199,13 @@ const commands = {
|
|
|
1979
2199
|
});
|
|
1980
2200
|
for (const state of DEMO.states) {
|
|
1981
2201
|
for (const variant of DEMO.variants) {
|
|
1982
|
-
await api("/api/
|
|
2202
|
+
await api("/api/designs", {
|
|
1983
2203
|
method: "POST",
|
|
1984
2204
|
body: JSON.stringify({
|
|
1985
2205
|
session: session.id,
|
|
1986
2206
|
project: DEMO.project,
|
|
1987
|
-
|
|
1988
|
-
kind: "
|
|
2207
|
+
design: DEMO.slug,
|
|
2208
|
+
kind: "screen",
|
|
1989
2209
|
title: DEMO.title,
|
|
1990
2210
|
state: state.label,
|
|
1991
2211
|
variant: variant.name,
|
|
@@ -1995,7 +2215,7 @@ const commands = {
|
|
|
1995
2215
|
});
|
|
1996
2216
|
}
|
|
1997
2217
|
}
|
|
1998
|
-
await api(`${
|
|
2218
|
+
await api(`${designPath(DEMO.slug)}/asks`, {
|
|
1999
2219
|
method: "POST",
|
|
2000
2220
|
body: JSON.stringify({ project: DEMO.project, session: session.id, asks: DEMO.asks }),
|
|
2001
2221
|
});
|
|
@@ -2048,14 +2268,14 @@ const commands = {
|
|
|
2048
2268
|
if (sub === "add") {
|
|
2049
2269
|
const result = await installIconSets(project, sets);
|
|
2050
2270
|
if (result.errors.length) fail(result.errors.map((e) => e.message).join("; "));
|
|
2051
|
-
if (flags.json) return out(result.
|
|
2271
|
+
if (flags.json) return out(result.designSystem);
|
|
2052
2272
|
for (const a of result.added) {
|
|
2053
2273
|
if (!flags.quiet) console.log(`added ${a.prefix} (${a.count} icons)`);
|
|
2054
2274
|
}
|
|
2055
2275
|
return;
|
|
2056
2276
|
}
|
|
2057
2277
|
if (sub === "remove") {
|
|
2058
|
-
const stored = await api(`${projectPath(project)}/design`);
|
|
2278
|
+
const stored = await api(`${projectPath(project)}/design-system`);
|
|
2059
2279
|
const installed = stored?.iconSets ?? [];
|
|
2060
2280
|
for (const set of sets) {
|
|
2061
2281
|
if (installed.some((s) => s.prefix === set)) continue;
|
|
@@ -2065,14 +2285,14 @@ const commands = {
|
|
|
2065
2285
|
: `${set} is not installed for ${project}`,
|
|
2066
2286
|
);
|
|
2067
2287
|
}
|
|
2068
|
-
const
|
|
2288
|
+
const designSystem = await api(`${projectPath(project)}/design-system`, {
|
|
2069
2289
|
method: "PUT",
|
|
2070
2290
|
body: JSON.stringify({
|
|
2071
2291
|
...stored,
|
|
2072
2292
|
iconSets: installed.filter((s) => !sets.includes(s.prefix)),
|
|
2073
2293
|
}),
|
|
2074
2294
|
});
|
|
2075
|
-
if (flags.json) return out(
|
|
2295
|
+
if (flags.json) return out(designSystem);
|
|
2076
2296
|
if (!flags.quiet) console.log(`removed ${sets.join(", ")}`);
|
|
2077
2297
|
return;
|
|
2078
2298
|
}
|
|
@@ -2099,13 +2319,13 @@ const commands = {
|
|
|
2099
2319
|
const { name: project } = resolveProject(flags);
|
|
2100
2320
|
if ((sub !== "add" && sub !== "remove") || !id)
|
|
2101
2321
|
fail("usage: mockpit kit add <id> --url <url> --doc <file|text> | mockpit kit remove <id>");
|
|
2102
|
-
const
|
|
2322
|
+
const designSystem =
|
|
2103
2323
|
sub === "add"
|
|
2104
2324
|
? await putProjectKit(project, id, flags)
|
|
2105
2325
|
: await api(`${projectPath(project)}/kits/${encodeURIComponent(id)}`, {
|
|
2106
2326
|
method: "DELETE",
|
|
2107
2327
|
});
|
|
2108
|
-
if (flags.json) return out(
|
|
2328
|
+
if (flags.json) return out(designSystem);
|
|
2109
2329
|
if (!flags.quiet) console.log(`${sub === "add" ? "added" : "removed"} kit ${id} (${project})`);
|
|
2110
2330
|
},
|
|
2111
2331
|
|