cyber-agentkit 0.0.0-stage → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,984 @@
1
+ import { C as userReferencesDir, E as require_dist, M as pluginManifestName, S as repositoryRoot, _ as parseSections, a as interfaceLookup, b as projectReferencesDir, c as readExtends, d as parseReferenceName, f as fitsName, g as parseDocument, h as isMergeOperation, i as checkResolved, m as hasBody, n as referenceImplementations, o as listReferences, p as frontmatterPattern, r as checkReferences, s as searchReferences, t as whereReference, u as resolveReference, v as canonical, w as skillName, x as referenceLayers } from "./reference-where-BhymfNgA.js";
2
+ import { dirname, join, resolve, sep } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { existsSync, mkdirSync, readFileSync, rmSync, rmdirSync, writeFileSync } from "node:fs";
5
+ import { command, exitCodes, t } from "clibuilder";
6
+ import { CliError, document, encode, isCliError, printAnswer, result } from "clibuilder/axi";
7
+ //#region src/command-output/collapse-home.ts
8
+ /**
9
+ * AXI §10: collapses the home directory to `~` so a path in a report is portable to another
10
+ * machine.
11
+ */
12
+ function collapseHome(home, path) {
13
+ return home && path.startsWith(home + sep) ? `~${path.slice(home.length)}` : path;
14
+ }
15
+ //#endregion
16
+ //#region src/references/create-reference.ts
17
+ var import_dist = require_dist();
18
+ /** No `#` title: `patch` matches by heading path, so a title would hold every section under it. */
19
+ const defaultTemplate = `---
20
+ description: What this reference covers, and when an agent should read it.
21
+ tags: [topic]
22
+ ---
23
+
24
+ ## Overview
25
+
26
+ Write the guidance here. Give each topic its own top-level \`##\` section.
27
+ `;
28
+ /** The interface's sections as `##` headings, in order, each stubbed `**Unsupported.**`. */
29
+ function implementationTemplate(iface) {
30
+ const sections = iface.sections.map((name) => `## ${name}\n\n**Unsupported.** Replace this line with the section, or say in one sentence why it is not supported.\n`);
31
+ return parseTemplate(`---\ndescription: What this ${iface.id} implementation covers, and when an agent should read it.\nimplements: ${iface.id}@${iface.version}\n---\n\n${sections.join("\n")}`, `the ${iface.id} interface`);
32
+ }
33
+ function readTemplate(path) {
34
+ if (path === void 0) return parseTemplate(defaultTemplate, "the default template");
35
+ let content;
36
+ try {
37
+ content = readFileSync(path, "utf8");
38
+ } catch (error) {
39
+ throw new Error(`cannot read the template ${path}: ${error.message}`);
40
+ }
41
+ return parseTemplate(content, `the template ${path}`);
42
+ }
43
+ function parseTemplate(content, label) {
44
+ let mapping = true;
45
+ const { metadata, body } = parseDocument(content, () => {
46
+ mapping = false;
47
+ });
48
+ if (!mapping) throw new Error(`${label} has frontmatter that is not a YAML mapping.`);
49
+ return {
50
+ content,
51
+ metadata,
52
+ body
53
+ };
54
+ }
55
+ /**
56
+ * A title: the top level is a single heading holding every other one. `patch` matches by heading
57
+ * path, so it would add the whole document under the title instead of patching its sections.
58
+ */
59
+ function titleOf({ body }) {
60
+ const tree = parseSections(body, () => {});
61
+ const [only] = tree.children;
62
+ return tree.children.length === 1 && only?.children.length ? only.heading.trim() : void 0;
63
+ }
64
+ function hasDescription({ metadata }) {
65
+ return typeof metadata["description"] === "string" && metadata["description"].trim().length > 0;
66
+ }
67
+ /**
68
+ * Sets or removes frontmatter keys and keeps the rest of the file as written: other keys, comments,
69
+ * and the body. A file left with no key and no body is empty.
70
+ */
71
+ function editFrontmatter(raw, keys) {
72
+ const match = frontmatterPattern.exec(raw);
73
+ const body = match ? raw.slice(match[0].length) : raw;
74
+ const doc = (0, import_dist.parseDocument)(match ? match[1] : "");
75
+ for (const [key, value] of Object.entries(keys)) if (value === void 0) doc.delete(key);
76
+ else {
77
+ const node = doc.createNode(value, { flow: Array.isArray(value) });
78
+ doc.set(key, node);
79
+ }
80
+ if (!doc.contents?.items?.length) return body.replace(/^(?:[ \t]*\r?\n)+/, "");
81
+ const gap = !match && body.trim() ? "\n" : "";
82
+ return `---\n${doc.toString({
83
+ lineWidth: 0,
84
+ flowCollectionPadding: false
85
+ })}---\n${gap}${body}`;
86
+ }
87
+ //#endregion
88
+ //#region src/references/reference-write.ts
89
+ /** A write refused. `usage` marks a wrong invocation (exit 2) rather than a refusal (exit 1). */
90
+ var ReferenceWriteError = class extends Error {
91
+ code;
92
+ usage;
93
+ constructor(code, message, usage = false) {
94
+ super(message);
95
+ this.code = code;
96
+ this.usage = usage;
97
+ }
98
+ };
99
+ /** A bare, valid name, or a refusal naming the bare name to use. */
100
+ function bareName(value) {
101
+ const name = parseReferenceName(value);
102
+ if (name.invalid) throw new ReferenceWriteError("reference-invalid", name.invalid);
103
+ if (name.plugin !== void 0) throw new ReferenceWriteError("reference-qualified", `a contribution is written under the bare name, which every tier resolves; ask for "${name.name}".`);
104
+ return name;
105
+ }
106
+ /** The repository the root sits in: a write to a source folder must land inside it, and never in an installed copy. */
107
+ function insideRepository(folder, root) {
108
+ const repository = canonical(repositoryRoot(root) ?? resolve(root));
109
+ const target = canonical(folder);
110
+ if (target !== repository && !target.startsWith(repository + sep)) return false;
111
+ return !target.slice(repository.length).split(sep).includes("node_modules");
112
+ }
113
+ /** Resolves `--scope`, `--plugin`, and `--default` to the file a write acts on; more than one is a usage error. */
114
+ function writeTarget(name, { root, home, scope, plugin, default: skill }) {
115
+ if ([
116
+ scope,
117
+ plugin,
118
+ skill
119
+ ].filter((value) => value !== void 0).length > 1) throw new ReferenceWriteError("options-conflict", "pass one of --scope, --plugin, and --default.", true);
120
+ if (plugin !== void 0) {
121
+ const folder = resolve(plugin);
122
+ if (!pluginManifestName(folder)) throw new ReferenceWriteError("folder-invalid", `${folder} holds no plugin manifest, so it is not a plugin folder.`);
123
+ if (!insideRepository(folder, root)) throw new ReferenceWriteError("folder-invalid", `${folder} is outside the repository, such as an installed copy; write to the plugin's source instead.`);
124
+ const dir = join(folder, "references");
125
+ return {
126
+ scope: "plugin",
127
+ dir,
128
+ path: join(dir, `${name.name}.md`)
129
+ };
130
+ }
131
+ if (skill !== void 0) {
132
+ const folder = resolve(skill);
133
+ if (skillName(folder) === void 0) throw new ReferenceWriteError("folder-invalid", `${folder} holds no SKILL.md, so it is not a skill folder.`);
134
+ if (!insideRepository(folder, root)) throw new ReferenceWriteError("folder-invalid", `${folder} is outside the repository, such as an installed copy; write to the skill's source instead.`);
135
+ const dir = join(folder, "references");
136
+ return {
137
+ scope: "default",
138
+ dir,
139
+ path: join(dir, `${name.name}.default.md`)
140
+ };
141
+ }
142
+ if (scope !== void 0 && scope !== "project" && scope !== "user") throw new ReferenceWriteError("options-invalid", "--scope is project or user.", true);
143
+ const dir = scope === "user" ? userReferencesDir(home) : projectReferencesDir(resolve(root));
144
+ return {
145
+ scope: scope === "user" ? "user" : "project",
146
+ dir,
147
+ path: join(dir, `${name.name}.md`)
148
+ };
149
+ }
150
+ function tryRead(path) {
151
+ try {
152
+ return readFileSync(path, "utf8");
153
+ } catch {
154
+ return;
155
+ }
156
+ }
157
+ /** What answers a name, in one sentence a person reads. */
158
+ function describeAnswer(resolved, display = (path) => path) {
159
+ if (resolved.status === "found") {
160
+ const top = resolved.trace.contributions.at(-1);
161
+ const by = top ? top.tier === "plugin" ? `plugin ${top.plugin}` : top.tier === "default" ? `the default of ${top.skill}` : `the ${top.tier} file ${display(top.path)}` : "nothing";
162
+ const stack = resolved.trace.stack.length ? `, on the stack ${resolved.trace.stack.join(" → ")}` : "";
163
+ return `"${resolved.name}" is answered by ${by}${stack}.`;
164
+ }
165
+ if (resolved.status === "ambiguous") return `"${resolved.name}" is ambiguous: ${resolved.message}; the options are ${(resolved.options ?? []).join(", ")}.`;
166
+ return `"${resolved.name}" is ${resolved.status}: ${resolved.message}.`;
167
+ }
168
+ /** The interface `create --implements` starts an implementation of, once the new name fits it. */
169
+ function interfaceFor(id, name, layers, options) {
170
+ const parsed = parseReferenceName(id);
171
+ if (parsed.invalid || parsed.plugin !== void 0) throw new ReferenceWriteError("interface-invalid", `"${id}" is not an interface id.`);
172
+ const iface = interfaceLookup(layers, options)(id);
173
+ if (!iface.value) throw new ReferenceWriteError("interface-invalid", iface.failure?.message ?? `no interface ${id}`);
174
+ if (!fitsName(iface.value, name)) throw new ReferenceWriteError("interface-invalid", `an implementation of ${id} is named ${iface.value.cardinality === "one" ? id : `${id}.<key>`}, not "${name}".`);
175
+ return iface.value;
176
+ }
177
+ /**
178
+ * Hides the new file: a `replace` with a body, or a `remove`, that applies above it. A folder that
179
+ * is not a layer the stack reads has nothing above it.
180
+ */
181
+ function hiddenBy(resolved, dir, path) {
182
+ const layers = resolved.trace.layers;
183
+ const at = layers.findIndex((layer) => layer.path === dir || layer.path === path);
184
+ if (at === -1) return void 0;
185
+ const target = layers[at];
186
+ const tiered = (tier) => tier === "managed" || tier === "project" || tier === "user";
187
+ const stackAt = target && !tiered(target.tier) ? resolved.trace.stack.indexOf(target.tier === "default" ? "default" : target.plugin ?? "") : -1;
188
+ const hider = layers.filter((layer, index) => {
189
+ if (!layer.file || layer.outcome !== "used") return false;
190
+ if (target && tiered(target.tier)) return index < at && tiered(layer.tier);
191
+ if (tiered(layer.tier)) return true;
192
+ const position = resolved.trace.stack.indexOf(layer.tier === "default" ? "default" : layer.plugin ?? "");
193
+ return stackAt !== -1 && position > stackAt;
194
+ }).find((layer) => layer.operation === "remove" || layer.operation === "replace" && !layer.empty);
195
+ return hider ? `${hider.tier}${hider.plugin ? ` ${hider.plugin}` : ""} ${hider.path} (${hider.operation})` : void 0;
196
+ }
197
+ /** A selection-only file keeps its own keys, `extends` among them; the template adds the keys it lacks. */
198
+ function selectionKeys(existing, template, merge) {
199
+ const held = parseDocument(existing, () => {}).metadata;
200
+ const keys = {};
201
+ for (const [key, value] of Object.entries(template.metadata)) if (key !== "merge" && !(key in held)) keys[key] = value;
202
+ keys["merge"] = merge;
203
+ return keys;
204
+ }
205
+ function withBody(head, body) {
206
+ const text = body.replace(/^(?:[ \t]*\r?\n)+/, "");
207
+ return text ? `${head.replace(/\s*$/, "")}\n\n${text}` : head;
208
+ }
209
+ function createContribution(layers, args, options = {}) {
210
+ const name = bareName(args.name);
211
+ const target = writeTarget(name, args);
212
+ if (args.merge !== void 0 && !isMergeOperation(args.merge)) throw new ReferenceWriteError("options-invalid", `--merge is replace, patch, combine, or remove; got "${args.merge}".`, true);
213
+ const merge = args.merge;
214
+ if (args.template !== void 0 && args.implements !== void 0) throw new ReferenceWriteError("options-conflict", "pass --template or --implements, not both.");
215
+ if (merge === "remove" && args.template !== void 0) throw new ReferenceWriteError("options-conflict", "--merge remove writes no body, so it takes no --template.");
216
+ if (merge !== void 0 && target.scope === "default") throw new ReferenceWriteError("options-conflict", "a default is written with no merge; drop --merge.");
217
+ const existing = tryRead(target.path);
218
+ if (existing !== void 0 && hasBody(parseDocument(existing, () => {}).body)) throw new ReferenceWriteError("file-exists", `${(options.display ?? ((path) => path))(target.path)} already holds "${name.name}"; change it with the reference skill's Update mode.`);
219
+ const template = args.implements !== void 0 ? implementationTemplate(interfaceFor(args.implements, name.name, layers, options)) : merge === "remove" ? {
220
+ content: "",
221
+ metadata: {},
222
+ body: ""
223
+ } : readTemplate(args.template === void 0 ? void 0 : resolve(args.template));
224
+ const title = titleOf(template);
225
+ if (title) throw new ReferenceWriteError("template-title", `the template's top level is the single heading "${title}" holding the others; patch matches by heading path, so make its sections top-level.`);
226
+ const before = resolveReference(name, layers, options);
227
+ const hider = hiddenBy(before, target.dir, target.path);
228
+ if (hider) throw new ReferenceWriteError("hidden", `${hider} applies above it, so nothing would read the new file; change that contribution instead.`);
229
+ const operation = target.scope === "default" ? void 0 : args.implements !== void 0 ? "replace" : merge ?? (isMergeOperation(template.metadata["merge"]) ? template.metadata["merge"] : void 0) ?? (target.scope === "plugin" ? void 0 : "patch");
230
+ const content = existing === void 0 ? editFrontmatter(template.content, { merge: operation }) : withBody(editFrontmatter(existing, selectionKeys(existing, template, operation)), template.body);
231
+ const hints = hasDescription(parseDocument(content, () => {})) ? [] : ["it has no `description` in its frontmatter, so `list` and `search` show it blank"];
232
+ const result = {
233
+ name: name.name,
234
+ scope: target.scope,
235
+ path: target.path,
236
+ content,
237
+ dryRun: Boolean(args.dryRun),
238
+ hints
239
+ };
240
+ if (result.dryRun) return result;
241
+ mkdirSync(target.dir, { recursive: true });
242
+ writeFileSync(target.path, content, existing === void 0 ? { flag: "wx" } : {});
243
+ if (before.trace.layers.some((layer) => layer.path === target.dir || layer.path === target.path)) result.after = resolveReference(name, layers, options);
244
+ return result;
245
+ }
246
+ function selectContribution(layers, args, options = {}) {
247
+ const name = bareName(args.name);
248
+ const target = writeTarget(name, {
249
+ root: args.root,
250
+ home: args.home,
251
+ scope: args.scope
252
+ });
253
+ const scope = target.scope;
254
+ const existing = tryRead(target.path);
255
+ if (args.clear) {
256
+ if (args.options.length) throw new ReferenceWriteError("options-conflict", "--clear takes no option.", true);
257
+ if (!(existing === void 0 ? void 0 : readExtends(parseDocument(existing, () => {}).metadata["extends"], () => {}))) throw new ReferenceWriteError("no-selection", `the ${scope} tier holds no selection of "${name.name}". ${describeAnswer(resolveReference(name, layers, options), options.display)}`);
258
+ const content = editFrontmatter(existing, { extends: void 0 });
259
+ const deleted = !content.trim();
260
+ if (deleted) rmSync(target.path);
261
+ else writeFileSync(target.path, content);
262
+ return {
263
+ name: name.name,
264
+ scope,
265
+ path: target.path,
266
+ extends: [],
267
+ deleted,
268
+ after: resolveReference(name, layers, options)
269
+ };
270
+ }
271
+ if (!args.options.length) throw new ReferenceWriteError("options-missing", "name at least one option: a plugin or default.", true);
272
+ const before = resolveReference(name, layers, options);
273
+ const holders = new Set(before.trace.layers.flatMap(({ plugin, file }) => plugin !== void 0 && file ? [plugin] : []));
274
+ const picked = args.options.map((option) => option.endsWith(`/${name.name}`) ? option.slice(0, -name.name.length - 1) : option);
275
+ const wrong = picked.filter((option) => option !== "default" && !holders.has(option));
276
+ if (wrong.length) throw new ReferenceWriteError("option-invalid", `${wrong.join(", ")} ${wrong.length === 1 ? "is" : "are"} neither default nor a plugin holding "${name.name}"${holders.size ? `; the plugins holding it are ${[...holders].join(", ")}` : ""}.`);
277
+ const content = editFrontmatter(existing ?? "", { extends: picked });
278
+ mkdirSync(target.dir, { recursive: true });
279
+ writeFileSync(target.path, content);
280
+ return {
281
+ name: name.name,
282
+ scope,
283
+ path: target.path,
284
+ extends: picked,
285
+ deleted: false,
286
+ after: resolveReference(name, layers, options)
287
+ };
288
+ }
289
+ function deleteContribution(layers, args, options = {}) {
290
+ const name = bareName(args.name);
291
+ const target = writeTarget(name, args);
292
+ const shown = options.display ?? ((path) => path);
293
+ const before = resolveReference(name, layers, options);
294
+ const path = (target.scope === "project" || target.scope === "user" ? [target.path, join(target.dir, name.name, "README.md")] : [target.path]).find((candidate) => candidate !== options.without && existsSync(candidate));
295
+ if (!path) throw new ReferenceWriteError("no-file", `${shown(target.path)} does not exist, so there is no ${target.scope} contribution of "${name.name}" to delete. ${describeAnswer(before, options.display)} An installed plugin's, a managed, or an installed skill's contribution is never deleted.`);
296
+ const steps = readExtends(parseDocument(tryRead(path) ?? "", () => {}).metadata["extends"], () => {});
297
+ const dryRun = Boolean(args.dryRun);
298
+ if (!dryRun) {
299
+ rmSync(path);
300
+ if (dirname(path) !== target.dir) try {
301
+ rmdirSync(dirname(path));
302
+ } catch {}
303
+ }
304
+ const result = {
305
+ name: name.name,
306
+ scope: target.scope,
307
+ path,
308
+ dryRun,
309
+ after: resolveReference(name, layers, {
310
+ ...options,
311
+ without: dryRun ? path : void 0
312
+ })
313
+ };
314
+ if (steps) result.selection = steps.map(({ target: step }) => step);
315
+ return result;
316
+ }
317
+ //#endregion
318
+ //#region src/references/reference.command.ts
319
+ const rootOption = {
320
+ description: "Directory the project tier is read from, and each folder above it up to the repository root; a project write goes to its `.agents/references/`. Defaults to the current directory.",
321
+ type: t.string().optional()
322
+ };
323
+ const traceOption = {
324
+ description: "Add the trace: every layer checked, with its outcome, and how the result was built.",
325
+ type: t.boolean().optional()
326
+ };
327
+ const defaultOption = {
328
+ description: "The calling skill's folder: its `references/<name>.default.md` joins as the `default` tier.",
329
+ type: t.string().optional()
330
+ };
331
+ function layerSource(plugin) {
332
+ return (args, home) => referenceLayers({
333
+ root: args.root ?? process.cwd(),
334
+ home,
335
+ platform: process.platform,
336
+ env: process.env,
337
+ plugin,
338
+ defaultSkill: args.default
339
+ });
340
+ }
341
+ /** A field with nothing to say is left out: `undefined`, an empty string, or an empty list. */
342
+ function compact(value) {
343
+ return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== void 0 && field !== "" && !(Array.isArray(field) && !field.length)));
344
+ }
345
+ /** stderr, not stdout: an agent parses stdout. Rethrows what the cli already reports. */
346
+ function fail(error, fallback) {
347
+ if (isCliError(error)) throw error;
348
+ if (error instanceof ReferenceWriteError) {
349
+ process.stderr.write(`error: ${error.code}: ${error.message}\n`);
350
+ throw CliError.reported(error.usage ? exitCodes.usage : exitCodes.error);
351
+ }
352
+ process.stderr.write(`error: ${error instanceof Error ? error.message : fallback}\n`);
353
+ throw CliError.reported(exitCodes.error);
354
+ }
355
+ function usage(message) {
356
+ process.stderr.write(`error: usage: ${message}\n`);
357
+ throw CliError.reported(exitCodes.usage);
358
+ }
359
+ /** An answer with a failing exit code: the cli prints only what a returned answer holds, so print it first. */
360
+ function answerThenFail(answer, format) {
361
+ const text = printAnswer(answer, format);
362
+ if (text !== void 0) process.stdout.write(text);
363
+ throw CliError.reported(exitCodes.error);
364
+ }
365
+ function writeWarnings(warnings) {
366
+ for (const { code, message } of warnings) process.stderr.write(`warning: ${code}: ${message}\n`);
367
+ }
368
+ function displayTrace(trace, home) {
369
+ const show = (path) => collapseHome(home, path);
370
+ return {
371
+ ...trace,
372
+ contributions: trace.contributions.map((row) => ({
373
+ ...row,
374
+ path: show(row.path)
375
+ })),
376
+ layers: trace.layers.map((layer) => ({
377
+ ...layer,
378
+ path: show(layer.path)
379
+ }))
380
+ };
381
+ }
382
+ /** The text trace: a summary line, then each section with its contributions, then each layer. */
383
+ function traceText(resolved, home) {
384
+ const trace = displayTrace(resolved.trace, home);
385
+ const lines = [`${resolved.name}: ${resolved.status}; stack: ${trace.stack.join(" → ") || "none"}; built by ${trace.builtBy}; harnesses: ${trace.harnesses.join(", ") || "none detected"}`];
386
+ if (resolved.message) lines.push(` ${resolved.message}`);
387
+ if (trace.sections.length) {
388
+ lines.push("sections:");
389
+ for (const section of trace.sections) {
390
+ const how = section.removedBy ? `removed by ${section.removedBy}` : section.contributions.map(({ contribution, operation }) => `${contribution} (${operation})`).join(", ") || "no text";
391
+ lines.push(` ${section.path}: ${how}`);
392
+ }
393
+ }
394
+ lines.push("layers:");
395
+ for (const layer of trace.layers) {
396
+ const who = layer.plugin ? ` ${layer.plugin}` : layer.skill ? ` ${layer.skill}` : "";
397
+ const how = layer.operation ? `; ${layer.operation} from ${layer.operationFrom}` : "";
398
+ const extendsNote = layer.extendsOutcome ? `; extends ${layer.extendsOutcome}` : "";
399
+ const selection = layer.selection ? "; selection" : "";
400
+ const frontmatter = layer.frontmatter && Object.keys(layer.frontmatter).length ? ` ${JSON.stringify(layer.frontmatter)}` : "";
401
+ lines.push(` ${layer.tier}${who} ${layer.path}: ${layer.outcome}${how}${extendsNote}${selection}${frontmatter}`);
402
+ }
403
+ return `${lines.join("\n")}\n`;
404
+ }
405
+ /** A name not found, for a person: the name, why, and, when ambiguous, the options `reference select` takes. */
406
+ function errorText(entry) {
407
+ const options = entry.options?.length ? `; options: ${entry.options.join(", ")}` : "";
408
+ return `${entry.name}: ${entry.message}${options}`;
409
+ }
410
+ function showEntry(resolved, home, { metadata, trace }) {
411
+ if (resolved.status === "found" && !resolved.warnings.length && !metadata && !trace) return resolved.content;
412
+ return compact({
413
+ name: resolved.name,
414
+ status: resolved.status,
415
+ content: resolved.content,
416
+ code: resolved.code,
417
+ options: resolved.options,
418
+ metadata: metadata ? resolved.metadata : void 0,
419
+ warnings: resolved.warnings,
420
+ trace: trace ? displayTrace(resolved.trace, home) : void 0
421
+ });
422
+ }
423
+ /** One entry per name asked, as the array itself rather than under a `result` key. */
424
+ const bareArray = {
425
+ toon: (entries) => encode(entries, "toon"),
426
+ json: (entries) => encode(entries, "json")
427
+ };
428
+ function showText(resolved) {
429
+ if (resolved.length === 1) {
430
+ const [only] = resolved;
431
+ return only.content === void 0 ? void 0 : document(only.content);
432
+ }
433
+ const blocks = resolved.map((entry) => entry.content === void 0 ? `<reference name="${entry.name}" status="${entry.status}" />` : `<reference name="${entry.name}">\n${entry.content}</reference>`);
434
+ return document(blocks.join("\n\n"));
435
+ }
436
+ function showCommand(layersFor) {
437
+ return command({
438
+ name: "show",
439
+ description: "Print one or more references, each composed from its contributions as their extends and merge declare.",
440
+ arguments: [{
441
+ name: "names",
442
+ description: "Reference names, without the `.md` extension; `<plugin>/<name>` builds that plugin’s own stack.",
443
+ type: t.array(t.string())
444
+ }],
445
+ options: {
446
+ root: rootOption,
447
+ default: defaultOption,
448
+ metadata: {
449
+ description: "Make every entry a record with its status and composed frontmatter.",
450
+ type: t.boolean().optional()
451
+ },
452
+ trace: traceOption
453
+ },
454
+ async run(args) {
455
+ const format = this.format;
456
+ if (!args.names.length) usage("name at least one reference.");
457
+ try {
458
+ const home = homedir();
459
+ const layers = await layersFor(args, home);
460
+ const display = (path) => collapseHome(home, path);
461
+ const lookup = interfaceLookup(layers, { display });
462
+ const resolved = args.names.map((name) => {
463
+ const entry = resolveReference(parseReferenceName(name), layers, { display });
464
+ for (const failure of checkResolved(entry, lookup).failures) {
465
+ if (failure.check === "compose") continue;
466
+ entry.warnings.push({
467
+ code: "interface-check",
468
+ message: `it fails its interface check (${failure.check}): ${failure.message}`
469
+ });
470
+ }
471
+ return entry;
472
+ });
473
+ const ok = resolved.every((entry) => entry.status === "found");
474
+ if (format === "text") {
475
+ for (const entry of resolved) {
476
+ writeWarnings(entry.warnings);
477
+ if (entry.status !== "found") process.stderr.write(`error: ${entry.code}: ${errorText(entry)}\n`);
478
+ if (args.trace) process.stderr.write(traceText(entry, home));
479
+ }
480
+ const answer = showText(resolved);
481
+ if (answer === void 0) throw CliError.reported(exitCodes.error);
482
+ return ok ? answer : answerThenFail(answer, format);
483
+ }
484
+ const flags = {
485
+ metadata: Boolean(args.metadata),
486
+ trace: Boolean(args.trace)
487
+ };
488
+ const answer = result(resolved.map((entry) => showEntry(entry, home, flags)), { render: bareArray });
489
+ return ok ? answer : answerThenFail(answer, format);
490
+ } catch (error) {
491
+ return fail(error, "Reference lookup failed.");
492
+ }
493
+ }
494
+ });
495
+ }
496
+ function listCommand(layersFor) {
497
+ return command({
498
+ name: "list",
499
+ description: "List every reference name, one row each, with its description, and its status when not found. A pattern keeps only the names it matches exactly.",
500
+ arguments: [{
501
+ name: "pattern",
502
+ description: "Exact name pattern, where `*` stands for any run of characters, such as `project-tracker.capabilities.*`.",
503
+ type: t.string().optional()
504
+ }],
505
+ options: {
506
+ root: rootOption,
507
+ trace: {
508
+ ...traceOption,
509
+ description: "Add one row per name per layer holding it, with its outcome."
510
+ }
511
+ },
512
+ async run(args) {
513
+ try {
514
+ const home = homedir();
515
+ const display = (path) => collapseHome(home, path);
516
+ const listing = listReferences(await layersFor(args, home), {
517
+ display,
518
+ pattern: args.pattern
519
+ });
520
+ const report = compact({
521
+ references: listing.references.length ? listing.references.map((row) => compact({
522
+ name: row.name,
523
+ description: row.description,
524
+ status: row.status === "found" ? void 0 : row.status,
525
+ options: row.options
526
+ })) : args.pattern ? `0 references match "${args.pattern}"` : "0 references — no layer holds one",
527
+ defaults: listing.defaults
528
+ });
529
+ if (args.trace) report.layers = listing.layers.map((row) => compact({
530
+ ...row,
531
+ path: display(row.path)
532
+ }));
533
+ if (listing.warnings.length) report.warnings = listing.warnings;
534
+ return result(report);
535
+ } catch (error) {
536
+ return fail(error, "Reference listing failed.");
537
+ }
538
+ }
539
+ });
540
+ }
541
+ function searchCommand(layersFor) {
542
+ return command({
543
+ name: "search",
544
+ description: "Find references by name, description, tags, headings, or body, best match first.",
545
+ arguments: [{
546
+ name: "query",
547
+ description: "What the reference is about.",
548
+ type: t.string()
549
+ }],
550
+ options: {
551
+ root: rootOption,
552
+ trace: {
553
+ ...traceOption,
554
+ description: "Add the tier and plugin or skill of each match."
555
+ }
556
+ },
557
+ async run(args) {
558
+ const query = args.query.trim();
559
+ if (!query) usage("search needs a query.");
560
+ try {
561
+ const home = homedir();
562
+ const { matches, defaults } = searchReferences(query, await layersFor(args, home), { display: (path) => collapseHome(home, path) });
563
+ const report = compact({
564
+ references: matches.length ? matches.map(({ name, description, match, tier, plugin, skill, loadedBy }) => compact(args.trace ? {
565
+ name,
566
+ description,
567
+ match,
568
+ loadedBy,
569
+ tier,
570
+ plugin,
571
+ skill
572
+ } : {
573
+ name,
574
+ description,
575
+ match,
576
+ loadedBy
577
+ })) : `0 references match "${query}"`,
578
+ defaults
579
+ });
580
+ return result(report);
581
+ } catch (error) {
582
+ return fail(error, "Reference search failed.");
583
+ }
584
+ }
585
+ });
586
+ }
587
+ function whereCommand(layersFor) {
588
+ return command({
589
+ name: "where",
590
+ description: "Give the project and user files a customization or a selection of a reference is written to, who each reaches, and the heading paths of the document below each.",
591
+ arguments: [{
592
+ name: "name",
593
+ description: "Reference name, without the `.md` extension.",
594
+ type: t.string()
595
+ }],
596
+ options: {
597
+ root: rootOption,
598
+ default: defaultOption,
599
+ trace: {
600
+ ...traceOption,
601
+ description: "Add every layer checked, with its outcome."
602
+ }
603
+ },
604
+ async run(args) {
605
+ const format = this.format;
606
+ try {
607
+ const home = homedir();
608
+ const display = (path) => collapseHome(home, path);
609
+ const report = whereReference(args.name, await layersFor(args, home), {
610
+ root: resolve(args.root ?? process.cwd()),
611
+ display
612
+ });
613
+ const output = compact({
614
+ name: report.name,
615
+ status: report.status,
616
+ places: report.places.map((place) => compact(place)),
617
+ options: report.options,
618
+ layers: args.trace ? report.layers.map((layer) => ({
619
+ ...layer,
620
+ path: display(layer.path)
621
+ })) : void 0,
622
+ warnings: report.warnings
623
+ });
624
+ if (format === "text") writeWarnings(report.warnings);
625
+ const answer = result(output);
626
+ if (report.status !== "invalid") return answer;
627
+ process.stderr.write(`error: reference-invalid: ${parseReferenceName(args.name).invalid}\n`);
628
+ return answerThenFail(answer, format);
629
+ } catch (error) {
630
+ return fail(error, "Reference placement lookup failed.");
631
+ }
632
+ }
633
+ });
634
+ }
635
+ function parseAccept(value) {
636
+ if (value === void 0) return void 0;
637
+ const versions = value.split(",").map((part) => part.trim());
638
+ if (!versions.length || versions.some((part) => !/^[1-9]\d*$/.test(part))) usage(`--accept is a comma-separated list of versions, such as 1,2; got "${value}".`);
639
+ return versions.map(Number);
640
+ }
641
+ function implementationsCommand(layersFor) {
642
+ return command({
643
+ name: "implementations",
644
+ description: "List the implementations of an interface a consumer can load, each with its pin, description, and unsupported sections.",
645
+ arguments: [{
646
+ name: "id",
647
+ description: "Interface id, the name of its `<id>.interface` document without the suffix.",
648
+ type: t.string()
649
+ }],
650
+ options: {
651
+ root: rootOption,
652
+ accept: {
653
+ description: "Versions the consumer knows, comma-separated, such as 1,2. Defaults to the interface's `versions`.",
654
+ type: t.string().optional()
655
+ },
656
+ trace: {
657
+ ...traceOption,
658
+ description: "Add each one's name, tier, and version, the interface's cardinality and accepted versions, and the ones left out with why."
659
+ }
660
+ },
661
+ async run(args) {
662
+ const accept = parseAccept(args.accept);
663
+ try {
664
+ const home = homedir();
665
+ const found = referenceImplementations(args.id, await layersFor(args, home), {
666
+ accept,
667
+ display: (path) => collapseHome(home, path)
668
+ });
669
+ const rows = found.implementations.map(({ pin, description, unsupported, name, tier, version }) => compact(args.trace ? {
670
+ pin,
671
+ description,
672
+ unsupported,
673
+ name,
674
+ tier,
675
+ version
676
+ } : {
677
+ pin,
678
+ description,
679
+ unsupported
680
+ }));
681
+ const report = { implementations: rows.length ? rows : `0 implementations of ${args.id}` };
682
+ if (args.trace) Object.assign(report, compact({
683
+ interface: found.interface,
684
+ cardinality: found.cardinality,
685
+ accept: found.accept,
686
+ excluded: found.excluded,
687
+ warnings: found.warnings
688
+ }));
689
+ return result(report);
690
+ } catch (error) {
691
+ return fail(error, "Implementation lookup failed.");
692
+ }
693
+ }
694
+ });
695
+ }
696
+ function checkCommand(layersFor) {
697
+ return command({
698
+ name: "check",
699
+ description: "Check interface documents and their implementations, each composed as `show` composes it. With no names, checks every one found. Exits 1 on any failure.",
700
+ arguments: [{
701
+ name: "names",
702
+ description: "Reference names, without the `.md` extension; `<plugin>/<name>` builds that plugin’s own stack.",
703
+ type: t.array(t.string()).optional()
704
+ }],
705
+ options: {
706
+ root: rootOption,
707
+ trace: {
708
+ ...traceOption,
709
+ description: "Add every entry checked, with its kind and unsupported sections."
710
+ }
711
+ },
712
+ async run(args) {
713
+ const format = this.format;
714
+ try {
715
+ const home = homedir();
716
+ const entries = checkReferences(await layersFor(args, home), {
717
+ names: args.names ?? [],
718
+ display: (path) => collapseHome(home, path)
719
+ });
720
+ const failing = entries.filter((entry) => entry.status !== "passed");
721
+ const strip = (failures) => failures.map(({ check, message }) => ({
722
+ check,
723
+ message
724
+ }));
725
+ const report = compact({
726
+ failed: failing.map(({ name, loadedBy, failures }) => compact({
727
+ name,
728
+ loadedBy,
729
+ failures: strip(failures)
730
+ })),
731
+ passed: entries.length - failing.length,
732
+ references: args.trace ? entries.map(({ name, loadedBy, kind, status, failures, unsupported }) => compact({
733
+ name,
734
+ loadedBy,
735
+ kind,
736
+ status,
737
+ failures: strip(failures),
738
+ unsupported
739
+ })) : void 0
740
+ });
741
+ const answer = result(report);
742
+ if (!failing.length) return answer;
743
+ if (format === "text") process.stderr.write(`error: interface-check: ${failing.length} of ${entries.length} references fail their check.\n`);
744
+ return answerThenFail(answer, format);
745
+ } catch (error) {
746
+ return fail(error, "Reference check failed.");
747
+ }
748
+ }
749
+ });
750
+ }
751
+ const scopeOption = {
752
+ description: "Tier to write: project (default), `.agents/references/` at the root, or user, `~/.agents/references/`.",
753
+ type: t.string().optional()
754
+ };
755
+ const pluginOption = {
756
+ description: "A plugin's source folder in this repository: acts on its `references/<name>.md`.",
757
+ type: t.string().optional()
758
+ };
759
+ const skillOption = {
760
+ description: "A skill's source folder in this repository: acts on its `references/<name>.default.md`.",
761
+ type: t.string().optional()
762
+ };
763
+ function createCommand(layersFor) {
764
+ return command({
765
+ name: "create",
766
+ description: "Start a contribution in the project or user tier, or in a plugin's or a skill's source folder, from a template or an interface. Never overwrites a file with a body.",
767
+ arguments: [{
768
+ name: "name",
769
+ description: "Reference name, without the `.md` extension.",
770
+ type: t.string()
771
+ }],
772
+ options: {
773
+ root: rootOption,
774
+ scope: scopeOption,
775
+ plugin: pluginOption,
776
+ default: skillOption,
777
+ merge: {
778
+ description: "How it applies over what is below: replace, patch (default), combine, or remove. remove writes no body.",
779
+ type: t.string().optional()
780
+ },
781
+ template: {
782
+ description: "File whose text is written as it is, frontmatter included. Defaults to a built-in template of top-level `##` sections.",
783
+ type: t.string().optional()
784
+ },
785
+ implements: {
786
+ description: "Interface id to implement: the interface's sections become `##` headings, each stubbed `**Unsupported.**`, with `merge: replace`. Not with --template.",
787
+ type: t.string().optional()
788
+ },
789
+ "dry-run": {
790
+ description: "Print the target path and the exact content, and write nothing.",
791
+ type: t.boolean().optional()
792
+ },
793
+ trace: {
794
+ ...traceOption,
795
+ description: "After the write, add the trace, in which the new file applies."
796
+ }
797
+ },
798
+ async run(args) {
799
+ const format = this.format;
800
+ try {
801
+ const home = homedir();
802
+ const root = resolve(args.root ?? process.cwd());
803
+ const display = (path) => collapseHome(home, path);
804
+ const created = createContribution(await layersFor({
805
+ root,
806
+ default: args.default
807
+ }, home), {
808
+ root,
809
+ home,
810
+ name: args.name,
811
+ scope: args.scope,
812
+ plugin: args.plugin,
813
+ default: args.default,
814
+ merge: args.merge,
815
+ template: args.template,
816
+ implements: args.implements,
817
+ dryRun: args["dry-run"]
818
+ }, { display });
819
+ const report = compact({
820
+ path: display(created.path),
821
+ hint: created.dryRun ? "Nothing was written. Run again without --dry-run to write it." : `Run \`show ${created.name}\` for the composed document.`,
822
+ content: created.dryRun ? created.content : void 0,
823
+ hints: created.hints,
824
+ trace: args.trace && created.after ? displayTrace(created.after.trace, home) : void 0
825
+ });
826
+ if (format !== "text") return result(report);
827
+ for (const hint of created.hints) process.stderr.write(`hint: ${hint}\n`);
828
+ if (args.trace && created.after) process.stderr.write(traceText(created.after, home));
829
+ return document(created.dryRun ? `${report.path}\n\n${created.content}` : `${report.path}\n\n${report.hint}\n`);
830
+ } catch (error) {
831
+ return fail(error, "Reference creation failed.");
832
+ }
833
+ }
834
+ });
835
+ }
836
+ function selectCommand(layersFor) {
837
+ return command({
838
+ name: "select",
839
+ description: "Choose what a name builds on: write an `extends` listing the options, bottom first, in the project or user file. Its body and other keys are kept.",
840
+ arguments: [{
841
+ name: "name",
842
+ description: "Reference name, without the `.md` extension.",
843
+ type: t.string()
844
+ }, {
845
+ name: "options",
846
+ description: "Bottom first: each a plugin holding the name (or `<plugin>/<name>`), or `default`.",
847
+ type: t.array(t.string()).optional()
848
+ }],
849
+ options: {
850
+ root: rootOption,
851
+ scope: scopeOption,
852
+ clear: {
853
+ description: "Remove the selection; a file left with nothing in it is deleted.",
854
+ type: t.boolean().optional()
855
+ }
856
+ },
857
+ async run(args) {
858
+ const format = this.format;
859
+ try {
860
+ const home = homedir();
861
+ const root = resolve(args.root ?? process.cwd());
862
+ const display = (path) => collapseHome(home, path);
863
+ const selected = selectContribution(await layersFor({ root }, home), {
864
+ root,
865
+ home,
866
+ name: args.name,
867
+ options: args.options ?? [],
868
+ scope: args.scope,
869
+ clear: args.clear
870
+ }, { display });
871
+ const report = compact({
872
+ path: display(selected.path),
873
+ extends: selected.extends,
874
+ deleted: selected.deleted || void 0,
875
+ status: selected.after.status,
876
+ stack: selected.after.trace.stack,
877
+ answer: describeAnswer(selected.after, display)
878
+ });
879
+ if (format !== "text") return result(report);
880
+ writeWarnings(selected.after.warnings);
881
+ const what = args.clear ? selected.deleted ? "selection cleared; the file was left empty and deleted" : "selection cleared" : `extends: [${selected.extends.join(", ")}]`;
882
+ return document(`${report.path}\n\n${what}. ${report.answer}\n`);
883
+ } catch (error) {
884
+ return fail(error, "Reference selection failed.");
885
+ }
886
+ }
887
+ });
888
+ }
889
+ function deleteReport(deleted, home) {
890
+ const selection = deleted.selection ? `the selection extends: [${deleted.selection.join(", ")}] went with it; run \`reference select ${deleted.name} <option>...\` to restore it` : void 0;
891
+ return compact({
892
+ path: collapseHome(home, deleted.path),
893
+ dryRun: deleted.dryRun || void 0,
894
+ status: deleted.after.status,
895
+ stack: deleted.after.trace.stack,
896
+ answer: describeAnswer(deleted.after, (path) => collapseHome(home, path)),
897
+ selection,
898
+ options: deleted.after.options
899
+ });
900
+ }
901
+ function deleteCommand(layersFor) {
902
+ return command({
903
+ name: "delete",
904
+ description: "Delete a project or user contribution, or one in a plugin's or a skill's source folder, and report what answers the name afterwards. Never deletes an installed or managed one.",
905
+ arguments: [{
906
+ name: "name",
907
+ description: "Reference name, without the `.md` extension.",
908
+ type: t.string()
909
+ }],
910
+ options: {
911
+ root: rootOption,
912
+ scope: {
913
+ ...scopeOption,
914
+ description: "Tier to delete from: project (default) or user."
915
+ },
916
+ plugin: pluginOption,
917
+ default: skillOption,
918
+ "dry-run": {
919
+ description: "Print the file that would be deleted and what would answer the name then, and delete nothing.",
920
+ type: t.boolean().optional()
921
+ }
922
+ },
923
+ async run(args) {
924
+ const format = this.format;
925
+ try {
926
+ const home = homedir();
927
+ const root = resolve(args.root ?? process.cwd());
928
+ const deleted = deleteContribution(await layersFor({
929
+ root,
930
+ default: args.default
931
+ }, home), {
932
+ root,
933
+ home,
934
+ name: args.name,
935
+ scope: args.scope,
936
+ plugin: args.plugin,
937
+ default: args.default,
938
+ dryRun: args["dry-run"]
939
+ }, { display: (path) => collapseHome(home, path) });
940
+ const report = deleteReport(deleted, home);
941
+ if (format !== "text") return result(report);
942
+ const selection = report.selection ? ` Also, ${report.selection}.` : "";
943
+ return document(`${report.path}\n\n${deleted.dryRun ? "would delete" : "deleted"}. ${report.answer}${selection}\n`);
944
+ } catch (error) {
945
+ return fail(error, "Reference deletion failed.");
946
+ }
947
+ }
948
+ });
949
+ }
950
+ /** A factory, so each host CLI names itself as the plugin whose own references come first. */
951
+ function createReferenceCommands({ plugin } = {}) {
952
+ const layersFor = layerSource(plugin);
953
+ return {
954
+ show: showCommand(layersFor),
955
+ list: listCommand(layersFor),
956
+ search: searchCommand(layersFor),
957
+ where: whereCommand(layersFor),
958
+ create: createCommand(layersFor),
959
+ select: selectCommand(layersFor),
960
+ delete: deleteCommand(layersFor),
961
+ check: checkCommand(layersFor),
962
+ implementations: implementationsCommand(layersFor)
963
+ };
964
+ }
965
+ function createReferenceCommand(options = {}) {
966
+ const { show, list, search, where, create, select, delete: remove, check, implementations } = createReferenceCommands(options);
967
+ return command({
968
+ name: "reference",
969
+ description: "Read reference documents by name, composed from the managed, project, user, plugin, and default tiers, and write, select, or delete a contribution.",
970
+ commands: [
971
+ show,
972
+ list,
973
+ search,
974
+ where,
975
+ create,
976
+ select,
977
+ remove,
978
+ implementations,
979
+ check
980
+ ]
981
+ });
982
+ }
983
+ //#endregion
984
+ export { createReferenceCommand, createReferenceCommands };