@parity-app/cli 0.4.0 → 0.5.1
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 +255 -34
- package/dist/{client-Cf66-n3R.mjs → client-CoW-KcnT.mjs} +130 -141
- package/dist/index.d.mts +98 -7
- package/dist/index.mjs +1 -1
- package/dist/schema-BpGSCLEu.mjs +113 -0
- package/dist/{src-y3hdgbK2.mjs → src-5CrSA_RX.mjs} +1 -1
- package/dist/{src-CYEKdXm_.mjs → src-C4faIpux.mjs} +142 -3
- package/dist/{src-CJUvccJO.mjs → src-Cr8pfFJE.mjs} +1 -1
- package/dist/{src-DaCYZuwb.mjs → src-Zm_PYFFH.mjs} +10 -5
- package/dist/src-p0vFxRBz.mjs +3 -0
- package/package.json +3 -2
- package/skills/SKILL.md +3 -2
- package/skills/execute.md +136 -7
- package/skills/guide/checking.md +6 -3
- package/skills/guide/components.md +3 -1
- package/skills/guide/states-and-modes.md +14 -0
- package/dist/src-DgI96EpD.mjs +0 -3
|
@@ -1,35 +1,38 @@
|
|
|
1
|
+
import { c as unknownReference, n as nonBlankString, r as CodedError, s as readErrorDetails, t as jsonText } from "./schema-BpGSCLEu.mjs";
|
|
1
2
|
import { join } from "node:path";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
|
|
4
5
|
import { homedir } from "node:os";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
// ../agent/src/routing.ts
|
|
8
|
+
/**
|
|
9
|
+
* Which running copy of Parity an agent's call goes to (PAR-514). Several copies can run at once, each with its own
|
|
10
|
+
* projects open: the person's app, a dev session, `pnpm perf`. Agents care about the project, not the copy, so every
|
|
11
|
+
* call is sent to the copy that has its project open. The CLI decides, once, with these rules, and sends that copy the
|
|
12
|
+
* exact tab it chose; the app matches a tab reference with the same `matchTab`. Nothing here touches files or the
|
|
13
|
+
* network, so the app's renderer can use it too.
|
|
14
|
+
*/
|
|
15
|
+
/** How to reach one running copy: what it writes to its registry entry. A number an entry lacks reads as 0. */
|
|
16
|
+
const copyEntry = z.object({
|
|
17
|
+
version: z.number().catch(0),
|
|
18
|
+
url: z.string(),
|
|
19
|
+
token: z.string(),
|
|
20
|
+
/** The app's main process, which names the copy and its entry. A service the app restarts is the same copy. */
|
|
21
|
+
app: z.number(),
|
|
22
|
+
/** When the copy started, in milliseconds since the epoch. A project open in two copies goes to the older one. */
|
|
23
|
+
startedAt: z.number().catch(0)
|
|
24
|
+
});
|
|
25
|
+
/** An entry's text. The file from before the registry names the app's main process `pid`. */
|
|
26
|
+
const entryText = jsonText(copyEntry.extend({
|
|
27
|
+
app: z.number().optional().catch(void 0),
|
|
28
|
+
pid: z.number().optional().catch(void 0)
|
|
29
|
+
}).transform(({ app, pid, ...entry }) => ({
|
|
30
|
+
...entry,
|
|
31
|
+
app: app ?? pid ?? 0
|
|
32
|
+
})));
|
|
5
33
|
/** Reads a registry entry's text, or nothing when it isn't one. A file from before the registry stands for its copy. */
|
|
6
34
|
function readCopyEntry(text) {
|
|
7
|
-
|
|
8
|
-
let value;
|
|
9
|
-
try {
|
|
10
|
-
value = JSON.parse(text);
|
|
11
|
-
} catch {
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
15
|
-
const fields = new Map(Object.entries(value));
|
|
16
|
-
const [version, url, token, app, pid, startedAt] = [
|
|
17
|
-
"version",
|
|
18
|
-
"url",
|
|
19
|
-
"token",
|
|
20
|
-
"app",
|
|
21
|
-
"pid",
|
|
22
|
-
"startedAt"
|
|
23
|
-
].map((key) => fields.get(key));
|
|
24
|
-
if (typeof url !== "string" || typeof token !== "string") return void 0;
|
|
25
|
-
const number = (field) => typeof field === "number" ? field : void 0;
|
|
26
|
-
return {
|
|
27
|
-
version: number(version) ?? 0,
|
|
28
|
-
url,
|
|
29
|
-
token,
|
|
30
|
-
app: number(app) ?? number(pid) ?? 0,
|
|
31
|
-
startedAt: number(startedAt) ?? 0
|
|
32
|
-
};
|
|
35
|
+
return entryText.safeParse(text).data;
|
|
33
36
|
}
|
|
34
37
|
/**
|
|
35
38
|
* A project folder as routing compares it: forward slashes, no trailing slash, and on Windows (a drive letter or a
|
|
@@ -160,15 +163,21 @@ function byAge(a, b) {
|
|
|
160
163
|
return a.entry.startedAt - b.entry.startedAt || a.entry.app - b.entry.app;
|
|
161
164
|
}
|
|
162
165
|
// ../agent/src/registry.ts
|
|
166
|
+
/** The settings that place the registry. One that's blank counts as unset. */
|
|
167
|
+
const registrySettings = z.object({
|
|
168
|
+
PARITY_AGENT_FILE: nonBlankString.optional().catch(void 0),
|
|
169
|
+
PARITY_AGENT_DIR: nonBlankString.optional().catch(void 0)
|
|
170
|
+
});
|
|
163
171
|
/** The registry these settings name: `PARITY_AGENT_FILE`, else `PARITY_AGENT_DIR`, else `~/.parity/agents`. */
|
|
164
172
|
function agentRegistry(env, home) {
|
|
165
|
-
|
|
173
|
+
const { PARITY_AGENT_FILE: file, PARITY_AGENT_DIR: folder } = registrySettings.parse(env);
|
|
174
|
+
if (file) return {
|
|
166
175
|
kind: "file",
|
|
167
|
-
path:
|
|
176
|
+
path: file
|
|
168
177
|
};
|
|
169
178
|
return {
|
|
170
179
|
kind: "folder",
|
|
171
|
-
path:
|
|
180
|
+
path: folder ?? join(home, ".parity", "agents")
|
|
172
181
|
};
|
|
173
182
|
}
|
|
174
183
|
/** The file before the registry, where an app from before it says how to reach it. */
|
|
@@ -221,105 +230,10 @@ function readText(path) {
|
|
|
221
230
|
return;
|
|
222
231
|
}
|
|
223
232
|
}
|
|
224
|
-
// ../core/src/errors.ts
|
|
225
|
-
/**
|
|
226
|
-
* Why an agent's call failed, as a stable code it can act on without reading the message. The CLI and MCP publish
|
|
227
|
-
* these; the app throws them where it knows the cause.
|
|
228
|
-
*/
|
|
229
|
-
const errorCodes = [
|
|
230
|
-
"invalid_input",
|
|
231
|
-
"not_running",
|
|
232
|
-
"not_answering",
|
|
233
|
-
"unknown_tab",
|
|
234
|
-
"tab_not_ready",
|
|
235
|
-
"unknown_design",
|
|
236
|
-
"unknown_node",
|
|
237
|
-
"ambiguous_reference",
|
|
238
|
-
"unknown_state",
|
|
239
|
-
"unknown_mode",
|
|
240
|
-
"unknown_viewport",
|
|
241
|
-
"unknown_component",
|
|
242
|
-
"unknown_icon_set",
|
|
243
|
-
"unknown_edit_id",
|
|
244
|
-
"conflict",
|
|
245
|
-
"execution_failed",
|
|
246
|
-
"app_error"
|
|
247
|
-
];
|
|
248
|
-
/** An error with a stable code, the field it's about and what to use instead. */
|
|
249
|
-
var CodedError = class extends Error {
|
|
250
|
-
details;
|
|
251
|
-
constructor(message, details) {
|
|
252
|
-
super(message);
|
|
253
|
-
this.name = "CodedError";
|
|
254
|
-
this.details = {
|
|
255
|
-
...details,
|
|
256
|
-
retryable: details.retryable ?? false
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
/** An unknown reference, naming the valid ones closest to what was asked for, up to `alternativeLimit`. */
|
|
261
|
-
function unknownReference(code, message, reference, candidates, field) {
|
|
262
|
-
return new CodedError(message, {
|
|
263
|
-
code,
|
|
264
|
-
...field === void 0 ? {} : { field },
|
|
265
|
-
alternatives: closest(reference, candidates)
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Candidates sharing text with the reference first, ignoring case, then the rest by how few single-character edits
|
|
270
|
-
* away they are, so a typo's intended id makes the cut; ties keep their own order. All of them when they fit.
|
|
271
|
-
*/
|
|
272
|
-
function closest(reference, candidates, limit = 20) {
|
|
273
|
-
const unique = [...new Set(candidates)];
|
|
274
|
-
if (unique.length <= limit) return unique;
|
|
275
|
-
const wanted = reference.toLowerCase();
|
|
276
|
-
const ranked = unique.map((candidate, index) => {
|
|
277
|
-
const lower = candidate.toLowerCase();
|
|
278
|
-
const near = wanted.length > 0 && (lower.includes(wanted) || wanted.includes(lower));
|
|
279
|
-
return {
|
|
280
|
-
candidate,
|
|
281
|
-
index,
|
|
282
|
-
near: near ? 0 : 1,
|
|
283
|
-
distance: near ? 0 : editDistance(wanted, lower)
|
|
284
|
-
};
|
|
285
|
-
});
|
|
286
|
-
ranked.sort((a, b) => a.near - b.near || a.distance - b.distance || a.index - b.index);
|
|
287
|
-
return ranked.slice(0, limit).map(({ candidate }) => candidate);
|
|
288
|
-
}
|
|
289
|
-
/** How many single-character insertions, deletions or substitutions turn one string into the other. */
|
|
290
|
-
function editDistance(a, b) {
|
|
291
|
-
let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
292
|
-
for (let i = 1; i <= a.length; i++) {
|
|
293
|
-
const row = [i];
|
|
294
|
-
for (let j = 1; j <= b.length; j++) row[j] = Math.min(previous[j] + 1, row[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
295
|
-
previous = row;
|
|
296
|
-
}
|
|
297
|
-
return previous[b.length];
|
|
298
|
-
}
|
|
299
|
-
/** A thrown value's details, when it carries them. */
|
|
300
|
-
function errorDetails(error) {
|
|
301
|
-
return error instanceof CodedError ? error.details : void 0;
|
|
302
|
-
}
|
|
303
|
-
/** Details that came from another process, checked before they're trusted. Anything malformed reads as none. */
|
|
304
|
-
function readErrorDetails(value) {
|
|
305
|
-
if (!value || typeof value !== "object") return void 0;
|
|
306
|
-
const code = "code" in value ? value.code : void 0;
|
|
307
|
-
const known = errorCodes.find((candidate) => candidate === code);
|
|
308
|
-
if (!known) return void 0;
|
|
309
|
-
const retryable = "retryable" in value && value.retryable === true;
|
|
310
|
-
const field = "field" in value && typeof value.field === "string" && value.field ? value.field : void 0;
|
|
311
|
-
const alternatives = "alternatives" in value && Array.isArray(value.alternatives) ? value.alternatives.filter((item) => typeof item === "string").slice(0, 20) : void 0;
|
|
312
|
-
return {
|
|
313
|
-
code: known,
|
|
314
|
-
retryable,
|
|
315
|
-
...field === void 0 ? {} : { field },
|
|
316
|
-
...alternatives === void 0 ? {} : { alternatives }
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
233
|
// src/caller.ts
|
|
320
234
|
/** Declared caller names are displayed verbatim apart from surrounding whitespace. */
|
|
321
235
|
function declaredName(value) {
|
|
322
|
-
return
|
|
236
|
+
return nonBlankString.safeParse(value).data;
|
|
323
237
|
}
|
|
324
238
|
/** Reserve the session suffix inside the desktop boundary's 80-character caller-id limit. */
|
|
325
239
|
function sessionCallerId(name, session) {
|
|
@@ -405,10 +319,13 @@ async function runningCopies() {
|
|
|
405
319
|
report
|
|
406
320
|
}] : [];
|
|
407
321
|
}))).flat();
|
|
408
|
-
if (entries.length > 0 && copies.length === 0)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
322
|
+
if (entries.length > 0 && copies.length === 0) {
|
|
323
|
+
const where = entries.map((entry) => entry.url).join(", ");
|
|
324
|
+
throw new CodedError(`Parity isn't answering at ${where}. Is the app still running?`, {
|
|
325
|
+
code: "not_answering",
|
|
326
|
+
retryable: true
|
|
327
|
+
});
|
|
328
|
+
}
|
|
412
329
|
if (copies.length === 0) throw new CodedError(`Parity isn't running (nothing in ${registry.path}). Start the app, then try again.`, {
|
|
413
330
|
code: "not_running",
|
|
414
331
|
retryable: true
|
|
@@ -468,10 +385,45 @@ function describeExec(result) {
|
|
|
468
385
|
if (result.output.length > 0) lines.push("", "Output:", ...result.output);
|
|
469
386
|
const created = Object.entries(result.created);
|
|
470
387
|
if (created.length > 0) lines.push("", "Created:", ...created.map(([name, id]) => ` ${name} = ${id}`));
|
|
471
|
-
|
|
388
|
+
lines.push(...describeWarnings(result));
|
|
389
|
+
if (result.discarded?.length) {
|
|
390
|
+
lines.push("", "Discarded by Replace (reapply what you still want to the new node):");
|
|
391
|
+
for (const { design, replaced, replacement, nodes } of result.discarded) {
|
|
392
|
+
lines.push(` ${design}/${replaced}, now ${replacement}:`);
|
|
393
|
+
for (const node of nodes) {
|
|
394
|
+
const where = [
|
|
395
|
+
node.viewports && `viewports ${node.viewports.join(", ")}`,
|
|
396
|
+
node.states && `states ${node.states.join(", ")}`,
|
|
397
|
+
node.variants && `variants ${node.variants.join(", ")}`
|
|
398
|
+
].filter(Boolean);
|
|
399
|
+
lines.push(` ${node.name ? `${node.name} (${node.id})` : node.id}: ${where.join("; ")}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
472
403
|
if (result.screenshots.length > 0) lines.push("", "Screenshots:", ...result.screenshots.map((shot) => ` ${shot.design} / ${shot.state} / ${shot.mode}: ${shot.path}`));
|
|
473
404
|
return lines.join("\n");
|
|
474
405
|
}
|
|
406
|
+
const warningHeadings = {
|
|
407
|
+
error: "Render failures (these frames weren't checked):",
|
|
408
|
+
warning: "Warnings (fix these next):",
|
|
409
|
+
suggestion: "Suggestions:"
|
|
410
|
+
};
|
|
411
|
+
/**
|
|
412
|
+
* The warnings under a heading for each severity, so a failed render doesn't read like a naming tip. The lines are
|
|
413
|
+
* the app's, one per group in the groups' order; an app older than PAR-460 sends no groups, so they're all warnings.
|
|
414
|
+
*/
|
|
415
|
+
function describeWarnings(result) {
|
|
416
|
+
const lines = [];
|
|
417
|
+
for (const severity of [
|
|
418
|
+
"error",
|
|
419
|
+
"warning",
|
|
420
|
+
"suggestion"
|
|
421
|
+
]) {
|
|
422
|
+
const matching = result.warnings.filter((_, index) => (result.warningGroups?.[index]?.severity ?? "warning") === severity);
|
|
423
|
+
if (matching.length > 0) lines.push("", warningHeadings[severity], ...matching.map((warning) => ` ${warning}`));
|
|
424
|
+
}
|
|
425
|
+
return lines;
|
|
426
|
+
}
|
|
475
427
|
/** Where a tab's edits stand: the revision, what's applied and undoable, what's on disk, and what failed to write. */
|
|
476
428
|
function describeStatus(result) {
|
|
477
429
|
const { tab, revision, unsaved, undo, redo, save, errors } = result;
|
|
@@ -563,6 +515,7 @@ function describeAccessibility(result) {
|
|
|
563
515
|
name: finding.name ?? "The design",
|
|
564
516
|
nodes: finding.node ? [finding.node] : [],
|
|
565
517
|
detail: finding.detail,
|
|
518
|
+
...fixLine(finding) ? { fix: fixLine(finding) } : {},
|
|
566
519
|
frames: []
|
|
567
520
|
};
|
|
568
521
|
layer.frames.push({
|
|
@@ -586,6 +539,7 @@ function describeAccessibility(result) {
|
|
|
586
539
|
const where = layer.frames.length === frames && frames > 1 ? "every frame" : frameList(layer.frames);
|
|
587
540
|
const ids = layer.nodes.length > 0 ? ` (${layer.nodes.join(", ")})` : "";
|
|
588
541
|
lines.push(` ${layer.name}${ids}, in ${where}: ${layer.detail}`);
|
|
542
|
+
if (layer.fix) lines.push(` ${layer.fix}`);
|
|
589
543
|
}
|
|
590
544
|
lines.push(` How to fix it: ${helpUrl}`);
|
|
591
545
|
}
|
|
@@ -593,12 +547,19 @@ function describeAccessibility(result) {
|
|
|
593
547
|
lines.push("", "Automated checks find some issues, not all. Try the design with a keyboard and a screen reader too.");
|
|
594
548
|
return lines.join("\n");
|
|
595
549
|
}
|
|
550
|
+
/** A contrast failure's fix in a sentence, naming the style to set so it can be passed to execute. */
|
|
551
|
+
function fixLine(finding) {
|
|
552
|
+
const fix = finding.contrast?.fix;
|
|
553
|
+
if (fix === "none") return "Fix: none of the project's color tokens pass on this background; change the background.";
|
|
554
|
+
return fix && `Fix: set style.foreground to "${fix.token}", which reaches ${fix.ratio}:1 in every frame checked.`;
|
|
555
|
+
}
|
|
596
556
|
/** A readable account of the layout problems in a design's frames, each once with the frames it's in. */
|
|
597
557
|
function describeProblems(result) {
|
|
598
558
|
const { design, problems, frames } = result;
|
|
599
559
|
const checked = `${design.name} (${design.id}), ${frames === 1 ? "1 frame" : `${frames} frames`}`;
|
|
600
560
|
const grouped = /* @__PURE__ */ new Map();
|
|
601
|
-
for (const { problem, node, state, mode } of problems) {
|
|
561
|
+
for (const { problem, node: id, name, state, mode } of problems) {
|
|
562
|
+
const node = id && name ? `${name} (${id})` : id;
|
|
602
563
|
const key = `${node ?? ""}\n${problem}`;
|
|
603
564
|
const entry = grouped.get(key) ?? {
|
|
604
565
|
problem,
|
|
@@ -671,19 +632,47 @@ function describeComponents({ components, iconLibraries = [], leftOut = [], draw
|
|
|
671
632
|
}
|
|
672
633
|
return lines.join("\n").trimEnd();
|
|
673
634
|
}
|
|
674
|
-
/** A component's props, with each variant's options and default. */
|
|
635
|
+
/** A component's props, with each variant's options and default, then what it needs around it and holds. */
|
|
675
636
|
function describeProps(result) {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
const
|
|
682
|
-
|
|
637
|
+
const lines = [];
|
|
638
|
+
if (!result.known) lines.push(`${result.component}'s props haven't been read from its source yet. Try again in a moment.`);
|
|
639
|
+
else {
|
|
640
|
+
const imported = result.import && result.import !== result.path ? `, imported from "${result.import}"` : "";
|
|
641
|
+
lines.push(`${result.component}, from ${result.path}${imported}:`);
|
|
642
|
+
for (const prop of result.props) {
|
|
643
|
+
const options = prop.options ? `: ${prop.options.map((option) => JSON.stringify(option)).join(", ")}` : "";
|
|
644
|
+
const fallback = prop.default === void 0 ? "" : `, default ${JSON.stringify(prop.default)}`;
|
|
645
|
+
lines.push(` ${prop.name} (${prop.kind}${prop.required ? ", required" : ""})${options}${fallback}`);
|
|
646
|
+
}
|
|
647
|
+
if (result.props.length === 0) lines.push(" No props of its own. --all lists the attributes it passes on.");
|
|
683
648
|
}
|
|
684
|
-
if (result.
|
|
649
|
+
if (result.composition) lines.push("", ...describeComposition(result.composition));
|
|
685
650
|
return lines.join("\n");
|
|
686
651
|
}
|
|
652
|
+
const roles = {
|
|
653
|
+
provider: "a provider, which draws nothing and gives the parts inside it their context",
|
|
654
|
+
wrapper: "a wrapper, which other parts go inside",
|
|
655
|
+
component: "a component"
|
|
656
|
+
};
|
|
657
|
+
const exampleStatus = {
|
|
658
|
+
drawn: "drawn on the canvas",
|
|
659
|
+
failing: "failing on the canvas",
|
|
660
|
+
untried: "not drawn yet"
|
|
661
|
+
};
|
|
662
|
+
/** What a component needs around it, what it holds, its slots, and its example as JSON for `Insert`. */
|
|
663
|
+
function describeComposition({ role, parents, holds, slots, example }) {
|
|
664
|
+
const lines = [`It's ${roles[role]}.`];
|
|
665
|
+
if (parents.status === "listed") {
|
|
666
|
+
const chain = parents.chain.map((parent) => parent.optional ? `${parent.component} (optional)` : parent.component);
|
|
667
|
+
lines.push(`Goes inside, outermost first: ${chain.join(" > ")}${parents.from ? ` (from its ${parents.from})` : ""}`);
|
|
668
|
+
} else if (parents.status === "none") lines.push("Needs nothing around it.");
|
|
669
|
+
else lines.push(`What it needs around it is unknown: ${parents.reason}`);
|
|
670
|
+
if (holds.length > 0) lines.push(`Holds: ${holds.join(", ")}`);
|
|
671
|
+
if (slots.length > 0) lines.push(`Slots: ${slots.map((slot) => `${slot.key} (${slot.kind})`).join(", ")}`);
|
|
672
|
+
const reason = example.reason ? `: ${example.reason}` : "";
|
|
673
|
+
lines.push(`Example for Insert, ${exampleStatus[example.status]}${reason}:`, ` ${JSON.stringify(example.node)}`);
|
|
674
|
+
return lines;
|
|
675
|
+
}
|
|
687
676
|
/** Frames as state and mode ids, naming a mode once when they share it: "default, error in desktop-light". */
|
|
688
677
|
function frameList(frames) {
|
|
689
678
|
const modes = [...new Set(frames.map((frame) => frame.mode))];
|
|
@@ -721,4 +710,4 @@ function scriptPath(url) {
|
|
|
721
710
|
return url || "an unknown script";
|
|
722
711
|
}
|
|
723
712
|
}
|
|
724
|
-
export {
|
|
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 };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
/**
|
|
2
3
|
* Where running copies of Parity say how to reach them (PAR-514): one entry per copy, named by its main process, in
|
|
3
4
|
* `~/.parity/agents`. Each copy writes and removes only its own. The app and the CLI both go through here, so the
|
|
@@ -63,15 +64,42 @@ interface Screenshot {
|
|
|
63
64
|
state: string;
|
|
64
65
|
mode: string;
|
|
65
66
|
}
|
|
67
|
+
/** What one `Replace` threw away: the patches keyed to the old node and what was inside it (PAR-455). */
|
|
68
|
+
interface Discarded {
|
|
69
|
+
design: string;
|
|
70
|
+
replaced: string;
|
|
71
|
+
replacement: string;
|
|
72
|
+
nodes: {
|
|
73
|
+
id: string;
|
|
74
|
+
name?: string;
|
|
75
|
+
viewports?: string[];
|
|
76
|
+
states?: string[];
|
|
77
|
+
variants?: string[];
|
|
78
|
+
}[];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Findings with the same category and message, once, with every place they were found (PAR-460). The app's
|
|
82
|
+
* `warnings` are these as lines, in the same order.
|
|
83
|
+
*/
|
|
84
|
+
interface WarningGroup {
|
|
85
|
+
category: string;
|
|
86
|
+
severity: "error" | "warning" | "suggestion";
|
|
87
|
+
message: string;
|
|
88
|
+
count: number;
|
|
89
|
+
at: string[];
|
|
90
|
+
}
|
|
66
91
|
/** The canonical result. Structured output is this without the screenshots' image data. */
|
|
67
92
|
interface ExecResponse {
|
|
68
93
|
ok: boolean;
|
|
69
94
|
applied: boolean;
|
|
70
95
|
output: string[];
|
|
71
96
|
warnings: string[];
|
|
97
|
+
warningGroups?: WarningGroup[];
|
|
72
98
|
/** Set when the call failed, with the editId to retry a snippet by. */
|
|
73
99
|
error?: OperationError;
|
|
74
100
|
created: Record<string, string>;
|
|
101
|
+
/** Set when a Replace threw away responsive, state or variant patches. */
|
|
102
|
+
discarded?: Discarded[];
|
|
75
103
|
screenshots: Screenshot[];
|
|
76
104
|
}
|
|
77
105
|
/**
|
|
@@ -225,6 +253,13 @@ export interface AccessibilityFinding {
|
|
|
225
253
|
node?: string;
|
|
226
254
|
name?: string;
|
|
227
255
|
detail: string;
|
|
256
|
+
/** For a contrast failure: the token to color the layer's text with, or `"none"` when none of the project's pass. */
|
|
257
|
+
contrast?: {
|
|
258
|
+
fix?: {
|
|
259
|
+
token: string;
|
|
260
|
+
ratio: number;
|
|
261
|
+
} | "none";
|
|
262
|
+
};
|
|
228
263
|
}
|
|
229
264
|
export interface AccessibilityResponse {
|
|
230
265
|
tab: {
|
|
@@ -253,15 +288,41 @@ export interface ProblemsResponse {
|
|
|
253
288
|
id: string;
|
|
254
289
|
name: string;
|
|
255
290
|
};
|
|
256
|
-
problems:
|
|
257
|
-
state: string;
|
|
258
|
-
mode: string;
|
|
259
|
-
problem: string;
|
|
260
|
-
node?: string;
|
|
261
|
-
}[];
|
|
291
|
+
problems: Problem[];
|
|
262
292
|
frames: number;
|
|
263
293
|
warnings: string[];
|
|
264
294
|
}
|
|
295
|
+
/** A box in a frame, in CSS pixels from its top left corner. */
|
|
296
|
+
export interface FrameBox {
|
|
297
|
+
x: number;
|
|
298
|
+
y: number;
|
|
299
|
+
width: number;
|
|
300
|
+
height: number;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* One problem in one frame. Layout problems (`frame-overflow`, `clipped`) name the node, what it overflows (the node
|
|
304
|
+
* that clips it, or the frame, which has none), both boxes, and how far past each edge it reaches.
|
|
305
|
+
*/
|
|
306
|
+
export interface Problem {
|
|
307
|
+
state: string;
|
|
308
|
+
mode: string;
|
|
309
|
+
kind?: "render-error" | "unknown-component" | "content-wider" | "frame-overflow" | "clipped";
|
|
310
|
+
problem: string;
|
|
311
|
+
node?: string;
|
|
312
|
+
name?: string;
|
|
313
|
+
box?: FrameBox;
|
|
314
|
+
container?: {
|
|
315
|
+
node?: string;
|
|
316
|
+
name?: string;
|
|
317
|
+
box: FrameBox;
|
|
318
|
+
};
|
|
319
|
+
overflow?: {
|
|
320
|
+
left?: number;
|
|
321
|
+
right?: number;
|
|
322
|
+
top?: number;
|
|
323
|
+
bottom?: number;
|
|
324
|
+
};
|
|
325
|
+
}
|
|
265
326
|
/** A readable account of the layout problems in a design's frames, each once with the frames it's in. */
|
|
266
327
|
export declare function describeProblems(result: ProblemsResponse): string;
|
|
267
328
|
export interface TokenInfo {
|
|
@@ -325,8 +386,38 @@ export interface PropsResponse {
|
|
|
325
386
|
default?: string | number | boolean | null;
|
|
326
387
|
required?: boolean;
|
|
327
388
|
}[];
|
|
389
|
+
/** What it needs around it and holds inside it, read from its library's metadata (PAR-454). */
|
|
390
|
+
composition?: Composition;
|
|
391
|
+
}
|
|
392
|
+
/** A component's composition, as `@parity-app/editor`'s `componentComposition` reads it. */
|
|
393
|
+
export interface Composition {
|
|
394
|
+
role: "provider" | "wrapper" | "component";
|
|
395
|
+
parents: {
|
|
396
|
+
status: "listed";
|
|
397
|
+
chain: {
|
|
398
|
+
component: string;
|
|
399
|
+
optional?: true;
|
|
400
|
+
}[];
|
|
401
|
+
from?: string;
|
|
402
|
+
} | {
|
|
403
|
+
status: "none";
|
|
404
|
+
} | {
|
|
405
|
+
status: "unknown";
|
|
406
|
+
reason: string;
|
|
407
|
+
};
|
|
408
|
+
holds: string[];
|
|
409
|
+
slots: {
|
|
410
|
+
name: string;
|
|
411
|
+
key: string;
|
|
412
|
+
kind: "children" | "element" | "render";
|
|
413
|
+
}[];
|
|
414
|
+
example: {
|
|
415
|
+
node: unknown;
|
|
416
|
+
status: "drawn" | "failing" | "untried";
|
|
417
|
+
reason?: string;
|
|
418
|
+
};
|
|
328
419
|
}
|
|
329
|
-
/** A component's props, with each variant's options and default. */
|
|
420
|
+
/** A component's props, with each variant's options and default, then what it needs around it and holds. */
|
|
330
421
|
export declare function describeProps(result: PropsResponse): string;
|
|
331
422
|
export interface ConsoleEntry {
|
|
332
423
|
level: "info" | "warning" | "error" | "debug";
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as call, n as describeAccessibility, r as describeComponents, i as describeConsole, a as describeExec, o as describeModes, s as describeProblems, c as describeProps, l as describeStatus, u as describeTokens, d as registry, f as setCaller } from "./client-
|
|
1
|
+
export { t as call, n as describeAccessibility, r as describeComponents, i as describeConsole, a as describeExec, o as describeModes, s as describeProblems, c as describeProps, l as describeStatus, u as describeTokens, d as registry, f as setCaller } from "./client-CoW-KcnT.mjs";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// ../core/src/errors.ts
|
|
3
|
+
/**
|
|
4
|
+
* Why an agent's call failed, as a stable code it can act on without reading the message. The CLI and MCP publish
|
|
5
|
+
* these; the app throws them where it knows the cause.
|
|
6
|
+
*/
|
|
7
|
+
const errorCodes = [
|
|
8
|
+
"invalid_input",
|
|
9
|
+
"not_running",
|
|
10
|
+
"not_answering",
|
|
11
|
+
"unknown_tab",
|
|
12
|
+
"tab_not_ready",
|
|
13
|
+
"unknown_design",
|
|
14
|
+
"unknown_node",
|
|
15
|
+
"ambiguous_reference",
|
|
16
|
+
"unknown_state",
|
|
17
|
+
"unknown_mode",
|
|
18
|
+
"unknown_viewport",
|
|
19
|
+
"unknown_component",
|
|
20
|
+
"unknown_icon_set",
|
|
21
|
+
"unknown_edit_id",
|
|
22
|
+
"conflict",
|
|
23
|
+
"execution_failed",
|
|
24
|
+
"app_error"
|
|
25
|
+
];
|
|
26
|
+
/** Details as another process sends them. Each field that's malformed falls back on its own; only the code must hold. */
|
|
27
|
+
const errorDetailsSchema = z.object({
|
|
28
|
+
code: z.enum(errorCodes),
|
|
29
|
+
/** Whether sending the same request again, unchanged, can succeed. */
|
|
30
|
+
retryable: z.boolean().catch(false),
|
|
31
|
+
/** The request field at fault, as a path: `design`, `state`, `edits/0/find`. */
|
|
32
|
+
field: z.string().min(1).optional().catch(void 0),
|
|
33
|
+
/** Valid references to use instead of an unknown or ambiguous one, at most `alternativeLimit` of them. */
|
|
34
|
+
alternatives: z.array(z.unknown()).transform((items) => items.filter((item) => typeof item === "string").slice(0, 20)).optional().catch(void 0)
|
|
35
|
+
});
|
|
36
|
+
/** An error with a stable code, the field it's about and what to use instead. */
|
|
37
|
+
var CodedError = class extends Error {
|
|
38
|
+
details;
|
|
39
|
+
constructor(message, details) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "CodedError";
|
|
42
|
+
this.details = {
|
|
43
|
+
...details,
|
|
44
|
+
retryable: details.retryable ?? false
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/** An unknown reference, naming the valid ones closest to what was asked for, up to `alternativeLimit`. */
|
|
49
|
+
function unknownReference(code, message, reference, candidates, field) {
|
|
50
|
+
return new CodedError(message, {
|
|
51
|
+
code,
|
|
52
|
+
...field === void 0 ? {} : { field },
|
|
53
|
+
alternatives: closest(reference, candidates)
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Candidates sharing text with the reference first, ignoring case, then the rest by how few single-character edits
|
|
58
|
+
* away they are, so a typo's intended id makes the cut; ties keep their own order. All of them when they fit.
|
|
59
|
+
*/
|
|
60
|
+
function closest(reference, candidates, limit = 20) {
|
|
61
|
+
const unique = [...new Set(candidates)];
|
|
62
|
+
if (unique.length <= limit) return unique;
|
|
63
|
+
const wanted = reference.toLowerCase();
|
|
64
|
+
const ranked = unique.map((candidate, index) => {
|
|
65
|
+
const lower = candidate.toLowerCase();
|
|
66
|
+
const near = wanted.length > 0 && (lower.includes(wanted) || wanted.includes(lower));
|
|
67
|
+
return {
|
|
68
|
+
candidate,
|
|
69
|
+
index,
|
|
70
|
+
near: near ? 0 : 1,
|
|
71
|
+
distance: near ? 0 : editDistance(wanted, lower)
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
ranked.sort((a, b) => a.near - b.near || a.distance - b.distance || a.index - b.index);
|
|
75
|
+
return ranked.slice(0, limit).map(({ candidate }) => candidate);
|
|
76
|
+
}
|
|
77
|
+
/** How many single-character insertions, deletions or substitutions turn one string into the other. */
|
|
78
|
+
function editDistance(a, b) {
|
|
79
|
+
let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
80
|
+
for (let i = 1; i <= a.length; i++) {
|
|
81
|
+
const row = [i];
|
|
82
|
+
for (let j = 1; j <= b.length; j++) row[j] = Math.min(previous[j] + 1, row[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
83
|
+
previous = row;
|
|
84
|
+
}
|
|
85
|
+
return previous[b.length];
|
|
86
|
+
}
|
|
87
|
+
/** A thrown value's details, when it carries them. */
|
|
88
|
+
function errorDetails(error) {
|
|
89
|
+
return error instanceof CodedError ? error.details : void 0;
|
|
90
|
+
}
|
|
91
|
+
/** Details that came from another process, checked before they're trusted. Anything malformed reads as none. */
|
|
92
|
+
function readErrorDetails(value) {
|
|
93
|
+
return errorDetailsSchema.safeParse(value).data;
|
|
94
|
+
}
|
|
95
|
+
z.record(z.string(), z.unknown());
|
|
96
|
+
/** Text with something besides whitespace, trimmed. */
|
|
97
|
+
const nonBlankString = z.string().trim().min(1, "can't be blank");
|
|
98
|
+
z.number();
|
|
99
|
+
/** JSON text holding what `schema` checks. Text that isn't JSON fails as `expected JSON`. */
|
|
100
|
+
function jsonText(schema) {
|
|
101
|
+
return z.string().transform((text, context) => {
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(text);
|
|
104
|
+
} catch {
|
|
105
|
+
context.addIssue({
|
|
106
|
+
code: "custom",
|
|
107
|
+
message: "expected JSON"
|
|
108
|
+
});
|
|
109
|
+
return z.NEVER;
|
|
110
|
+
}
|
|
111
|
+
}).pipe(schema);
|
|
112
|
+
}
|
|
113
|
+
export { errorCodes as a, unknownReference as c, closest as i, nonBlankString as n, errorDetails as o, CodedError as r, readErrorDetails as s, jsonText as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { p as parseLibraryManifest, u as libraryManifestExtension } from "./src-
|
|
1
|
+
import { p as parseLibraryManifest, u as libraryManifestExtension } from "./src-C4faIpux.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { readFile, readdir } from "node:fs/promises";
|
|
4
4
|
import { dirname, join } from "node:path";
|