@oprt/cli 0.0.0-stage → 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/dist/bin.js ADDED
@@ -0,0 +1,2730 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.30_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js
13
+ import path from "path";
14
+ import { fileURLToPath } from "url";
15
+ var init_esm_shims = __esm({
16
+ "../../node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.30_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js"() {
17
+ "use strict";
18
+ }
19
+ });
20
+
21
+ // src/types.ts
22
+ var CliError;
23
+ var init_types = __esm({
24
+ "src/types.ts"() {
25
+ "use strict";
26
+ init_esm_shims();
27
+ CliError = class extends Error {
28
+ constructor(message, exitCode = 1, details = {}) {
29
+ super(message);
30
+ this.exitCode = exitCode;
31
+ this.name = "CliError";
32
+ this.code = details.code ?? (exitCode === 2 ? "ERR_USAGE" : "ERR_FAILED");
33
+ if (details.suggestions !== void 0) this.suggestions = details.suggestions;
34
+ }
35
+ exitCode;
36
+ code;
37
+ suggestions;
38
+ };
39
+ }
40
+ });
41
+
42
+ // src/lookup.ts
43
+ function distance(a, b) {
44
+ const row = Array.from({ length: b.length + 1 }, (_, index) => index);
45
+ for (let i = 1; i <= a.length; i++) {
46
+ let diagonal = row[0];
47
+ row[0] = i;
48
+ for (let j = 1; j <= b.length; j++) {
49
+ const above = row[j];
50
+ row[j] = Math.min(above + 1, row[j - 1] + 1, diagonal + (a[i - 1] === b[j - 1] ? 0 : 1));
51
+ diagonal = above;
52
+ }
53
+ }
54
+ return row[b.length];
55
+ }
56
+ function nearest(wanted, names2, limit = 5) {
57
+ const target = squash(wanted);
58
+ return names2.map((name) => {
59
+ const candidate = squash(name);
60
+ const apart = distance(target, candidate);
61
+ const close = apart <= 2 || target.length >= 3 && (candidate.includes(target) || target.includes(candidate));
62
+ return { name, apart, close };
63
+ }).filter(({ close }) => close).sort((a, b) => a.apart - b.apart || a.name.localeCompare(b.name)).slice(0, limit).map(({ name }) => name);
64
+ }
65
+ function resolveComponent(contract2, wanted) {
66
+ const names2 = Object.keys(contract2.components);
67
+ const found = names2.find((name) => name === wanted) ?? names2.find((name) => squash(name) === squash(wanted));
68
+ if (found !== void 0) return found;
69
+ const suggestions = nearest(wanted, names2);
70
+ throw new CliError(
71
+ `Unknown component ${JSON.stringify(wanted)}.` + (suggestions.length > 0 ? ` Did you mean ${suggestions.join(", ")}?` : "") + " Run oprt component --list.",
72
+ 2,
73
+ { code: "ERR_UNKNOWN_COMPONENT", suggestions }
74
+ );
75
+ }
76
+ function allowedChildren(rules, name) {
77
+ for (const [parent, rule] of Object.entries(rules ?? {})) {
78
+ if (parent === name) return rule.allowed_children;
79
+ const nested = allowedChildren(rule.children, name);
80
+ if (nested !== void 0) return nested;
81
+ }
82
+ return void 0;
83
+ }
84
+ function componentIndex(contract2) {
85
+ return Object.keys(contract2.components).filter((name) => (contract2.guidance.components[name]?.use_for ?? []).length > 0).sort((a, b) => a.localeCompare(b)).map((name) => ({
86
+ name,
87
+ purpose: contract2.guidance.components[name].use_for.join("; "),
88
+ parts: partsOf(contract2, name)
89
+ }));
90
+ }
91
+ function componentDetail(contract2, wanted) {
92
+ const name = resolveComponent(contract2, wanted);
93
+ const component = contract2.components[name];
94
+ const guide = contract2.guidance.components[name] ?? {};
95
+ const props = Object.entries(component.props).map(([prop, definition]) => {
96
+ const notes = guide.values?.[prop];
97
+ return { name: prop, ...definition, ...notes === void 0 ? {} : { notes } };
98
+ });
99
+ const allowed = allowedChildren(contract2.composition_rules, name);
100
+ return {
101
+ name,
102
+ description: component.description,
103
+ use_for: guide.use_for ?? [],
104
+ not_for: guide.not_for ?? [],
105
+ ...guide.part_of === void 0 ? {} : { part_of: guide.part_of },
106
+ parts: partsOf(contract2, name),
107
+ props,
108
+ ...component.slots === void 0 ? {} : { slots: component.slots },
109
+ composable_with: component.composable_with ?? [],
110
+ never_inside: component.never_inside ?? [],
111
+ ...allowed === void 0 ? {} : { allowed_children: allowed },
112
+ example: component.example
113
+ };
114
+ }
115
+ function propLine(prop, dense) {
116
+ 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)})`);
117
+ const description = prop.description === void 0 ? "" : dense ? firstSentence(prop.description) : prop.description;
118
+ const lines = [
119
+ ` ${prop.name}: ${kind}${prop.required ? ", required" : ""}${description ? `. ${description}` : ""}`
120
+ ];
121
+ for (const [value, note] of Object.entries(prop.notes ?? {})) lines.push(` ${value}: ${note}`);
122
+ return lines;
123
+ }
124
+ function renderComponent(detail, { dense }) {
125
+ const lines = [
126
+ detail.part_of === void 0 ? `${detail.name}: ${detail.use_for.join("; ")}` : `${detail.name}: a part of ${detail.part_of}. ${firstSentence(detail.description)}`
127
+ ];
128
+ if (detail.not_for.length > 0)
129
+ lines.push(
130
+ `Not for: ${detail.not_for.map(({ need, use }) => `${need} (use ${use})`).join("; ")}`
131
+ );
132
+ if (!dense) lines.push("", detail.description, "");
133
+ if (detail.props.length > 0) {
134
+ lines.push("Props:");
135
+ for (const prop of detail.props) lines.push(...propLine(prop, dense));
136
+ }
137
+ for (const [slot, { type, required }] of Object.entries(detail.slots ?? {}))
138
+ lines.push(
139
+ `${slot === "children" ? "Children" : `Slot ${slot}`}: ${type}${required ? ", required" : ""}`
140
+ );
141
+ if (detail.parts.length > 0) lines.push(`Parts: ${detail.parts.join(", ")}`);
142
+ if (detail.allowed_children !== void 0)
143
+ lines.push(`Takes directly inside: ${detail.allowed_children.join(", ")}`);
144
+ if (detail.composable_with.length > 0)
145
+ lines.push(`Goes inside: ${detail.composable_with.join(", ")}`);
146
+ if (detail.never_inside.length > 0) lines.push(`Never inside: ${detail.never_inside.join(", ")}`);
147
+ lines.push(
148
+ detail.example.includes("\n") ? `Example:
149
+ ${detail.example.split("\n").map((line) => ` ${line}`).join("\n")}` : `Example: ${detail.example}`
150
+ );
151
+ return `${lines.join("\n")}
152
+ `;
153
+ }
154
+ function search(contract2, registry2, query, limit = SEARCH_LIMIT_DEFAULT) {
155
+ const wanted = words(query);
156
+ if (wanted.length === 0) return [];
157
+ const whole = squash(query);
158
+ const scores = /* @__PURE__ */ new Map();
159
+ const add = (name, points, why) => {
160
+ const before = scores.get(name) ?? { score: 0 };
161
+ scores.set(name, {
162
+ score: before.score + points,
163
+ ...before.why !== void 0 ? { why: before.why } : why !== void 0 ? { why } : {}
164
+ });
165
+ };
166
+ for (const [name, component] of Object.entries(contract2.components)) {
167
+ const guide = contract2.guidance.components[name] ?? {};
168
+ const plain = squash(name);
169
+ if (plain === whole) add(name, 100);
170
+ else if (wanted.some((word) => word.length >= 3 && plain.includes(word))) add(name, 15);
171
+ else if (wanted.some((word) => word.length >= 4 && distance(word, plain) <= 1)) add(name, 12);
172
+ for (const phrase of guide.use_for ?? []) {
173
+ const hits = matched(wanted, phrase);
174
+ if (hits > 0) add(name, 12 * hits + (hits === wanted.length ? 40 : 0));
175
+ }
176
+ for (const { need, use } of guide.not_for ?? []) {
177
+ const hits = matched(wanted, need);
178
+ if (hits > 0 && hits >= Math.ceil(wanted.length * 0.6))
179
+ add(use, 10 * hits + (hits === wanted.length ? 15 : 0), `${name} is not for this: ${need}`);
180
+ }
181
+ const inDescription = matched(wanted, component.description);
182
+ if (inDescription > 0) add(name, 2 * inDescription);
183
+ }
184
+ const results = [];
185
+ for (const [name, { score, why }] of scores) {
186
+ const guide = contract2.guidance.components[name] ?? {};
187
+ results.push({
188
+ kind: "component",
189
+ name,
190
+ purpose: guide.part_of === void 0 ? (guide.use_for ?? []).join("; ") : `a part of ${guide.part_of}`,
191
+ score,
192
+ ...why === void 0 ? {} : { why },
193
+ next: `oprt component ${name}`
194
+ });
195
+ }
196
+ for (const entry of registry2.entries.filter(({ kind }) => kind === "recipe")) {
197
+ const recipe = contract2.patterns?.[entry.name];
198
+ const plain = squash(entry.name);
199
+ const inName = plain === whole ? 100 : wanted.some((word) => word.length >= 3 && plain.includes(word)) ? 15 : 0;
200
+ const hits = matched(wanted, entry.description);
201
+ const score = inName + (hits > 0 ? 10 * hits + (hits === wanted.length ? 15 : 0) : 0);
202
+ if (score > 0)
203
+ results.push({
204
+ kind: "recipe",
205
+ name: entry.name,
206
+ purpose: firstSentence(entry.description),
207
+ score,
208
+ next: recipe?.install ?? `oprt add ${entry.slug}`
209
+ });
210
+ }
211
+ results.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
212
+ const floor = Math.max(4, (results[0]?.score ?? 0) * 0.15);
213
+ return results.filter(({ score }) => score >= floor).slice(0, limit);
214
+ }
215
+ var SEARCH_LIMIT_MAX, SEARCH_LIMIT_DEFAULT, squash, firstSentence, partsOf, STOP, words, matched;
216
+ var init_lookup = __esm({
217
+ "src/lookup.ts"() {
218
+ "use strict";
219
+ init_esm_shims();
220
+ init_types();
221
+ SEARCH_LIMIT_MAX = 50;
222
+ SEARCH_LIMIT_DEFAULT = 10;
223
+ squash = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, "");
224
+ firstSentence = (text2) => /^.*?[.!?](?=\s|$)/.exec(text2)?.[0] ?? text2;
225
+ partsOf = (contract2, name) => Object.keys(contract2.components).filter(
226
+ (part) => contract2.guidance.components[part]?.part_of === name
227
+ );
228
+ STOP = new Set(
229
+ "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(
230
+ " "
231
+ )
232
+ );
233
+ 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);
234
+ matched = (query, text2) => {
235
+ const present = new Set(words(text2));
236
+ return query.filter((word) => present.has(word)).length;
237
+ };
238
+ }
239
+ });
240
+
241
+ // src/args.ts
242
+ function split(command, rest, spec) {
243
+ const flags = /* @__PURE__ */ new Set();
244
+ const values = /* @__PURE__ */ new Map();
245
+ const positional = [];
246
+ for (let i = 0; i < rest.length; i++) {
247
+ const token = rest[i];
248
+ if (!token.startsWith("-")) {
249
+ positional.push(token);
250
+ continue;
251
+ }
252
+ const kind = spec[token];
253
+ if (kind === void 0)
254
+ throw new CliError(
255
+ `Unknown option ${JSON.stringify(token)} for ${command}. Run oprt --help.`,
256
+ 2
257
+ );
258
+ if (flags.has(token) || values.has(token)) throw new CliError(`Duplicate option ${token}.`, 2);
259
+ if (kind === "flag") flags.add(token);
260
+ else {
261
+ const value = rest[++i];
262
+ if (!value || value.startsWith("-")) throw new CliError(`${token} requires a value.`, 2);
263
+ values.set(token, value);
264
+ }
265
+ }
266
+ return {
267
+ has: (flag) => flags.has(flag),
268
+ value: (name) => values.get(name),
269
+ positional
270
+ };
271
+ }
272
+ function parseArgs(args) {
273
+ if (!args.length || args.length === 1 && ["--help", "-h", "help"].includes(args[0]))
274
+ return { command: "help" };
275
+ if (args.length === 1 && ["--version", "-v"].includes(args[0])) return { command: "version" };
276
+ const [command, ...rest] = args;
277
+ if ([...Object.keys(AGENT_COMMANDS), "list", "add"].includes(command) && rest.length === 1 && ["--help", "-h"].includes(rest[0]))
278
+ return { command: "help" };
279
+ if (Object.hasOwn(AGENT_COMMANDS, command)) return AGENT_COMMANDS[command](rest);
280
+ if (command === "list") {
281
+ if (rest.length === 0) return { command, json: false };
282
+ if (rest.length === 1 && rest[0] === "--json") return { command, json: true };
283
+ throw new CliError("Usage: oprt list [--json]", 2);
284
+ }
285
+ if (command !== "add")
286
+ throw new CliError(`Unknown command ${JSON.stringify(command)}. Run oprt --help.`, 2);
287
+ const parsed = {
288
+ command,
289
+ names: [],
290
+ out: DEFAULT_OUT,
291
+ dryRun: false,
292
+ overwrite: false,
293
+ json: false
294
+ };
295
+ const seen = /* @__PURE__ */ new Set();
296
+ for (let i = 0; i < rest.length; i++) {
297
+ const token = rest[i];
298
+ if (!token.startsWith("-")) {
299
+ parsed.names.push(token);
300
+ continue;
301
+ }
302
+ if (!["--out", "--dry-run", "--overwrite", "--json"].includes(token))
303
+ throw new CliError(`Unknown option ${JSON.stringify(token)}. Run oprt --help.`, 2);
304
+ if (seen.has(token)) throw new CliError(`Duplicate option ${token}.`, 2);
305
+ seen.add(token);
306
+ if (token === "--out") {
307
+ const value = rest[++i];
308
+ if (!value || value.startsWith("-"))
309
+ throw new CliError("--out requires a relative directory.", 2);
310
+ parsed.out = value;
311
+ } else if (token === "--dry-run") parsed.dryRun = true;
312
+ else if (token === "--json") parsed.json = true;
313
+ else parsed.overwrite = true;
314
+ }
315
+ if (!parsed.names.length)
316
+ throw new CliError("add requires one or more component or recipe names. Run oprt list.", 2);
317
+ return parsed;
318
+ }
319
+ var DEFAULT_OUT, AGENT_COMMANDS;
320
+ var init_args = __esm({
321
+ "src/args.ts"() {
322
+ "use strict";
323
+ init_esm_shims();
324
+ init_lookup();
325
+ init_types();
326
+ DEFAULT_OUT = "src/components/operator";
327
+ AGENT_COMMANDS = {
328
+ component(rest) {
329
+ const { has, positional } = split("component", rest, {
330
+ "--list": "flag",
331
+ "--dense": "flag",
332
+ "--json": "flag"
333
+ });
334
+ if (has("--list") ? positional.length > 0 : positional.length === 0)
335
+ throw new CliError("Usage: oprt component <Name...> [--dense], or component --list", 2);
336
+ return {
337
+ command: "component",
338
+ names: positional,
339
+ list: has("--list"),
340
+ dense: has("--dense"),
341
+ json: has("--json")
342
+ };
343
+ },
344
+ search(rest) {
345
+ const { has, value, positional } = split("search", rest, {
346
+ "--limit": "value",
347
+ "--json": "flag"
348
+ });
349
+ if (positional.length === 0) throw new CliError("Usage: oprt search <need> [--limit <n>]", 2);
350
+ const given = value("--limit");
351
+ const limit = given === void 0 ? SEARCH_LIMIT_DEFAULT : Number(given);
352
+ if (!Number.isInteger(limit) || limit < 1 || limit > SEARCH_LIMIT_MAX)
353
+ throw new CliError(`--limit takes a whole number from 1 to ${SEARCH_LIMIT_MAX}.`, 2);
354
+ return { command: "search", query: positional.join(" "), limit, json: has("--json") };
355
+ },
356
+ init(rest) {
357
+ const { has, value, positional } = split("init", rest, {
358
+ "--agent": "value",
359
+ "--path": "value",
360
+ "--remove": "flag",
361
+ "--dry-run": "flag",
362
+ "--json": "flag"
363
+ });
364
+ const agent = value("--agent");
365
+ const path2 = value("--path");
366
+ if (positional.length > 0 || agent !== void 0 && path2 !== void 0)
367
+ throw new CliError(
368
+ "Usage: oprt init [--agent <name> | --path <file>] [--remove] [--dry-run]",
369
+ 2
370
+ );
371
+ return {
372
+ command: "init",
373
+ ...agent === void 0 ? {} : { agent },
374
+ ...path2 === void 0 ? {} : { path: path2 },
375
+ remove: has("--remove"),
376
+ dryRun: has("--dry-run"),
377
+ json: has("--json")
378
+ };
379
+ },
380
+ check(rest) {
381
+ const { has, positional } = split("check", rest, { "--strict": "flag", "--json": "flag" });
382
+ if (positional.length === 0)
383
+ throw new CliError("Usage: oprt check <file or folder...> [--strict]", 2);
384
+ return { command: "check", paths: positional, strict: has("--strict"), json: has("--json") };
385
+ },
386
+ mcp(rest) {
387
+ if (rest.length > 0) throw new CliError("Usage: oprt mcp", 2);
388
+ return { command: "mcp" };
389
+ },
390
+ doctor(rest) {
391
+ const { has, positional } = split("doctor", rest, { "--json": "flag" });
392
+ if (positional.length > 0) throw new CliError("Usage: oprt doctor", 2);
393
+ return { command: "doctor", json: has("--json") };
394
+ }
395
+ };
396
+ }
397
+ });
398
+
399
+ // src/parser.ts
400
+ import { createRequire } from "module";
401
+ import { join } from "path";
402
+ import { fileURLToPath as fileURLToPath2 } from "url";
403
+ function usable(typescript) {
404
+ const ts = typescript;
405
+ if (typeof ts.createSourceFile !== "function" || ts.ScriptKind === void 0)
406
+ return `TypeScript ${ts.version ?? "(unknown version)"} has no JavaScript syntax API (createSourceFile), which oprt reads code with; ${SUPPORTED_TYPESCRIPT} has it`;
407
+ try {
408
+ const probe = ts.createSourceFile(
409
+ "probe.tsx",
410
+ "<a>",
411
+ ts.ScriptTarget?.Latest ?? 99,
412
+ true,
413
+ ts.ScriptKind.TSX
414
+ );
415
+ const diagnostics = probe.parseDiagnostics;
416
+ if (!Array.isArray(diagnostics)) return "it does not report parse errors on a source file";
417
+ if (diagnostics.length === 0) return "it did not report a syntax error in code that has one";
418
+ } catch (error) {
419
+ return `it failed on a probe parse: ${error instanceof Error ? error.message : String(error)}`;
420
+ }
421
+ return null;
422
+ }
423
+ function probeTypeScript(cwd, options = {}) {
424
+ const places = [
425
+ { from: join(cwd, "package.json"), source: "project" },
426
+ ...options.installer === false ? [] : [{ from: fileURLToPath2(import.meta.url), source: "installer" }]
427
+ ];
428
+ let unusable;
429
+ for (const { from, source } of places) {
430
+ let file;
431
+ let loaded2;
432
+ try {
433
+ const require2 = createRequire(from);
434
+ file = require2.resolve("typescript");
435
+ loaded2 = require2("typescript");
436
+ } catch (error) {
437
+ if (error.code !== "MODULE_NOT_FOUND")
438
+ unusable ??= `typescript was found but failed to load: ${error instanceof Error ? error.message : String(error)}`;
439
+ continue;
440
+ }
441
+ const typescript = loaded2.default ?? loaded2;
442
+ const problem = usable(typescript);
443
+ if (problem !== null) {
444
+ unusable ??= `${problem} (at ${packageFolder(file)}).`;
445
+ continue;
446
+ }
447
+ return {
448
+ ready: true,
449
+ typescript,
450
+ resolution: {
451
+ name: "typescript",
452
+ version: typescript.version,
453
+ source,
454
+ resolved_from: packageFolder(file)
455
+ }
456
+ };
457
+ }
458
+ if (unusable !== void 0)
459
+ return { ready: false, reason: "typescript_unusable", detail: unusable };
460
+ return {
461
+ ready: false,
462
+ reason: "typescript_missing",
463
+ detail: `typescript could not be resolved from ${cwd}${options.installer === false ? "" : " or from beside the installer"}.`
464
+ };
465
+ }
466
+ var SUPPORTED_TYPESCRIPT, INSTALL_TYPESCRIPT, packageFolder;
467
+ var init_parser = __esm({
468
+ "src/parser.ts"() {
469
+ "use strict";
470
+ init_esm_shims();
471
+ SUPPORTED_TYPESCRIPT = "TypeScript 5 or 6";
472
+ INSTALL_TYPESCRIPT = "npm install -D typescript@6";
473
+ packageFolder = (file) => /^(.*[\\/]node_modules[\\/]typescript)[\\/]/.exec(file)?.[1] ?? file;
474
+ }
475
+ });
476
+
477
+ // src/check.ts
478
+ import { existsSync, readdirSync, readFileSync, statSync } from "fs";
479
+ import { basename, extname, join as join2, relative, resolve, sep } from "path";
480
+ function allowedChildren2(rules, name) {
481
+ for (const [parent, rule] of Object.entries(rules ?? {})) {
482
+ if (parent === name) return rule.allowed_children;
483
+ const nested = allowedChildren2(rule.children, name);
484
+ if (nested !== void 0) return nested;
485
+ }
486
+ return void 0;
487
+ }
488
+ function checkSource(typescript, contract2, file, text2) {
489
+ const ts = typescript;
490
+ const kind = /\.tsx$/.test(file) ? ts.ScriptKind.TSX : ts.ScriptKind.JSX;
491
+ const source = ts.createSourceFile(file, text2, ts.ScriptTarget.Latest, true, kind);
492
+ const parseDiagnostics = source.parseDiagnostics;
493
+ if (!Array.isArray(parseDiagnostics))
494
+ throw new CliError(
495
+ `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}).`,
496
+ 1,
497
+ { code: "ERR_TYPESCRIPT_UNUSABLE" }
498
+ );
499
+ if (parseDiagnostics.length > 0) {
500
+ const ordered = [...parseDiagnostics].sort((a, b) => (a.start ?? 0) - (b.start ?? 0));
501
+ const shown = ordered.slice(0, MAX_SYNTAX_ERRORS);
502
+ return {
503
+ file,
504
+ status: "not_evaluated",
505
+ limitations: [],
506
+ findings: shown.map((diagnostic, index) => {
507
+ const { line, character } = source.getLineAndCharacterOfPosition(diagnostic.start ?? 0);
508
+ const more = ordered.length - shown.length;
509
+ return {
510
+ code: "SYNTAX_ERROR",
511
+ severity: "error",
512
+ file,
513
+ line: line + 1,
514
+ column: character + 1,
515
+ 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.` : "")
516
+ };
517
+ })
518
+ };
519
+ }
520
+ const findings = [];
521
+ const limitations = [];
522
+ const limit = (node, limitation) => {
523
+ const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
524
+ limitations.push({ ...limitation, file, line: line + 1, column: character + 1 });
525
+ };
526
+ const report = (node, finding) => {
527
+ const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
528
+ findings.push({
529
+ code: finding.code,
530
+ severity: finding.severity,
531
+ file,
532
+ line: line + 1,
533
+ column: character + 1,
534
+ ...finding.component === void 0 ? {} : { component: finding.component },
535
+ ...finding.prop === void 0 ? {} : { prop: finding.prop },
536
+ message: finding.message,
537
+ ...finding.valid_values === void 0 ? {} : { valid_values: finding.valid_values },
538
+ ...finding.suggestions === void 0 ? {} : { suggestions: finding.suggestions }
539
+ });
540
+ };
541
+ const isComponent = (name) => Object.hasOwn(contract2.components, name);
542
+ const named = /* @__PURE__ */ new Map();
543
+ const namespaces = /* @__PURE__ */ new Set();
544
+ for (const statement of source.statements) {
545
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier))
546
+ continue;
547
+ const from = statement.moduleSpecifier.text;
548
+ const clause = statement.importClause;
549
+ if (!clause || clause.isTypeOnly || !clause.namedBindings) continue;
550
+ const copied = from !== PACKAGE && /[./@]/.test(from[0] ?? "") && isComponent(basename(from));
551
+ if (from !== PACKAGE && !copied) continue;
552
+ if (ts.isNamespaceImport(clause.namedBindings)) {
553
+ if (from === PACKAGE) namespaces.add(clause.namedBindings.name.text);
554
+ continue;
555
+ }
556
+ for (const element2 of clause.namedBindings.elements) {
557
+ if (element2.isTypeOnly) continue;
558
+ const exported = (element2.propertyName ?? element2.name).text;
559
+ if (from === PACKAGE && !contract2.exports.includes(exported)) {
560
+ let suggestions = nearest(exported, Object.keys(contract2.components));
561
+ if (suggestions.length === 0) suggestions = nearest(exported, contract2.exports);
562
+ let hint = suggestions.length > 0 ? ` Did you mean ${suggestions.join(", ")}?` : "";
563
+ if (suggestions.length === 0) {
564
+ const [byPurpose] = search(
565
+ contract2,
566
+ { version: contract2.version, entries: [], files: {} },
567
+ exported.replace(/([a-z0-9])([A-Z])/g, "$1 $2"),
568
+ 1
569
+ );
570
+ if (byPurpose !== void 0) {
571
+ suggestions = [byPurpose.name];
572
+ hint = ` Closest by purpose: ${byPurpose.name} (${byPurpose.purpose}).`;
573
+ }
574
+ }
575
+ report(element2, {
576
+ code: "UNKNOWN_EXPORT",
577
+ severity: "error",
578
+ message: `${PACKAGE} has no export ${JSON.stringify(exported)}.${hint}`,
579
+ ...suggestions.length > 0 ? { suggestions } : {}
580
+ });
581
+ continue;
582
+ }
583
+ if (isComponent(exported)) named.set(element2.name.text, exported);
584
+ }
585
+ }
586
+ const literals = (expression) => {
587
+ if (!expression) return [];
588
+ if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression))
589
+ return [expression.text];
590
+ if (ts.isParenthesizedExpression(expression)) return literals(expression.expression);
591
+ if (ts.isConditionalExpression(expression))
592
+ return [...literals(expression.whenTrue), ...literals(expression.whenFalse)];
593
+ return [];
594
+ };
595
+ const valueOf = (attribute) => attribute.initializer === void 0 ? void 0 : ts.isJsxExpression(attribute.initializer) ? attribute.initializer.expression : attribute.initializer;
596
+ const optionValue = (expression) => {
597
+ const read = [];
598
+ const unread = [];
599
+ const walk = (node) => {
600
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
601
+ read.push(node.text);
602
+ else if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isNonNullExpression(node) || ts.isTypeAssertionExpression(node) || typeof ts.isSatisfiesExpression === "function" && ts.isSatisfiesExpression(node))
603
+ walk(node.expression);
604
+ else if (ts.isConditionalExpression(node)) {
605
+ walk(node.whenTrue);
606
+ walk(node.whenFalse);
607
+ } else if (ts.isBinaryExpression(node)) {
608
+ const operator = node.operatorToken.kind;
609
+ if (operator === ts.SyntaxKind.QuestionQuestionToken || operator === ts.SyntaxKind.BarBarToken) {
610
+ walk(node.left);
611
+ walk(node.right);
612
+ } else if (operator === ts.SyntaxKind.AmpersandAmpersandToken) walk(node.right);
613
+ else unread.push(node);
614
+ } else if (!// No value: the component's default applies.
615
+ (ts.isIdentifier(node) && node.text === "undefined" || node.kind === ts.SyntaxKind.NullKeyword))
616
+ unread.push(node);
617
+ };
618
+ if (expression) walk(expression);
619
+ return { literals: read, unread };
620
+ };
621
+ const classText = (expression) => {
622
+ if (!expression) return "";
623
+ if (ts.isTemplateExpression(expression))
624
+ return [
625
+ expression.head.text,
626
+ ...expression.templateSpans.map((span) => span.literal.text)
627
+ ].join(" ");
628
+ return literals(expression).join(" ");
629
+ };
630
+ const houseRules = (attribute, name) => {
631
+ const value = valueOf(attribute);
632
+ if (name === "className") {
633
+ const classes = classText(value);
634
+ if (RAW_COLOR_CLASS.test(classes))
635
+ report(attribute, {
636
+ code: "RAW_COLOR",
637
+ severity: "warning",
638
+ message: "A color written out by hand. Use an OPERATOR color token (bg-oprt-\u2026, text-oprt-\u2026)."
639
+ });
640
+ if (ARBITRARY_SIZE.test(classes))
641
+ report(attribute, {
642
+ code: "ARBITRARY_SIZE",
643
+ severity: "warning",
644
+ message: "A size in pixels written out by hand. Use the spacing scale (p-2u, gap-3u, h-oprt-md)."
645
+ });
646
+ if (ROUNDED_CORNER.test(classes))
647
+ report(attribute, {
648
+ code: "ROUNDED_CORNER",
649
+ severity: "warning",
650
+ message: "A rounded corner. The radius is 0 everywhere in OPERATOR."
651
+ });
652
+ }
653
+ if (name === "style" && value && ts.isObjectLiteralExpression(value)) {
654
+ for (const property of value.properties)
655
+ if (ts.isPropertyAssignment(property) && literals(property.initializer).some((literal) => RAW_COLOR_VALUE.test(literal)))
656
+ report(attribute, {
657
+ code: "RAW_COLOR",
658
+ severity: "warning",
659
+ message: "A color written out by hand. Use an OPERATOR color token (var(--oprt-\u2026))."
660
+ });
661
+ }
662
+ };
663
+ const element = (opening, ancestors, parent, hasChildren, renderedAs) => {
664
+ const tag = opening.tagName;
665
+ let component = null;
666
+ if (ts.isIdentifier(tag)) {
667
+ component = named.get(tag.text) ?? null;
668
+ if (component === null && !renderedAs && Object.hasOwn(NATIVE, tag.text))
669
+ report(opening, {
670
+ code: "NATIVE_CONTROL",
671
+ severity: "warning",
672
+ message: `A native <${tag.text}>: use ${NATIVE[tag.text]}.`
673
+ });
674
+ } else if (ts.isPropertyAccessExpression(tag) && ts.isIdentifier(tag.expression)) {
675
+ const owner = tag.expression.text;
676
+ if (namespaces.has(owner) && isComponent(tag.name.text)) component = tag.name.text;
677
+ else if (named.has(owner)) {
678
+ const joined = `${named.get(owner)}${tag.name.text}`;
679
+ report(opening, {
680
+ code: "DOT_NOTATION",
681
+ severity: "error",
682
+ message: `${owner}.${tag.name.text} is not a component.` + (isComponent(joined) ? ` Use <${joined}>.` : " OPERATOR's parts are components of their own, imported by name.")
683
+ });
684
+ }
685
+ }
686
+ const attributes = opening.attributes.properties;
687
+ for (const attribute of attributes)
688
+ if (ts.isJsxAttribute(attribute) && ts.isIdentifier(attribute.name))
689
+ houseRules(attribute, attribute.name.text);
690
+ if (component === null) return null;
691
+ const definition = contract2.components[component];
692
+ const spread = attributes.some((attribute) => ts.isJsxSpreadAttribute(attribute));
693
+ for (const attribute of attributes)
694
+ if (ts.isJsxSpreadAttribute(attribute))
695
+ limit(attribute, {
696
+ code: "UNRESOLVED_SPREAD",
697
+ component,
698
+ message: `Props spread into <${component}> (${quote(attribute.getText(source))}) were not read: option values and required props that arrive through them were not checked.`
699
+ });
700
+ const given = /* @__PURE__ */ new Set();
701
+ for (const attribute of attributes) {
702
+ if (!ts.isJsxAttribute(attribute)) continue;
703
+ const prop = ts.isIdentifier(attribute.name) ? attribute.name.text : attribute.name.getText(source);
704
+ given.add(prop);
705
+ const known = Object.hasOwn(definition.props, prop) ? definition.props[prop] : void 0;
706
+ if (known === void 0) {
707
+ if (nativeAttribute(prop) || Object.hasOwn(definition.slots ?? {}, prop)) continue;
708
+ const names2 = Object.keys(definition.props);
709
+ report(attribute, {
710
+ code: "UNKNOWN_PROP",
711
+ severity: "warning",
712
+ component,
713
+ prop,
714
+ 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" : ""}.`)
715
+ });
716
+ continue;
717
+ }
718
+ if (known.type !== "enum" || known.values === void 0) continue;
719
+ const { literals: options, unread } = optionValue(valueOf(attribute));
720
+ for (const expression of unread)
721
+ limit(attribute, {
722
+ code: "UNRESOLVED_VALUE",
723
+ component,
724
+ prop,
725
+ 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.`
726
+ });
727
+ for (const literal of options) {
728
+ if (known.values.includes(literal)) continue;
729
+ const elsewhere = Object.entries(contract2.components).filter(([, other]) => other.props[prop]?.values?.includes(literal)).map(([name]) => name);
730
+ report(attribute, {
731
+ code: "INVALID_PROP_VALUE",
732
+ severity: "error",
733
+ component,
734
+ prop,
735
+ message: `${component} ${prop}=${JSON.stringify(literal)} is not an option. Use one of: ${known.values.join(", ")}.` + (elsewhere.length > 0 ? ` ${JSON.stringify(literal)} is a ${elsewhere.slice(0, 3).join(" or ")} ${prop}.` : ""),
736
+ valid_values: known.values
737
+ });
738
+ }
739
+ }
740
+ if (!spread) {
741
+ const needed = [
742
+ ...Object.entries(definition.props),
743
+ ...Object.entries(definition.slots ?? {})
744
+ ].filter(([, { required }]) => required);
745
+ for (const [prop] of needed)
746
+ if (!given.has(prop) && !(prop === "children" && hasChildren))
747
+ report(opening, {
748
+ code: "MISSING_REQUIRED_PROP",
749
+ severity: "error",
750
+ component,
751
+ prop,
752
+ message: `${component} needs ${prop}.`
753
+ });
754
+ }
755
+ const banned = (definition.never_inside ?? []).find((name) => ancestors.includes(name));
756
+ if (banned !== void 0)
757
+ report(opening, {
758
+ code: "INVALID_NESTING",
759
+ severity: "error",
760
+ component,
761
+ message: `${component} must not be inside ${banned}.`
762
+ });
763
+ const allowed = parent === null ? void 0 : allowedChildren2(contract2.composition_rules, parent);
764
+ if (allowed !== void 0 && !allowed.includes("any") && !allowed.includes(component))
765
+ report(opening, {
766
+ code: "INVALID_CHILD",
767
+ severity: "error",
768
+ component,
769
+ message: `${parent} takes only ${allowed.filter((name) => name !== "text").join(", ")} directly inside it, not ${component}.`
770
+ });
771
+ return component;
772
+ };
773
+ const visit = (node, ancestors, parent, renderedAs = false) => {
774
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
775
+ const opening = ts.isJsxElement(node) ? node.openingElement : node;
776
+ const hasChildren = ts.isJsxElement(node) && node.children.some(
777
+ (child) => !(ts.isJsxText(child) && child.containsOnlyTriviaWhiteSpaces)
778
+ );
779
+ const component = element(opening, ancestors, parent, hasChildren, renderedAs);
780
+ const asChild = component !== null && opening.attributes.properties.some(
781
+ (attribute) => ts.isJsxAttribute(attribute) && attribute.name.getText(source) === "asChild"
782
+ );
783
+ for (const attribute of opening.attributes.properties) visit(attribute, [], null);
784
+ if (ts.isJsxElement(node))
785
+ for (const child of node.children)
786
+ visit(
787
+ child,
788
+ component === null ? ancestors : [...ancestors, component],
789
+ component,
790
+ asChild
791
+ );
792
+ return;
793
+ }
794
+ ts.forEachChild(node, (child) => visit(child, ancestors, parent));
795
+ };
796
+ visit(source, [], null);
797
+ const byPlace = (a, b) => a.line - b.line || a.column - b.column;
798
+ return {
799
+ file,
800
+ status: limitations.length > 0 ? "partial" : "evaluated",
801
+ findings: findings.sort(byPlace),
802
+ limitations: limitations.sort(byPlace)
803
+ };
804
+ }
805
+ function* sources(path2) {
806
+ if (excluded(basename(path2))) return;
807
+ if (statSync(path2).isDirectory()) {
808
+ for (const entry of readdirSync(path2).sort()) yield* sources(join2(path2, entry));
809
+ } else if (READ.has(extname(path2)) && !NOT_INTERFACE.test(basename(path2))) yield path2;
810
+ }
811
+ function whyNotRead(path2) {
812
+ const name = basename(path2);
813
+ if (excluded(name)) return "excluded_folder";
814
+ if (statSync(path2).isDirectory()) return null;
815
+ if (!READ.has(extname(path2))) return "unsupported_extension";
816
+ return NOT_INTERFACE.test(name) ? "test_or_story_file" : null;
817
+ }
818
+ function buildCheckReport(evaluations, context) {
819
+ const findings = evaluations.flatMap(({ findings: found }) => found);
820
+ const count = (status2) => evaluations.filter((evaluation) => evaluation.status === status2).length;
821
+ const partial = count("partial");
822
+ const notEvaluated = count("not_evaluated");
823
+ const evaluated = evaluations.length - notEvaluated;
824
+ const errors = findings.filter(({ severity }) => severity === "error").length;
825
+ const warnings = findings.length - errors;
826
+ let status;
827
+ if (evaluations.length === 0) status = "no_files_evaluated";
828
+ else if (evaluated === 0) status = "not_evaluated";
829
+ else if (partial > 0 || notEvaluated > 0) status = "partially_evaluated";
830
+ else status = findings.length > 0 ? "evaluated_with_findings" : "evaluated_clean";
831
+ return {
832
+ evaluation_status: status,
833
+ files: evaluations.length,
834
+ findings,
835
+ summary: { errors, warnings },
836
+ coverage: {
837
+ files_found: evaluations.length,
838
+ files_evaluated: evaluated,
839
+ files_partial: partial,
840
+ files_not_evaluated: notEvaluated
841
+ },
842
+ limitations: evaluations.flatMap(({ limitations }) => limitations),
843
+ skipped_inputs: context.skipped ?? [],
844
+ scope: { checked: [...SCOPE.checked], not_checked: [...SCOPE.not_checked] },
845
+ parser: context.parser
846
+ };
847
+ }
848
+ function checkExitCode(report, { strict }) {
849
+ if (report.evaluation_status === "no_files_evaluated") return 1;
850
+ if (report.summary.errors > 0 || report.coverage.files_not_evaluated > 0) return 1;
851
+ return strict && report.summary.warnings > 0 ? 1 : 0;
852
+ }
853
+ function resolveParser(cwd, provided, who) {
854
+ if (provided !== void 0 && provided !== null)
855
+ return { typescript: provided, parser: describeProvided(provided) };
856
+ const probe = provided === null ? null : probeTypeScript(cwd);
857
+ if (probe?.ready) {
858
+ const { name, version, source } = probe.resolution;
859
+ return { typescript: probe.typescript, parser: { name, version, source } };
860
+ }
861
+ if (probe !== null && probe.reason === "typescript_unusable")
862
+ throw new CliError(`${who} cannot use the TypeScript it found: ${probe.detail}`, 1, {
863
+ code: "ERR_TYPESCRIPT_UNUSABLE"
864
+ });
865
+ throw new CliError(
866
+ `${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}.`,
867
+ 1,
868
+ { code: "ERR_TYPESCRIPT_MISSING" }
869
+ );
870
+ }
871
+ function checkPaths(paths, context) {
872
+ const root = resolve(context.cwd);
873
+ const files = [];
874
+ const skipped = [];
875
+ for (const path2 of paths) {
876
+ const absolute = resolve(root, path2);
877
+ if (!existsSync(absolute))
878
+ throw new CliError(`No such file or folder: ${path2}`, 2, { code: "ERR_PATH_NOT_FOUND" });
879
+ const reason = whyNotRead(absolute);
880
+ if (reason !== null) skipped.push({ path: path2, reason });
881
+ files.push(...sources(absolute));
882
+ }
883
+ const { typescript, parser } = resolveParser(root, context.typescript, "oprt check");
884
+ const evaluations = [...new Set(files)].map(
885
+ (file) => checkSource(
886
+ typescript,
887
+ context.contract,
888
+ relative(root, file).split(sep).join("/"),
889
+ readFileSync(file, "utf8")
890
+ )
891
+ );
892
+ return buildCheckReport(evaluations, { parser, skipped });
893
+ }
894
+ function renderCheckText(report) {
895
+ const lines = report.findings.map(
896
+ ({ file, line, column, severity, code, message }) => `${file}:${line}:${column} ${severity} ${code} ${message}`
897
+ );
898
+ const shown = report.limitations.slice(0, MAX_LIMITS_SHOWN);
899
+ for (const { file, line, column, code, message } of shown)
900
+ lines.push(`${file}:${line}:${column} limit ${code} ${message}`);
901
+ if (report.limitations.length > shown.length)
902
+ lines.push(
903
+ ` ...and ${plural(report.limitations.length - shown.length, "more limit")}; the JSON lists them all.`
904
+ );
905
+ const { coverage, evaluation_status: status } = report;
906
+ if (status === "no_files_evaluated") {
907
+ lines.push("No .tsx or .jsx file was read, so nothing was checked.");
908
+ const labels = {
909
+ unsupported_extension: "not a .tsx or .jsx file",
910
+ test_or_story_file: "a test, spec or story file, which is skipped",
911
+ excluded_folder: "a folder that is skipped (node_modules, dist, build, out, coverage, hidden)"
912
+ };
913
+ for (const { path: path2, reason } of report.skipped_inputs)
914
+ lines.push(` ${path2}: ${labels[reason]}`);
915
+ } else if (coverage.files_not_evaluated > 0)
916
+ lines.push(
917
+ `${plural(coverage.files_not_evaluated, "file")} did not parse and ${coverage.files_not_evaluated === 1 ? "was" : "were"} not checked against the contract.`
918
+ );
919
+ else if (status === "evaluated_clean")
920
+ lines.push(
921
+ "Nothing found. Checked syntax, option values, nesting, required props and the house rules; types and rendering are not checked."
922
+ );
923
+ lines.push(
924
+ `${plural(report.summary.errors, "error")}, ${plural(report.summary.warnings, "warning")} in ${plural(report.files, "file")}. Status: ${status}.`
925
+ );
926
+ return `${lines.join("\n")}
927
+ `;
928
+ }
929
+ var FINDING_CODES, LIMITATION_CODES, SCOPE, PACKAGE, NATIVE, NATIVE_ATTRIBUTES, nativeAttribute, RAW_COLOR_CLASS, RAW_COLOR_VALUE, ARBITRARY_SIZE, ROUNDED_CORNER, quote, MAX_SYNTAX_ERRORS, SKIPPED, READ, NOT_INTERFACE, excluded, describeProvided, plural, MAX_LIMITS_SHOWN;
930
+ var init_check = __esm({
931
+ "src/check.ts"() {
932
+ "use strict";
933
+ init_esm_shims();
934
+ init_lookup();
935
+ init_parser();
936
+ init_types();
937
+ init_parser();
938
+ FINDING_CODES = [
939
+ "INVALID_PROP_VALUE",
940
+ "UNKNOWN_EXPORT",
941
+ "DOT_NOTATION",
942
+ "INVALID_NESTING",
943
+ "INVALID_CHILD",
944
+ "MISSING_REQUIRED_PROP",
945
+ "UNKNOWN_PROP",
946
+ "RAW_COLOR",
947
+ "ARBITRARY_SIZE",
948
+ "ROUNDED_CORNER",
949
+ "NATIVE_CONTROL",
950
+ /** The file does not parse. It was not checked against the contract. */
951
+ "SYNTAX_ERROR"
952
+ ];
953
+ LIMITATION_CODES = ["UNRESOLVED_SPREAD", "UNRESOLVED_VALUE"];
954
+ SCOPE = {
955
+ checked: ["syntax", "contract", "house_rules"],
956
+ not_checked: ["types", "render"]
957
+ };
958
+ PACKAGE = "@oprt/react";
959
+ NATIVE = {
960
+ button: "Button",
961
+ input: "Input, or the control for its type (Checkbox, RadioGroup, NumberInput, Slider, DatePicker, FileUpload)",
962
+ select: "Select",
963
+ textarea: "Textarea",
964
+ table: "DataTable or DataGrid",
965
+ dialog: "Dialog",
966
+ progress: "Progress",
967
+ hr: "Divider"
968
+ };
969
+ NATIVE_ATTRIBUTES = new Set(
970
+ `key ref children className style id title role tabIndex hidden lang dir slot draggable spellCheck
971
+ translate inert accessKey autoCapitalize contentEditable enterKeyHint inputMode
972
+ dangerouslySetInnerHTML suppressHydrationWarning suppressContentEditableWarning htmlFor type name
973
+ value defaultValue checked defaultChecked disabled readOnly required placeholder autoComplete
974
+ autoFocus min max step minLength maxLength pattern multiple accept form formAction href target rel
975
+ download src alt width height loading rows cols wrap open colSpan rowSpan scope headers label list
976
+ size method action encType noValidate popover popoverTarget popoverTargetAction asChild`.split(
977
+ /\s+/
978
+ )
979
+ );
980
+ nativeAttribute = (name) => NATIVE_ATTRIBUTES.has(name) || /^(aria-|data-)/.test(name) || /^on[A-Z]/.test(name);
981
+ RAW_COLOR_CLASS = /\[(?:#[0-9a-fA-F]{3,8}\b|rgba?\(|hsla?\()/;
982
+ RAW_COLOR_VALUE = /^#[0-9a-fA-F]{3,8}$|^(?:rgb|hsl)a?\(/;
983
+ ARBITRARY_SIZE = /(?:^|[\s:])-?[a-z][a-z0-9-]*-\[-?\d*\.?\d+px\]/;
984
+ ROUNDED_CORNER = /(?:^|[\s:])rounded(?!-none(?:\s|$))(?:-[^\s]+)?(?=\s|$)/;
985
+ quote = (text2) => {
986
+ const flat = text2.replace(/\s+/g, " ").trim();
987
+ return flat.length > 48 ? `${flat.slice(0, 45)}...` : flat;
988
+ };
989
+ MAX_SYNTAX_ERRORS = 10;
990
+ SKIPPED = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "coverage"]);
991
+ READ = /* @__PURE__ */ new Set([".tsx", ".jsx"]);
992
+ NOT_INTERFACE = /\.(?:test|spec|stories)\.[jt]sx$/;
993
+ excluded = (name) => SKIPPED.has(name) || name.startsWith(".");
994
+ describeProvided = (typescript) => ({
995
+ name: "typescript",
996
+ version: typescript.version ?? "unknown",
997
+ source: "provided"
998
+ });
999
+ plural = (amount, word) => `${amount} ${word}${amount === 1 ? "" : "s"}`;
1000
+ MAX_LIMITS_SHOWN = 20;
1001
+ }
1002
+ });
1003
+
1004
+ // package.json
1005
+ var package_default;
1006
+ var init_package = __esm({
1007
+ "package.json"() {
1008
+ package_default = {
1009
+ name: "@oprt/cli",
1010
+ version: "0.3.0",
1011
+ 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.",
1012
+ keywords: [
1013
+ "design-system",
1014
+ "cli",
1015
+ "coding-agents",
1016
+ "mcp",
1017
+ "model-context-protocol",
1018
+ "code-check",
1019
+ "components"
1020
+ ],
1021
+ homepage: "https://oprt.dev/docs/agents/cli",
1022
+ type: "module",
1023
+ main: "./dist/index.cjs",
1024
+ module: "./dist/index.js",
1025
+ types: "./dist/index.d.ts",
1026
+ bin: {
1027
+ oprt: "./dist/bin.js"
1028
+ },
1029
+ engines: {
1030
+ node: ">=22"
1031
+ },
1032
+ exports: {
1033
+ ".": {
1034
+ import: {
1035
+ types: "./dist/index.d.ts",
1036
+ default: "./dist/index.js"
1037
+ },
1038
+ require: {
1039
+ types: "./dist/index.d.cts",
1040
+ default: "./dist/index.cjs"
1041
+ }
1042
+ },
1043
+ "./read": {
1044
+ import: {
1045
+ types: "./dist/read.d.ts",
1046
+ default: "./dist/read.js"
1047
+ },
1048
+ require: {
1049
+ types: "./dist/read.d.cts",
1050
+ default: "./dist/read.cjs"
1051
+ }
1052
+ }
1053
+ },
1054
+ files: [
1055
+ "dist"
1056
+ ],
1057
+ scripts: {
1058
+ clean: `node -e "require('node:fs').rmSync('dist',{recursive:true,force:true})"`,
1059
+ build: "pnpm run clean && node scripts/build-registry.mjs && tsup",
1060
+ dev: "pnpm run clean && node scripts/build-registry.mjs && tsup --watch",
1061
+ test: "vitest run",
1062
+ typecheck: "tsc --noEmit",
1063
+ lint: "eslint src scripts"
1064
+ },
1065
+ devDependencies: {
1066
+ "@modelcontextprotocol/sdk": "1.32.1",
1067
+ tsup: "^8.3.0",
1068
+ typescript: "^5.6.3",
1069
+ vitest: "^2.1.3"
1070
+ },
1071
+ license: "MIT",
1072
+ publishConfig: {
1073
+ access: "public"
1074
+ }
1075
+ };
1076
+ }
1077
+ });
1078
+
1079
+ // src/identity.ts
1080
+ import { createHash } from "crypto";
1081
+ function contractDigest(contract2) {
1082
+ let digest = digests.get(contract2);
1083
+ if (digest === void 0) {
1084
+ digest = `sha256:${createHash("sha256").update(JSON.stringify(contract2)).digest("hex")}`;
1085
+ digests.set(contract2, digest);
1086
+ }
1087
+ return digest;
1088
+ }
1089
+ function identityOf(contract2) {
1090
+ return {
1091
+ cli_version: VERSION,
1092
+ contract_version: contract2.version,
1093
+ contract_digest: contractDigest(contract2),
1094
+ schema_version: RESULT_SCHEMA_VERSION
1095
+ };
1096
+ }
1097
+ var VERSION, RESULT_SCHEMA_VERSION, digests;
1098
+ var init_identity = __esm({
1099
+ "src/identity.ts"() {
1100
+ "use strict";
1101
+ init_esm_shims();
1102
+ init_package();
1103
+ VERSION = package_default.version;
1104
+ RESULT_SCHEMA_VERSION = 1;
1105
+ digests = /* @__PURE__ */ new WeakMap();
1106
+ }
1107
+ });
1108
+
1109
+ // src/results.ts
1110
+ function errorEnvelope(error, contract2) {
1111
+ const known = error instanceof CliError ? error : void 0;
1112
+ const identity = contract2 === void 0 ? { cli_version: VERSION, schema_version: RESULT_SCHEMA_VERSION } : (({ cli_version, schema_version, contract_version, contract_digest }) => ({
1113
+ cli_version,
1114
+ schema_version,
1115
+ contract_version,
1116
+ contract_digest
1117
+ }))(identityOf(contract2));
1118
+ return {
1119
+ apiVersion: 1,
1120
+ error: error instanceof Error ? error.message : String(error),
1121
+ code: known?.code ?? "ERR_FAILED",
1122
+ ...known?.suggestions === void 0 ? {} : { suggestions: known.suggestions },
1123
+ identity
1124
+ };
1125
+ }
1126
+ function componentData(detail, dense) {
1127
+ if (!dense) return { ...detail, dense };
1128
+ const { description: _description, ...rest } = detail;
1129
+ return {
1130
+ ...rest,
1131
+ props: detail.props.map(
1132
+ (prop) => prop.description === void 0 ? prop : { ...prop, description: firstSentence(prop.description) }
1133
+ ),
1134
+ dense
1135
+ };
1136
+ }
1137
+ function instructionsData(contract2) {
1138
+ return {
1139
+ rules: contract2.guidance.rules.map(({ id, rule, wrong, right }) => ({
1140
+ id,
1141
+ rule,
1142
+ ...wrong === void 0 ? {} : { wrong },
1143
+ ...right === void 0 ? {} : { right }
1144
+ })),
1145
+ components: componentIndex(contract2),
1146
+ recipes: Object.entries(contract2.patterns ?? {}).map(([name, { description, install: install2 }]) => ({
1147
+ name,
1148
+ description,
1149
+ install: install2
1150
+ }))
1151
+ };
1152
+ }
1153
+ function bundle(root) {
1154
+ const used = {};
1155
+ const visit = (node) => {
1156
+ if (Array.isArray(node)) node.forEach(visit);
1157
+ else if (typeof node === "object" && node !== null) {
1158
+ for (const [key, value] of Object.entries(node)) {
1159
+ if (key === "$ref" && typeof value === "string") {
1160
+ const name = value.replace("#/$defs/", "");
1161
+ if (used[name] === void 0) {
1162
+ used[name] = DEFS[name];
1163
+ visit(DEFS[name]);
1164
+ }
1165
+ } else visit(value);
1166
+ }
1167
+ }
1168
+ };
1169
+ visit(root);
1170
+ return { ...root, $defs: used };
1171
+ }
1172
+ function schemaFor(type) {
1173
+ return bundle(type === "error" ? failure() : success(type));
1174
+ }
1175
+ function shareRepeats(schema) {
1176
+ const count = /* @__PURE__ */ new Map();
1177
+ const tally = (node) => {
1178
+ if (Array.isArray(node)) node.forEach(tally);
1179
+ else if (typeof node === "object" && node !== null) {
1180
+ const text2 = JSON.stringify(node);
1181
+ if (text2.length >= 30) count.set(text2, (count.get(text2) ?? 0) + 1);
1182
+ Object.values(node).forEach(tally);
1183
+ }
1184
+ };
1185
+ tally(schema);
1186
+ const repeated = [...count].filter(([, times]) => times >= 3).map(([text2]) => text2);
1187
+ if (repeated.length === 0) return schema;
1188
+ const names2 = new Map(
1189
+ repeated.map((text2, index) => [text2, index === 0 ? "strings" : `part${index}`])
1190
+ );
1191
+ const defs = {};
1192
+ const rewrite = (node) => {
1193
+ if (Array.isArray(node)) return node.map(rewrite);
1194
+ if (typeof node !== "object" || node === null) return node;
1195
+ const name = names2.get(JSON.stringify(node));
1196
+ if (name !== void 0) {
1197
+ defs[name] = node;
1198
+ return { $ref: `#/$defs/${name}` };
1199
+ }
1200
+ return Object.fromEntries(Object.entries(node).map(([key, value]) => [key, rewrite(value)]));
1201
+ };
1202
+ const rewritten = rewrite(schema);
1203
+ return { ...rewritten, $defs: defs };
1204
+ }
1205
+ function outputSchemaFor(type) {
1206
+ return shareRepeats({
1207
+ type: "object",
1208
+ properties: {
1209
+ type: { const: type },
1210
+ // The four versions and the digest; the docs name them, and so does every answer.
1211
+ identity: { type: "object" },
1212
+ data: SUMMARIES[type]
1213
+ },
1214
+ required: ["identity"],
1215
+ anyOf: [{ required: ["data"] }, { required: ["error"] }]
1216
+ });
1217
+ }
1218
+ var RESULT_TYPES, envelope, str, int, bool, arr, names, words2, ref, obj, scalar, DEFS, DATA, success, failure, S, I, B, list, shape, strings, SUMMARIES, RESULT_SCHEMAS;
1219
+ var init_results = __esm({
1220
+ "src/results.ts"() {
1221
+ "use strict";
1222
+ init_esm_shims();
1223
+ init_check();
1224
+ init_identity();
1225
+ init_lookup();
1226
+ init_types();
1227
+ RESULT_TYPES = [
1228
+ "check",
1229
+ "search",
1230
+ "component.detail",
1231
+ "component.batch",
1232
+ "component.list",
1233
+ "init",
1234
+ "doctor",
1235
+ "add",
1236
+ "list",
1237
+ "instructions"
1238
+ ];
1239
+ envelope = (type, data, contract2) => ({
1240
+ apiVersion: 1,
1241
+ type,
1242
+ identity: identityOf(contract2),
1243
+ data
1244
+ });
1245
+ str = (extra = {}) => ({ type: "string", ...extra });
1246
+ int = () => ({ type: "integer", minimum: 0 });
1247
+ bool = () => ({ type: "boolean" });
1248
+ arr = (items) => ({ type: "array", items });
1249
+ names = () => arr(str());
1250
+ words2 = (...values) => ({ type: "string", enum: [...values] });
1251
+ ref = (name) => ({ $ref: `#/$defs/${name}` });
1252
+ obj = (properties, optional = [], open = false) => ({
1253
+ type: "object",
1254
+ properties,
1255
+ required: Object.keys(properties).filter((key) => !optional.includes(key)),
1256
+ additionalProperties: open
1257
+ });
1258
+ scalar = { type: ["string", "number", "boolean"] };
1259
+ DEFS = {
1260
+ identity: obj({
1261
+ cli_version: str(),
1262
+ contract_version: str(),
1263
+ contract_digest: str({ pattern: "^sha256:[0-9a-f]{64}$" }),
1264
+ schema_version: { type: "integer", minimum: 1 }
1265
+ }),
1266
+ error_identity: obj(
1267
+ {
1268
+ cli_version: str(),
1269
+ schema_version: { type: "integer", minimum: 1 },
1270
+ contract_version: str(),
1271
+ contract_digest: str({ pattern: "^sha256:[0-9a-f]{64}$" })
1272
+ },
1273
+ ["contract_version", "contract_digest"]
1274
+ ),
1275
+ finding: obj(
1276
+ {
1277
+ code: words2(...FINDING_CODES),
1278
+ severity: words2("error", "warning"),
1279
+ file: str(),
1280
+ line: { type: "integer", minimum: 1 },
1281
+ column: { type: "integer", minimum: 1 },
1282
+ component: str(),
1283
+ prop: str(),
1284
+ message: str(),
1285
+ valid_values: names(),
1286
+ suggestions: names()
1287
+ },
1288
+ ["component", "prop", "valid_values", "suggestions"]
1289
+ ),
1290
+ limitation: obj(
1291
+ {
1292
+ code: words2(...LIMITATION_CODES),
1293
+ file: str(),
1294
+ line: { type: "integer", minimum: 1 },
1295
+ column: { type: "integer", minimum: 1 },
1296
+ component: str(),
1297
+ prop: str(),
1298
+ message: str()
1299
+ },
1300
+ ["prop"]
1301
+ ),
1302
+ skipped_input: obj({
1303
+ path: str(),
1304
+ reason: words2("unsupported_extension", "test_or_story_file", "excluded_folder")
1305
+ }),
1306
+ parser: obj({
1307
+ name: words2("typescript"),
1308
+ version: str(),
1309
+ source: words2("project", "installer", "provided")
1310
+ }),
1311
+ check_data: obj({
1312
+ evaluation_status: words2(
1313
+ "evaluated_clean",
1314
+ "evaluated_with_findings",
1315
+ "partially_evaluated",
1316
+ "not_evaluated",
1317
+ "no_files_evaluated"
1318
+ ),
1319
+ files: int(),
1320
+ findings: arr(ref("finding")),
1321
+ summary: obj({ errors: int(), warnings: int() }),
1322
+ coverage: obj({
1323
+ files_found: int(),
1324
+ files_evaluated: int(),
1325
+ files_partial: int(),
1326
+ files_not_evaluated: int()
1327
+ }),
1328
+ limitations: arr(ref("limitation")),
1329
+ skipped_inputs: arr(ref("skipped_input")),
1330
+ scope: obj({ checked: names(), not_checked: names() }),
1331
+ parser: ref("parser")
1332
+ }),
1333
+ search_result: obj(
1334
+ {
1335
+ kind: words2("component", "recipe"),
1336
+ name: str(),
1337
+ purpose: str(),
1338
+ score: { type: "number" },
1339
+ why: str(),
1340
+ next: str()
1341
+ },
1342
+ ["why"]
1343
+ ),
1344
+ search_data: obj({
1345
+ query: str(),
1346
+ limit: { type: "integer", minimum: 1 },
1347
+ matchCount: int(),
1348
+ results: arr(ref("search_result"))
1349
+ }),
1350
+ index_entry: obj({ name: str(), purpose: str(), parts: names() }),
1351
+ component_list_data: obj({ components: arr(ref("index_entry")) }),
1352
+ detail_prop: obj(
1353
+ {
1354
+ name: str(),
1355
+ type: str(),
1356
+ values: names(),
1357
+ types: names(),
1358
+ default: scalar,
1359
+ required: bool(),
1360
+ description: str(),
1361
+ notes: { type: "object", additionalProperties: str() }
1362
+ },
1363
+ ["values", "types", "default", "required", "description", "notes"],
1364
+ // The contract may describe a prop with more than these (an array's item type, for one).
1365
+ true
1366
+ ),
1367
+ component_detail: obj(
1368
+ {
1369
+ name: str(),
1370
+ description: str(),
1371
+ use_for: names(),
1372
+ not_for: arr(obj({ need: str(), use: str() })),
1373
+ part_of: str(),
1374
+ parts: names(),
1375
+ props: arr(ref("detail_prop")),
1376
+ slots: {
1377
+ type: "object",
1378
+ additionalProperties: obj({ type: str(), required: bool() }, ["required"])
1379
+ },
1380
+ composable_with: names(),
1381
+ never_inside: names(),
1382
+ allowed_children: names(),
1383
+ example: str(),
1384
+ dense: bool()
1385
+ },
1386
+ ["description", "part_of", "slots", "allowed_children"]
1387
+ ),
1388
+ component_batch_data: obj({ components: arr(ref("component_detail")) }),
1389
+ doctor_check: obj(
1390
+ { id: str(), status: words2("ok", "warn", "fail"), message: str(), fix: str() },
1391
+ ["fix"]
1392
+ ),
1393
+ doctor_data: obj({
1394
+ consumer_directory: str(),
1395
+ operations: obj({
1396
+ lookup: obj({ status: words2("ready"), message: str(), contract_version: str() }),
1397
+ check_code: obj(
1398
+ {
1399
+ status: words2("ready", "blocked"),
1400
+ message: str(),
1401
+ reason: words2("typescript_missing", "typescript_unusable"),
1402
+ fix: str(),
1403
+ typescript: obj({
1404
+ version: str(),
1405
+ resolved_from: str(),
1406
+ source: words2("project", "installer", "provided")
1407
+ })
1408
+ },
1409
+ ["reason", "fix", "typescript"]
1410
+ )
1411
+ }),
1412
+ checks: arr(ref("doctor_check")),
1413
+ summary: obj({ ok: int(), warn: int(), fail: int() })
1414
+ }),
1415
+ init_data: obj({
1416
+ version: str(),
1417
+ dryRun: bool(),
1418
+ files: arr(
1419
+ obj({
1420
+ path: str(),
1421
+ action: words2("create", "append", "update", "unchanged", "remove", "delete", "absent")
1422
+ })
1423
+ )
1424
+ }),
1425
+ install_file: obj({
1426
+ path: str(),
1427
+ destination: str(),
1428
+ action: words2("create", "overwrite", "skip")
1429
+ }),
1430
+ add_data: obj({
1431
+ outputDirectory: str(),
1432
+ entries: names(),
1433
+ dependencies: names(),
1434
+ devDependencies: names(),
1435
+ ranges: { type: "object", additionalProperties: str() },
1436
+ files: arr(ref("install_file")),
1437
+ dryRun: bool(),
1438
+ setup: ref("setup")
1439
+ }),
1440
+ setup: obj({
1441
+ tailwind: str(),
1442
+ packages: arr(obj({ name: str(), range: str(), dev: bool(), why: str() })),
1443
+ stylesheet: arr(obj({ line: str(), why: str() })),
1444
+ root_import: obj({ line: str(), note: str() }),
1445
+ not_needed: arr(obj({ name: str(), why: str() }))
1446
+ }),
1447
+ list_data: obj({
1448
+ version: str(),
1449
+ entries: arr(
1450
+ obj(
1451
+ {
1452
+ name: str(),
1453
+ slug: str(),
1454
+ kind: words2("component", "recipe"),
1455
+ description: str(),
1456
+ files: names(),
1457
+ dependencies: names(),
1458
+ devDependencies: names()
1459
+ },
1460
+ ["devDependencies"]
1461
+ )
1462
+ ),
1463
+ ranges: { type: "object", additionalProperties: str() }
1464
+ }),
1465
+ instructions_data: obj({
1466
+ rules: arr(obj({ id: str(), rule: str(), wrong: str(), right: str() }, ["wrong", "right"])),
1467
+ components: arr(ref("index_entry")),
1468
+ recipes: arr(obj({ name: str(), description: str(), install: str() }))
1469
+ })
1470
+ };
1471
+ DATA = {
1472
+ check: "check_data",
1473
+ search: "search_data",
1474
+ "component.detail": "component_detail",
1475
+ "component.batch": "component_batch_data",
1476
+ "component.list": "component_list_data",
1477
+ init: "init_data",
1478
+ doctor: "doctor_data",
1479
+ add: "add_data",
1480
+ list: "list_data",
1481
+ instructions: "instructions_data"
1482
+ };
1483
+ success = (type) => obj({
1484
+ apiVersion: { const: 1 },
1485
+ type: { const: type },
1486
+ identity: ref("identity"),
1487
+ data: ref(DATA[type])
1488
+ });
1489
+ failure = () => obj(
1490
+ {
1491
+ apiVersion: { const: 1 },
1492
+ error: str(),
1493
+ code: str({ pattern: "^ERR_[A-Z_]+$" }),
1494
+ suggestions: names(),
1495
+ identity: ref("error_identity")
1496
+ },
1497
+ ["suggestions"]
1498
+ );
1499
+ S = { type: "string" };
1500
+ I = { type: "integer" };
1501
+ B = { type: "boolean" };
1502
+ list = (items) => items === void 0 ? { type: "array" } : { type: "array", items };
1503
+ shape = (properties, required) => ({
1504
+ type: "object",
1505
+ properties,
1506
+ ...required === void 0 ? {} : { required }
1507
+ });
1508
+ strings = list(S);
1509
+ SUMMARIES = {
1510
+ check: shape(
1511
+ {
1512
+ evaluation_status: words2(
1513
+ "evaluated_clean",
1514
+ "evaluated_with_findings",
1515
+ "partially_evaluated",
1516
+ "not_evaluated",
1517
+ "no_files_evaluated"
1518
+ ),
1519
+ findings: list(
1520
+ shape({
1521
+ code: S,
1522
+ severity: words2("error", "warning"),
1523
+ file: S,
1524
+ line: I,
1525
+ column: I,
1526
+ message: S
1527
+ })
1528
+ ),
1529
+ summary: shape({ errors: I, warnings: I }),
1530
+ coverage: shape({ files_evaluated: I, files_partial: I, files_not_evaluated: I }),
1531
+ limitations: list(shape({ code: S, file: S, line: I, column: I, message: S })),
1532
+ skipped_inputs: list(),
1533
+ scope: { type: "object" },
1534
+ parser: { type: "object" }
1535
+ },
1536
+ ["evaluation_status", "findings", "summary", "coverage", "limitations"]
1537
+ ),
1538
+ search: shape(
1539
+ {
1540
+ matchCount: I,
1541
+ results: list(
1542
+ shape({ kind: words2("component", "recipe"), name: S, purpose: S, why: S, next: S })
1543
+ )
1544
+ },
1545
+ ["results"]
1546
+ ),
1547
+ "component.detail": shape(
1548
+ {
1549
+ name: S,
1550
+ description: S,
1551
+ use_for: strings,
1552
+ not_for: list(),
1553
+ part_of: S,
1554
+ parts: strings,
1555
+ props: list(
1556
+ shape({ name: S, type: S, values: strings, required: B, notes: { type: "object" } })
1557
+ ),
1558
+ slots: { type: "object" },
1559
+ composable_with: strings,
1560
+ never_inside: strings,
1561
+ allowed_children: strings,
1562
+ example: S,
1563
+ dense: B
1564
+ },
1565
+ ["name", "props", "example", "dense"]
1566
+ ),
1567
+ "component.list": shape({ components: list() }, ["components"]),
1568
+ "component.batch": shape({ components: list() }, ["components"]),
1569
+ instructions: shape(
1570
+ {
1571
+ rules: list(),
1572
+ components: list(),
1573
+ recipes: list()
1574
+ },
1575
+ ["rules", "components", "recipes"]
1576
+ ),
1577
+ init: shape({ files: list() }),
1578
+ doctor: shape({ operations: { type: "object" }, checks: list() }),
1579
+ add: shape({ files: list(), setup: { type: "object" } }),
1580
+ list: shape({ entries: list() })
1581
+ };
1582
+ RESULT_SCHEMAS = Object.freeze(
1583
+ Object.fromEntries(
1584
+ [...RESULT_TYPES, "error"].map((type) => [type, schemaFor(type)])
1585
+ )
1586
+ );
1587
+ }
1588
+ });
1589
+
1590
+ // src/contract.ts
1591
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1592
+ import { dirname, join as join3 } from "path";
1593
+ import { fileURLToPath as fileURLToPath3 } from "url";
1594
+ function loadContract() {
1595
+ const here = dirname(fileURLToPath3(import.meta.url));
1596
+ const candidates = [join3(here, "contract.json"), join3(here, "../generated/contract.json")];
1597
+ const found = candidates.find((candidate) => existsSync2(candidate));
1598
+ if (found === void 0) {
1599
+ throw new Error(
1600
+ `contract.json is missing next to ${here}. Install @oprt/cli again: the contract ships beside the executable, and the executable cannot be copied without it.`
1601
+ );
1602
+ }
1603
+ return JSON.parse(readFileSync2(found, "utf8"));
1604
+ }
1605
+ var init_contract = __esm({
1606
+ "src/contract.ts"() {
1607
+ "use strict";
1608
+ init_esm_shims();
1609
+ }
1610
+ });
1611
+
1612
+ // src/installer.ts
1613
+ import {
1614
+ constants,
1615
+ closeSync,
1616
+ lstatSync,
1617
+ mkdirSync,
1618
+ openSync,
1619
+ readFileSync as readFileSync3,
1620
+ realpathSync,
1621
+ writeFileSync
1622
+ } from "fs";
1623
+ import { dirname as dirname2, isAbsolute, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
1624
+ function resolveEntries(names2, registry2) {
1625
+ if (!names2.length) throw new CliError("add requires one or more names.", 2);
1626
+ const selected = /* @__PURE__ */ new Map();
1627
+ for (const name of names2) {
1628
+ const normalized = name.toLowerCase();
1629
+ const entry = registry2.entries.find(
1630
+ (candidate) => candidate.slug === normalized || candidate.name.toLowerCase() === normalized
1631
+ );
1632
+ if (!entry)
1633
+ throw new CliError(`Unknown component or recipe ${JSON.stringify(name)}. Run oprt list.`, 2);
1634
+ selected.set(entry.name, entry);
1635
+ }
1636
+ return [...selected.values()];
1637
+ }
1638
+ function validateRelative(path2) {
1639
+ if (!path2 || isAbsolute(path2) || /[\\:\0]/.test(path2) || path2.split("/").includes(".."))
1640
+ throw new CliError(
1641
+ `Unsafe path ${JSON.stringify(path2)}: use a nonempty relative path without traversal.`,
1642
+ 2
1643
+ );
1644
+ }
1645
+ function stat(path2) {
1646
+ try {
1647
+ return lstatSync(path2);
1648
+ } catch (error) {
1649
+ if (error.code === "ENOENT") return void 0;
1650
+ throw error;
1651
+ }
1652
+ }
1653
+ function inspectPath(root, destination, directory = false) {
1654
+ const path2 = relative2(root, destination);
1655
+ if (path2 === ".." || path2.startsWith(`..${sep2}`) || isAbsolute(path2))
1656
+ throw new CliError("Output path escapes the working directory.");
1657
+ let cursor = root;
1658
+ const segments = path2 ? path2.split(sep2) : [];
1659
+ for (let i = 0; i < segments.length; i++) {
1660
+ cursor = resolve2(cursor, segments[i]);
1661
+ const info = stat(cursor);
1662
+ if (!info) continue;
1663
+ if (info.isSymbolicLink()) throw new CliError(`Refusing symlink in output path: ${cursor}`);
1664
+ const needsDirectory = i < segments.length - 1 || directory;
1665
+ if (needsDirectory && !info.isDirectory())
1666
+ throw new CliError(`Output ancestor is not a directory: ${cursor}`);
1667
+ if (!needsDirectory && !info.isFile())
1668
+ throw new CliError(`Output target is not a regular file: ${cursor}`);
1669
+ }
1670
+ return stat(destination);
1671
+ }
1672
+ function installSources(options, context) {
1673
+ const entries = resolveEntries(options.names, context.registry);
1674
+ const out = options.out ?? DEFAULT_OUT;
1675
+ validateRelative(out);
1676
+ const root = realpathSync(context.cwd);
1677
+ const outputDirectory = resolve2(root, out);
1678
+ inspectPath(root, outputDirectory, true);
1679
+ const paths = [...new Set(entries.flatMap((entry) => entry.files))].sort();
1680
+ const files = paths.map((path2) => {
1681
+ validateRelative(path2);
1682
+ const content = context.registry.files[path2];
1683
+ if (!Object.hasOwn(context.registry.files, path2) || typeof content !== "string")
1684
+ throw new CliError(`Registry is missing source ${path2}.`);
1685
+ const destination = resolve2(outputDirectory, path2);
1686
+ const info = inspectPath(root, destination);
1687
+ if (!info) return { path: path2, destination, action: "create" };
1688
+ if (readFileSync3(destination, "utf8") === content) return { path: path2, destination, action: "skip" };
1689
+ if (!options.overwrite)
1690
+ throw new CliError(
1691
+ `Refusing to overwrite modified file ${destination}. Use --overwrite to replace it explicitly.`
1692
+ );
1693
+ return { path: path2, destination, action: "overwrite" };
1694
+ });
1695
+ const dependencies = [...new Set(entries.flatMap((entry) => entry.dependencies))].sort();
1696
+ const runtime = new Set(
1697
+ entries.flatMap(
1698
+ (entry) => entry.dependencies.filter((name) => !(entry.devDependencies ?? []).includes(name))
1699
+ )
1700
+ );
1701
+ const result = {
1702
+ outputDirectory,
1703
+ entries: entries.map((entry) => entry.name),
1704
+ dependencies,
1705
+ devDependencies: dependencies.filter(
1706
+ (name) => !runtime.has(name) && entries.some((entry) => (entry.devDependencies ?? []).includes(name))
1707
+ ),
1708
+ ranges: Object.fromEntries(
1709
+ dependencies.flatMap((name) => {
1710
+ const range = context.registry.ranges?.[name];
1711
+ return range === void 0 ? [] : [[name, range]];
1712
+ })
1713
+ ),
1714
+ files,
1715
+ dryRun: options.dryRun ?? false
1716
+ };
1717
+ if (result.dryRun) return result;
1718
+ for (const file of files) {
1719
+ if (file.action === "skip") continue;
1720
+ inspectPath(root, file.destination);
1721
+ const parent = dirname2(file.destination);
1722
+ mkdirSync(parent, { recursive: true });
1723
+ inspectPath(root, parent, true);
1724
+ const flags = constants.O_WRONLY | constants.O_CREAT | (constants.O_NOFOLLOW ?? 0) | (file.action === "create" ? constants.O_EXCL : constants.O_TRUNC);
1725
+ const descriptor = openSync(file.destination, flags, 420);
1726
+ try {
1727
+ writeFileSync(descriptor, context.registry.files[file.path]);
1728
+ } finally {
1729
+ closeSync(descriptor);
1730
+ }
1731
+ }
1732
+ return result;
1733
+ }
1734
+ var init_installer = __esm({
1735
+ "src/installer.ts"() {
1736
+ "use strict";
1737
+ init_esm_shims();
1738
+ init_args();
1739
+ init_types();
1740
+ }
1741
+ });
1742
+
1743
+ // src/agent-block.ts
1744
+ function renderAgentBlock(contract2) {
1745
+ const lines = [
1746
+ START,
1747
+ `<!-- Written by oprt ${contract2.version}. Do not edit between these markers: run \`oprt init\` to refresh. -->`,
1748
+ "## OPERATOR design system",
1749
+ "",
1750
+ "This project builds its interface with OPERATOR (`@oprt/react`). Use its components, and look them up instead of guessing: these commands print the facts for the version installed here. Run them with the project's package runner (`npx oprt \u2026`, `pnpm exec oprt \u2026`).",
1751
+ "",
1752
+ "- `oprt search <need>`: which component or recipe answers a need",
1753
+ "- `oprt component <Name>`: one component's options, nesting rules and example (`--dense` for the short form)",
1754
+ "- `oprt add <recipe>`: copy a working screen into the project to adapt",
1755
+ "- `oprt check <paths>`: check written code against the contract",
1756
+ "",
1757
+ "### Rules",
1758
+ ""
1759
+ ];
1760
+ contract2.guidance.rules.forEach(({ rule, wrong, right }, index) => {
1761
+ lines.push(`${index + 1}. ${rule}`);
1762
+ if (wrong !== void 0) lines.push(` Wrong: \`${wrong}\``);
1763
+ if (right !== void 0) lines.push(` Right: \`${right}\``);
1764
+ });
1765
+ lines.push("", "### Components", "");
1766
+ for (const { name, purpose, parts } of componentIndex(contract2))
1767
+ lines.push(`- ${name}${parts.length > 0 ? ` (${parts.join(", ")})` : ""}: ${purpose}`);
1768
+ const recipes = Object.entries(contract2.patterns ?? {});
1769
+ if (recipes.length > 0) {
1770
+ lines.push("", "### Recipes (whole screens to start from)", "");
1771
+ for (const [name, { description, install: install2 }] of recipes)
1772
+ lines.push(`- ${name}: ${firstSentence2(description)} \`${install2}\``);
1773
+ }
1774
+ lines.push(END);
1775
+ return `${lines.join("\n")}
1776
+ `;
1777
+ }
1778
+ function blockVersion(text2) {
1779
+ const start = text2.indexOf(START);
1780
+ if (start === -1) return void 0;
1781
+ return /Written by oprt (\S+)\. Do not edit/.exec(text2.slice(start))?.[1];
1782
+ }
1783
+ function region(text2) {
1784
+ const start = text2.indexOf(START);
1785
+ if (start === -1) return void 0;
1786
+ const end = text2.indexOf(END, start);
1787
+ if (end === -1) return void 0;
1788
+ const after = end + END.length;
1789
+ return [start, text2[after] === "\n" ? after + 1 : after];
1790
+ }
1791
+ function applyBlock(existing, block) {
1792
+ if (existing === null) return { text: block, action: "create" };
1793
+ const found = region(existing);
1794
+ if (found === void 0) {
1795
+ const before = existing === "" || existing.endsWith("\n") ? existing : `${existing}
1796
+ `;
1797
+ return { text: `${before}${before === "" ? "" : "\n"}${block}`, action: "append" };
1798
+ }
1799
+ const text2 = existing.slice(0, found[0]) + block + existing.slice(found[1]);
1800
+ return { text: text2, action: text2 === existing ? "unchanged" : "update" };
1801
+ }
1802
+ function removeBlock(existing) {
1803
+ const found = region(existing);
1804
+ if (found === void 0) return { text: existing, found: false };
1805
+ const before = existing.slice(0, found[0]);
1806
+ return {
1807
+ text: (before.endsWith("\n\n") ? before.slice(0, -1) : before) + existing.slice(found[1]),
1808
+ found: true
1809
+ };
1810
+ }
1811
+ var START, END, firstSentence2;
1812
+ var init_agent_block = __esm({
1813
+ "src/agent-block.ts"() {
1814
+ "use strict";
1815
+ init_esm_shims();
1816
+ init_lookup();
1817
+ START = "<!-- OPERATOR:START -->";
1818
+ END = "<!-- OPERATOR:END -->";
1819
+ firstSentence2 = (text2) => /^.*?[.!?](?=\s|$)/.exec(text2)?.[0] ?? text2;
1820
+ }
1821
+ });
1822
+
1823
+ // src/doctor.ts
1824
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
1825
+ import { dirname as dirname3, join as join4 } from "path";
1826
+ function installedVersion(cwd, name) {
1827
+ for (let folder = cwd; ; folder = dirname3(folder)) {
1828
+ const manifest = join4(folder, "node_modules", name, "package.json");
1829
+ if (existsSync3(manifest))
1830
+ return JSON.parse(readFileSync4(manifest, "utf8")).version;
1831
+ if (dirname3(folder) === folder) return void 0;
1832
+ }
1833
+ }
1834
+ function doctor({
1835
+ cwd,
1836
+ contract: contract2,
1837
+ parser = probeTypeScript(cwd)
1838
+ }) {
1839
+ const checks = [];
1840
+ for (const name of PACKAGES) {
1841
+ const version = installedVersion(cwd, name);
1842
+ const id = `package:${name}`;
1843
+ if (version === void 0)
1844
+ checks.push({
1845
+ id,
1846
+ status: "warn",
1847
+ message: `${name} is not installed.`,
1848
+ fix: `Install ${name}, or copy the sources you need with oprt add.`
1849
+ });
1850
+ else if (minor(version) !== minor(contract2.version))
1851
+ checks.push({
1852
+ id,
1853
+ status: "warn",
1854
+ message: `${name} is ${version}; this installer describes ${contract2.version}.`,
1855
+ fix: "Install matching versions of the OPERATOR packages and @oprt/cli."
1856
+ });
1857
+ else checks.push({ id, status: "ok", message: `${name} ${version} is installed.` });
1858
+ }
1859
+ const written = INSTRUCTION_FILES.map((path2) => {
1860
+ const file = join4(cwd, path2);
1861
+ return {
1862
+ path: path2,
1863
+ version: existsSync3(file) ? blockVersion(readFileSync4(file, "utf8")) : void 0
1864
+ };
1865
+ }).filter(({ version }) => version !== void 0);
1866
+ const stale = written.find(({ version }) => version !== contract2.version);
1867
+ if (written.length === 0)
1868
+ checks.push({
1869
+ id: "agent-instructions",
1870
+ status: "warn",
1871
+ message: "No agent instruction file carries the OPERATOR block.",
1872
+ fix: "oprt init"
1873
+ });
1874
+ else if (stale !== void 0)
1875
+ checks.push({
1876
+ id: "agent-instructions",
1877
+ status: "warn",
1878
+ message: `${stale.path} was written by oprt ${stale.version}; this is ${contract2.version}.`,
1879
+ fix: "oprt init"
1880
+ });
1881
+ else
1882
+ checks.push({
1883
+ id: "agent-instructions",
1884
+ status: "ok",
1885
+ message: `${written.map(({ path: path2 }) => path2).join(", ")} carries the OPERATOR block (${contract2.version}).`
1886
+ });
1887
+ const install2 = `${INSTALL_TYPESCRIPT} (or your package manager's equivalent), then run oprt doctor again. Lookups work without it.`;
1888
+ let checkCode;
1889
+ if (parser.ready) {
1890
+ const { version, resolved_from, source } = parser.resolution;
1891
+ checkCode = {
1892
+ status: "ready",
1893
+ message: `TypeScript ${version}, resolved from ${resolved_from}${source === "project" ? "" : " (beside oprt, not this project)"}.`,
1894
+ typescript: { version, resolved_from, source }
1895
+ };
1896
+ checks.push(
1897
+ source === "project" ? {
1898
+ id: "typescript",
1899
+ status: "ok",
1900
+ message: `TypeScript ${version} is installed in this project (${resolved_from}).`
1901
+ } : {
1902
+ id: "typescript",
1903
+ status: "warn",
1904
+ message: `TypeScript ${version} is not in this project; the one beside oprt is used (${resolved_from}).`,
1905
+ fix: `${INSTALL_TYPESCRIPT}, so checking reads code with a version installed in the project.`
1906
+ }
1907
+ );
1908
+ } else {
1909
+ checkCode = {
1910
+ status: "blocked",
1911
+ message: `${parser.reason}: ${parser.detail}`,
1912
+ reason: parser.reason,
1913
+ fix: install2
1914
+ };
1915
+ checks.push({
1916
+ id: "typescript",
1917
+ status: "fail",
1918
+ message: `oprt check and the check_code tool cannot run (${parser.reason}): ${parser.detail}`,
1919
+ fix: install2
1920
+ });
1921
+ }
1922
+ const count = (status) => checks.filter((check) => check.status === status).length;
1923
+ return {
1924
+ consumer_directory: cwd,
1925
+ operations: {
1926
+ lookup: {
1927
+ status: "ready",
1928
+ message: `search, component and list read the contract bundled with oprt (${contract2.version}): always available.`,
1929
+ contract_version: contract2.version
1930
+ },
1931
+ check_code: checkCode
1932
+ },
1933
+ checks,
1934
+ summary: { ok: count("ok"), warn: count("warn"), fail: count("fail") }
1935
+ };
1936
+ }
1937
+ var PACKAGES, INSTRUCTION_FILES, minor;
1938
+ var init_doctor = __esm({
1939
+ "src/doctor.ts"() {
1940
+ "use strict";
1941
+ init_esm_shims();
1942
+ init_agent_block();
1943
+ init_parser();
1944
+ PACKAGES = ["@oprt/react", "@oprt/tokens", "@oprt/tailwind"];
1945
+ INSTRUCTION_FILES = [
1946
+ "AGENTS.md",
1947
+ "CLAUDE.md",
1948
+ ".claude/CLAUDE.md",
1949
+ ".cursor/rules/operator.mdc"
1950
+ ];
1951
+ minor = (version) => version.split(".").slice(0, 2).join(".");
1952
+ }
1953
+ });
1954
+
1955
+ // src/init.ts
1956
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, realpathSync as realpathSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
1957
+ import { dirname as dirname4, resolve as resolve3 } from "path";
1958
+ function targets({ agent, path: path2 }) {
1959
+ if (path2 !== void 0) return [path2];
1960
+ if (agent === void 0) return DEFAULT_FILES;
1961
+ const files = AGENT_FILES[agent];
1962
+ if (files === void 0)
1963
+ throw new CliError(
1964
+ `Unknown agent ${JSON.stringify(agent)}. Use one of: ${Object.keys(AGENT_FILES).join(", ")}; or --path <file>.`,
1965
+ 2,
1966
+ { code: "ERR_UNKNOWN_AGENT", suggestions: Object.keys(AGENT_FILES) }
1967
+ );
1968
+ return files;
1969
+ }
1970
+ function init(options, context) {
1971
+ const root = realpathSync2(context.cwd);
1972
+ const block = renderAgentBlock(context.contract);
1973
+ const dryRun = options.dryRun ?? false;
1974
+ const files = targets(options).map((path2) => {
1975
+ validateRelative(path2);
1976
+ const destination = resolve3(root, path2);
1977
+ inspectPath(root, destination);
1978
+ const existing = existsSync4(destination) ? readFileSync5(destination, "utf8") : null;
1979
+ if (options.remove) {
1980
+ if (existing === null) return { path: path2, action: "absent" };
1981
+ const { text: text3, found } = removeBlock(existing);
1982
+ if (!found) return { path: path2, action: "absent" };
1983
+ const empty = withoutFrontMatter(text3).trim() === "";
1984
+ if (!dryRun) {
1985
+ if (empty) rmSync(destination);
1986
+ else writeFileSync2(destination, text3);
1987
+ }
1988
+ return { path: path2, action: empty ? "delete" : "remove" };
1989
+ }
1990
+ const fresh = existing === null && path2.endsWith(".mdc") ? CURSOR_FRONT_MATTER : existing;
1991
+ const { text: text2, action } = applyBlock(fresh, block);
1992
+ if (action !== "unchanged" && !dryRun) {
1993
+ mkdirSync2(dirname4(destination), { recursive: true });
1994
+ writeFileSync2(destination, text2);
1995
+ }
1996
+ return { path: path2, action: existing === null ? "create" : action };
1997
+ });
1998
+ return { version: context.contract.version, dryRun, files };
1999
+ }
2000
+ var AGENT_FILES, DEFAULT_FILES, CURSOR_FRONT_MATTER, withoutFrontMatter;
2001
+ var init_init = __esm({
2002
+ "src/init.ts"() {
2003
+ "use strict";
2004
+ init_esm_shims();
2005
+ init_agent_block();
2006
+ init_installer();
2007
+ init_types();
2008
+ AGENT_FILES = {
2009
+ claude: ["CLAUDE.md"],
2010
+ cursor: [".cursor/rules/operator.mdc"],
2011
+ codex: ["AGENTS.md"],
2012
+ all: ["AGENTS.md", "CLAUDE.md", ".cursor/rules/operator.mdc"]
2013
+ };
2014
+ DEFAULT_FILES = ["AGENTS.md"];
2015
+ CURSOR_FRONT_MATTER = "---\nalwaysApply: true\n---\n\n";
2016
+ withoutFrontMatter = (text2) => text2.replace(/^---\n[\s\S]*?\n---\n?/, "");
2017
+ }
2018
+ });
2019
+
2020
+ // src/setup.ts
2021
+ function setupFor(dependencies, contract2) {
2022
+ const { setup } = contract2.guidance;
2023
+ const scansPackage = dependencies.includes(setup.package_source.when);
2024
+ return {
2025
+ tailwind: setup.tailwind,
2026
+ packages: setup.packages.map(({ name, range, dev, why }) => ({
2027
+ name,
2028
+ range: range ?? "latest",
2029
+ dev,
2030
+ why
2031
+ })),
2032
+ stylesheet: [
2033
+ ...setup.stylesheet,
2034
+ ...scansPackage ? [{ line: setup.package_source.line, why: setup.package_source.why }] : []
2035
+ ],
2036
+ root_import: { line: setup.root_import, note: setup.root_import_note },
2037
+ not_needed: setup.not_needed
2038
+ };
2039
+ }
2040
+ function renderSetup(setup, copiedPackageImports) {
2041
+ const named = ({ name, range }) => range === "latest" ? name : `${name}@${range}`;
2042
+ const runtime = setup.packages.filter(({ dev: dev2 }) => !dev2).map(named);
2043
+ const dev = setup.packages.filter(({ dev: isDev }) => isDev).map(named);
2044
+ return [
2045
+ `Setup, once per project: ${setup.tailwind}`,
2046
+ ` Install: ${runtime.join(", ")}; as dev dependencies: ${dev.join(", ")}.`,
2047
+ " In the stylesheet that imports tailwindcss, after the import:",
2048
+ ...setup.stylesheet.map(({ line }) => ` ${line}`),
2049
+ ...copiedPackageImports ? [" (the @source path is relative to the stylesheet)"] : [
2050
+ " (source copied into the project is scanned by Tailwind 4 without an @source line)"
2051
+ ],
2052
+ ` At the application root, from JavaScript, before the stylesheet: ${setup.root_import.line}`,
2053
+ ...setup.not_needed.map(({ name, why }) => ` Not needed: ${name}. ${why}`)
2054
+ ];
2055
+ }
2056
+ var init_setup = __esm({
2057
+ "src/setup.ts"() {
2058
+ "use strict";
2059
+ init_esm_shims();
2060
+ }
2061
+ });
2062
+
2063
+ // src/agent-commands.ts
2064
+ function renderInstall(result, setup) {
2065
+ const lines = [
2066
+ `${result.dryRun ? "Dry run" : "Installed"}: ${result.entries.join(", ")}`,
2067
+ `Output: ${result.outputDirectory}`,
2068
+ ...result.files.map((file) => ` ${file.action.padEnd(9)} ${file.path}`)
2069
+ ];
2070
+ const versioned = (name) => result.ranges[name] === void 0 ? name : `${name}@${result.ranges[name]}`;
2071
+ const runtime = result.dependencies.filter((name) => !result.devDependencies.includes(name));
2072
+ lines.push("", `Dependencies: ${runtime.map(versioned).join(", ")}`);
2073
+ if (result.devDependencies.length > 0)
2074
+ lines.push(
2075
+ `Dev dependencies (types only): ${result.devDependencies.map(versioned).join(", ")}`
2076
+ );
2077
+ lines.push(
2078
+ "",
2079
+ ...renderSetup(setup, setup.stylesheet.length > 1),
2080
+ "",
2081
+ "Install what is missing yourself. No package configuration or stylesheet was changed."
2082
+ );
2083
+ return `${lines.join("\n")}
2084
+ `;
2085
+ }
2086
+ function runAgentCommand(command, context) {
2087
+ const { contract: contract2, registry: registry2, cwd } = context;
2088
+ switch (command.command) {
2089
+ case "component": {
2090
+ if (command.list) {
2091
+ const components = componentIndex(contract2);
2092
+ return {
2093
+ text: components.map(
2094
+ ({ name, purpose, parts }) => `${name}${parts.length > 0 ? ` (${parts.join(", ")})` : ""}: ${purpose}
2095
+ `
2096
+ ).join(""),
2097
+ json: envelope("component.list", { components }, contract2),
2098
+ exitCode: 0
2099
+ };
2100
+ }
2101
+ const details = command.names.map((name) => componentDetail(contract2, name));
2102
+ const data = details.map((detail) => componentData(detail, command.dense));
2103
+ return {
2104
+ text: details.map((detail) => renderComponent(detail, { dense: command.dense })).join("\n"),
2105
+ json: data.length === 1 ? envelope("component.detail", data[0], contract2) : envelope("component.batch", { components: data }, contract2),
2106
+ exitCode: 0
2107
+ };
2108
+ }
2109
+ case "search": {
2110
+ const results = search(contract2, registry2, command.query, command.limit);
2111
+ return {
2112
+ text: results.length === 0 ? `Nothing matches ${JSON.stringify(command.query)}. Run oprt component --list to see what there is.
2113
+ ` : results.map(
2114
+ ({ kind, name, purpose, why, next }) => `${name} (${kind}): ${purpose}${why === void 0 ? "" : ` [${why}]`}
2115
+ ${next}
2116
+ `
2117
+ ).join(""),
2118
+ json: envelope(
2119
+ "search",
2120
+ { query: command.query, limit: command.limit, matchCount: results.length, results },
2121
+ contract2
2122
+ ),
2123
+ exitCode: 0
2124
+ };
2125
+ }
2126
+ case "init": {
2127
+ const result = init(command, { cwd, contract: contract2 });
2128
+ const lines = [
2129
+ `OPERATOR ${result.version} agent instructions${result.dryRun ? " (dry run)" : ""}`,
2130
+ ...result.files.map(({ path: path2, action }) => ` ${action.padEnd(9)} ${path2}`),
2131
+ 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."
2132
+ ];
2133
+ return {
2134
+ text: `${lines.join("\n")}
2135
+ `,
2136
+ json: envelope("init", result, contract2),
2137
+ exitCode: 0
2138
+ };
2139
+ }
2140
+ case "check": {
2141
+ const report = checkPaths(command.paths, { cwd, contract: contract2 });
2142
+ return {
2143
+ text: renderCheckText(report),
2144
+ json: envelope("check", report, contract2),
2145
+ // Warnings fail the run only when asked to: they are house rules, or may be native attributes.
2146
+ exitCode: checkExitCode(report, { strict: command.strict })
2147
+ };
2148
+ }
2149
+ case "doctor": {
2150
+ const report = doctor({
2151
+ cwd,
2152
+ contract: contract2,
2153
+ ...context.parser ? { parser: context.parser } : {}
2154
+ });
2155
+ const { lookup, check_code: checking } = report.operations;
2156
+ const lines = [
2157
+ "Readiness",
2158
+ ` lookups ${lookup.status.padEnd(8)} ${lookup.message}`,
2159
+ ` code checking ${checking.status.padEnd(8)} ${checking.message}`,
2160
+ ...checking.fix === void 0 ? [] : [` fix: ${checking.fix}`],
2161
+ "",
2162
+ "Setup"
2163
+ ];
2164
+ lines.push(
2165
+ ...report.checks.flatMap(({ status, message, fix }) => [
2166
+ `[${status}] ${message}`,
2167
+ ...fix === void 0 ? [] : [` fix: ${fix}`]
2168
+ ])
2169
+ );
2170
+ lines.push(
2171
+ `${report.summary.ok} ok, ${report.summary.warn} to look at, ${report.summary.fail} failed.`
2172
+ );
2173
+ return {
2174
+ text: `${lines.join("\n")}
2175
+ `,
2176
+ json: envelope("doctor", report, contract2),
2177
+ exitCode: report.summary.fail > 0 ? 1 : 0
2178
+ };
2179
+ }
2180
+ case "list": {
2181
+ const lines = [`OPERATOR ${VERSION}`, ""];
2182
+ for (const kind of ["component", "recipe"]) {
2183
+ const entries = registry2.entries.filter((entry) => entry.kind === kind);
2184
+ lines.push(`${kind === "component" ? "Components" : "Recipes"} (${entries.length})`);
2185
+ for (const entry of entries) lines.push(` ${entry.slug.padEnd(22)} ${entry.description}`);
2186
+ lines.push("");
2187
+ }
2188
+ return {
2189
+ text: `${lines.join("\n")}
2190
+ `,
2191
+ json: envelope(
2192
+ "list",
2193
+ { version: VERSION, entries: registry2.entries, ranges: registry2.ranges ?? {} },
2194
+ contract2
2195
+ ),
2196
+ exitCode: 0
2197
+ };
2198
+ }
2199
+ case "add": {
2200
+ const result = installSources(command, { cwd, registry: registry2 });
2201
+ const setup = setupFor(result.dependencies, contract2);
2202
+ return {
2203
+ text: renderInstall(result, setup),
2204
+ json: envelope("add", { ...result, setup }, contract2),
2205
+ exitCode: 0
2206
+ };
2207
+ }
2208
+ }
2209
+ }
2210
+ var init_agent_commands = __esm({
2211
+ "src/agent-commands.ts"() {
2212
+ "use strict";
2213
+ init_esm_shims();
2214
+ init_check();
2215
+ init_doctor();
2216
+ init_identity();
2217
+ init_init();
2218
+ init_installer();
2219
+ init_lookup();
2220
+ init_results();
2221
+ init_setup();
2222
+ }
2223
+ });
2224
+
2225
+ // src/validate.ts
2226
+ function describeValue(value) {
2227
+ if (typeof value === "string")
2228
+ return `${JSON.stringify(value.length > 40 ? `${value.slice(0, 37)}...` : value)} (a string)`;
2229
+ if (typeof value === "number") return Number.isNaN(value) ? "NaN" : String(value);
2230
+ if (value === null || typeof value === "boolean" || value === void 0) return String(value);
2231
+ return `${typeName(value)} (${JSON.stringify(value)?.slice(0, 40) ?? ""})`;
2232
+ }
2233
+ function expectation(schema) {
2234
+ switch (schema.type) {
2235
+ case "boolean":
2236
+ return "true or false";
2237
+ case "integer":
2238
+ case "number": {
2239
+ const noun = schema.type === "integer" ? "a whole number" : "a number";
2240
+ if (schema.minimum !== void 0 && schema.maximum !== void 0)
2241
+ return `${noun} from ${schema.minimum} to ${schema.maximum}`;
2242
+ if (schema.minimum !== void 0) return `${noun} of at least ${schema.minimum}`;
2243
+ if (schema.maximum !== void 0) return `${noun} of at most ${schema.maximum}`;
2244
+ return noun;
2245
+ }
2246
+ case "string": {
2247
+ if (schema.enum !== void 0) return `one of ${schema.enum.map(String).join(", ")}`;
2248
+ const { minLength, maxLength } = schema;
2249
+ if (minLength !== void 0 && maxLength !== void 0)
2250
+ return `a string of ${minLength} to ${maxLength.toLocaleString("en-US")} characters`;
2251
+ if (maxLength !== void 0)
2252
+ return `a string of at most ${maxLength.toLocaleString("en-US")} characters`;
2253
+ return "a string";
2254
+ }
2255
+ }
2256
+ }
2257
+ function accepts(schema, value) {
2258
+ switch (schema.type) {
2259
+ case "boolean":
2260
+ return typeof value === "boolean";
2261
+ case "string":
2262
+ 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));
2263
+ case "integer":
2264
+ case "number":
2265
+ 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);
2266
+ }
2267
+ }
2268
+ function validateArguments(tool, schema, args) {
2269
+ for (const [field, property] of Object.entries(schema.properties))
2270
+ for (const key of Object.keys(property))
2271
+ if (!KNOWN.has(key))
2272
+ throw new Error(`${tool}.${field}: the schema uses "${key}", which is not enforced.`);
2273
+ if (typeof args !== "object" || args === null || Array.isArray(args))
2274
+ return `${tool}: arguments must be an object.`;
2275
+ const given = args;
2276
+ const required = schema.required ?? [];
2277
+ const blank = (value) => typeof value === "string" && value.trim() === "";
2278
+ const missing = [];
2279
+ const wrong = [];
2280
+ const unknown = [];
2281
+ for (const field of required)
2282
+ if (given[field] === void 0 || blank(given[field])) missing.push(field);
2283
+ for (const [field, value] of Object.entries(given)) {
2284
+ const property = Object.hasOwn(schema.properties, field) ? schema.properties[field] : void 0;
2285
+ if (property === void 0) unknown.push(field);
2286
+ else if (value !== void 0 && !(required.includes(field) && blank(value))) {
2287
+ if (!accepts(property, value))
2288
+ wrong.push(`${field} must be ${expectation(property)}, but got ${describeValue(value)}`);
2289
+ }
2290
+ }
2291
+ const reject = schema.additionalProperties === false ? unknown : [];
2292
+ const names2 = Object.keys(schema.properties);
2293
+ if (missing.length === 1 && wrong.length === 0 && reject.length === 0)
2294
+ return `${tool} needs ${missing[0]}, ${expectation({ type: schema.properties[missing[0]].type })}.`;
2295
+ const fragments = [
2296
+ ...missing.map(
2297
+ (field) => `${field} is required (${expectation({ type: schema.properties[field].type })})`
2298
+ ),
2299
+ ...wrong,
2300
+ ...reject.map(
2301
+ (field) => `unknown argument ${JSON.stringify(field)}. It takes${names2.length > 0 ? `: ${names2.join(", ")}` : " none"}`
2302
+ )
2303
+ ];
2304
+ return fragments.length === 0 ? null : `${tool}: ${fragments.join("; ")}.`;
2305
+ }
2306
+ var KNOWN, typeName;
2307
+ var init_validate = __esm({
2308
+ "src/validate.ts"() {
2309
+ "use strict";
2310
+ init_esm_shims();
2311
+ KNOWN = /* @__PURE__ */ new Set([
2312
+ "type",
2313
+ "description",
2314
+ "default",
2315
+ "enum",
2316
+ "minimum",
2317
+ "maximum",
2318
+ "minLength",
2319
+ "maxLength"
2320
+ ]);
2321
+ 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;
2322
+ }
2323
+ });
2324
+
2325
+ // src/mcp.ts
2326
+ import { createInterface } from "readline";
2327
+ function runTool(name, args, context) {
2328
+ const { contract: contract2, registry: registry2 } = context;
2329
+ const definition = TOOLS.find((tool) => tool.name === name);
2330
+ if (definition === void 0) throw new UnknownTool(name);
2331
+ const problem = validateArguments(name, definition.inputSchema, args);
2332
+ if (problem !== null) throw new CliError(problem, 2, { code: "ERR_INVALID_ARGUMENT" });
2333
+ switch (name) {
2334
+ case "search": {
2335
+ const query = args["query"];
2336
+ const limit = args["limit"] ?? SEARCH_LIMIT_DEFAULT;
2337
+ const results = search(contract2, registry2, query, limit);
2338
+ return {
2339
+ text: results.length === 0 ? `Nothing matches ${JSON.stringify(query)}. Call list_components to see what there is.` : results.map(
2340
+ ({ kind, name: found, purpose, why, next }) => `${found} (${kind}): ${purpose}${why === void 0 ? "" : ` [${why}]`}
2341
+ ${next}`
2342
+ ).join("\n"),
2343
+ structured: envelope(
2344
+ "search",
2345
+ { query, limit, matchCount: results.length, results },
2346
+ contract2
2347
+ )
2348
+ };
2349
+ }
2350
+ case "get_component": {
2351
+ const dense = args["dense"] ?? true;
2352
+ const detail = componentDetail(contract2, args["name"]);
2353
+ return {
2354
+ text: renderComponent(detail, { dense }),
2355
+ structured: envelope("component.detail", componentData(detail, dense), contract2)
2356
+ };
2357
+ }
2358
+ case "list_components": {
2359
+ const components = componentIndex(contract2);
2360
+ return {
2361
+ text: components.map(
2362
+ ({ name: component, purpose, parts }) => `${component}${parts.length > 0 ? ` (${parts.join(", ")})` : ""}: ${purpose}`
2363
+ ).join("\n"),
2364
+ structured: envelope("component.list", { components }, contract2)
2365
+ };
2366
+ }
2367
+ case "get_instructions":
2368
+ return {
2369
+ text: renderAgentBlock(contract2).split("\n").filter((line) => line !== START && line !== END && !line.startsWith("<!-- Written by")).join("\n"),
2370
+ structured: envelope("instructions", instructionsData(contract2), contract2)
2371
+ };
2372
+ case "check_code": {
2373
+ const { typescript, parser } = resolveParser(
2374
+ context.cwd ?? process.cwd(),
2375
+ context.typescript,
2376
+ "check_code"
2377
+ );
2378
+ const report = buildCheckReport(
2379
+ [checkSource(typescript, contract2, "snippet.tsx", args["code"])],
2380
+ { parser }
2381
+ );
2382
+ return { text: renderCheckText(report), structured: envelope("check", report, contract2) };
2383
+ }
2384
+ default:
2385
+ throw new UnknownTool(name);
2386
+ }
2387
+ }
2388
+ function handleMessage(raw, context) {
2389
+ const failure2 = (id2, code, message) => ({
2390
+ jsonrpc: "2.0",
2391
+ id: id2 ?? null,
2392
+ error: { code, message }
2393
+ });
2394
+ if (typeof raw !== "object" || raw === null) return failure2(null, -32700, "Parse error");
2395
+ const { id, method, params } = raw;
2396
+ if (typeof method !== "string") return failure2(id, -32600, "Invalid request");
2397
+ if (id === void 0) return null;
2398
+ const result = (value) => ({ jsonrpc: "2.0", id, result: value });
2399
+ switch (method) {
2400
+ case "initialize": {
2401
+ const asked = params?.protocolVersion;
2402
+ const identity = identityOf(context.contract);
2403
+ return result({
2404
+ protocolVersion: asked !== void 0 && VERSIONS.includes(asked) ? asked : VERSIONS[0],
2405
+ capabilities: { tools: {} },
2406
+ // The program's version, not the contract's: the contract has its own, in `_meta` and in
2407
+ // the identity of every result.
2408
+ serverInfo: { name: "oprt", version: VERSION },
2409
+ 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.",
2410
+ _meta: { "dev.oprt/identity": identity }
2411
+ });
2412
+ }
2413
+ case "ping":
2414
+ return result({});
2415
+ case "tools/list":
2416
+ return result({ tools: TOOLS });
2417
+ case "tools/call": {
2418
+ const { name, arguments: args } = params ?? {};
2419
+ try {
2420
+ const output = runTool(String(name), args ?? {}, context);
2421
+ return result({
2422
+ content: [text(output.text)],
2423
+ structuredContent: output.structured
2424
+ });
2425
+ } catch (error) {
2426
+ if (error instanceof UnknownTool) return failure2(id, -32602, error.message);
2427
+ const failed = errorEnvelope(error, context.contract);
2428
+ return result({
2429
+ content: [text(failed.error)],
2430
+ structuredContent: failed,
2431
+ isError: true
2432
+ });
2433
+ }
2434
+ }
2435
+ default:
2436
+ return failure2(id, -32601, `Method not found: ${method}`);
2437
+ }
2438
+ }
2439
+ function serve(input, output, context) {
2440
+ const lines = createInterface({ input, crlfDelay: Infinity });
2441
+ lines.on("line", (line) => {
2442
+ if (line.trim() === "") return;
2443
+ let message;
2444
+ try {
2445
+ message = JSON.parse(line);
2446
+ } catch {
2447
+ message = line;
2448
+ }
2449
+ const response = handleMessage(message, context);
2450
+ if (response !== null) output.write(`${JSON.stringify(response)}
2451
+ `);
2452
+ });
2453
+ return new Promise((done) => lines.on("close", done));
2454
+ }
2455
+ var VERSIONS, text, LIMITS, ANSWERS, readOnly, define, TOOLS, UnknownTool;
2456
+ var init_mcp = __esm({
2457
+ "src/mcp.ts"() {
2458
+ "use strict";
2459
+ init_esm_shims();
2460
+ init_agent_block();
2461
+ init_check();
2462
+ init_identity();
2463
+ init_lookup();
2464
+ init_results();
2465
+ init_types();
2466
+ init_validate();
2467
+ VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"];
2468
+ text = (value) => ({ type: "text", text: value });
2469
+ LIMITS = { query: 500, name: 100, code: 2e5 };
2470
+ ANSWERS = {
2471
+ search: "search",
2472
+ get_component: "component.detail",
2473
+ list_components: "component.list",
2474
+ get_instructions: "instructions",
2475
+ check_code: "check"
2476
+ };
2477
+ readOnly = { readOnlyHint: true };
2478
+ define = (tool) => ({
2479
+ ...tool,
2480
+ outputSchema: outputSchemaFor(ANSWERS[tool.name]),
2481
+ annotations: readOnly
2482
+ });
2483
+ TOOLS = [
2484
+ define({
2485
+ name: "search",
2486
+ description: "Find the OPERATOR component or recipe for a need, such as 'show a status' or 'table with selection'. Call before writing interface code.",
2487
+ inputSchema: {
2488
+ type: "object",
2489
+ properties: {
2490
+ query: {
2491
+ type: "string",
2492
+ minLength: 1,
2493
+ maxLength: LIMITS.query,
2494
+ description: "The need, in plain words."
2495
+ },
2496
+ limit: {
2497
+ type: "integer",
2498
+ minimum: 1,
2499
+ maximum: SEARCH_LIMIT_MAX,
2500
+ default: SEARCH_LIMIT_DEFAULT,
2501
+ description: `Results to return, 1 to ${SEARCH_LIMIT_MAX}.`
2502
+ }
2503
+ },
2504
+ required: ["query"],
2505
+ additionalProperties: false
2506
+ }
2507
+ }),
2508
+ define({
2509
+ name: "get_component",
2510
+ 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.",
2511
+ inputSchema: {
2512
+ type: "object",
2513
+ properties: {
2514
+ name: {
2515
+ type: "string",
2516
+ minLength: 1,
2517
+ maxLength: LIMITS.name,
2518
+ description: "Component name, such as Button."
2519
+ },
2520
+ dense: {
2521
+ type: "boolean",
2522
+ default: true,
2523
+ description: "Compact form (every constraint, one sentence per prop). False: full prose."
2524
+ }
2525
+ },
2526
+ required: ["name"],
2527
+ additionalProperties: false
2528
+ }
2529
+ }),
2530
+ define({
2531
+ name: "list_components",
2532
+ description: "Every OPERATOR component in one line each, with its parts.",
2533
+ inputSchema: { type: "object", properties: {}, additionalProperties: false }
2534
+ }),
2535
+ define({
2536
+ name: "get_instructions",
2537
+ description: "OPERATOR's house rules, component index and recipes: read once before building.",
2538
+ inputSchema: { type: "object", properties: {}, additionalProperties: false }
2539
+ }),
2540
+ define({
2541
+ name: "check_code",
2542
+ 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.",
2543
+ inputSchema: {
2544
+ type: "object",
2545
+ properties: {
2546
+ code: {
2547
+ type: "string",
2548
+ minLength: 1,
2549
+ maxLength: LIMITS.code,
2550
+ description: "The TSX source of one file."
2551
+ }
2552
+ },
2553
+ required: ["code"],
2554
+ additionalProperties: false
2555
+ }
2556
+ })
2557
+ ];
2558
+ UnknownTool = class extends Error {
2559
+ constructor(name) {
2560
+ super(
2561
+ `Unknown tool ${JSON.stringify(name)}. Its tools: ${TOOLS.map((tool) => tool.name).join(", ")}.`
2562
+ );
2563
+ }
2564
+ };
2565
+ }
2566
+ });
2567
+
2568
+ // src/index.ts
2569
+ var index_exports = {};
2570
+ __export(index_exports, {
2571
+ CliError: () => CliError,
2572
+ DEFAULT_OUT: () => DEFAULT_OUT,
2573
+ RESULT_SCHEMAS: () => RESULT_SCHEMAS,
2574
+ RESULT_SCHEMA_VERSION: () => RESULT_SCHEMA_VERSION,
2575
+ RESULT_TYPES: () => RESULT_TYPES,
2576
+ TOOLS: () => TOOLS,
2577
+ VERSION: () => VERSION,
2578
+ applyBlock: () => applyBlock,
2579
+ blockVersion: () => blockVersion,
2580
+ buildCheckReport: () => buildCheckReport,
2581
+ checkExitCode: () => checkExitCode,
2582
+ checkPaths: () => checkPaths,
2583
+ checkSource: () => checkSource,
2584
+ componentDetail: () => componentDetail,
2585
+ componentIndex: () => componentIndex,
2586
+ contract: () => contract,
2587
+ contractDigest: () => contractDigest,
2588
+ doctor: () => doctor,
2589
+ envelope: () => envelope,
2590
+ errorEnvelope: () => errorEnvelope,
2591
+ handleMessage: () => handleMessage,
2592
+ identityOf: () => identityOf,
2593
+ init: () => init,
2594
+ install: () => install,
2595
+ outputSchemaFor: () => outputSchemaFor,
2596
+ parseArgs: () => parseArgs,
2597
+ registry: () => registry,
2598
+ removeBlock: () => removeBlock,
2599
+ renderAgentBlock: () => renderAgentBlock,
2600
+ renderCheckText: () => renderCheckText,
2601
+ renderComponent: () => renderComponent,
2602
+ resolveEntries: () => resolveEntries,
2603
+ runAgentCommand: () => runAgentCommand,
2604
+ schemaFor: () => schemaFor,
2605
+ search: () => search,
2606
+ serve: () => serve
2607
+ });
2608
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
2609
+ import { dirname as dirname5, join as join5 } from "path";
2610
+ import { fileURLToPath as fileURLToPath4 } from "url";
2611
+ function loadRegistry() {
2612
+ const here = dirname5(fileURLToPath4(import.meta.url));
2613
+ const candidates = [join5(here, "registry.json"), join5(here, "../generated/registry.json")];
2614
+ const found = candidates.find((candidate) => existsSync5(candidate));
2615
+ if (found === void 0) {
2616
+ throw new Error(
2617
+ `registry.json is missing next to ${here}. Install @oprt/cli again: the registry ships beside the executable, and the executable cannot be copied without it.`
2618
+ );
2619
+ }
2620
+ return JSON.parse(readFileSync6(found, "utf8"));
2621
+ }
2622
+ function install(options, context = {}) {
2623
+ return installSources(options, {
2624
+ cwd: context.cwd ?? process.cwd(),
2625
+ registry: context.registry ?? registry
2626
+ });
2627
+ }
2628
+ var registry, loaded, contract;
2629
+ var init_index = __esm({
2630
+ "src/index.ts"() {
2631
+ "use strict";
2632
+ init_esm_shims();
2633
+ init_contract();
2634
+ init_installer();
2635
+ init_identity();
2636
+ init_agent_commands();
2637
+ init_identity();
2638
+ init_results();
2639
+ init_agent_block();
2640
+ init_check();
2641
+ init_doctor();
2642
+ init_mcp();
2643
+ init_init();
2644
+ init_lookup();
2645
+ init_args();
2646
+ init_installer();
2647
+ init_types();
2648
+ registry = loadRegistry();
2649
+ contract = () => loaded ??= loadContract();
2650
+ }
2651
+ });
2652
+
2653
+ // src/bin.ts
2654
+ init_esm_shims();
2655
+ init_args();
2656
+ init_results();
2657
+ init_types();
2658
+ var helpText = (version) => `oprt ${version} \u2014 OPERATOR source installer
2659
+
2660
+ Usage:
2661
+ oprt list [--json]
2662
+ oprt add <name...> [--out <relative directory>] [--dry-run] [--overwrite]
2663
+ oprt --help
2664
+ oprt --version
2665
+
2666
+ For coding agents (each takes --json):
2667
+ oprt init [--agent claude|cursor|codex|all | --path <file>] [--remove] [--dry-run]
2668
+ Write the house rules and component index into the
2669
+ agent's instruction file (AGENTS.md by default).
2670
+ oprt search <need> Which component or recipe answers a need.
2671
+ oprt component <Name...> [--dense]
2672
+ One component: options, nesting rules, example.
2673
+ oprt component --list Every component, one line each.
2674
+ oprt check <paths...> [--strict]
2675
+ Check written code against the contract: option values,
2676
+ nesting, required props, house rules. Says how much it
2677
+ read (evaluation_status); a file that does not parse is
2678
+ not checked, and is never clean.
2679
+ oprt doctor Check this project's setup.
2680
+ oprt mcp Serve the lookups and the check to an agent over the
2681
+ Model Context Protocol (stdin and stdout).
2682
+
2683
+ Exit codes: 0 success. check: 0 when every file read has no errors (no warnings either
2684
+ under --strict); limits it could not read are shown and do not fail the run. 1 when check
2685
+ finds an error, a file does not parse, or no .tsx or .jsx file was read; also for other
2686
+ failures (file conflicts, TypeScript missing). 2 for a usage error, an unknown name or a
2687
+ path that does not exist.
2688
+
2689
+ Names accept PascalCase or kebab-case. Default output: src/components/operator.
2690
+ --dry-run Preview all files and dependencies without creating anything.
2691
+ --overwrite Replace different existing files; identical files are always skipped.
2692
+
2693
+ Examples:
2694
+ oprt add button filter-bar --out src/operator
2695
+ oprt add asset-management --dry-run
2696
+
2697
+ Run from the consumer project. Output must stay inside that working directory.
2698
+ Dependencies and Tailwind setup are reported; package configuration is never edited.
2699
+ `;
2700
+ try {
2701
+ const major = Number(process.versions.node.split(".")[0]);
2702
+ if (major < 22) throw new CliError("oprt requires Node.js 22 or newer.");
2703
+ const command = parseArgs(process.argv.slice(2));
2704
+ const { VERSION: VERSION2, contract: contract2, registry: registry2, runAgentCommand: runAgentCommand2, serve: serve2 } = await Promise.resolve().then(() => (init_index(), index_exports));
2705
+ if (command.command === "mcp")
2706
+ await serve2(process.stdin, process.stdout, { contract: contract2(), registry: registry2 });
2707
+ else if (command.command === "help") process.stdout.write(helpText(VERSION2));
2708
+ else if (command.command === "version") process.stdout.write(`${VERSION2}
2709
+ `);
2710
+ else {
2711
+ const answer = runAgentCommand2(command, { cwd: process.cwd(), contract: contract2(), registry: registry2 });
2712
+ process.stdout.write(command.json ? `${JSON.stringify(answer.json, null, 2)}
2713
+ ` : answer.text);
2714
+ process.exitCode = answer.exitCode;
2715
+ }
2716
+ } catch (error) {
2717
+ const message = error instanceof Error ? error.message : String(error);
2718
+ if (process.argv.includes("--json")) {
2719
+ let loaded2;
2720
+ try {
2721
+ loaded2 = (await Promise.resolve().then(() => (init_index(), index_exports))).contract();
2722
+ } catch {
2723
+ loaded2 = void 0;
2724
+ }
2725
+ process.stdout.write(`${JSON.stringify(errorEnvelope(error, loaded2), null, 2)}
2726
+ `);
2727
+ } else process.stderr.write(`oprt: ${message}
2728
+ `);
2729
+ process.exitCode = error instanceof CliError ? error.exitCode : 1;
2730
+ }