@parity-app/cli 0.5.0 → 0.6.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/dist/bin.mjs +29 -9
- package/dist/{client-DgpoM3e7.mjs → client-CoW-KcnT.mjs} +47 -130
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +1 -1
- package/dist/schema-BpGSCLEu.mjs +113 -0
- package/dist/{src-D85OVBQL.mjs → src-5CrSA_RX.mjs} +1 -1
- package/dist/{src-BryimTPw.mjs → src-C4faIpux.mjs} +10 -0
- package/dist/{src-BwTWa2_Y.mjs → src-Cr8pfFJE.mjs} +1 -1
- package/dist/{src-CpoDOrn3.mjs → src-Zm_PYFFH.mjs} +2 -2
- package/dist/{src-CqmC2-Em.mjs → src-p0vFxRBz.mjs} +3 -3
- package/package.json +3 -2
- package/skills/SKILL.md +3 -2
- package/skills/execute.md +46 -7
- package/skills/guide/checking.md +2 -1
package/dist/bin.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { a as errorCodes, i as closest, o as errorDetails, r as CodedError, s as readErrorDetails } from "./schema-BpGSCLEu.mjs";
|
|
3
|
+
import { a as describeExec, c as describeProps, f as setCaller, i as describeConsole, l as describeStatus, n as describeAccessibility, o as describeModes, p as mcpCallerName, r as describeComponents, s as describeProblems, t as call, u as describeTokens } from "./client-CoW-KcnT.mjs";
|
|
3
4
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
4
5
|
import { dirname, join, normalize, relative, resolve, sep } from "node:path";
|
|
5
6
|
import { parseArgs } from "node:util";
|
|
@@ -179,7 +180,7 @@ const warningGroupsSchema = {
|
|
|
179
180
|
properties: {
|
|
180
181
|
category: {
|
|
181
182
|
type: "string",
|
|
182
|
-
description: "Stable: render_failed, unknown_component, unknown_component_design, unknown_prop, invalid_option, not_a_token, not_a_length_token, overlap, kind_changed or unnamed_layout."
|
|
183
|
+
description: "Stable: render_failed, unknown_component, unknown_component_design, unknown_prop, invalid_option, not_a_token, not_a_length_token, overlap, title_clearance, canvas_overlaps, kind_changed or unnamed_layout. overlap and title_clearance cover boards this call created, changed or moved: overlap when two boards collide, title_clearance when they're clear but one's title covers the other. canvas_overlaps counts the rest of the canvas's; call Overlaps() to check every board."
|
|
183
184
|
},
|
|
184
185
|
severity: {
|
|
185
186
|
type: "string",
|
|
@@ -386,8 +387,8 @@ const executeOperation = {
|
|
|
386
387
|
*/
|
|
387
388
|
async function runCheck(library, options) {
|
|
388
389
|
if (!library) throw new Error("parity lib check <library> [--tab t] [--mode m]");
|
|
389
|
-
const { applyLibraryEdits, componentNode, generateId, librarySource, parseLibraryEdits, withLibraryContext } = await import("./src-
|
|
390
|
-
const { registryDir, registryEntries } = await import("./src-
|
|
390
|
+
const { applyLibraryEdits, componentNode, generateId, librarySource, parseLibraryEdits, withLibraryContext } = await import("./src-p0vFxRBz.mjs");
|
|
391
|
+
const { registryDir, registryEntries } = await import("./src-Cr8pfFJE.mjs");
|
|
391
392
|
const entry = (await registryEntries()).find((item) => item.manifest.id === library);
|
|
392
393
|
if (!entry) throw new Error(`The registry has no library "${library}"`);
|
|
393
394
|
let manifest = entry.manifest;
|
|
@@ -515,8 +516,8 @@ function describeCheck(report) {
|
|
|
515
516
|
* because it reads the project's files and its own copy of TypeScript.
|
|
516
517
|
*/
|
|
517
518
|
async function runLib(packageName, options) {
|
|
518
|
-
const { appManifestPath, generateLibrary, manifestText, mergeManifest, reviseLibrary } = await import("./src-
|
|
519
|
-
const { covers, registryDir, registryEntries } = await import("./src-
|
|
519
|
+
const { appManifestPath, generateLibrary, manifestText, mergeManifest, reviseLibrary } = await import("./src-Zm_PYFFH.mjs");
|
|
520
|
+
const { covers, registryDir, registryEntries } = await import("./src-Cr8pfFJE.mjs");
|
|
520
521
|
const spec = packageName === void 0 ? void 0 : packageSpec(packageName);
|
|
521
522
|
let root = resolve(options.root ?? ".");
|
|
522
523
|
if (spec && (spec.version !== void 0 || options.registry && !options.root)) {
|
|
@@ -527,7 +528,7 @@ async function runLib(packageName, options) {
|
|
|
527
528
|
console.error(`Installing ${spec.name} ${version} on its own…`);
|
|
528
529
|
root = await installedVersion(spec.name, version);
|
|
529
530
|
}
|
|
530
|
-
const { parseLibraryEdits } = await import("./src-
|
|
531
|
+
const { parseLibraryEdits } = await import("./src-Zm_PYFFH.mjs");
|
|
531
532
|
const trialEdits = options.edits ? parseLibraryEdits(JSON.parse(await readFile(resolve(options.edits), "utf8"))) : void 0;
|
|
532
533
|
const recordedId = options.registry && spec ? options.id ?? (await registryEntries()).find((entry) => entry.manifest.package?.name === spec.name)?.manifest.id : void 0;
|
|
533
534
|
const appErrors = recordedId ? await readAppErrors(registryDir(), recordedId) : void 0;
|
|
@@ -921,10 +922,29 @@ const stateSchema = object({
|
|
|
921
922
|
},
|
|
922
923
|
overlaps: {
|
|
923
924
|
type: "array",
|
|
925
|
+
description: "Every pair of boards on the canvas that's too close, as execute's Overlaps() lists them. A pair with titleOnly doesn't collide: the lower board's title covers the upper one.",
|
|
924
926
|
items: object({
|
|
925
927
|
designs: strings,
|
|
926
|
-
names: strings
|
|
927
|
-
|
|
928
|
+
names: strings,
|
|
929
|
+
width: {
|
|
930
|
+
...number,
|
|
931
|
+
description: "How far they share, title band included."
|
|
932
|
+
},
|
|
933
|
+
height: number,
|
|
934
|
+
titleOnly: {
|
|
935
|
+
...boolean_,
|
|
936
|
+
description: "Only the lower board's title covers the other; the boards are clear."
|
|
937
|
+
},
|
|
938
|
+
estimated: {
|
|
939
|
+
...boolean_,
|
|
940
|
+
description: "One isn't drawn yet, so its size is estimated from its frames. Absent: both measured."
|
|
941
|
+
}
|
|
942
|
+
}, [
|
|
943
|
+
"width",
|
|
944
|
+
"height",
|
|
945
|
+
"titleOnly",
|
|
946
|
+
"estimated"
|
|
947
|
+
])
|
|
928
948
|
},
|
|
929
949
|
modes: {
|
|
930
950
|
type: "array",
|
|
@@ -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
|
|
@@ -793,4 +710,4 @@ function scriptPath(url) {
|
|
|
793
710
|
return url || "an unknown script";
|
|
794
711
|
}
|
|
795
712
|
}
|
|
796
|
-
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
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";
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import "./schema-BpGSCLEu.mjs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
// ../core/src/adapters.ts
|
|
4
|
+
const packageVersions = z.record(z.string(), z.string());
|
|
5
|
+
z.looseObject({
|
|
6
|
+
name: z.string().optional(),
|
|
7
|
+
dependencies: packageVersions.optional(),
|
|
8
|
+
devDependencies: packageVersions.optional(),
|
|
9
|
+
scripts: z.record(z.string(), z.string()).optional()
|
|
10
|
+
});
|
|
1
11
|
// ../core/src/devices.ts
|
|
2
12
|
const deviceKinds = [
|
|
3
13
|
"phone",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as covers, n as registryDir, r as registryEntries } from "./src-
|
|
1
|
+
export { t as covers, n as registryDir, r as registryEntries } from "./src-5CrSA_RX.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as collectCustomProperties, a as applyLibraryEdits, c as isSampleElement, f as parseLibraryEdits, j as lengthToPixels, l as isSampleRender, m as renderSlotOf, p as parseLibraryManifest, s as isMadeValue, u as libraryManifestExtension } from "./src-
|
|
2
|
-
import "./src-
|
|
1
|
+
import { D as collectCustomProperties, a as applyLibraryEdits, c as isSampleElement, f as parseLibraryEdits, j as lengthToPixels, l as isSampleRender, m as renderSlotOf, p as parseLibraryManifest, s as isMadeValue, u as libraryManifestExtension } from "./src-C4faIpux.mjs";
|
|
2
|
+
import "./src-5CrSA_RX.mjs";
|
|
3
3
|
import { a as publishedVersions, n as packageFile, o as repositoryFiles, r as publishedSource } from "./published-CdXoUgfD.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { mkdir, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
export { a as applyLibraryEdits, t as componentNode, T as generateId, n as librarySource, f as parseLibraryEdits, i as withLibraryContext } from "./src-
|
|
3
|
-
import "./src-
|
|
1
|
+
import "./schema-BpGSCLEu.mjs";
|
|
2
|
+
export { a as applyLibraryEdits, t as componentNode, T as generateId, n as librarySource, f as parseLibraryEdits, i as withLibraryContext } from "./src-C4faIpux.mjs";
|
|
3
|
+
import "./src-Zm_PYFFH.mjs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parity-app/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Drive the Parity app from a terminal or an agent: read state, run edits, take screenshots, serve MCP.",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"semver": "^7.8.5",
|
|
47
|
-
"typescript": "6.0.3"
|
|
47
|
+
"typescript": "6.0.3",
|
|
48
|
+
"zod": "4.6.7"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@parity-app/agent": "0.0.0",
|
package/skills/SKILL.md
CHANGED
|
@@ -132,8 +132,9 @@ parity status --summary
|
|
|
132
132
|
Lorem ipsum hides layout problems.
|
|
133
133
|
- **Reuse what repeats.** When a row, card or list item appears more than twice, make it a component design with
|
|
134
134
|
`ExtractComponent`, so each copy is an instance with its own content.
|
|
135
|
-
- **Leave the canvas tidy.** New designs go into empty space (`FindEmptySpace`)
|
|
136
|
-
|
|
135
|
+
- **Leave the canvas tidy.** New designs go into empty space (`FindEmptySpace`). A call warns only about the boards it
|
|
136
|
+
moves; `Overlaps()` checks every board, telling collisions from a title too close (`titleOnly`) and boards sized from
|
|
137
|
+
their frames because the canvas hasn't drawn them (`estimated: true`).
|
|
137
138
|
|
|
138
139
|
## When something fails
|
|
139
140
|
|
package/skills/execute.md
CHANGED
|
@@ -89,7 +89,7 @@ SetTokens({ name: value | { theme: value } }): void
|
|
|
89
89
|
|
|
90
90
|
// Checking
|
|
91
91
|
Screenshot(design, { state?, mode?, scale? }?): number
|
|
92
|
-
Measure(design, { state?, mode? }?): Layout
|
|
92
|
+
Measure(design, { state?, mode? }?): Layout // one frame: one state id, one mode id
|
|
93
93
|
Problems(design, { state?, mode? }?): Problem[]
|
|
94
94
|
Accessibility(design, { state?, mode? }?): Finding[]
|
|
95
95
|
|
|
@@ -436,6 +436,21 @@ Print(
|
|
|
436
436
|
)
|
|
437
437
|
```
|
|
438
438
|
|
|
439
|
+
`Measure` takes one state id and one mode id, because it returns one frame's layout. A list, `"all"` or `"shown"` fails
|
|
440
|
+
with `invalid_input`, naming the ids it takes. To measure several frames, call it once for each; every layout says which
|
|
441
|
+
state and mode it drew:
|
|
442
|
+
|
|
443
|
+
```js
|
|
444
|
+
const signup = Designs().find((design) => design.id === "signup")
|
|
445
|
+
const layouts = []
|
|
446
|
+
for (const state of signup.states) {
|
|
447
|
+
for (const mode of [signup.showing.mode, ...signup.showing.beside]) {
|
|
448
|
+
layouts.push(await Measure("signup", { state: state.id, mode }))
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
for (const layout of layouts) Print(`${layout.state} in ${layout.mode}: ${layout.width} × ${layout.height}`)
|
|
452
|
+
```
|
|
453
|
+
|
|
439
454
|
`Accessibility` returns axe-core's findings per node and frame, with the rule, its impact and what's wrong.
|
|
440
455
|
|
|
441
456
|
These frames are drawn in the same window as the canvas's: as wide as the frame, and as tall as the design's fixed
|
|
@@ -444,12 +459,22 @@ size checks measure the same in `Measure` and `Screenshot` as on the canvas, eve
|
|
|
444
459
|
|
|
445
460
|
## The canvas
|
|
446
461
|
|
|
447
|
-
`FindEmptySpace({ width, height })` finds room for a
|
|
448
|
-
or next to the design named in `near`. `
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
462
|
+
`FindEmptySpace({ width, height })` finds room for a board that size, to the right of the others unless
|
|
463
|
+
`direction: "below"`, or next to the design named in `near`. Its default `padding` of 240 leaves room for the board's
|
|
464
|
+
title. Give it the size the board will be: `Designs()` has a drawn board's `width` and `height`.
|
|
465
|
+
|
|
466
|
+
`Overlaps()` checks every board on the canvas and lists each pair that's too close, in one of two kinds:
|
|
467
|
+
|
|
468
|
+
- **A collision:** the boards themselves overlap.
|
|
469
|
+
- **Title clearance:** `titleOnly: true`. The boards are clear, but the lower one starts less than 160 below the upper
|
|
470
|
+
one, so the title the canvas draws above it covers the upper board.
|
|
471
|
+
|
|
472
|
+
`width` and `height` are the area they share, title band included. Move one with `UpdateDesign(design, { x, y })`.
|
|
473
|
+
|
|
474
|
+
A board is as big as the canvas last drew it, so its size is measured. One it hasn't drawn yet is sized from its frames,
|
|
475
|
+
laid out as the canvas lays them out: each shown mode's frame size (`frames` on the design, else the mode's width),
|
|
476
|
+
companions shrunk to 640 wide, and a frame without a fixed height as tall as its window. A pair with an estimated board
|
|
477
|
+
says `estimated: true`; without it, both sizes are measured.
|
|
453
478
|
|
|
454
479
|
```js
|
|
455
480
|
// With the default modes: a 1280 main frame, the dark one beside it at half size and the 390 phone, 2505 wide in all.
|
|
@@ -459,6 +484,20 @@ CreateDesign({ name: "Settings", x: 2200, y: 0 })
|
|
|
459
484
|
Print(Overlaps(dashboard)) // Dashboard and Settings, 305 across, estimated: true
|
|
460
485
|
```
|
|
461
486
|
|
|
487
|
+
An execute call's own warnings cover only the boards it creates, changes or moves: `overlap` for a collision and
|
|
488
|
+
`title_clearance` for a title too close. Other crowded boards are counted in one `canvas_overlaps` suggestion, so a call
|
|
489
|
+
that changes nothing, or only reads, doesn't make a crowded canvas look clear. To audit the canvas, call `Overlaps()`:
|
|
490
|
+
|
|
491
|
+
```js
|
|
492
|
+
for (const { names, titleOnly, estimated } of Overlaps()) {
|
|
493
|
+
const kind = titleOnly ? "a title covers a board" : "the boards collide"
|
|
494
|
+
Print(`${names.join(" and ")}: ${kind}, ${estimated ? "estimated" : "measured"}`)
|
|
495
|
+
}
|
|
496
|
+
// Room below Settings for a board 1400 wide and 1000 tall, and its title above it.
|
|
497
|
+
const room = FindEmptySpace({ width: 1400, height: 1000, near: "settings", direction: "below" })
|
|
498
|
+
CreateDesign({ name: "Plans", x: room.x, y: room.y })
|
|
499
|
+
```
|
|
500
|
+
|
|
462
501
|
## Examples
|
|
463
502
|
|
|
464
503
|
A new screen, checked in every state and mode:
|
package/skills/guide/checking.md
CHANGED
|
@@ -21,7 +21,8 @@ small details.
|
|
|
21
21
|
box. A node that only spills past a parent that doesn't clip still shows, so it isn't listed.
|
|
22
22
|
- **Unknown components.** A name that isn't in `Components()`; it draws as a placeholder.
|
|
23
23
|
|
|
24
|
-
`Measure` gives every node's box in one frame, for checking alignment or sizes by numbers.
|
|
24
|
+
`Measure` gives every node's box in one frame, for checking alignment or sizes by numbers. It takes one state and one
|
|
25
|
+
mode; to compare frames, call it for each, as the execute skill shows.
|
|
25
26
|
|
|
26
27
|
## Accessibility
|
|
27
28
|
|