@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
|
@@ -1,9 +1,625 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as nonBlankString, c as CodedError, f as readErrorDetails, i as jsonText, p as unknownReference, s as plainObject, t as checkData } from "./schema-CemdtCeF.mjs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
3
4
|
import { randomUUID } from "node:crypto";
|
|
4
5
|
import { readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
|
|
5
6
|
import { homedir } from "node:os";
|
|
6
|
-
|
|
7
|
+
// ../core/src/props.ts
|
|
8
|
+
/** Every kind of prop, so a schema can check one (PAR-590). */
|
|
9
|
+
const propKinds = [
|
|
10
|
+
"string",
|
|
11
|
+
"number",
|
|
12
|
+
"boolean",
|
|
13
|
+
"enum",
|
|
14
|
+
"node",
|
|
15
|
+
"function",
|
|
16
|
+
"object",
|
|
17
|
+
"unknown"
|
|
18
|
+
];
|
|
19
|
+
const propGroups = [
|
|
20
|
+
"variant",
|
|
21
|
+
"own",
|
|
22
|
+
"html",
|
|
23
|
+
"aria",
|
|
24
|
+
"event"
|
|
25
|
+
];
|
|
26
|
+
// ../agent/src/contracts.ts
|
|
27
|
+
/**
|
|
28
|
+
* What agents send Parity and what it answers, as zod schemas (PAR-590): each RPC method's params and its result. The
|
|
29
|
+
* CLI and its MCP server check agents' requests with them, publish them to MCP clients as JSON Schema, and check the
|
|
30
|
+
* app's answers before reading them. The app can check what it's sent with the same schemas (PAR-598). Nothing here
|
|
31
|
+
* touches files or the network, or loads the rest of core or the agent's workspace, so the CLI starts quickly.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Checks an agent's request: returns what the schema reads, with its defaults, or throws `invalid_input` with the
|
|
35
|
+
* schema's message and the field at fault as its path. `depth` stops the path that many fields in: an operation's
|
|
36
|
+
* fields are flat, so a bad item in a list of modes points at `mode`. A field set to `undefined` counts as left out,
|
|
37
|
+
* as a terminal's missing options leave it.
|
|
38
|
+
*/
|
|
39
|
+
function readRequest(schema, input, depth = Infinity) {
|
|
40
|
+
const checked = checkData(schema, withoutUnset(input));
|
|
41
|
+
if (checked.success) return checked.data;
|
|
42
|
+
const field = checked.problem.pointer.split("/").slice(1, depth + 1).join("/");
|
|
43
|
+
throw new CodedError(checked.problem.message, {
|
|
44
|
+
code: "invalid_input",
|
|
45
|
+
...field ? { field } : {}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function withoutUnset(input) {
|
|
49
|
+
const fields = plainObject.safeParse(input).data;
|
|
50
|
+
return fields ? Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== void 0)) : input;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A request's JSON Schema, as MCP's `tools/list` publishes it: what an agent may send, with the defaults. It leaves out
|
|
54
|
+
* `$schema`, since MCP reads 2020-12 without it and every tool would pay for it in the list's 12 KiB (PAR-510).
|
|
55
|
+
*/
|
|
56
|
+
function requestJsonSchema(schema) {
|
|
57
|
+
const { $schema: _dialect, ...json } = z.toJSONSchema(schema, { io: "input" });
|
|
58
|
+
return json;
|
|
59
|
+
}
|
|
60
|
+
/** Text with something besides whitespace, kept as written, since a snippet is sent as it is. */
|
|
61
|
+
function nonblank(message) {
|
|
62
|
+
return z.string(message).min(1, message).regex(/\S/, message);
|
|
63
|
+
}
|
|
64
|
+
function text(name, description) {
|
|
65
|
+
return nonblank(`${name} must be a nonblank string`).describe(description);
|
|
66
|
+
}
|
|
67
|
+
/** One id, a list of them, or a word such as "all", as renders take a state and a mode. */
|
|
68
|
+
function selector(name) {
|
|
69
|
+
const message = `${name} must be a nonblank string or a nonempty list of nonblank strings`;
|
|
70
|
+
const item = nonblank(message);
|
|
71
|
+
return z.union([item, z.array(item, message).min(1, message)], message);
|
|
72
|
+
}
|
|
73
|
+
/** A one-item list as its item, as the app has always been sent it. */
|
|
74
|
+
function single(value) {
|
|
75
|
+
return Array.isArray(value) && value.length === 1 ? value[0] : value;
|
|
76
|
+
}
|
|
77
|
+
function flag(name, description) {
|
|
78
|
+
return z.boolean(`${name} must be a boolean`).default(false).describe(description);
|
|
79
|
+
}
|
|
80
|
+
/** How many items a list read returns at most: a default that keeps a reply small, and a ceiling. */
|
|
81
|
+
function limit(description, fallback, most) {
|
|
82
|
+
const message = `limit must be a whole number from 1 to ${most}`;
|
|
83
|
+
return z.int(message).min(1, message).max(most, message).default(fallback).describe(description);
|
|
84
|
+
}
|
|
85
|
+
/** An operation's input: these fields and no others, in an object. */
|
|
86
|
+
function fields(shape) {
|
|
87
|
+
return z.strictObject(shape, { error: (issue) => issue.code === "unrecognized_keys" ? `Unknown input field ${issue.keys[0] ?? ""}` : "Operation input must be an object" });
|
|
88
|
+
}
|
|
89
|
+
const tab = text("tab", "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\".").optional();
|
|
90
|
+
const design = text("design", "Design id or name.");
|
|
91
|
+
const scaleMessage = "scale must be a finite number from 0.25 to 3";
|
|
92
|
+
const scale = z.number(scaleMessage).min(.25, scaleMessage).max(3, scaleMessage).default(1).describe("Pixel density.");
|
|
93
|
+
const absoluteRoot = /^(?:\/|[A-Za-z]:[\\/]|\\\\)/;
|
|
94
|
+
const blankRoot = "root must be a nonblank string";
|
|
95
|
+
/** A project folder. Being absolute already rules out blank text, so the schema publishes only that pattern. */
|
|
96
|
+
const root = z.string(blankRoot).min(1, blankRoot).refine((path) => /\S/.test(path), blankRoot).regex(absoluteRoot, "root must be an absolute project folder path").describe("Absolute project folder path.");
|
|
97
|
+
/** A render's frames: every state and the modes the design shows, unless the call narrows them. */
|
|
98
|
+
const checks = {
|
|
99
|
+
tab,
|
|
100
|
+
design,
|
|
101
|
+
state: selector("state").default("all").transform(single).describe("State id, list of ids, or \"all\"."),
|
|
102
|
+
mode: selector("mode").default("shown").transform(single).describe("Mode id, list of ids, \"shown\" or \"all\".")
|
|
103
|
+
};
|
|
104
|
+
const defaultLabel = "Edit designs";
|
|
105
|
+
const nothingToRun = "Nothing to run: pass a snippet, or an editId with edits";
|
|
106
|
+
const codeMessage = "code must be a nonblank string";
|
|
107
|
+
const findMessage = "Each edit needs a nonempty find string";
|
|
108
|
+
/** One exact find and replace edit to a failed snippet. */
|
|
109
|
+
const edit = z.strictObject({
|
|
110
|
+
find: z.string(findMessage).min(1, findMessage),
|
|
111
|
+
replace: z.string("Each edit needs a replace string"),
|
|
112
|
+
all: z.boolean("Edit all must be a boolean").optional()
|
|
113
|
+
}, { error: (issue) => issue.code === "unrecognized_keys" ? "Unknown edit field" : "Each edit must be an object" });
|
|
114
|
+
/** Execute's fields. A snippet and a retry take different ones; MCP names label and code description and input. */
|
|
115
|
+
const execute = {
|
|
116
|
+
tab,
|
|
117
|
+
label: nonblank("label must be a nonblank string").default(defaultLabel).describe("Shown while it runs. Prefer 3 to 8 words, e.g. \"Add pricing tiers\"."),
|
|
118
|
+
code: z.string({ error: (issue) => issue.input === void 0 ? nothingToRun : codeMessage }).min(1, codeMessage).regex(/\S/, codeMessage).describe("The snippet. Omit when retrying with editId."),
|
|
119
|
+
editId: text("editId", "The failed call's editId, to retry with edits."),
|
|
120
|
+
edits: z.array(edit, "edits must be an array").default([]).describe("Exact find and replace edits to the failed snippet, applied in order.")
|
|
121
|
+
};
|
|
122
|
+
/** A field only the other kind of request takes: a snippet with an editId, or retry edits with no editId. */
|
|
123
|
+
function misplaced(keys) {
|
|
124
|
+
if (keys.includes("editId") || keys.includes("code")) return "Pass a snippet or an editId, not both";
|
|
125
|
+
if (keys.includes("edits")) return "edits requires an editId";
|
|
126
|
+
return `Unknown input field ${keys[0] ?? ""}`;
|
|
127
|
+
}
|
|
128
|
+
function executeKind(shape) {
|
|
129
|
+
return z.strictObject(shape, { error: (issue) => issue.code === "unrecognized_keys" ? misplaced(issue.keys) : "Operation input must be an object" });
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Each operation's input by RPC method, as the CLI and MCP take it and the app is sent it. Execute's is a snippet to
|
|
133
|
+
* run, or a failed snippet's editId with edits to it (PAR-451).
|
|
134
|
+
*/
|
|
135
|
+
const requests = {
|
|
136
|
+
tabs: fields({}),
|
|
137
|
+
state: fields({ tab }),
|
|
138
|
+
status: fields({
|
|
139
|
+
tab,
|
|
140
|
+
summary: flag("summary", "Add each unsaved file's designs and token values, and the undo and redo labels."),
|
|
141
|
+
limit: limit("Most ids or labels in each list of the summary.", 10, 100)
|
|
142
|
+
}),
|
|
143
|
+
modes: fields({
|
|
144
|
+
tab,
|
|
145
|
+
design: text("design", "Design id or name. With it, just that design, with each of its frames' sizes.").optional()
|
|
146
|
+
}),
|
|
147
|
+
open: fields({ root }),
|
|
148
|
+
screenshot: fields({
|
|
149
|
+
tab,
|
|
150
|
+
design,
|
|
151
|
+
scale,
|
|
152
|
+
state: selector("state").transform(single).optional().describe("State id, list of ids, or \"all\". Defaults to the state the design shows."),
|
|
153
|
+
mode: selector("mode").transform(single).optional().describe("Mode id, list of ids, \"shown\" or \"all\". Defaults to the primary mode.")
|
|
154
|
+
}),
|
|
155
|
+
problems: fields(checks),
|
|
156
|
+
accessibility: fields(checks),
|
|
157
|
+
console: fields({
|
|
158
|
+
tab,
|
|
159
|
+
clear: flag("clear", "Empty the console after reading, including entries beyond limit."),
|
|
160
|
+
limit: limit("Most entries to return, newest kept.", 50, 1e3)
|
|
161
|
+
}),
|
|
162
|
+
tokens: fields({
|
|
163
|
+
tab,
|
|
164
|
+
category: text("category", "Token category.").optional(),
|
|
165
|
+
source: text("source", "Token source, such as semantic or theme.").optional(),
|
|
166
|
+
search: z.string("search must be a string").optional().describe("Token name search."),
|
|
167
|
+
limit: limit("Most tokens to return.", 100, 1e3)
|
|
168
|
+
}),
|
|
169
|
+
components: fields({ tab }),
|
|
170
|
+
props: fields({
|
|
171
|
+
tab,
|
|
172
|
+
component: text("component", "Component id or name."),
|
|
173
|
+
all: flag("all", "Include passed-through attributes.")
|
|
174
|
+
}),
|
|
175
|
+
exec: z.union([executeKind({
|
|
176
|
+
tab: execute.tab,
|
|
177
|
+
label: execute.label,
|
|
178
|
+
code: execute.code
|
|
179
|
+
}), executeKind({
|
|
180
|
+
tab: execute.tab,
|
|
181
|
+
label: execute.label,
|
|
182
|
+
editId: execute.editId,
|
|
183
|
+
edits: execute.edits
|
|
184
|
+
})])
|
|
185
|
+
};
|
|
186
|
+
/** Reading the skill or a file it references, which the CLI does on its own, with no app. */
|
|
187
|
+
const skillRequest = fields({ path: text("path", "Skill file to read.").default("SKILL.md") });
|
|
188
|
+
/** What the app answers over HTTP: the method's result, or the error with its details when it knew the cause. */
|
|
189
|
+
const rpcReply = z.looseObject({
|
|
190
|
+
result: z.unknown().optional(),
|
|
191
|
+
error: z.string().optional(),
|
|
192
|
+
details: z.unknown().optional()
|
|
193
|
+
});
|
|
194
|
+
const strings = z.array(z.string());
|
|
195
|
+
const ref = z.looseObject({
|
|
196
|
+
id: z.string(),
|
|
197
|
+
name: z.string()
|
|
198
|
+
});
|
|
199
|
+
const number = z.number();
|
|
200
|
+
/** One open project. */
|
|
201
|
+
const tabSummary = z.looseObject({
|
|
202
|
+
id: z.string(),
|
|
203
|
+
name: z.string(),
|
|
204
|
+
root: z.string(),
|
|
205
|
+
active: z.boolean(),
|
|
206
|
+
status: z.enum([
|
|
207
|
+
"loading",
|
|
208
|
+
"ready",
|
|
209
|
+
"error"
|
|
210
|
+
]),
|
|
211
|
+
error: z.string().optional(),
|
|
212
|
+
designs: z.int().describe("How many designs the project has.")
|
|
213
|
+
});
|
|
214
|
+
/** One design on the canvas, and where its board is. Its size is missing until its board has been drawn. */
|
|
215
|
+
const designSummary = z.looseObject({
|
|
216
|
+
id: z.string(),
|
|
217
|
+
name: z.string(),
|
|
218
|
+
kind: z.string(),
|
|
219
|
+
file: z.string(),
|
|
220
|
+
x: number,
|
|
221
|
+
y: number,
|
|
222
|
+
width: number.optional(),
|
|
223
|
+
height: number.optional(),
|
|
224
|
+
states: z.array(ref),
|
|
225
|
+
showing: z.looseObject({
|
|
226
|
+
state: z.string(),
|
|
227
|
+
mode: z.string(),
|
|
228
|
+
beside: strings
|
|
229
|
+
}),
|
|
230
|
+
nodes: z.int()
|
|
231
|
+
});
|
|
232
|
+
const overlap = z.looseObject({
|
|
233
|
+
designs: strings,
|
|
234
|
+
names: strings,
|
|
235
|
+
width: number.optional().describe("How far they share, title band included."),
|
|
236
|
+
height: number.optional(),
|
|
237
|
+
titleOnly: z.boolean().optional().describe("Only the lower board's title covers the other; the boards are clear."),
|
|
238
|
+
estimated: z.boolean().optional().describe("One isn't drawn yet, so its size is estimated from its frames. Absent: both measured.")
|
|
239
|
+
});
|
|
240
|
+
const stateReply = z.looseObject({
|
|
241
|
+
tab: tabSummary,
|
|
242
|
+
revision: z.string().optional().describe("Opaque; the same only while nothing in the tab has changed. Missing from a copy older than PAR-481."),
|
|
243
|
+
selection: z.looseObject({
|
|
244
|
+
design: z.string().optional(),
|
|
245
|
+
node: z.string().optional(),
|
|
246
|
+
mode: z.string().optional()
|
|
247
|
+
}),
|
|
248
|
+
designs: z.array(designSummary),
|
|
249
|
+
overlaps: z.array(overlap).describe("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."),
|
|
250
|
+
modes: z.array(z.looseObject({
|
|
251
|
+
id: z.string(),
|
|
252
|
+
name: z.string(),
|
|
253
|
+
width: number,
|
|
254
|
+
primary: z.boolean().describe("The project's first mode: every design's main frame unless it picks another. Only one is.")
|
|
255
|
+
})).describe("The project's modes, primary first."),
|
|
256
|
+
components: strings,
|
|
257
|
+
tokens: z.looseObject({
|
|
258
|
+
count: z.int(),
|
|
259
|
+
semantic: strings,
|
|
260
|
+
themeCategories: strings
|
|
261
|
+
}),
|
|
262
|
+
viewport: plainObject.optional(),
|
|
263
|
+
detection: plainObject.optional()
|
|
264
|
+
});
|
|
265
|
+
const historySide = z.looseObject({
|
|
266
|
+
count: z.int(),
|
|
267
|
+
labels: strings.optional().describe("With summary: newest first, up to limit.")
|
|
268
|
+
});
|
|
269
|
+
const statusReply = z.looseObject({
|
|
270
|
+
tab: ref,
|
|
271
|
+
revision: z.string().describe("Opaque; the same only while nothing in the tab has changed. Compare it with state's revision."),
|
|
272
|
+
dirty: z.boolean().describe("Something applied (and undoable) isn't on disk yet."),
|
|
273
|
+
autosave: z.boolean().describe("Changes save themselves; otherwise they wait for the person's Save."),
|
|
274
|
+
unsaved: z.array(z.looseObject({
|
|
275
|
+
path: z.string(),
|
|
276
|
+
kind: z.enum(["document", "stylesheet"]),
|
|
277
|
+
designs: z.looseObject({
|
|
278
|
+
added: strings,
|
|
279
|
+
changed: strings,
|
|
280
|
+
removed: strings
|
|
281
|
+
}).optional(),
|
|
282
|
+
modes: z.boolean().optional(),
|
|
283
|
+
tokens: strings.optional(),
|
|
284
|
+
more: z.int().optional()
|
|
285
|
+
})),
|
|
286
|
+
tokenEdits: z.int().describe("Token values waiting for Save to write them to the stylesheet."),
|
|
287
|
+
save: z.looseObject({
|
|
288
|
+
status: z.enum([
|
|
289
|
+
"saved",
|
|
290
|
+
"unsaved",
|
|
291
|
+
"saving",
|
|
292
|
+
"failed"
|
|
293
|
+
]),
|
|
294
|
+
error: z.string().optional()
|
|
295
|
+
}),
|
|
296
|
+
errors: z.array(z.looseObject({
|
|
297
|
+
path: z.string(),
|
|
298
|
+
error: z.string()
|
|
299
|
+
})),
|
|
300
|
+
undo: historySide,
|
|
301
|
+
redo: historySide
|
|
302
|
+
});
|
|
303
|
+
const modeOption = z.looseObject({
|
|
304
|
+
id: z.string(),
|
|
305
|
+
name: z.string(),
|
|
306
|
+
width: number.optional(),
|
|
307
|
+
height: number.optional(),
|
|
308
|
+
device: z.string().optional(),
|
|
309
|
+
className: z.string().optional(),
|
|
310
|
+
attributes: z.record(z.string(), z.string()).optional()
|
|
311
|
+
});
|
|
312
|
+
const modesReply = z.looseObject({
|
|
313
|
+
tab: ref,
|
|
314
|
+
variables: z.array(z.looseObject({
|
|
315
|
+
id: z.string(),
|
|
316
|
+
name: z.string(),
|
|
317
|
+
kind: z.enum([
|
|
318
|
+
"viewport",
|
|
319
|
+
"theme",
|
|
320
|
+
"locale",
|
|
321
|
+
"custom"
|
|
322
|
+
]),
|
|
323
|
+
options: z.array(modeOption)
|
|
324
|
+
})),
|
|
325
|
+
viewports: z.array(z.looseObject({
|
|
326
|
+
id: z.string(),
|
|
327
|
+
name: z.string(),
|
|
328
|
+
width: number,
|
|
329
|
+
height: number,
|
|
330
|
+
device: z.string().optional()
|
|
331
|
+
})).describe("Viewport ids, which Responsive and a node's responsive patches take. Not mode ids."),
|
|
332
|
+
modes: z.array(z.looseObject({
|
|
333
|
+
id: z.string(),
|
|
334
|
+
name: z.string(),
|
|
335
|
+
primary: z.boolean(),
|
|
336
|
+
viewport: z.string(),
|
|
337
|
+
width: number,
|
|
338
|
+
height: number,
|
|
339
|
+
values: z.record(z.string(), z.string()),
|
|
340
|
+
classNames: strings,
|
|
341
|
+
attributes: z.record(z.string(), z.string())
|
|
342
|
+
})).describe("Mode ids, which renders (screenshot, problems, accessibility) and a design's modes take."),
|
|
343
|
+
designs: z.array(z.looseObject({
|
|
344
|
+
id: z.string(),
|
|
345
|
+
name: z.string(),
|
|
346
|
+
state: z.string(),
|
|
347
|
+
states: strings,
|
|
348
|
+
main: z.string(),
|
|
349
|
+
beside: strings,
|
|
350
|
+
frames: z.array(z.looseObject({
|
|
351
|
+
mode: z.string(),
|
|
352
|
+
width: number,
|
|
353
|
+
height: number.optional().describe("Fixed frame height, which clips; absent when it grows."),
|
|
354
|
+
window: z.looseObject({
|
|
355
|
+
width: number,
|
|
356
|
+
height: number
|
|
357
|
+
})
|
|
358
|
+
})).optional()
|
|
359
|
+
}))
|
|
360
|
+
});
|
|
361
|
+
const warningGroup = z.looseObject({
|
|
362
|
+
category: z.string().describe("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."),
|
|
363
|
+
severity: z.enum([
|
|
364
|
+
"error",
|
|
365
|
+
"warning",
|
|
366
|
+
"suggestion"
|
|
367
|
+
]).describe("error: a render failed, so a check or screenshot is missing. suggestion: worth doing, not wrong."),
|
|
368
|
+
message: z.string().describe("What's wrong and what to do about it."),
|
|
369
|
+
count: z.int().min(1).describe("How many places it was found."),
|
|
370
|
+
at: strings.describe("Every place: a node's design/node path, or a frame. Empty for a whole board.")
|
|
371
|
+
});
|
|
372
|
+
const discarded = z.looseObject({
|
|
373
|
+
design: z.string(),
|
|
374
|
+
replaced: z.string().describe("The node Replace swapped out."),
|
|
375
|
+
replacement: z.string().describe("The node that took its place."),
|
|
376
|
+
nodes: z.array(z.looseObject({
|
|
377
|
+
id: z.string().describe("The old node's id, or instance/inner path."),
|
|
378
|
+
name: z.string().optional(),
|
|
379
|
+
viewports: strings.optional().describe("Viewport ids it had responsive patches for."),
|
|
380
|
+
states: strings.optional().describe("State ids it had patches in."),
|
|
381
|
+
variants: strings.optional().describe("Variant options it had patches in, as param=option.")
|
|
382
|
+
}))
|
|
383
|
+
});
|
|
384
|
+
/** A snippet's or a screenshot's result: a failure's message, with its details beside it when the app knew the cause. */
|
|
385
|
+
const execReply = z.looseObject({
|
|
386
|
+
ok: z.boolean(),
|
|
387
|
+
applied: z.boolean().describe("Changes landed on the canvas as one undoable step."),
|
|
388
|
+
output: strings.describe("Lines from Print."),
|
|
389
|
+
warnings: strings.describe("One line per warning group, places first."),
|
|
390
|
+
warningGroups: z.array(warningGroup).optional().describe("The warnings as data, in the same order: each finding once, with how many places and which. Errors come first, then warnings, then suggestions. Missing from a copy older than PAR-460."),
|
|
391
|
+
error: z.string().optional(),
|
|
392
|
+
details: z.unknown().optional(),
|
|
393
|
+
editId: z.string().optional(),
|
|
394
|
+
created: z.record(z.string(), z.string()).describe("Names of designs and nodes created, mapped to their ids."),
|
|
395
|
+
discarded: z.array(discarded).optional().describe("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."),
|
|
396
|
+
screenshots: z.array(z.looseObject({
|
|
397
|
+
path: z.string().describe("Where the PNG was saved."),
|
|
398
|
+
png: z.string(),
|
|
399
|
+
design: z.string(),
|
|
400
|
+
state: z.string(),
|
|
401
|
+
mode: z.string()
|
|
402
|
+
}))
|
|
403
|
+
});
|
|
404
|
+
/** A box in a frame, in CSS pixels from its top left corner. */
|
|
405
|
+
const frameBox = z.looseObject({
|
|
406
|
+
x: number,
|
|
407
|
+
y: number,
|
|
408
|
+
width: number,
|
|
409
|
+
height: number
|
|
410
|
+
});
|
|
411
|
+
/**
|
|
412
|
+
* One problem in one frame. Layout problems (`frame-overflow`, `clipped`) name the node, what it overflows (the node
|
|
413
|
+
* that clips it, or the frame, which has none), both boxes, and how far past each edge it reaches.
|
|
414
|
+
*/
|
|
415
|
+
const problem = z.looseObject({
|
|
416
|
+
state: z.string(),
|
|
417
|
+
mode: z.string(),
|
|
418
|
+
kind: z.enum([
|
|
419
|
+
"render-error",
|
|
420
|
+
"unknown-component",
|
|
421
|
+
"content-wider",
|
|
422
|
+
"frame-overflow",
|
|
423
|
+
"clipped"
|
|
424
|
+
]).optional(),
|
|
425
|
+
problem: z.string(),
|
|
426
|
+
node: z.string().optional(),
|
|
427
|
+
name: z.string().optional(),
|
|
428
|
+
box: frameBox.optional(),
|
|
429
|
+
container: z.looseObject({
|
|
430
|
+
node: z.string().optional(),
|
|
431
|
+
name: z.string().optional(),
|
|
432
|
+
box: frameBox
|
|
433
|
+
}).optional(),
|
|
434
|
+
overflow: z.looseObject({
|
|
435
|
+
left: number.optional(),
|
|
436
|
+
right: number.optional(),
|
|
437
|
+
top: number.optional(),
|
|
438
|
+
bottom: number.optional()
|
|
439
|
+
}).optional()
|
|
440
|
+
});
|
|
441
|
+
const problemsReply = z.looseObject({
|
|
442
|
+
tab: ref,
|
|
443
|
+
design: ref,
|
|
444
|
+
problems: z.array(problem),
|
|
445
|
+
frames: z.int(),
|
|
446
|
+
warnings: strings
|
|
447
|
+
});
|
|
448
|
+
const accessibilityReply = z.looseObject({
|
|
449
|
+
tab: ref,
|
|
450
|
+
design: ref,
|
|
451
|
+
findings: z.array(z.looseObject({
|
|
452
|
+
state: z.string(),
|
|
453
|
+
mode: z.string(),
|
|
454
|
+
rule: z.string(),
|
|
455
|
+
impact: z.enum([
|
|
456
|
+
"critical",
|
|
457
|
+
"serious",
|
|
458
|
+
"moderate",
|
|
459
|
+
"minor"
|
|
460
|
+
]),
|
|
461
|
+
help: z.string(),
|
|
462
|
+
helpUrl: z.string(),
|
|
463
|
+
node: z.string().optional(),
|
|
464
|
+
name: z.string().optional(),
|
|
465
|
+
detail: z.string(),
|
|
466
|
+
/** For a contrast failure: the token to color the layer's text with, or `"none"` when none of the project's pass. */
|
|
467
|
+
contrast: z.looseObject({ fix: z.union([z.literal("none"), z.looseObject({
|
|
468
|
+
token: z.string(),
|
|
469
|
+
ratio: number
|
|
470
|
+
})]).optional() }).optional()
|
|
471
|
+
})),
|
|
472
|
+
frames: z.int(),
|
|
473
|
+
warnings: strings
|
|
474
|
+
});
|
|
475
|
+
const consoleReply = z.looseObject({
|
|
476
|
+
tab: ref,
|
|
477
|
+
/** Every entry the console holds, oldest first. */
|
|
478
|
+
entries: z.array(z.looseObject({
|
|
479
|
+
level: z.enum([
|
|
480
|
+
"info",
|
|
481
|
+
"warning",
|
|
482
|
+
"error",
|
|
483
|
+
"debug"
|
|
484
|
+
]),
|
|
485
|
+
text: z.string(),
|
|
486
|
+
source: z.string(),
|
|
487
|
+
line: z.int(),
|
|
488
|
+
where: z.enum(["canvas", "offscreen"]),
|
|
489
|
+
count: z.int(),
|
|
490
|
+
first: number,
|
|
491
|
+
last: number
|
|
492
|
+
}))
|
|
493
|
+
});
|
|
494
|
+
const tokenInfo = z.looseObject({
|
|
495
|
+
name: z.string(),
|
|
496
|
+
category: z.string(),
|
|
497
|
+
source: z.string(),
|
|
498
|
+
values: z.record(z.string(), z.string()).describe("Value per theme.")
|
|
499
|
+
});
|
|
500
|
+
const componentsReply = z.looseObject({
|
|
501
|
+
components: strings,
|
|
502
|
+
iconLibraries: strings.optional(),
|
|
503
|
+
/** The app's capitalised exports the type checker left out, with why. */
|
|
504
|
+
leftOut: z.array(z.looseObject({
|
|
505
|
+
name: z.string(),
|
|
506
|
+
path: z.string(),
|
|
507
|
+
reason: z.string(),
|
|
508
|
+
undecided: z.boolean().optional()
|
|
509
|
+
})).optional(),
|
|
510
|
+
/** How many components have drawn on the canvas without errors so far. */
|
|
511
|
+
drawn: z.int().optional(),
|
|
512
|
+
/** Components failing to draw on the canvas now: why in words, and the kind of problem. */
|
|
513
|
+
failing: z.array(z.looseObject({
|
|
514
|
+
id: z.string(),
|
|
515
|
+
problem: z.enum([
|
|
516
|
+
"provider",
|
|
517
|
+
"server",
|
|
518
|
+
"async",
|
|
519
|
+
"error"
|
|
520
|
+
]),
|
|
521
|
+
reason: z.string()
|
|
522
|
+
})).optional(),
|
|
523
|
+
/** The person's corrections in the main project file, saved or not: exports listed by hand. */
|
|
524
|
+
corrections: z.looseObject({ include: z.array(z.looseObject({
|
|
525
|
+
path: z.string(),
|
|
526
|
+
export: z.string()
|
|
527
|
+
})).optional() }).optional()
|
|
528
|
+
});
|
|
529
|
+
/** A component's composition, as `@parity-app/editor`'s `componentComposition` reads it (PAR-454). */
|
|
530
|
+
const composition = z.looseObject({
|
|
531
|
+
role: z.enum([
|
|
532
|
+
"provider",
|
|
533
|
+
"wrapper",
|
|
534
|
+
"component"
|
|
535
|
+
]).describe("provider draws nothing and gives parts inside it context; wrapper draws and holds other parts."),
|
|
536
|
+
parents: z.discriminatedUnion("status", [
|
|
537
|
+
z.looseObject({
|
|
538
|
+
status: z.literal("listed"),
|
|
539
|
+
chain: z.array(z.looseObject({
|
|
540
|
+
component: z.string(),
|
|
541
|
+
optional: z.boolean().optional()
|
|
542
|
+
})).describe("Outermost first."),
|
|
543
|
+
from: z.string().optional().describe("What said so, such as the library's examples or the error it throws.")
|
|
544
|
+
}),
|
|
545
|
+
z.looseObject({ status: z.literal("none") }),
|
|
546
|
+
z.looseObject({
|
|
547
|
+
status: z.literal("unknown"),
|
|
548
|
+
reason: z.string()
|
|
549
|
+
})
|
|
550
|
+
]).describe("listed: insert it inside chain; none: it needs nothing; unknown: nothing says, see reason."),
|
|
551
|
+
holds: strings.describe("Parts that go directly inside it."),
|
|
552
|
+
slots: z.array(z.looseObject({
|
|
553
|
+
name: z.string(),
|
|
554
|
+
key: z.string(),
|
|
555
|
+
kind: z.enum([
|
|
556
|
+
"children",
|
|
557
|
+
"element",
|
|
558
|
+
"render"
|
|
559
|
+
])
|
|
560
|
+
})),
|
|
561
|
+
example: z.looseObject({
|
|
562
|
+
node: z.unknown().describe("A node Insert takes as it is: the component inside its parents."),
|
|
563
|
+
status: z.enum([
|
|
564
|
+
"drawn",
|
|
565
|
+
"failing",
|
|
566
|
+
"untried"
|
|
567
|
+
]).describe("Whether the component has drawn on the canvas."),
|
|
568
|
+
reason: z.string().optional()
|
|
569
|
+
})
|
|
570
|
+
});
|
|
571
|
+
const propsReply = z.looseObject({
|
|
572
|
+
component: z.string(),
|
|
573
|
+
path: z.string().optional(),
|
|
574
|
+
/** What the project's code imports it from: a package, or the file through the app's alias. */
|
|
575
|
+
import: z.string().optional(),
|
|
576
|
+
known: z.boolean(),
|
|
577
|
+
props: z.array(z.looseObject({
|
|
578
|
+
name: z.string(),
|
|
579
|
+
kind: z.enum(propKinds),
|
|
580
|
+
group: z.enum(propGroups),
|
|
581
|
+
options: z.array(z.union([
|
|
582
|
+
z.string(),
|
|
583
|
+
number,
|
|
584
|
+
z.boolean()
|
|
585
|
+
])).optional(),
|
|
586
|
+
default: z.union([
|
|
587
|
+
z.string(),
|
|
588
|
+
number,
|
|
589
|
+
z.boolean(),
|
|
590
|
+
z.null()
|
|
591
|
+
]).optional(),
|
|
592
|
+
required: z.boolean().optional(),
|
|
593
|
+
type: z.string().optional(),
|
|
594
|
+
description: z.string().optional()
|
|
595
|
+
})),
|
|
596
|
+
/** What it needs around it and holds inside it. Missing from a copy older than PAR-454. */
|
|
597
|
+
composition: composition.optional()
|
|
598
|
+
});
|
|
599
|
+
/**
|
|
600
|
+
* Each method's answer by RPC method, which the CLI checks before reading it. `copy` is what a copy says about itself,
|
|
601
|
+
* which calls are routed by (PAR-514): its tabs, and when its window last had focus, missing from a copy too old to
|
|
602
|
+
* say. A screenshot is a snippet Parity writes, so it answers as `exec` does.
|
|
603
|
+
*/
|
|
604
|
+
const replies = {
|
|
605
|
+
copy: z.looseObject({
|
|
606
|
+
tabs: z.array(tabSummary),
|
|
607
|
+
focusedAt: number.optional()
|
|
608
|
+
}),
|
|
609
|
+
tabs: z.array(tabSummary),
|
|
610
|
+
state: stateReply,
|
|
611
|
+
status: statusReply,
|
|
612
|
+
modes: modesReply,
|
|
613
|
+
open: tabSummary,
|
|
614
|
+
exec: execReply,
|
|
615
|
+
screenshot: execReply,
|
|
616
|
+
problems: problemsReply,
|
|
617
|
+
accessibility: accessibilityReply,
|
|
618
|
+
console: consoleReply,
|
|
619
|
+
tokens: z.array(tokenInfo),
|
|
620
|
+
components: componentsReply,
|
|
621
|
+
props: propsReply
|
|
622
|
+
};
|
|
7
623
|
// ../agent/src/routing.ts
|
|
8
624
|
/**
|
|
9
625
|
* Which running copy of Parity an agent's call goes to (PAR-514). Several copies can run at once, each with its own
|
|
@@ -267,12 +883,12 @@ function setCaller(name, session = false) {
|
|
|
267
883
|
}
|
|
268
884
|
/**
|
|
269
885
|
* Calls the running Parity app: the copy that has the call's project open, with the exact tab it chose, by the rules
|
|
270
|
-
* in `@parity-app/agent/routing
|
|
271
|
-
* project, or the call fails: the app's own code when it gives one.
|
|
886
|
+
* in `@parity-app/agent/routing`, and returns its answer once `reply` has checked it. Fails with a clear message, and a
|
|
887
|
+
* code, when no copy is running, none has the project, or the call fails: the app's own code when it gives one.
|
|
272
888
|
*/
|
|
273
|
-
async function call(method, params
|
|
889
|
+
async function call(method, params, routing, reply) {
|
|
274
890
|
const copies = await runningCopies();
|
|
275
|
-
if (routing === "copies") return openTabs(copies);
|
|
891
|
+
if (routing === "copies") return answer(reply, openTabs(copies), "tabs");
|
|
276
892
|
const target = route(copies, routing === "open" ? {
|
|
277
893
|
kind: "open",
|
|
278
894
|
root: String(params.root)
|
|
@@ -293,7 +909,7 @@ async function call(method, params = {}, routing = "tab") {
|
|
|
293
909
|
return send(target.copy.entry, method, target.tab ? {
|
|
294
910
|
...params,
|
|
295
911
|
tab: target.tab.id
|
|
296
|
-
} : params);
|
|
912
|
+
} : params, reply);
|
|
297
913
|
}
|
|
298
914
|
/** A folder's real path, through any link, or the folder as named when it can't be read. */
|
|
299
915
|
function real(path) {
|
|
@@ -335,17 +951,17 @@ async function runningCopies() {
|
|
|
335
951
|
/** What a copy has open and when it was last used; just its tabs from an app too old to say more. */
|
|
336
952
|
async function reportOf(entry) {
|
|
337
953
|
try {
|
|
338
|
-
return await send(entry, "copy", {}, 5e3);
|
|
954
|
+
return await send(entry, "copy", {}, replies.copy, 5e3);
|
|
339
955
|
} catch (error) {
|
|
340
956
|
if (!(error instanceof CodedError) || error.details.code !== "app_error") return void 0;
|
|
341
957
|
try {
|
|
342
|
-
return { tabs: await send(entry, "tabs", {}, 5e3) };
|
|
958
|
+
return { tabs: await send(entry, "tabs", {}, replies.tabs, 5e3) };
|
|
343
959
|
} catch {
|
|
344
960
|
return;
|
|
345
961
|
}
|
|
346
962
|
}
|
|
347
963
|
}
|
|
348
|
-
async function send(entry, method, params, timeout) {
|
|
964
|
+
async function send(entry, method, params, reply, timeout) {
|
|
349
965
|
let response;
|
|
350
966
|
try {
|
|
351
967
|
response = await fetch(`${entry.url}/rpc`, {
|
|
@@ -368,9 +984,18 @@ async function send(entry, method, params, timeout) {
|
|
|
368
984
|
retryable: true
|
|
369
985
|
});
|
|
370
986
|
}
|
|
371
|
-
const body = await response.
|
|
987
|
+
const body = answer(jsonText(rpcReply), await response.text(), method);
|
|
372
988
|
if (body.error !== void 0) throw new CodedError(body.error, readErrorDetails(body.details) ?? { code: "app_error" });
|
|
373
|
-
return body.result;
|
|
989
|
+
return answer(reply, body.result, method);
|
|
990
|
+
}
|
|
991
|
+
/** The app's answer to `method` once `schema` has checked it, or `app_error` saying what this CLI couldn't read. */
|
|
992
|
+
function answer(schema, value, method) {
|
|
993
|
+
const checked = checkData(schema, value);
|
|
994
|
+
if (checked.success) return checked.data;
|
|
995
|
+
throw new CodedError(unreadable(method, checked.problem), { code: "app_error" });
|
|
996
|
+
}
|
|
997
|
+
function unreadable(method, { pointer, message }) {
|
|
998
|
+
return `This CLI can't read Parity's answer to ${method} (${pointer || "/"}: ${message}). Update Parity and @parity-app/cli, then try again.`;
|
|
374
999
|
}
|
|
375
1000
|
/** A readable account of an exec call, for terminals and for agents. */
|
|
376
1001
|
function describeExec(result) {
|
|
@@ -710,4 +1335,4 @@ function scriptPath(url) {
|
|
|
710
1335
|
return url || "an unknown script";
|
|
711
1336
|
}
|
|
712
1337
|
}
|
|
713
|
-
export { describeExec as a, describeProps as c, registry as d, setCaller as f, describeConsole as i, describeStatus as l, describeAccessibility as n, describeModes as o, mcpCallerName as p, describeComponents as r, describeProblems as s, call as t, describeTokens as u };
|
|
1338
|
+
export { requests as _, describeExec as a, propKinds as b, describeProps as c, registry as d, setCaller as f, requestJsonSchema as g, replies as h, describeConsole as i, describeStatus as l, readRequest as m, describeAccessibility as n, describeModes as o, mcpCallerName as p, describeComponents as r, describeProblems as s, call as t, describeTokens as u, skillRequest as v, propGroups as y };
|