@parity-app/cli 0.7.1 → 0.7.3
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/dist/bin.mjs +214 -1098
- package/dist/{client-D5kbiMmn.mjs → client-BtxrNeIk.mjs} +638 -13
- package/dist/index.d.mts +754 -380
- package/dist/index.mjs +1 -1
- package/dist/{schema-DcQLoUPP.mjs → schema-CemdtCeF.mjs} +19 -4
- package/dist/{src-C6QQltHZ.mjs → src-B9VW36v9.mjs} +1 -1
- package/dist/{src-DM51TWZ6.mjs → src-Bf12xdm5.mjs} +4 -4
- package/dist/{src-IWA2JHPw.mjs → src-DjSCuWcT.mjs} +3 -2
- package/dist/{src-B5sEtTw_.mjs → src-Q382DbAK.mjs} +1 -1
- package/dist/src-ok4hEd7p.mjs +5 -0
- package/package.json +1 -1
- package/skills/execute.md +14 -0
- package/dist/src-_5ETJOb0.mjs +0 -4
package/dist/bin.mjs
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as
|
|
3
|
-
import { a as describeExec, c as describeProps, f as setCaller, i as describeConsole, l as describeStatus, n as describeAccessibility, o as describeModes, p as mcpCallerName, r as describeComponents, s as describeProblems, t as call, u as describeTokens } from "./client-
|
|
2
|
+
import { c as CodedError, d as errorDetails, f as readErrorDetails, i as jsonText, l as closest, s as plainObject, t as checkData, u as errorCodes } from "./schema-CemdtCeF.mjs";
|
|
3
|
+
import { _ as requests, a as describeExec, c as describeProps, f as setCaller, g as requestJsonSchema, h as replies, i as describeConsole, l as describeStatus, m as readRequest, n as describeAccessibility, o as describeModes, p as mcpCallerName, r as describeComponents, s as describeProblems, t as call, u as describeTokens, v as skillRequest } from "./client-BtxrNeIk.mjs";
|
|
4
4
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
5
5
|
import { dirname, join, normalize, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
|
+
import { z } from "zod";
|
|
7
8
|
import { createInterface } from "node:readline";
|
|
8
9
|
// src/errors.ts
|
|
10
|
+
/**
|
|
11
|
+
* A failed call, the same in CLI `--json` and MCP `structuredContent`: a stable code to branch on, the message people
|
|
12
|
+
* read, whether the same request can succeed when sent again, the input at fault, and what to use instead. What each
|
|
13
|
+
* field means is in the CLI's README and the skill, too.
|
|
14
|
+
*/
|
|
15
|
+
const failure = z.object({
|
|
16
|
+
code: z.enum(errorCodes),
|
|
17
|
+
message: z.string(),
|
|
18
|
+
/** Whether sending the same request again, unchanged, can succeed: the app was starting, or edits collided. */
|
|
19
|
+
retryable: z.boolean(),
|
|
20
|
+
/** A JSON pointer to the input at fault, such as `/design` or `/edits/0/find`, in the operation's field names. */
|
|
21
|
+
path: z.string().optional(),
|
|
22
|
+
/** Valid ids to use instead of an unknown or ambiguous one, closest first, at most 20. */
|
|
23
|
+
alternatives: z.array(z.string()).optional(),
|
|
24
|
+
/** A failed snippet's id: retry it with `editId` and `edits` instead of sending it again. */
|
|
25
|
+
editId: z.string().optional()
|
|
26
|
+
});
|
|
9
27
|
/** A request field that isn't valid, rejected before anything reaches the app. */
|
|
10
28
|
function invalidInput(field, message) {
|
|
11
29
|
return new CodedError(message, {
|
|
@@ -47,30 +65,12 @@ function renamePath(error, names) {
|
|
|
47
65
|
].join("/")
|
|
48
66
|
};
|
|
49
67
|
}
|
|
50
|
-
/** The error's schema. What each field means is in the CLI's README and the skill, to keep every tool's schema small. */
|
|
51
|
-
const operationErrorSchema = {
|
|
52
|
-
type: "object",
|
|
53
|
-
properties: {
|
|
54
|
-
code: {
|
|
55
|
-
type: "string",
|
|
56
|
-
enum: [...errorCodes]
|
|
57
|
-
},
|
|
58
|
-
message: { type: "string" },
|
|
59
|
-
retryable: { type: "boolean" },
|
|
60
|
-
path: { type: "string" },
|
|
61
|
-
alternatives: {
|
|
62
|
-
type: "array",
|
|
63
|
-
items: { type: "string" }
|
|
64
|
-
},
|
|
65
|
-
editId: { type: "string" }
|
|
66
|
-
},
|
|
67
|
-
required: [
|
|
68
|
-
"code",
|
|
69
|
-
"message",
|
|
70
|
-
"retryable"
|
|
71
|
-
]
|
|
72
|
-
};
|
|
73
68
|
// src/execute.ts
|
|
69
|
+
replies.exec.omit({
|
|
70
|
+
error: true,
|
|
71
|
+
details: true,
|
|
72
|
+
editId: true
|
|
73
|
+
}).extend({ error: failure.optional() });
|
|
74
74
|
/** The same request fields in both adapters' paths, before MCP renames label and code. */
|
|
75
75
|
const executeFields = [
|
|
76
76
|
"tab",
|
|
@@ -84,11 +84,11 @@ function execResponse(reply, fields = executeFields) {
|
|
|
84
84
|
const { error, details, editId, ...rest } = reply;
|
|
85
85
|
if (reply.ok || error === void 0) return rest;
|
|
86
86
|
const coded = readErrorDetails(details);
|
|
87
|
-
const
|
|
87
|
+
const failed = operationError(coded ? new CodedError(error, coded) : new CodedError(error, { code: "execution_failed" }), fields);
|
|
88
88
|
return {
|
|
89
89
|
...rest,
|
|
90
90
|
error: {
|
|
91
|
-
...
|
|
91
|
+
...failed,
|
|
92
92
|
...editId ? { editId } : {}
|
|
93
93
|
}
|
|
94
94
|
};
|
|
@@ -100,285 +100,58 @@ function structuredExec(result) {
|
|
|
100
100
|
screenshots: result.screenshots.map(({ png: _png, ...shot }) => shot)
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
-
|
|
104
|
-
type: "object",
|
|
105
|
-
properties: {
|
|
106
|
-
path: {
|
|
107
|
-
type: "string",
|
|
108
|
-
description: "Where the PNG was saved."
|
|
109
|
-
},
|
|
110
|
-
design: { type: "string" },
|
|
111
|
-
state: { type: "string" },
|
|
112
|
-
mode: { type: "string" }
|
|
113
|
-
},
|
|
114
|
-
required: [
|
|
115
|
-
"path",
|
|
116
|
-
"design",
|
|
117
|
-
"state",
|
|
118
|
-
"mode"
|
|
119
|
-
]
|
|
120
|
-
};
|
|
121
|
-
const strings$1 = {
|
|
122
|
-
type: "array",
|
|
123
|
-
items: { type: "string" }
|
|
124
|
-
};
|
|
125
|
-
const discardedSchema = {
|
|
126
|
-
type: "array",
|
|
127
|
-
description: "Patches each Replace threw away with the node it swapped out. Present only when there were some; reapply what's still wanted to the replacement with Responsive, Update with a state, or UpdateState.",
|
|
128
|
-
items: {
|
|
129
|
-
type: "object",
|
|
130
|
-
properties: {
|
|
131
|
-
design: { type: "string" },
|
|
132
|
-
replaced: {
|
|
133
|
-
type: "string",
|
|
134
|
-
description: "The node Replace swapped out."
|
|
135
|
-
},
|
|
136
|
-
replacement: {
|
|
137
|
-
type: "string",
|
|
138
|
-
description: "The node that took its place."
|
|
139
|
-
},
|
|
140
|
-
nodes: {
|
|
141
|
-
type: "array",
|
|
142
|
-
items: {
|
|
143
|
-
type: "object",
|
|
144
|
-
properties: {
|
|
145
|
-
id: {
|
|
146
|
-
type: "string",
|
|
147
|
-
description: "The old node's id, or instance/inner path."
|
|
148
|
-
},
|
|
149
|
-
name: { type: "string" },
|
|
150
|
-
viewports: {
|
|
151
|
-
...strings$1,
|
|
152
|
-
description: "Viewport ids it had responsive patches for."
|
|
153
|
-
},
|
|
154
|
-
states: {
|
|
155
|
-
...strings$1,
|
|
156
|
-
description: "State ids it had patches in."
|
|
157
|
-
},
|
|
158
|
-
variants: {
|
|
159
|
-
...strings$1,
|
|
160
|
-
description: "Variant options it had patches in, as param=option."
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
required: ["id"]
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
required: [
|
|
168
|
-
"design",
|
|
169
|
-
"replaced",
|
|
170
|
-
"replacement",
|
|
171
|
-
"nodes"
|
|
172
|
-
]
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
const warningGroupsSchema = {
|
|
176
|
-
type: "array",
|
|
177
|
-
description: "The warnings as data, in the same order: each finding once, with how many places and which. Errors come first, then warnings, then suggestions.",
|
|
178
|
-
items: {
|
|
179
|
-
type: "object",
|
|
180
|
-
properties: {
|
|
181
|
-
category: {
|
|
182
|
-
type: "string",
|
|
183
|
-
description: "Stable: render_failed, unknown_component, unknown_component_design, unknown_prop, invalid_option, not_a_token, not_a_length_token, overlap, title_clearance, canvas_overlaps, kind_changed or unnamed_layout. overlap and title_clearance cover boards this call created, changed or moved: overlap when two boards collide, title_clearance when they're clear but one's title covers the other. canvas_overlaps counts the rest of the canvas's; call Overlaps() to check every board."
|
|
184
|
-
},
|
|
185
|
-
severity: {
|
|
186
|
-
type: "string",
|
|
187
|
-
enum: [
|
|
188
|
-
"error",
|
|
189
|
-
"warning",
|
|
190
|
-
"suggestion"
|
|
191
|
-
],
|
|
192
|
-
description: "error: a render failed, so a check or screenshot is missing. suggestion: worth doing, not wrong."
|
|
193
|
-
},
|
|
194
|
-
message: {
|
|
195
|
-
type: "string",
|
|
196
|
-
description: "What's wrong and what to do about it."
|
|
197
|
-
},
|
|
198
|
-
count: {
|
|
199
|
-
type: "integer",
|
|
200
|
-
minimum: 1,
|
|
201
|
-
description: "How many places it was found."
|
|
202
|
-
},
|
|
203
|
-
at: {
|
|
204
|
-
...strings$1,
|
|
205
|
-
description: "Every place: a node's design/node path, or a frame. Empty for a whole board."
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
required: [
|
|
209
|
-
"category",
|
|
210
|
-
"severity",
|
|
211
|
-
"message",
|
|
212
|
-
"count",
|
|
213
|
-
"at"
|
|
214
|
-
]
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
/** Execute's and screenshot's structured result. */
|
|
218
|
-
const execOutputSchema = {
|
|
219
|
-
type: "object",
|
|
220
|
-
properties: {
|
|
221
|
-
ok: { type: "boolean" },
|
|
222
|
-
applied: {
|
|
223
|
-
type: "boolean",
|
|
224
|
-
description: "Changes landed on the canvas as one undoable step."
|
|
225
|
-
},
|
|
226
|
-
output: {
|
|
227
|
-
type: "array",
|
|
228
|
-
items: { type: "string" },
|
|
229
|
-
description: "Lines from Print."
|
|
230
|
-
},
|
|
231
|
-
warnings: {
|
|
232
|
-
...strings$1,
|
|
233
|
-
description: "One line per warning group, places first."
|
|
234
|
-
},
|
|
235
|
-
warningGroups: warningGroupsSchema,
|
|
236
|
-
created: {
|
|
237
|
-
type: "object",
|
|
238
|
-
additionalProperties: { type: "string" },
|
|
239
|
-
description: "Names of designs and nodes created, mapped to their ids."
|
|
240
|
-
},
|
|
241
|
-
discarded: discardedSchema,
|
|
242
|
-
screenshots: {
|
|
243
|
-
type: "array",
|
|
244
|
-
items: screenshotSchema
|
|
245
|
-
},
|
|
246
|
-
error: operationErrorSchema
|
|
247
|
-
},
|
|
248
|
-
required: [
|
|
249
|
-
"ok",
|
|
250
|
-
"applied",
|
|
251
|
-
"output",
|
|
252
|
-
"warnings",
|
|
253
|
-
"created",
|
|
254
|
-
"screenshots"
|
|
255
|
-
]
|
|
256
|
-
};
|
|
257
|
-
const defaultLabel = "Edit designs";
|
|
258
|
-
/** What every call's tab field says, the same in the CLI's help, MCP tools and the skill (PAR-514). */
|
|
259
|
-
const tabDescription = "Tab id, project root or name, or \"active\" for what the person is looking at. Defaults to the open project the current folder is in, else \"active\".";
|
|
260
|
-
/** Canonical properties, also used by MCP discovery after renaming label and code. */
|
|
261
|
-
const executeProperties = {
|
|
262
|
-
tab: {
|
|
263
|
-
type: "string",
|
|
264
|
-
minLength: 1,
|
|
265
|
-
description: tabDescription
|
|
266
|
-
},
|
|
267
|
-
label: {
|
|
268
|
-
type: "string",
|
|
269
|
-
minLength: 1,
|
|
270
|
-
default: defaultLabel,
|
|
271
|
-
description: "Shown while it runs. Prefer 3 to 8 words, e.g. \"Add pricing tiers\"."
|
|
272
|
-
},
|
|
273
|
-
code: {
|
|
274
|
-
type: "string",
|
|
275
|
-
minLength: 1,
|
|
276
|
-
description: "The snippet. Omit when retrying with editId."
|
|
277
|
-
},
|
|
278
|
-
editId: {
|
|
279
|
-
type: "string",
|
|
280
|
-
minLength: 1,
|
|
281
|
-
description: "The failed call's editId, to retry with edits."
|
|
282
|
-
},
|
|
283
|
-
edits: {
|
|
284
|
-
type: "array",
|
|
285
|
-
description: "Exact find and replace edits to the failed snippet, applied in order.",
|
|
286
|
-
items: {
|
|
287
|
-
type: "object",
|
|
288
|
-
additionalProperties: false,
|
|
289
|
-
properties: {
|
|
290
|
-
find: {
|
|
291
|
-
type: "string",
|
|
292
|
-
minLength: 1
|
|
293
|
-
},
|
|
294
|
-
replace: { type: "string" },
|
|
295
|
-
all: { type: "boolean" }
|
|
296
|
-
},
|
|
297
|
-
required: ["find", "replace"]
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
function optionalText(value, field) {
|
|
302
|
-
if (value === void 0) return void 0;
|
|
303
|
-
if (typeof value !== "string" || !value.trim()) throw invalidInput(field, `${field} must be a nonblank string`);
|
|
304
|
-
return value;
|
|
305
|
-
}
|
|
306
|
-
function edit(value, index) {
|
|
307
|
-
const at = `edits/${index}`;
|
|
308
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw invalidInput(at, "Each edit must be an object");
|
|
309
|
-
if (!("find" in value) || typeof value.find !== "string" || !value.find.length) throw invalidInput(`${at}/find`, "Each edit needs a nonempty find string");
|
|
310
|
-
if (!("replace" in value) || typeof value.replace !== "string") throw invalidInput(`${at}/replace`, "Each edit needs a replace string");
|
|
311
|
-
if ("all" in value && typeof value.all !== "boolean") throw invalidInput(`${at}/all`, "Edit all must be a boolean");
|
|
312
|
-
const unknown = Object.keys(value).find((key) => ![
|
|
313
|
-
"find",
|
|
314
|
-
"replace",
|
|
315
|
-
"all"
|
|
316
|
-
].includes(key));
|
|
317
|
-
if (unknown) throw invalidInput(`${at}/${unknown}`, "Unknown edit field");
|
|
318
|
-
return {
|
|
319
|
-
find: value.find,
|
|
320
|
-
replace: value.replace,
|
|
321
|
-
..."all" in value && typeof value.all === "boolean" ? { all: value.all } : {}
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
/** Validate once, before either transport sends a request to the desktop. Never rewrite snippet or patch text. */
|
|
103
|
+
/** Checks the request once, before either transport sends it to the desktop. Snippet and patch text stay as written. */
|
|
325
104
|
function executeRequest(input) {
|
|
326
|
-
|
|
327
|
-
const label = optionalText(input.label, "label") ?? defaultLabel;
|
|
328
|
-
const code = optionalText(input.code, "code");
|
|
329
|
-
const editId = optionalText(input.editId, "editId");
|
|
330
|
-
if (!code && !editId) throw invalidInput("code", "Nothing to run: pass a snippet, or an editId with edits");
|
|
331
|
-
if (code !== void 0 && editId !== void 0) throw invalidInput("editId", "Pass a snippet or an editId, not both");
|
|
332
|
-
if (input.edits !== void 0 && !Array.isArray(input.edits)) throw invalidInput("edits", "edits must be an array");
|
|
333
|
-
if (input.edits !== void 0 && !editId) throw invalidInput("edits", "edits requires an editId");
|
|
334
|
-
const edits = Array.isArray(input.edits) ? input.edits.map(edit) : void 0;
|
|
335
|
-
return {
|
|
336
|
-
label,
|
|
337
|
-
...tab === void 0 ? {} : { tab },
|
|
338
|
-
...code === void 0 ? {} : { code },
|
|
339
|
-
...editId === void 0 ? {} : {
|
|
340
|
-
editId,
|
|
341
|
-
edits: edits ?? []
|
|
342
|
-
}
|
|
343
|
-
};
|
|
105
|
+
return readRequest(requests.exec, input);
|
|
344
106
|
}
|
|
345
107
|
/** Execution failures have the same meaning for CLI exits and MCP tool errors. */
|
|
346
108
|
function executionFailed(result) {
|
|
347
109
|
return !result.ok;
|
|
348
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Discovery's schema: every field either kind of request takes, under the adapter's names, and which ones each kind
|
|
113
|
+
* needs and refuses, from the two kinds' own schemas. Clients read one object, so the kinds become `oneOf` rules on it.
|
|
114
|
+
*/
|
|
115
|
+
function discoverySchema(names) {
|
|
116
|
+
const renamed = {
|
|
117
|
+
label: names.label,
|
|
118
|
+
code: names.code
|
|
119
|
+
};
|
|
120
|
+
const rename = (key) => renamed[key] ?? key;
|
|
121
|
+
const kinds = requests.exec.options.map((kind) => ({
|
|
122
|
+
json: requestJsonSchema(kind),
|
|
123
|
+
keys: Object.keys(kind.shape)
|
|
124
|
+
}));
|
|
125
|
+
const every = [...new Set(kinds.flatMap(({ keys }) => keys))];
|
|
126
|
+
return {
|
|
127
|
+
type: "object",
|
|
128
|
+
properties: Object.fromEntries(kinds.flatMap(({ json }) => Object.entries(json.properties ?? {})).map(([key, schema]) => [rename(key), schema])),
|
|
129
|
+
additionalProperties: false,
|
|
130
|
+
oneOf: kinds.map(({ json, keys }) => {
|
|
131
|
+
const refused = every.filter((key) => !keys.includes(key)).map((key) => ({ required: [rename(key)] }));
|
|
132
|
+
return {
|
|
133
|
+
required: (json.required ?? []).map(rename),
|
|
134
|
+
not: refused.length === 1 ? refused[0] : { anyOf: refused }
|
|
135
|
+
};
|
|
136
|
+
})
|
|
137
|
+
};
|
|
138
|
+
}
|
|
349
139
|
/** One handler for both adapters. Caller identity stays in the transport's connection context. */
|
|
350
140
|
const executeOperation = {
|
|
351
141
|
failed: executionFailed,
|
|
352
142
|
fields: executeFields,
|
|
353
|
-
outputSchema: execOutputSchema,
|
|
354
143
|
structured: structuredExec,
|
|
355
144
|
/** Discovery uses the same contract, with just the two historical MCP field aliases. */
|
|
356
|
-
inputSchema
|
|
357
|
-
label: "label",
|
|
358
|
-
code: "code"
|
|
359
|
-
}) {
|
|
360
|
-
const { label, code, ...rest } = executeProperties;
|
|
361
|
-
return {
|
|
362
|
-
type: "object",
|
|
363
|
-
properties: {
|
|
364
|
-
...rest,
|
|
365
|
-
[names.label]: label,
|
|
366
|
-
[names.code]: code
|
|
367
|
-
},
|
|
368
|
-
oneOf: [{
|
|
369
|
-
required: [names.code],
|
|
370
|
-
not: { anyOf: [{ required: ["editId"] }, { required: ["edits"] }] }
|
|
371
|
-
}, {
|
|
372
|
-
required: ["editId"],
|
|
373
|
-
not: { required: [names.code] }
|
|
374
|
-
}]
|
|
375
|
-
};
|
|
376
|
-
},
|
|
145
|
+
inputSchema: discoverySchema,
|
|
377
146
|
async run(input, send) {
|
|
378
|
-
return execResponse(await send("exec",
|
|
147
|
+
return execResponse(await send("exec", executeRequest(input), "tab", replies.exec));
|
|
379
148
|
}
|
|
380
149
|
};
|
|
381
150
|
// src/check.ts
|
|
151
|
+
/** The registry's least share of placed parts each library must render, by library, in `in-app.json`. */
|
|
152
|
+
const floorsFile = jsonText(z.record(z.string(), z.number()));
|
|
153
|
+
/** The app's errors kept for a library, by part. */
|
|
154
|
+
const appErrorsFile = jsonText(z.record(z.string(), z.string()));
|
|
382
155
|
/**
|
|
383
156
|
* `parity lib check <library>`: inserts every component of a registry library file into the running app, once on its
|
|
384
157
|
* own and once the way the Assets panel inserts it, inside its parents with its sample, and reports the parts that fail
|
|
@@ -387,8 +160,8 @@ const executeOperation = {
|
|
|
387
160
|
*/
|
|
388
161
|
async function runCheck(library, options) {
|
|
389
162
|
if (!library) throw new Error("parity lib check <library> [--tab t] [--mode m]");
|
|
390
|
-
const { componentNode, generateId, librarySource, withLibraryContext } = await import("./src-
|
|
391
|
-
const { newestEntries, registryDir } = await import("./src-
|
|
163
|
+
const { componentNode, generateId, librarySource, withLibraryContext } = await import("./src-ok4hEd7p.mjs");
|
|
164
|
+
const { newestEntries, registryDir } = await import("./src-Q382DbAK.mjs");
|
|
392
165
|
const entry = (await newestEntries()).find((item) => item.manifest.id === library);
|
|
393
166
|
if (!entry) throw new Error(`The registry has no library "${library}"`);
|
|
394
167
|
const list = entry.manifest.components?.list ?? {};
|
|
@@ -399,7 +172,8 @@ async function runCheck(library, options) {
|
|
|
399
172
|
declaration: ""
|
|
400
173
|
}]));
|
|
401
174
|
const mode = options.mode ?? "desktop-light";
|
|
402
|
-
|
|
175
|
+
/** Runs one of these snippets and reads the JSON it prints, once `printed` has checked it. */
|
|
176
|
+
const exec = async (label, code, printed) => {
|
|
403
177
|
const result = await executeOperation.run({
|
|
404
178
|
tab: options.tab,
|
|
405
179
|
label,
|
|
@@ -408,11 +182,13 @@ async function runCheck(library, options) {
|
|
|
408
182
|
if (executeOperation.failed(result)) throw new Error(result.error?.message ?? `${label} failed`);
|
|
409
183
|
const line = result.output.find((item) => item.startsWith("{") || item.startsWith("["));
|
|
410
184
|
if (!line) throw new Error(`${label} printed nothing`);
|
|
411
|
-
|
|
185
|
+
const checked = checkData(jsonText(printed), line);
|
|
186
|
+
if (checked.success) return checked.data;
|
|
187
|
+
throw new Error(`This CLI can't read what "${label}" printed (${checked.problem.pointer}: ${checked.problem.message})`);
|
|
412
188
|
};
|
|
413
|
-
const infos =
|
|
189
|
+
const infos = await exec(`Read ${library}'s props`, `const ids = ${JSON.stringify(names.map(id))}
|
|
414
190
|
const known = new Set(Components())
|
|
415
|
-
Print(JSON.stringify(Object.fromEntries(ids.filter((id) => known.has(id)).map((id) => [id, Props(id, { all: true })]))))
|
|
191
|
+
Print(JSON.stringify(Object.fromEntries(ids.filter((id) => known.has(id)).map((id) => [id, Props(id, { all: true })]))))`, z.record(z.string(), replies.props));
|
|
416
192
|
if (names.filter((name) => !infos[id(name)]).length === names.length) throw new Error(`The tab's project doesn't have ${library} installed`);
|
|
417
193
|
const taken = /* @__PURE__ */ new Set();
|
|
418
194
|
const alone = names.map((name) => {
|
|
@@ -426,18 +202,18 @@ Print(JSON.stringify(Object.fromEntries(ids.filter((id) => known.has(id)).map((i
|
|
|
426
202
|
});
|
|
427
203
|
const inside = names.filter((name) => list[name].parents?.length || list[name].sample).map((name) => withLibraryContext(componentNode(id(name), infos[id(name)], taken), sources, [], taken));
|
|
428
204
|
const failing = async (design, nodes) => {
|
|
429
|
-
|
|
205
|
+
return failures(await exec(`Check ${design}`, `for (const design of Designs().filter((d) => d.name === ${JSON.stringify(design)})) DeleteDesign(design.id)
|
|
430
206
|
const spot = FindEmptySpace({ width: 1280, height: 2000 })
|
|
431
207
|
const id = CreateDesign({ name: ${JSON.stringify(design)}, x: spot.x, y: spot.y })
|
|
432
208
|
const grid = Insert(id, { type: "layout", name: "Grid", style: { direction: "row", wrap: true, gap: 4, padding: 4 }, children: [] })
|
|
433
209
|
for (const node of ${JSON.stringify(nodes)}) Insert(grid, node)
|
|
434
|
-
Print(JSON.stringify(await Problems(id, { mode: ${JSON.stringify(mode)} })))
|
|
435
|
-
return failures(JSON.parse(line), library);
|
|
210
|
+
Print(JSON.stringify(await Problems(id, { mode: ${JSON.stringify(mode)} })))`, replies.problems.shape.problems), library);
|
|
436
211
|
};
|
|
437
212
|
const failAlone = await failing(`${library} alone`, alone);
|
|
438
213
|
const failInside = await failing(`${library} inside`, inside);
|
|
439
214
|
const report = score(library, mode, names.length, failAlone, failInside, list);
|
|
440
|
-
|
|
215
|
+
const floors = join(registryDir(), "..", "in-app.json");
|
|
216
|
+
report.floor = registryFile(floors, await readFile(floors, "utf8"), floorsFile)[library];
|
|
441
217
|
if (options.record) {
|
|
442
218
|
const kept = await recordAppErrors(registryDir(), library, report);
|
|
443
219
|
console.error(`Kept ${kept} errors from the app for ${library}; run parity lib with --registry to use them.`);
|
|
@@ -452,7 +228,15 @@ function appErrorsPath(registry, library) {
|
|
|
452
228
|
}
|
|
453
229
|
/** The app's errors kept for a library, by part, or none. */
|
|
454
230
|
async function readAppErrors(registry, library) {
|
|
455
|
-
|
|
231
|
+
const path = appErrorsPath(registry, library);
|
|
232
|
+
const text = await readFile(path, "utf8").catch(() => void 0);
|
|
233
|
+
return text === void 0 ? void 0 : registryFile(path, text, appErrorsFile);
|
|
234
|
+
}
|
|
235
|
+
/** One of the registry's own JSON files once `schema` has checked it, or an error naming the file and what's wrong. */
|
|
236
|
+
function registryFile(path, text, schema) {
|
|
237
|
+
const checked = checkData(schema, text);
|
|
238
|
+
if (checked.success) return checked.data;
|
|
239
|
+
throw new TypeError(`${path}: ${checked.problem.pointer || "/"}: ${checked.problem.message}`);
|
|
456
240
|
}
|
|
457
241
|
/**
|
|
458
242
|
* Adds the parts that failed in the app to those kept before, with their own errors rather than a parent's: one kept
|
|
@@ -511,8 +295,8 @@ function describeCheck(report) {
|
|
|
511
295
|
* because it reads the project's files and its own copy of TypeScript.
|
|
512
296
|
*/
|
|
513
297
|
async function runLib(packageName, options) {
|
|
514
|
-
const { appManifestPath, generateLibrary, manifestText, mergeManifest, reviseLibrary } = await import("./src-
|
|
515
|
-
const { covers, registryDir, registryEntries } = await import("./src-
|
|
298
|
+
const { appManifestPath, generateLibrary, manifestText, mergeManifest, reviseLibrary } = await import("./src-Bf12xdm5.mjs");
|
|
299
|
+
const { covers, registryDir, registryEntries } = await import("./src-Q382DbAK.mjs");
|
|
516
300
|
const spec = packageName === void 0 ? void 0 : packageSpec(packageName);
|
|
517
301
|
let root = resolve(options.root ?? ".");
|
|
518
302
|
if (spec && (spec.version !== void 0 || options.registry && !options.root)) {
|
|
@@ -523,7 +307,7 @@ async function runLib(packageName, options) {
|
|
|
523
307
|
console.error(`Installing ${spec.name} ${version} on its own…`);
|
|
524
308
|
root = await installedVersion(spec.name, version);
|
|
525
309
|
}
|
|
526
|
-
const { parseLibraryEdits } = await import("./src-
|
|
310
|
+
const { parseLibraryEdits } = await import("./src-Bf12xdm5.mjs");
|
|
527
311
|
const trialEdits = options.edits ? parseLibraryEdits(JSON.parse(await readFile(resolve(options.edits), "utf8"))) : void 0;
|
|
528
312
|
const recordedId = options.registry && spec ? options.id ?? (await registryEntries()).find((entry) => entry.manifest.package?.name === spec.name)?.manifest.id : void 0;
|
|
529
313
|
const appErrors = recordedId ? await readAppErrors(registryDir(), recordedId) : void 0;
|
|
@@ -548,7 +332,7 @@ async function runLib(packageName, options) {
|
|
|
548
332
|
else target = await appManifestPath(root);
|
|
549
333
|
const existing = target ? await readFile(target, "utf8").then((text) => JSON.parse(text), () => void 0) : void 0;
|
|
550
334
|
if (!options.range) {
|
|
551
|
-
const read = generated.manifest.package;
|
|
335
|
+
const read = packageVersions.safeParse(generated.manifest.package).data;
|
|
552
336
|
const range = read && keptRange(existing, read, (versions) => covers({ package: versions }, read.version));
|
|
553
337
|
if (range) generated.manifest.package = {
|
|
554
338
|
...read,
|
|
@@ -580,13 +364,21 @@ async function runLib(packageName, options) {
|
|
|
580
364
|
}, null, 2));
|
|
581
365
|
else console.log(`Wrote ${relative(process.cwd(), target) || target}\n${describeReport(report)}`);
|
|
582
366
|
}
|
|
367
|
+
/** The package a library file describes: the version read, and the range of versions the file covers. */
|
|
368
|
+
const packageVersions = z.looseObject({
|
|
369
|
+
name: z.string(),
|
|
370
|
+
version: z.string(),
|
|
371
|
+
range: z.string()
|
|
372
|
+
});
|
|
373
|
+
/** The range a file being written over has, which it keeps while that still covers the version read. */
|
|
374
|
+
const writtenRange = z.looseObject({ package: z.looseObject({ range: z.string() }) });
|
|
583
375
|
/**
|
|
584
376
|
* The range a file being written over keeps: its own, while it still covers the version just read, so a range widened
|
|
585
377
|
* by hand (Lucide's, from 0.400) survives regenerating. Undefined when the new range stands.
|
|
586
378
|
*/
|
|
587
379
|
function keptRange(existing, read, covers) {
|
|
588
|
-
const range = existing?.package
|
|
589
|
-
if (
|
|
380
|
+
const range = writtenRange.safeParse(existing).data?.package.range;
|
|
381
|
+
if (range === void 0 || range === read.range) return void 0;
|
|
590
382
|
return covers({
|
|
591
383
|
...read,
|
|
592
384
|
range
|
|
@@ -674,140 +466,20 @@ function describeReport(report) {
|
|
|
674
466
|
return lines.join("\n");
|
|
675
467
|
}
|
|
676
468
|
// src/operations.ts
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
description,
|
|
684
|
-
...fallback === void 0 ? {} : { default: fallback }
|
|
685
|
-
},
|
|
686
|
-
required,
|
|
687
|
-
read(value, name) {
|
|
688
|
-
if (value === void 0 && !required) return fallback;
|
|
689
|
-
if (typeof value !== "string" || !value.trim()) throw invalidInput(name, `${name} must be a nonblank string`);
|
|
690
|
-
return value;
|
|
691
|
-
}
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
function boolean(description) {
|
|
695
|
-
return {
|
|
696
|
-
schema: {
|
|
697
|
-
type: "boolean",
|
|
698
|
-
default: false,
|
|
699
|
-
description
|
|
700
|
-
},
|
|
701
|
-
read(value, name) {
|
|
702
|
-
if (value === void 0) return false;
|
|
703
|
-
if (typeof value !== "boolean") throw invalidInput(name, `${name} must be a boolean`);
|
|
704
|
-
return value;
|
|
705
|
-
}
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
function selector(description, fallback) {
|
|
709
|
-
const item = {
|
|
710
|
-
type: "string",
|
|
711
|
-
minLength: 1,
|
|
712
|
-
pattern: "\\S"
|
|
713
|
-
};
|
|
714
|
-
return {
|
|
715
|
-
schema: {
|
|
716
|
-
oneOf: [item, {
|
|
717
|
-
type: "array",
|
|
718
|
-
minItems: 1,
|
|
719
|
-
items: item
|
|
720
|
-
}],
|
|
721
|
-
description,
|
|
722
|
-
...fallback ? { default: fallback } : {}
|
|
723
|
-
},
|
|
724
|
-
read(value, name) {
|
|
725
|
-
if (value === void 0) return fallback;
|
|
726
|
-
if (typeof value === "string" && value.trim()) return value;
|
|
727
|
-
if (Array.isArray(value) && value.length && value.every((item) => typeof item === "string" && item.trim())) return value.length === 1 ? value[0] : value;
|
|
728
|
-
throw invalidInput(name, `${name} must be a nonblank string or a nonempty list of nonblank strings`);
|
|
729
|
-
}
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
/** How many items a list read returns at most: a default that keeps a reply small, and a ceiling. */
|
|
733
|
-
function limit(description, fallback, most) {
|
|
734
|
-
return {
|
|
735
|
-
schema: {
|
|
736
|
-
type: "integer",
|
|
737
|
-
minimum: 1,
|
|
738
|
-
maximum: most,
|
|
739
|
-
default: fallback,
|
|
740
|
-
description
|
|
741
|
-
},
|
|
742
|
-
read(value, name) {
|
|
743
|
-
if (value === void 0) return fallback;
|
|
744
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > most) throw invalidInput(name, `${name} must be a whole number from 1 to ${most}`);
|
|
745
|
-
return value;
|
|
746
|
-
}
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
const tab = text$1(tabDescription);
|
|
750
|
-
const design = text$1("Design id or name.", true);
|
|
751
|
-
const scale = {
|
|
752
|
-
schema: {
|
|
753
|
-
type: "number",
|
|
754
|
-
minimum: .25,
|
|
755
|
-
maximum: 3,
|
|
756
|
-
default: 1,
|
|
757
|
-
description: "Pixel density."
|
|
758
|
-
},
|
|
759
|
-
read(value) {
|
|
760
|
-
if (value === void 0) return 1;
|
|
761
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < .25 || value > 3) throw invalidInput("scale", "scale must be a finite number from 0.25 to 3");
|
|
762
|
-
return value;
|
|
763
|
-
}
|
|
764
|
-
};
|
|
765
|
-
const rootText = text$1("Absolute project folder path.", true);
|
|
766
|
-
const absoluteRoot = /^(?:\/|[A-Za-z]:[\\/]|\\\\)/;
|
|
767
|
-
const root$1 = {
|
|
768
|
-
...rootText,
|
|
769
|
-
schema: {
|
|
770
|
-
...rootText.schema,
|
|
771
|
-
pattern: absoluteRoot.source
|
|
772
|
-
},
|
|
773
|
-
read(value) {
|
|
774
|
-
const path = rootText.read(value, "root");
|
|
775
|
-
if (typeof path !== "string" || !absoluteRoot.test(path)) throw invalidInput("root", "root must be an absolute project folder path");
|
|
776
|
-
return path;
|
|
777
|
-
}
|
|
778
|
-
};
|
|
779
|
-
function fieldsContract(fields) {
|
|
780
|
-
return {
|
|
781
|
-
inputSchema: {
|
|
782
|
-
type: "object",
|
|
783
|
-
additionalProperties: false,
|
|
784
|
-
properties: Object.fromEntries(Object.entries(fields).map(([key, field]) => [key, field.schema])),
|
|
785
|
-
required: Object.entries(fields).filter(([, field]) => field.required).map(([key]) => key)
|
|
786
|
-
},
|
|
787
|
-
request(input) {
|
|
788
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) throw invalidInput(void 0, "Operation input must be an object");
|
|
789
|
-
for (const key of Object.keys(input)) if (!Object.hasOwn(fields, key)) throw invalidInput(key, `Unknown input field ${key}`);
|
|
790
|
-
const result = {};
|
|
791
|
-
for (const [key, field] of Object.entries(fields)) {
|
|
792
|
-
const value = field.read(input[key], key);
|
|
793
|
-
if (value !== void 0) result[key] = value;
|
|
794
|
-
}
|
|
795
|
-
return result;
|
|
796
|
-
}
|
|
797
|
-
};
|
|
798
|
-
}
|
|
799
|
-
/** An operation whose canonical result is built from the app's reply, given the validated request. */
|
|
800
|
-
function mapped(method, fields, outputSchema, result, options = {}) {
|
|
801
|
-
const contract = fieldsContract(fields);
|
|
469
|
+
/**
|
|
470
|
+
* An operation whose canonical result is built from the app's checked answer, given the checked request. A request's
|
|
471
|
+
* error points at the field, since an operation's fields are flat.
|
|
472
|
+
*/
|
|
473
|
+
function mapped(method, input, reply, result, options = {}) {
|
|
474
|
+
const fields = Object.keys(input.shape);
|
|
802
475
|
const routing = routingFor(fields);
|
|
803
476
|
return {
|
|
804
|
-
inputSchema:
|
|
805
|
-
|
|
806
|
-
fields: Object.keys(fields),
|
|
477
|
+
inputSchema: requestJsonSchema(input),
|
|
478
|
+
fields,
|
|
807
479
|
routing,
|
|
808
|
-
async run(
|
|
809
|
-
const request =
|
|
810
|
-
return result(await send(method, request, routing), request);
|
|
480
|
+
async run(given, send) {
|
|
481
|
+
const request = readRequest(input, given, 1);
|
|
482
|
+
return result(await send(method, request, routing, reply), request);
|
|
811
483
|
},
|
|
812
484
|
failed: options.failed ?? (() => false),
|
|
813
485
|
structured: options.structured ?? ((value) => Object.fromEntries(Object.entries(value)))
|
|
@@ -815,13 +487,13 @@ function mapped(method, fields, outputSchema, result, options = {}) {
|
|
|
815
487
|
}
|
|
816
488
|
/** Which copy a call goes to, from its fields: see `Operation.routing`. */
|
|
817
489
|
function routingFor(fields) {
|
|
818
|
-
if ("tab"
|
|
819
|
-
if ("root"
|
|
490
|
+
if (fields.includes("tab")) return "tab";
|
|
491
|
+
if (fields.includes("root")) return "open";
|
|
820
492
|
return "copies";
|
|
821
493
|
}
|
|
822
|
-
/** An operation whose canonical result is the app's
|
|
823
|
-
function operation(method,
|
|
824
|
-
return mapped(method,
|
|
494
|
+
/** An operation whose canonical result is the app's answer. */
|
|
495
|
+
function operation(method, input, reply, options = {}) {
|
|
496
|
+
return mapped(method, input, reply, (answer) => answer, options);
|
|
825
497
|
}
|
|
826
498
|
/** The first `count` items, with how many there were, so a reply says when it left some out. */
|
|
827
499
|
function bounded(items, count, keep = "first") {
|
|
@@ -832,660 +504,56 @@ function bounded(items, count, keep = "first") {
|
|
|
832
504
|
truncated: kept.length < items.length
|
|
833
505
|
};
|
|
834
506
|
}
|
|
835
|
-
const string = { type: "string" };
|
|
836
|
-
const strings = {
|
|
837
|
-
type: "array",
|
|
838
|
-
items: string
|
|
839
|
-
};
|
|
840
|
-
const integer = { type: "integer" };
|
|
841
|
-
const boolean_ = { type: "boolean" };
|
|
842
|
-
function object(properties, optional = []) {
|
|
843
|
-
return {
|
|
844
|
-
type: "object",
|
|
845
|
-
properties,
|
|
846
|
-
required: Object.keys(properties).filter((key) => !optional.includes(key))
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
const ref = object({
|
|
850
|
-
id: string,
|
|
851
|
-
name: string
|
|
852
|
-
});
|
|
853
|
-
const tabSchema = object({
|
|
854
|
-
id: string,
|
|
855
|
-
name: string,
|
|
856
|
-
root: string,
|
|
857
|
-
active: boolean_,
|
|
858
|
-
status: {
|
|
859
|
-
type: "string",
|
|
860
|
-
enum: [
|
|
861
|
-
"loading",
|
|
862
|
-
"ready",
|
|
863
|
-
"error"
|
|
864
|
-
]
|
|
865
|
-
},
|
|
866
|
-
error: string,
|
|
867
|
-
designs: {
|
|
868
|
-
...integer,
|
|
869
|
-
description: "How many designs the project has."
|
|
870
|
-
}
|
|
871
|
-
}, ["error"]);
|
|
872
|
-
const frameSchema = {
|
|
873
|
-
state: string,
|
|
874
|
-
mode: string
|
|
875
|
-
};
|
|
876
|
-
const number = { type: "number" };
|
|
877
|
-
const boxSchema = object({
|
|
878
|
-
x: number,
|
|
879
|
-
y: number,
|
|
880
|
-
width: number,
|
|
881
|
-
height: number
|
|
882
|
-
});
|
|
883
|
-
const designSchema = object({
|
|
884
|
-
id: string,
|
|
885
|
-
name: string,
|
|
886
|
-
kind: string,
|
|
887
|
-
file: string,
|
|
888
|
-
x: { type: "number" },
|
|
889
|
-
y: { type: "number" },
|
|
890
|
-
width: { type: "number" },
|
|
891
|
-
height: { type: "number" },
|
|
892
|
-
states: {
|
|
893
|
-
type: "array",
|
|
894
|
-
items: ref
|
|
895
|
-
},
|
|
896
|
-
showing: object({
|
|
897
|
-
state: string,
|
|
898
|
-
mode: string,
|
|
899
|
-
beside: strings
|
|
900
|
-
}),
|
|
901
|
-
nodes: integer
|
|
902
|
-
}, ["width", "height"]);
|
|
903
|
-
const stateSchema = object({
|
|
904
|
-
tab: tabSchema,
|
|
905
|
-
selection: object({
|
|
906
|
-
design: string,
|
|
907
|
-
node: string,
|
|
908
|
-
mode: string
|
|
909
|
-
}, [
|
|
910
|
-
"design",
|
|
911
|
-
"node",
|
|
912
|
-
"mode"
|
|
913
|
-
]),
|
|
914
|
-
designs: {
|
|
915
|
-
type: "array",
|
|
916
|
-
items: designSchema
|
|
917
|
-
},
|
|
918
|
-
overlaps: {
|
|
919
|
-
type: "array",
|
|
920
|
-
description: "Every pair of boards on the canvas that's too close, as execute's Overlaps() lists them. A pair with titleOnly doesn't collide: the lower board's title covers the upper one.",
|
|
921
|
-
items: object({
|
|
922
|
-
designs: strings,
|
|
923
|
-
names: strings,
|
|
924
|
-
width: {
|
|
925
|
-
...number,
|
|
926
|
-
description: "How far they share, title band included."
|
|
927
|
-
},
|
|
928
|
-
height: number,
|
|
929
|
-
titleOnly: {
|
|
930
|
-
...boolean_,
|
|
931
|
-
description: "Only the lower board's title covers the other; the boards are clear."
|
|
932
|
-
},
|
|
933
|
-
estimated: {
|
|
934
|
-
...boolean_,
|
|
935
|
-
description: "One isn't drawn yet, so its size is estimated from its frames. Absent: both measured."
|
|
936
|
-
}
|
|
937
|
-
}, [
|
|
938
|
-
"width",
|
|
939
|
-
"height",
|
|
940
|
-
"titleOnly",
|
|
941
|
-
"estimated"
|
|
942
|
-
])
|
|
943
|
-
},
|
|
944
|
-
modes: {
|
|
945
|
-
type: "array",
|
|
946
|
-
description: "The project's modes, primary first.",
|
|
947
|
-
items: object({
|
|
948
|
-
id: string,
|
|
949
|
-
name: string,
|
|
950
|
-
width: { type: "number" },
|
|
951
|
-
primary: {
|
|
952
|
-
...boolean_,
|
|
953
|
-
description: "The project's first mode: every design's main frame unless it picks another. Only one is."
|
|
954
|
-
}
|
|
955
|
-
})
|
|
956
|
-
},
|
|
957
|
-
components: strings,
|
|
958
|
-
tokens: object({
|
|
959
|
-
count: integer,
|
|
960
|
-
semantic: strings,
|
|
961
|
-
themeCategories: strings
|
|
962
|
-
}),
|
|
963
|
-
viewport: { type: "object" },
|
|
964
|
-
detection: { type: "object" }
|
|
965
|
-
}, ["viewport", "detection"]);
|
|
966
507
|
const truncation = {
|
|
967
|
-
total:
|
|
968
|
-
|
|
969
|
-
description: "How many there were before the limit."
|
|
970
|
-
},
|
|
971
|
-
truncated: {
|
|
972
|
-
...boolean_,
|
|
973
|
-
description: "Some were left out; narrow the read or raise limit."
|
|
974
|
-
}
|
|
508
|
+
total: z.int().describe("How many there were before the limit."),
|
|
509
|
+
truncated: z.boolean().describe("Some were left out; narrow the read or raise limit.")
|
|
975
510
|
};
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
items: object({
|
|
980
|
-
name: string,
|
|
981
|
-
category: string,
|
|
982
|
-
source: string,
|
|
983
|
-
values: {
|
|
984
|
-
type: "object",
|
|
985
|
-
additionalProperties: string,
|
|
986
|
-
description: "Value per theme."
|
|
987
|
-
}
|
|
988
|
-
})
|
|
989
|
-
},
|
|
511
|
+
z.looseObject({ tabs: replies.tabs });
|
|
512
|
+
replies.console.extend({
|
|
513
|
+
entries: replies.console.shape.entries.describe("The newest entries, up to the limit, oldest first."),
|
|
990
514
|
...truncation
|
|
991
515
|
});
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
entries: {
|
|
995
|
-
type: "array",
|
|
996
|
-
items: object({
|
|
997
|
-
level: {
|
|
998
|
-
type: "string",
|
|
999
|
-
enum: [
|
|
1000
|
-
"info",
|
|
1001
|
-
"warning",
|
|
1002
|
-
"error",
|
|
1003
|
-
"debug"
|
|
1004
|
-
]
|
|
1005
|
-
},
|
|
1006
|
-
text: string,
|
|
1007
|
-
source: string,
|
|
1008
|
-
line: integer,
|
|
1009
|
-
where: {
|
|
1010
|
-
type: "string",
|
|
1011
|
-
enum: ["canvas", "offscreen"]
|
|
1012
|
-
},
|
|
1013
|
-
count: integer,
|
|
1014
|
-
first: { type: "number" },
|
|
1015
|
-
last: { type: "number" }
|
|
1016
|
-
})
|
|
1017
|
-
},
|
|
516
|
+
z.looseObject({
|
|
517
|
+
tokens: replies.tokens,
|
|
1018
518
|
...truncation
|
|
1019
519
|
});
|
|
1020
|
-
const problemsSchema = object({
|
|
1021
|
-
tab: ref,
|
|
1022
|
-
design: ref,
|
|
1023
|
-
problems: {
|
|
1024
|
-
type: "array",
|
|
1025
|
-
items: object({
|
|
1026
|
-
...frameSchema,
|
|
1027
|
-
kind: {
|
|
1028
|
-
type: "string",
|
|
1029
|
-
enum: [
|
|
1030
|
-
"render-error",
|
|
1031
|
-
"unknown-component",
|
|
1032
|
-
"content-wider",
|
|
1033
|
-
"frame-overflow",
|
|
1034
|
-
"clipped"
|
|
1035
|
-
]
|
|
1036
|
-
},
|
|
1037
|
-
problem: string,
|
|
1038
|
-
node: string,
|
|
1039
|
-
name: string,
|
|
1040
|
-
box: boxSchema,
|
|
1041
|
-
container: object({
|
|
1042
|
-
node: string,
|
|
1043
|
-
name: string,
|
|
1044
|
-
box: boxSchema
|
|
1045
|
-
}, ["node", "name"]),
|
|
1046
|
-
overflow: object({
|
|
1047
|
-
left: number,
|
|
1048
|
-
right: number,
|
|
1049
|
-
top: number,
|
|
1050
|
-
bottom: number
|
|
1051
|
-
}, [
|
|
1052
|
-
"left",
|
|
1053
|
-
"right",
|
|
1054
|
-
"top",
|
|
1055
|
-
"bottom"
|
|
1056
|
-
])
|
|
1057
|
-
}, [
|
|
1058
|
-
"kind",
|
|
1059
|
-
"node",
|
|
1060
|
-
"name",
|
|
1061
|
-
"box",
|
|
1062
|
-
"container",
|
|
1063
|
-
"overflow"
|
|
1064
|
-
])
|
|
1065
|
-
},
|
|
1066
|
-
frames: integer,
|
|
1067
|
-
warnings: strings
|
|
1068
|
-
});
|
|
1069
|
-
const accessibilitySchema = object({
|
|
1070
|
-
tab: ref,
|
|
1071
|
-
design: ref,
|
|
1072
|
-
findings: {
|
|
1073
|
-
type: "array",
|
|
1074
|
-
items: object({
|
|
1075
|
-
...frameSchema,
|
|
1076
|
-
rule: string,
|
|
1077
|
-
impact: {
|
|
1078
|
-
type: "string",
|
|
1079
|
-
enum: [
|
|
1080
|
-
"critical",
|
|
1081
|
-
"serious",
|
|
1082
|
-
"moderate",
|
|
1083
|
-
"minor"
|
|
1084
|
-
]
|
|
1085
|
-
},
|
|
1086
|
-
help: string,
|
|
1087
|
-
helpUrl: string,
|
|
1088
|
-
node: string,
|
|
1089
|
-
name: string,
|
|
1090
|
-
detail: string
|
|
1091
|
-
}, ["node", "name"])
|
|
1092
|
-
},
|
|
1093
|
-
frames: integer,
|
|
1094
|
-
warnings: strings
|
|
1095
|
-
});
|
|
1096
|
-
const componentsSchema = object({
|
|
1097
|
-
components: strings,
|
|
1098
|
-
iconLibraries: strings,
|
|
1099
|
-
leftOut: {
|
|
1100
|
-
type: "array",
|
|
1101
|
-
items: object({
|
|
1102
|
-
name: string,
|
|
1103
|
-
path: string,
|
|
1104
|
-
reason: string,
|
|
1105
|
-
undecided: boolean_
|
|
1106
|
-
}, ["undecided"])
|
|
1107
|
-
},
|
|
1108
|
-
drawn: integer,
|
|
1109
|
-
failing: {
|
|
1110
|
-
type: "array",
|
|
1111
|
-
items: object({
|
|
1112
|
-
id: string,
|
|
1113
|
-
problem: {
|
|
1114
|
-
type: "string",
|
|
1115
|
-
enum: [
|
|
1116
|
-
"provider",
|
|
1117
|
-
"server",
|
|
1118
|
-
"async",
|
|
1119
|
-
"error"
|
|
1120
|
-
]
|
|
1121
|
-
},
|
|
1122
|
-
reason: string
|
|
1123
|
-
})
|
|
1124
|
-
},
|
|
1125
|
-
corrections: { type: "object" }
|
|
1126
|
-
}, [
|
|
1127
|
-
"iconLibraries",
|
|
1128
|
-
"leftOut",
|
|
1129
|
-
"drawn",
|
|
1130
|
-
"failing",
|
|
1131
|
-
"corrections"
|
|
1132
|
-
]);
|
|
1133
|
-
const propsSchema = object({
|
|
1134
|
-
component: string,
|
|
1135
|
-
path: string,
|
|
1136
|
-
import: string,
|
|
1137
|
-
known: boolean_,
|
|
1138
|
-
props: {
|
|
1139
|
-
type: "array",
|
|
1140
|
-
items: object({
|
|
1141
|
-
name: string,
|
|
1142
|
-
kind: string,
|
|
1143
|
-
group: string,
|
|
1144
|
-
options: {
|
|
1145
|
-
type: "array",
|
|
1146
|
-
items: { type: [
|
|
1147
|
-
"string",
|
|
1148
|
-
"number",
|
|
1149
|
-
"boolean"
|
|
1150
|
-
] }
|
|
1151
|
-
},
|
|
1152
|
-
default: { type: [
|
|
1153
|
-
"string",
|
|
1154
|
-
"number",
|
|
1155
|
-
"boolean",
|
|
1156
|
-
"null"
|
|
1157
|
-
] },
|
|
1158
|
-
required: boolean_
|
|
1159
|
-
}, [
|
|
1160
|
-
"group",
|
|
1161
|
-
"options",
|
|
1162
|
-
"default",
|
|
1163
|
-
"required"
|
|
1164
|
-
])
|
|
1165
|
-
},
|
|
1166
|
-
composition: object({
|
|
1167
|
-
role: {
|
|
1168
|
-
type: "string",
|
|
1169
|
-
enum: [
|
|
1170
|
-
"provider",
|
|
1171
|
-
"wrapper",
|
|
1172
|
-
"component"
|
|
1173
|
-
],
|
|
1174
|
-
description: "provider draws nothing and gives parts inside it context; wrapper draws and holds other parts."
|
|
1175
|
-
},
|
|
1176
|
-
parents: object({
|
|
1177
|
-
status: {
|
|
1178
|
-
type: "string",
|
|
1179
|
-
enum: [
|
|
1180
|
-
"listed",
|
|
1181
|
-
"none",
|
|
1182
|
-
"unknown"
|
|
1183
|
-
],
|
|
1184
|
-
description: "listed: insert it inside chain; none: it needs nothing; unknown: nothing says, see reason."
|
|
1185
|
-
},
|
|
1186
|
-
chain: {
|
|
1187
|
-
type: "array",
|
|
1188
|
-
description: "Outermost first.",
|
|
1189
|
-
items: object({
|
|
1190
|
-
component: string,
|
|
1191
|
-
optional: boolean_
|
|
1192
|
-
}, ["optional"])
|
|
1193
|
-
},
|
|
1194
|
-
from: {
|
|
1195
|
-
...string,
|
|
1196
|
-
description: "What said so, such as the library's examples or the error it throws."
|
|
1197
|
-
},
|
|
1198
|
-
reason: string
|
|
1199
|
-
}, [
|
|
1200
|
-
"chain",
|
|
1201
|
-
"from",
|
|
1202
|
-
"reason"
|
|
1203
|
-
]),
|
|
1204
|
-
holds: {
|
|
1205
|
-
...strings,
|
|
1206
|
-
description: "Parts that go directly inside it."
|
|
1207
|
-
},
|
|
1208
|
-
slots: {
|
|
1209
|
-
type: "array",
|
|
1210
|
-
items: object({
|
|
1211
|
-
name: string,
|
|
1212
|
-
key: string,
|
|
1213
|
-
kind: {
|
|
1214
|
-
type: "string",
|
|
1215
|
-
enum: [
|
|
1216
|
-
"children",
|
|
1217
|
-
"element",
|
|
1218
|
-
"render"
|
|
1219
|
-
]
|
|
1220
|
-
}
|
|
1221
|
-
})
|
|
1222
|
-
},
|
|
1223
|
-
example: object({
|
|
1224
|
-
node: {
|
|
1225
|
-
type: "object",
|
|
1226
|
-
description: "A node Insert takes as it is: the component inside its parents."
|
|
1227
|
-
},
|
|
1228
|
-
status: {
|
|
1229
|
-
type: "string",
|
|
1230
|
-
enum: [
|
|
1231
|
-
"drawn",
|
|
1232
|
-
"failing",
|
|
1233
|
-
"untried"
|
|
1234
|
-
],
|
|
1235
|
-
description: "Whether the component has drawn on the canvas."
|
|
1236
|
-
},
|
|
1237
|
-
reason: string
|
|
1238
|
-
}, ["reason"])
|
|
1239
|
-
})
|
|
1240
|
-
}, [
|
|
1241
|
-
"path",
|
|
1242
|
-
"import",
|
|
1243
|
-
"composition"
|
|
1244
|
-
]);
|
|
1245
|
-
const viewportSchema = object({
|
|
1246
|
-
id: string,
|
|
1247
|
-
name: string,
|
|
1248
|
-
width: { type: "number" },
|
|
1249
|
-
height: { type: "number" },
|
|
1250
|
-
device: string
|
|
1251
|
-
}, ["device"]);
|
|
1252
|
-
const frameReportSchema = object({
|
|
1253
|
-
mode: string,
|
|
1254
|
-
width: { type: "number" },
|
|
1255
|
-
height: {
|
|
1256
|
-
type: "number",
|
|
1257
|
-
description: "Fixed frame height, which clips; absent when it grows."
|
|
1258
|
-
},
|
|
1259
|
-
window: object({
|
|
1260
|
-
width: { type: "number" },
|
|
1261
|
-
height: { type: "number" }
|
|
1262
|
-
})
|
|
1263
|
-
}, ["height"]);
|
|
1264
|
-
const modesSchema = object({
|
|
1265
|
-
tab: ref,
|
|
1266
|
-
variables: {
|
|
1267
|
-
type: "array",
|
|
1268
|
-
items: object({
|
|
1269
|
-
id: string,
|
|
1270
|
-
name: string,
|
|
1271
|
-
kind: {
|
|
1272
|
-
type: "string",
|
|
1273
|
-
enum: [
|
|
1274
|
-
"viewport",
|
|
1275
|
-
"theme",
|
|
1276
|
-
"locale",
|
|
1277
|
-
"custom"
|
|
1278
|
-
]
|
|
1279
|
-
},
|
|
1280
|
-
options: {
|
|
1281
|
-
type: "array",
|
|
1282
|
-
items: { type: "object" }
|
|
1283
|
-
}
|
|
1284
|
-
})
|
|
1285
|
-
},
|
|
1286
|
-
viewports: {
|
|
1287
|
-
type: "array",
|
|
1288
|
-
items: viewportSchema,
|
|
1289
|
-
description: "Viewport ids, which Responsive and a node's responsive patches take. Not mode ids."
|
|
1290
|
-
},
|
|
1291
|
-
modes: {
|
|
1292
|
-
type: "array",
|
|
1293
|
-
description: "Mode ids, which renders (screenshot, problems, accessibility) and a design's modes take.",
|
|
1294
|
-
items: object({
|
|
1295
|
-
id: string,
|
|
1296
|
-
name: string,
|
|
1297
|
-
primary: boolean_,
|
|
1298
|
-
viewport: string,
|
|
1299
|
-
width: { type: "number" },
|
|
1300
|
-
height: { type: "number" },
|
|
1301
|
-
values: {
|
|
1302
|
-
type: "object",
|
|
1303
|
-
additionalProperties: string
|
|
1304
|
-
},
|
|
1305
|
-
classNames: strings,
|
|
1306
|
-
attributes: {
|
|
1307
|
-
type: "object",
|
|
1308
|
-
additionalProperties: string
|
|
1309
|
-
}
|
|
1310
|
-
})
|
|
1311
|
-
},
|
|
1312
|
-
designs: {
|
|
1313
|
-
type: "array",
|
|
1314
|
-
items: object({
|
|
1315
|
-
id: string,
|
|
1316
|
-
name: string,
|
|
1317
|
-
state: string,
|
|
1318
|
-
states: strings,
|
|
1319
|
-
main: string,
|
|
1320
|
-
beside: strings,
|
|
1321
|
-
frames: {
|
|
1322
|
-
type: "array",
|
|
1323
|
-
items: frameReportSchema
|
|
1324
|
-
}
|
|
1325
|
-
}, ["frames"])
|
|
1326
|
-
}
|
|
1327
|
-
});
|
|
1328
|
-
const historySide = object({
|
|
1329
|
-
count: integer,
|
|
1330
|
-
labels: {
|
|
1331
|
-
...strings,
|
|
1332
|
-
description: "With summary: newest first, up to limit."
|
|
1333
|
-
}
|
|
1334
|
-
}, ["labels"]);
|
|
1335
|
-
const statusSchema = object({
|
|
1336
|
-
tab: ref,
|
|
1337
|
-
revision: {
|
|
1338
|
-
type: "string",
|
|
1339
|
-
description: "Opaque; the same only while nothing in the tab has changed. Compare it with state's revision."
|
|
1340
|
-
},
|
|
1341
|
-
dirty: {
|
|
1342
|
-
...boolean_,
|
|
1343
|
-
description: "Something applied (and undoable) isn't on disk yet."
|
|
1344
|
-
},
|
|
1345
|
-
autosave: {
|
|
1346
|
-
...boolean_,
|
|
1347
|
-
description: "Changes save themselves; otherwise they wait for the person's Save."
|
|
1348
|
-
},
|
|
1349
|
-
unsaved: {
|
|
1350
|
-
type: "array",
|
|
1351
|
-
items: object({
|
|
1352
|
-
path: string,
|
|
1353
|
-
kind: {
|
|
1354
|
-
type: "string",
|
|
1355
|
-
enum: ["document", "stylesheet"]
|
|
1356
|
-
},
|
|
1357
|
-
designs: object({
|
|
1358
|
-
added: strings,
|
|
1359
|
-
changed: strings,
|
|
1360
|
-
removed: strings
|
|
1361
|
-
}),
|
|
1362
|
-
modes: boolean_,
|
|
1363
|
-
tokens: strings,
|
|
1364
|
-
more: integer
|
|
1365
|
-
}, [
|
|
1366
|
-
"designs",
|
|
1367
|
-
"modes",
|
|
1368
|
-
"tokens",
|
|
1369
|
-
"more"
|
|
1370
|
-
])
|
|
1371
|
-
},
|
|
1372
|
-
tokenEdits: {
|
|
1373
|
-
...integer,
|
|
1374
|
-
description: "Token values waiting for Save to write them to the stylesheet."
|
|
1375
|
-
},
|
|
1376
|
-
save: object({
|
|
1377
|
-
status: {
|
|
1378
|
-
type: "string",
|
|
1379
|
-
enum: [
|
|
1380
|
-
"saved",
|
|
1381
|
-
"unsaved",
|
|
1382
|
-
"saving",
|
|
1383
|
-
"failed"
|
|
1384
|
-
]
|
|
1385
|
-
},
|
|
1386
|
-
error: string
|
|
1387
|
-
}, ["error"]),
|
|
1388
|
-
errors: {
|
|
1389
|
-
type: "array",
|
|
1390
|
-
items: object({
|
|
1391
|
-
path: string,
|
|
1392
|
-
error: string
|
|
1393
|
-
})
|
|
1394
|
-
},
|
|
1395
|
-
undo: historySide,
|
|
1396
|
-
redo: historySide
|
|
1397
|
-
}, []);
|
|
1398
|
-
const checks = {
|
|
1399
|
-
tab,
|
|
1400
|
-
design,
|
|
1401
|
-
state: selector("State id, list of ids, or \"all\".", "all"),
|
|
1402
|
-
mode: selector("Mode id, list of ids, \"shown\" or \"all\".", "shown")
|
|
1403
|
-
};
|
|
1404
520
|
const operations = {
|
|
1405
|
-
tabs: mapped("tabs",
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
tab,
|
|
1412
|
-
summary: boolean("Add each unsaved file's designs and token values, and the undo and redo labels."),
|
|
1413
|
-
limit: limit("Most ids or labels in each list of the summary.", 10, 100)
|
|
1414
|
-
}, statusSchema),
|
|
1415
|
-
modes: operation("modes", {
|
|
1416
|
-
tab,
|
|
1417
|
-
design: text$1("Design id or name. With it, just that design, with each of its frames' sizes.")
|
|
1418
|
-
}, modesSchema),
|
|
1419
|
-
open: operation("open", { root: root$1 }, tabSchema),
|
|
1420
|
-
screenshot: mapped("screenshot", {
|
|
1421
|
-
tab,
|
|
1422
|
-
design,
|
|
1423
|
-
scale,
|
|
1424
|
-
state: selector("State id, list of ids, or \"all\". Defaults to the state the design shows."),
|
|
1425
|
-
mode: selector("Mode id, list of ids, \"shown\" or \"all\". Defaults to the primary mode.")
|
|
1426
|
-
}, execOutputSchema, (reply) => execResponse(reply, [
|
|
1427
|
-
"tab",
|
|
1428
|
-
"design",
|
|
1429
|
-
"scale",
|
|
1430
|
-
"state",
|
|
1431
|
-
"mode"
|
|
1432
|
-
]), {
|
|
521
|
+
tabs: mapped("tabs", requests.tabs, replies.tabs, (tabs) => ({ tabs })),
|
|
522
|
+
state: operation("state", requests.state, replies.state),
|
|
523
|
+
status: operation("status", requests.status, replies.status),
|
|
524
|
+
modes: operation("modes", requests.modes, replies.modes),
|
|
525
|
+
open: operation("open", requests.open, replies.open),
|
|
526
|
+
screenshot: mapped("screenshot", requests.screenshot, replies.screenshot, (reply) => execResponse(reply, Object.keys(requests.screenshot.shape)), {
|
|
1433
527
|
failed: executionFailed,
|
|
1434
528
|
structured: structuredExec
|
|
1435
529
|
}),
|
|
1436
|
-
problems: operation("problems",
|
|
1437
|
-
accessibility: operation("accessibility",
|
|
1438
|
-
console: mapped("console", {
|
|
1439
|
-
|
|
1440
|
-
clear: boolean("Empty the console after reading, including entries beyond limit."),
|
|
1441
|
-
limit: limit("Most entries to return, newest kept.", 50, 1e3)
|
|
1442
|
-
}, consoleSchema, ({ tab: from, entries }, request) => {
|
|
1443
|
-
const { items, total, truncated } = bounded(entries, Number(request.limit), "last");
|
|
530
|
+
problems: operation("problems", requests.problems, replies.problems, { failed: (result) => result.problems.length > 0 }),
|
|
531
|
+
accessibility: operation("accessibility", requests.accessibility, replies.accessibility, { failed: (result) => result.findings.length > 0 }),
|
|
532
|
+
console: mapped("console", requests.console, replies.console, ({ tab, entries }, request) => {
|
|
533
|
+
const { items, total, truncated } = bounded(entries, request.limit, "last");
|
|
1444
534
|
return {
|
|
1445
|
-
tab
|
|
535
|
+
tab,
|
|
1446
536
|
entries: items,
|
|
1447
537
|
total,
|
|
1448
538
|
truncated
|
|
1449
539
|
};
|
|
1450
540
|
}),
|
|
1451
|
-
tokens: mapped("tokens", {
|
|
1452
|
-
|
|
1453
|
-
category: text$1("Token category."),
|
|
1454
|
-
source: text$1("Token source, such as semantic or theme."),
|
|
1455
|
-
search: {
|
|
1456
|
-
schema: {
|
|
1457
|
-
type: "string",
|
|
1458
|
-
description: "Token name search."
|
|
1459
|
-
},
|
|
1460
|
-
read(value) {
|
|
1461
|
-
if (value !== void 0 && typeof value !== "string") throw invalidInput("search", "search must be a string");
|
|
1462
|
-
return value;
|
|
1463
|
-
}
|
|
1464
|
-
},
|
|
1465
|
-
limit: limit("Most tokens to return.", 100, 1e3)
|
|
1466
|
-
}, tokensSchema, (tokens, request) => {
|
|
1467
|
-
const { items, total, truncated } = bounded(tokens, Number(request.limit));
|
|
541
|
+
tokens: mapped("tokens", requests.tokens, replies.tokens, (tokens, request) => {
|
|
542
|
+
const { items, total, truncated } = bounded(tokens, request.limit);
|
|
1468
543
|
return {
|
|
1469
544
|
tokens: items,
|
|
1470
545
|
total,
|
|
1471
546
|
truncated
|
|
1472
547
|
};
|
|
1473
548
|
}),
|
|
1474
|
-
components: operation("components",
|
|
1475
|
-
props: operation("props",
|
|
1476
|
-
tab,
|
|
1477
|
-
component: text$1("Component id or name.", true),
|
|
1478
|
-
all: boolean("Include passed-through attributes.")
|
|
1479
|
-
}, propsSchema)
|
|
549
|
+
components: operation("components", requests.components, replies.components),
|
|
550
|
+
props: operation("props", requests.props, replies.props)
|
|
1480
551
|
};
|
|
1481
|
-
const skill = fieldsContract({ path: text$1("Skill file to read.", false, "SKILL.md") });
|
|
1482
552
|
const skillOperation = {
|
|
1483
|
-
inputSchema:
|
|
1484
|
-
fields:
|
|
553
|
+
inputSchema: requestJsonSchema(skillRequest),
|
|
554
|
+
fields: Object.keys(skillRequest.shape),
|
|
1485
555
|
async run(input, read) {
|
|
1486
|
-
|
|
1487
|
-
if (typeof path !== "string") throw invalidInput("path", "path must be a nonblank string");
|
|
1488
|
-
return read(path);
|
|
556
|
+
return read(readRequest(skillRequest, input, 1).path);
|
|
1489
557
|
}
|
|
1490
558
|
};
|
|
1491
559
|
// src/skills.ts
|
|
@@ -1516,7 +584,7 @@ async function list(directory = root) {
|
|
|
1516
584
|
*
|
|
1517
585
|
* Tools publish no outputSchema (PAR-510). Claude Code never shows one to the model, Codex's code mode puts it in the
|
|
1518
586
|
* model's context, and with them tools/list was 33 KB rather than 11. structuredContent needs no schema; the contracts
|
|
1519
|
-
* keep theirs
|
|
587
|
+
* keep theirs, which the CLI checks the app's answers with.
|
|
1520
588
|
*/
|
|
1521
589
|
function operationTool(name, description, operation, format) {
|
|
1522
590
|
return {
|
|
@@ -1541,6 +609,17 @@ const executeNames = {
|
|
|
1541
609
|
label: "description",
|
|
1542
610
|
code: "input"
|
|
1543
611
|
};
|
|
612
|
+
/** MCP's execute arguments under the contract's names: description is label and input is code. */
|
|
613
|
+
function contractNames(input) {
|
|
614
|
+
const fields = plainObject.safeParse(input).data;
|
|
615
|
+
if (!fields) return input;
|
|
616
|
+
const { description: label, input: code, ...rest } = fields;
|
|
617
|
+
return {
|
|
618
|
+
...rest,
|
|
619
|
+
...label === void 0 ? {} : { label },
|
|
620
|
+
...code === void 0 ? {} : { code }
|
|
621
|
+
};
|
|
622
|
+
}
|
|
1544
623
|
const tools = [
|
|
1545
624
|
operationTool("get_tabs", "List the projects open in Parity, one per tab. The active tab is the one the person is looking at.", operations.tabs, json),
|
|
1546
625
|
operationTool("get_app_state", "Get a tab's state: its designs (ids, states, modes shown, where their boards are and how big), boards that overlap or crowd each other's titles, the selection, project modes, components and tokens, and what Parity detected in the project (each tool's version, where it's installed and its config, the package manager, and a note naming versions Parity doesn't test). Call it first, and again after the person may have changed things. Keep boards apart: Overlaps() and FindEmptySpace() in execute help place them.", operations.state, json),
|
|
@@ -1553,13 +632,7 @@ const tools = [
|
|
|
1553
632
|
fields: executeOperation.fields,
|
|
1554
633
|
names: executeNames,
|
|
1555
634
|
async run(input) {
|
|
1556
|
-
const result = await executeOperation.run(
|
|
1557
|
-
tab: input.tab,
|
|
1558
|
-
label: input.description,
|
|
1559
|
-
code: input.input,
|
|
1560
|
-
editId: input.editId,
|
|
1561
|
-
edits: input.edits
|
|
1562
|
-
}, call);
|
|
635
|
+
const result = await executeOperation.run(contractNames(input), call);
|
|
1563
636
|
const structured = executeOperation.structured(result);
|
|
1564
637
|
return {
|
|
1565
638
|
content: [text(describeExec(result)), ...result.screenshots.map((shot) => image(shot.png))],
|
|
@@ -1588,16 +661,36 @@ const tools = [
|
|
|
1588
661
|
}
|
|
1589
662
|
];
|
|
1590
663
|
const instructions = "Parity renders a project's real components on a canvas. Designs are trees of the project's own components, layouts and text, styled only with its tokens, with states (like stories) and modes (viewport, theme, locale). Read the skill (read_skill) before designing, start with get_app_state, change designs with execute, check every state and mode with screenshot and find_problems, check accessibility with check_accessibility, and read what the components log with get_console.";
|
|
664
|
+
/**
|
|
665
|
+
* One JSON-RPC message from the client, as far as the server reads it. `params` stays open, since each method reads
|
|
666
|
+
* its own. A message without an id is a notification, which gets no reply.
|
|
667
|
+
*/
|
|
668
|
+
const message = z.looseObject({
|
|
669
|
+
id: z.union([z.string(), z.number()]).optional(),
|
|
670
|
+
method: z.string(),
|
|
671
|
+
params: z.looseObject({}).optional()
|
|
672
|
+
});
|
|
673
|
+
/** The client's side of `initialize`: who it is, for the canvas, and the protocol version it speaks. */
|
|
674
|
+
const initializeParams = z.looseObject({
|
|
675
|
+
protocolVersion: z.string().optional().catch(void 0),
|
|
676
|
+
clientInfo: z.looseObject({
|
|
677
|
+
name: z.unknown().optional(),
|
|
678
|
+
title: z.unknown().optional()
|
|
679
|
+
}).optional().catch(void 0)
|
|
680
|
+
});
|
|
681
|
+
/** `tools/call`'s params: which tool, and its arguments, which the tool's own schema checks. */
|
|
682
|
+
const toolCall = z.looseObject({
|
|
683
|
+
name: z.string(),
|
|
684
|
+
arguments: z.unknown()
|
|
685
|
+
});
|
|
1591
686
|
/** Serves Parity's tools over MCP on stdin and stdout. */
|
|
1592
687
|
function serveMcp() {
|
|
1593
688
|
const lines = createInterface({ input: process.stdin });
|
|
1594
|
-
const send = (
|
|
689
|
+
const send = (reply) => process.stdout.write(`${JSON.stringify(reply)}\n`);
|
|
1595
690
|
lines.on("line", (line) => {
|
|
1596
691
|
if (!line.trim()) return;
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
message = JSON.parse(line);
|
|
1600
|
-
} catch {
|
|
692
|
+
const json = jsonText(z.unknown()).safeParse(line);
|
|
693
|
+
if (!json.success) {
|
|
1601
694
|
send({
|
|
1602
695
|
jsonrpc: "2.0",
|
|
1603
696
|
id: null,
|
|
@@ -1608,22 +701,37 @@ function serveMcp() {
|
|
|
1608
701
|
});
|
|
1609
702
|
return;
|
|
1610
703
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
704
|
+
const read = checkData(message, json.data);
|
|
705
|
+
if (!read.success) {
|
|
706
|
+
const id = z.union([z.string(), z.number()]).safeParse(plainObject.safeParse(json.data).data?.id).data ?? null;
|
|
707
|
+
const { pointer, message: problem } = read.problem;
|
|
708
|
+
send({
|
|
1613
709
|
jsonrpc: "2.0",
|
|
1614
|
-
id
|
|
710
|
+
id,
|
|
711
|
+
error: {
|
|
712
|
+
code: -32600,
|
|
713
|
+
message: `Invalid request: ${pointer || "/"}: ${problem}`
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
const request = read.data;
|
|
719
|
+
respond(request.method, request.params).then((reply) => {
|
|
720
|
+
if (request.id !== void 0 && reply) send({
|
|
721
|
+
jsonrpc: "2.0",
|
|
722
|
+
id: request.id,
|
|
1615
723
|
...reply
|
|
1616
724
|
});
|
|
1617
725
|
});
|
|
1618
726
|
});
|
|
1619
727
|
}
|
|
1620
|
-
async function respond(
|
|
1621
|
-
switch (
|
|
728
|
+
async function respond(method, params = {}) {
|
|
729
|
+
switch (method) {
|
|
1622
730
|
case "initialize": {
|
|
1623
|
-
const
|
|
1624
|
-
setCaller(
|
|
731
|
+
const { clientInfo, protocolVersion } = initializeParams.parse(params);
|
|
732
|
+
setCaller(mcpCallerName(clientInfo), true);
|
|
1625
733
|
return { result: {
|
|
1626
|
-
protocolVersion:
|
|
734
|
+
protocolVersion: protocolVersion ?? "2025-06-18",
|
|
1627
735
|
capabilities: { tools: {} },
|
|
1628
736
|
serverInfo: {
|
|
1629
737
|
name: "parity",
|
|
@@ -1639,13 +747,21 @@ async function respond(message) {
|
|
|
1639
747
|
inputSchema
|
|
1640
748
|
})) } };
|
|
1641
749
|
case "tools/call": {
|
|
1642
|
-
const
|
|
750
|
+
const called = checkData(toolCall, params);
|
|
751
|
+
if (!called.success) {
|
|
752
|
+
const { pointer, message: problem } = called.problem;
|
|
753
|
+
return { error: {
|
|
754
|
+
code: -32602,
|
|
755
|
+
message: `Invalid params: ${pointer}: ${problem}`
|
|
756
|
+
} };
|
|
757
|
+
}
|
|
758
|
+
const tool = tools.find((candidate) => candidate.name === called.data.name);
|
|
1643
759
|
if (!tool) return { error: {
|
|
1644
760
|
code: -32602,
|
|
1645
|
-
message: `Unknown tool ${
|
|
761
|
+
message: `Unknown tool ${called.data.name}`
|
|
1646
762
|
} };
|
|
1647
763
|
try {
|
|
1648
|
-
return { result: await tool.run(
|
|
764
|
+
return { result: await tool.run(called.data.arguments ?? {}) };
|
|
1649
765
|
} catch (error) {
|
|
1650
766
|
const failure = renamePath(operationError(error, tool.fields), tool.names ?? {});
|
|
1651
767
|
return { result: {
|
|
@@ -1655,9 +771,9 @@ async function respond(message) {
|
|
|
1655
771
|
} };
|
|
1656
772
|
}
|
|
1657
773
|
}
|
|
1658
|
-
default: return
|
|
774
|
+
default: return method.startsWith("notifications/") ? void 0 : { error: {
|
|
1659
775
|
code: -32601,
|
|
1660
|
-
message: `Unknown method ${
|
|
776
|
+
message: `Unknown method ${method}`
|
|
1661
777
|
} };
|
|
1662
778
|
}
|
|
1663
779
|
}
|