mockpit 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -48
- package/bin/demoData.js +3 -3
- package/bin/mockpit.js +426 -51
- package/dist/mcp/server.js +45 -7
- package/dist/server/apiViews.d.ts +407 -21
- package/dist/server/apiViews.js +110 -11
- package/dist/server/app.d.ts +5 -1
- package/dist/server/app.js +1454 -192
- package/dist/server/codeWorker.js +2 -1
- package/dist/server/decisions.d.ts +23 -0
- package/dist/server/decisions.js +100 -0
- package/dist/server/designGuide.d.ts +6 -2
- package/dist/server/designGuide.js +96 -27
- package/dist/server/embeds.d.ts +26 -0
- package/dist/server/embeds.js +144 -0
- package/dist/server/eventLog.js +2 -1
- package/dist/server/feedbackBatch.d.ts +15 -4
- package/dist/server/feedbackBatch.js +31 -7
- package/dist/server/fills.d.ts +41 -0
- package/dist/server/fills.js +328 -0
- package/dist/server/guidance.d.ts +23 -0
- package/dist/server/guidance.js +71 -0
- package/dist/server/index.js +25 -2
- package/dist/server/knobs.d.ts +6 -2
- package/dist/server/knobs.js +11 -1
- package/dist/server/mcpHttp.d.ts +6 -0
- package/dist/server/mcpHttp.js +35 -0
- package/dist/server/mcpSpec.d.ts +14 -13
- package/dist/server/mcpSpec.js +133 -18
- package/dist/server/postSurfaces.js +6 -12
- package/dist/server/richRender.js +3 -1
- package/dist/server/run.js +10 -1
- package/dist/server/runApi.d.ts +3 -3
- package/dist/server/runApi.js +18 -13
- package/dist/server/sqlStore.d.ts +97 -5
- package/dist/server/sqlStore.js +367 -27
- package/dist/server/surfacePage.d.ts +6 -2
- package/dist/server/surfacePage.js +142 -10
- package/dist/server/themes.d.ts +2 -0
- package/dist/server/themes.js +23 -3
- package/dist/server/types.d.ts +107 -16
- package/dist/server/types.js +73 -10
- package/dist/server/viewerRequest.d.ts +2 -0
- package/dist/server/viewerRequest.js +14 -0
- package/extensions/mockpit.js +179 -16
- package/guide/packs/design-craft.md +35 -0
- package/guide/topics/asks.md +82 -31
- package/guide/topics/feedback.md +151 -43
- package/guide/topics/html.md +8 -10
- package/guide/topics/http.md +8 -4
- package/guide/topics/knobs.md +4 -3
- package/guide/topics/scripts.md +2 -2
- package/guide/topics/surfaces.md +45 -11
- package/package.json +3 -15
- package/skills/launch-video/SKILL.md +2 -2
- package/skills/mockpit/SKILL.md +23 -7
- package/viewer/dist/index.html +9 -8
- package/dist/server/slots.d.ts +0 -14
- package/dist/server/slots.js +0 -71
package/dist/server/apiViews.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Response shapes shared by every tier (REST, both MCP transports, the CLI), so
|
|
2
2
|
// one mock reads the same however an agent asks for it.
|
|
3
|
+
import { slotKnobs, slotsInSurfaces } from "./fills.js";
|
|
3
4
|
import { mergeParts, partsInSurfaces } from "./parts.js";
|
|
4
|
-
import { isSandboxedSurfaceKind,
|
|
5
|
+
import { draftedCount, inUseVersion, isSandboxedSurfaceKind, isLive, momentsOf, openCount, SURFACE_CONTENT_FIELDS, variantStatus, } from "./types.js";
|
|
5
6
|
export const surfaceRef = (surface, index) => ({
|
|
6
7
|
id: surface.id,
|
|
7
8
|
kind: surface.kind,
|
|
@@ -24,6 +25,27 @@ export const hydratedSurfaceView = (surface, index) => {
|
|
|
24
25
|
delete view[field];
|
|
25
26
|
return view;
|
|
26
27
|
};
|
|
28
|
+
// The still overviews show for a version: its html page's poster.
|
|
29
|
+
const pagePoster = (surfaces) => {
|
|
30
|
+
const page = surfaces.find((s) => s.kind === "html");
|
|
31
|
+
return page?.kind === "html" ? page.poster : undefined;
|
|
32
|
+
};
|
|
33
|
+
const posterOf = (surfaces) => {
|
|
34
|
+
const poster = pagePoster(surfaces);
|
|
35
|
+
return poster ? { poster } : {};
|
|
36
|
+
};
|
|
37
|
+
// The poster of one version of a variant: what Home shows for it.
|
|
38
|
+
const posterAt = (post, version) => pagePoster((version === post.version
|
|
39
|
+
? post.surfaces
|
|
40
|
+
: post.history.find((h) => h.version === version)?.surfaces) ?? []);
|
|
41
|
+
// The in-use version's poster, when Home shows an older version than the
|
|
42
|
+
// newest.
|
|
43
|
+
const inUsePoster = (p) => {
|
|
44
|
+
if (p.acceptedVersion === undefined || p.acceptedVersion === p.version)
|
|
45
|
+
return {};
|
|
46
|
+
const poster = posterAt(p, p.acceptedVersion);
|
|
47
|
+
return poster ? { inUsePoster: poster } : {};
|
|
48
|
+
};
|
|
27
49
|
// One past version without its bodies: enough to list versions or pick one to
|
|
28
50
|
// fetch, at a bounded cost.
|
|
29
51
|
export const historyMetaView = (version) => ({
|
|
@@ -34,12 +56,13 @@ export const historyMetaView = (version) => ({
|
|
|
34
56
|
...(version.prompt ? { prompt: version.prompt } : {}),
|
|
35
57
|
...(version.author === undefined ? {} : { author: version.author }),
|
|
36
58
|
surfaceKinds: version.surfaces.map((s) => s.kind),
|
|
59
|
+
...posterOf(version.surfaces),
|
|
37
60
|
});
|
|
38
61
|
export const variantView = (post, opts = {}) => ({
|
|
39
62
|
postId: post.id,
|
|
40
63
|
state: post.state,
|
|
41
64
|
variant: post.variant,
|
|
42
|
-
status: post.status,
|
|
65
|
+
status: variantStatus(post.status),
|
|
43
66
|
version: post.version,
|
|
44
67
|
title: post.title,
|
|
45
68
|
sessionId: post.sessionId,
|
|
@@ -48,10 +71,23 @@ export const variantView = (post, opts = {}) => ({
|
|
|
48
71
|
...(post.from === undefined ? {} : { from: post.from }),
|
|
49
72
|
...(post.prompt ? { prompt: post.prompt } : {}),
|
|
50
73
|
...(post.author === undefined ? {} : { author: post.author }),
|
|
51
|
-
|
|
52
|
-
...(post.
|
|
74
|
+
// A part with fills is a select knob on its part (fills.ts); declared knobs win.
|
|
75
|
+
...(slotsInSurfaces(post.surfaces).length || post.knobs
|
|
76
|
+
? { knobs: { ...slotKnobs(slotsInSurfaces(post.surfaces)), ...post.knobs } }
|
|
77
|
+
: {}),
|
|
78
|
+
...(slotsInSurfaces(post.surfaces).length ? { fills: slotsInSurfaces(post.surfaces) } : {}),
|
|
79
|
+
...(post.note ? { note: post.note } : {}),
|
|
80
|
+
// Embedded components as they render now (server/embeds.ts); `publishedWith`
|
|
81
|
+
// is what this version rendered with when it was published.
|
|
82
|
+
...(opts.embeds?.get(post.id)
|
|
83
|
+
? { embeds: opts.embeds.get(post.id).list, embedsKey: opts.embeds.get(post.id).key }
|
|
84
|
+
: {}),
|
|
85
|
+
...(post.embeds.length ? { publishedWith: post.embeds } : {}),
|
|
86
|
+
...(post.forkedFrom ? { forkedFrom: post.forkedFrom } : {}),
|
|
87
|
+
...(post.starred ? { starred: true } : {}),
|
|
88
|
+
...(post.acceptedVersion === undefined ? {} : { inUseVersion: post.acceptedVersion }),
|
|
53
89
|
// The part names this variant's own markup marks, so the viewer can tell
|
|
54
|
-
// which
|
|
90
|
+
// which variants render a part (Borrow) without the html bodies.
|
|
55
91
|
parts: partsInSurfaces(post.surfaces).map((p) => p.name),
|
|
56
92
|
surfaces: post.surfaces.map(opts.body ? fullSurfaceView : hydratedSurfaceView),
|
|
57
93
|
...(opts.history
|
|
@@ -67,20 +103,32 @@ export const variantView = (post, opts = {}) => ({
|
|
|
67
103
|
...(post.author === undefined ? {} : { author: post.author }),
|
|
68
104
|
}),
|
|
69
105
|
...[...post.history].sort((a, b) => b.version - a.version).map(historyMetaView),
|
|
70
|
-
],
|
|
106
|
+
].map((row) => post.hiddenVersions?.includes(row.version) ? { ...row, hidden: true } : row),
|
|
71
107
|
}
|
|
72
108
|
: {}),
|
|
73
109
|
});
|
|
74
110
|
const stateOrder = (mock) => (mock.states.length ? mock.states : [null]);
|
|
111
|
+
// What the user put in use, per state, in the mock's order; a state with
|
|
112
|
+
// nothing accepted is absent. Export and embeds resolve to exactly these.
|
|
113
|
+
export const inUseByState = (mock, posts) => stateOrder(mock).flatMap((state) => {
|
|
114
|
+
const p = posts.find((x) => x.state === state && x.status === "accepted");
|
|
115
|
+
return p ? [{ state, variant: p.variant, version: inUseVersion(p) }] : [];
|
|
116
|
+
});
|
|
75
117
|
// The parts the agent marked, per state, across the variants still in play.
|
|
76
118
|
export function partsByState(mock, posts) {
|
|
77
119
|
return stateOrder(mock).map((state) => ({
|
|
78
120
|
state,
|
|
79
121
|
parts: mergeParts(posts
|
|
80
|
-
.filter((p) => p.state === state && p.status
|
|
122
|
+
.filter((p) => p.state === state && isLive(p.status))
|
|
81
123
|
.map((p) => partsInSurfaces(p.surfaces))),
|
|
82
124
|
}));
|
|
83
125
|
}
|
|
126
|
+
// Where a mock sits in the product and the code that implements it, only once
|
|
127
|
+
// declared, so the views of a project that names neither cost nothing.
|
|
128
|
+
export const placeOf = (mock) => ({
|
|
129
|
+
...(mock.area ? { area: mock.area } : {}),
|
|
130
|
+
...(mock.source ? { source: mock.source } : {}),
|
|
131
|
+
});
|
|
84
132
|
// What Home shows for a mock: the first state's chosen (else first) variant.
|
|
85
133
|
function thumbnail(mock, posts) {
|
|
86
134
|
const first = stateOrder(mock)[0];
|
|
@@ -92,8 +140,13 @@ function thumbnail(mock, posts) {
|
|
|
92
140
|
if (!pick)
|
|
93
141
|
return null;
|
|
94
142
|
const surface = pick.surfaces.findIndex((s) => isSandboxedSurfaceKind(s.kind));
|
|
95
|
-
|
|
143
|
+
const version = inUseVersion(pick);
|
|
144
|
+
const poster = posterAt(pick, version);
|
|
145
|
+
return { postId: pick.id, surface: Math.max(surface, 0), version, ...(poster ? { poster } : {}) };
|
|
96
146
|
}
|
|
147
|
+
// A mock's situations, only once it has any, so the views of mocks without
|
|
148
|
+
// them are unchanged.
|
|
149
|
+
const situationsOf = (mock) => Object.keys(mock.situations).length ? { situations: mock.situations } : {};
|
|
97
150
|
export const mockSummaryView = (mock, posts) => {
|
|
98
151
|
const drafted = draftedCount(mock, posts);
|
|
99
152
|
return {
|
|
@@ -102,13 +155,16 @@ export const mockSummaryView = (mock, posts) => {
|
|
|
102
155
|
slug: mock.slug,
|
|
103
156
|
title: mock.title,
|
|
104
157
|
kind: mock.kind,
|
|
158
|
+
...placeOf(mock),
|
|
105
159
|
states: mock.states,
|
|
106
|
-
|
|
160
|
+
...situationsOf(mock),
|
|
161
|
+
stateCount: Math.max(momentsOf(mock.states, mock.situations).length, 1),
|
|
107
162
|
variants: posts.length,
|
|
108
163
|
open: openCount(mock, posts),
|
|
109
164
|
// Only once the user has started one, so a list with no drafts costs nothing.
|
|
110
165
|
...(drafted === null ? {} : { drafted }),
|
|
111
166
|
sessionId: mock.sessionId,
|
|
167
|
+
closedAt: mock.closedAt,
|
|
112
168
|
createdAt: mock.createdAt,
|
|
113
169
|
updatedAt: mock.updatedAt,
|
|
114
170
|
thumbnail: thumbnail(mock, posts),
|
|
@@ -122,19 +178,62 @@ export const mockDetailView = (mock, posts, opts = {}) => ({
|
|
|
122
178
|
slug: mock.slug,
|
|
123
179
|
title: mock.title,
|
|
124
180
|
kind: mock.kind,
|
|
181
|
+
...placeOf(mock),
|
|
125
182
|
states: mock.states,
|
|
183
|
+
...situationsOf(mock),
|
|
126
184
|
asks: mock.asks,
|
|
127
185
|
open: openCount(mock, posts),
|
|
128
186
|
knobs: mock.knobs,
|
|
187
|
+
widths: mock.widths,
|
|
129
188
|
sessionId: mock.sessionId,
|
|
189
|
+
closedAt: mock.closedAt,
|
|
130
190
|
createdAt: mock.createdAt,
|
|
131
191
|
updatedAt: mock.updatedAt,
|
|
132
192
|
variants: posts.map((p) => variantView(p, opts)),
|
|
193
|
+
inUse: inUseByState(mock, posts),
|
|
133
194
|
parts: partsByState(mock, posts),
|
|
134
|
-
//
|
|
135
|
-
|
|
195
|
+
// Every knob value the user has sent (answers' sets and tuning, later replies
|
|
196
|
+
// winning), never drafts. Later questions render on top of it.
|
|
197
|
+
settled: opts.settled ?? {},
|
|
136
198
|
});
|
|
137
199
|
export const sessionRowView = (session, postCount) => ({
|
|
138
200
|
...session,
|
|
139
201
|
postCount,
|
|
140
202
|
});
|
|
203
|
+
// One mock as a node of the project tree (GET /api/projects/:name/tree): its
|
|
204
|
+
// variants per state with status and lineage, and the mocks it embeds (`embeds`);
|
|
205
|
+
// `embeddedIn` is filled in once every node is known.
|
|
206
|
+
export const treeNodeView = (mock, posts, extra) => ({
|
|
207
|
+
id: mock.id,
|
|
208
|
+
slug: mock.slug,
|
|
209
|
+
title: mock.title,
|
|
210
|
+
kind: mock.kind,
|
|
211
|
+
...placeOf(mock),
|
|
212
|
+
open: openCount(mock, posts),
|
|
213
|
+
// Home shows a mock's states, never their situations.
|
|
214
|
+
states: mock.states.length ? momentsOf(mock.states, mock.situations) : [null],
|
|
215
|
+
widths: mock.widths,
|
|
216
|
+
createdAt: mock.createdAt,
|
|
217
|
+
updatedAt: mock.updatedAt,
|
|
218
|
+
closedAt: mock.closedAt,
|
|
219
|
+
settled: extra.settled,
|
|
220
|
+
embeds: extra.embeds,
|
|
221
|
+
embeddedIn: [],
|
|
222
|
+
inUse: inUseByState(mock, posts),
|
|
223
|
+
variants: posts.map((p) => ({
|
|
224
|
+
postId: p.id,
|
|
225
|
+
state: p.state,
|
|
226
|
+
variant: p.variant,
|
|
227
|
+
status: variantStatus(p.status),
|
|
228
|
+
version: p.version,
|
|
229
|
+
updatedAt: p.updatedAt,
|
|
230
|
+
surface: p.surfaces.findIndex((x) => isSandboxedSurfaceKind(x.kind)),
|
|
231
|
+
...posterOf(p.surfaces),
|
|
232
|
+
...inUsePoster(p),
|
|
233
|
+
...(p.note ? { note: p.note } : {}),
|
|
234
|
+
...(p.forkedFrom ? { forkedFrom: p.forkedFrom } : {}),
|
|
235
|
+
...(p.starred ? { starred: true } : {}),
|
|
236
|
+
...(p.acceptedVersion === undefined ? {} : { inUseVersion: p.acceptedVersion }),
|
|
237
|
+
...(extra.embedViews.get(p.id) ? { embedsKey: extra.embedViews.get(p.id).key } : {}),
|
|
238
|
+
})),
|
|
239
|
+
});
|
package/dist/server/app.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { type FeedEvent } from "./events.ts";
|
|
3
3
|
import { type GuideTopic } from "./designGuide.ts";
|
|
4
|
+
import { type GuidePack } from "./guidance.ts";
|
|
4
5
|
import { type Executor, type RunLimits } from "./run.ts";
|
|
5
6
|
import { type LogSink } from "./eventLog.ts";
|
|
6
7
|
import { type Store } from "./types.ts";
|
|
@@ -14,6 +15,8 @@ export interface AppOptions {
|
|
|
14
15
|
viewerHtml: string;
|
|
15
16
|
setupText: string;
|
|
16
17
|
topics?: Partial<Record<GuideTopic, string>>;
|
|
18
|
+
packs?: Partial<Record<GuidePack, string>>;
|
|
19
|
+
userRules?: () => Promise<string | null>;
|
|
17
20
|
authenticate?: AuthenticateHook;
|
|
18
21
|
authToken?: string;
|
|
19
22
|
basePath?: BasePathHook;
|
|
@@ -21,6 +24,7 @@ export interface AppOptions {
|
|
|
21
24
|
screenshots?: boolean;
|
|
22
25
|
version?: string;
|
|
23
26
|
upgradeCommand?: string;
|
|
27
|
+
renderRev?: string;
|
|
24
28
|
fetchLatestRelease?: () => Promise<LatestRelease | null>;
|
|
25
29
|
onEvent?: (event: FeedEvent) => void;
|
|
26
30
|
maxHoldConnections?: number;
|
|
@@ -52,4 +56,4 @@ export interface FlowContext {
|
|
|
52
56
|
request?: Request;
|
|
53
57
|
signal?: AbortSignal;
|
|
54
58
|
}
|
|
55
|
-
export declare function createApp({ store, viewerHtml, setupText, topics, authenticate, authToken, basePath, publicRead, screenshots, version, upgradeCommand, fetchLatestRelease, onEvent, maxHoldConnections, executor, runLimits, log, }: AppOptions): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
59
|
+
export declare function createApp({ store, viewerHtml, setupText, topics, packs, userRules, authenticate, authToken, basePath, publicRead, screenshots, version, upgradeCommand, renderRev, fetchLatestRelease, onEvent, maxHoldConnections, executor, runLimits, log, }: AppOptions): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|