@parity-app/cli 0.1.0 → 0.3.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 +87 -1
- package/dist/bin.mjs +1132 -272
- 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 +17 -17
- 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/rhea.lib.par +20 -20
- package/dist/libraries/sera.lib.par +20 -20
- package/dist/libraries/vega.lib.par +20 -20
- package/dist/{src-8MhDV_sA.mjs → src-Aoey5Uq0.mjs} +498 -207
- package/dist/{src-DYi23Ypb.mjs → src-BGF7szJh.mjs} +1 -1
- package/dist/{src-FF8eY6EI.mjs → src-DZKi9702.mjs} +3 -2
- package/dist/{src-DXZqtm6z.mjs → src-eso2QXtH.mjs} +1 -1
- package/dist/{src-D9EMN48r.mjs → src-lVLSuOgh.mjs} +138 -38
- package/package.json +8 -6
- package/skills/SKILL.md +61 -14
- package/skills/document.md +11 -1
- package/skills/execute.md +82 -13
- package/skills/guide/states-and-modes.md +5 -3
- package/LICENSE +0 -21
- package/dist/client-DsTLQ2lT.mjs +0 -286
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Andrew Craswell
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/dist/client-DsTLQ2lT.mjs
DELETED
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { randomUUID } from "node:crypto";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
// src/caller.ts
|
|
6
|
-
/** Who is driving Parity, named the way people know it, for the agent's cursor on the canvas. */
|
|
7
|
-
/** Known clients, by the name they give MCP's `initialize`, most specific first. */
|
|
8
|
-
const clients = [
|
|
9
|
-
[/claude[-_ ]?code/i, "Claude Code"],
|
|
10
|
-
[/^claude/i, "Claude"],
|
|
11
|
-
[/cursor/i, "Cursor"],
|
|
12
|
-
[/codex/i, "Codex"],
|
|
13
|
-
[/gemini/i, "Gemini CLI"],
|
|
14
|
-
[/windsurf|codeium/i, "Windsurf"],
|
|
15
|
-
[/copilot|visual studio code|^vscode/i, "Copilot"],
|
|
16
|
-
[/zed/i, "Zed"],
|
|
17
|
-
[/cline/i, "Cline"],
|
|
18
|
-
[/goose/i, "Goose"]
|
|
19
|
-
];
|
|
20
|
-
/**
|
|
21
|
-
* An MCP client's name for people: a known agent's own name, else the title it gives, else its name with its
|
|
22
|
-
* separators made spaces and its words capitalized ("my-agent" becomes "My Agent").
|
|
23
|
-
*/
|
|
24
|
-
function mcpCallerName(clientInfo) {
|
|
25
|
-
const name = typeof clientInfo?.name === "string" ? clientInfo.name.trim() : "";
|
|
26
|
-
const title = typeof clientInfo?.title === "string" ? clientInfo.title.trim() : "";
|
|
27
|
-
for (const [pattern, known] of clients) if (pattern.test(name) || pattern.test(title)) return known;
|
|
28
|
-
if (title) return title;
|
|
29
|
-
if (!name) return void 0;
|
|
30
|
-
return name.replace(/[-_]+(mcp[-_]?)?client$/i, "").split(/[-_\s]+/).filter(Boolean).map((word) => word[0].toUpperCase() + word.slice(1)).join(" ");
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Who runs the CLI, from what agents put in their shell's environment. `PARITY_AGENT_NAME` names it outright; with
|
|
34
|
-
* no sign of an agent, it's someone at a terminal.
|
|
35
|
-
*/
|
|
36
|
-
function cliCallerName(env) {
|
|
37
|
-
const named = env.PARITY_AGENT_NAME?.trim();
|
|
38
|
-
if (named) return named;
|
|
39
|
-
if (env.CLAUDECODE === "1") return "Claude Code";
|
|
40
|
-
if (env.GEMINI_CLI === "1") return "Gemini CLI";
|
|
41
|
-
if (Object.keys(env).some((key) => key.startsWith("CODEX_"))) return "Codex";
|
|
42
|
-
return "Terminal";
|
|
43
|
-
}
|
|
44
|
-
// src/client.ts
|
|
45
|
-
/** Where the running app writes its address and token. */
|
|
46
|
-
const agentFile = process.env.PARITY_AGENT_FILE ?? join(homedir(), ".parity", "agent.json");
|
|
47
|
-
let caller = cliCallerName(process.env);
|
|
48
|
-
/** Tells callers with the same name apart. A terminal's calls are separate processes, so they share their name. */
|
|
49
|
-
let callerId = process.env.PARITY_AGENT_ID?.trim() || caller;
|
|
50
|
-
/**
|
|
51
|
-
* Names who's calling in later calls, so the canvas can say who's at work. The MCP server learns the name from its
|
|
52
|
-
* client, and is one session: its calls share an id, apart from any other session of the same agent.
|
|
53
|
-
*/
|
|
54
|
-
function setCaller(name, session = false) {
|
|
55
|
-
caller = name ?? caller;
|
|
56
|
-
callerId = session ? `${caller}#${randomUUID().slice(0, 8)}` : caller;
|
|
57
|
-
}
|
|
58
|
-
/** Calls the running Parity app. Fails with a clear message when it isn't running. */
|
|
59
|
-
async function call(method, params = {}) {
|
|
60
|
-
let connection;
|
|
61
|
-
try {
|
|
62
|
-
connection = JSON.parse(await readFile(agentFile, "utf8"));
|
|
63
|
-
} catch {
|
|
64
|
-
throw new Error(`Parity isn't running (no ${agentFile}). Start the app, then try again.`);
|
|
65
|
-
}
|
|
66
|
-
let response;
|
|
67
|
-
try {
|
|
68
|
-
response = await fetch(`${connection.url}/rpc`, {
|
|
69
|
-
method: "POST",
|
|
70
|
-
headers: {
|
|
71
|
-
"content-type": "application/json",
|
|
72
|
-
authorization: `Bearer ${connection.token}`
|
|
73
|
-
},
|
|
74
|
-
body: JSON.stringify({
|
|
75
|
-
method,
|
|
76
|
-
params,
|
|
77
|
-
agent: caller,
|
|
78
|
-
agentId: callerId
|
|
79
|
-
})
|
|
80
|
-
});
|
|
81
|
-
} catch {
|
|
82
|
-
throw new Error(`Parity isn't answering at ${connection.url}. Is the app still running?`);
|
|
83
|
-
}
|
|
84
|
-
const body = await response.json();
|
|
85
|
-
if (body.error !== void 0) throw new Error(body.error);
|
|
86
|
-
return body.result;
|
|
87
|
-
}
|
|
88
|
-
/** A readable account of an exec call, for terminals and for agents. */
|
|
89
|
-
function describeExec(result) {
|
|
90
|
-
const lines = [];
|
|
91
|
-
if (result.ok) lines.push(result.applied ? "Done. The changes are on the canvas as one undoable step." : "Done. Nothing changed.");
|
|
92
|
-
else {
|
|
93
|
-
lines.push(`Failed, nothing changed: ${result.error}`);
|
|
94
|
-
lines.push(`Fix it by retrying with editId "${result.editId}" and edits ({find, replace}) to the snippet.`);
|
|
95
|
-
}
|
|
96
|
-
if (result.output.length > 0) lines.push("", "Output:", ...result.output);
|
|
97
|
-
const created = Object.entries(result.created);
|
|
98
|
-
if (created.length > 0) lines.push("", "Created:", ...created.map(([name, id]) => ` ${name} = ${id}`));
|
|
99
|
-
if (result.warnings.length > 0) lines.push("", "Warnings (fix these next):", ...result.warnings.map((warning) => ` ${warning}`));
|
|
100
|
-
if (result.screenshots.length > 0) lines.push("", "Screenshots:", ...result.screenshots.map((shot) => ` ${shot.design} / ${shot.state} / ${shot.mode}: ${shot.path}`));
|
|
101
|
-
return lines.join("\n");
|
|
102
|
-
}
|
|
103
|
-
const impactOrder = [
|
|
104
|
-
"critical",
|
|
105
|
-
"serious",
|
|
106
|
-
"moderate",
|
|
107
|
-
"minor"
|
|
108
|
-
];
|
|
109
|
-
/**
|
|
110
|
-
* A readable account of an accessibility check: each rule broken, worst first, with the layers that break it, the
|
|
111
|
-
* frames they break it in and what's wrong. Alike layers, such as the rows of a list, share a line.
|
|
112
|
-
*/
|
|
113
|
-
function describeAccessibility(result) {
|
|
114
|
-
const { design, findings, frames } = result;
|
|
115
|
-
const checked = `${design.name} (${design.id}), ${frames === 1 ? "1 frame" : `${frames} frames`}`;
|
|
116
|
-
const rules = /* @__PURE__ */ new Map();
|
|
117
|
-
for (const finding of findings) rules.set(finding.rule, [...rules.get(finding.rule) ?? [], finding]);
|
|
118
|
-
const lines = [rules.size === 0 ? `No issues found in ${checked}, with axe-core against WCAG 2.2 AA.` : `${rules.size === 1 ? "1 rule" : `${rules.size} rules`} broken in ${checked}, with axe-core against WCAG 2.2 AA.`];
|
|
119
|
-
const groups = [...rules.values()].sort((a, b) => impactOrder.indexOf(a[0].impact) - impactOrder.indexOf(b[0].impact));
|
|
120
|
-
for (const group of groups) {
|
|
121
|
-
const { impact, rule, help, helpUrl } = group[0];
|
|
122
|
-
lines.push("", `${impact}: ${help} (${rule})`);
|
|
123
|
-
const byLayer = /* @__PURE__ */ new Map();
|
|
124
|
-
for (const finding of group) {
|
|
125
|
-
const key = `${finding.node ?? ""}\n${finding.detail}`;
|
|
126
|
-
const layer = byLayer.get(key) ?? {
|
|
127
|
-
name: finding.name ?? "The design",
|
|
128
|
-
nodes: finding.node ? [finding.node] : [],
|
|
129
|
-
detail: finding.detail,
|
|
130
|
-
frames: []
|
|
131
|
-
};
|
|
132
|
-
layer.frames.push({
|
|
133
|
-
state: finding.state,
|
|
134
|
-
mode: finding.mode
|
|
135
|
-
});
|
|
136
|
-
byLayer.set(key, layer);
|
|
137
|
-
}
|
|
138
|
-
const alike = /* @__PURE__ */ new Map();
|
|
139
|
-
for (const layer of byLayer.values()) {
|
|
140
|
-
const same = JSON.stringify([
|
|
141
|
-
layer.name,
|
|
142
|
-
layer.detail,
|
|
143
|
-
layer.frames
|
|
144
|
-
]);
|
|
145
|
-
const existing = alike.get(same);
|
|
146
|
-
if (existing) existing.nodes.push(...layer.nodes);
|
|
147
|
-
else alike.set(same, layer);
|
|
148
|
-
}
|
|
149
|
-
for (const layer of alike.values()) {
|
|
150
|
-
const where = layer.frames.length === frames && frames > 1 ? "every frame" : frameList(layer.frames);
|
|
151
|
-
const ids = layer.nodes.length > 0 ? ` (${layer.nodes.join(", ")})` : "";
|
|
152
|
-
lines.push(` ${layer.name}${ids}, in ${where}: ${layer.detail}`);
|
|
153
|
-
}
|
|
154
|
-
lines.push(` How to fix it: ${helpUrl}`);
|
|
155
|
-
}
|
|
156
|
-
if (result.warnings.length > 0) lines.push("", "Warnings:", ...result.warnings.map((warning) => ` ${warning}`));
|
|
157
|
-
lines.push("", "Automated checks find some issues, not all. Try the design with a keyboard and a screen reader too.");
|
|
158
|
-
return lines.join("\n");
|
|
159
|
-
}
|
|
160
|
-
/** A readable account of the layout problems in a design's frames, each once with the frames it's in. */
|
|
161
|
-
function describeProblems(result) {
|
|
162
|
-
const { design, problems, frames } = result;
|
|
163
|
-
const checked = `${design.name} (${design.id}), ${frames === 1 ? "1 frame" : `${frames} frames`}`;
|
|
164
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
165
|
-
for (const { problem, node, state, mode } of problems) {
|
|
166
|
-
const key = `${node ?? ""}\n${problem}`;
|
|
167
|
-
const entry = grouped.get(key) ?? {
|
|
168
|
-
problem,
|
|
169
|
-
...node ? { node } : {},
|
|
170
|
-
frames: []
|
|
171
|
-
};
|
|
172
|
-
entry.frames.push({
|
|
173
|
-
state,
|
|
174
|
-
mode
|
|
175
|
-
});
|
|
176
|
-
grouped.set(key, entry);
|
|
177
|
-
}
|
|
178
|
-
const count = grouped.size === 1 ? "1 problem" : `${grouped.size} problems`;
|
|
179
|
-
const lines = [grouped.size === 0 ? `No problems found in ${checked}.` : `${count} in ${checked}:`];
|
|
180
|
-
for (const { problem, node, frames: where } of grouped.values()) {
|
|
181
|
-
const every = where.length === frames && frames > 1 ? "every frame" : frameList(where);
|
|
182
|
-
lines.push(` ${node ? `${node}: ` : ""}${problem}, in ${every}`);
|
|
183
|
-
}
|
|
184
|
-
if (result.warnings.length > 0) lines.push("", "Warnings:", ...result.warnings.map((warning) => ` ${warning}`));
|
|
185
|
-
return lines.join("\n");
|
|
186
|
-
}
|
|
187
|
-
/** Tokens by category, each with its value in every theme that sets one. */
|
|
188
|
-
function describeTokens(tokens) {
|
|
189
|
-
if (tokens.length === 0) return "No tokens match.";
|
|
190
|
-
const lines = [`${tokens.length === 1 ? "1 token" : `${tokens.length} tokens`}. Name them in styles without "--".`];
|
|
191
|
-
const categories = /* @__PURE__ */ new Map();
|
|
192
|
-
for (const token of tokens) {
|
|
193
|
-
const key = `${token.category}, ${token.source}`;
|
|
194
|
-
categories.set(key, [...categories.get(key) ?? [], token]);
|
|
195
|
-
}
|
|
196
|
-
for (const [category, list] of categories) {
|
|
197
|
-
lines.push("", `${category}:`);
|
|
198
|
-
for (const token of list) {
|
|
199
|
-
const values = Object.entries(token.values).sort(([a], [b]) => first("default", a, b)).map(([theme, value]) => `${theme} ${value}`);
|
|
200
|
-
lines.push(` ${token.name}: ${values.join(", ")}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return lines.join("\n");
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* The project's own components, then each library's, then the icon libraries, then the exports left out and why, the
|
|
207
|
-
* components failing to draw and why, and what the person corrected by hand.
|
|
208
|
-
*/
|
|
209
|
-
function describeComponents({ components, iconLibraries = [], leftOut = [], drawn = 0, failing = [], corrections = {} }) {
|
|
210
|
-
const groups = /* @__PURE__ */ new Map();
|
|
211
|
-
for (const component of components) {
|
|
212
|
-
const slash = component.indexOf("/");
|
|
213
|
-
const group = slash > 0 ? component.slice(0, slash) : "";
|
|
214
|
-
groups.set(group, [...groups.get(group) ?? [], slash > 0 ? component.slice(slash + 1) : component]);
|
|
215
|
-
}
|
|
216
|
-
const lines = [];
|
|
217
|
-
for (const [group, names] of [...groups].sort(([a], [b]) => first("", a, b))) lines.push(group ? `${group}, as ${group}/Name:` : "The project's own:", ` ${names.join(", ")}`, "");
|
|
218
|
-
if (iconLibraries.length > 0) lines.push(`Icons: ${iconLibraries.map((library) => `${library}, as ${library}/Name`).join("; ")}`, "");
|
|
219
|
-
if (leftOut.length > 0) {
|
|
220
|
-
lines.push("Not components:");
|
|
221
|
-
for (const entry of leftOut) lines.push(` ${entry.name}, in ${entry.path}: ${entry.undecided ? "can't tell, " : ""}${entry.reason}`);
|
|
222
|
-
lines.push("");
|
|
223
|
-
}
|
|
224
|
-
if (failing.length > 0) {
|
|
225
|
-
lines.push(`Failing to draw on the canvas, ${failing.length} of ${failing.length + drawn} drawn so far:`);
|
|
226
|
-
for (const entry of failing) lines.push(` ${entry.id}: ${entry.reason}`);
|
|
227
|
-
lines.push("");
|
|
228
|
-
}
|
|
229
|
-
const { include = [], exclude = [] } = corrections;
|
|
230
|
-
if (include.length > 0 || exclude.length > 0) {
|
|
231
|
-
const name = (ref) => ref.export === "default" ? "the default export" : ref.export;
|
|
232
|
-
lines.push("Corrected by hand in the project file:");
|
|
233
|
-
for (const ref of include) lines.push(` listed: ${name(ref)}, in ${ref.path}`);
|
|
234
|
-
for (const ref of exclude) lines.push(` taken off: ${name(ref)}, in ${ref.path}`);
|
|
235
|
-
}
|
|
236
|
-
return lines.join("\n").trimEnd();
|
|
237
|
-
}
|
|
238
|
-
/** A component's props, with each variant's options and default. */
|
|
239
|
-
function describeProps(result) {
|
|
240
|
-
if (!result.known) return `${result.component}'s props haven't been read from its source yet. Try again in a moment.`;
|
|
241
|
-
const imported = result.import && result.import !== result.path ? `, imported from "${result.import}"` : "";
|
|
242
|
-
const lines = [`${result.component}, from ${result.path}${imported}:`];
|
|
243
|
-
for (const prop of result.props) {
|
|
244
|
-
const options = prop.options ? `: ${prop.options.map((option) => JSON.stringify(option)).join(", ")}` : "";
|
|
245
|
-
const fallback = prop.default === void 0 ? "" : `, default ${JSON.stringify(prop.default)}`;
|
|
246
|
-
lines.push(` ${prop.name} (${prop.kind}${prop.required ? ", required" : ""})${options}${fallback}`);
|
|
247
|
-
}
|
|
248
|
-
if (result.props.length === 0) lines.push(" No props of its own. --all lists the attributes it passes on.");
|
|
249
|
-
return lines.join("\n");
|
|
250
|
-
}
|
|
251
|
-
/** Frames as state and mode ids, naming a mode once when they share it: "default, error in desktop-light". */
|
|
252
|
-
function frameList(frames) {
|
|
253
|
-
const modes = [...new Set(frames.map((frame) => frame.mode))];
|
|
254
|
-
if (modes.length === 1) return `${frames.map((frame) => frame.state).join(", ")} in ${modes[0]}`;
|
|
255
|
-
return frames.map((frame) => `${frame.state} in ${frame.mode}`).join(", ");
|
|
256
|
-
}
|
|
257
|
-
/** A readable account of what a project's code wrote to the console, oldest first, with where each came from. */
|
|
258
|
-
function describeConsole(result) {
|
|
259
|
-
const { tab, entries } = result;
|
|
260
|
-
if (entries.length === 0) return `${tab.name}'s code hasn't written anything to the console.`;
|
|
261
|
-
const lines = [`${entries.length === 1 ? "1 message" : `${entries.length} messages`} from ${tab.name}'s code, oldest first:`];
|
|
262
|
-
for (const entry of entries) {
|
|
263
|
-
const time = new Date(entry.last).toLocaleTimeString([], { hour12: false });
|
|
264
|
-
const repeats = entry.count > 1 ? ` (${entry.count} times)` : "";
|
|
265
|
-
const [first = "", ...rest] = entry.text.split("\n");
|
|
266
|
-
lines.push("", `${time} ${entry.level}${repeats}: ${first}`, ...rest.map((line) => ` ${line}`));
|
|
267
|
-
const offscreen = entry.where === "offscreen" ? ", in an offscreen render" : "";
|
|
268
|
-
lines.push(` at ${scriptPath(entry.source)}:${entry.line}${offscreen}`);
|
|
269
|
-
}
|
|
270
|
-
return lines.join("\n");
|
|
271
|
-
}
|
|
272
|
-
/** Compares two names alphabetically, with `name` before every other. */
|
|
273
|
-
function first(name, a, b) {
|
|
274
|
-
if (a === name) return -1;
|
|
275
|
-
if (b === name) return 1;
|
|
276
|
-
return a.localeCompare(b);
|
|
277
|
-
}
|
|
278
|
-
/** A script's path on its server, without the cache-busting query Vite adds. */
|
|
279
|
-
function scriptPath(url) {
|
|
280
|
-
try {
|
|
281
|
-
return new URL(url).pathname;
|
|
282
|
-
} catch {
|
|
283
|
-
return url || "an unknown script";
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
export { describeConsole as a, describeProps as c, mcpCallerName as d, describeComponents as i, describeTokens as l, call as n, describeExec as o, describeAccessibility as r, describeProblems as s, agentFile as t, setCaller as u };
|