@oprt/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 +48 -23
- package/dist/bin.js +1750 -673
- package/dist/contract.json +1 -1
- package/dist/index.cjs +1493 -408
- package/dist/index.d.cts +259 -90
- package/dist/index.d.ts +259 -90
- package/dist/index.js +1470 -397
- package/dist/{read-8a9euzJx.d.cts → read-C7rE5UlY.d.cts} +48 -21
- package/dist/{read-8a9euzJx.d.ts → read-C7rE5UlY.d.ts} +48 -21
- package/dist/read.cjs +2 -1
- package/dist/read.d.cts +1 -1
- package/dist/read.d.ts +1 -1
- package/dist/read.js +2 -1
- package/dist/registry.json +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,77 +1,8 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
3
|
-
import { dirname as dirname5, join as
|
|
3
|
+
import { dirname as dirname5, join as join5 } from "path";
|
|
4
4
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5
5
|
|
|
6
|
-
// package.json
|
|
7
|
-
var package_default = {
|
|
8
|
-
name: "@oprt/cli",
|
|
9
|
-
version: "0.1.0",
|
|
10
|
-
description: "The OPERATOR command line: copy components and recipes into your app as source, give coding agents the contract, and check their code against it. Includes an MCP server.",
|
|
11
|
-
keywords: [
|
|
12
|
-
"design-system",
|
|
13
|
-
"cli",
|
|
14
|
-
"coding-agents",
|
|
15
|
-
"mcp",
|
|
16
|
-
"model-context-protocol",
|
|
17
|
-
"code-check",
|
|
18
|
-
"components"
|
|
19
|
-
],
|
|
20
|
-
homepage: "https://oprt.dev/docs/agents/cli",
|
|
21
|
-
type: "module",
|
|
22
|
-
main: "./dist/index.cjs",
|
|
23
|
-
module: "./dist/index.js",
|
|
24
|
-
types: "./dist/index.d.ts",
|
|
25
|
-
bin: {
|
|
26
|
-
oprt: "./dist/bin.js"
|
|
27
|
-
},
|
|
28
|
-
engines: {
|
|
29
|
-
node: ">=22"
|
|
30
|
-
},
|
|
31
|
-
exports: {
|
|
32
|
-
".": {
|
|
33
|
-
import: {
|
|
34
|
-
types: "./dist/index.d.ts",
|
|
35
|
-
default: "./dist/index.js"
|
|
36
|
-
},
|
|
37
|
-
require: {
|
|
38
|
-
types: "./dist/index.d.cts",
|
|
39
|
-
default: "./dist/index.cjs"
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
"./read": {
|
|
43
|
-
import: {
|
|
44
|
-
types: "./dist/read.d.ts",
|
|
45
|
-
default: "./dist/read.js"
|
|
46
|
-
},
|
|
47
|
-
require: {
|
|
48
|
-
types: "./dist/read.d.cts",
|
|
49
|
-
default: "./dist/read.cjs"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
files: [
|
|
54
|
-
"dist"
|
|
55
|
-
],
|
|
56
|
-
scripts: {
|
|
57
|
-
clean: `node -e "require('node:fs').rmSync('dist',{recursive:true,force:true})"`,
|
|
58
|
-
build: "pnpm run clean && node scripts/build-registry.mjs && tsup",
|
|
59
|
-
dev: "pnpm run clean && node scripts/build-registry.mjs && tsup --watch",
|
|
60
|
-
test: "vitest run",
|
|
61
|
-
typecheck: "tsc --noEmit",
|
|
62
|
-
lint: "eslint src scripts"
|
|
63
|
-
},
|
|
64
|
-
devDependencies: {
|
|
65
|
-
tsup: "^8.3.0",
|
|
66
|
-
typescript: "^5.6.3",
|
|
67
|
-
vitest: "^2.1.3"
|
|
68
|
-
},
|
|
69
|
-
license: "MIT",
|
|
70
|
-
publishConfig: {
|
|
71
|
-
access: "public"
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
6
|
// src/contract.ts
|
|
76
7
|
import { existsSync, readFileSync } from "fs";
|
|
77
8
|
import { dirname, join } from "path";
|
|
@@ -115,6 +46,197 @@ var CliError = class extends Error {
|
|
|
115
46
|
suggestions;
|
|
116
47
|
};
|
|
117
48
|
|
|
49
|
+
// src/lookup.ts
|
|
50
|
+
var SEARCH_LIMIT_MAX = 50;
|
|
51
|
+
var SEARCH_LIMIT_DEFAULT = 10;
|
|
52
|
+
var squash = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
53
|
+
var firstSentence = (text2) => /^.*?[.!?](?=\s|$)/.exec(text2)?.[0] ?? text2;
|
|
54
|
+
function distance(a, b) {
|
|
55
|
+
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
56
|
+
for (let i = 1; i <= a.length; i++) {
|
|
57
|
+
let diagonal = row[0];
|
|
58
|
+
row[0] = i;
|
|
59
|
+
for (let j = 1; j <= b.length; j++) {
|
|
60
|
+
const above = row[j];
|
|
61
|
+
row[j] = Math.min(above + 1, row[j - 1] + 1, diagonal + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
62
|
+
diagonal = above;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return row[b.length];
|
|
66
|
+
}
|
|
67
|
+
function nearest(wanted, names2, limit = 5) {
|
|
68
|
+
const target = squash(wanted);
|
|
69
|
+
return names2.map((name) => {
|
|
70
|
+
const candidate = squash(name);
|
|
71
|
+
const apart = distance(target, candidate);
|
|
72
|
+
const close = apart <= 2 || target.length >= 3 && (candidate.includes(target) || target.includes(candidate));
|
|
73
|
+
return { name, apart, close };
|
|
74
|
+
}).filter(({ close }) => close).sort((a, b) => a.apart - b.apart || a.name.localeCompare(b.name)).slice(0, limit).map(({ name }) => name);
|
|
75
|
+
}
|
|
76
|
+
function resolveComponent(contract2, wanted) {
|
|
77
|
+
const names2 = Object.keys(contract2.components);
|
|
78
|
+
const found = names2.find((name) => name === wanted) ?? names2.find((name) => squash(name) === squash(wanted));
|
|
79
|
+
if (found !== void 0) return found;
|
|
80
|
+
const suggestions = nearest(wanted, names2);
|
|
81
|
+
throw new CliError(
|
|
82
|
+
`Unknown component ${JSON.stringify(wanted)}.` + (suggestions.length > 0 ? ` Did you mean ${suggestions.join(", ")}?` : "") + " Run oprt component --list.",
|
|
83
|
+
2,
|
|
84
|
+
{ code: "ERR_UNKNOWN_COMPONENT", suggestions }
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
function allowedChildren(rules, name) {
|
|
88
|
+
for (const [parent, rule] of Object.entries(rules ?? {})) {
|
|
89
|
+
if (parent === name) return rule.allowed_children;
|
|
90
|
+
const nested = allowedChildren(rule.children, name);
|
|
91
|
+
if (nested !== void 0) return nested;
|
|
92
|
+
}
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
var partsOf = (contract2, name) => Object.keys(contract2.components).filter(
|
|
96
|
+
(part) => contract2.guidance.components[part]?.part_of === name
|
|
97
|
+
);
|
|
98
|
+
function componentIndex(contract2) {
|
|
99
|
+
return Object.keys(contract2.components).filter((name) => (contract2.guidance.components[name]?.use_for ?? []).length > 0).sort((a, b) => a.localeCompare(b)).map((name) => ({
|
|
100
|
+
name,
|
|
101
|
+
purpose: contract2.guidance.components[name].use_for.join("; "),
|
|
102
|
+
parts: partsOf(contract2, name)
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
function componentDetail(contract2, wanted) {
|
|
106
|
+
const name = resolveComponent(contract2, wanted);
|
|
107
|
+
const component = contract2.components[name];
|
|
108
|
+
const guide = contract2.guidance.components[name] ?? {};
|
|
109
|
+
const props = Object.entries(component.props).map(([prop, definition]) => {
|
|
110
|
+
const notes = guide.values?.[prop];
|
|
111
|
+
return { name: prop, ...definition, ...notes === void 0 ? {} : { notes } };
|
|
112
|
+
});
|
|
113
|
+
const allowed = allowedChildren(contract2.composition_rules, name);
|
|
114
|
+
return {
|
|
115
|
+
name,
|
|
116
|
+
description: component.description,
|
|
117
|
+
use_for: guide.use_for ?? [],
|
|
118
|
+
not_for: guide.not_for ?? [],
|
|
119
|
+
...guide.part_of === void 0 ? {} : { part_of: guide.part_of },
|
|
120
|
+
parts: partsOf(contract2, name),
|
|
121
|
+
props,
|
|
122
|
+
...component.slots === void 0 ? {} : { slots: component.slots },
|
|
123
|
+
composable_with: component.composable_with ?? [],
|
|
124
|
+
never_inside: component.never_inside ?? [],
|
|
125
|
+
...allowed === void 0 ? {} : { allowed_children: allowed },
|
|
126
|
+
example: component.example
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function propLine(prop, dense) {
|
|
130
|
+
const kind = prop.type === "enum" ? (prop.values ?? []).map((value) => value === prop.default ? `${value} (default)` : value).join(" | ") : (prop.type === "union" ? (prop.types ?? []).join(" or ") : prop.type) + (prop.default === void 0 ? "" : ` (default ${String(prop.default)})`);
|
|
131
|
+
const description = prop.description === void 0 ? "" : dense ? firstSentence(prop.description) : prop.description;
|
|
132
|
+
const lines = [
|
|
133
|
+
` ${prop.name}: ${kind}${prop.required ? ", required" : ""}${description ? `. ${description}` : ""}`
|
|
134
|
+
];
|
|
135
|
+
for (const [value, note] of Object.entries(prop.notes ?? {})) lines.push(` ${value}: ${note}`);
|
|
136
|
+
return lines;
|
|
137
|
+
}
|
|
138
|
+
function renderComponent(detail, { dense }) {
|
|
139
|
+
const lines = [
|
|
140
|
+
detail.part_of === void 0 ? `${detail.name}: ${detail.use_for.join("; ")}` : `${detail.name}: a part of ${detail.part_of}. ${firstSentence(detail.description)}`
|
|
141
|
+
];
|
|
142
|
+
if (detail.not_for.length > 0)
|
|
143
|
+
lines.push(
|
|
144
|
+
`Not for: ${detail.not_for.map(({ need, use }) => `${need} (use ${use})`).join("; ")}`
|
|
145
|
+
);
|
|
146
|
+
if (!dense) lines.push("", detail.description, "");
|
|
147
|
+
if (detail.props.length > 0) {
|
|
148
|
+
lines.push("Props:");
|
|
149
|
+
for (const prop of detail.props) lines.push(...propLine(prop, dense));
|
|
150
|
+
}
|
|
151
|
+
for (const [slot, { type, required }] of Object.entries(detail.slots ?? {}))
|
|
152
|
+
lines.push(
|
|
153
|
+
`${slot === "children" ? "Children" : `Slot ${slot}`}: ${type}${required ? ", required" : ""}`
|
|
154
|
+
);
|
|
155
|
+
if (detail.parts.length > 0) lines.push(`Parts: ${detail.parts.join(", ")}`);
|
|
156
|
+
if (detail.allowed_children !== void 0)
|
|
157
|
+
lines.push(`Takes directly inside: ${detail.allowed_children.join(", ")}`);
|
|
158
|
+
if (detail.composable_with.length > 0)
|
|
159
|
+
lines.push(`Goes inside: ${detail.composable_with.join(", ")}`);
|
|
160
|
+
if (detail.never_inside.length > 0) lines.push(`Never inside: ${detail.never_inside.join(", ")}`);
|
|
161
|
+
lines.push(
|
|
162
|
+
detail.example.includes("\n") ? `Example:
|
|
163
|
+
${detail.example.split("\n").map((line) => ` ${line}`).join("\n")}` : `Example: ${detail.example}`
|
|
164
|
+
);
|
|
165
|
+
return `${lines.join("\n")}
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
var STOP = new Set(
|
|
169
|
+
"a an the to of for in on at with and or i we need want that which is are it its my some from by".split(
|
|
170
|
+
" "
|
|
171
|
+
)
|
|
172
|
+
);
|
|
173
|
+
var words = (text2) => (text2.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((word) => !STOP.has(word)).map((word) => word.length > 4 ? word.replace(/(?:ing|ed|es|s)$/, "") : word);
|
|
174
|
+
var matched = (query, text2) => {
|
|
175
|
+
const present = new Set(words(text2));
|
|
176
|
+
return query.filter((word) => present.has(word)).length;
|
|
177
|
+
};
|
|
178
|
+
function search(contract2, registry2, query, limit = SEARCH_LIMIT_DEFAULT) {
|
|
179
|
+
const wanted = words(query);
|
|
180
|
+
if (wanted.length === 0) return [];
|
|
181
|
+
const whole = squash(query);
|
|
182
|
+
const scores = /* @__PURE__ */ new Map();
|
|
183
|
+
const add = (name, points, why) => {
|
|
184
|
+
const before = scores.get(name) ?? { score: 0 };
|
|
185
|
+
scores.set(name, {
|
|
186
|
+
score: before.score + points,
|
|
187
|
+
...before.why !== void 0 ? { why: before.why } : why !== void 0 ? { why } : {}
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
for (const [name, component] of Object.entries(contract2.components)) {
|
|
191
|
+
const guide = contract2.guidance.components[name] ?? {};
|
|
192
|
+
const plain = squash(name);
|
|
193
|
+
if (plain === whole) add(name, 100);
|
|
194
|
+
else if (wanted.some((word) => word.length >= 3 && plain.includes(word))) add(name, 15);
|
|
195
|
+
else if (wanted.some((word) => word.length >= 4 && distance(word, plain) <= 1)) add(name, 12);
|
|
196
|
+
for (const phrase of guide.use_for ?? []) {
|
|
197
|
+
const hits = matched(wanted, phrase);
|
|
198
|
+
if (hits > 0) add(name, 12 * hits + (hits === wanted.length ? 40 : 0));
|
|
199
|
+
}
|
|
200
|
+
for (const { need, use } of guide.not_for ?? []) {
|
|
201
|
+
const hits = matched(wanted, need);
|
|
202
|
+
if (hits > 0 && hits >= Math.ceil(wanted.length * 0.6))
|
|
203
|
+
add(use, 10 * hits + (hits === wanted.length ? 15 : 0), `${name} is not for this: ${need}`);
|
|
204
|
+
}
|
|
205
|
+
const inDescription = matched(wanted, component.description);
|
|
206
|
+
if (inDescription > 0) add(name, 2 * inDescription);
|
|
207
|
+
}
|
|
208
|
+
const results = [];
|
|
209
|
+
for (const [name, { score, why }] of scores) {
|
|
210
|
+
const guide = contract2.guidance.components[name] ?? {};
|
|
211
|
+
results.push({
|
|
212
|
+
kind: "component",
|
|
213
|
+
name,
|
|
214
|
+
purpose: guide.part_of === void 0 ? (guide.use_for ?? []).join("; ") : `a part of ${guide.part_of}`,
|
|
215
|
+
score,
|
|
216
|
+
...why === void 0 ? {} : { why },
|
|
217
|
+
next: `oprt component ${name}`
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
for (const entry of registry2.entries.filter(({ kind }) => kind === "recipe")) {
|
|
221
|
+
const recipe = contract2.patterns?.[entry.name];
|
|
222
|
+
const plain = squash(entry.name);
|
|
223
|
+
const inName = plain === whole ? 100 : wanted.some((word) => word.length >= 3 && plain.includes(word)) ? 15 : 0;
|
|
224
|
+
const hits = matched(wanted, entry.description);
|
|
225
|
+
const score = inName + (hits > 0 ? 10 * hits + (hits === wanted.length ? 15 : 0) : 0);
|
|
226
|
+
if (score > 0)
|
|
227
|
+
results.push({
|
|
228
|
+
kind: "recipe",
|
|
229
|
+
name: entry.name,
|
|
230
|
+
purpose: firstSentence(entry.description),
|
|
231
|
+
score,
|
|
232
|
+
next: recipe?.install ?? `oprt add ${entry.slug}`
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
results.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
|
|
236
|
+
const floor = Math.max(4, (results[0]?.score ?? 0) * 0.15);
|
|
237
|
+
return results.filter(({ score }) => score >= floor).slice(0, limit);
|
|
238
|
+
}
|
|
239
|
+
|
|
118
240
|
// src/args.ts
|
|
119
241
|
var DEFAULT_OUT = "src/components/operator";
|
|
120
242
|
function split(command, rest, spec) {
|
|
@@ -170,9 +292,10 @@ var AGENT_COMMANDS = {
|
|
|
170
292
|
"--json": "flag"
|
|
171
293
|
});
|
|
172
294
|
if (positional.length === 0) throw new CliError("Usage: oprt search <need> [--limit <n>]", 2);
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
295
|
+
const given = value("--limit");
|
|
296
|
+
const limit = given === void 0 ? SEARCH_LIMIT_DEFAULT : Number(given);
|
|
297
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > SEARCH_LIMIT_MAX)
|
|
298
|
+
throw new CliError(`--limit takes a whole number from 1 to ${SEARCH_LIMIT_MAX}.`, 2);
|
|
176
299
|
return { command: "search", query: positional.join(" "), limit, json: has("--json") };
|
|
177
300
|
},
|
|
178
301
|
init(rest) {
|
|
@@ -235,7 +358,8 @@ function parseArgs(args) {
|
|
|
235
358
|
names: [],
|
|
236
359
|
out: DEFAULT_OUT,
|
|
237
360
|
dryRun: false,
|
|
238
|
-
overwrite: false
|
|
361
|
+
overwrite: false,
|
|
362
|
+
json: false
|
|
239
363
|
};
|
|
240
364
|
const seen = /* @__PURE__ */ new Set();
|
|
241
365
|
for (let i = 0; i < rest.length; i++) {
|
|
@@ -244,7 +368,7 @@ function parseArgs(args) {
|
|
|
244
368
|
parsed.names.push(token);
|
|
245
369
|
continue;
|
|
246
370
|
}
|
|
247
|
-
if (!["--out", "--dry-run", "--overwrite"].includes(token))
|
|
371
|
+
if (!["--out", "--dry-run", "--overwrite", "--json"].includes(token))
|
|
248
372
|
throw new CliError(`Unknown option ${JSON.stringify(token)}. Run oprt --help.`, 2);
|
|
249
373
|
if (seen.has(token)) throw new CliError(`Duplicate option ${token}.`, 2);
|
|
250
374
|
seen.add(token);
|
|
@@ -254,6 +378,7 @@ function parseArgs(args) {
|
|
|
254
378
|
throw new CliError("--out requires a relative directory.", 2);
|
|
255
379
|
parsed.out = value;
|
|
256
380
|
} else if (token === "--dry-run") parsed.dryRun = true;
|
|
381
|
+
else if (token === "--json") parsed.json = true;
|
|
257
382
|
else parsed.overwrite = true;
|
|
258
383
|
}
|
|
259
384
|
if (!parsed.names.length)
|
|
@@ -262,10 +387,10 @@ function parseArgs(args) {
|
|
|
262
387
|
}
|
|
263
388
|
|
|
264
389
|
// src/installer.ts
|
|
265
|
-
function resolveEntries(
|
|
266
|
-
if (!
|
|
390
|
+
function resolveEntries(names2, registry2) {
|
|
391
|
+
if (!names2.length) throw new CliError("add requires one or more names.", 2);
|
|
267
392
|
const selected = /* @__PURE__ */ new Map();
|
|
268
|
-
for (const name of
|
|
393
|
+
for (const name of names2) {
|
|
269
394
|
const normalized = name.toLowerCase();
|
|
270
395
|
const entry = registry2.entries.find(
|
|
271
396
|
(candidate) => candidate.slug === normalized || candidate.name.toLowerCase() === normalized
|
|
@@ -373,202 +498,196 @@ function installSources(options, context) {
|
|
|
373
498
|
return result;
|
|
374
499
|
}
|
|
375
500
|
|
|
376
|
-
// src/
|
|
377
|
-
import {
|
|
378
|
-
import { createRequire } from "module";
|
|
379
|
-
import { basename, extname, join as join2, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
|
|
380
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
501
|
+
// src/identity.ts
|
|
502
|
+
import { createHash } from "crypto";
|
|
381
503
|
|
|
382
|
-
//
|
|
383
|
-
var
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
504
|
+
// package.json
|
|
505
|
+
var package_default = {
|
|
506
|
+
name: "@oprt/cli",
|
|
507
|
+
version: "0.3.0",
|
|
508
|
+
description: "The OPERATOR command line: copy components and recipes into your app as source, give coding agents the contract, and check their code against it. Includes an MCP server.",
|
|
509
|
+
keywords: [
|
|
510
|
+
"design-system",
|
|
511
|
+
"cli",
|
|
512
|
+
"coding-agents",
|
|
513
|
+
"mcp",
|
|
514
|
+
"model-context-protocol",
|
|
515
|
+
"code-check",
|
|
516
|
+
"components"
|
|
517
|
+
],
|
|
518
|
+
homepage: "https://oprt.dev/docs/agents/cli",
|
|
519
|
+
type: "module",
|
|
520
|
+
main: "./dist/index.cjs",
|
|
521
|
+
module: "./dist/index.js",
|
|
522
|
+
types: "./dist/index.d.ts",
|
|
523
|
+
bin: {
|
|
524
|
+
oprt: "./dist/bin.js"
|
|
525
|
+
},
|
|
526
|
+
engines: {
|
|
527
|
+
node: ">=22"
|
|
528
|
+
},
|
|
529
|
+
exports: {
|
|
530
|
+
".": {
|
|
531
|
+
import: {
|
|
532
|
+
types: "./dist/index.d.ts",
|
|
533
|
+
default: "./dist/index.js"
|
|
534
|
+
},
|
|
535
|
+
require: {
|
|
536
|
+
types: "./dist/index.d.cts",
|
|
537
|
+
default: "./dist/index.cjs"
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
"./read": {
|
|
541
|
+
import: {
|
|
542
|
+
types: "./dist/read.d.ts",
|
|
543
|
+
default: "./dist/read.js"
|
|
544
|
+
},
|
|
545
|
+
require: {
|
|
546
|
+
types: "./dist/read.d.cts",
|
|
547
|
+
default: "./dist/read.cjs"
|
|
548
|
+
}
|
|
394
549
|
}
|
|
550
|
+
},
|
|
551
|
+
files: [
|
|
552
|
+
"dist"
|
|
553
|
+
],
|
|
554
|
+
scripts: {
|
|
555
|
+
clean: `node -e "require('node:fs').rmSync('dist',{recursive:true,force:true})"`,
|
|
556
|
+
build: "pnpm run clean && node scripts/build-registry.mjs && tsup",
|
|
557
|
+
dev: "pnpm run clean && node scripts/build-registry.mjs && tsup --watch",
|
|
558
|
+
test: "vitest run",
|
|
559
|
+
typecheck: "tsc --noEmit",
|
|
560
|
+
lint: "eslint src scripts"
|
|
561
|
+
},
|
|
562
|
+
devDependencies: {
|
|
563
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
564
|
+
tsup: "^8.3.0",
|
|
565
|
+
typescript: "^5.6.3",
|
|
566
|
+
vitest: "^2.1.3"
|
|
567
|
+
},
|
|
568
|
+
license: "MIT",
|
|
569
|
+
publishConfig: {
|
|
570
|
+
access: "public"
|
|
395
571
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
function resolveComponent(contract2, wanted) {
|
|
408
|
-
const names = Object.keys(contract2.components);
|
|
409
|
-
const found = names.find((name) => name === wanted) ?? names.find((name) => squash(name) === squash(wanted));
|
|
410
|
-
if (found !== void 0) return found;
|
|
411
|
-
const suggestions = nearest(wanted, names);
|
|
412
|
-
throw new CliError(
|
|
413
|
-
`Unknown component ${JSON.stringify(wanted)}.` + (suggestions.length > 0 ? ` Did you mean ${suggestions.join(", ")}?` : "") + " Run oprt component --list.",
|
|
414
|
-
2,
|
|
415
|
-
{ code: "ERR_UNKNOWN_COMPONENT", suggestions }
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
function allowedChildren(rules, name) {
|
|
419
|
-
for (const [parent, rule] of Object.entries(rules ?? {})) {
|
|
420
|
-
if (parent === name) return rule.allowed_children;
|
|
421
|
-
const nested = allowedChildren(rule.children, name);
|
|
422
|
-
if (nested !== void 0) return nested;
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
// src/identity.ts
|
|
575
|
+
var VERSION = package_default.version;
|
|
576
|
+
var RESULT_SCHEMA_VERSION = 1;
|
|
577
|
+
var digests = /* @__PURE__ */ new WeakMap();
|
|
578
|
+
function contractDigest(contract2) {
|
|
579
|
+
let digest = digests.get(contract2);
|
|
580
|
+
if (digest === void 0) {
|
|
581
|
+
digest = `sha256:${createHash("sha256").update(JSON.stringify(contract2)).digest("hex")}`;
|
|
582
|
+
digests.set(contract2, digest);
|
|
423
583
|
}
|
|
424
|
-
return
|
|
584
|
+
return digest;
|
|
425
585
|
}
|
|
426
|
-
|
|
427
|
-
(part) => contract2.guidance.components[part]?.part_of === name
|
|
428
|
-
);
|
|
429
|
-
function componentIndex(contract2) {
|
|
430
|
-
return Object.keys(contract2.components).filter((name) => (contract2.guidance.components[name]?.use_for ?? []).length > 0).sort((a, b) => a.localeCompare(b)).map((name) => ({
|
|
431
|
-
name,
|
|
432
|
-
purpose: contract2.guidance.components[name].use_for.join("; "),
|
|
433
|
-
parts: partsOf(contract2, name)
|
|
434
|
-
}));
|
|
435
|
-
}
|
|
436
|
-
function componentDetail(contract2, wanted) {
|
|
437
|
-
const name = resolveComponent(contract2, wanted);
|
|
438
|
-
const component = contract2.components[name];
|
|
439
|
-
const guide = contract2.guidance.components[name] ?? {};
|
|
440
|
-
const props = Object.entries(component.props).map(([prop, definition]) => {
|
|
441
|
-
const notes = guide.values?.[prop];
|
|
442
|
-
return { name: prop, ...definition, ...notes === void 0 ? {} : { notes } };
|
|
443
|
-
});
|
|
444
|
-
const allowed = allowedChildren(contract2.composition_rules, name);
|
|
586
|
+
function identityOf(contract2) {
|
|
445
587
|
return {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
...guide.part_of === void 0 ? {} : { part_of: guide.part_of },
|
|
451
|
-
parts: partsOf(contract2, name),
|
|
452
|
-
props,
|
|
453
|
-
...component.slots === void 0 ? {} : { slots: component.slots },
|
|
454
|
-
composable_with: component.composable_with ?? [],
|
|
455
|
-
never_inside: component.never_inside ?? [],
|
|
456
|
-
...allowed === void 0 ? {} : { allowed_children: allowed },
|
|
457
|
-
example: component.example
|
|
588
|
+
cli_version: VERSION,
|
|
589
|
+
contract_version: contract2.version,
|
|
590
|
+
contract_digest: contractDigest(contract2),
|
|
591
|
+
schema_version: RESULT_SCHEMA_VERSION
|
|
458
592
|
};
|
|
459
593
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
);
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
lines.push(`Takes directly inside: ${detail.allowed_children.join(", ")}`);
|
|
489
|
-
if (detail.composable_with.length > 0)
|
|
490
|
-
lines.push(`Goes inside: ${detail.composable_with.join(", ")}`);
|
|
491
|
-
if (detail.never_inside.length > 0) lines.push(`Never inside: ${detail.never_inside.join(", ")}`);
|
|
492
|
-
lines.push(
|
|
493
|
-
detail.example.includes("\n") ? `Example:
|
|
494
|
-
${detail.example.split("\n").map((line) => ` ${line}`).join("\n")}` : `Example: ${detail.example}`
|
|
495
|
-
);
|
|
496
|
-
return `${lines.join("\n")}
|
|
497
|
-
`;
|
|
498
|
-
}
|
|
499
|
-
var STOP = new Set(
|
|
500
|
-
"a an the to of for in on at with and or i we need want that which is are it its my some from by".split(
|
|
501
|
-
" "
|
|
502
|
-
)
|
|
503
|
-
);
|
|
504
|
-
var words = (text2) => (text2.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((word) => !STOP.has(word)).map((word) => word.length > 4 ? word.replace(/(?:ing|ed|es|s)$/, "") : word);
|
|
505
|
-
var matched = (query, text2) => {
|
|
506
|
-
const present = new Set(words(text2));
|
|
507
|
-
return query.filter((word) => present.has(word)).length;
|
|
508
|
-
};
|
|
509
|
-
function search(contract2, registry2, query, limit = 10) {
|
|
510
|
-
const wanted = words(query);
|
|
511
|
-
if (wanted.length === 0) return [];
|
|
512
|
-
const whole = squash(query);
|
|
513
|
-
const scores = /* @__PURE__ */ new Map();
|
|
514
|
-
const add = (name, points, why) => {
|
|
515
|
-
const before = scores.get(name) ?? { score: 0 };
|
|
516
|
-
scores.set(name, {
|
|
517
|
-
score: before.score + points,
|
|
518
|
-
...before.why !== void 0 ? { why: before.why } : why !== void 0 ? { why } : {}
|
|
519
|
-
});
|
|
520
|
-
};
|
|
521
|
-
for (const [name, component] of Object.entries(contract2.components)) {
|
|
522
|
-
const guide = contract2.guidance.components[name] ?? {};
|
|
523
|
-
const plain = squash(name);
|
|
524
|
-
if (plain === whole) add(name, 100);
|
|
525
|
-
else if (wanted.some((word) => word.length >= 3 && plain.includes(word))) add(name, 15);
|
|
526
|
-
else if (wanted.some((word) => word.length >= 4 && distance(word, plain) <= 1)) add(name, 12);
|
|
527
|
-
for (const phrase of guide.use_for ?? []) {
|
|
528
|
-
const hits = matched(wanted, phrase);
|
|
529
|
-
if (hits > 0) add(name, 12 * hits + (hits === wanted.length ? 40 : 0));
|
|
530
|
-
}
|
|
531
|
-
for (const { need, use } of guide.not_for ?? []) {
|
|
532
|
-
const hits = matched(wanted, need);
|
|
533
|
-
if (hits > 0 && hits >= Math.ceil(wanted.length * 0.6))
|
|
534
|
-
add(use, 10 * hits + (hits === wanted.length ? 15 : 0), `${name} is not for this: ${need}`);
|
|
535
|
-
}
|
|
536
|
-
const inDescription = matched(wanted, component.description);
|
|
537
|
-
if (inDescription > 0) add(name, 2 * inDescription);
|
|
538
|
-
}
|
|
539
|
-
const results = [];
|
|
540
|
-
for (const [name, { score, why }] of scores) {
|
|
541
|
-
const guide = contract2.guidance.components[name] ?? {};
|
|
542
|
-
results.push({
|
|
543
|
-
kind: "component",
|
|
544
|
-
name,
|
|
545
|
-
purpose: guide.part_of === void 0 ? (guide.use_for ?? []).join("; ") : `a part of ${guide.part_of}`,
|
|
546
|
-
score,
|
|
547
|
-
...why === void 0 ? {} : { why },
|
|
548
|
-
next: `oprt component ${name}`
|
|
549
|
-
});
|
|
594
|
+
|
|
595
|
+
// src/check.ts
|
|
596
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3, statSync } from "fs";
|
|
597
|
+
import { basename, extname, join as join3, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
|
|
598
|
+
|
|
599
|
+
// src/parser.ts
|
|
600
|
+
import { createRequire } from "module";
|
|
601
|
+
import { join as join2 } from "path";
|
|
602
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
603
|
+
var SUPPORTED_TYPESCRIPT = "TypeScript 5 or 6";
|
|
604
|
+
var INSTALL_TYPESCRIPT = "npm install -D typescript@6";
|
|
605
|
+
function usable(typescript) {
|
|
606
|
+
const ts = typescript;
|
|
607
|
+
if (typeof ts.createSourceFile !== "function" || ts.ScriptKind === void 0)
|
|
608
|
+
return `TypeScript ${ts.version ?? "(unknown version)"} has no JavaScript syntax API (createSourceFile), which oprt reads code with; ${SUPPORTED_TYPESCRIPT} has it`;
|
|
609
|
+
try {
|
|
610
|
+
const probe = ts.createSourceFile(
|
|
611
|
+
"probe.tsx",
|
|
612
|
+
"<a>",
|
|
613
|
+
ts.ScriptTarget?.Latest ?? 99,
|
|
614
|
+
true,
|
|
615
|
+
ts.ScriptKind.TSX
|
|
616
|
+
);
|
|
617
|
+
const diagnostics = probe.parseDiagnostics;
|
|
618
|
+
if (!Array.isArray(diagnostics)) return "it does not report parse errors on a source file";
|
|
619
|
+
if (diagnostics.length === 0) return "it did not report a syntax error in code that has one";
|
|
620
|
+
} catch (error) {
|
|
621
|
+
return `it failed on a probe parse: ${error instanceof Error ? error.message : String(error)}`;
|
|
550
622
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
var packageFolder = (file) => /^(.*[\\/]node_modules[\\/]typescript)[\\/]/.exec(file)?.[1] ?? file;
|
|
626
|
+
function probeTypeScript(cwd, options = {}) {
|
|
627
|
+
const places = [
|
|
628
|
+
{ from: join2(cwd, "package.json"), source: "project" },
|
|
629
|
+
...options.installer === false ? [] : [{ from: fileURLToPath2(import.meta.url), source: "installer" }]
|
|
630
|
+
];
|
|
631
|
+
let unusable;
|
|
632
|
+
for (const { from, source } of places) {
|
|
633
|
+
let file;
|
|
634
|
+
let loaded2;
|
|
635
|
+
try {
|
|
636
|
+
const require2 = createRequire(from);
|
|
637
|
+
file = require2.resolve("typescript");
|
|
638
|
+
loaded2 = require2("typescript");
|
|
639
|
+
} catch (error) {
|
|
640
|
+
if (error.code !== "MODULE_NOT_FOUND")
|
|
641
|
+
unusable ??= `typescript was found but failed to load: ${error instanceof Error ? error.message : String(error)}`;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
const typescript = loaded2.default ?? loaded2;
|
|
645
|
+
const problem = usable(typescript);
|
|
646
|
+
if (problem !== null) {
|
|
647
|
+
unusable ??= `${problem} (at ${packageFolder(file)}).`;
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
return {
|
|
651
|
+
ready: true,
|
|
652
|
+
typescript,
|
|
653
|
+
resolution: {
|
|
654
|
+
name: "typescript",
|
|
655
|
+
version: typescript.version,
|
|
656
|
+
source,
|
|
657
|
+
resolved_from: packageFolder(file)
|
|
658
|
+
}
|
|
659
|
+
};
|
|
565
660
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
return
|
|
661
|
+
if (unusable !== void 0)
|
|
662
|
+
return { ready: false, reason: "typescript_unusable", detail: unusable };
|
|
663
|
+
return {
|
|
664
|
+
ready: false,
|
|
665
|
+
reason: "typescript_missing",
|
|
666
|
+
detail: `typescript could not be resolved from ${cwd}${options.installer === false ? "" : " or from beside the installer"}.`
|
|
667
|
+
};
|
|
569
668
|
}
|
|
570
669
|
|
|
571
670
|
// src/check.ts
|
|
671
|
+
var FINDING_CODES = [
|
|
672
|
+
"INVALID_PROP_VALUE",
|
|
673
|
+
"UNKNOWN_EXPORT",
|
|
674
|
+
"DOT_NOTATION",
|
|
675
|
+
"INVALID_NESTING",
|
|
676
|
+
"INVALID_CHILD",
|
|
677
|
+
"MISSING_REQUIRED_PROP",
|
|
678
|
+
"UNKNOWN_PROP",
|
|
679
|
+
"RAW_COLOR",
|
|
680
|
+
"ARBITRARY_SIZE",
|
|
681
|
+
"ROUNDED_CORNER",
|
|
682
|
+
"NATIVE_CONTROL",
|
|
683
|
+
/** The file does not parse. It was not checked against the contract. */
|
|
684
|
+
"SYNTAX_ERROR"
|
|
685
|
+
];
|
|
686
|
+
var LIMITATION_CODES = ["UNRESOLVED_SPREAD", "UNRESOLVED_VALUE"];
|
|
687
|
+
var SCOPE = {
|
|
688
|
+
checked: ["syntax", "contract", "house_rules"],
|
|
689
|
+
not_checked: ["types", "render"]
|
|
690
|
+
};
|
|
572
691
|
var PACKAGE = "@oprt/react";
|
|
573
692
|
var NATIVE = {
|
|
574
693
|
button: "Button",
|
|
@@ -604,11 +723,49 @@ function allowedChildren2(rules, name) {
|
|
|
604
723
|
}
|
|
605
724
|
return void 0;
|
|
606
725
|
}
|
|
726
|
+
var quote = (text2) => {
|
|
727
|
+
const flat = text2.replace(/\s+/g, " ").trim();
|
|
728
|
+
return flat.length > 48 ? `${flat.slice(0, 45)}...` : flat;
|
|
729
|
+
};
|
|
730
|
+
var MAX_SYNTAX_ERRORS = 10;
|
|
607
731
|
function checkSource(typescript, contract2, file, text2) {
|
|
608
732
|
const ts = typescript;
|
|
609
733
|
const kind = /\.tsx$/.test(file) ? ts.ScriptKind.TSX : ts.ScriptKind.JSX;
|
|
610
734
|
const source = ts.createSourceFile(file, text2, ts.ScriptTarget.Latest, true, kind);
|
|
735
|
+
const parseDiagnostics = source.parseDiagnostics;
|
|
736
|
+
if (!Array.isArray(parseDiagnostics))
|
|
737
|
+
throw new CliError(
|
|
738
|
+
`This TypeScript does not report syntax errors on a parsed file, so a file that does not parse could be called clean. Install TypeScript 5 or 6 in this project (${INSTALL_TYPESCRIPT}).`,
|
|
739
|
+
1,
|
|
740
|
+
{ code: "ERR_TYPESCRIPT_UNUSABLE" }
|
|
741
|
+
);
|
|
742
|
+
if (parseDiagnostics.length > 0) {
|
|
743
|
+
const ordered = [...parseDiagnostics].sort((a, b) => (a.start ?? 0) - (b.start ?? 0));
|
|
744
|
+
const shown = ordered.slice(0, MAX_SYNTAX_ERRORS);
|
|
745
|
+
return {
|
|
746
|
+
file,
|
|
747
|
+
status: "not_evaluated",
|
|
748
|
+
limitations: [],
|
|
749
|
+
findings: shown.map((diagnostic, index) => {
|
|
750
|
+
const { line, character } = source.getLineAndCharacterOfPosition(diagnostic.start ?? 0);
|
|
751
|
+
const more = ordered.length - shown.length;
|
|
752
|
+
return {
|
|
753
|
+
code: "SYNTAX_ERROR",
|
|
754
|
+
severity: "error",
|
|
755
|
+
file,
|
|
756
|
+
line: line + 1,
|
|
757
|
+
column: character + 1,
|
|
758
|
+
message: `${ts.flattenDiagnosticMessageText(diagnostic.messageText, " ")} (TS${diagnostic.code})` + (index === shown.length - 1 && more > 0 ? ` ${more} more syntax error${more === 1 ? "" : "s"} in this file not shown.` : "")
|
|
759
|
+
};
|
|
760
|
+
})
|
|
761
|
+
};
|
|
762
|
+
}
|
|
611
763
|
const findings = [];
|
|
764
|
+
const limitations = [];
|
|
765
|
+
const limit = (node, limitation) => {
|
|
766
|
+
const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
767
|
+
limitations.push({ ...limitation, file, line: line + 1, column: character + 1 });
|
|
768
|
+
};
|
|
612
769
|
const report = (node, finding) => {
|
|
613
770
|
const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
614
771
|
findings.push({
|
|
@@ -679,6 +836,31 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
679
836
|
return [];
|
|
680
837
|
};
|
|
681
838
|
const valueOf = (attribute) => attribute.initializer === void 0 ? void 0 : ts.isJsxExpression(attribute.initializer) ? attribute.initializer.expression : attribute.initializer;
|
|
839
|
+
const optionValue = (expression) => {
|
|
840
|
+
const read = [];
|
|
841
|
+
const unread = [];
|
|
842
|
+
const walk = (node) => {
|
|
843
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
|
|
844
|
+
read.push(node.text);
|
|
845
|
+
else if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isNonNullExpression(node) || ts.isTypeAssertionExpression(node) || typeof ts.isSatisfiesExpression === "function" && ts.isSatisfiesExpression(node))
|
|
846
|
+
walk(node.expression);
|
|
847
|
+
else if (ts.isConditionalExpression(node)) {
|
|
848
|
+
walk(node.whenTrue);
|
|
849
|
+
walk(node.whenFalse);
|
|
850
|
+
} else if (ts.isBinaryExpression(node)) {
|
|
851
|
+
const operator = node.operatorToken.kind;
|
|
852
|
+
if (operator === ts.SyntaxKind.QuestionQuestionToken || operator === ts.SyntaxKind.BarBarToken) {
|
|
853
|
+
walk(node.left);
|
|
854
|
+
walk(node.right);
|
|
855
|
+
} else if (operator === ts.SyntaxKind.AmpersandAmpersandToken) walk(node.right);
|
|
856
|
+
else unread.push(node);
|
|
857
|
+
} else if (!// No value: the component's default applies.
|
|
858
|
+
(ts.isIdentifier(node) && node.text === "undefined" || node.kind === ts.SyntaxKind.NullKeyword))
|
|
859
|
+
unread.push(node);
|
|
860
|
+
};
|
|
861
|
+
if (expression) walk(expression);
|
|
862
|
+
return { literals: read, unread };
|
|
863
|
+
};
|
|
682
864
|
const classText = (expression) => {
|
|
683
865
|
if (!expression) return "";
|
|
684
866
|
if (ts.isTemplateExpression(expression))
|
|
@@ -751,6 +933,13 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
751
933
|
if (component === null) return null;
|
|
752
934
|
const definition = contract2.components[component];
|
|
753
935
|
const spread = attributes.some((attribute) => ts.isJsxSpreadAttribute(attribute));
|
|
936
|
+
for (const attribute of attributes)
|
|
937
|
+
if (ts.isJsxSpreadAttribute(attribute))
|
|
938
|
+
limit(attribute, {
|
|
939
|
+
code: "UNRESOLVED_SPREAD",
|
|
940
|
+
component,
|
|
941
|
+
message: `Props spread into <${component}> (${quote(attribute.getText(source))}) were not read: option values and required props that arrive through them were not checked.`
|
|
942
|
+
});
|
|
754
943
|
const given = /* @__PURE__ */ new Set();
|
|
755
944
|
for (const attribute of attributes) {
|
|
756
945
|
if (!ts.isJsxAttribute(attribute)) continue;
|
|
@@ -759,18 +948,26 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
759
948
|
const known = Object.hasOwn(definition.props, prop) ? definition.props[prop] : void 0;
|
|
760
949
|
if (known === void 0) {
|
|
761
950
|
if (nativeAttribute(prop) || Object.hasOwn(definition.slots ?? {}, prop)) continue;
|
|
762
|
-
const
|
|
951
|
+
const names2 = Object.keys(definition.props);
|
|
763
952
|
report(attribute, {
|
|
764
953
|
code: "UNKNOWN_PROP",
|
|
765
954
|
severity: "warning",
|
|
766
955
|
component,
|
|
767
956
|
prop,
|
|
768
|
-
message: `${component} has no prop ${prop}.` + (
|
|
957
|
+
message: `${component} has no prop ${prop}.` + (names2.length === 0 ? " It takes no props of its own." : ` Its props: ${names2.slice(0, 14).join(", ")}${names2.length > 14 ? ", \u2026" : ""}.`)
|
|
769
958
|
});
|
|
770
959
|
continue;
|
|
771
960
|
}
|
|
772
961
|
if (known.type !== "enum" || known.values === void 0) continue;
|
|
773
|
-
|
|
962
|
+
const { literals: options, unread } = optionValue(valueOf(attribute));
|
|
963
|
+
for (const expression of unread)
|
|
964
|
+
limit(attribute, {
|
|
965
|
+
code: "UNRESOLVED_VALUE",
|
|
966
|
+
component,
|
|
967
|
+
prop,
|
|
968
|
+
message: `${component} ${prop} is set from an expression (${quote(expression.getText(source))}) whose value is not in the source, so it was not compared with the options.`
|
|
969
|
+
});
|
|
970
|
+
for (const literal of options) {
|
|
774
971
|
if (known.values.includes(literal)) continue;
|
|
775
972
|
const elsewhere = Object.entries(contract2.components).filter(([, other]) => other.props[prop]?.values?.includes(literal)).map(([name]) => name);
|
|
776
973
|
report(attribute, {
|
|
@@ -840,64 +1037,153 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
840
1037
|
ts.forEachChild(node, (child) => visit(child, ancestors, parent));
|
|
841
1038
|
};
|
|
842
1039
|
visit(source, [], null);
|
|
843
|
-
|
|
1040
|
+
const byPlace = (a, b) => a.line - b.line || a.column - b.column;
|
|
1041
|
+
return {
|
|
1042
|
+
file,
|
|
1043
|
+
status: limitations.length > 0 ? "partial" : "evaluated",
|
|
1044
|
+
findings: findings.sort(byPlace),
|
|
1045
|
+
limitations: limitations.sort(byPlace)
|
|
1046
|
+
};
|
|
844
1047
|
}
|
|
845
1048
|
var SKIPPED = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "coverage"]);
|
|
846
1049
|
var READ = /* @__PURE__ */ new Set([".tsx", ".jsx"]);
|
|
847
1050
|
var NOT_INTERFACE = /\.(?:test|spec|stories)\.[jt]sx$/;
|
|
1051
|
+
var excluded = (name) => SKIPPED.has(name) || name.startsWith(".");
|
|
848
1052
|
function* sources(path) {
|
|
849
|
-
|
|
850
|
-
if (SKIPPED.has(name) || name.startsWith(".") && name !== "." && name !== "..") return;
|
|
1053
|
+
if (excluded(basename(path))) return;
|
|
851
1054
|
if (statSync(path).isDirectory()) {
|
|
852
|
-
for (const entry of readdirSync(path).sort()) yield* sources(
|
|
853
|
-
} else if (READ.has(extname(path)) && !NOT_INTERFACE.test(
|
|
1055
|
+
for (const entry of readdirSync(path).sort()) yield* sources(join3(path, entry));
|
|
1056
|
+
} else if (READ.has(extname(path)) && !NOT_INTERFACE.test(basename(path))) yield path;
|
|
854
1057
|
}
|
|
855
|
-
function
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1058
|
+
function whyNotRead(path) {
|
|
1059
|
+
const name = basename(path);
|
|
1060
|
+
if (excluded(name)) return "excluded_folder";
|
|
1061
|
+
if (statSync(path).isDirectory()) return null;
|
|
1062
|
+
if (!READ.has(extname(path))) return "unsupported_extension";
|
|
1063
|
+
return NOT_INTERFACE.test(name) ? "test_or_story_file" : null;
|
|
1064
|
+
}
|
|
1065
|
+
function buildCheckReport(evaluations, context) {
|
|
1066
|
+
const findings = evaluations.flatMap(({ findings: found }) => found);
|
|
1067
|
+
const count = (status2) => evaluations.filter((evaluation) => evaluation.status === status2).length;
|
|
1068
|
+
const partial = count("partial");
|
|
1069
|
+
const notEvaluated = count("not_evaluated");
|
|
1070
|
+
const evaluated = evaluations.length - notEvaluated;
|
|
1071
|
+
const errors = findings.filter(({ severity }) => severity === "error").length;
|
|
1072
|
+
const warnings = findings.length - errors;
|
|
1073
|
+
let status;
|
|
1074
|
+
if (evaluations.length === 0) status = "no_files_evaluated";
|
|
1075
|
+
else if (evaluated === 0) status = "not_evaluated";
|
|
1076
|
+
else if (partial > 0 || notEvaluated > 0) status = "partially_evaluated";
|
|
1077
|
+
else status = findings.length > 0 ? "evaluated_with_findings" : "evaluated_clean";
|
|
1078
|
+
return {
|
|
1079
|
+
evaluation_status: status,
|
|
1080
|
+
files: evaluations.length,
|
|
1081
|
+
findings,
|
|
1082
|
+
summary: { errors, warnings },
|
|
1083
|
+
coverage: {
|
|
1084
|
+
files_found: evaluations.length,
|
|
1085
|
+
files_evaluated: evaluated,
|
|
1086
|
+
files_partial: partial,
|
|
1087
|
+
files_not_evaluated: notEvaluated
|
|
1088
|
+
},
|
|
1089
|
+
limitations: evaluations.flatMap(({ limitations }) => limitations),
|
|
1090
|
+
skipped_inputs: context.skipped ?? [],
|
|
1091
|
+
scope: { checked: [...SCOPE.checked], not_checked: [...SCOPE.not_checked] },
|
|
1092
|
+
parser: context.parser
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function checkExitCode(report, { strict }) {
|
|
1096
|
+
if (report.evaluation_status === "no_files_evaluated") return 1;
|
|
1097
|
+
if (report.summary.errors > 0 || report.coverage.files_not_evaluated > 0) return 1;
|
|
1098
|
+
return strict && report.summary.warnings > 0 ? 1 : 0;
|
|
1099
|
+
}
|
|
1100
|
+
var describeProvided = (typescript) => ({
|
|
1101
|
+
name: "typescript",
|
|
1102
|
+
version: typescript.version ?? "unknown",
|
|
1103
|
+
source: "provided"
|
|
1104
|
+
});
|
|
1105
|
+
function resolveParser(cwd, provided, who) {
|
|
1106
|
+
if (provided !== void 0 && provided !== null)
|
|
1107
|
+
return { typescript: provided, parser: describeProvided(provided) };
|
|
1108
|
+
const probe = provided === null ? null : probeTypeScript(cwd);
|
|
1109
|
+
if (probe?.ready) {
|
|
1110
|
+
const { name, version, source } = probe.resolution;
|
|
1111
|
+
return { typescript: probe.typescript, parser: { name, version, source } };
|
|
861
1112
|
}
|
|
862
|
-
|
|
1113
|
+
if (probe !== null && probe.reason === "typescript_unusable")
|
|
1114
|
+
throw new CliError(`${who} cannot use the TypeScript it found: ${probe.detail}`, 1, {
|
|
1115
|
+
code: "ERR_TYPESCRIPT_UNUSABLE"
|
|
1116
|
+
});
|
|
1117
|
+
throw new CliError(
|
|
1118
|
+
`${who} reads code with the project's TypeScript, and found none. Install typescript in this project (${INSTALL_TYPESCRIPT}) and run it again. It looked from ${cwd}.`,
|
|
1119
|
+
1,
|
|
1120
|
+
{ code: "ERR_TYPESCRIPT_MISSING" }
|
|
1121
|
+
);
|
|
863
1122
|
}
|
|
864
1123
|
function checkPaths(paths, context) {
|
|
865
1124
|
const root = resolve2(context.cwd);
|
|
866
1125
|
const files = [];
|
|
1126
|
+
const skipped = [];
|
|
867
1127
|
for (const path of paths) {
|
|
868
1128
|
const absolute = resolve2(root, path);
|
|
869
1129
|
if (!existsSync2(absolute))
|
|
870
1130
|
throw new CliError(`No such file or folder: ${path}`, 2, { code: "ERR_PATH_NOT_FOUND" });
|
|
1131
|
+
const reason = whyNotRead(absolute);
|
|
1132
|
+
if (reason !== null) skipped.push({ path, reason });
|
|
871
1133
|
files.push(...sources(absolute));
|
|
872
1134
|
}
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
throw new CliError(
|
|
876
|
-
"oprt check reads code with the project's TypeScript, and found none. Install typescript in this project (npm install -D typescript) and run it again.",
|
|
877
|
-
1,
|
|
878
|
-
{ code: "ERR_TYPESCRIPT_MISSING" }
|
|
879
|
-
);
|
|
880
|
-
const findings = [...new Set(files)].flatMap(
|
|
1135
|
+
const { typescript, parser } = resolveParser(root, context.typescript, "oprt check");
|
|
1136
|
+
const evaluations = [...new Set(files)].map(
|
|
881
1137
|
(file) => checkSource(
|
|
882
|
-
|
|
1138
|
+
typescript,
|
|
883
1139
|
context.contract,
|
|
884
1140
|
relative2(root, file).split(sep2).join("/"),
|
|
885
1141
|
readFileSync3(file, "utf8")
|
|
886
1142
|
)
|
|
887
1143
|
);
|
|
888
|
-
return {
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
}
|
|
895
|
-
|
|
1144
|
+
return buildCheckReport(evaluations, { parser, skipped });
|
|
1145
|
+
}
|
|
1146
|
+
var plural = (amount, word) => `${amount} ${word}${amount === 1 ? "" : "s"}`;
|
|
1147
|
+
var MAX_LIMITS_SHOWN = 20;
|
|
1148
|
+
function renderCheckText(report) {
|
|
1149
|
+
const lines = report.findings.map(
|
|
1150
|
+
({ file, line, column, severity, code, message }) => `${file}:${line}:${column} ${severity} ${code} ${message}`
|
|
1151
|
+
);
|
|
1152
|
+
const shown = report.limitations.slice(0, MAX_LIMITS_SHOWN);
|
|
1153
|
+
for (const { file, line, column, code, message } of shown)
|
|
1154
|
+
lines.push(`${file}:${line}:${column} limit ${code} ${message}`);
|
|
1155
|
+
if (report.limitations.length > shown.length)
|
|
1156
|
+
lines.push(
|
|
1157
|
+
` ...and ${plural(report.limitations.length - shown.length, "more limit")}; the JSON lists them all.`
|
|
1158
|
+
);
|
|
1159
|
+
const { coverage, evaluation_status: status } = report;
|
|
1160
|
+
if (status === "no_files_evaluated") {
|
|
1161
|
+
lines.push("No .tsx or .jsx file was read, so nothing was checked.");
|
|
1162
|
+
const labels = {
|
|
1163
|
+
unsupported_extension: "not a .tsx or .jsx file",
|
|
1164
|
+
test_or_story_file: "a test, spec or story file, which is skipped",
|
|
1165
|
+
excluded_folder: "a folder that is skipped (node_modules, dist, build, out, coverage, hidden)"
|
|
1166
|
+
};
|
|
1167
|
+
for (const { path, reason } of report.skipped_inputs)
|
|
1168
|
+
lines.push(` ${path}: ${labels[reason]}`);
|
|
1169
|
+
} else if (coverage.files_not_evaluated > 0)
|
|
1170
|
+
lines.push(
|
|
1171
|
+
`${plural(coverage.files_not_evaluated, "file")} did not parse and ${coverage.files_not_evaluated === 1 ? "was" : "were"} not checked against the contract.`
|
|
1172
|
+
);
|
|
1173
|
+
else if (status === "evaluated_clean")
|
|
1174
|
+
lines.push(
|
|
1175
|
+
"Nothing found. Checked syntax, option values, nesting, required props and the house rules; types and rendering are not checked."
|
|
1176
|
+
);
|
|
1177
|
+
lines.push(
|
|
1178
|
+
`${plural(report.summary.errors, "error")}, ${plural(report.summary.warnings, "warning")} in ${plural(report.files, "file")}. Status: ${status}.`
|
|
1179
|
+
);
|
|
1180
|
+
return `${lines.join("\n")}
|
|
1181
|
+
`;
|
|
896
1182
|
}
|
|
897
1183
|
|
|
898
1184
|
// src/doctor.ts
|
|
899
1185
|
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
900
|
-
import { dirname as dirname3, join as
|
|
1186
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
901
1187
|
|
|
902
1188
|
// src/agent-block.ts
|
|
903
1189
|
var START = "<!-- OPERATOR:START -->";
|
|
@@ -982,13 +1268,17 @@ var INSTRUCTION_FILES = [
|
|
|
982
1268
|
var minor = (version) => version.split(".").slice(0, 2).join(".");
|
|
983
1269
|
function installedVersion(cwd, name) {
|
|
984
1270
|
for (let folder = cwd; ; folder = dirname3(folder)) {
|
|
985
|
-
const manifest =
|
|
1271
|
+
const manifest = join4(folder, "node_modules", name, "package.json");
|
|
986
1272
|
if (existsSync3(manifest))
|
|
987
1273
|
return JSON.parse(readFileSync4(manifest, "utf8")).version;
|
|
988
1274
|
if (dirname3(folder) === folder) return void 0;
|
|
989
1275
|
}
|
|
990
1276
|
}
|
|
991
|
-
function doctor({
|
|
1277
|
+
function doctor({
|
|
1278
|
+
cwd,
|
|
1279
|
+
contract: contract2,
|
|
1280
|
+
parser = probeTypeScript(cwd)
|
|
1281
|
+
}) {
|
|
992
1282
|
const checks = [];
|
|
993
1283
|
for (const name of PACKAGES) {
|
|
994
1284
|
const version = installedVersion(cwd, name);
|
|
@@ -1010,7 +1300,7 @@ function doctor({ cwd, contract: contract2 }) {
|
|
|
1010
1300
|
else checks.push({ id, status: "ok", message: `${name} ${version} is installed.` });
|
|
1011
1301
|
}
|
|
1012
1302
|
const written = INSTRUCTION_FILES.map((path) => {
|
|
1013
|
-
const file =
|
|
1303
|
+
const file = join4(cwd, path);
|
|
1014
1304
|
return {
|
|
1015
1305
|
path,
|
|
1016
1306
|
version: existsSync3(file) ? blockVersion(readFileSync4(file, "utf8")) : void 0
|
|
@@ -1037,8 +1327,55 @@ function doctor({ cwd, contract: contract2 }) {
|
|
|
1037
1327
|
status: "ok",
|
|
1038
1328
|
message: `${written.map(({ path }) => path).join(", ")} carries the OPERATOR block (${contract2.version}).`
|
|
1039
1329
|
});
|
|
1330
|
+
const install2 = `${INSTALL_TYPESCRIPT} (or your package manager's equivalent), then run oprt doctor again. Lookups work without it.`;
|
|
1331
|
+
let checkCode;
|
|
1332
|
+
if (parser.ready) {
|
|
1333
|
+
const { version, resolved_from, source } = parser.resolution;
|
|
1334
|
+
checkCode = {
|
|
1335
|
+
status: "ready",
|
|
1336
|
+
message: `TypeScript ${version}, resolved from ${resolved_from}${source === "project" ? "" : " (beside oprt, not this project)"}.`,
|
|
1337
|
+
typescript: { version, resolved_from, source }
|
|
1338
|
+
};
|
|
1339
|
+
checks.push(
|
|
1340
|
+
source === "project" ? {
|
|
1341
|
+
id: "typescript",
|
|
1342
|
+
status: "ok",
|
|
1343
|
+
message: `TypeScript ${version} is installed in this project (${resolved_from}).`
|
|
1344
|
+
} : {
|
|
1345
|
+
id: "typescript",
|
|
1346
|
+
status: "warn",
|
|
1347
|
+
message: `TypeScript ${version} is not in this project; the one beside oprt is used (${resolved_from}).`,
|
|
1348
|
+
fix: `${INSTALL_TYPESCRIPT}, so checking reads code with a version installed in the project.`
|
|
1349
|
+
}
|
|
1350
|
+
);
|
|
1351
|
+
} else {
|
|
1352
|
+
checkCode = {
|
|
1353
|
+
status: "blocked",
|
|
1354
|
+
message: `${parser.reason}: ${parser.detail}`,
|
|
1355
|
+
reason: parser.reason,
|
|
1356
|
+
fix: install2
|
|
1357
|
+
};
|
|
1358
|
+
checks.push({
|
|
1359
|
+
id: "typescript",
|
|
1360
|
+
status: "fail",
|
|
1361
|
+
message: `oprt check and the check_code tool cannot run (${parser.reason}): ${parser.detail}`,
|
|
1362
|
+
fix: install2
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1040
1365
|
const count = (status) => checks.filter((check) => check.status === status).length;
|
|
1041
|
-
return {
|
|
1366
|
+
return {
|
|
1367
|
+
consumer_directory: cwd,
|
|
1368
|
+
operations: {
|
|
1369
|
+
lookup: {
|
|
1370
|
+
status: "ready",
|
|
1371
|
+
message: `search, component and list read the contract bundled with oprt (${contract2.version}): always available.`,
|
|
1372
|
+
contract_version: contract2.version
|
|
1373
|
+
},
|
|
1374
|
+
check_code: checkCode
|
|
1375
|
+
},
|
|
1376
|
+
checks,
|
|
1377
|
+
summary: { ok: count("ok"), warn: count("warn"), fail: count("fail") }
|
|
1378
|
+
};
|
|
1042
1379
|
}
|
|
1043
1380
|
|
|
1044
1381
|
// src/init.ts
|
|
@@ -1096,8 +1433,536 @@ function init(options, context) {
|
|
|
1096
1433
|
return { version: context.contract.version, dryRun, files };
|
|
1097
1434
|
}
|
|
1098
1435
|
|
|
1436
|
+
// src/results.ts
|
|
1437
|
+
var RESULT_TYPES = [
|
|
1438
|
+
"check",
|
|
1439
|
+
"search",
|
|
1440
|
+
"component.detail",
|
|
1441
|
+
"component.batch",
|
|
1442
|
+
"component.list",
|
|
1443
|
+
"init",
|
|
1444
|
+
"doctor",
|
|
1445
|
+
"add",
|
|
1446
|
+
"list",
|
|
1447
|
+
"instructions"
|
|
1448
|
+
];
|
|
1449
|
+
var envelope = (type, data, contract2) => ({
|
|
1450
|
+
apiVersion: 1,
|
|
1451
|
+
type,
|
|
1452
|
+
identity: identityOf(contract2),
|
|
1453
|
+
data
|
|
1454
|
+
});
|
|
1455
|
+
function errorEnvelope(error, contract2) {
|
|
1456
|
+
const known = error instanceof CliError ? error : void 0;
|
|
1457
|
+
const identity = contract2 === void 0 ? { cli_version: VERSION, schema_version: RESULT_SCHEMA_VERSION } : (({ cli_version, schema_version, contract_version, contract_digest }) => ({
|
|
1458
|
+
cli_version,
|
|
1459
|
+
schema_version,
|
|
1460
|
+
contract_version,
|
|
1461
|
+
contract_digest
|
|
1462
|
+
}))(identityOf(contract2));
|
|
1463
|
+
return {
|
|
1464
|
+
apiVersion: 1,
|
|
1465
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1466
|
+
code: known?.code ?? "ERR_FAILED",
|
|
1467
|
+
...known?.suggestions === void 0 ? {} : { suggestions: known.suggestions },
|
|
1468
|
+
identity
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
function componentData(detail, dense) {
|
|
1472
|
+
if (!dense) return { ...detail, dense };
|
|
1473
|
+
const { description: _description, ...rest } = detail;
|
|
1474
|
+
return {
|
|
1475
|
+
...rest,
|
|
1476
|
+
props: detail.props.map(
|
|
1477
|
+
(prop) => prop.description === void 0 ? prop : { ...prop, description: firstSentence(prop.description) }
|
|
1478
|
+
),
|
|
1479
|
+
dense
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
function instructionsData(contract2) {
|
|
1483
|
+
return {
|
|
1484
|
+
rules: contract2.guidance.rules.map(({ id, rule, wrong, right }) => ({
|
|
1485
|
+
id,
|
|
1486
|
+
rule,
|
|
1487
|
+
...wrong === void 0 ? {} : { wrong },
|
|
1488
|
+
...right === void 0 ? {} : { right }
|
|
1489
|
+
})),
|
|
1490
|
+
components: componentIndex(contract2),
|
|
1491
|
+
recipes: Object.entries(contract2.patterns ?? {}).map(([name, { description, install: install2 }]) => ({
|
|
1492
|
+
name,
|
|
1493
|
+
description,
|
|
1494
|
+
install: install2
|
|
1495
|
+
}))
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
var str = (extra = {}) => ({ type: "string", ...extra });
|
|
1499
|
+
var int = () => ({ type: "integer", minimum: 0 });
|
|
1500
|
+
var bool = () => ({ type: "boolean" });
|
|
1501
|
+
var arr = (items) => ({ type: "array", items });
|
|
1502
|
+
var names = () => arr(str());
|
|
1503
|
+
var words2 = (...values) => ({ type: "string", enum: [...values] });
|
|
1504
|
+
var ref = (name) => ({ $ref: `#/$defs/${name}` });
|
|
1505
|
+
var obj = (properties, optional = [], open = false) => ({
|
|
1506
|
+
type: "object",
|
|
1507
|
+
properties,
|
|
1508
|
+
required: Object.keys(properties).filter((key) => !optional.includes(key)),
|
|
1509
|
+
additionalProperties: open
|
|
1510
|
+
});
|
|
1511
|
+
var scalar = { type: ["string", "number", "boolean"] };
|
|
1512
|
+
var DEFS = {
|
|
1513
|
+
identity: obj({
|
|
1514
|
+
cli_version: str(),
|
|
1515
|
+
contract_version: str(),
|
|
1516
|
+
contract_digest: str({ pattern: "^sha256:[0-9a-f]{64}$" }),
|
|
1517
|
+
schema_version: { type: "integer", minimum: 1 }
|
|
1518
|
+
}),
|
|
1519
|
+
error_identity: obj(
|
|
1520
|
+
{
|
|
1521
|
+
cli_version: str(),
|
|
1522
|
+
schema_version: { type: "integer", minimum: 1 },
|
|
1523
|
+
contract_version: str(),
|
|
1524
|
+
contract_digest: str({ pattern: "^sha256:[0-9a-f]{64}$" })
|
|
1525
|
+
},
|
|
1526
|
+
["contract_version", "contract_digest"]
|
|
1527
|
+
),
|
|
1528
|
+
finding: obj(
|
|
1529
|
+
{
|
|
1530
|
+
code: words2(...FINDING_CODES),
|
|
1531
|
+
severity: words2("error", "warning"),
|
|
1532
|
+
file: str(),
|
|
1533
|
+
line: { type: "integer", minimum: 1 },
|
|
1534
|
+
column: { type: "integer", minimum: 1 },
|
|
1535
|
+
component: str(),
|
|
1536
|
+
prop: str(),
|
|
1537
|
+
message: str(),
|
|
1538
|
+
valid_values: names(),
|
|
1539
|
+
suggestions: names()
|
|
1540
|
+
},
|
|
1541
|
+
["component", "prop", "valid_values", "suggestions"]
|
|
1542
|
+
),
|
|
1543
|
+
limitation: obj(
|
|
1544
|
+
{
|
|
1545
|
+
code: words2(...LIMITATION_CODES),
|
|
1546
|
+
file: str(),
|
|
1547
|
+
line: { type: "integer", minimum: 1 },
|
|
1548
|
+
column: { type: "integer", minimum: 1 },
|
|
1549
|
+
component: str(),
|
|
1550
|
+
prop: str(),
|
|
1551
|
+
message: str()
|
|
1552
|
+
},
|
|
1553
|
+
["prop"]
|
|
1554
|
+
),
|
|
1555
|
+
skipped_input: obj({
|
|
1556
|
+
path: str(),
|
|
1557
|
+
reason: words2("unsupported_extension", "test_or_story_file", "excluded_folder")
|
|
1558
|
+
}),
|
|
1559
|
+
parser: obj({
|
|
1560
|
+
name: words2("typescript"),
|
|
1561
|
+
version: str(),
|
|
1562
|
+
source: words2("project", "installer", "provided")
|
|
1563
|
+
}),
|
|
1564
|
+
check_data: obj({
|
|
1565
|
+
evaluation_status: words2(
|
|
1566
|
+
"evaluated_clean",
|
|
1567
|
+
"evaluated_with_findings",
|
|
1568
|
+
"partially_evaluated",
|
|
1569
|
+
"not_evaluated",
|
|
1570
|
+
"no_files_evaluated"
|
|
1571
|
+
),
|
|
1572
|
+
files: int(),
|
|
1573
|
+
findings: arr(ref("finding")),
|
|
1574
|
+
summary: obj({ errors: int(), warnings: int() }),
|
|
1575
|
+
coverage: obj({
|
|
1576
|
+
files_found: int(),
|
|
1577
|
+
files_evaluated: int(),
|
|
1578
|
+
files_partial: int(),
|
|
1579
|
+
files_not_evaluated: int()
|
|
1580
|
+
}),
|
|
1581
|
+
limitations: arr(ref("limitation")),
|
|
1582
|
+
skipped_inputs: arr(ref("skipped_input")),
|
|
1583
|
+
scope: obj({ checked: names(), not_checked: names() }),
|
|
1584
|
+
parser: ref("parser")
|
|
1585
|
+
}),
|
|
1586
|
+
search_result: obj(
|
|
1587
|
+
{
|
|
1588
|
+
kind: words2("component", "recipe"),
|
|
1589
|
+
name: str(),
|
|
1590
|
+
purpose: str(),
|
|
1591
|
+
score: { type: "number" },
|
|
1592
|
+
why: str(),
|
|
1593
|
+
next: str()
|
|
1594
|
+
},
|
|
1595
|
+
["why"]
|
|
1596
|
+
),
|
|
1597
|
+
search_data: obj({
|
|
1598
|
+
query: str(),
|
|
1599
|
+
limit: { type: "integer", minimum: 1 },
|
|
1600
|
+
matchCount: int(),
|
|
1601
|
+
results: arr(ref("search_result"))
|
|
1602
|
+
}),
|
|
1603
|
+
index_entry: obj({ name: str(), purpose: str(), parts: names() }),
|
|
1604
|
+
component_list_data: obj({ components: arr(ref("index_entry")) }),
|
|
1605
|
+
detail_prop: obj(
|
|
1606
|
+
{
|
|
1607
|
+
name: str(),
|
|
1608
|
+
type: str(),
|
|
1609
|
+
values: names(),
|
|
1610
|
+
types: names(),
|
|
1611
|
+
default: scalar,
|
|
1612
|
+
required: bool(),
|
|
1613
|
+
description: str(),
|
|
1614
|
+
notes: { type: "object", additionalProperties: str() }
|
|
1615
|
+
},
|
|
1616
|
+
["values", "types", "default", "required", "description", "notes"],
|
|
1617
|
+
// The contract may describe a prop with more than these (an array's item type, for one).
|
|
1618
|
+
true
|
|
1619
|
+
),
|
|
1620
|
+
component_detail: obj(
|
|
1621
|
+
{
|
|
1622
|
+
name: str(),
|
|
1623
|
+
description: str(),
|
|
1624
|
+
use_for: names(),
|
|
1625
|
+
not_for: arr(obj({ need: str(), use: str() })),
|
|
1626
|
+
part_of: str(),
|
|
1627
|
+
parts: names(),
|
|
1628
|
+
props: arr(ref("detail_prop")),
|
|
1629
|
+
slots: {
|
|
1630
|
+
type: "object",
|
|
1631
|
+
additionalProperties: obj({ type: str(), required: bool() }, ["required"])
|
|
1632
|
+
},
|
|
1633
|
+
composable_with: names(),
|
|
1634
|
+
never_inside: names(),
|
|
1635
|
+
allowed_children: names(),
|
|
1636
|
+
example: str(),
|
|
1637
|
+
dense: bool()
|
|
1638
|
+
},
|
|
1639
|
+
["description", "part_of", "slots", "allowed_children"]
|
|
1640
|
+
),
|
|
1641
|
+
component_batch_data: obj({ components: arr(ref("component_detail")) }),
|
|
1642
|
+
doctor_check: obj(
|
|
1643
|
+
{ id: str(), status: words2("ok", "warn", "fail"), message: str(), fix: str() },
|
|
1644
|
+
["fix"]
|
|
1645
|
+
),
|
|
1646
|
+
doctor_data: obj({
|
|
1647
|
+
consumer_directory: str(),
|
|
1648
|
+
operations: obj({
|
|
1649
|
+
lookup: obj({ status: words2("ready"), message: str(), contract_version: str() }),
|
|
1650
|
+
check_code: obj(
|
|
1651
|
+
{
|
|
1652
|
+
status: words2("ready", "blocked"),
|
|
1653
|
+
message: str(),
|
|
1654
|
+
reason: words2("typescript_missing", "typescript_unusable"),
|
|
1655
|
+
fix: str(),
|
|
1656
|
+
typescript: obj({
|
|
1657
|
+
version: str(),
|
|
1658
|
+
resolved_from: str(),
|
|
1659
|
+
source: words2("project", "installer", "provided")
|
|
1660
|
+
})
|
|
1661
|
+
},
|
|
1662
|
+
["reason", "fix", "typescript"]
|
|
1663
|
+
)
|
|
1664
|
+
}),
|
|
1665
|
+
checks: arr(ref("doctor_check")),
|
|
1666
|
+
summary: obj({ ok: int(), warn: int(), fail: int() })
|
|
1667
|
+
}),
|
|
1668
|
+
init_data: obj({
|
|
1669
|
+
version: str(),
|
|
1670
|
+
dryRun: bool(),
|
|
1671
|
+
files: arr(
|
|
1672
|
+
obj({
|
|
1673
|
+
path: str(),
|
|
1674
|
+
action: words2("create", "append", "update", "unchanged", "remove", "delete", "absent")
|
|
1675
|
+
})
|
|
1676
|
+
)
|
|
1677
|
+
}),
|
|
1678
|
+
install_file: obj({
|
|
1679
|
+
path: str(),
|
|
1680
|
+
destination: str(),
|
|
1681
|
+
action: words2("create", "overwrite", "skip")
|
|
1682
|
+
}),
|
|
1683
|
+
add_data: obj({
|
|
1684
|
+
outputDirectory: str(),
|
|
1685
|
+
entries: names(),
|
|
1686
|
+
dependencies: names(),
|
|
1687
|
+
devDependencies: names(),
|
|
1688
|
+
ranges: { type: "object", additionalProperties: str() },
|
|
1689
|
+
files: arr(ref("install_file")),
|
|
1690
|
+
dryRun: bool(),
|
|
1691
|
+
setup: ref("setup")
|
|
1692
|
+
}),
|
|
1693
|
+
setup: obj({
|
|
1694
|
+
tailwind: str(),
|
|
1695
|
+
packages: arr(obj({ name: str(), range: str(), dev: bool(), why: str() })),
|
|
1696
|
+
stylesheet: arr(obj({ line: str(), why: str() })),
|
|
1697
|
+
root_import: obj({ line: str(), note: str() }),
|
|
1698
|
+
not_needed: arr(obj({ name: str(), why: str() }))
|
|
1699
|
+
}),
|
|
1700
|
+
list_data: obj({
|
|
1701
|
+
version: str(),
|
|
1702
|
+
entries: arr(
|
|
1703
|
+
obj(
|
|
1704
|
+
{
|
|
1705
|
+
name: str(),
|
|
1706
|
+
slug: str(),
|
|
1707
|
+
kind: words2("component", "recipe"),
|
|
1708
|
+
description: str(),
|
|
1709
|
+
files: names(),
|
|
1710
|
+
dependencies: names(),
|
|
1711
|
+
devDependencies: names()
|
|
1712
|
+
},
|
|
1713
|
+
["devDependencies"]
|
|
1714
|
+
)
|
|
1715
|
+
),
|
|
1716
|
+
ranges: { type: "object", additionalProperties: str() }
|
|
1717
|
+
}),
|
|
1718
|
+
instructions_data: obj({
|
|
1719
|
+
rules: arr(obj({ id: str(), rule: str(), wrong: str(), right: str() }, ["wrong", "right"])),
|
|
1720
|
+
components: arr(ref("index_entry")),
|
|
1721
|
+
recipes: arr(obj({ name: str(), description: str(), install: str() }))
|
|
1722
|
+
})
|
|
1723
|
+
};
|
|
1724
|
+
var DATA = {
|
|
1725
|
+
check: "check_data",
|
|
1726
|
+
search: "search_data",
|
|
1727
|
+
"component.detail": "component_detail",
|
|
1728
|
+
"component.batch": "component_batch_data",
|
|
1729
|
+
"component.list": "component_list_data",
|
|
1730
|
+
init: "init_data",
|
|
1731
|
+
doctor: "doctor_data",
|
|
1732
|
+
add: "add_data",
|
|
1733
|
+
list: "list_data",
|
|
1734
|
+
instructions: "instructions_data"
|
|
1735
|
+
};
|
|
1736
|
+
var success = (type) => obj({
|
|
1737
|
+
apiVersion: { const: 1 },
|
|
1738
|
+
type: { const: type },
|
|
1739
|
+
identity: ref("identity"),
|
|
1740
|
+
data: ref(DATA[type])
|
|
1741
|
+
});
|
|
1742
|
+
var failure = () => obj(
|
|
1743
|
+
{
|
|
1744
|
+
apiVersion: { const: 1 },
|
|
1745
|
+
error: str(),
|
|
1746
|
+
code: str({ pattern: "^ERR_[A-Z_]+$" }),
|
|
1747
|
+
suggestions: names(),
|
|
1748
|
+
identity: ref("error_identity")
|
|
1749
|
+
},
|
|
1750
|
+
["suggestions"]
|
|
1751
|
+
);
|
|
1752
|
+
function bundle(root) {
|
|
1753
|
+
const used = {};
|
|
1754
|
+
const visit = (node) => {
|
|
1755
|
+
if (Array.isArray(node)) node.forEach(visit);
|
|
1756
|
+
else if (typeof node === "object" && node !== null) {
|
|
1757
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1758
|
+
if (key === "$ref" && typeof value === "string") {
|
|
1759
|
+
const name = value.replace("#/$defs/", "");
|
|
1760
|
+
if (used[name] === void 0) {
|
|
1761
|
+
used[name] = DEFS[name];
|
|
1762
|
+
visit(DEFS[name]);
|
|
1763
|
+
}
|
|
1764
|
+
} else visit(value);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
visit(root);
|
|
1769
|
+
return { ...root, $defs: used };
|
|
1770
|
+
}
|
|
1771
|
+
function schemaFor(type) {
|
|
1772
|
+
return bundle(type === "error" ? failure() : success(type));
|
|
1773
|
+
}
|
|
1774
|
+
var S = { type: "string" };
|
|
1775
|
+
var I = { type: "integer" };
|
|
1776
|
+
var B = { type: "boolean" };
|
|
1777
|
+
var list = (items) => items === void 0 ? { type: "array" } : { type: "array", items };
|
|
1778
|
+
var shape = (properties, required) => ({
|
|
1779
|
+
type: "object",
|
|
1780
|
+
properties,
|
|
1781
|
+
...required === void 0 ? {} : { required }
|
|
1782
|
+
});
|
|
1783
|
+
var strings = list(S);
|
|
1784
|
+
var SUMMARIES = {
|
|
1785
|
+
check: shape(
|
|
1786
|
+
{
|
|
1787
|
+
evaluation_status: words2(
|
|
1788
|
+
"evaluated_clean",
|
|
1789
|
+
"evaluated_with_findings",
|
|
1790
|
+
"partially_evaluated",
|
|
1791
|
+
"not_evaluated",
|
|
1792
|
+
"no_files_evaluated"
|
|
1793
|
+
),
|
|
1794
|
+
findings: list(
|
|
1795
|
+
shape({
|
|
1796
|
+
code: S,
|
|
1797
|
+
severity: words2("error", "warning"),
|
|
1798
|
+
file: S,
|
|
1799
|
+
line: I,
|
|
1800
|
+
column: I,
|
|
1801
|
+
message: S
|
|
1802
|
+
})
|
|
1803
|
+
),
|
|
1804
|
+
summary: shape({ errors: I, warnings: I }),
|
|
1805
|
+
coverage: shape({ files_evaluated: I, files_partial: I, files_not_evaluated: I }),
|
|
1806
|
+
limitations: list(shape({ code: S, file: S, line: I, column: I, message: S })),
|
|
1807
|
+
skipped_inputs: list(),
|
|
1808
|
+
scope: { type: "object" },
|
|
1809
|
+
parser: { type: "object" }
|
|
1810
|
+
},
|
|
1811
|
+
["evaluation_status", "findings", "summary", "coverage", "limitations"]
|
|
1812
|
+
),
|
|
1813
|
+
search: shape(
|
|
1814
|
+
{
|
|
1815
|
+
matchCount: I,
|
|
1816
|
+
results: list(
|
|
1817
|
+
shape({ kind: words2("component", "recipe"), name: S, purpose: S, why: S, next: S })
|
|
1818
|
+
)
|
|
1819
|
+
},
|
|
1820
|
+
["results"]
|
|
1821
|
+
),
|
|
1822
|
+
"component.detail": shape(
|
|
1823
|
+
{
|
|
1824
|
+
name: S,
|
|
1825
|
+
description: S,
|
|
1826
|
+
use_for: strings,
|
|
1827
|
+
not_for: list(),
|
|
1828
|
+
part_of: S,
|
|
1829
|
+
parts: strings,
|
|
1830
|
+
props: list(
|
|
1831
|
+
shape({ name: S, type: S, values: strings, required: B, notes: { type: "object" } })
|
|
1832
|
+
),
|
|
1833
|
+
slots: { type: "object" },
|
|
1834
|
+
composable_with: strings,
|
|
1835
|
+
never_inside: strings,
|
|
1836
|
+
allowed_children: strings,
|
|
1837
|
+
example: S,
|
|
1838
|
+
dense: B
|
|
1839
|
+
},
|
|
1840
|
+
["name", "props", "example", "dense"]
|
|
1841
|
+
),
|
|
1842
|
+
"component.list": shape({ components: list() }, ["components"]),
|
|
1843
|
+
"component.batch": shape({ components: list() }, ["components"]),
|
|
1844
|
+
instructions: shape(
|
|
1845
|
+
{
|
|
1846
|
+
rules: list(),
|
|
1847
|
+
components: list(),
|
|
1848
|
+
recipes: list()
|
|
1849
|
+
},
|
|
1850
|
+
["rules", "components", "recipes"]
|
|
1851
|
+
),
|
|
1852
|
+
init: shape({ files: list() }),
|
|
1853
|
+
doctor: shape({ operations: { type: "object" }, checks: list() }),
|
|
1854
|
+
add: shape({ files: list(), setup: { type: "object" } }),
|
|
1855
|
+
list: shape({ entries: list() })
|
|
1856
|
+
};
|
|
1857
|
+
function shareRepeats(schema) {
|
|
1858
|
+
const count = /* @__PURE__ */ new Map();
|
|
1859
|
+
const tally = (node) => {
|
|
1860
|
+
if (Array.isArray(node)) node.forEach(tally);
|
|
1861
|
+
else if (typeof node === "object" && node !== null) {
|
|
1862
|
+
const text2 = JSON.stringify(node);
|
|
1863
|
+
if (text2.length >= 30) count.set(text2, (count.get(text2) ?? 0) + 1);
|
|
1864
|
+
Object.values(node).forEach(tally);
|
|
1865
|
+
}
|
|
1866
|
+
};
|
|
1867
|
+
tally(schema);
|
|
1868
|
+
const repeated = [...count].filter(([, times]) => times >= 3).map(([text2]) => text2);
|
|
1869
|
+
if (repeated.length === 0) return schema;
|
|
1870
|
+
const names2 = new Map(
|
|
1871
|
+
repeated.map((text2, index) => [text2, index === 0 ? "strings" : `part${index}`])
|
|
1872
|
+
);
|
|
1873
|
+
const defs = {};
|
|
1874
|
+
const rewrite = (node) => {
|
|
1875
|
+
if (Array.isArray(node)) return node.map(rewrite);
|
|
1876
|
+
if (typeof node !== "object" || node === null) return node;
|
|
1877
|
+
const name = names2.get(JSON.stringify(node));
|
|
1878
|
+
if (name !== void 0) {
|
|
1879
|
+
defs[name] = node;
|
|
1880
|
+
return { $ref: `#/$defs/${name}` };
|
|
1881
|
+
}
|
|
1882
|
+
return Object.fromEntries(Object.entries(node).map(([key, value]) => [key, rewrite(value)]));
|
|
1883
|
+
};
|
|
1884
|
+
const rewritten = rewrite(schema);
|
|
1885
|
+
return { ...rewritten, $defs: defs };
|
|
1886
|
+
}
|
|
1887
|
+
function outputSchemaFor(type) {
|
|
1888
|
+
return shareRepeats({
|
|
1889
|
+
type: "object",
|
|
1890
|
+
properties: {
|
|
1891
|
+
type: { const: type },
|
|
1892
|
+
// The four versions and the digest; the docs name them, and so does every answer.
|
|
1893
|
+
identity: { type: "object" },
|
|
1894
|
+
data: SUMMARIES[type]
|
|
1895
|
+
},
|
|
1896
|
+
required: ["identity"],
|
|
1897
|
+
anyOf: [{ required: ["data"] }, { required: ["error"] }]
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
var RESULT_SCHEMAS = Object.freeze(
|
|
1901
|
+
Object.fromEntries(
|
|
1902
|
+
[...RESULT_TYPES, "error"].map((type) => [type, schemaFor(type)])
|
|
1903
|
+
)
|
|
1904
|
+
);
|
|
1905
|
+
|
|
1906
|
+
// src/setup.ts
|
|
1907
|
+
function setupFor(dependencies, contract2) {
|
|
1908
|
+
const { setup } = contract2.guidance;
|
|
1909
|
+
const scansPackage = dependencies.includes(setup.package_source.when);
|
|
1910
|
+
return {
|
|
1911
|
+
tailwind: setup.tailwind,
|
|
1912
|
+
packages: setup.packages.map(({ name, range, dev, why }) => ({
|
|
1913
|
+
name,
|
|
1914
|
+
range: range ?? "latest",
|
|
1915
|
+
dev,
|
|
1916
|
+
why
|
|
1917
|
+
})),
|
|
1918
|
+
stylesheet: [
|
|
1919
|
+
...setup.stylesheet,
|
|
1920
|
+
...scansPackage ? [{ line: setup.package_source.line, why: setup.package_source.why }] : []
|
|
1921
|
+
],
|
|
1922
|
+
root_import: { line: setup.root_import, note: setup.root_import_note },
|
|
1923
|
+
not_needed: setup.not_needed
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
function renderSetup(setup, copiedPackageImports) {
|
|
1927
|
+
const named = ({ name, range }) => range === "latest" ? name : `${name}@${range}`;
|
|
1928
|
+
const runtime = setup.packages.filter(({ dev: dev2 }) => !dev2).map(named);
|
|
1929
|
+
const dev = setup.packages.filter(({ dev: isDev }) => isDev).map(named);
|
|
1930
|
+
return [
|
|
1931
|
+
`Setup, once per project: ${setup.tailwind}`,
|
|
1932
|
+
` Install: ${runtime.join(", ")}; as dev dependencies: ${dev.join(", ")}.`,
|
|
1933
|
+
" In the stylesheet that imports tailwindcss, after the import:",
|
|
1934
|
+
...setup.stylesheet.map(({ line }) => ` ${line}`),
|
|
1935
|
+
...copiedPackageImports ? [" (the @source path is relative to the stylesheet)"] : [
|
|
1936
|
+
" (source copied into the project is scanned by Tailwind 4 without an @source line)"
|
|
1937
|
+
],
|
|
1938
|
+
` At the application root, from JavaScript, before the stylesheet: ${setup.root_import.line}`,
|
|
1939
|
+
...setup.not_needed.map(({ name, why }) => ` Not needed: ${name}. ${why}`)
|
|
1940
|
+
];
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1099
1943
|
// src/agent-commands.ts
|
|
1100
|
-
|
|
1944
|
+
function renderInstall(result, setup) {
|
|
1945
|
+
const lines = [
|
|
1946
|
+
`${result.dryRun ? "Dry run" : "Installed"}: ${result.entries.join(", ")}`,
|
|
1947
|
+
`Output: ${result.outputDirectory}`,
|
|
1948
|
+
...result.files.map((file) => ` ${file.action.padEnd(9)} ${file.path}`)
|
|
1949
|
+
];
|
|
1950
|
+
const versioned = (name) => result.ranges[name] === void 0 ? name : `${name}@${result.ranges[name]}`;
|
|
1951
|
+
const runtime = result.dependencies.filter((name) => !result.devDependencies.includes(name));
|
|
1952
|
+
lines.push("", `Dependencies: ${runtime.map(versioned).join(", ")}`);
|
|
1953
|
+
if (result.devDependencies.length > 0)
|
|
1954
|
+
lines.push(
|
|
1955
|
+
`Dev dependencies (types only): ${result.devDependencies.map(versioned).join(", ")}`
|
|
1956
|
+
);
|
|
1957
|
+
lines.push(
|
|
1958
|
+
"",
|
|
1959
|
+
...renderSetup(setup, setup.stylesheet.length > 1),
|
|
1960
|
+
"",
|
|
1961
|
+
"Install what is missing yourself. No package configuration or stylesheet was changed."
|
|
1962
|
+
);
|
|
1963
|
+
return `${lines.join("\n")}
|
|
1964
|
+
`;
|
|
1965
|
+
}
|
|
1101
1966
|
function runAgentCommand(command, context) {
|
|
1102
1967
|
const { contract: contract2, registry: registry2, cwd } = context;
|
|
1103
1968
|
switch (command.command) {
|
|
@@ -1109,14 +1974,15 @@ function runAgentCommand(command, context) {
|
|
|
1109
1974
|
({ name, purpose, parts }) => `${name}${parts.length > 0 ? ` (${parts.join(", ")})` : ""}: ${purpose}
|
|
1110
1975
|
`
|
|
1111
1976
|
).join(""),
|
|
1112
|
-
json: envelope("component.list", { components }),
|
|
1977
|
+
json: envelope("component.list", { components }, contract2),
|
|
1113
1978
|
exitCode: 0
|
|
1114
1979
|
};
|
|
1115
1980
|
}
|
|
1116
1981
|
const details = command.names.map((name) => componentDetail(contract2, name));
|
|
1982
|
+
const data = details.map((detail) => componentData(detail, command.dense));
|
|
1117
1983
|
return {
|
|
1118
1984
|
text: details.map((detail) => renderComponent(detail, { dense: command.dense })).join("\n"),
|
|
1119
|
-
json:
|
|
1985
|
+
json: data.length === 1 ? envelope("component.detail", data[0], contract2) : envelope("component.batch", { components: data }, contract2),
|
|
1120
1986
|
exitCode: 0
|
|
1121
1987
|
};
|
|
1122
1988
|
}
|
|
@@ -1129,7 +1995,11 @@ function runAgentCommand(command, context) {
|
|
|
1129
1995
|
${next}
|
|
1130
1996
|
`
|
|
1131
1997
|
).join(""),
|
|
1132
|
-
json: envelope(
|
|
1998
|
+
json: envelope(
|
|
1999
|
+
"search",
|
|
2000
|
+
{ query: command.query, limit: command.limit, matchCount: results.length, results },
|
|
2001
|
+
contract2
|
|
2002
|
+
),
|
|
1133
2003
|
exitCode: 0
|
|
1134
2004
|
};
|
|
1135
2005
|
}
|
|
@@ -1140,146 +2010,328 @@ function runAgentCommand(command, context) {
|
|
|
1140
2010
|
...result.files.map(({ path, action }) => ` ${action.padEnd(9)} ${path}`),
|
|
1141
2011
|
command.remove ? "The block was taken out; the rest of each file is as it was." : "Run oprt init again after an upgrade to refresh the block."
|
|
1142
2012
|
];
|
|
1143
|
-
return {
|
|
1144
|
-
|
|
2013
|
+
return {
|
|
2014
|
+
text: `${lines.join("\n")}
|
|
2015
|
+
`,
|
|
2016
|
+
json: envelope("init", result, contract2),
|
|
2017
|
+
exitCode: 0
|
|
2018
|
+
};
|
|
1145
2019
|
}
|
|
1146
2020
|
case "check": {
|
|
1147
2021
|
const report = checkPaths(command.paths, { cwd, contract: contract2 });
|
|
1148
|
-
const { errors, warnings } = report.summary;
|
|
1149
|
-
const lines = report.findings.map(
|
|
1150
|
-
({ file, line, column, severity, code, message }) => `${file}:${line}:${column} ${severity} ${code} ${message}`
|
|
1151
|
-
);
|
|
1152
|
-
const count = (amount, word) => `${amount} ${word}${amount === 1 ? "" : "s"}`;
|
|
1153
|
-
lines.push(
|
|
1154
|
-
`${count(errors, "error")}, ${count(warnings, "warning")} in ${count(report.files, "file")}.`
|
|
1155
|
-
);
|
|
1156
2022
|
return {
|
|
1157
|
-
text:
|
|
1158
|
-
|
|
1159
|
-
json: envelope("check", report),
|
|
2023
|
+
text: renderCheckText(report),
|
|
2024
|
+
json: envelope("check", report, contract2),
|
|
1160
2025
|
// Warnings fail the run only when asked to: they are house rules, or may be native attributes.
|
|
1161
|
-
exitCode:
|
|
2026
|
+
exitCode: checkExitCode(report, { strict: command.strict })
|
|
1162
2027
|
};
|
|
1163
2028
|
}
|
|
1164
2029
|
case "doctor": {
|
|
1165
|
-
const report = doctor({
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
...
|
|
1169
|
-
|
|
2030
|
+
const report = doctor({
|
|
2031
|
+
cwd,
|
|
2032
|
+
contract: contract2,
|
|
2033
|
+
...context.parser ? { parser: context.parser } : {}
|
|
2034
|
+
});
|
|
2035
|
+
const { lookup, check_code: checking } = report.operations;
|
|
2036
|
+
const lines = [
|
|
2037
|
+
"Readiness",
|
|
2038
|
+
` lookups ${lookup.status.padEnd(8)} ${lookup.message}`,
|
|
2039
|
+
` code checking ${checking.status.padEnd(8)} ${checking.message}`,
|
|
2040
|
+
...checking.fix === void 0 ? [] : [` fix: ${checking.fix}`],
|
|
2041
|
+
"",
|
|
2042
|
+
"Setup"
|
|
2043
|
+
];
|
|
2044
|
+
lines.push(
|
|
2045
|
+
...report.checks.flatMap(({ status, message, fix }) => [
|
|
2046
|
+
`[${status}] ${message}`,
|
|
2047
|
+
...fix === void 0 ? [] : [` fix: ${fix}`]
|
|
2048
|
+
])
|
|
2049
|
+
);
|
|
1170
2050
|
lines.push(
|
|
1171
2051
|
`${report.summary.ok} ok, ${report.summary.warn} to look at, ${report.summary.fail} failed.`
|
|
1172
2052
|
);
|
|
1173
2053
|
return {
|
|
1174
2054
|
text: `${lines.join("\n")}
|
|
1175
2055
|
`,
|
|
1176
|
-
json: envelope("doctor", report),
|
|
2056
|
+
json: envelope("doctor", report, contract2),
|
|
1177
2057
|
exitCode: report.summary.fail > 0 ? 1 : 0
|
|
1178
2058
|
};
|
|
1179
2059
|
}
|
|
2060
|
+
case "list": {
|
|
2061
|
+
const lines = [`OPERATOR ${VERSION}`, ""];
|
|
2062
|
+
for (const kind of ["component", "recipe"]) {
|
|
2063
|
+
const entries = registry2.entries.filter((entry) => entry.kind === kind);
|
|
2064
|
+
lines.push(`${kind === "component" ? "Components" : "Recipes"} (${entries.length})`);
|
|
2065
|
+
for (const entry of entries) lines.push(` ${entry.slug.padEnd(22)} ${entry.description}`);
|
|
2066
|
+
lines.push("");
|
|
2067
|
+
}
|
|
2068
|
+
return {
|
|
2069
|
+
text: `${lines.join("\n")}
|
|
2070
|
+
`,
|
|
2071
|
+
json: envelope(
|
|
2072
|
+
"list",
|
|
2073
|
+
{ version: VERSION, entries: registry2.entries, ranges: registry2.ranges ?? {} },
|
|
2074
|
+
contract2
|
|
2075
|
+
),
|
|
2076
|
+
exitCode: 0
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
case "add": {
|
|
2080
|
+
const result = installSources(command, { cwd, registry: registry2 });
|
|
2081
|
+
const setup = setupFor(result.dependencies, contract2);
|
|
2082
|
+
return {
|
|
2083
|
+
text: renderInstall(result, setup),
|
|
2084
|
+
json: envelope("add", { ...result, setup }, contract2),
|
|
2085
|
+
exitCode: 0
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
1180
2088
|
}
|
|
1181
2089
|
}
|
|
1182
2090
|
|
|
1183
2091
|
// src/mcp.ts
|
|
1184
2092
|
import { createInterface } from "readline";
|
|
2093
|
+
|
|
2094
|
+
// src/validate.ts
|
|
2095
|
+
var KNOWN = /* @__PURE__ */ new Set([
|
|
2096
|
+
"type",
|
|
2097
|
+
"description",
|
|
2098
|
+
"default",
|
|
2099
|
+
"enum",
|
|
2100
|
+
"minimum",
|
|
2101
|
+
"maximum",
|
|
2102
|
+
"minLength",
|
|
2103
|
+
"maxLength"
|
|
2104
|
+
]);
|
|
2105
|
+
var typeName = (value) => value === null ? "null" : Array.isArray(value) ? "an array" : typeof value === "object" ? "an object" : typeof value === "number" ? "a number" : typeof value === "string" ? "a string" : typeof value === "boolean" ? "a boolean" : typeof value;
|
|
2106
|
+
function describeValue(value) {
|
|
2107
|
+
if (typeof value === "string")
|
|
2108
|
+
return `${JSON.stringify(value.length > 40 ? `${value.slice(0, 37)}...` : value)} (a string)`;
|
|
2109
|
+
if (typeof value === "number") return Number.isNaN(value) ? "NaN" : String(value);
|
|
2110
|
+
if (value === null || typeof value === "boolean" || value === void 0) return String(value);
|
|
2111
|
+
return `${typeName(value)} (${JSON.stringify(value)?.slice(0, 40) ?? ""})`;
|
|
2112
|
+
}
|
|
2113
|
+
function expectation(schema) {
|
|
2114
|
+
switch (schema.type) {
|
|
2115
|
+
case "boolean":
|
|
2116
|
+
return "true or false";
|
|
2117
|
+
case "integer":
|
|
2118
|
+
case "number": {
|
|
2119
|
+
const noun = schema.type === "integer" ? "a whole number" : "a number";
|
|
2120
|
+
if (schema.minimum !== void 0 && schema.maximum !== void 0)
|
|
2121
|
+
return `${noun} from ${schema.minimum} to ${schema.maximum}`;
|
|
2122
|
+
if (schema.minimum !== void 0) return `${noun} of at least ${schema.minimum}`;
|
|
2123
|
+
if (schema.maximum !== void 0) return `${noun} of at most ${schema.maximum}`;
|
|
2124
|
+
return noun;
|
|
2125
|
+
}
|
|
2126
|
+
case "string": {
|
|
2127
|
+
if (schema.enum !== void 0) return `one of ${schema.enum.map(String).join(", ")}`;
|
|
2128
|
+
const { minLength, maxLength } = schema;
|
|
2129
|
+
if (minLength !== void 0 && maxLength !== void 0)
|
|
2130
|
+
return `a string of ${minLength} to ${maxLength.toLocaleString("en-US")} characters`;
|
|
2131
|
+
if (maxLength !== void 0)
|
|
2132
|
+
return `a string of at most ${maxLength.toLocaleString("en-US")} characters`;
|
|
2133
|
+
return "a string";
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
function accepts(schema, value) {
|
|
2138
|
+
switch (schema.type) {
|
|
2139
|
+
case "boolean":
|
|
2140
|
+
return typeof value === "boolean";
|
|
2141
|
+
case "string":
|
|
2142
|
+
return typeof value === "string" && (schema.minLength === void 0 || value.length >= schema.minLength) && (schema.maxLength === void 0 || value.length <= schema.maxLength) && (schema.enum === void 0 || schema.enum.includes(value));
|
|
2143
|
+
case "integer":
|
|
2144
|
+
case "number":
|
|
2145
|
+
return typeof value === "number" && Number.isFinite(value) && (schema.type === "number" || Number.isInteger(value)) && (schema.minimum === void 0 || value >= schema.minimum) && (schema.maximum === void 0 || value <= schema.maximum);
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
function validateArguments(tool, schema, args) {
|
|
2149
|
+
for (const [field, property] of Object.entries(schema.properties))
|
|
2150
|
+
for (const key of Object.keys(property))
|
|
2151
|
+
if (!KNOWN.has(key))
|
|
2152
|
+
throw new Error(`${tool}.${field}: the schema uses "${key}", which is not enforced.`);
|
|
2153
|
+
if (typeof args !== "object" || args === null || Array.isArray(args))
|
|
2154
|
+
return `${tool}: arguments must be an object.`;
|
|
2155
|
+
const given = args;
|
|
2156
|
+
const required = schema.required ?? [];
|
|
2157
|
+
const blank = (value) => typeof value === "string" && value.trim() === "";
|
|
2158
|
+
const missing = [];
|
|
2159
|
+
const wrong = [];
|
|
2160
|
+
const unknown = [];
|
|
2161
|
+
for (const field of required)
|
|
2162
|
+
if (given[field] === void 0 || blank(given[field])) missing.push(field);
|
|
2163
|
+
for (const [field, value] of Object.entries(given)) {
|
|
2164
|
+
const property = Object.hasOwn(schema.properties, field) ? schema.properties[field] : void 0;
|
|
2165
|
+
if (property === void 0) unknown.push(field);
|
|
2166
|
+
else if (value !== void 0 && !(required.includes(field) && blank(value))) {
|
|
2167
|
+
if (!accepts(property, value))
|
|
2168
|
+
wrong.push(`${field} must be ${expectation(property)}, but got ${describeValue(value)}`);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
const reject = schema.additionalProperties === false ? unknown : [];
|
|
2172
|
+
const names2 = Object.keys(schema.properties);
|
|
2173
|
+
if (missing.length === 1 && wrong.length === 0 && reject.length === 0)
|
|
2174
|
+
return `${tool} needs ${missing[0]}, ${expectation({ type: schema.properties[missing[0]].type })}.`;
|
|
2175
|
+
const fragments = [
|
|
2176
|
+
...missing.map(
|
|
2177
|
+
(field) => `${field} is required (${expectation({ type: schema.properties[field].type })})`
|
|
2178
|
+
),
|
|
2179
|
+
...wrong,
|
|
2180
|
+
...reject.map(
|
|
2181
|
+
(field) => `unknown argument ${JSON.stringify(field)}. It takes${names2.length > 0 ? `: ${names2.join(", ")}` : " none"}`
|
|
2182
|
+
)
|
|
2183
|
+
];
|
|
2184
|
+
return fragments.length === 0 ? null : `${tool}: ${fragments.join("; ")}.`;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
// src/mcp.ts
|
|
1185
2188
|
var VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"];
|
|
1186
2189
|
var text = (value) => ({ type: "text", text: value });
|
|
1187
|
-
var
|
|
2190
|
+
var LIMITS = { query: 500, name: 100, code: 2e5 };
|
|
2191
|
+
var ANSWERS = {
|
|
2192
|
+
search: "search",
|
|
2193
|
+
get_component: "component.detail",
|
|
2194
|
+
list_components: "component.list",
|
|
2195
|
+
get_instructions: "instructions",
|
|
2196
|
+
check_code: "check"
|
|
2197
|
+
};
|
|
2198
|
+
var readOnly = { readOnlyHint: true };
|
|
2199
|
+
var define = (tool) => ({
|
|
2200
|
+
...tool,
|
|
2201
|
+
outputSchema: outputSchemaFor(ANSWERS[tool.name]),
|
|
2202
|
+
annotations: readOnly
|
|
2203
|
+
});
|
|
1188
2204
|
var TOOLS = [
|
|
1189
|
-
{
|
|
2205
|
+
define({
|
|
1190
2206
|
name: "search",
|
|
1191
|
-
description: "Find the OPERATOR component or recipe
|
|
2207
|
+
description: "Find the OPERATOR component or recipe for a need, such as 'show a status' or 'table with selection'. Call before writing interface code.",
|
|
1192
2208
|
inputSchema: {
|
|
1193
2209
|
type: "object",
|
|
1194
2210
|
properties: {
|
|
1195
|
-
query:
|
|
1196
|
-
|
|
2211
|
+
query: {
|
|
2212
|
+
type: "string",
|
|
2213
|
+
minLength: 1,
|
|
2214
|
+
maxLength: LIMITS.query,
|
|
2215
|
+
description: "The need, in plain words."
|
|
2216
|
+
},
|
|
2217
|
+
limit: {
|
|
2218
|
+
type: "integer",
|
|
2219
|
+
minimum: 1,
|
|
2220
|
+
maximum: SEARCH_LIMIT_MAX,
|
|
2221
|
+
default: SEARCH_LIMIT_DEFAULT,
|
|
2222
|
+
description: `Results to return, 1 to ${SEARCH_LIMIT_MAX}.`
|
|
2223
|
+
}
|
|
1197
2224
|
},
|
|
1198
|
-
required: ["query"]
|
|
2225
|
+
required: ["query"],
|
|
2226
|
+
additionalProperties: false
|
|
1199
2227
|
}
|
|
1200
|
-
},
|
|
1201
|
-
{
|
|
2228
|
+
}),
|
|
2229
|
+
define({
|
|
1202
2230
|
name: "get_component",
|
|
1203
|
-
description: "One
|
|
2231
|
+
description: "One component's contract: what it is for, what to use instead, every prop and option value, nesting rules, an example. Call before using a component.",
|
|
1204
2232
|
inputSchema: {
|
|
1205
2233
|
type: "object",
|
|
1206
2234
|
properties: {
|
|
1207
|
-
name:
|
|
2235
|
+
name: {
|
|
2236
|
+
type: "string",
|
|
2237
|
+
minLength: 1,
|
|
2238
|
+
maxLength: LIMITS.name,
|
|
2239
|
+
description: "Component name, such as Button."
|
|
2240
|
+
},
|
|
1208
2241
|
dense: {
|
|
1209
2242
|
type: "boolean",
|
|
1210
|
-
|
|
2243
|
+
default: true,
|
|
2244
|
+
description: "Compact form (every constraint, one sentence per prop). False: full prose."
|
|
1211
2245
|
}
|
|
1212
2246
|
},
|
|
1213
|
-
required: ["name"]
|
|
2247
|
+
required: ["name"],
|
|
2248
|
+
additionalProperties: false
|
|
1214
2249
|
}
|
|
1215
|
-
},
|
|
1216
|
-
{
|
|
2250
|
+
}),
|
|
2251
|
+
define({
|
|
1217
2252
|
name: "list_components",
|
|
1218
2253
|
description: "Every OPERATOR component in one line each, with its parts.",
|
|
1219
|
-
inputSchema: { type: "object", properties: {} }
|
|
1220
|
-
},
|
|
1221
|
-
{
|
|
2254
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
2255
|
+
}),
|
|
2256
|
+
define({
|
|
1222
2257
|
name: "get_instructions",
|
|
1223
|
-
description: "OPERATOR's house rules,
|
|
1224
|
-
inputSchema: { type: "object", properties: {} }
|
|
1225
|
-
},
|
|
1226
|
-
{
|
|
2258
|
+
description: "OPERATOR's house rules, component index and recipes: read once before building.",
|
|
2259
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
2260
|
+
}),
|
|
2261
|
+
define({
|
|
1227
2262
|
name: "check_code",
|
|
1228
|
-
description: "Check TSX
|
|
2263
|
+
description: "Check TSX against OPERATOR's contract: option values, nesting, required props, house rules. Call before you finish. Read evaluation_status: only evaluated_clean means every file was read in full with nothing found. Types and rendering are not checked.",
|
|
1229
2264
|
inputSchema: {
|
|
1230
2265
|
type: "object",
|
|
1231
|
-
properties: {
|
|
1232
|
-
|
|
2266
|
+
properties: {
|
|
2267
|
+
code: {
|
|
2268
|
+
type: "string",
|
|
2269
|
+
minLength: 1,
|
|
2270
|
+
maxLength: LIMITS.code,
|
|
2271
|
+
description: "The TSX source of one file."
|
|
2272
|
+
}
|
|
2273
|
+
},
|
|
2274
|
+
required: ["code"],
|
|
2275
|
+
additionalProperties: false
|
|
1233
2276
|
}
|
|
1234
|
-
}
|
|
2277
|
+
})
|
|
1235
2278
|
];
|
|
1236
2279
|
function runTool(name, args, context) {
|
|
1237
2280
|
const { contract: contract2, registry: registry2 } = context;
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
return value;
|
|
1243
|
-
};
|
|
2281
|
+
const definition = TOOLS.find((tool) => tool.name === name);
|
|
2282
|
+
if (definition === void 0) throw new UnknownTool(name);
|
|
2283
|
+
const problem = validateArguments(name, definition.inputSchema, args);
|
|
2284
|
+
if (problem !== null) throw new CliError(problem, 2, { code: "ERR_INVALID_ARGUMENT" });
|
|
1244
2285
|
switch (name) {
|
|
1245
2286
|
case "search": {
|
|
1246
|
-
const query =
|
|
1247
|
-
const
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
query
|
|
1251
|
-
|
|
1252
|
-
);
|
|
1253
|
-
return results.length === 0 ? `Nothing matches ${JSON.stringify(query)}. Call list_components to see what there is.` : results.map(
|
|
1254
|
-
({ kind, name: found, purpose, why, next }) => `${found} (${kind}): ${purpose}${why === void 0 ? "" : ` [${why}]`}
|
|
2287
|
+
const query = args["query"];
|
|
2288
|
+
const limit = args["limit"] ?? SEARCH_LIMIT_DEFAULT;
|
|
2289
|
+
const results = search(contract2, registry2, query, limit);
|
|
2290
|
+
return {
|
|
2291
|
+
text: results.length === 0 ? `Nothing matches ${JSON.stringify(query)}. Call list_components to see what there is.` : results.map(
|
|
2292
|
+
({ kind, name: found, purpose, why, next }) => `${found} (${kind}): ${purpose}${why === void 0 ? "" : ` [${why}]`}
|
|
1255
2293
|
${next}`
|
|
1256
|
-
|
|
2294
|
+
).join("\n"),
|
|
2295
|
+
structured: envelope(
|
|
2296
|
+
"search",
|
|
2297
|
+
{ query, limit, matchCount: results.length, results },
|
|
2298
|
+
contract2
|
|
2299
|
+
)
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
case "get_component": {
|
|
2303
|
+
const dense = args["dense"] ?? true;
|
|
2304
|
+
const detail = componentDetail(contract2, args["name"]);
|
|
2305
|
+
return {
|
|
2306
|
+
text: renderComponent(detail, { dense }),
|
|
2307
|
+
structured: envelope("component.detail", componentData(detail, dense), contract2)
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
case "list_components": {
|
|
2311
|
+
const components = componentIndex(contract2);
|
|
2312
|
+
return {
|
|
2313
|
+
text: components.map(
|
|
2314
|
+
({ name: component, purpose, parts }) => `${component}${parts.length > 0 ? ` (${parts.join(", ")})` : ""}: ${purpose}`
|
|
2315
|
+
).join("\n"),
|
|
2316
|
+
structured: envelope("component.list", { components }, contract2)
|
|
2317
|
+
};
|
|
1257
2318
|
}
|
|
1258
|
-
case "get_component":
|
|
1259
|
-
return renderComponent(componentDetail(contract2, required("name")), {
|
|
1260
|
-
dense: args.dense !== false
|
|
1261
|
-
});
|
|
1262
|
-
case "list_components":
|
|
1263
|
-
return componentIndex(contract2).map(
|
|
1264
|
-
({ name: component, purpose, parts }) => `${component}${parts.length > 0 ? ` (${parts.join(", ")})` : ""}: ${purpose}`
|
|
1265
|
-
).join("\n");
|
|
1266
2319
|
case "get_instructions":
|
|
1267
|
-
return
|
|
2320
|
+
return {
|
|
2321
|
+
text: renderAgentBlock(contract2).split("\n").filter((line) => line !== START && line !== END && !line.startsWith("<!-- Written by")).join("\n"),
|
|
2322
|
+
structured: envelope("instructions", instructionsData(contract2), contract2)
|
|
2323
|
+
};
|
|
1268
2324
|
case "check_code": {
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
({ line, column, severity, code: finding, message }) => `${line}:${column} ${severity} ${finding} ${message}`
|
|
1280
|
-
),
|
|
1281
|
-
`${count(errors, "error")}, ${count(findings.length - errors, "warning")}.`
|
|
1282
|
-
].join("\n");
|
|
2325
|
+
const { typescript, parser } = resolveParser(
|
|
2326
|
+
context.cwd ?? process.cwd(),
|
|
2327
|
+
context.typescript,
|
|
2328
|
+
"check_code"
|
|
2329
|
+
);
|
|
2330
|
+
const report = buildCheckReport(
|
|
2331
|
+
[checkSource(typescript, contract2, "snippet.tsx", args["code"])],
|
|
2332
|
+
{ parser }
|
|
2333
|
+
);
|
|
2334
|
+
return { text: renderCheckText(report), structured: envelope("check", report, contract2) };
|
|
1283
2335
|
}
|
|
1284
2336
|
default:
|
|
1285
2337
|
throw new UnknownTool(name);
|
|
@@ -1293,24 +2345,28 @@ var UnknownTool = class extends Error {
|
|
|
1293
2345
|
}
|
|
1294
2346
|
};
|
|
1295
2347
|
function handleMessage(raw, context) {
|
|
1296
|
-
const
|
|
2348
|
+
const failure2 = (id2, code, message) => ({
|
|
1297
2349
|
jsonrpc: "2.0",
|
|
1298
2350
|
id: id2 ?? null,
|
|
1299
2351
|
error: { code, message }
|
|
1300
2352
|
});
|
|
1301
|
-
if (typeof raw !== "object" || raw === null) return
|
|
2353
|
+
if (typeof raw !== "object" || raw === null) return failure2(null, -32700, "Parse error");
|
|
1302
2354
|
const { id, method, params } = raw;
|
|
1303
|
-
if (typeof method !== "string") return
|
|
2355
|
+
if (typeof method !== "string") return failure2(id, -32600, "Invalid request");
|
|
1304
2356
|
if (id === void 0) return null;
|
|
1305
2357
|
const result = (value) => ({ jsonrpc: "2.0", id, result: value });
|
|
1306
2358
|
switch (method) {
|
|
1307
2359
|
case "initialize": {
|
|
1308
2360
|
const asked = params?.protocolVersion;
|
|
2361
|
+
const identity = identityOf(context.contract);
|
|
1309
2362
|
return result({
|
|
1310
2363
|
protocolVersion: asked !== void 0 && VERSIONS.includes(asked) ? asked : VERSIONS[0],
|
|
1311
2364
|
capabilities: { tools: {} },
|
|
1312
|
-
|
|
1313
|
-
|
|
2365
|
+
// The program's version, not the contract's: the contract has its own, in `_meta` and in
|
|
2366
|
+
// the identity of every result.
|
|
2367
|
+
serverInfo: { name: "oprt", version: VERSION },
|
|
2368
|
+
instructions: "OPERATOR design system. Call get_instructions once, search for the component that answers a need, get_component before using one, and check_code before you finish. check_code says how much it could read: only evaluated_clean means the code was read in full with nothing found.",
|
|
2369
|
+
_meta: { "dev.oprt/identity": identity }
|
|
1314
2370
|
});
|
|
1315
2371
|
}
|
|
1316
2372
|
case "ping":
|
|
@@ -1320,17 +2376,23 @@ function handleMessage(raw, context) {
|
|
|
1320
2376
|
case "tools/call": {
|
|
1321
2377
|
const { name, arguments: args } = params ?? {};
|
|
1322
2378
|
try {
|
|
1323
|
-
|
|
2379
|
+
const output = runTool(String(name), args ?? {}, context);
|
|
2380
|
+
return result({
|
|
2381
|
+
content: [text(output.text)],
|
|
2382
|
+
structuredContent: output.structured
|
|
2383
|
+
});
|
|
1324
2384
|
} catch (error) {
|
|
1325
|
-
if (error instanceof UnknownTool) return
|
|
2385
|
+
if (error instanceof UnknownTool) return failure2(id, -32602, error.message);
|
|
2386
|
+
const failed = errorEnvelope(error, context.contract);
|
|
1326
2387
|
return result({
|
|
1327
|
-
content: [text(
|
|
2388
|
+
content: [text(failed.error)],
|
|
2389
|
+
structuredContent: failed,
|
|
1328
2390
|
isError: true
|
|
1329
2391
|
});
|
|
1330
2392
|
}
|
|
1331
2393
|
}
|
|
1332
2394
|
default:
|
|
1333
|
-
return
|
|
2395
|
+
return failure2(id, -32601, `Method not found: ${method}`);
|
|
1334
2396
|
}
|
|
1335
2397
|
}
|
|
1336
2398
|
function serve(input, output, context) {
|
|
@@ -1351,10 +2413,9 @@ function serve(input, output, context) {
|
|
|
1351
2413
|
}
|
|
1352
2414
|
|
|
1353
2415
|
// src/index.ts
|
|
1354
|
-
var VERSION = package_default.version;
|
|
1355
2416
|
function loadRegistry() {
|
|
1356
2417
|
const here = dirname5(fileURLToPath3(import.meta.url));
|
|
1357
|
-
const candidates = [
|
|
2418
|
+
const candidates = [join5(here, "registry.json"), join5(here, "../generated/registry.json")];
|
|
1358
2419
|
const found = candidates.find((candidate) => existsSync5(candidate));
|
|
1359
2420
|
if (found === void 0) {
|
|
1360
2421
|
throw new Error(
|
|
@@ -1375,26 +2436,38 @@ function install(options, context = {}) {
|
|
|
1375
2436
|
export {
|
|
1376
2437
|
CliError,
|
|
1377
2438
|
DEFAULT_OUT,
|
|
2439
|
+
RESULT_SCHEMAS,
|
|
2440
|
+
RESULT_SCHEMA_VERSION,
|
|
2441
|
+
RESULT_TYPES,
|
|
1378
2442
|
TOOLS,
|
|
1379
2443
|
VERSION,
|
|
1380
2444
|
applyBlock,
|
|
1381
2445
|
blockVersion,
|
|
2446
|
+
buildCheckReport,
|
|
2447
|
+
checkExitCode,
|
|
1382
2448
|
checkPaths,
|
|
1383
2449
|
checkSource,
|
|
1384
2450
|
componentDetail,
|
|
1385
2451
|
componentIndex,
|
|
1386
2452
|
contract,
|
|
2453
|
+
contractDigest,
|
|
1387
2454
|
doctor,
|
|
2455
|
+
envelope,
|
|
2456
|
+
errorEnvelope,
|
|
1388
2457
|
handleMessage,
|
|
2458
|
+
identityOf,
|
|
1389
2459
|
init,
|
|
1390
2460
|
install,
|
|
2461
|
+
outputSchemaFor,
|
|
1391
2462
|
parseArgs,
|
|
1392
2463
|
registry,
|
|
1393
2464
|
removeBlock,
|
|
1394
2465
|
renderAgentBlock,
|
|
2466
|
+
renderCheckText,
|
|
1395
2467
|
renderComponent,
|
|
1396
2468
|
resolveEntries,
|
|
1397
2469
|
runAgentCommand,
|
|
2470
|
+
schemaFor,
|
|
1398
2471
|
search,
|
|
1399
2472
|
serve
|
|
1400
2473
|
};
|