@parity-app/cli 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -3
- package/dist/bin.mjs +1133 -273
- package/dist/client-Cf66-n3R.mjs +724 -0
- package/dist/index.d.mts +211 -17
- package/dist/index.mjs +1 -1
- package/dist/libraries/aria.lib.par +5 -5
- package/dist/libraries/baseui.lib.par +25 -25
- package/dist/libraries/chakra.lib.par +61 -22
- package/dist/libraries/fluent.lib.par +27 -27
- package/dist/libraries/luma.lib.par +20 -20
- package/dist/libraries/lyra.lib.par +20 -20
- package/dist/libraries/maia.lib.par +20 -20
- package/dist/libraries/mantine.lib.par +14 -14
- package/dist/libraries/mira.lib.par +20 -20
- package/dist/libraries/mui.lib.par +5 -5
- package/dist/libraries/nova.lib.par +20 -20
- package/dist/libraries/radix.lib.par +11 -11
- package/dist/libraries/recharts.lib.par +5445 -0
- package/dist/libraries/rhea.lib.par +20 -20
- package/dist/libraries/sera.lib.par +20 -20
- package/dist/libraries/vega.lib.par +20 -20
- package/dist/{src-C8DCd2a1.mjs → src-BOUQtcWB.mjs} +1 -1
- package/dist/{src-WcAfq6g1.mjs → src-BRgxMRtE.mjs} +3 -2
- package/dist/{src-CSeISXoZ.mjs → src-BZ1p_ip_.mjs} +135 -38
- package/dist/{src-uPmBVSsB.mjs → src-CAm6EBIK.mjs} +1 -1
- package/dist/{src-rlZNC2MU.mjs → src-CcWc-KsX.mjs} +593 -212
- package/package.json +8 -6
- package/skills/SKILL.md +61 -14
- package/skills/document.md +6 -1
- package/skills/execute.md +96 -8
- package/skills/guide/states-and-modes.md +5 -3
- package/LICENSE +0 -21
- package/dist/client-DsTLQ2lT.mjs +0 -286
package/dist/bin.mjs
CHANGED
|
@@ -1,9 +1,283 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
2
|
+
import { _ as errorDetails, a as describeExec, c as describeProps, f as setCaller, g as errorCodes, h as closest, i as describeConsole, l as describeStatus, m as CodedError, n as describeAccessibility, o as describeModes, p as mcpCallerName, r as describeComponents, s as describeProblems, t as call, u as describeTokens, v as readErrorDetails } from "./client-Cf66-n3R.mjs";
|
|
3
3
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, normalize, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
6
|
import { createInterface } from "node:readline";
|
|
7
|
+
// src/errors.ts
|
|
8
|
+
/** A request field that isn't valid, rejected before anything reaches the app. */
|
|
9
|
+
function invalidInput(field, message) {
|
|
10
|
+
return new CodedError(message, {
|
|
11
|
+
code: "invalid_input",
|
|
12
|
+
...field ? { field } : {}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A thrown value as an operation error. Invalid input always points at the request; a field the app names becomes a
|
|
17
|
+
* path only when the operation has that input: an unknown design inside a snippet has no `design` input to point at.
|
|
18
|
+
*/
|
|
19
|
+
function operationError(error, fields) {
|
|
20
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
21
|
+
const details = errorDetails(error) ?? {
|
|
22
|
+
code: "app_error",
|
|
23
|
+
retryable: false
|
|
24
|
+
};
|
|
25
|
+
const field = details.field;
|
|
26
|
+
const known = field !== void 0 && (details.code === "invalid_input" || !fields || fields.includes(field.split("/")[0]));
|
|
27
|
+
return {
|
|
28
|
+
code: details.code,
|
|
29
|
+
message,
|
|
30
|
+
retryable: details.retryable,
|
|
31
|
+
...known ? { path: `/${field}` } : {},
|
|
32
|
+
...details.alternatives?.length ? { alternatives: details.alternatives } : {}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Renames the top-level field a path points at, for an adapter whose names differ from the contract's. */
|
|
36
|
+
function renamePath(error, names) {
|
|
37
|
+
if (!error.path) return error;
|
|
38
|
+
const [, first = "", ...rest] = error.path.split("/");
|
|
39
|
+
if (!Object.hasOwn(names, first)) return error;
|
|
40
|
+
return {
|
|
41
|
+
...error,
|
|
42
|
+
path: [
|
|
43
|
+
"",
|
|
44
|
+
names[first],
|
|
45
|
+
...rest
|
|
46
|
+
].join("/")
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** The error's schema. What each field means is in the CLI's README and the skill, to keep every tool's schema small. */
|
|
50
|
+
const operationErrorSchema = {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: {
|
|
53
|
+
code: {
|
|
54
|
+
type: "string",
|
|
55
|
+
enum: [...errorCodes]
|
|
56
|
+
},
|
|
57
|
+
message: { type: "string" },
|
|
58
|
+
retryable: { type: "boolean" },
|
|
59
|
+
path: { type: "string" },
|
|
60
|
+
alternatives: {
|
|
61
|
+
type: "array",
|
|
62
|
+
items: { type: "string" }
|
|
63
|
+
},
|
|
64
|
+
editId: { type: "string" }
|
|
65
|
+
},
|
|
66
|
+
required: [
|
|
67
|
+
"code",
|
|
68
|
+
"message",
|
|
69
|
+
"retryable"
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
// src/execute.ts
|
|
73
|
+
/** The same request fields in both adapters' paths, before MCP renames label and code. */
|
|
74
|
+
const executeFields = [
|
|
75
|
+
"tab",
|
|
76
|
+
"label",
|
|
77
|
+
"code",
|
|
78
|
+
"editId",
|
|
79
|
+
"edits"
|
|
80
|
+
];
|
|
81
|
+
/** A reply as the canonical result: the failure's message, code and retry id together in one error. */
|
|
82
|
+
function execResponse(reply, fields = executeFields) {
|
|
83
|
+
const { error, details, editId, ...rest } = reply;
|
|
84
|
+
if (reply.ok || error === void 0) return rest;
|
|
85
|
+
const coded = readErrorDetails(details);
|
|
86
|
+
const failure = operationError(coded ? new CodedError(error, coded) : new CodedError(error, { code: "execution_failed" }), fields);
|
|
87
|
+
return {
|
|
88
|
+
...rest,
|
|
89
|
+
error: {
|
|
90
|
+
...failure,
|
|
91
|
+
...editId ? { editId } : {}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** The result as structured data: everything but image data, which MCP sends as image blocks. */
|
|
96
|
+
function structuredExec(result) {
|
|
97
|
+
return {
|
|
98
|
+
...result,
|
|
99
|
+
screenshots: result.screenshots.map(({ png: _png, ...shot }) => shot)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** Execute's and screenshot's structured result. */
|
|
103
|
+
const execOutputSchema = {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {
|
|
106
|
+
ok: { type: "boolean" },
|
|
107
|
+
applied: {
|
|
108
|
+
type: "boolean",
|
|
109
|
+
description: "Changes landed on the canvas as one undoable step."
|
|
110
|
+
},
|
|
111
|
+
output: {
|
|
112
|
+
type: "array",
|
|
113
|
+
items: { type: "string" },
|
|
114
|
+
description: "Lines from Print."
|
|
115
|
+
},
|
|
116
|
+
warnings: {
|
|
117
|
+
type: "array",
|
|
118
|
+
items: { type: "string" }
|
|
119
|
+
},
|
|
120
|
+
created: {
|
|
121
|
+
type: "object",
|
|
122
|
+
additionalProperties: { type: "string" },
|
|
123
|
+
description: "Names of designs and nodes created, mapped to their ids."
|
|
124
|
+
},
|
|
125
|
+
screenshots: {
|
|
126
|
+
type: "array",
|
|
127
|
+
items: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
path: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: "Where the PNG was saved."
|
|
133
|
+
},
|
|
134
|
+
design: { type: "string" },
|
|
135
|
+
state: { type: "string" },
|
|
136
|
+
mode: { type: "string" }
|
|
137
|
+
},
|
|
138
|
+
required: [
|
|
139
|
+
"path",
|
|
140
|
+
"design",
|
|
141
|
+
"state",
|
|
142
|
+
"mode"
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
error: operationErrorSchema
|
|
147
|
+
},
|
|
148
|
+
required: [
|
|
149
|
+
"ok",
|
|
150
|
+
"applied",
|
|
151
|
+
"output",
|
|
152
|
+
"warnings",
|
|
153
|
+
"created",
|
|
154
|
+
"screenshots"
|
|
155
|
+
]
|
|
156
|
+
};
|
|
157
|
+
const defaultLabel = "Edit designs";
|
|
158
|
+
/** What every call's tab field says, the same in the CLI's help, MCP tools and the skill (PAR-514). */
|
|
159
|
+
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\".";
|
|
160
|
+
/** Canonical properties, also used by MCP discovery after renaming label and code. */
|
|
161
|
+
const executeProperties = {
|
|
162
|
+
tab: {
|
|
163
|
+
type: "string",
|
|
164
|
+
minLength: 1,
|
|
165
|
+
description: tabDescription
|
|
166
|
+
},
|
|
167
|
+
label: {
|
|
168
|
+
type: "string",
|
|
169
|
+
minLength: 1,
|
|
170
|
+
default: defaultLabel,
|
|
171
|
+
description: "Shown while it runs. Prefer 3 to 8 words, e.g. \"Add pricing tiers\"."
|
|
172
|
+
},
|
|
173
|
+
code: {
|
|
174
|
+
type: "string",
|
|
175
|
+
minLength: 1,
|
|
176
|
+
description: "The snippet. Omit when retrying with editId."
|
|
177
|
+
},
|
|
178
|
+
editId: {
|
|
179
|
+
type: "string",
|
|
180
|
+
minLength: 1,
|
|
181
|
+
description: "The failed call's editId, to retry with edits."
|
|
182
|
+
},
|
|
183
|
+
edits: {
|
|
184
|
+
type: "array",
|
|
185
|
+
description: "Exact find and replace edits to the failed snippet, applied in order.",
|
|
186
|
+
items: {
|
|
187
|
+
type: "object",
|
|
188
|
+
additionalProperties: false,
|
|
189
|
+
properties: {
|
|
190
|
+
find: {
|
|
191
|
+
type: "string",
|
|
192
|
+
minLength: 1
|
|
193
|
+
},
|
|
194
|
+
replace: { type: "string" },
|
|
195
|
+
all: { type: "boolean" }
|
|
196
|
+
},
|
|
197
|
+
required: ["find", "replace"]
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
function optionalText(value, field) {
|
|
202
|
+
if (value === void 0) return void 0;
|
|
203
|
+
if (typeof value !== "string" || !value.trim()) throw invalidInput(field, `${field} must be a nonblank string`);
|
|
204
|
+
return value;
|
|
205
|
+
}
|
|
206
|
+
function edit(value, index) {
|
|
207
|
+
const at = `edits/${index}`;
|
|
208
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw invalidInput(at, "Each edit must be an object");
|
|
209
|
+
if (!("find" in value) || typeof value.find !== "string" || !value.find.length) throw invalidInput(`${at}/find`, "Each edit needs a nonempty find string");
|
|
210
|
+
if (!("replace" in value) || typeof value.replace !== "string") throw invalidInput(`${at}/replace`, "Each edit needs a replace string");
|
|
211
|
+
if ("all" in value && typeof value.all !== "boolean") throw invalidInput(`${at}/all`, "Edit all must be a boolean");
|
|
212
|
+
const unknown = Object.keys(value).find((key) => ![
|
|
213
|
+
"find",
|
|
214
|
+
"replace",
|
|
215
|
+
"all"
|
|
216
|
+
].includes(key));
|
|
217
|
+
if (unknown) throw invalidInput(`${at}/${unknown}`, "Unknown edit field");
|
|
218
|
+
return {
|
|
219
|
+
find: value.find,
|
|
220
|
+
replace: value.replace,
|
|
221
|
+
..."all" in value && typeof value.all === "boolean" ? { all: value.all } : {}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/** Validate once, before either transport sends a request to the desktop. Never rewrite snippet or patch text. */
|
|
225
|
+
function executeRequest(input) {
|
|
226
|
+
const tab = optionalText(input.tab, "tab");
|
|
227
|
+
const label = optionalText(input.label, "label") ?? defaultLabel;
|
|
228
|
+
const code = optionalText(input.code, "code");
|
|
229
|
+
const editId = optionalText(input.editId, "editId");
|
|
230
|
+
if (!code && !editId) throw invalidInput("code", "Nothing to run: pass a snippet, or an editId with edits");
|
|
231
|
+
if (code !== void 0 && editId !== void 0) throw invalidInput("editId", "Pass a snippet or an editId, not both");
|
|
232
|
+
if (input.edits !== void 0 && !Array.isArray(input.edits)) throw invalidInput("edits", "edits must be an array");
|
|
233
|
+
if (input.edits !== void 0 && !editId) throw invalidInput("edits", "edits requires an editId");
|
|
234
|
+
const edits = Array.isArray(input.edits) ? input.edits.map(edit) : void 0;
|
|
235
|
+
return {
|
|
236
|
+
label,
|
|
237
|
+
...tab === void 0 ? {} : { tab },
|
|
238
|
+
...code === void 0 ? {} : { code },
|
|
239
|
+
...editId === void 0 ? {} : {
|
|
240
|
+
editId,
|
|
241
|
+
edits: edits ?? []
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/** Execution failures have the same meaning for CLI exits and MCP tool errors. */
|
|
246
|
+
function executionFailed(result) {
|
|
247
|
+
return !result.ok;
|
|
248
|
+
}
|
|
249
|
+
/** One handler for both adapters. Caller identity stays in the transport's connection context. */
|
|
250
|
+
const executeOperation = {
|
|
251
|
+
failed: executionFailed,
|
|
252
|
+
fields: executeFields,
|
|
253
|
+
outputSchema: execOutputSchema,
|
|
254
|
+
structured: structuredExec,
|
|
255
|
+
/** Discovery uses the same contract, with just the two historical MCP field aliases. */
|
|
256
|
+
inputSchema(names = {
|
|
257
|
+
label: "label",
|
|
258
|
+
code: "code"
|
|
259
|
+
}) {
|
|
260
|
+
const { label, code, ...rest } = executeProperties;
|
|
261
|
+
return {
|
|
262
|
+
type: "object",
|
|
263
|
+
properties: {
|
|
264
|
+
...rest,
|
|
265
|
+
[names.label]: label,
|
|
266
|
+
[names.code]: code
|
|
267
|
+
},
|
|
268
|
+
oneOf: [{
|
|
269
|
+
required: [names.code],
|
|
270
|
+
not: { anyOf: [{ required: ["editId"] }, { required: ["edits"] }] }
|
|
271
|
+
}, {
|
|
272
|
+
required: ["editId"],
|
|
273
|
+
not: { required: [names.code] }
|
|
274
|
+
}]
|
|
275
|
+
};
|
|
276
|
+
},
|
|
277
|
+
async run(input, send) {
|
|
278
|
+
return execResponse(await send("exec", { ...executeRequest(input) }));
|
|
279
|
+
}
|
|
280
|
+
};
|
|
7
281
|
// src/check.ts
|
|
8
282
|
/**
|
|
9
283
|
* `parity lib check <library>`: inserts every component of a registry library file into the running app, once on its
|
|
@@ -13,8 +287,8 @@ import { createInterface } from "node:readline";
|
|
|
13
287
|
*/
|
|
14
288
|
async function runCheck(library, options) {
|
|
15
289
|
if (!library) throw new Error("parity lib check <library> [--tab t] [--mode m]");
|
|
16
|
-
const { applyLibraryEdits, componentNode, generateId, librarySource, parseLibraryEdits, withLibraryContext } = await import("./src-
|
|
17
|
-
const { registryDir, registryEntries } = await import("./src-
|
|
290
|
+
const { applyLibraryEdits, componentNode, generateId, librarySource, parseLibraryEdits, withLibraryContext } = await import("./src-BRgxMRtE.mjs");
|
|
291
|
+
const { registryDir, registryEntries } = await import("./src-BOUQtcWB.mjs");
|
|
18
292
|
const entry = (await registryEntries()).find((item) => item.manifest.id === library);
|
|
19
293
|
if (!entry) throw new Error(`The registry has no library "${library}"`);
|
|
20
294
|
let manifest = entry.manifest;
|
|
@@ -25,18 +299,18 @@ async function runCheck(library, options) {
|
|
|
25
299
|
const list = manifest.components?.list ?? {};
|
|
26
300
|
const names = Object.keys(list).filter((name) => list[name].visual !== false);
|
|
27
301
|
const id = (name) => `${library}/${name}`;
|
|
28
|
-
const sources = Object.fromEntries(
|
|
302
|
+
const sources = Object.fromEntries(Object.keys(list).map((name) => [id(name), {
|
|
29
303
|
...librarySource(library, name, list[name], library),
|
|
30
304
|
declaration: ""
|
|
31
305
|
}]));
|
|
32
306
|
const mode = options.mode ?? "desktop-light";
|
|
33
307
|
const exec = async (label, code) => {
|
|
34
|
-
const result = await
|
|
308
|
+
const result = await executeOperation.run({
|
|
35
309
|
tab: options.tab,
|
|
36
310
|
label,
|
|
37
311
|
code
|
|
38
|
-
});
|
|
39
|
-
if (
|
|
312
|
+
}, call);
|
|
313
|
+
if (executeOperation.failed(result)) throw new Error(result.error?.message ?? `${label} failed`);
|
|
40
314
|
const line = result.output.find((item) => item.startsWith("{") || item.startsWith("["));
|
|
41
315
|
if (!line) throw new Error(`${label} printed nothing`);
|
|
42
316
|
return line;
|
|
@@ -142,8 +416,8 @@ function describeCheck(report) {
|
|
|
142
416
|
* because it reads the project's files and its own copy of TypeScript.
|
|
143
417
|
*/
|
|
144
418
|
async function runLib(packageName, options) {
|
|
145
|
-
const { appManifestPath, generateLibrary, manifestText, mergeManifest, reviseLibrary } = await import("./src-
|
|
146
|
-
const { covers, registryDir, registryEntries } = await import("./src-
|
|
419
|
+
const { appManifestPath, generateLibrary, manifestText, mergeManifest, reviseLibrary } = await import("./src-CcWc-KsX.mjs");
|
|
420
|
+
const { covers, registryDir, registryEntries } = await import("./src-BOUQtcWB.mjs");
|
|
147
421
|
const spec = packageName === void 0 ? void 0 : packageSpec(packageName);
|
|
148
422
|
let root = resolve(options.root ?? ".");
|
|
149
423
|
if (spec && (spec.version !== void 0 || options.registry && !options.root)) {
|
|
@@ -154,7 +428,7 @@ async function runLib(packageName, options) {
|
|
|
154
428
|
console.error(`Installing ${spec.name} ${version} on its own…`);
|
|
155
429
|
root = await installedVersion(spec.name, version);
|
|
156
430
|
}
|
|
157
|
-
const { parseLibraryEdits } = await import("./src-
|
|
431
|
+
const { parseLibraryEdits } = await import("./src-CcWc-KsX.mjs");
|
|
158
432
|
const trialEdits = options.edits ? parseLibraryEdits(JSON.parse(await readFile(resolve(options.edits), "utf8"))) : void 0;
|
|
159
433
|
const recordedId = options.registry && spec ? options.id ?? (await registryEntries()).find((entry) => entry.manifest.package?.name === spec.name)?.manifest.id : void 0;
|
|
160
434
|
const appErrors = recordedId ? await readAppErrors(registryDir(), recordedId) : void 0;
|
|
@@ -304,17 +578,693 @@ function describeReport(report) {
|
|
|
304
578
|
for (const note of report.notes) lines.push(`Note: ${note}`);
|
|
305
579
|
return lines.join("\n");
|
|
306
580
|
}
|
|
581
|
+
// src/operations.ts
|
|
582
|
+
function text$1(description, required = false, fallback) {
|
|
583
|
+
return {
|
|
584
|
+
schema: {
|
|
585
|
+
type: "string",
|
|
586
|
+
minLength: 1,
|
|
587
|
+
pattern: "\\S",
|
|
588
|
+
description,
|
|
589
|
+
...fallback === void 0 ? {} : { default: fallback }
|
|
590
|
+
},
|
|
591
|
+
required,
|
|
592
|
+
read(value, name) {
|
|
593
|
+
if (value === void 0 && !required) return fallback;
|
|
594
|
+
if (typeof value !== "string" || !value.trim()) throw invalidInput(name, `${name} must be a nonblank string`);
|
|
595
|
+
return value;
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
function boolean(description) {
|
|
600
|
+
return {
|
|
601
|
+
schema: {
|
|
602
|
+
type: "boolean",
|
|
603
|
+
default: false,
|
|
604
|
+
description
|
|
605
|
+
},
|
|
606
|
+
read(value, name) {
|
|
607
|
+
if (value === void 0) return false;
|
|
608
|
+
if (typeof value !== "boolean") throw invalidInput(name, `${name} must be a boolean`);
|
|
609
|
+
return value;
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function selector(description, fallback) {
|
|
614
|
+
const item = {
|
|
615
|
+
type: "string",
|
|
616
|
+
minLength: 1,
|
|
617
|
+
pattern: "\\S"
|
|
618
|
+
};
|
|
619
|
+
return {
|
|
620
|
+
schema: {
|
|
621
|
+
oneOf: [item, {
|
|
622
|
+
type: "array",
|
|
623
|
+
minItems: 1,
|
|
624
|
+
items: item
|
|
625
|
+
}],
|
|
626
|
+
description,
|
|
627
|
+
...fallback ? { default: fallback } : {}
|
|
628
|
+
},
|
|
629
|
+
read(value, name) {
|
|
630
|
+
if (value === void 0) return fallback;
|
|
631
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
632
|
+
if (Array.isArray(value) && value.length && value.every((item) => typeof item === "string" && item.trim())) return value.length === 1 ? value[0] : value;
|
|
633
|
+
throw invalidInput(name, `${name} must be a nonblank string or a nonempty list of nonblank strings`);
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
/** How many items a list read returns at most: a default that keeps a reply small, and a ceiling. */
|
|
638
|
+
function limit(description, fallback, most) {
|
|
639
|
+
return {
|
|
640
|
+
schema: {
|
|
641
|
+
type: "integer",
|
|
642
|
+
minimum: 1,
|
|
643
|
+
maximum: most,
|
|
644
|
+
default: fallback,
|
|
645
|
+
description
|
|
646
|
+
},
|
|
647
|
+
read(value, name) {
|
|
648
|
+
if (value === void 0) return fallback;
|
|
649
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > most) throw invalidInput(name, `${name} must be a whole number from 1 to ${most}`);
|
|
650
|
+
return value;
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
const tab = text$1(tabDescription);
|
|
655
|
+
const design = text$1("Design id or name.", true);
|
|
656
|
+
const scale = {
|
|
657
|
+
schema: {
|
|
658
|
+
type: "number",
|
|
659
|
+
minimum: .25,
|
|
660
|
+
maximum: 3,
|
|
661
|
+
default: 1,
|
|
662
|
+
description: "Pixel density."
|
|
663
|
+
},
|
|
664
|
+
read(value) {
|
|
665
|
+
if (value === void 0) return 1;
|
|
666
|
+
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");
|
|
667
|
+
return value;
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
const rootText = text$1("Absolute project folder path.", true);
|
|
671
|
+
const absoluteRoot = /^(?:\/|[A-Za-z]:[\\/]|\\\\)/;
|
|
672
|
+
const root$1 = {
|
|
673
|
+
...rootText,
|
|
674
|
+
schema: {
|
|
675
|
+
...rootText.schema,
|
|
676
|
+
pattern: absoluteRoot.source
|
|
677
|
+
},
|
|
678
|
+
read(value) {
|
|
679
|
+
const path = rootText.read(value, "root");
|
|
680
|
+
if (typeof path !== "string" || !absoluteRoot.test(path)) throw invalidInput("root", "root must be an absolute project folder path");
|
|
681
|
+
return path;
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
function fieldsContract(fields) {
|
|
685
|
+
return {
|
|
686
|
+
inputSchema: {
|
|
687
|
+
type: "object",
|
|
688
|
+
additionalProperties: false,
|
|
689
|
+
properties: Object.fromEntries(Object.entries(fields).map(([key, field]) => [key, field.schema])),
|
|
690
|
+
required: Object.entries(fields).filter(([, field]) => field.required).map(([key]) => key)
|
|
691
|
+
},
|
|
692
|
+
request(input) {
|
|
693
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) throw invalidInput(void 0, "Operation input must be an object");
|
|
694
|
+
for (const key of Object.keys(input)) if (!Object.hasOwn(fields, key)) throw invalidInput(key, `Unknown input field ${key}`);
|
|
695
|
+
const result = {};
|
|
696
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
697
|
+
const value = field.read(input[key], key);
|
|
698
|
+
if (value !== void 0) result[key] = value;
|
|
699
|
+
}
|
|
700
|
+
return result;
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
/** An operation whose canonical result is built from the app's reply, given the validated request. */
|
|
705
|
+
function mapped(method, fields, outputSchema, result, options = {}) {
|
|
706
|
+
const contract = fieldsContract(fields);
|
|
707
|
+
const routing = routingFor(fields);
|
|
708
|
+
return {
|
|
709
|
+
inputSchema: contract.inputSchema,
|
|
710
|
+
outputSchema,
|
|
711
|
+
fields: Object.keys(fields),
|
|
712
|
+
routing,
|
|
713
|
+
async run(input, send) {
|
|
714
|
+
const request = contract.request(input);
|
|
715
|
+
return result(await send(method, request, routing), request);
|
|
716
|
+
},
|
|
717
|
+
failed: options.failed ?? (() => false),
|
|
718
|
+
structured: options.structured ?? ((value) => Object.fromEntries(Object.entries(value)))
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
/** Which copy a call goes to, from its fields: see `Operation.routing`. */
|
|
722
|
+
function routingFor(fields) {
|
|
723
|
+
if ("tab" in fields) return "tab";
|
|
724
|
+
if ("root" in fields) return "open";
|
|
725
|
+
return "copies";
|
|
726
|
+
}
|
|
727
|
+
/** An operation whose canonical result is the app's reply. */
|
|
728
|
+
function operation(method, fields, outputSchema, options = {}) {
|
|
729
|
+
return mapped(method, fields, outputSchema, (reply) => reply, options);
|
|
730
|
+
}
|
|
731
|
+
/** The first `count` items, with how many there were, so a reply says when it left some out. */
|
|
732
|
+
function bounded(items, count, keep = "first") {
|
|
733
|
+
const kept = keep === "first" ? items.slice(0, count) : items.slice(Math.max(0, items.length - count));
|
|
734
|
+
return {
|
|
735
|
+
items: kept,
|
|
736
|
+
total: items.length,
|
|
737
|
+
truncated: kept.length < items.length
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const string = { type: "string" };
|
|
741
|
+
const strings = {
|
|
742
|
+
type: "array",
|
|
743
|
+
items: string
|
|
744
|
+
};
|
|
745
|
+
const integer = { type: "integer" };
|
|
746
|
+
const boolean_ = { type: "boolean" };
|
|
747
|
+
function object(properties, optional = []) {
|
|
748
|
+
return {
|
|
749
|
+
type: "object",
|
|
750
|
+
properties,
|
|
751
|
+
required: Object.keys(properties).filter((key) => !optional.includes(key))
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
const ref = object({
|
|
755
|
+
id: string,
|
|
756
|
+
name: string
|
|
757
|
+
});
|
|
758
|
+
const tabSchema = object({
|
|
759
|
+
id: string,
|
|
760
|
+
name: string,
|
|
761
|
+
root: string,
|
|
762
|
+
active: boolean_,
|
|
763
|
+
status: {
|
|
764
|
+
type: "string",
|
|
765
|
+
enum: [
|
|
766
|
+
"loading",
|
|
767
|
+
"ready",
|
|
768
|
+
"error"
|
|
769
|
+
]
|
|
770
|
+
},
|
|
771
|
+
error: string,
|
|
772
|
+
designs: {
|
|
773
|
+
...integer,
|
|
774
|
+
description: "How many designs the project has."
|
|
775
|
+
}
|
|
776
|
+
}, ["error"]);
|
|
777
|
+
const frameSchema = {
|
|
778
|
+
state: string,
|
|
779
|
+
mode: string
|
|
780
|
+
};
|
|
781
|
+
const designSchema = object({
|
|
782
|
+
id: string,
|
|
783
|
+
name: string,
|
|
784
|
+
kind: string,
|
|
785
|
+
file: string,
|
|
786
|
+
x: { type: "number" },
|
|
787
|
+
y: { type: "number" },
|
|
788
|
+
width: { type: "number" },
|
|
789
|
+
height: { type: "number" },
|
|
790
|
+
states: {
|
|
791
|
+
type: "array",
|
|
792
|
+
items: ref
|
|
793
|
+
},
|
|
794
|
+
showing: object({
|
|
795
|
+
state: string,
|
|
796
|
+
mode: string,
|
|
797
|
+
beside: strings
|
|
798
|
+
}),
|
|
799
|
+
nodes: integer
|
|
800
|
+
}, ["width", "height"]);
|
|
801
|
+
const stateSchema = object({
|
|
802
|
+
tab: tabSchema,
|
|
803
|
+
selection: object({
|
|
804
|
+
design: string,
|
|
805
|
+
node: string,
|
|
806
|
+
mode: string
|
|
807
|
+
}, [
|
|
808
|
+
"design",
|
|
809
|
+
"node",
|
|
810
|
+
"mode"
|
|
811
|
+
]),
|
|
812
|
+
designs: {
|
|
813
|
+
type: "array",
|
|
814
|
+
items: designSchema
|
|
815
|
+
},
|
|
816
|
+
overlaps: {
|
|
817
|
+
type: "array",
|
|
818
|
+
items: object({
|
|
819
|
+
designs: strings,
|
|
820
|
+
names: strings
|
|
821
|
+
})
|
|
822
|
+
},
|
|
823
|
+
modes: {
|
|
824
|
+
type: "array",
|
|
825
|
+
items: object({
|
|
826
|
+
id: string,
|
|
827
|
+
name: string,
|
|
828
|
+
width: { type: "number" },
|
|
829
|
+
primary: boolean_
|
|
830
|
+
})
|
|
831
|
+
},
|
|
832
|
+
components: strings,
|
|
833
|
+
tokens: object({
|
|
834
|
+
count: integer,
|
|
835
|
+
semantic: strings,
|
|
836
|
+
themeCategories: strings
|
|
837
|
+
}),
|
|
838
|
+
viewport: { type: "object" },
|
|
839
|
+
detection: { type: "object" }
|
|
840
|
+
}, ["viewport", "detection"]);
|
|
841
|
+
const truncation = {
|
|
842
|
+
total: {
|
|
843
|
+
...integer,
|
|
844
|
+
description: "How many there were before the limit."
|
|
845
|
+
},
|
|
846
|
+
truncated: {
|
|
847
|
+
...boolean_,
|
|
848
|
+
description: "Some were left out; narrow the read or raise limit."
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
const tokensSchema = object({
|
|
852
|
+
tokens: {
|
|
853
|
+
type: "array",
|
|
854
|
+
items: object({
|
|
855
|
+
name: string,
|
|
856
|
+
category: string,
|
|
857
|
+
source: string,
|
|
858
|
+
values: {
|
|
859
|
+
type: "object",
|
|
860
|
+
additionalProperties: string,
|
|
861
|
+
description: "Value per theme."
|
|
862
|
+
}
|
|
863
|
+
})
|
|
864
|
+
},
|
|
865
|
+
...truncation
|
|
866
|
+
});
|
|
867
|
+
const consoleSchema = object({
|
|
868
|
+
tab: ref,
|
|
869
|
+
entries: {
|
|
870
|
+
type: "array",
|
|
871
|
+
items: object({
|
|
872
|
+
level: {
|
|
873
|
+
type: "string",
|
|
874
|
+
enum: [
|
|
875
|
+
"info",
|
|
876
|
+
"warning",
|
|
877
|
+
"error",
|
|
878
|
+
"debug"
|
|
879
|
+
]
|
|
880
|
+
},
|
|
881
|
+
text: string,
|
|
882
|
+
source: string,
|
|
883
|
+
line: integer,
|
|
884
|
+
where: {
|
|
885
|
+
type: "string",
|
|
886
|
+
enum: ["canvas", "offscreen"]
|
|
887
|
+
},
|
|
888
|
+
count: integer,
|
|
889
|
+
first: { type: "number" },
|
|
890
|
+
last: { type: "number" }
|
|
891
|
+
})
|
|
892
|
+
},
|
|
893
|
+
...truncation
|
|
894
|
+
});
|
|
895
|
+
const problemsSchema = object({
|
|
896
|
+
tab: ref,
|
|
897
|
+
design: ref,
|
|
898
|
+
problems: {
|
|
899
|
+
type: "array",
|
|
900
|
+
items: object({
|
|
901
|
+
...frameSchema,
|
|
902
|
+
problem: string,
|
|
903
|
+
node: string
|
|
904
|
+
}, ["node"])
|
|
905
|
+
},
|
|
906
|
+
frames: integer,
|
|
907
|
+
warnings: strings
|
|
908
|
+
});
|
|
909
|
+
const accessibilitySchema = object({
|
|
910
|
+
tab: ref,
|
|
911
|
+
design: ref,
|
|
912
|
+
findings: {
|
|
913
|
+
type: "array",
|
|
914
|
+
items: object({
|
|
915
|
+
...frameSchema,
|
|
916
|
+
rule: string,
|
|
917
|
+
impact: {
|
|
918
|
+
type: "string",
|
|
919
|
+
enum: [
|
|
920
|
+
"critical",
|
|
921
|
+
"serious",
|
|
922
|
+
"moderate",
|
|
923
|
+
"minor"
|
|
924
|
+
]
|
|
925
|
+
},
|
|
926
|
+
help: string,
|
|
927
|
+
helpUrl: string,
|
|
928
|
+
node: string,
|
|
929
|
+
name: string,
|
|
930
|
+
detail: string
|
|
931
|
+
}, ["node", "name"])
|
|
932
|
+
},
|
|
933
|
+
frames: integer,
|
|
934
|
+
warnings: strings
|
|
935
|
+
});
|
|
936
|
+
const componentsSchema = object({
|
|
937
|
+
components: strings,
|
|
938
|
+
iconLibraries: strings,
|
|
939
|
+
leftOut: {
|
|
940
|
+
type: "array",
|
|
941
|
+
items: object({
|
|
942
|
+
name: string,
|
|
943
|
+
path: string,
|
|
944
|
+
reason: string,
|
|
945
|
+
undecided: boolean_
|
|
946
|
+
}, ["undecided"])
|
|
947
|
+
},
|
|
948
|
+
drawn: integer,
|
|
949
|
+
failing: {
|
|
950
|
+
type: "array",
|
|
951
|
+
items: object({
|
|
952
|
+
id: string,
|
|
953
|
+
problem: {
|
|
954
|
+
type: "string",
|
|
955
|
+
enum: [
|
|
956
|
+
"provider",
|
|
957
|
+
"server",
|
|
958
|
+
"async",
|
|
959
|
+
"error"
|
|
960
|
+
]
|
|
961
|
+
},
|
|
962
|
+
reason: string
|
|
963
|
+
})
|
|
964
|
+
},
|
|
965
|
+
corrections: { type: "object" }
|
|
966
|
+
}, [
|
|
967
|
+
"iconLibraries",
|
|
968
|
+
"leftOut",
|
|
969
|
+
"drawn",
|
|
970
|
+
"failing",
|
|
971
|
+
"corrections"
|
|
972
|
+
]);
|
|
973
|
+
const propsSchema = object({
|
|
974
|
+
component: string,
|
|
975
|
+
path: string,
|
|
976
|
+
import: string,
|
|
977
|
+
known: boolean_,
|
|
978
|
+
props: {
|
|
979
|
+
type: "array",
|
|
980
|
+
items: object({
|
|
981
|
+
name: string,
|
|
982
|
+
kind: string,
|
|
983
|
+
group: string,
|
|
984
|
+
options: {
|
|
985
|
+
type: "array",
|
|
986
|
+
items: { type: [
|
|
987
|
+
"string",
|
|
988
|
+
"number",
|
|
989
|
+
"boolean"
|
|
990
|
+
] }
|
|
991
|
+
},
|
|
992
|
+
default: { type: [
|
|
993
|
+
"string",
|
|
994
|
+
"number",
|
|
995
|
+
"boolean",
|
|
996
|
+
"null"
|
|
997
|
+
] },
|
|
998
|
+
required: boolean_
|
|
999
|
+
}, [
|
|
1000
|
+
"group",
|
|
1001
|
+
"options",
|
|
1002
|
+
"default",
|
|
1003
|
+
"required"
|
|
1004
|
+
])
|
|
1005
|
+
}
|
|
1006
|
+
}, ["path", "import"]);
|
|
1007
|
+
const viewportSchema = object({
|
|
1008
|
+
id: string,
|
|
1009
|
+
name: string,
|
|
1010
|
+
width: { type: "number" },
|
|
1011
|
+
height: { type: "number" },
|
|
1012
|
+
device: string
|
|
1013
|
+
}, ["device"]);
|
|
1014
|
+
const frameReportSchema = object({
|
|
1015
|
+
mode: string,
|
|
1016
|
+
width: { type: "number" },
|
|
1017
|
+
height: {
|
|
1018
|
+
type: "number",
|
|
1019
|
+
description: "Fixed frame height, which clips; absent when it grows."
|
|
1020
|
+
},
|
|
1021
|
+
window: object({
|
|
1022
|
+
width: { type: "number" },
|
|
1023
|
+
height: { type: "number" }
|
|
1024
|
+
})
|
|
1025
|
+
}, ["height"]);
|
|
1026
|
+
const modesSchema = object({
|
|
1027
|
+
tab: ref,
|
|
1028
|
+
variables: {
|
|
1029
|
+
type: "array",
|
|
1030
|
+
items: object({
|
|
1031
|
+
id: string,
|
|
1032
|
+
name: string,
|
|
1033
|
+
kind: {
|
|
1034
|
+
type: "string",
|
|
1035
|
+
enum: [
|
|
1036
|
+
"viewport",
|
|
1037
|
+
"theme",
|
|
1038
|
+
"locale",
|
|
1039
|
+
"custom"
|
|
1040
|
+
]
|
|
1041
|
+
},
|
|
1042
|
+
options: {
|
|
1043
|
+
type: "array",
|
|
1044
|
+
items: { type: "object" }
|
|
1045
|
+
}
|
|
1046
|
+
})
|
|
1047
|
+
},
|
|
1048
|
+
viewports: {
|
|
1049
|
+
type: "array",
|
|
1050
|
+
items: viewportSchema,
|
|
1051
|
+
description: "Viewport ids, which Responsive and a node's responsive patches take. Not mode ids."
|
|
1052
|
+
},
|
|
1053
|
+
modes: {
|
|
1054
|
+
type: "array",
|
|
1055
|
+
description: "Mode ids, which renders (screenshot, problems, accessibility) and a design's modes take.",
|
|
1056
|
+
items: object({
|
|
1057
|
+
id: string,
|
|
1058
|
+
name: string,
|
|
1059
|
+
primary: boolean_,
|
|
1060
|
+
viewport: string,
|
|
1061
|
+
width: { type: "number" },
|
|
1062
|
+
height: { type: "number" },
|
|
1063
|
+
values: {
|
|
1064
|
+
type: "object",
|
|
1065
|
+
additionalProperties: string
|
|
1066
|
+
},
|
|
1067
|
+
classNames: strings,
|
|
1068
|
+
attributes: {
|
|
1069
|
+
type: "object",
|
|
1070
|
+
additionalProperties: string
|
|
1071
|
+
}
|
|
1072
|
+
})
|
|
1073
|
+
},
|
|
1074
|
+
designs: {
|
|
1075
|
+
type: "array",
|
|
1076
|
+
items: object({
|
|
1077
|
+
id: string,
|
|
1078
|
+
name: string,
|
|
1079
|
+
state: string,
|
|
1080
|
+
states: strings,
|
|
1081
|
+
main: string,
|
|
1082
|
+
beside: strings,
|
|
1083
|
+
frames: {
|
|
1084
|
+
type: "array",
|
|
1085
|
+
items: frameReportSchema
|
|
1086
|
+
}
|
|
1087
|
+
}, ["frames"])
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
const historySide = object({
|
|
1091
|
+
count: integer,
|
|
1092
|
+
labels: {
|
|
1093
|
+
...strings,
|
|
1094
|
+
description: "With summary: newest first, up to limit."
|
|
1095
|
+
}
|
|
1096
|
+
}, ["labels"]);
|
|
1097
|
+
const statusSchema = object({
|
|
1098
|
+
tab: ref,
|
|
1099
|
+
revision: {
|
|
1100
|
+
type: "string",
|
|
1101
|
+
description: "Opaque; the same only while nothing in the tab has changed. Compare it with state's revision."
|
|
1102
|
+
},
|
|
1103
|
+
dirty: {
|
|
1104
|
+
...boolean_,
|
|
1105
|
+
description: "Something applied (and undoable) isn't on disk yet."
|
|
1106
|
+
},
|
|
1107
|
+
autosave: {
|
|
1108
|
+
...boolean_,
|
|
1109
|
+
description: "Changes save themselves; otherwise they wait for the person's Save."
|
|
1110
|
+
},
|
|
1111
|
+
unsaved: {
|
|
1112
|
+
type: "array",
|
|
1113
|
+
items: object({
|
|
1114
|
+
path: string,
|
|
1115
|
+
kind: {
|
|
1116
|
+
type: "string",
|
|
1117
|
+
enum: ["document", "stylesheet"]
|
|
1118
|
+
},
|
|
1119
|
+
designs: object({
|
|
1120
|
+
added: strings,
|
|
1121
|
+
changed: strings,
|
|
1122
|
+
removed: strings
|
|
1123
|
+
}),
|
|
1124
|
+
modes: boolean_,
|
|
1125
|
+
tokens: strings,
|
|
1126
|
+
more: integer
|
|
1127
|
+
}, [
|
|
1128
|
+
"designs",
|
|
1129
|
+
"modes",
|
|
1130
|
+
"tokens",
|
|
1131
|
+
"more"
|
|
1132
|
+
])
|
|
1133
|
+
},
|
|
1134
|
+
tokenEdits: {
|
|
1135
|
+
...integer,
|
|
1136
|
+
description: "Token values waiting for Save to write them to the stylesheet."
|
|
1137
|
+
},
|
|
1138
|
+
save: object({
|
|
1139
|
+
status: {
|
|
1140
|
+
type: "string",
|
|
1141
|
+
enum: [
|
|
1142
|
+
"saved",
|
|
1143
|
+
"unsaved",
|
|
1144
|
+
"saving",
|
|
1145
|
+
"failed"
|
|
1146
|
+
]
|
|
1147
|
+
},
|
|
1148
|
+
error: string
|
|
1149
|
+
}, ["error"]),
|
|
1150
|
+
errors: {
|
|
1151
|
+
type: "array",
|
|
1152
|
+
items: object({
|
|
1153
|
+
path: string,
|
|
1154
|
+
error: string
|
|
1155
|
+
})
|
|
1156
|
+
},
|
|
1157
|
+
undo: historySide,
|
|
1158
|
+
redo: historySide
|
|
1159
|
+
}, []);
|
|
1160
|
+
const checks = {
|
|
1161
|
+
tab,
|
|
1162
|
+
design,
|
|
1163
|
+
state: selector("State id, list of ids, or \"all\".", "all"),
|
|
1164
|
+
mode: selector("Mode id, list of ids, \"shown\" or \"all\".", "shown")
|
|
1165
|
+
};
|
|
1166
|
+
const operations = {
|
|
1167
|
+
tabs: mapped("tabs", {}, object({ tabs: {
|
|
1168
|
+
type: "array",
|
|
1169
|
+
items: tabSchema
|
|
1170
|
+
} }), (tabs) => ({ tabs })),
|
|
1171
|
+
state: operation("state", { tab }, stateSchema),
|
|
1172
|
+
status: operation("status", {
|
|
1173
|
+
tab,
|
|
1174
|
+
summary: boolean("Add each unsaved file's designs and token values, and the undo and redo labels."),
|
|
1175
|
+
limit: limit("Most ids or labels in each list of the summary.", 10, 100)
|
|
1176
|
+
}, statusSchema),
|
|
1177
|
+
modes: operation("modes", {
|
|
1178
|
+
tab,
|
|
1179
|
+
design: text$1("Design id or name. With it, just that design, with each of its frames' sizes.")
|
|
1180
|
+
}, modesSchema),
|
|
1181
|
+
open: operation("open", { root: root$1 }, tabSchema),
|
|
1182
|
+
screenshot: mapped("screenshot", {
|
|
1183
|
+
tab,
|
|
1184
|
+
design,
|
|
1185
|
+
scale,
|
|
1186
|
+
state: selector("State id, list of ids, or \"all\". Defaults to the state the design shows."),
|
|
1187
|
+
mode: selector("Mode id, list of ids, \"shown\" or \"all\". Defaults to the primary mode.")
|
|
1188
|
+
}, execOutputSchema, (reply) => execResponse(reply, [
|
|
1189
|
+
"tab",
|
|
1190
|
+
"design",
|
|
1191
|
+
"scale",
|
|
1192
|
+
"state",
|
|
1193
|
+
"mode"
|
|
1194
|
+
]), {
|
|
1195
|
+
failed: executionFailed,
|
|
1196
|
+
structured: structuredExec
|
|
1197
|
+
}),
|
|
1198
|
+
problems: operation("problems", checks, problemsSchema, { failed: (result) => result.problems.length > 0 }),
|
|
1199
|
+
accessibility: operation("accessibility", checks, accessibilitySchema, { failed: (result) => result.findings.length > 0 }),
|
|
1200
|
+
console: mapped("console", {
|
|
1201
|
+
tab,
|
|
1202
|
+
clear: boolean("Empty the console after reading, including entries beyond limit."),
|
|
1203
|
+
limit: limit("Most entries to return, newest kept.", 50, 1e3)
|
|
1204
|
+
}, consoleSchema, ({ tab: from, entries }, request) => {
|
|
1205
|
+
const { items, total, truncated } = bounded(entries, Number(request.limit), "last");
|
|
1206
|
+
return {
|
|
1207
|
+
tab: from,
|
|
1208
|
+
entries: items,
|
|
1209
|
+
total,
|
|
1210
|
+
truncated
|
|
1211
|
+
};
|
|
1212
|
+
}),
|
|
1213
|
+
tokens: mapped("tokens", {
|
|
1214
|
+
tab,
|
|
1215
|
+
category: text$1("Token category."),
|
|
1216
|
+
source: text$1("Token source, such as semantic or theme."),
|
|
1217
|
+
search: {
|
|
1218
|
+
schema: {
|
|
1219
|
+
type: "string",
|
|
1220
|
+
description: "Token name search."
|
|
1221
|
+
},
|
|
1222
|
+
read(value) {
|
|
1223
|
+
if (value !== void 0 && typeof value !== "string") throw invalidInput("search", "search must be a string");
|
|
1224
|
+
return value;
|
|
1225
|
+
}
|
|
1226
|
+
},
|
|
1227
|
+
limit: limit("Most tokens to return.", 100, 1e3)
|
|
1228
|
+
}, tokensSchema, (tokens, request) => {
|
|
1229
|
+
const { items, total, truncated } = bounded(tokens, Number(request.limit));
|
|
1230
|
+
return {
|
|
1231
|
+
tokens: items,
|
|
1232
|
+
total,
|
|
1233
|
+
truncated
|
|
1234
|
+
};
|
|
1235
|
+
}),
|
|
1236
|
+
components: operation("components", { tab }, componentsSchema),
|
|
1237
|
+
props: operation("props", {
|
|
1238
|
+
tab,
|
|
1239
|
+
component: text$1("Component id or name.", true),
|
|
1240
|
+
all: boolean("Include passed-through attributes.")
|
|
1241
|
+
}, propsSchema)
|
|
1242
|
+
};
|
|
1243
|
+
const skill = fieldsContract({ path: text$1("Skill file to read.", false, "SKILL.md") });
|
|
1244
|
+
const skillOperation = {
|
|
1245
|
+
inputSchema: skill.inputSchema,
|
|
1246
|
+
fields: ["path"],
|
|
1247
|
+
async run(input, read) {
|
|
1248
|
+
const { path } = skill.request(input);
|
|
1249
|
+
if (typeof path !== "string") throw invalidInput("path", "path must be a nonblank string");
|
|
1250
|
+
return read(path);
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
307
1253
|
// src/skills.ts
|
|
308
1254
|
const root = join(import.meta.dirname, "..", "skills");
|
|
309
1255
|
/** Reads the Parity design skill: SKILL.md, or a file it references. */
|
|
310
1256
|
async function readSkill(path = "SKILL.md") {
|
|
311
1257
|
const target = normalize(join(root, path));
|
|
312
|
-
if (relative(root, target).startsWith("..") || relative(root, target).includes(`..${sep}`)) throw
|
|
1258
|
+
if (relative(root, target).startsWith("..") || relative(root, target).includes(`..${sep}`)) throw invalidInput("path", `${path} isn't part of the skill`);
|
|
313
1259
|
try {
|
|
314
1260
|
return await readFile(target, "utf8");
|
|
315
1261
|
} catch {
|
|
316
1262
|
const files = await list();
|
|
317
|
-
throw new
|
|
1263
|
+
throw new CodedError(`No skill file ${path}. Files: ${files.join(", ")}`, {
|
|
1264
|
+
code: "invalid_input",
|
|
1265
|
+
field: "path",
|
|
1266
|
+
alternatives: closest(path, files)
|
|
1267
|
+
});
|
|
318
1268
|
}
|
|
319
1269
|
}
|
|
320
1270
|
async function list(directory = root) {
|
|
@@ -322,172 +1272,81 @@ async function list(directory = root) {
|
|
|
322
1272
|
return (await Promise.all(entries.map(async (entry) => entry.isDirectory() ? list(join(directory, entry.name)) : [relative(root, join(directory, entry.name)).replaceAll("\\", "/")]))).flat();
|
|
323
1273
|
}
|
|
324
1274
|
// src/mcp.ts
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
1275
|
+
/**
|
|
1276
|
+
* One adapter for RPC-backed tools: operation contracts own defaults, validation, failure semantics and the structured
|
|
1277
|
+
* result, which is what CLI --json prints. The text beside it is for people and clients that don't read structure.
|
|
1278
|
+
*
|
|
1279
|
+
* Tools publish no outputSchema (PAR-510). Claude Code never shows one to the model, Codex's code mode puts it in the
|
|
1280
|
+
* model's context, and with them tools/list was 33 KB rather than 11. structuredContent needs no schema; the contracts
|
|
1281
|
+
* keep theirs for CLI --json and the tests.
|
|
1282
|
+
*/
|
|
1283
|
+
function operationTool(name, description, operation, format) {
|
|
1284
|
+
return {
|
|
1285
|
+
name,
|
|
1286
|
+
description,
|
|
1287
|
+
inputSchema: operation.inputSchema,
|
|
1288
|
+
fields: operation.fields,
|
|
1289
|
+
async run(input) {
|
|
1290
|
+
const result = await operation.run(input, call);
|
|
1291
|
+
return {
|
|
1292
|
+
content: format(result),
|
|
1293
|
+
structuredContent: operation.structured(result),
|
|
1294
|
+
isError: operation.failed(result)
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
/** Results that are data to begin with, as compact JSON text for clients that don't read structuredContent. */
|
|
1300
|
+
const json = (value) => [text(JSON.stringify(value))];
|
|
1301
|
+
/** MCP's names for execute's label and code. */
|
|
1302
|
+
const executeNames = {
|
|
1303
|
+
label: "description",
|
|
1304
|
+
code: "input"
|
|
328
1305
|
};
|
|
329
1306
|
const tools = [
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
type: "object",
|
|
335
|
-
properties: {}
|
|
336
|
-
},
|
|
337
|
-
run: async () => [text(JSON.stringify(await call("tabs"), null, 2))]
|
|
338
|
-
},
|
|
339
|
-
{
|
|
340
|
-
name: "get_app_state",
|
|
341
|
-
description: "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.",
|
|
342
|
-
inputSchema: {
|
|
343
|
-
type: "object",
|
|
344
|
-
properties: { tab }
|
|
345
|
-
},
|
|
346
|
-
run: async (input) => [text(JSON.stringify(await call("state", { tab: input.tab }), null, 2))]
|
|
347
|
-
},
|
|
1307
|
+
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),
|
|
1308
|
+
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),
|
|
1309
|
+
operationTool("get_status", "Read where a tab's edits stand, changing nothing: its revision (the same only while nothing has changed, so compare it before reading again), whether anything applied isn't saved (dirty), the files waiting for Save, token values waiting, how the last save went and writes that failed, and how many steps undo and redo hold. summary adds each unsaved file's designs added, changed and removed, the token values, and undo and redo labels, up to limit each. Changes are undoable once applied; only the person saves them, unless automatic saving is on.", operations.status, (result) => [text(describeStatus(result))]),
|
|
1310
|
+
operationTool("get_modes", "Read the project's modes and what each design shows. viewports are the ids Responsive takes; modes are the ids screenshot, find_problems and check_accessibility take, each one option per variable (viewport, theme, locale), with its width, window height and the classes and attributes it sets. designs gives each board's state, main mode and the modes beside it; name a design to get just it, with each frame's size and window. Reading changes nothing.", operations.modes, (result) => [text(describeModes(result))]),
|
|
348
1311
|
{
|
|
349
1312
|
name: "execute",
|
|
350
|
-
description: "Run a JavaScript snippet
|
|
351
|
-
inputSchema:
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
description: "Shown to the person while it runs. 3 to 8 words, imperative, e.g. \"Add pricing tiers\"."
|
|
357
|
-
},
|
|
358
|
-
input: {
|
|
359
|
-
type: "string",
|
|
360
|
-
description: "The snippet. Omit when retrying with editId and edits."
|
|
361
|
-
},
|
|
362
|
-
editId: {
|
|
363
|
-
type: "string",
|
|
364
|
-
description: "The failed call's editId, to patch and rerun its snippet."
|
|
365
|
-
},
|
|
366
|
-
edits: {
|
|
367
|
-
type: "array",
|
|
368
|
-
description: "Exact find and replace edits to the failed snippet, applied in order.",
|
|
369
|
-
items: {
|
|
370
|
-
type: "object",
|
|
371
|
-
properties: {
|
|
372
|
-
find: { type: "string" },
|
|
373
|
-
replace: { type: "string" },
|
|
374
|
-
all: { type: "boolean" }
|
|
375
|
-
},
|
|
376
|
-
required: ["find", "replace"]
|
|
377
|
-
}
|
|
378
|
-
},
|
|
379
|
-
tab
|
|
380
|
-
},
|
|
381
|
-
required: ["description"]
|
|
382
|
-
},
|
|
383
|
-
run: async (input) => {
|
|
384
|
-
const result = await call("exec", {
|
|
1313
|
+
description: "Run a JavaScript snippet with the Parity API (read_skill \"execute.md\"). All changes land as one undoable step, or none on failure. Retry failures with editId and edits.",
|
|
1314
|
+
inputSchema: executeOperation.inputSchema(executeNames),
|
|
1315
|
+
fields: executeOperation.fields,
|
|
1316
|
+
names: executeNames,
|
|
1317
|
+
async run(input) {
|
|
1318
|
+
const result = await executeOperation.run({
|
|
385
1319
|
tab: input.tab,
|
|
386
1320
|
label: input.description,
|
|
387
1321
|
code: input.input,
|
|
388
1322
|
editId: input.editId,
|
|
389
1323
|
edits: input.edits
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
design: {
|
|
401
|
-
type: "string",
|
|
402
|
-
description: "Design id or name."
|
|
403
|
-
},
|
|
404
|
-
state: { description: "State id, list of ids, or \"all\". Defaults to the state the design shows." },
|
|
405
|
-
mode: { description: "Mode id, list of ids, \"shown\" or \"all\". Defaults to the primary mode." },
|
|
406
|
-
scale: {
|
|
407
|
-
type: "number",
|
|
408
|
-
description: "Pixel density, 0.25 to 3. Defaults to 1."
|
|
409
|
-
},
|
|
410
|
-
tab
|
|
411
|
-
},
|
|
412
|
-
required: ["design"]
|
|
413
|
-
},
|
|
414
|
-
run: async (input) => {
|
|
415
|
-
const result = await call("screenshot", input);
|
|
416
|
-
return [text(describeExec(result)), ...result.screenshots.map((shot) => image(shot.png))];
|
|
1324
|
+
}, call);
|
|
1325
|
+
const structured = executeOperation.structured(result);
|
|
1326
|
+
return {
|
|
1327
|
+
content: [text(describeExec(result)), ...result.screenshots.map((shot) => image(shot.png))],
|
|
1328
|
+
structuredContent: result.error ? {
|
|
1329
|
+
...structured,
|
|
1330
|
+
error: renamePath(result.error, executeNames)
|
|
1331
|
+
} : structured,
|
|
1332
|
+
isError: executeOperation.failed(result)
|
|
1333
|
+
};
|
|
417
1334
|
}
|
|
418
1335
|
},
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
description: "Design id or name."
|
|
428
|
-
},
|
|
429
|
-
state: { description: "State id, list of ids, or \"all\". Defaults to \"all\"." },
|
|
430
|
-
mode: { description: "Mode id, list of ids, \"shown\" or \"all\". Defaults to \"shown\"." },
|
|
431
|
-
tab
|
|
432
|
-
},
|
|
433
|
-
required: ["design"]
|
|
434
|
-
},
|
|
435
|
-
run: async (input) => [text(describeProblems(await call("problems", input)))]
|
|
436
|
-
},
|
|
437
|
-
{
|
|
438
|
-
name: "check_accessibility",
|
|
439
|
-
description: "Check a design's accessibility with axe-core against WCAG 2.2 AA, rendered offscreen in every state and the modes it shows unless you narrow it. Lists each rule broken, worst first, with the layers that break it (ids you can pass to execute), the frames, what's wrong and a link on how to fix it. state and mode take an id, a list, or \"all\"; mode also takes \"shown\". Automated checks find some issues, not all.",
|
|
440
|
-
inputSchema: {
|
|
441
|
-
type: "object",
|
|
442
|
-
properties: {
|
|
443
|
-
design: {
|
|
444
|
-
type: "string",
|
|
445
|
-
description: "Design id or name."
|
|
446
|
-
},
|
|
447
|
-
state: { description: "State id, list of ids, or \"all\". Defaults to \"all\"." },
|
|
448
|
-
mode: { description: "Mode id, list of ids, \"shown\" or \"all\". Defaults to \"shown\"." },
|
|
449
|
-
tab
|
|
450
|
-
},
|
|
451
|
-
required: ["design"]
|
|
452
|
-
},
|
|
453
|
-
run: async (input) => [text(describeAccessibility(await call("accessibility", input)))]
|
|
454
|
-
},
|
|
455
|
-
{
|
|
456
|
-
name: "get_console",
|
|
457
|
-
description: "Read what the project's own code wrote to the console: errors, warnings and logs from its components on the canvas, including while the person interacts with them, and from offscreen renders such as screenshots. Oldest first; the same message several times in a row is counted once. clear: true empties it after reading, so the next read shows only what's new.",
|
|
458
|
-
inputSchema: {
|
|
459
|
-
type: "object",
|
|
460
|
-
properties: {
|
|
461
|
-
clear: {
|
|
462
|
-
type: "boolean",
|
|
463
|
-
description: "Empty it after reading."
|
|
464
|
-
},
|
|
465
|
-
tab
|
|
466
|
-
}
|
|
467
|
-
},
|
|
468
|
-
run: async (input) => [text(describeConsole(await call("console", input)))]
|
|
469
|
-
},
|
|
470
|
-
{
|
|
471
|
-
name: "open_project",
|
|
472
|
-
description: "Open a project folder in a new Parity tab, or switch to it if it's already open.",
|
|
473
|
-
inputSchema: {
|
|
474
|
-
type: "object",
|
|
475
|
-
properties: { root: {
|
|
476
|
-
type: "string",
|
|
477
|
-
description: "Absolute path."
|
|
478
|
-
} },
|
|
479
|
-
required: ["root"]
|
|
480
|
-
},
|
|
481
|
-
run: async (input) => [text(JSON.stringify(await call("open", { root: input.root }), null, 2))]
|
|
482
|
-
},
|
|
1336
|
+
operationTool("screenshot", "Capture a design in any of its states and modes, rendered offscreen at real widths. state and mode take an id, a list, or \"all\"; mode also takes \"shown\".", operations.screenshot, (result) => [text(describeExec(result)), ...result.screenshots.map((shot) => image(shot.png))]),
|
|
1337
|
+
operationTool("find_problems", "Find layout problems in a design, rendered offscreen in every state and the modes it shows unless you narrow it: render errors, content wider than the frame, nodes cut off by a parent that clips, and components that aren't the project's. Run it after changes, with screenshot. state and mode take an id, a list, or \"all\"; mode also takes \"shown\".", operations.problems, (result) => [text(describeProblems(result))]),
|
|
1338
|
+
operationTool("check_accessibility", "Check a design's accessibility with axe-core against WCAG 2.2 AA, rendered offscreen in every state and the modes it shows unless you narrow it. Lists each rule broken, worst first, with the layers that break it (ids you can pass to execute), the frames, what's wrong and a link on how to fix it. state and mode take an id, a list, or \"all\"; mode also takes \"shown\". Automated checks find some issues, not all.", operations.accessibility, (result) => [text(describeAccessibility(result))]),
|
|
1339
|
+
operationTool("get_console", "Read what the project's own code wrote to the console: errors, warnings and logs from its components on the canvas, including while the person interacts with them, and from offscreen renders such as screenshots. Oldest first; the same message several times in a row is counted once. clear: true empties it after reading, so the next read shows only what's new.", operations.console, (result) => [text(describeConsole(result))]),
|
|
1340
|
+
operationTool("open_project", "Open a project folder in a new Parity tab, or switch to it if it's already open.", operations.open, json),
|
|
1341
|
+
operationTool("get_tokens", "Read tokens filtered by category, source or name, with values in every theme.", operations.tokens, (result) => [text(describeTokens(result))]),
|
|
1342
|
+
operationTool("get_components", "List project components, libraries, icon sets and exports that were left out or failed to render.", operations.components, (result) => [text(describeComponents(result))]),
|
|
1343
|
+
operationTool("get_props", "Read a component's props from source. all includes attributes it passes through.", operations.props, (result) => [text(describeProps(result))]),
|
|
483
1344
|
{
|
|
484
1345
|
name: "read_skill",
|
|
485
|
-
description: "Read Parity
|
|
486
|
-
inputSchema:
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
},
|
|
490
|
-
run: async (input) => [text(await readSkill(input.path))]
|
|
1346
|
+
description: "Read the Parity design skill or a file it references.",
|
|
1347
|
+
inputSchema: skillOperation.inputSchema,
|
|
1348
|
+
fields: skillOperation.fields,
|
|
1349
|
+
run: async (input) => ({ content: [text(await skillOperation.run(input, readSkill))] })
|
|
491
1350
|
}
|
|
492
1351
|
];
|
|
493
1352
|
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.";
|
|
@@ -548,10 +1407,12 @@ async function respond(message) {
|
|
|
548
1407
|
message: `Unknown tool ${String(message.params?.name)}`
|
|
549
1408
|
} };
|
|
550
1409
|
try {
|
|
551
|
-
return { result:
|
|
1410
|
+
return { result: await tool.run(message.params?.arguments ?? {}) };
|
|
552
1411
|
} catch (error) {
|
|
1412
|
+
const failure = renamePath(operationError(error, tool.fields), tool.names ?? {});
|
|
553
1413
|
return { result: {
|
|
554
|
-
content: [text(
|
|
1414
|
+
content: [text(failure.message)],
|
|
1415
|
+
structuredContent: { error: failure },
|
|
555
1416
|
isError: true
|
|
556
1417
|
} };
|
|
557
1418
|
}
|
|
@@ -580,6 +1441,12 @@ const usage = `parity: drive the Parity app from a terminal or an agent
|
|
|
580
1441
|
|
|
581
1442
|
parity tabs list open project tabs
|
|
582
1443
|
parity state [--tab t] designs, selection, modes, components and tokens of a tab
|
|
1444
|
+
parity status [--summary] [--limit 10] [--tab t]
|
|
1445
|
+
the tab's revision, whether anything applied isn't saved, the files and
|
|
1446
|
+
token values waiting for Save, the last save and failed writes, and undo
|
|
1447
|
+
and redo; --summary names the designs, tokens and steps
|
|
1448
|
+
parity modes [design] [--tab t] viewports (the ids Responsive takes), variables, modes (the ids renders
|
|
1449
|
+
take) and what each design shows; a design adds its frames' sizes
|
|
583
1450
|
parity exec [file] --label "Do x" run a snippet (from a file, or stdin) as one undoable step
|
|
584
1451
|
[--edit-id id --edits '[{"find":"a","replace":"b"}]'] retry a failed snippet with edits
|
|
585
1452
|
parity screenshot <design> [--state s|all] [--mode m|shown|all] [--scale 2] [--tab t]
|
|
@@ -589,10 +1456,13 @@ const usage = `parity: drive the Parity app from a terminal or an agent
|
|
|
589
1456
|
parity problems <design> [--state s|all] [--mode m|shown|all] [--tab t]
|
|
590
1457
|
render errors, overflow, clipping and unknown components, in every state
|
|
591
1458
|
and the modes shown unless narrowed; exits with 1 when it finds any
|
|
592
|
-
parity console [--clear] [--tab t]
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
1459
|
+
parity console [--clear] [--limit 50] [--tab t]
|
|
1460
|
+
what the project's code wrote to the console, on the canvas and in
|
|
1461
|
+
offscreen renders, the newest 50 unless --limit says; --clear empties it
|
|
1462
|
+
after reading
|
|
1463
|
+
parity tokens [--category c] [--source semantic|theme] [--search s] [--limit 100] [--tab t]
|
|
1464
|
+
the tokens styles can name, with their values per theme, the first 100
|
|
1465
|
+
unless --limit says
|
|
596
1466
|
parity components [--tab t] the components designs can use, by library
|
|
597
1467
|
parity props <Component> [--all] [--tab t]
|
|
598
1468
|
a component's props from its source; --all adds the attributes it passes on
|
|
@@ -628,7 +1498,11 @@ const usage = `parity: drive the Parity app from a terminal or an agent
|
|
|
628
1498
|
parity skill [file] print the design skill, or one of its files
|
|
629
1499
|
parity mcp serve these as MCP tools on stdio
|
|
630
1500
|
|
|
631
|
-
Options take a tab id, project root or name with --tab
|
|
1501
|
+
Options take a tab id, project root or name with --tab, or "active" for what the person is looking at; without it, a
|
|
1502
|
+
command acts on the open project the current folder is in, else on "active". Each call goes to the running copy of
|
|
1503
|
+
Parity that has its project open. --json prints any command's
|
|
1504
|
+
result as the data MCP returns as structuredContent, and a failure as {"error": {code, message, retryable, path,
|
|
1505
|
+
alternatives, editId}}.`;
|
|
632
1506
|
const { positionals, values } = parseArgs({
|
|
633
1507
|
allowPositionals: true,
|
|
634
1508
|
options: {
|
|
@@ -648,10 +1522,12 @@ const { positionals, values } = parseArgs({
|
|
|
648
1522
|
json: { type: "boolean" },
|
|
649
1523
|
record: { type: "boolean" },
|
|
650
1524
|
clear: { type: "boolean" },
|
|
1525
|
+
summary: { type: "boolean" },
|
|
651
1526
|
all: { type: "boolean" },
|
|
652
1527
|
category: { type: "string" },
|
|
653
1528
|
source: { type: "string" },
|
|
654
1529
|
search: { type: "string" },
|
|
1530
|
+
limit: { type: "string" },
|
|
655
1531
|
root: { type: "string" },
|
|
656
1532
|
out: { type: "string" },
|
|
657
1533
|
registry: { type: "boolean" },
|
|
@@ -669,10 +1545,14 @@ const { positionals, values } = parseArgs({
|
|
|
669
1545
|
}
|
|
670
1546
|
});
|
|
671
1547
|
const [command, ...rest] = positionals;
|
|
1548
|
+
/** The running command's input fields, which a failure's path can point at. */
|
|
1549
|
+
let fields;
|
|
672
1550
|
try {
|
|
673
1551
|
await main();
|
|
674
1552
|
} catch (error) {
|
|
675
|
-
|
|
1553
|
+
const failure = operationError(error, fields);
|
|
1554
|
+
if (values.json) print({ error: failure });
|
|
1555
|
+
console.error(failure.message);
|
|
676
1556
|
process.exitCode = 1;
|
|
677
1557
|
}
|
|
678
1558
|
async function main() {
|
|
@@ -680,14 +1560,23 @@ async function main() {
|
|
|
680
1560
|
console.log(usage);
|
|
681
1561
|
return;
|
|
682
1562
|
}
|
|
1563
|
+
const limit = values.limit === void 0 ? void 0 : Number(values.limit);
|
|
683
1564
|
switch (command) {
|
|
684
|
-
case "tabs": return
|
|
685
|
-
case "state": return
|
|
686
|
-
case "
|
|
687
|
-
|
|
688
|
-
|
|
1565
|
+
case "tabs": return show(operations.tabs, {});
|
|
1566
|
+
case "state": return show(operations.state, { tab: values.tab });
|
|
1567
|
+
case "status": return show(operations.status, {
|
|
1568
|
+
tab: values.tab,
|
|
1569
|
+
summary: values.summary,
|
|
1570
|
+
limit
|
|
1571
|
+
}, describeStatus);
|
|
1572
|
+
case "modes": return show(operations.modes, {
|
|
1573
|
+
tab: values.tab,
|
|
1574
|
+
design: rest[0]
|
|
1575
|
+
}, describeModes);
|
|
1576
|
+
case "open": return show(operations.open, { root: rest[0]?.trim() ? resolve(rest[0]) : rest[0] });
|
|
689
1577
|
case "skill":
|
|
690
|
-
|
|
1578
|
+
fields = skillOperation.fields;
|
|
1579
|
+
console.log(await skillOperation.run({ path: rest[0] }, readSkill));
|
|
691
1580
|
return;
|
|
692
1581
|
case "mcp":
|
|
693
1582
|
serveMcp();
|
|
@@ -714,111 +1603,82 @@ async function main() {
|
|
|
714
1603
|
...values.json ? { json: true } : {}
|
|
715
1604
|
});
|
|
716
1605
|
case "exec": {
|
|
1606
|
+
fields = executeOperation.fields;
|
|
717
1607
|
const editId = values["edit-id"];
|
|
718
1608
|
let code;
|
|
719
|
-
if (
|
|
720
|
-
|
|
721
|
-
|
|
1609
|
+
if (rest[0]) code = await readFile(rest[0], "utf8");
|
|
1610
|
+
else if (!editId) code = await stdin() || void 0;
|
|
1611
|
+
let edits;
|
|
1612
|
+
try {
|
|
1613
|
+
edits = values.edits === void 0 ? void 0 : JSON.parse(values.edits);
|
|
1614
|
+
} catch {
|
|
1615
|
+
throw invalidInput("edits", "edits must be JSON: a list of {find, replace}");
|
|
1616
|
+
}
|
|
1617
|
+
const result = await executeOperation.run({
|
|
722
1618
|
tab: values.tab,
|
|
723
|
-
label: values.label
|
|
1619
|
+
label: values.label,
|
|
724
1620
|
code,
|
|
725
1621
|
editId,
|
|
726
1622
|
edits
|
|
727
|
-
})
|
|
1623
|
+
}, call);
|
|
1624
|
+
if (values.json) print(executeOperation.structured(result));
|
|
1625
|
+
else console.log(describeExec(result));
|
|
1626
|
+
if (executeOperation.failed(result)) process.exitCode = 1;
|
|
728
1627
|
return;
|
|
729
1628
|
}
|
|
730
|
-
case "screenshot":
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
scale: values.scale ? Number(values.scale) : void 0
|
|
738
|
-
}));
|
|
739
|
-
return;
|
|
1629
|
+
case "screenshot": return show(operations.screenshot, {
|
|
1630
|
+
tab: values.tab,
|
|
1631
|
+
design: rest[0],
|
|
1632
|
+
state: values.state,
|
|
1633
|
+
mode: values.mode,
|
|
1634
|
+
scale: values.scale === void 0 ? void 0 : Number(values.scale)
|
|
1635
|
+
}, describeExec);
|
|
740
1636
|
case "a11y":
|
|
741
|
-
case "accessibility": {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
tab: values.tab,
|
|
775
|
-
...filter
|
|
776
|
-
});
|
|
777
|
-
if (values.json) print(tokens);
|
|
778
|
-
else console.log(describeTokens(tokens));
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
case "components": {
|
|
782
|
-
const result = await call("components", { tab: values.tab });
|
|
783
|
-
if (values.json) print(result);
|
|
784
|
-
else console.log(describeComponents(result));
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
787
|
-
case "props": {
|
|
788
|
-
if (!rest[0]) throw new Error("parity props <Component> [--all]");
|
|
789
|
-
const result = await call("props", {
|
|
790
|
-
tab: values.tab,
|
|
791
|
-
component: rest[0],
|
|
792
|
-
all: values.all
|
|
793
|
-
});
|
|
794
|
-
if (values.json) print(result);
|
|
795
|
-
else console.log(describeProps(result));
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
case "console": {
|
|
799
|
-
const result = await call("console", {
|
|
800
|
-
tab: values.tab,
|
|
801
|
-
clear: values.clear
|
|
802
|
-
});
|
|
803
|
-
if (values.json) print(result);
|
|
804
|
-
else console.log(describeConsole(result));
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
default: throw new Error(`Unknown command ${command}\n\n${usage}`);
|
|
1637
|
+
case "accessibility": return show(operations.accessibility, {
|
|
1638
|
+
tab: values.tab,
|
|
1639
|
+
design: rest[0],
|
|
1640
|
+
state: values.state,
|
|
1641
|
+
mode: values.mode
|
|
1642
|
+
}, describeAccessibility);
|
|
1643
|
+
case "problems": return show(operations.problems, {
|
|
1644
|
+
tab: values.tab,
|
|
1645
|
+
design: rest[0],
|
|
1646
|
+
state: values.state,
|
|
1647
|
+
mode: values.mode
|
|
1648
|
+
}, describeProblems);
|
|
1649
|
+
case "tokens": return show(operations.tokens, {
|
|
1650
|
+
tab: values.tab,
|
|
1651
|
+
category: values.category,
|
|
1652
|
+
source: values.source,
|
|
1653
|
+
search: values.search,
|
|
1654
|
+
limit
|
|
1655
|
+
}, describeTokens);
|
|
1656
|
+
case "components": return show(operations.components, { tab: values.tab }, describeComponents);
|
|
1657
|
+
case "props": return show(operations.props, {
|
|
1658
|
+
tab: values.tab,
|
|
1659
|
+
component: rest[0],
|
|
1660
|
+
all: values.all
|
|
1661
|
+
}, describeProps);
|
|
1662
|
+
case "console": return show(operations.console, {
|
|
1663
|
+
tab: values.tab,
|
|
1664
|
+
clear: values.clear,
|
|
1665
|
+
limit
|
|
1666
|
+
}, describeConsole);
|
|
1667
|
+
default: throw invalidInput(void 0, `Unknown command ${command}
|
|
1668
|
+
|
|
1669
|
+
${usage}`);
|
|
808
1670
|
}
|
|
809
1671
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
if (!list || list.length === 0) return void 0;
|
|
821
|
-
return list.length === 1 ? list[0] : list;
|
|
1672
|
+
/**
|
|
1673
|
+
* Runs a shared operation and prints it: the structured result with --json, the same data MCP returns as
|
|
1674
|
+
* structuredContent, or the account for people. A failure exits with 1 either way.
|
|
1675
|
+
*/
|
|
1676
|
+
async function show(operation, input, describe) {
|
|
1677
|
+
fields = operation.fields;
|
|
1678
|
+
const result = await operation.run(input, call);
|
|
1679
|
+
if (values.json || !describe) print(operation.structured(result));
|
|
1680
|
+
else console.log(describe(result));
|
|
1681
|
+
if (operation.failed(result)) process.exitCode = 1;
|
|
822
1682
|
}
|
|
823
1683
|
function print(value) {
|
|
824
1684
|
console.log(JSON.stringify(value, null, 2));
|