cyber-agentkit 1.2.0 → 2.0.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +181 -0
- package/dist/cli.js +8687 -4886
- package/dist/commands.d.ts +159 -87
- package/dist/commands.js +1099 -838
- package/dist/index.d.ts +335 -70
- package/dist/index.js +26 -22
- package/dist/reference-where-BsBpwLyg.d.ts +735 -0
- package/dist/{reference-where-C84J6MsR.js → reference-where-CVmGFOwR.js} +5468 -1864
- package/package.json +3 -2
- package/plugin.json +1 -1
- package/readme.md +59 -26
- package/skills/asset/scripts/asset.mjs +82 -69
- package/skills/prose-update/SKILL.md +1 -1
- package/skills/prose-update/scripts/prose-update.mjs +82 -69
- package/skills/reference/README.md +7 -7
- package/skills/reference/SKILL.md +64 -58
- package/skills/reference/references/create.md +29 -24
- package/skills/reference/references/customize.md +82 -0
- package/skills/reference/references/load.md +58 -37
- package/skills/reference/scripts/reference.mjs +82 -69
- package/dist/resolve-reference-D62VJZc0.d.ts +0 -246
package/dist/commands.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as userAssetsDir, C as referenceLayers, E as ReferenceFailure, F as resolveAsset, I as assetDataExtensions, K as skillName, L as parseAssetName, P as assetNames, R as assetLayers, S as findRoot, b as requireName, c as deleteContribution, d as insideRepository, f as checkReferences, g as searchReferences, h as listReferences, l as describeAnswer, m as interfaceLookup, n as referenceImplementations, p as checkResolved, r as customizeContribution, s as createContribution, t as whereReference, u as validateWrite, v as resolveReference, y as parseReferenceName, z as projectAssetsDir } from "./reference-where-CVmGFOwR.js";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, rmdirSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { basename, dirname, extname, join, resolve, sep } from "node:path";
|
|
4
|
+
import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { command, exitCodes, t } from "clibuilder";
|
|
6
|
-
import { CliError, document, encode, isCliError, printAnswer, result } from "clibuilder/axi";
|
|
6
|
+
import { CliError, document, empty, encode, isCliError, printAnswer, result } from "clibuilder/axi";
|
|
7
7
|
//#region src/command-output/collapse-home.ts
|
|
8
8
|
/**
|
|
9
9
|
* AXI §10: collapses the home directory to `~` so a path in a report is portable to another
|
|
@@ -13,351 +13,124 @@ function collapseHome(home, path) {
|
|
|
13
13
|
return home && path.startsWith(home + sep) ? `~${path.slice(home.length)}` : path;
|
|
14
14
|
}
|
|
15
15
|
//#endregion
|
|
16
|
-
//#region src/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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;
|
|
16
|
+
//#region src/assets/asset-catalog.ts
|
|
17
|
+
function patternMatcher(pattern) {
|
|
18
|
+
const source = pattern.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&"));
|
|
19
|
+
return new RegExp(`^${source.join(".*")}$`, "s");
|
|
66
20
|
}
|
|
67
21
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
22
|
+
* One row per asset name any layer holds, and one row per name per layer holding it (the skill's own
|
|
23
|
+
* `default` tier is not listed per layer). A `pattern` keeps only the names it matches exactly.
|
|
70
24
|
*/
|
|
71
|
-
function
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
25
|
+
function listAssetNames(source, { pattern, ...resolveOptions } = {}) {
|
|
26
|
+
const matcher = pattern === void 0 ? void 0 : patternMatcher(pattern);
|
|
27
|
+
const listing = {
|
|
28
|
+
references: [],
|
|
29
|
+
layers: [],
|
|
30
|
+
warnings: []
|
|
31
|
+
};
|
|
32
|
+
const seen = /* @__PURE__ */ new Set();
|
|
33
|
+
for (const name of assetNames(source)) {
|
|
34
|
+
if (matcher && !matcher.test(name)) continue;
|
|
35
|
+
const resolved = resolveAsset(name, source, resolveOptions);
|
|
36
|
+
for (const warning of resolved.warnings) {
|
|
37
|
+
const key = `${warning.code} ${warning.message}`;
|
|
38
|
+
if (!seen.has(key)) listing.warnings.push(warning);
|
|
39
|
+
seen.add(key);
|
|
40
|
+
}
|
|
41
|
+
const row = {
|
|
42
|
+
name,
|
|
43
|
+
status: resolved.status
|
|
44
|
+
};
|
|
45
|
+
if (resolved.format && resolved.format !== "markdown") row.format = resolved.format;
|
|
46
|
+
if (resolved.options) row.options = resolved.options.filter((option) => option !== "default");
|
|
47
|
+
listing.references.push(row);
|
|
48
|
+
for (const entry of resolved.trace.layers) {
|
|
49
|
+
if (entry.file === void 0 || entry.tier === "default") continue;
|
|
50
|
+
const layerRow = {
|
|
51
|
+
name,
|
|
52
|
+
tier: entry.tier,
|
|
53
|
+
path: entry.path,
|
|
54
|
+
outcome: entry.outcome
|
|
55
|
+
};
|
|
56
|
+
if (entry.plugin !== void 0) layerRow.plugin = entry.plugin;
|
|
57
|
+
if (entry.skill !== void 0) layerRow.skill = entry.skill;
|
|
58
|
+
listing.layers.push(layerRow);
|
|
59
|
+
}
|
|
79
60
|
}
|
|
80
|
-
|
|
81
|
-
const gap = !match && body.trim() ? "\n" : "";
|
|
82
|
-
return `---\n${doc.toString({
|
|
83
|
-
lineWidth: 0,
|
|
84
|
-
flowCollectionPadding: false
|
|
85
|
-
})}---\n${gap}${body}`;
|
|
61
|
+
return listing;
|
|
86
62
|
}
|
|
87
63
|
//#endregion
|
|
88
|
-
//#region src/
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
usage;
|
|
93
|
-
constructor(code, message, usage = false) {
|
|
94
|
-
super(message);
|
|
95
|
-
this.code = code;
|
|
96
|
-
this.usage = usage;
|
|
97
|
-
}
|
|
64
|
+
//#region src/assets/asset-where.ts
|
|
65
|
+
const reaches = {
|
|
66
|
+
project: "everyone working in this repository",
|
|
67
|
+
user: "only you, in every repository"
|
|
98
68
|
};
|
|
99
|
-
/**
|
|
100
|
-
function
|
|
101
|
-
const name =
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
69
|
+
/** The project and user folders an override of the asset `name` is written to. */
|
|
70
|
+
function whereAsset(input, source, { root, display = (path) => path }) {
|
|
71
|
+
const name = typeof input === "string" ? parseAssetName(input) : input;
|
|
72
|
+
const resolved = resolveAsset(name, source, { display });
|
|
73
|
+
const report = {
|
|
74
|
+
name: name.raw,
|
|
75
|
+
status: resolved.status,
|
|
76
|
+
places: [],
|
|
77
|
+
layers: resolved.trace.layers,
|
|
78
|
+
warnings: resolved.warnings
|
|
239
79
|
};
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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,
|
|
80
|
+
if (resolved.options) report.options = resolved.options;
|
|
81
|
+
if (name.invalid) return report;
|
|
82
|
+
const userDir = (Array.isArray(source) ? source : source.layers).find(({ tier }) => tier === "user")?.dir;
|
|
83
|
+
const targets = [{
|
|
84
|
+
scope: "project",
|
|
85
|
+
dir: projectAssetsDir(root)
|
|
86
|
+
}, ...userDir ? [{
|
|
87
|
+
scope: "user",
|
|
88
|
+
dir: userDir
|
|
89
|
+
}] : []];
|
|
90
|
+
for (const { scope, dir } of targets) {
|
|
91
|
+
const held = [join(dir, `${name.name}.md`), ...assetDataExtensions.map(({ extension }) => join(dir, `${name.name}${extension}`))];
|
|
92
|
+
const entry = resolved.trace.layers.find((layer) => held.includes(layer.path));
|
|
93
|
+
report.places.push({
|
|
264
94
|
scope,
|
|
265
|
-
path:
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
/** The data files that can answer a name in a folder. */
|
|
290
|
-
function dataFiles(dir, name) {
|
|
291
|
-
return dataExtensions.map(({ extension }) => join(dir, `${name}${extension}`));
|
|
292
|
-
}
|
|
293
|
-
function deleteContribution(layers, args, options = {}) {
|
|
294
|
-
const name = bareName(args.name);
|
|
295
|
-
const target = writeTarget(name, args);
|
|
296
|
-
const shown = options.display ?? ((path) => path);
|
|
297
|
-
const before = resolveReference(name, layers, options);
|
|
298
|
-
const path = (target.scope === "project" || target.scope === "user" ? [
|
|
299
|
-
target.path,
|
|
300
|
-
join(target.dir, name.name, "README.md"),
|
|
301
|
-
...dataFiles(target.dir, name.name)
|
|
302
|
-
] : [target.path]).find((candidate) => candidate !== options.without && existsSync(candidate));
|
|
303
|
-
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.`);
|
|
304
|
-
const steps = readExtends(parseDocument(tryRead(path) ?? "", () => {}).metadata["extends"], () => {});
|
|
305
|
-
const dryRun = Boolean(args.dryRun);
|
|
306
|
-
if (!dryRun) {
|
|
307
|
-
rmSync(path);
|
|
308
|
-
if (dirname(path) !== target.dir) try {
|
|
309
|
-
rmdirSync(dirname(path));
|
|
310
|
-
} catch {}
|
|
95
|
+
path: `${scope === "project" ? relative(root, dir) : display(dir)}/${name.name}.<ext>`,
|
|
96
|
+
reaches: reaches[scope],
|
|
97
|
+
status: entry?.file ? entry.outcome : "empty",
|
|
98
|
+
sections: []
|
|
99
|
+
});
|
|
311
100
|
}
|
|
312
|
-
|
|
313
|
-
name: name.name,
|
|
314
|
-
scope: target.scope,
|
|
315
|
-
path,
|
|
316
|
-
dryRun,
|
|
317
|
-
after: resolveReference(name, layers, {
|
|
318
|
-
...options,
|
|
319
|
-
without: dryRun ? path : void 0
|
|
320
|
-
})
|
|
321
|
-
};
|
|
322
|
-
if (steps) result.selection = steps.map(({ target: step }) => step);
|
|
323
|
-
return result;
|
|
101
|
+
return report;
|
|
324
102
|
}
|
|
325
103
|
//#endregion
|
|
326
|
-
//#region src/
|
|
327
|
-
const rootOption$
|
|
104
|
+
//#region src/assets/asset-resolve.command.ts
|
|
105
|
+
const rootOption$2 = {
|
|
328
106
|
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.",
|
|
329
107
|
type: t.string().optional()
|
|
330
108
|
};
|
|
331
|
-
const traceOption = {
|
|
109
|
+
const traceOption$1 = {
|
|
332
110
|
description: "Add the trace: every layer checked, with its outcome, and how the result was built.",
|
|
333
111
|
type: t.boolean().optional()
|
|
334
112
|
};
|
|
335
|
-
const defaultOption = {
|
|
113
|
+
const defaultOption$1 = {
|
|
336
114
|
description: "The calling skill's folder: its `references/<name>.default.md` joins as the `default` tier.",
|
|
337
115
|
type: t.string().optional()
|
|
338
116
|
};
|
|
339
|
-
function layerSource(plugin
|
|
340
|
-
return (args, home) =>
|
|
117
|
+
function layerSource$1(plugin) {
|
|
118
|
+
return (args, home) => assetLayers({
|
|
341
119
|
root: args.root ?? process.cwd(),
|
|
342
120
|
home,
|
|
343
121
|
platform: process.platform,
|
|
344
122
|
env: process.env,
|
|
345
123
|
plugin,
|
|
346
|
-
defaultSkill: args.default
|
|
347
|
-
folder
|
|
124
|
+
defaultSkill: args.default
|
|
348
125
|
});
|
|
349
126
|
}
|
|
350
127
|
/** A field with nothing to say is left out: `undefined`, an empty string, or an empty list. */
|
|
351
|
-
function compact(value) {
|
|
128
|
+
function compact$1(value) {
|
|
352
129
|
return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== void 0 && field !== "" && !(Array.isArray(field) && !field.length)));
|
|
353
130
|
}
|
|
354
131
|
/** stderr, not stdout: an agent parses stdout. Rethrows what the cli already reports. */
|
|
355
132
|
function fail$1(error, fallback) {
|
|
356
133
|
if (isCliError(error)) throw error;
|
|
357
|
-
if (error instanceof ReferenceWriteError) {
|
|
358
|
-
process.stderr.write(`error: ${error.code}: ${error.message}\n`);
|
|
359
|
-
throw CliError.reported(error.usage ? exitCodes.usage : exitCodes.error);
|
|
360
|
-
}
|
|
361
134
|
process.stderr.write(`error: ${error instanceof Error ? error.message : fallback}\n`);
|
|
362
135
|
throw CliError.reported(exitCodes.error);
|
|
363
136
|
}
|
|
@@ -371,10 +144,10 @@ function answerThenFail(answer, format) {
|
|
|
371
144
|
if (text !== void 0) process.stdout.write(text);
|
|
372
145
|
throw CliError.reported(exitCodes.error);
|
|
373
146
|
}
|
|
374
|
-
function writeWarnings(warnings) {
|
|
147
|
+
function writeWarnings$1(warnings) {
|
|
375
148
|
for (const { code, message } of warnings) process.stderr.write(`warning: ${code}: ${message}\n`);
|
|
376
149
|
}
|
|
377
|
-
function displayTrace(trace, home) {
|
|
150
|
+
function displayTrace$1(trace, home) {
|
|
378
151
|
const show = (path) => collapseHome(home, path);
|
|
379
152
|
return {
|
|
380
153
|
...trace,
|
|
@@ -388,37 +161,27 @@ function displayTrace(trace, home) {
|
|
|
388
161
|
}))
|
|
389
162
|
};
|
|
390
163
|
}
|
|
391
|
-
/** The text trace: a summary line, then each
|
|
392
|
-
function traceText(resolved, home) {
|
|
393
|
-
const trace = displayTrace(resolved.trace, home);
|
|
164
|
+
/** The text trace: a summary line, then each layer. */
|
|
165
|
+
function traceText$1(resolved, home) {
|
|
166
|
+
const trace = displayTrace$1(resolved.trace, home);
|
|
394
167
|
const lines = [`${resolved.name}: ${resolved.status}; stack: ${trace.stack.join(" → ") || "none"}; built by ${trace.builtBy}; harnesses: ${trace.harnesses.join(", ") || "none detected"}`];
|
|
395
168
|
if (resolved.message) lines.push(` ${resolved.message}`);
|
|
396
|
-
if (trace.sections.length) {
|
|
397
|
-
lines.push("sections:");
|
|
398
|
-
for (const section of trace.sections) {
|
|
399
|
-
const how = section.removedBy ? `removed by ${section.removedBy}` : section.contributions.map(({ contribution, operation }) => `${contribution} (${operation})`).join(", ") || "no text";
|
|
400
|
-
lines.push(` ${section.path}: ${how}`);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
169
|
lines.push("layers:");
|
|
404
170
|
for (const layer of trace.layers) {
|
|
405
171
|
const who = layer.plugin ? ` ${layer.plugin}` : layer.skill ? ` ${layer.skill}` : "";
|
|
406
172
|
const how = layer.operation ? `; ${layer.operation} from ${layer.operationFrom}` : "";
|
|
407
|
-
|
|
408
|
-
const selection = layer.selection ? "; selection" : "";
|
|
409
|
-
const frontmatter = layer.frontmatter && Object.keys(layer.frontmatter).length ? ` ${JSON.stringify(layer.frontmatter)}` : "";
|
|
410
|
-
lines.push(` ${layer.tier}${who} ${layer.path}: ${layer.outcome}${how}${extendsNote}${selection}${frontmatter}`);
|
|
173
|
+
lines.push(` ${layer.tier}${who} ${layer.path}: ${layer.outcome}${how}`);
|
|
411
174
|
}
|
|
412
175
|
return `${lines.join("\n")}\n`;
|
|
413
176
|
}
|
|
414
|
-
/** A name not found, for a person: the name, why, and, when ambiguous, the options `
|
|
177
|
+
/** A name not found, for a person: the name, why, and, when ambiguous, the options `asset select` takes. */
|
|
415
178
|
function errorText(entry) {
|
|
416
179
|
const options = entry.options?.length ? `; options: ${entry.options.join(", ")}` : "";
|
|
417
180
|
return `${entry.name}: ${entry.message}${options}`;
|
|
418
181
|
}
|
|
419
182
|
function showEntry(resolved, home, { metadata, trace }) {
|
|
420
183
|
if (resolved.status === "found" && !resolved.warnings.length && !metadata && !trace && resolved.format === "markdown") return resolved.content;
|
|
421
|
-
return compact({
|
|
184
|
+
return compact$1({
|
|
422
185
|
name: resolved.name,
|
|
423
186
|
status: resolved.status,
|
|
424
187
|
content: resolved.content,
|
|
@@ -427,11 +190,11 @@ function showEntry(resolved, home, { metadata, trace }) {
|
|
|
427
190
|
options: resolved.options,
|
|
428
191
|
metadata: metadata ? resolved.metadata : void 0,
|
|
429
192
|
warnings: resolved.warnings,
|
|
430
|
-
trace: trace ? displayTrace(resolved.trace, home) : void 0
|
|
193
|
+
trace: trace ? displayTrace$1(resolved.trace, home) : void 0
|
|
431
194
|
});
|
|
432
195
|
}
|
|
433
196
|
/** One entry per name asked, as the array itself rather than under a `result` key. */
|
|
434
|
-
const bareArray = {
|
|
197
|
+
const bareArray$1 = {
|
|
435
198
|
toon: (entries) => encode(entries, "toon"),
|
|
436
199
|
json: (entries) => encode(entries, "json")
|
|
437
200
|
};
|
|
@@ -443,9 +206,9 @@ function showText(resolved) {
|
|
|
443
206
|
const blocks = resolved.map((entry) => entry.content === void 0 ? `<reference name="${entry.name}" status="${entry.status}" />` : `<reference name="${entry.name}">\n${entry.content}</reference>`);
|
|
444
207
|
return document(blocks.join("\n\n"));
|
|
445
208
|
}
|
|
446
|
-
function
|
|
209
|
+
function loadCommand$1(layersFor) {
|
|
447
210
|
return command({
|
|
448
|
-
name,
|
|
211
|
+
name: "load",
|
|
449
212
|
description: "Print one or more references, each composed from its contributions as their extends and merge declare.",
|
|
450
213
|
arguments: [{
|
|
451
214
|
name: "names",
|
|
@@ -453,13 +216,13 @@ function showCommand$1(layersFor, name = "show") {
|
|
|
453
216
|
type: t.array(t.string())
|
|
454
217
|
}],
|
|
455
218
|
options: {
|
|
456
|
-
root: rootOption$
|
|
457
|
-
default: defaultOption,
|
|
219
|
+
root: rootOption$2,
|
|
220
|
+
default: defaultOption$1,
|
|
458
221
|
metadata: {
|
|
459
222
|
description: "Make every entry a record with its status and composed frontmatter.",
|
|
460
223
|
type: t.boolean().optional()
|
|
461
224
|
},
|
|
462
|
-
trace: traceOption
|
|
225
|
+
trace: traceOption$1
|
|
463
226
|
},
|
|
464
227
|
async run(args) {
|
|
465
228
|
const format = this.format;
|
|
@@ -468,24 +231,13 @@ function showCommand$1(layersFor, name = "show") {
|
|
|
468
231
|
const home = homedir();
|
|
469
232
|
const layers = await layersFor(args, home);
|
|
470
233
|
const display = (path) => collapseHome(home, path);
|
|
471
|
-
const
|
|
472
|
-
const resolved = args.names.map((name) => {
|
|
473
|
-
const entry = resolveReference(parseReferenceName(name), layers, { display });
|
|
474
|
-
for (const failure of checkResolved(entry, lookup).failures) {
|
|
475
|
-
if (failure.check === "compose") continue;
|
|
476
|
-
entry.warnings.push({
|
|
477
|
-
code: "interface-check",
|
|
478
|
-
message: `it fails its interface check (${failure.check}): ${failure.message}`
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
return entry;
|
|
482
|
-
});
|
|
234
|
+
const resolved = args.names.map((name) => resolveAsset(parseAssetName(name), layers, { display }));
|
|
483
235
|
const ok = resolved.every((entry) => entry.status === "found");
|
|
484
236
|
if (format === "text") {
|
|
485
237
|
for (const entry of resolved) {
|
|
486
|
-
writeWarnings(entry.warnings);
|
|
238
|
+
writeWarnings$1(entry.warnings);
|
|
487
239
|
if (entry.status !== "found") process.stderr.write(`error: ${entry.code}: ${errorText(entry)}\n`);
|
|
488
|
-
if (args.trace) process.stderr.write(traceText(entry, home));
|
|
240
|
+
if (args.trace) process.stderr.write(traceText$1(entry, home));
|
|
489
241
|
}
|
|
490
242
|
const answer = showText(resolved);
|
|
491
243
|
if (answer === void 0) throw CliError.reported(exitCodes.error);
|
|
@@ -495,7 +247,7 @@ function showCommand$1(layersFor, name = "show") {
|
|
|
495
247
|
metadata: Boolean(args.metadata),
|
|
496
248
|
trace: Boolean(args.trace)
|
|
497
249
|
};
|
|
498
|
-
const answer = result(resolved.map((entry) => showEntry(entry, home, flags)), { render: bareArray });
|
|
250
|
+
const answer = result(resolved.map((entry) => showEntry(entry, home, flags)), { render: bareArray$1 });
|
|
499
251
|
return ok ? answer : answerThenFail(answer, format);
|
|
500
252
|
} catch (error) {
|
|
501
253
|
return fail$1(error, "Reference lookup failed.");
|
|
@@ -503,9 +255,9 @@ function showCommand$1(layersFor, name = "show") {
|
|
|
503
255
|
}
|
|
504
256
|
});
|
|
505
257
|
}
|
|
506
|
-
function
|
|
258
|
+
function namesCommand(layersFor) {
|
|
507
259
|
return command({
|
|
508
|
-
name,
|
|
260
|
+
name: "names",
|
|
509
261
|
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.",
|
|
510
262
|
arguments: [{
|
|
511
263
|
name: "pattern",
|
|
@@ -513,9 +265,9 @@ function listCommand$1(layersFor, name = "list") {
|
|
|
513
265
|
type: t.string().optional()
|
|
514
266
|
}],
|
|
515
267
|
options: {
|
|
516
|
-
root: rootOption$
|
|
268
|
+
root: rootOption$2,
|
|
517
269
|
trace: {
|
|
518
|
-
...traceOption,
|
|
270
|
+
...traceOption$1,
|
|
519
271
|
description: "Add one row per name per layer holding it, with its outcome."
|
|
520
272
|
}
|
|
521
273
|
},
|
|
@@ -523,25 +275,21 @@ function listCommand$1(layersFor, name = "list") {
|
|
|
523
275
|
try {
|
|
524
276
|
const home = homedir();
|
|
525
277
|
const display = (path) => collapseHome(home, path);
|
|
526
|
-
const listing =
|
|
278
|
+
const listing = listAssetNames(await layersFor(args, home), {
|
|
527
279
|
display,
|
|
528
280
|
pattern: args.pattern
|
|
529
281
|
});
|
|
530
|
-
const report = compact({
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
})) : args.pattern ? `0 references match "${args.pattern}"` : "0 references — no layer holds one",
|
|
538
|
-
defaults: listing.defaults
|
|
539
|
-
});
|
|
540
|
-
if (args.trace) report.layers = listing.layers.map((row) => compact({
|
|
282
|
+
const report = compact$1({ references: listing.references.length ? listing.references.map((row) => compact$1({
|
|
283
|
+
name: row.name,
|
|
284
|
+
format: row.format,
|
|
285
|
+
status: row.status === "found" ? void 0 : row.status,
|
|
286
|
+
options: row.options
|
|
287
|
+
})) : args.pattern ? `0 references match "${args.pattern}"` : "0 references — no layer holds one" });
|
|
288
|
+
if (args.trace) report["layers"] = listing.layers.map((row) => compact$1({
|
|
541
289
|
...row,
|
|
542
290
|
path: display(row.path)
|
|
543
291
|
}));
|
|
544
|
-
if (listing.warnings.length) report
|
|
292
|
+
if (listing.warnings.length) report["warnings"] = listing.warnings;
|
|
545
293
|
return result(report);
|
|
546
294
|
} catch (error) {
|
|
547
295
|
return fail$1(error, "Reference listing failed.");
|
|
@@ -549,55 +297,9 @@ function listCommand$1(layersFor, name = "list") {
|
|
|
549
297
|
}
|
|
550
298
|
});
|
|
551
299
|
}
|
|
552
|
-
function
|
|
553
|
-
return command({
|
|
554
|
-
name: "search",
|
|
555
|
-
description: "Find references by name, description, tags, headings, or body, best match first.",
|
|
556
|
-
arguments: [{
|
|
557
|
-
name: "query",
|
|
558
|
-
description: "What the reference is about.",
|
|
559
|
-
type: t.string()
|
|
560
|
-
}],
|
|
561
|
-
options: {
|
|
562
|
-
root: rootOption$1,
|
|
563
|
-
trace: {
|
|
564
|
-
...traceOption,
|
|
565
|
-
description: "Add the tier and plugin or skill of each match."
|
|
566
|
-
}
|
|
567
|
-
},
|
|
568
|
-
async run(args) {
|
|
569
|
-
const query = args.query.trim();
|
|
570
|
-
if (!query) usage$1("search needs a query.");
|
|
571
|
-
try {
|
|
572
|
-
const home = homedir();
|
|
573
|
-
const { matches, defaults } = searchReferences(query, await layersFor(args, home), { display: (path) => collapseHome(home, path) });
|
|
574
|
-
const report = compact({
|
|
575
|
-
references: matches.length ? matches.map(({ name, description, match, tier, plugin, skill, loadedBy }) => compact(args.trace ? {
|
|
576
|
-
name,
|
|
577
|
-
description,
|
|
578
|
-
match,
|
|
579
|
-
loadedBy,
|
|
580
|
-
tier,
|
|
581
|
-
plugin,
|
|
582
|
-
skill
|
|
583
|
-
} : {
|
|
584
|
-
name,
|
|
585
|
-
description,
|
|
586
|
-
match,
|
|
587
|
-
loadedBy
|
|
588
|
-
})) : `0 references match "${query}"`,
|
|
589
|
-
defaults
|
|
590
|
-
});
|
|
591
|
-
return result(report);
|
|
592
|
-
} catch (error) {
|
|
593
|
-
return fail$1(error, "Reference search failed.");
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
function whereCommand(layersFor, assets = false, name = "where") {
|
|
300
|
+
function whereCommand$1(layersFor) {
|
|
599
301
|
return command({
|
|
600
|
-
name,
|
|
302
|
+
name: "where",
|
|
601
303
|
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.",
|
|
602
304
|
arguments: [{
|
|
603
305
|
name: "name",
|
|
@@ -605,10 +307,10 @@ function whereCommand(layersFor, assets = false, name = "where") {
|
|
|
605
307
|
type: t.string()
|
|
606
308
|
}],
|
|
607
309
|
options: {
|
|
608
|
-
root: rootOption$
|
|
609
|
-
default: defaultOption,
|
|
310
|
+
root: rootOption$2,
|
|
311
|
+
default: defaultOption$1,
|
|
610
312
|
trace: {
|
|
611
|
-
...traceOption,
|
|
313
|
+
...traceOption$1,
|
|
612
314
|
description: "Add every layer checked, with its outcome."
|
|
613
315
|
}
|
|
614
316
|
},
|
|
@@ -617,15 +319,14 @@ function whereCommand(layersFor, assets = false, name = "where") {
|
|
|
617
319
|
try {
|
|
618
320
|
const home = homedir();
|
|
619
321
|
const display = (path) => collapseHome(home, path);
|
|
620
|
-
const report =
|
|
322
|
+
const report = whereAsset(args.name, await layersFor(args, home), {
|
|
621
323
|
root: resolve(args.root ?? process.cwd()),
|
|
622
|
-
display
|
|
623
|
-
assets
|
|
324
|
+
display
|
|
624
325
|
});
|
|
625
|
-
const output = compact({
|
|
326
|
+
const output = compact$1({
|
|
626
327
|
name: report.name,
|
|
627
328
|
status: report.status,
|
|
628
|
-
places: report.places.map((place) => compact(place)),
|
|
329
|
+
places: report.places.map((place) => compact$1(place)),
|
|
629
330
|
options: report.options,
|
|
630
331
|
layers: args.trace ? report.layers.map((layer) => ({
|
|
631
332
|
...layer,
|
|
@@ -633,10 +334,10 @@ function whereCommand(layersFor, assets = false, name = "where") {
|
|
|
633
334
|
})) : void 0,
|
|
634
335
|
warnings: report.warnings
|
|
635
336
|
});
|
|
636
|
-
if (format === "text") writeWarnings(report.warnings);
|
|
337
|
+
if (format === "text") writeWarnings$1(report.warnings);
|
|
637
338
|
const answer = result(output);
|
|
638
339
|
if (report.status !== "invalid") return answer;
|
|
639
|
-
process.stderr.write(`error: reference-invalid: ${
|
|
340
|
+
process.stderr.write(`error: reference-invalid: ${parseAssetName(args.name).invalid}\n`);
|
|
640
341
|
return answerThenFail(answer, format);
|
|
641
342
|
} catch (error) {
|
|
642
343
|
return fail$1(error, "Reference placement lookup failed.");
|
|
@@ -644,384 +345,33 @@ function whereCommand(layersFor, assets = false, name = "where") {
|
|
|
644
345
|
}
|
|
645
346
|
});
|
|
646
347
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
348
|
+
/** The tiered, by-name commands over `assets/` folders. `asset`'s own commands act on a path. */
|
|
349
|
+
function createAssetResolveCommands({ plugin } = {}) {
|
|
350
|
+
const layersFor = layerSource$1(plugin);
|
|
351
|
+
return {
|
|
352
|
+
load: loadCommand$1(layersFor),
|
|
353
|
+
names: namesCommand(layersFor),
|
|
354
|
+
where: whereCommand$1(layersFor)
|
|
355
|
+
};
|
|
652
356
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
672
|
-
},
|
|
673
|
-
async run(args) {
|
|
674
|
-
const accept = parseAccept(args.accept);
|
|
675
|
-
try {
|
|
676
|
-
const home = homedir();
|
|
677
|
-
const found = referenceImplementations(args.id, await layersFor(args, home), {
|
|
678
|
-
accept,
|
|
679
|
-
display: (path) => collapseHome(home, path)
|
|
680
|
-
});
|
|
681
|
-
const rows = found.implementations.map(({ pin, description, unsupported, name, tier, version }) => compact(args.trace ? {
|
|
682
|
-
pin,
|
|
683
|
-
description,
|
|
684
|
-
unsupported,
|
|
685
|
-
name,
|
|
686
|
-
tier,
|
|
687
|
-
version
|
|
688
|
-
} : {
|
|
689
|
-
pin,
|
|
690
|
-
description,
|
|
691
|
-
unsupported
|
|
692
|
-
}));
|
|
693
|
-
const report = { implementations: rows.length ? rows : `0 implementations of ${args.id}` };
|
|
694
|
-
if (args.trace) Object.assign(report, compact({
|
|
695
|
-
interface: found.interface,
|
|
696
|
-
cardinality: found.cardinality,
|
|
697
|
-
accept: found.accept,
|
|
698
|
-
excluded: found.excluded,
|
|
699
|
-
warnings: found.warnings
|
|
700
|
-
}));
|
|
701
|
-
return result(report);
|
|
702
|
-
} catch (error) {
|
|
703
|
-
return fail$1(error, "Implementation lookup failed.");
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
function checkCommand(layersFor) {
|
|
709
|
-
return command({
|
|
710
|
-
name: "check",
|
|
711
|
-
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.",
|
|
712
|
-
arguments: [{
|
|
713
|
-
name: "names",
|
|
714
|
-
description: "Reference names, without the `.md` extension; `<plugin>/<name>` builds that plugin’s own stack.",
|
|
715
|
-
type: t.array(t.string()).optional()
|
|
716
|
-
}],
|
|
717
|
-
options: {
|
|
718
|
-
root: rootOption$1,
|
|
719
|
-
trace: {
|
|
720
|
-
...traceOption,
|
|
721
|
-
description: "Add every entry checked, with its kind and unsupported sections."
|
|
722
|
-
}
|
|
723
|
-
},
|
|
724
|
-
async run(args) {
|
|
725
|
-
const format = this.format;
|
|
726
|
-
try {
|
|
727
|
-
const home = homedir();
|
|
728
|
-
const entries = checkReferences(await layersFor(args, home), {
|
|
729
|
-
names: args.names ?? [],
|
|
730
|
-
display: (path) => collapseHome(home, path)
|
|
731
|
-
});
|
|
732
|
-
const failing = entries.filter((entry) => entry.status !== "passed");
|
|
733
|
-
const strip = (failures) => failures.map(({ check, message }) => ({
|
|
734
|
-
check,
|
|
735
|
-
message
|
|
736
|
-
}));
|
|
737
|
-
const report = compact({
|
|
738
|
-
failed: failing.map(({ name, loadedBy, failures }) => compact({
|
|
739
|
-
name,
|
|
740
|
-
loadedBy,
|
|
741
|
-
failures: strip(failures)
|
|
742
|
-
})),
|
|
743
|
-
passed: entries.length - failing.length,
|
|
744
|
-
references: args.trace ? entries.map(({ name, loadedBy, kind, status, failures, unsupported }) => compact({
|
|
745
|
-
name,
|
|
746
|
-
loadedBy,
|
|
747
|
-
kind,
|
|
748
|
-
status,
|
|
749
|
-
failures: strip(failures),
|
|
750
|
-
unsupported
|
|
751
|
-
})) : void 0
|
|
752
|
-
});
|
|
753
|
-
const answer = result(report);
|
|
754
|
-
if (!failing.length) return answer;
|
|
755
|
-
if (format === "text") process.stderr.write(`error: interface-check: ${failing.length} of ${entries.length} references fail their check.\n`);
|
|
756
|
-
return answerThenFail(answer, format);
|
|
757
|
-
} catch (error) {
|
|
758
|
-
return fail$1(error, "Reference check failed.");
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
});
|
|
762
|
-
}
|
|
763
|
-
const scopeOption = {
|
|
764
|
-
description: "Tier to write: project (default), `.agents/references/` at the root, or user, `~/.agents/references/`.",
|
|
765
|
-
type: t.string().optional()
|
|
766
|
-
};
|
|
767
|
-
const pluginOption = {
|
|
768
|
-
description: "A plugin's source folder in this repository: acts on its `references/<name>.md`.",
|
|
769
|
-
type: t.string().optional()
|
|
770
|
-
};
|
|
771
|
-
const skillOption$1 = {
|
|
772
|
-
description: "A skill's source folder in this repository: acts on its `references/<name>.default.md`.",
|
|
773
|
-
type: t.string().optional()
|
|
774
|
-
};
|
|
775
|
-
function createCommand(layersFor) {
|
|
776
|
-
return command({
|
|
777
|
-
name: "create",
|
|
778
|
-
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.",
|
|
779
|
-
arguments: [{
|
|
780
|
-
name: "name",
|
|
781
|
-
description: "Reference name, without the `.md` extension.",
|
|
782
|
-
type: t.string()
|
|
783
|
-
}],
|
|
784
|
-
options: {
|
|
785
|
-
root: rootOption$1,
|
|
786
|
-
scope: scopeOption,
|
|
787
|
-
plugin: pluginOption,
|
|
788
|
-
default: skillOption$1,
|
|
789
|
-
merge: {
|
|
790
|
-
description: "How it applies over what is below: replace, patch (default), combine, or remove. remove writes no body.",
|
|
791
|
-
type: t.string().optional()
|
|
792
|
-
},
|
|
793
|
-
template: {
|
|
794
|
-
description: "File whose text is written as it is, frontmatter included. Defaults to a built-in template of top-level `##` sections.",
|
|
795
|
-
type: t.string().optional()
|
|
796
|
-
},
|
|
797
|
-
implements: {
|
|
798
|
-
description: "Interface id to implement: the interface's sections become `##` headings, each stubbed `**Unsupported.**`, with `merge: replace`. Not with --template.",
|
|
799
|
-
type: t.string().optional()
|
|
800
|
-
},
|
|
801
|
-
"dry-run": {
|
|
802
|
-
description: "Print the target path and the exact content, and write nothing.",
|
|
803
|
-
type: t.boolean().optional()
|
|
804
|
-
},
|
|
805
|
-
trace: {
|
|
806
|
-
...traceOption,
|
|
807
|
-
description: "After the write, add the trace, in which the new file applies."
|
|
808
|
-
}
|
|
809
|
-
},
|
|
810
|
-
async run(args) {
|
|
811
|
-
const format = this.format;
|
|
812
|
-
try {
|
|
813
|
-
const home = homedir();
|
|
814
|
-
const root = resolve(args.root ?? process.cwd());
|
|
815
|
-
const display = (path) => collapseHome(home, path);
|
|
816
|
-
const created = createContribution(await layersFor({
|
|
817
|
-
root,
|
|
818
|
-
default: args.default
|
|
819
|
-
}, home), {
|
|
820
|
-
root,
|
|
821
|
-
home,
|
|
822
|
-
name: args.name,
|
|
823
|
-
scope: args.scope,
|
|
824
|
-
plugin: args.plugin,
|
|
825
|
-
default: args.default,
|
|
826
|
-
merge: args.merge,
|
|
827
|
-
template: args.template,
|
|
828
|
-
implements: args.implements,
|
|
829
|
-
dryRun: args["dry-run"]
|
|
830
|
-
}, { display });
|
|
831
|
-
const report = compact({
|
|
832
|
-
path: display(created.path),
|
|
833
|
-
hint: created.dryRun ? "Nothing was written. Run again without --dry-run to write it." : `Run \`show ${created.name}\` for the composed document.`,
|
|
834
|
-
content: created.dryRun ? created.content : void 0,
|
|
835
|
-
hints: created.hints,
|
|
836
|
-
trace: args.trace && created.after ? displayTrace(created.after.trace, home) : void 0
|
|
837
|
-
});
|
|
838
|
-
if (format !== "text") return result(report);
|
|
839
|
-
for (const hint of created.hints) process.stderr.write(`hint: ${hint}\n`);
|
|
840
|
-
if (args.trace && created.after) process.stderr.write(traceText(created.after, home));
|
|
841
|
-
return document(created.dryRun ? `${report.path}\n\n${created.content}` : `${report.path}\n\n${report.hint}\n`);
|
|
842
|
-
} catch (error) {
|
|
843
|
-
return fail$1(error, "Reference creation failed.");
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
function selectCommand$1(layersFor) {
|
|
849
|
-
return command({
|
|
850
|
-
name: "select",
|
|
851
|
-
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.",
|
|
852
|
-
arguments: [{
|
|
853
|
-
name: "name",
|
|
854
|
-
description: "Reference name, without the `.md` extension.",
|
|
855
|
-
type: t.string()
|
|
856
|
-
}, {
|
|
857
|
-
name: "options",
|
|
858
|
-
description: "Bottom first: each a plugin holding the name (or `<plugin>/<name>`), or `default`.",
|
|
859
|
-
type: t.array(t.string()).optional()
|
|
860
|
-
}],
|
|
861
|
-
options: {
|
|
862
|
-
root: rootOption$1,
|
|
863
|
-
scope: scopeOption,
|
|
864
|
-
clear: {
|
|
865
|
-
description: "Remove the selection; a file left with nothing in it is deleted.",
|
|
866
|
-
type: t.boolean().optional()
|
|
867
|
-
}
|
|
868
|
-
},
|
|
869
|
-
async run(args) {
|
|
870
|
-
const format = this.format;
|
|
871
|
-
try {
|
|
872
|
-
const home = homedir();
|
|
873
|
-
const root = resolve(args.root ?? process.cwd());
|
|
874
|
-
const display = (path) => collapseHome(home, path);
|
|
875
|
-
const selected = selectContribution(await layersFor({ root }, home), {
|
|
876
|
-
root,
|
|
877
|
-
home,
|
|
878
|
-
name: args.name,
|
|
879
|
-
options: args.options ?? [],
|
|
880
|
-
scope: args.scope,
|
|
881
|
-
clear: args.clear
|
|
882
|
-
}, { display });
|
|
883
|
-
const report = compact({
|
|
884
|
-
path: display(selected.path),
|
|
885
|
-
extends: selected.extends,
|
|
886
|
-
deleted: selected.deleted || void 0,
|
|
887
|
-
status: selected.after.status,
|
|
888
|
-
stack: selected.after.trace.stack,
|
|
889
|
-
answer: describeAnswer(selected.after, display)
|
|
890
|
-
});
|
|
891
|
-
if (format !== "text") return result(report);
|
|
892
|
-
writeWarnings(selected.after.warnings);
|
|
893
|
-
const what = args.clear ? selected.deleted ? "selection cleared; the file was left empty and deleted" : "selection cleared" : `extends: [${selected.extends.join(", ")}]`;
|
|
894
|
-
return document(`${report.path}\n\n${what}. ${report.answer}\n`);
|
|
895
|
-
} catch (error) {
|
|
896
|
-
return fail$1(error, "Reference selection failed.");
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
function deleteReport(deleted, home) {
|
|
902
|
-
const selection = deleted.selection ? `the selection extends: [${deleted.selection.join(", ")}] went with it; run \`reference select ${deleted.name} <option>...\` to restore it` : void 0;
|
|
903
|
-
return compact({
|
|
904
|
-
path: collapseHome(home, deleted.path),
|
|
905
|
-
dryRun: deleted.dryRun || void 0,
|
|
906
|
-
status: deleted.after.status,
|
|
907
|
-
stack: deleted.after.trace.stack,
|
|
908
|
-
answer: describeAnswer(deleted.after, (path) => collapseHome(home, path)),
|
|
909
|
-
selection,
|
|
910
|
-
options: deleted.after.options
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
function deleteCommand$1(layersFor) {
|
|
914
|
-
return command({
|
|
915
|
-
name: "delete",
|
|
916
|
-
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.",
|
|
917
|
-
arguments: [{
|
|
918
|
-
name: "name",
|
|
919
|
-
description: "Reference name, without the `.md` extension.",
|
|
920
|
-
type: t.string()
|
|
921
|
-
}],
|
|
922
|
-
options: {
|
|
923
|
-
root: rootOption$1,
|
|
924
|
-
scope: {
|
|
925
|
-
...scopeOption,
|
|
926
|
-
description: "Tier to delete from: project (default) or user."
|
|
927
|
-
},
|
|
928
|
-
plugin: pluginOption,
|
|
929
|
-
default: skillOption$1,
|
|
930
|
-
"dry-run": {
|
|
931
|
-
description: "Print the file that would be deleted and what would answer the name then, and delete nothing.",
|
|
932
|
-
type: t.boolean().optional()
|
|
933
|
-
}
|
|
934
|
-
},
|
|
935
|
-
async run(args) {
|
|
936
|
-
const format = this.format;
|
|
937
|
-
try {
|
|
938
|
-
const home = homedir();
|
|
939
|
-
const root = resolve(args.root ?? process.cwd());
|
|
940
|
-
const deleted = deleteContribution(await layersFor({
|
|
941
|
-
root,
|
|
942
|
-
default: args.default
|
|
943
|
-
}, home), {
|
|
944
|
-
root,
|
|
945
|
-
home,
|
|
946
|
-
name: args.name,
|
|
947
|
-
scope: args.scope,
|
|
948
|
-
plugin: args.plugin,
|
|
949
|
-
default: args.default,
|
|
950
|
-
dryRun: args["dry-run"]
|
|
951
|
-
}, { display: (path) => collapseHome(home, path) });
|
|
952
|
-
const report = deleteReport(deleted, home);
|
|
953
|
-
if (format !== "text") return result(report);
|
|
954
|
-
const selection = report.selection ? ` Also, ${report.selection}.` : "";
|
|
955
|
-
return document(`${report.path}\n\n${deleted.dryRun ? "would delete" : "deleted"}. ${report.answer}${selection}\n`);
|
|
956
|
-
} catch (error) {
|
|
957
|
-
return fail$1(error, "Reference deletion failed.");
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
});
|
|
961
|
-
}
|
|
962
|
-
/** A factory, so each host CLI names itself as the plugin whose own references come first. */
|
|
963
|
-
function createReferenceCommands({ plugin } = {}) {
|
|
964
|
-
const layersFor = layerSource(plugin);
|
|
965
|
-
return {
|
|
966
|
-
show: showCommand$1(layersFor),
|
|
967
|
-
list: listCommand$1(layersFor),
|
|
968
|
-
search: searchCommand(layersFor),
|
|
969
|
-
where: whereCommand(layersFor),
|
|
970
|
-
create: createCommand(layersFor),
|
|
971
|
-
select: selectCommand$1(layersFor),
|
|
972
|
-
delete: deleteCommand$1(layersFor),
|
|
973
|
-
check: checkCommand(layersFor),
|
|
974
|
-
implementations: implementationsCommand(layersFor)
|
|
975
|
-
};
|
|
976
|
-
}
|
|
977
|
-
function createReferenceCommand(options = {}) {
|
|
978
|
-
const { show, list, search, where, create, select, delete: remove, check, implementations } = createReferenceCommands(options);
|
|
979
|
-
return command({
|
|
980
|
-
name: "reference",
|
|
981
|
-
description: "Read reference documents by name, composed from the managed, project, user, plugin, and default tiers, and write, select, or delete a contribution.",
|
|
982
|
-
commands: [
|
|
983
|
-
show,
|
|
984
|
-
list,
|
|
985
|
-
search,
|
|
986
|
-
where,
|
|
987
|
-
create,
|
|
988
|
-
select,
|
|
989
|
-
remove,
|
|
990
|
-
implementations,
|
|
991
|
-
check
|
|
992
|
-
]
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
/**
|
|
996
|
-
* The tiered, by-name commands over `assets/` folders: the reference engine reading `assets/` where it
|
|
997
|
-
* reads `references/`. `load` is `show`, `names` is `list`. `asset`'s own commands act on a path.
|
|
998
|
-
*/
|
|
999
|
-
function createAssetResolveCommands({ plugin } = {}) {
|
|
1000
|
-
const layersFor = layerSource(plugin, "assets");
|
|
1001
|
-
return {
|
|
1002
|
-
load: showCommand$1(layersFor, "load"),
|
|
1003
|
-
names: listCommand$1(layersFor, "names"),
|
|
1004
|
-
where: whereCommand(layersFor, true)
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
//#endregion
|
|
1008
|
-
//#region src/assets/skill-assets.ts
|
|
1009
|
-
/** A refusal. `usage` marks a wrong invocation (exit 2) rather than a refusal (exit 1). */
|
|
1010
|
-
var AssetError = class extends Error {
|
|
1011
|
-
code;
|
|
1012
|
-
usage;
|
|
1013
|
-
constructor(code, message, usage = false) {
|
|
1014
|
-
super(message);
|
|
1015
|
-
this.code = code;
|
|
1016
|
-
this.usage = usage;
|
|
1017
|
-
}
|
|
1018
|
-
};
|
|
1019
|
-
/** The skill's `assets/` folder, for a skill folder that holds a `SKILL.md`. */
|
|
1020
|
-
function assetsDir(skill, root, write) {
|
|
1021
|
-
const folder = resolve(skill);
|
|
1022
|
-
if (skillName(folder) === void 0) throw new AssetError("folder-invalid", `${folder} holds no SKILL.md, so it is not a skill folder.`);
|
|
1023
|
-
if (write && !insideRepository(folder, root)) throw new AssetError("folder-invalid", `${folder} is outside the repository, such as an installed copy; write to the skill's source instead.`);
|
|
1024
|
-
return join(folder, "assets");
|
|
357
|
+
//#endregion
|
|
358
|
+
//#region src/assets/skill-assets.ts
|
|
359
|
+
/** A refusal. `usage` marks a wrong invocation (exit 2) rather than a refusal (exit 1). */
|
|
360
|
+
var AssetError = class extends Error {
|
|
361
|
+
code;
|
|
362
|
+
usage;
|
|
363
|
+
constructor(code, message, usage = false) {
|
|
364
|
+
super(message);
|
|
365
|
+
this.code = code;
|
|
366
|
+
this.usage = usage;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
/** The skill's `assets/` folder, for a skill folder that holds a `SKILL.md`. */
|
|
370
|
+
function assetsDir(skill, root, write) {
|
|
371
|
+
const folder = resolve(skill);
|
|
372
|
+
if (skillName(folder) === void 0) throw new AssetError("folder-invalid", `${folder} holds no SKILL.md, so it is not a skill folder.`);
|
|
373
|
+
if (write && !insideRepository(folder, root)) throw new AssetError("folder-invalid", `${folder} is outside the repository, such as an installed copy; write to the skill's source instead.`);
|
|
374
|
+
return join(folder, "assets");
|
|
1025
375
|
}
|
|
1026
376
|
/** An asset path: relative, `/`-separated, with no `..` or empty part. */
|
|
1027
377
|
function assetFile(dir, path) {
|
|
@@ -1107,21 +457,20 @@ function deleteAsset(skill, root, path) {
|
|
|
1107
457
|
* were in effect (the managed file would still win).
|
|
1108
458
|
*/
|
|
1109
459
|
function copyAsset(layers, args) {
|
|
1110
|
-
const name =
|
|
460
|
+
const name = parseAssetName(args.name);
|
|
1111
461
|
if (name.invalid || name.plugin !== void 0) throw new AssetError("name-invalid", name.invalid ?? "create takes a bare name.", true);
|
|
1112
462
|
if (args.scope !== "project" && args.scope !== "user") throw new AssetError("options-invalid", "--scope is project or user.", true);
|
|
1113
463
|
if (args.copy === "managed") throw new AssetError("managed-copy-refused", "the managed tier is policy and is never copied into a project or user folder. Ask the administrator, or read it with \"asset load\".");
|
|
1114
|
-
const found =
|
|
464
|
+
const found = resolveAsset(name.name, layers).trace.layers.filter(({ file }) => file);
|
|
1115
465
|
const source = found.find((layer) => args.copy === "default" || args.copy === "project" || args.copy === "user" ? layer.tier === args.copy : layer.tier === "plugin" && layer.plugin === args.copy);
|
|
1116
466
|
if (!source) {
|
|
1117
467
|
const held = found.map((layer) => layer.tier === "plugin" ? layer.plugin : layer.tier);
|
|
1118
468
|
throw new AssetError("no-source", `"${args.copy}" holds no asset "${name.name}". Held by: ${held.filter((tier) => tier !== "managed").join(", ") || "nothing"}.`);
|
|
1119
469
|
}
|
|
1120
470
|
const extension = extname(source.path);
|
|
1121
|
-
if (source.file && ![...
|
|
1122
|
-
const
|
|
1123
|
-
const
|
|
1124
|
-
const taken = [".md", ...dataExtensions.map((item) => item.extension)].map((item) => join(dir, `${name.name}${item}`)).find((path) => existsSync(path));
|
|
471
|
+
if (source.file && ![...assetDataExtensions.map((item) => item.extension), ".md"].includes(extension)) throw new AssetError("no-source", `${basename(source.path)} is not a file an asset name resolves to.`);
|
|
472
|
+
const dir = args.scope === "user" ? userAssetsDir(args.home) : projectAssetsDir(resolve(args.root));
|
|
473
|
+
const taken = [".md", ...assetDataExtensions.map((item) => item.extension)].map((item) => join(dir, `${name.name}${item}`)).find((path) => existsSync(path));
|
|
1125
474
|
if (taken) throw new AssetError("exists", `${taken} already exists; edit it, or delete it first.`);
|
|
1126
475
|
const path = join(dir, `${name.name}${extension}`);
|
|
1127
476
|
mkdirSync(dir, { recursive: true });
|
|
@@ -1136,7 +485,7 @@ function copyAsset(layers, args) {
|
|
|
1136
485
|
//#region src/assets/select-asset.ts
|
|
1137
486
|
/** The options a name can be selected between: each plugin holding it, and `default` when the skill's file exists. */
|
|
1138
487
|
function options(layers, name) {
|
|
1139
|
-
const found =
|
|
488
|
+
const found = resolveAsset(name, layers).trace.layers;
|
|
1140
489
|
return [...found.filter(({ tier, file }) => tier === "plugin" && file).map(({ plugin }) => plugin), ...found.some(({ tier, file }) => tier === "default" && file) ? ["default"] : []];
|
|
1141
490
|
}
|
|
1142
491
|
/**
|
|
@@ -1144,11 +493,11 @@ function options(layers, name) {
|
|
|
1144
493
|
* answers the name. Nothing else is in the file, and nothing extends or merges.
|
|
1145
494
|
*/
|
|
1146
495
|
function selectAsset(layers, args) {
|
|
1147
|
-
const name =
|
|
496
|
+
const name = parseAssetName(args.name);
|
|
1148
497
|
if (name.invalid || name.plugin !== void 0) throw new AssetError("name-invalid", name.invalid ?? "select takes a bare name.", true);
|
|
1149
498
|
if (args.scope !== void 0 && args.scope !== "project" && args.scope !== "user") throw new AssetError("options-invalid", "--scope is project or user.", true);
|
|
1150
|
-
const
|
|
1151
|
-
const path = join(
|
|
499
|
+
const dir = args.scope === "user" ? userAssetsDir(args.home) : projectAssetsDir(resolve(args.root));
|
|
500
|
+
const path = join(dir, `${name.name}.select`);
|
|
1152
501
|
if (args.clear) {
|
|
1153
502
|
if (!existsSync(path)) throw new AssetError("no-file", `${path} does not exist, so there is no selection to clear.`);
|
|
1154
503
|
rmSync(path);
|
|
@@ -1161,7 +510,7 @@ function selectAsset(layers, args) {
|
|
|
1161
510
|
const result = {
|
|
1162
511
|
path,
|
|
1163
512
|
cleared: Boolean(args.clear),
|
|
1164
|
-
after:
|
|
513
|
+
after: resolveAsset(name.name, layers, { without: args.clear ? path : void 0 })
|
|
1165
514
|
};
|
|
1166
515
|
return args.clear ? result : {
|
|
1167
516
|
...result,
|
|
@@ -1170,11 +519,11 @@ function selectAsset(layers, args) {
|
|
|
1170
519
|
}
|
|
1171
520
|
//#endregion
|
|
1172
521
|
//#region src/assets/asset.command.ts
|
|
1173
|
-
const skillOption = {
|
|
522
|
+
const skillOption$1 = {
|
|
1174
523
|
description: "A skill's source folder in this repository: its `assets/` folder is acted on.",
|
|
1175
524
|
type: t.string()
|
|
1176
525
|
};
|
|
1177
|
-
const rootOption = {
|
|
526
|
+
const rootOption$1 = {
|
|
1178
527
|
description: "The directory the repository is found from. Defaults to the current directory.",
|
|
1179
528
|
type: t.string().optional()
|
|
1180
529
|
};
|
|
@@ -1195,13 +544,13 @@ const pathDescription = "Asset path inside `assets/`, such as `data/rules.json`.
|
|
|
1195
544
|
function rootOf(args) {
|
|
1196
545
|
return resolve(args.root ?? process.cwd());
|
|
1197
546
|
}
|
|
1198
|
-
function listCommand() {
|
|
547
|
+
function listCommand$1() {
|
|
1199
548
|
return command({
|
|
1200
549
|
name: "list",
|
|
1201
550
|
description: "List every file in a skill's assets/ folder with its size.",
|
|
1202
551
|
options: {
|
|
1203
|
-
skill: skillOption,
|
|
1204
|
-
root: rootOption
|
|
552
|
+
skill: skillOption$1,
|
|
553
|
+
root: rootOption$1
|
|
1205
554
|
},
|
|
1206
555
|
async run(args) {
|
|
1207
556
|
try {
|
|
@@ -1223,8 +572,8 @@ function showCommand() {
|
|
|
1223
572
|
type: t.string()
|
|
1224
573
|
}],
|
|
1225
574
|
options: {
|
|
1226
|
-
skill: skillOption,
|
|
1227
|
-
root: rootOption
|
|
575
|
+
skill: skillOption$1,
|
|
576
|
+
root: rootOption$1
|
|
1228
577
|
},
|
|
1229
578
|
async run(args) {
|
|
1230
579
|
try {
|
|
@@ -1247,10 +596,10 @@ function writeCommand(mode, host) {
|
|
|
1247
596
|
}],
|
|
1248
597
|
options: {
|
|
1249
598
|
skill: {
|
|
1250
|
-
...skillOption,
|
|
599
|
+
...skillOption$1,
|
|
1251
600
|
type: t.string().optional()
|
|
1252
601
|
},
|
|
1253
|
-
root: rootOption,
|
|
602
|
+
root: rootOption$1,
|
|
1254
603
|
content: {
|
|
1255
604
|
description: "The text to write.",
|
|
1256
605
|
type: t.string().optional()
|
|
@@ -1280,14 +629,13 @@ function writeCommand(mode, host) {
|
|
|
1280
629
|
if (args.content !== void 0 || args.from !== void 0) usage("--copy takes neither --content nor --from.");
|
|
1281
630
|
const home = homedir();
|
|
1282
631
|
const root = rootOf(args);
|
|
1283
|
-
const done = copyAsset(await
|
|
632
|
+
const done = copyAsset(await assetLayers({
|
|
1284
633
|
root,
|
|
1285
634
|
home,
|
|
1286
635
|
platform: process.platform,
|
|
1287
636
|
env: process.env,
|
|
1288
637
|
plugin: host.plugin,
|
|
1289
|
-
defaultSkill: args.default ?? args.skill
|
|
1290
|
-
folder: "assets"
|
|
638
|
+
defaultSkill: args.default ?? args.skill
|
|
1291
639
|
}), {
|
|
1292
640
|
root,
|
|
1293
641
|
home,
|
|
@@ -1308,7 +656,7 @@ function writeCommand(mode, host) {
|
|
|
1308
656
|
}
|
|
1309
657
|
});
|
|
1310
658
|
}
|
|
1311
|
-
function deleteCommand() {
|
|
659
|
+
function deleteCommand$1() {
|
|
1312
660
|
return command({
|
|
1313
661
|
name: "delete",
|
|
1314
662
|
description: "Delete an asset from a skill's source folder, and any folder it leaves empty.",
|
|
@@ -1318,8 +666,8 @@ function deleteCommand() {
|
|
|
1318
666
|
type: t.string()
|
|
1319
667
|
}],
|
|
1320
668
|
options: {
|
|
1321
|
-
skill: skillOption,
|
|
1322
|
-
root: rootOption
|
|
669
|
+
skill: skillOption$1,
|
|
670
|
+
root: rootOption$1
|
|
1323
671
|
},
|
|
1324
672
|
async run(args) {
|
|
1325
673
|
try {
|
|
@@ -1344,7 +692,7 @@ function selectCommand(options) {
|
|
|
1344
692
|
type: t.string().optional()
|
|
1345
693
|
}],
|
|
1346
694
|
options: {
|
|
1347
|
-
root: rootOption,
|
|
695
|
+
root: rootOption$1,
|
|
1348
696
|
default: {
|
|
1349
697
|
description: "The calling skill's folder: its `assets/<name>.<ext>` is the `default` option.",
|
|
1350
698
|
type: t.string().optional()
|
|
@@ -1362,14 +710,13 @@ function selectCommand(options) {
|
|
|
1362
710
|
try {
|
|
1363
711
|
const home = homedir();
|
|
1364
712
|
const root = resolve(args.root ?? process.cwd());
|
|
1365
|
-
const done = selectAsset(await
|
|
713
|
+
const done = selectAsset(await assetLayers({
|
|
1366
714
|
root,
|
|
1367
715
|
home,
|
|
1368
716
|
platform: process.platform,
|
|
1369
717
|
env: process.env,
|
|
1370
718
|
plugin: options.plugin,
|
|
1371
|
-
defaultSkill: args.default
|
|
1372
|
-
folder: "assets"
|
|
719
|
+
defaultSkill: args.default
|
|
1373
720
|
}), {
|
|
1374
721
|
...args,
|
|
1375
722
|
root,
|
|
@@ -1389,11 +736,11 @@ function createAssetCommand(options = {}) {
|
|
|
1389
736
|
name: "asset",
|
|
1390
737
|
description: "Manage a skill's assets/ files by path (list, show, create, update, delete), and load one by name through the tiers (load, names, where, select).",
|
|
1391
738
|
commands: [
|
|
1392
|
-
listCommand(),
|
|
739
|
+
listCommand$1(),
|
|
1393
740
|
showCommand(),
|
|
1394
741
|
writeCommand("create", options),
|
|
1395
742
|
writeCommand("update", options),
|
|
1396
|
-
deleteCommand(),
|
|
743
|
+
deleteCommand$1(),
|
|
1397
744
|
load,
|
|
1398
745
|
names,
|
|
1399
746
|
where,
|
|
@@ -1402,4 +749,918 @@ function createAssetCommand(options = {}) {
|
|
|
1402
749
|
});
|
|
1403
750
|
}
|
|
1404
751
|
//#endregion
|
|
752
|
+
//#region src/references/reference-command-support.ts
|
|
753
|
+
const rootOption = {
|
|
754
|
+
description: "The repository root: its `.agents/references/` is the repo tier, and its `package.json` names the dependency plugins. Defaults to the nearest folder at or above the current directory holding `.git`, else the current directory.",
|
|
755
|
+
type: t.string().optional()
|
|
756
|
+
};
|
|
757
|
+
const traceOption = {
|
|
758
|
+
description: "Add the trace: every layer checked, with its outcome, and how the result was built.",
|
|
759
|
+
type: t.boolean().optional()
|
|
760
|
+
};
|
|
761
|
+
const defaultOption = {
|
|
762
|
+
description: "The calling skill's folder: its `references/<name>.default.md` joins as the `default` tier.",
|
|
763
|
+
type: t.string().optional()
|
|
764
|
+
};
|
|
765
|
+
function layerSource(plugin) {
|
|
766
|
+
return (args, home) => referenceLayers({
|
|
767
|
+
root: args.root,
|
|
768
|
+
cwd: process.cwd(),
|
|
769
|
+
home,
|
|
770
|
+
platform: process.platform,
|
|
771
|
+
env: process.env,
|
|
772
|
+
plugin,
|
|
773
|
+
defaultSkill: args.default
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
/** A field with nothing to say is left out: `undefined`, an empty string, or an empty list. */
|
|
777
|
+
function compact(value) {
|
|
778
|
+
return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== void 0 && field !== "" && !(Array.isArray(field) && !field.length)));
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* A thrown failure as the one axi error value the cli prints on stdout: a `ReferenceFailure` keeps its
|
|
782
|
+
* code and exits 1, or 2 for a usage error; a `CliError` passes through.
|
|
783
|
+
*/
|
|
784
|
+
function asCliError(error, fallback) {
|
|
785
|
+
if (isCliError(error)) throw error;
|
|
786
|
+
if (error instanceof ReferenceFailure) throw new CliError(error.message, {
|
|
787
|
+
code: error.code,
|
|
788
|
+
exitCode: error.usage ? exitCodes.usage : exitCodes.error,
|
|
789
|
+
details: error.details,
|
|
790
|
+
help: error.help
|
|
791
|
+
});
|
|
792
|
+
throw new CliError(error instanceof Error ? error.message : fallback, { cause: error });
|
|
793
|
+
}
|
|
794
|
+
function writeWarnings(warnings) {
|
|
795
|
+
for (const { code, message } of warnings) process.stderr.write(`warning: ${code}: ${message}\n`);
|
|
796
|
+
}
|
|
797
|
+
function displayTrace(trace, home) {
|
|
798
|
+
const show = (path) => collapseHome(home, path);
|
|
799
|
+
return {
|
|
800
|
+
...trace,
|
|
801
|
+
contributions: trace.contributions.map((row) => ({
|
|
802
|
+
...row,
|
|
803
|
+
path: show(row.path)
|
|
804
|
+
})),
|
|
805
|
+
layers: trace.layers.map((layer) => ({
|
|
806
|
+
...layer,
|
|
807
|
+
path: show(layer.path)
|
|
808
|
+
})),
|
|
809
|
+
projectSections: trace.projectSections.map((row) => ({
|
|
810
|
+
...row,
|
|
811
|
+
path: show(row.path)
|
|
812
|
+
}))
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
/** A generated document's record in the text trace: each source, recorded and installed, then the request (R46). */
|
|
816
|
+
function recordLines(row, indent) {
|
|
817
|
+
const lines = (row.sources ?? []).map((source) => {
|
|
818
|
+
return `${indent}source ${source.default !== void 0 ? `default ${source.default}${source.plugin ? ` (plugin ${source.plugin})` : ""}` : `plugin ${source.plugin}`}: recorded ${source.recorded ?? "no version"}, installed ${source.status === "missing" ? "not installed" : source.installed ?? "no version"} (${source.status})`;
|
|
819
|
+
});
|
|
820
|
+
if (row.request !== void 0) lines.push(`${indent}request:`, ...row.request.replace(/\n$/, "").split("\n").map((line) => `${indent} ${line}`));
|
|
821
|
+
return lines;
|
|
822
|
+
}
|
|
823
|
+
/** The text trace: a summary line, then each section with its contributions, then each layer. */
|
|
824
|
+
function traceText(resolved, home) {
|
|
825
|
+
const trace = displayTrace(resolved.trace, home);
|
|
826
|
+
const lines = [`${resolved.name}: ${resolved.status}; stack: ${trace.stack.join(" → ") || "none"}; built by ${trace.builtBy}; harnesses: ${trace.harnesses.join(", ") || "none detected"}`];
|
|
827
|
+
if (resolved.message) lines.push(` ${resolved.message}`);
|
|
828
|
+
if (trace.sections.length) {
|
|
829
|
+
lines.push("sections:");
|
|
830
|
+
for (const section of trace.sections) {
|
|
831
|
+
const how = section.removedBy ? `removed by ${section.removedBy}` : section.contributions.map(({ contribution, operation }) => `${contribution} (${operation})`).join(", ") || "no text";
|
|
832
|
+
lines.push(` ${section.path}: ${how}`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
lines.push("layers:");
|
|
836
|
+
for (const layer of trace.layers) {
|
|
837
|
+
const who = layer.plugin ? ` ${layer.plugin}` : layer.skill ? ` ${layer.skill}` : "";
|
|
838
|
+
const how = layer.operation ? `; ${layer.operation} from ${layer.operationFrom}` : "";
|
|
839
|
+
const extendsNote = layer.extendsOutcome ? `; extends ${layer.extendsOutcome}` : "";
|
|
840
|
+
const selection = layer.selection ? "; selection" : "";
|
|
841
|
+
const frontmatter = layer.frontmatter && Object.keys(layer.frontmatter).length ? ` ${JSON.stringify(layer.frontmatter)}` : "";
|
|
842
|
+
lines.push(` ${layer.tier}${who} ${layer.path}: ${layer.outcome}${how}${extendsNote}${selection}${frontmatter}`);
|
|
843
|
+
lines.push(...recordLines(layer, " "));
|
|
844
|
+
}
|
|
845
|
+
if (trace.projectSections.length) {
|
|
846
|
+
lines.push("project sections:");
|
|
847
|
+
for (const row of trace.projectSections) {
|
|
848
|
+
const frontmatter = row.frontmatter && Object.keys(row.frontmatter).length ? ` ${JSON.stringify(row.frontmatter)}` : "";
|
|
849
|
+
const from = row.plugin === void 0 ? "" : ` (plugin ${row.plugin}${row.version ? `@${row.version}` : ""})`;
|
|
850
|
+
lines.push(` ${row.folder}${from} ${row.path}: ${row.outcome}${frontmatter}`);
|
|
851
|
+
lines.push(...recordLines(row, " "));
|
|
852
|
+
for (const section of row.sections ?? []) {
|
|
853
|
+
const how = section.contributions.map(({ contribution, operation }) => `${contribution} (${operation})`).join(", ");
|
|
854
|
+
lines.push(` ${section.path}: ${how || "no text"}`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return `${lines.join("\n")}\n`;
|
|
859
|
+
}
|
|
860
|
+
function escapeText(value) {
|
|
861
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
862
|
+
}
|
|
863
|
+
function escapeAttribute(value) {
|
|
864
|
+
return escapeText(value).replace(/"/g, """);
|
|
865
|
+
}
|
|
866
|
+
/** A name not `found`, in text: its status and code, holding its message, and its options when ambiguous. */
|
|
867
|
+
function notFoundTag(entry) {
|
|
868
|
+
const options = entry.options?.length ? `; options: ${entry.options.join(", ")}` : "";
|
|
869
|
+
return `<reference name="${escapeAttribute(entry.name)}" status="${entry.status}" code="${entry.code}">${escapeText(`${entry.message ?? entry.status}${options}`)}</reference>`;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* A found name's text: the main document, then each project section wrapped with its folder, in path
|
|
873
|
+
* order, keeping its own heading levels (R44). A package plugin's section also names the plugin, so
|
|
874
|
+
* the agent can put the folder's own section over it (R90).
|
|
875
|
+
*/
|
|
876
|
+
function deliveredText(entry) {
|
|
877
|
+
const content = entry.content ?? "";
|
|
878
|
+
if (!entry.projectSections.length) return content;
|
|
879
|
+
return `${[...content.trim() ? [content.trimEnd()] : [], ...entry.projectSections.map(({ folder, plugin, content: text }) => `<project folder="${escapeAttribute(folder)}"${plugin === void 0 ? "" : ` plugin="${escapeAttribute(plugin)}"`}>\n${text}</project>`)].join("\n\n")}\n`;
|
|
880
|
+
}
|
|
881
|
+
/** A found name among several, in text. */
|
|
882
|
+
function referenceTag(name, content) {
|
|
883
|
+
return `<reference name="${escapeAttribute(name)}">\n${content}</reference>`;
|
|
884
|
+
}
|
|
885
|
+
const scopeOption = {
|
|
886
|
+
description: "Tier to write: repo (default), `.agents/references/` at the root, or user, `~/.agents/references/`.",
|
|
887
|
+
type: t.string().optional()
|
|
888
|
+
};
|
|
889
|
+
const inOption = {
|
|
890
|
+
description: "A folder strictly below the root, which must exist and not be ignored by git: acts on its project section, `<folder>/.agents/references/<name>.md`.",
|
|
891
|
+
type: t.string().optional()
|
|
892
|
+
};
|
|
893
|
+
/** The root a write checks its usage against before the layers are read: `--root`, else the one found from the working folder. */
|
|
894
|
+
function givenRoot(root) {
|
|
895
|
+
return root === void 0 ? findRoot(process.cwd()) : resolve(root);
|
|
896
|
+
}
|
|
897
|
+
const pluginOption = {
|
|
898
|
+
description: "A plugin's source folder in this repository: acts on its `references/<name>.md`.",
|
|
899
|
+
type: t.string().optional()
|
|
900
|
+
};
|
|
901
|
+
const skillOption = {
|
|
902
|
+
description: "A skill's source folder in this repository: acts on its `references/<name>.default.md`.",
|
|
903
|
+
type: t.string().optional()
|
|
904
|
+
};
|
|
905
|
+
//#endregion
|
|
906
|
+
//#region src/references/reference-check.command.ts
|
|
907
|
+
function checkCommand(layersFor) {
|
|
908
|
+
return command({
|
|
909
|
+
name: "check",
|
|
910
|
+
description: "Check references, each composed as `load` composes it: an interface document against its shape, an implementation against its interface, and every document for the defects composing it raises. With no names, checks every name found. Every warning `load` raises is reported. A failure is an entry in `failed`, not an error; a warning never fails a check.",
|
|
911
|
+
arguments: [{
|
|
912
|
+
name: "names",
|
|
913
|
+
description: "Reference names, without the `.md` extension; `<plugin>/<name>` builds that plugin’s own stack.",
|
|
914
|
+
type: t.array(t.string()).optional()
|
|
915
|
+
}],
|
|
916
|
+
options: {
|
|
917
|
+
root: rootOption,
|
|
918
|
+
trace: {
|
|
919
|
+
...traceOption,
|
|
920
|
+
description: "Add every entry checked, with its kind and unsupported sections."
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
async run(args) {
|
|
924
|
+
try {
|
|
925
|
+
const home = homedir();
|
|
926
|
+
const entries = checkReferences(await layersFor(args, home), {
|
|
927
|
+
names: args.names ?? [],
|
|
928
|
+
display: (path) => collapseHome(home, path)
|
|
929
|
+
});
|
|
930
|
+
const failing = entries.filter((entry) => entry.failures.length);
|
|
931
|
+
const strip = (failures) => failures.map(({ check, message }) => ({
|
|
932
|
+
check,
|
|
933
|
+
message
|
|
934
|
+
}));
|
|
935
|
+
const report = compact({
|
|
936
|
+
failed: failing.map(({ name, loadedBy, failures, warnings }) => compact({
|
|
937
|
+
name,
|
|
938
|
+
loadedBy,
|
|
939
|
+
failures: strip(failures),
|
|
940
|
+
warnings
|
|
941
|
+
})),
|
|
942
|
+
warned: entries.filter((entry) => !entry.failures.length && entry.warnings.length).map(({ name, loadedBy, warnings }) => compact({
|
|
943
|
+
name,
|
|
944
|
+
loadedBy,
|
|
945
|
+
warnings
|
|
946
|
+
})),
|
|
947
|
+
passed: entries.length - failing.length,
|
|
948
|
+
references: args.trace ? entries.map(({ name, loadedBy, kind, status, failures, unsupported, warnings }) => compact({
|
|
949
|
+
name,
|
|
950
|
+
loadedBy,
|
|
951
|
+
kind,
|
|
952
|
+
status,
|
|
953
|
+
failures: strip(failures),
|
|
954
|
+
unsupported,
|
|
955
|
+
warnings
|
|
956
|
+
})) : void 0
|
|
957
|
+
});
|
|
958
|
+
return result(report);
|
|
959
|
+
} catch (error) {
|
|
960
|
+
return asCliError(error, "Reference check failed.");
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
//#endregion
|
|
966
|
+
//#region src/references/reference-create.command.ts
|
|
967
|
+
function createCommand(layersFor) {
|
|
968
|
+
return command({
|
|
969
|
+
name: "create",
|
|
970
|
+
description: "Start a contribution in the repo or user tier, a folder's project section, or a plugin's or a skill's source folder, from a template or an interface. Never overwrites a file with a body; writes a file a contribution above hides, with a warning.",
|
|
971
|
+
arguments: [{
|
|
972
|
+
name: "name",
|
|
973
|
+
description: "Reference name, without the `.md` extension.",
|
|
974
|
+
type: t.string()
|
|
975
|
+
}],
|
|
976
|
+
options: {
|
|
977
|
+
root: rootOption,
|
|
978
|
+
scope: scopeOption,
|
|
979
|
+
in: inOption,
|
|
980
|
+
plugin: pluginOption,
|
|
981
|
+
default: skillOption,
|
|
982
|
+
merge: {
|
|
983
|
+
description: "How it applies over what is below: replace, patch (default), combine, or remove. remove writes no body.",
|
|
984
|
+
type: t.string().optional()
|
|
985
|
+
},
|
|
986
|
+
template: {
|
|
987
|
+
description: "File whose text is written as it is, frontmatter included. Defaults to a built-in template of top-level `##` sections.",
|
|
988
|
+
type: t.string().optional()
|
|
989
|
+
},
|
|
990
|
+
implements: {
|
|
991
|
+
description: "Interface id to implement: the interface's sections become `##` headings, each stubbed `**Unsupported.**` with what it holds, and `merge: replace`; with --in, a project section with neither. Not with --template.",
|
|
992
|
+
type: t.string().optional()
|
|
993
|
+
},
|
|
994
|
+
"dry-run": {
|
|
995
|
+
description: "Print the target path and the exact content, and write nothing.",
|
|
996
|
+
type: t.boolean().optional()
|
|
997
|
+
},
|
|
998
|
+
trace: {
|
|
999
|
+
...traceOption,
|
|
1000
|
+
description: "After the write, add the trace, in which the new file applies."
|
|
1001
|
+
}
|
|
1002
|
+
},
|
|
1003
|
+
async run(args) {
|
|
1004
|
+
const format = this.format;
|
|
1005
|
+
try {
|
|
1006
|
+
const home = homedir();
|
|
1007
|
+
const display = (path) => collapseHome(home, path);
|
|
1008
|
+
const request = {
|
|
1009
|
+
root: givenRoot(args.root),
|
|
1010
|
+
home,
|
|
1011
|
+
name: args.name,
|
|
1012
|
+
scope: args.scope,
|
|
1013
|
+
in: args.in,
|
|
1014
|
+
plugin: args.plugin,
|
|
1015
|
+
default: args.default,
|
|
1016
|
+
merge: args.merge,
|
|
1017
|
+
template: args.template,
|
|
1018
|
+
implements: args.implements,
|
|
1019
|
+
dryRun: args["dry-run"]
|
|
1020
|
+
};
|
|
1021
|
+
validateWrite({
|
|
1022
|
+
command: "create",
|
|
1023
|
+
...request
|
|
1024
|
+
});
|
|
1025
|
+
const layers = await layersFor({
|
|
1026
|
+
root: args.root,
|
|
1027
|
+
default: args.default
|
|
1028
|
+
}, home);
|
|
1029
|
+
const created = createContribution(layers, {
|
|
1030
|
+
...request,
|
|
1031
|
+
root: layers.root ?? request.root
|
|
1032
|
+
}, { display });
|
|
1033
|
+
const report = compact({
|
|
1034
|
+
path: display(created.path),
|
|
1035
|
+
folder: created.folder,
|
|
1036
|
+
hint: created.dryRun ? "Nothing was written. Run again without --dry-run to write it." : `Run \`load ${created.name}\` for the composed document.`,
|
|
1037
|
+
content: created.dryRun ? created.content : void 0,
|
|
1038
|
+
hints: created.hints,
|
|
1039
|
+
warnings: created.warnings,
|
|
1040
|
+
trace: args.trace && created.after ? displayTrace(created.after.trace, home) : void 0
|
|
1041
|
+
});
|
|
1042
|
+
if (format !== "text") return result(report);
|
|
1043
|
+
writeWarnings(created.warnings);
|
|
1044
|
+
for (const hint of created.hints) process.stderr.write(`hint: ${hint}\n`);
|
|
1045
|
+
if (args.trace && created.after) process.stderr.write(traceText(created.after, home));
|
|
1046
|
+
return document(created.dryRun ? `${report.path}\n\n${created.content}` : `${report.path}\n\n${report.hint}\n`);
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
return asCliError(error, "Reference creation failed.");
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
//#endregion
|
|
1054
|
+
//#region src/references/reference-customize.command.ts
|
|
1055
|
+
function baseReport(customized) {
|
|
1056
|
+
const base = customized.base;
|
|
1057
|
+
const resolved = base?.resolved;
|
|
1058
|
+
return compact({
|
|
1059
|
+
status: resolved?.status ?? "missing",
|
|
1060
|
+
stack: resolved?.trace.stack,
|
|
1061
|
+
content: base?.content ?? "",
|
|
1062
|
+
projectSections: base?.projectSections.map(({ folder, content }) => ({
|
|
1063
|
+
folder,
|
|
1064
|
+
content
|
|
1065
|
+
}))
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
function baseText(customized) {
|
|
1069
|
+
const base = customized.base;
|
|
1070
|
+
if (!base) return "";
|
|
1071
|
+
if (!base.content && !base.projectSections.length && base.resolved.status !== "found") return `${notFoundTag(base.resolved)}\n`;
|
|
1072
|
+
return deliveredText({
|
|
1073
|
+
...base.resolved,
|
|
1074
|
+
content: base.content,
|
|
1075
|
+
projectSections: base.projectSections
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
function report(customized, args, home) {
|
|
1079
|
+
const display = (path) => collapseHome(home, path);
|
|
1080
|
+
const after = customized.after;
|
|
1081
|
+
return compact({
|
|
1082
|
+
path: display(customized.path),
|
|
1083
|
+
folder: customized.folder,
|
|
1084
|
+
metadataFile: customized.metadataFile === void 0 ? void 0 : display(customized.metadataFile),
|
|
1085
|
+
code: customized.code,
|
|
1086
|
+
message: customized.message,
|
|
1087
|
+
extends: customized.extends,
|
|
1088
|
+
deleted: customized.deleted,
|
|
1089
|
+
status: after?.status,
|
|
1090
|
+
stack: after?.trace.stack,
|
|
1091
|
+
answer: after ? describeAnswer(after, display) : void 0,
|
|
1092
|
+
hint: customized.dryRun ? "Nothing was written. Run again without --dry-run to write it." : customized.code ? void 0 : `Run \`load ${customized.name}\` for the composed document.`,
|
|
1093
|
+
content: customized.dryRun ? customized.content : void 0,
|
|
1094
|
+
metadataContent: customized.dryRun ? customized.metadataContent : void 0,
|
|
1095
|
+
hints: customized.hints,
|
|
1096
|
+
warnings: customized.warnings,
|
|
1097
|
+
trace: args.trace && after && !customized.code ? displayTrace(after.trace, home) : void 0
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
function text(customized, shown) {
|
|
1101
|
+
if (customized.code) return `${shown.path}\n\n${customized.code}: ${customized.message}\n`;
|
|
1102
|
+
if (customized.dryRun) {
|
|
1103
|
+
const head = customized.deleted ? "would delete the file, left with nothing in it.\n" : customized.content ?? "";
|
|
1104
|
+
const meta = customized.metadataContent === void 0 ? "" : `\n${shown.metadataFile}\n\n${customized.metadataContent}`;
|
|
1105
|
+
return `${shown.path}\n\n${head}${meta}`;
|
|
1106
|
+
}
|
|
1107
|
+
if (customized.action === "cleared") {
|
|
1108
|
+
const what = customized.deleted ? "selection removed; the file was left empty and deleted" : "selection removed";
|
|
1109
|
+
return `${shown.path}\n\n${what}. ${shown.answer}\n`;
|
|
1110
|
+
}
|
|
1111
|
+
if (customized.action === "selection") return `${shown.path}\n\nextends: ${JSON.stringify(customized.extends)}. ${shown.answer}\n`;
|
|
1112
|
+
const meta = shown.metadataFile ? ` Its record is in ${shown.metadataFile}.` : "";
|
|
1113
|
+
return `${shown.path}\n\n${shown.hint}${meta}\n`;
|
|
1114
|
+
}
|
|
1115
|
+
function customizeCommand(layersFor) {
|
|
1116
|
+
return command({
|
|
1117
|
+
name: "customize",
|
|
1118
|
+
description: "Customize a name for the repo or user tier, or a folder's project section, running no model: pick what it builds on (`extends`), write a body generated from a request with its record, or print what the generator reads (--base). Writes a file a contribution above hides, with a warning.",
|
|
1119
|
+
arguments: [{
|
|
1120
|
+
name: "name",
|
|
1121
|
+
description: "Reference name, without the `.md` extension.",
|
|
1122
|
+
type: t.string()
|
|
1123
|
+
}, {
|
|
1124
|
+
name: "steps",
|
|
1125
|
+
description: "More steps for --extends, so `--extends a b` lists both, bottom first.",
|
|
1126
|
+
type: t.array(t.string()).optional()
|
|
1127
|
+
}],
|
|
1128
|
+
options: {
|
|
1129
|
+
root: rootOption,
|
|
1130
|
+
scope: scopeOption,
|
|
1131
|
+
in: {
|
|
1132
|
+
...inOption,
|
|
1133
|
+
description: `${inOption.description} Takes no --extends or --merge.`
|
|
1134
|
+
},
|
|
1135
|
+
default: {
|
|
1136
|
+
description: "The calling skill's folder, read only: its `references/<name>.default.md` joins the stack as the `default` tier.",
|
|
1137
|
+
type: t.string().optional()
|
|
1138
|
+
},
|
|
1139
|
+
plugin: {
|
|
1140
|
+
description: "Not accepted: customize never writes a plugin's file; an author uses `create --plugin`.",
|
|
1141
|
+
type: t.string().optional()
|
|
1142
|
+
},
|
|
1143
|
+
extends: {
|
|
1144
|
+
description: "The picks, bottom first: each a plugin holding the name, `<plugin>:<merge>` (such as `house-style:combine`), or `default`; `none` removes them. A file with no body is a selection.",
|
|
1145
|
+
type: t.array(t.string()).optional()
|
|
1146
|
+
},
|
|
1147
|
+
"extends-file": {
|
|
1148
|
+
description: "A YAML file holding the whole `extends` list, with every step key (`merge`, `extends`, `sections`).",
|
|
1149
|
+
type: t.string().optional()
|
|
1150
|
+
},
|
|
1151
|
+
request: {
|
|
1152
|
+
description: "A file holding the customizer's request, in their own words; recorded as `request`. Needs --body.",
|
|
1153
|
+
type: t.string().optional()
|
|
1154
|
+
},
|
|
1155
|
+
body: {
|
|
1156
|
+
description: "A file holding the body the generator wrote; written as given, with the picks, sources, and request recorded. Needs --request.",
|
|
1157
|
+
type: t.string().optional()
|
|
1158
|
+
},
|
|
1159
|
+
merge: {
|
|
1160
|
+
description: "How the body applies over what is below: patch (default), combine, or replace for a whole document.",
|
|
1161
|
+
type: t.string().optional()
|
|
1162
|
+
},
|
|
1163
|
+
implements: {
|
|
1164
|
+
description: "Interface id the body implements: a whole document recording `implements` and `merge: replace`; with --in, a project section from the interface's stub.",
|
|
1165
|
+
type: t.string().optional()
|
|
1166
|
+
},
|
|
1167
|
+
base: {
|
|
1168
|
+
description: "Print what the generator reads for the target, and write nothing: the name resolved with the picks, leaving out the target file, managed text, the tier above, and personal text; with --implements, the interface's stub first.",
|
|
1169
|
+
type: t.boolean().optional()
|
|
1170
|
+
},
|
|
1171
|
+
"dry-run": {
|
|
1172
|
+
description: "Print the target path and the exact content, and write nothing.",
|
|
1173
|
+
type: t.boolean().optional()
|
|
1174
|
+
},
|
|
1175
|
+
trace: {
|
|
1176
|
+
...traceOption,
|
|
1177
|
+
description: "After the write, add the trace, in which the written file applies."
|
|
1178
|
+
}
|
|
1179
|
+
},
|
|
1180
|
+
async run(args) {
|
|
1181
|
+
const format = this.format;
|
|
1182
|
+
try {
|
|
1183
|
+
const home = homedir();
|
|
1184
|
+
const display = (path) => collapseHome(home, path);
|
|
1185
|
+
const picks = args.extends === void 0 ? void 0 : [...args.extends, ...args.steps ?? []];
|
|
1186
|
+
const request = {
|
|
1187
|
+
root: givenRoot(args.root),
|
|
1188
|
+
home,
|
|
1189
|
+
name: args.name,
|
|
1190
|
+
scope: args.scope,
|
|
1191
|
+
in: args.in,
|
|
1192
|
+
plugin: args.plugin,
|
|
1193
|
+
default: args.default,
|
|
1194
|
+
extends: picks,
|
|
1195
|
+
extendsFile: args["extends-file"],
|
|
1196
|
+
request: args.request,
|
|
1197
|
+
body: args.body,
|
|
1198
|
+
merge: args.merge,
|
|
1199
|
+
implements: args.implements,
|
|
1200
|
+
base: args.base,
|
|
1201
|
+
dryRun: args["dry-run"]
|
|
1202
|
+
};
|
|
1203
|
+
validateWrite({
|
|
1204
|
+
command: "customize",
|
|
1205
|
+
...request
|
|
1206
|
+
});
|
|
1207
|
+
if (args.steps?.length && args.extends === void 0) throw new ReferenceFailure("options-missing", `${args.steps.join(" ")} follow no --extends; pass --extends before the steps.`);
|
|
1208
|
+
const layers = await layersFor({
|
|
1209
|
+
root: args.root,
|
|
1210
|
+
default: args.default
|
|
1211
|
+
}, home);
|
|
1212
|
+
const customized = customizeContribution(layers, {
|
|
1213
|
+
...request,
|
|
1214
|
+
root: layers.root ?? request.root
|
|
1215
|
+
}, { display });
|
|
1216
|
+
if (customized.action === "base") {
|
|
1217
|
+
if (format !== "text") return result(baseReport(customized));
|
|
1218
|
+
return document(baseText(customized));
|
|
1219
|
+
}
|
|
1220
|
+
const shown = report(customized, args, home);
|
|
1221
|
+
if (format !== "text") return result(shown);
|
|
1222
|
+
writeWarnings(customized.warnings);
|
|
1223
|
+
for (const hint of customized.hints) process.stderr.write(`hint: ${hint}\n`);
|
|
1224
|
+
if (args.trace && customized.after && !customized.code) process.stderr.write(traceText(customized.after, home));
|
|
1225
|
+
return document(text(customized, shown));
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
return asCliError(error, "Reference customization failed.");
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
//#endregion
|
|
1233
|
+
//#region src/references/reference-delete.command.ts
|
|
1234
|
+
function deleteReport(deleted, home) {
|
|
1235
|
+
const selection = deleted.selection ? `the selection extends: [${deleted.selection.join(", ")}] went with it; run \`reference customize ${deleted.name} --extends ${deleted.selection.join(" ")}\` to restore it` : void 0;
|
|
1236
|
+
return compact({
|
|
1237
|
+
path: collapseHome(home, deleted.path),
|
|
1238
|
+
folder: deleted.folder,
|
|
1239
|
+
metadataFile: deleted.metadataFile === void 0 ? void 0 : collapseHome(home, deleted.metadataFile),
|
|
1240
|
+
code: deleted.code,
|
|
1241
|
+
message: deleted.message,
|
|
1242
|
+
dryRun: deleted.dryRun || void 0,
|
|
1243
|
+
status: deleted.after.status,
|
|
1244
|
+
stack: deleted.after.trace.stack,
|
|
1245
|
+
answer: describeAnswer(deleted.after, (path) => collapseHome(home, path)),
|
|
1246
|
+
selection,
|
|
1247
|
+
options: deleted.after.options
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
function deleteCommand(layersFor) {
|
|
1251
|
+
return command({
|
|
1252
|
+
name: "delete",
|
|
1253
|
+
description: "Delete a repo or user contribution, a folder's project section, or one in a plugin's or a skill's source folder, with a non-Markdown file's metadata file, and report what answers the name afterwards. Never deletes an installed or managed one.",
|
|
1254
|
+
arguments: [{
|
|
1255
|
+
name: "name",
|
|
1256
|
+
description: "Reference name, without the `.md` extension.",
|
|
1257
|
+
type: t.string()
|
|
1258
|
+
}],
|
|
1259
|
+
options: {
|
|
1260
|
+
root: rootOption,
|
|
1261
|
+
scope: {
|
|
1262
|
+
...scopeOption,
|
|
1263
|
+
description: "Tier to delete from: repo (default) or user."
|
|
1264
|
+
},
|
|
1265
|
+
in: inOption,
|
|
1266
|
+
plugin: pluginOption,
|
|
1267
|
+
default: skillOption,
|
|
1268
|
+
"dry-run": {
|
|
1269
|
+
description: "Print the file that would be deleted and what would answer the name then, and delete nothing.",
|
|
1270
|
+
type: t.boolean().optional()
|
|
1271
|
+
}
|
|
1272
|
+
},
|
|
1273
|
+
async run(args) {
|
|
1274
|
+
const format = this.format;
|
|
1275
|
+
try {
|
|
1276
|
+
const home = homedir();
|
|
1277
|
+
const request = {
|
|
1278
|
+
root: givenRoot(args.root),
|
|
1279
|
+
home,
|
|
1280
|
+
name: args.name,
|
|
1281
|
+
scope: args.scope,
|
|
1282
|
+
in: args.in,
|
|
1283
|
+
plugin: args.plugin,
|
|
1284
|
+
default: args.default,
|
|
1285
|
+
dryRun: args["dry-run"]
|
|
1286
|
+
};
|
|
1287
|
+
validateWrite({
|
|
1288
|
+
command: "delete",
|
|
1289
|
+
...request
|
|
1290
|
+
});
|
|
1291
|
+
const layers = await layersFor({
|
|
1292
|
+
root: args.root,
|
|
1293
|
+
default: args.default
|
|
1294
|
+
}, home);
|
|
1295
|
+
const deleted = deleteContribution(layers, {
|
|
1296
|
+
...request,
|
|
1297
|
+
root: layers.root ?? request.root
|
|
1298
|
+
}, { display: (path) => collapseHome(home, path) });
|
|
1299
|
+
const report = deleteReport(deleted, home);
|
|
1300
|
+
if (format !== "text") return result(report);
|
|
1301
|
+
if (deleted.code) return document(`${report.path}\n\n${deleted.code}: ${deleted.message}\n`);
|
|
1302
|
+
const selection = report.selection ? ` Also, ${report.selection}.` : "";
|
|
1303
|
+
const metadata = report.metadataFile ? ` Its metadata file ${report.metadataFile} went with it.` : "";
|
|
1304
|
+
return document(`${report.path}\n\n${deleted.dryRun ? "would delete" : "deleted"}. ${report.answer}${metadata}${selection}\n`);
|
|
1305
|
+
} catch (error) {
|
|
1306
|
+
return asCliError(error, "Reference deletion failed.");
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
//#endregion
|
|
1312
|
+
//#region src/references/reference-implementations.command.ts
|
|
1313
|
+
function parseAccept(value) {
|
|
1314
|
+
if (value === void 0) return void 0;
|
|
1315
|
+
const versions = value.split(",").map((part) => part.trim());
|
|
1316
|
+
if (!versions.length || versions.some((part) => !/^[1-9]\d*$/.test(part))) throw new ReferenceFailure("option-invalid", `--accept is a comma-separated list of versions, such as 1,2; got "${value}".`, { details: {
|
|
1317
|
+
option: "--accept",
|
|
1318
|
+
value
|
|
1319
|
+
} });
|
|
1320
|
+
return versions.map(Number);
|
|
1321
|
+
}
|
|
1322
|
+
function implementationsCommand(layersFor) {
|
|
1323
|
+
return command({
|
|
1324
|
+
name: "implementations",
|
|
1325
|
+
description: "List the implementations of an interface a consumer can load, each with its pin, description, and unsupported sections.",
|
|
1326
|
+
arguments: [{
|
|
1327
|
+
name: "id",
|
|
1328
|
+
description: "Interface id, the name of its `<id>.interface` document without the suffix.",
|
|
1329
|
+
type: t.string()
|
|
1330
|
+
}],
|
|
1331
|
+
options: {
|
|
1332
|
+
root: rootOption,
|
|
1333
|
+
accept: {
|
|
1334
|
+
description: "Versions the consumer knows, comma-separated, such as 1,2. Defaults to the interface's `versions`.",
|
|
1335
|
+
type: t.string().optional()
|
|
1336
|
+
},
|
|
1337
|
+
trace: {
|
|
1338
|
+
...traceOption,
|
|
1339
|
+
description: "Add each one's name, tier, and version, the interface's cardinality and accepted versions, and the ones left out with why."
|
|
1340
|
+
}
|
|
1341
|
+
},
|
|
1342
|
+
async run(args) {
|
|
1343
|
+
try {
|
|
1344
|
+
const accept = parseAccept(args.accept);
|
|
1345
|
+
const home = homedir();
|
|
1346
|
+
const found = referenceImplementations(args.id, await layersFor(args, home), {
|
|
1347
|
+
accept,
|
|
1348
|
+
display: (path) => collapseHome(home, path)
|
|
1349
|
+
});
|
|
1350
|
+
const rows = found.implementations.map(({ pin, description, unsupported, name, tier, version }) => compact(args.trace ? {
|
|
1351
|
+
pin,
|
|
1352
|
+
description,
|
|
1353
|
+
unsupported,
|
|
1354
|
+
name,
|
|
1355
|
+
tier,
|
|
1356
|
+
version
|
|
1357
|
+
} : {
|
|
1358
|
+
pin,
|
|
1359
|
+
...tier === "plugin" ? {} : { tier },
|
|
1360
|
+
description,
|
|
1361
|
+
unsupported
|
|
1362
|
+
}));
|
|
1363
|
+
if (!rows.length && !args.trace) return empty(`0 implementations of ${args.id}`, { help: `Run \`reference implementations ${args.id} --trace\` for the ones left out, with why.` });
|
|
1364
|
+
const report = { implementations: rows };
|
|
1365
|
+
if (args.trace) Object.assign(report, compact({
|
|
1366
|
+
interface: found.interface,
|
|
1367
|
+
cardinality: found.cardinality,
|
|
1368
|
+
accept: found.accept,
|
|
1369
|
+
excluded: found.excluded,
|
|
1370
|
+
warnings: found.warnings
|
|
1371
|
+
}));
|
|
1372
|
+
return result(report);
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
return asCliError(error, "Implementation lookup failed.");
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
//#endregion
|
|
1380
|
+
//#region src/references/reference-list.command.ts
|
|
1381
|
+
function listCommand(layersFor) {
|
|
1382
|
+
return command({
|
|
1383
|
+
name: "list",
|
|
1384
|
+
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.",
|
|
1385
|
+
arguments: [{
|
|
1386
|
+
name: "pattern",
|
|
1387
|
+
description: "Exact name pattern, where `*` stands for any run of characters, such as `project-tracker.capabilities.*`.",
|
|
1388
|
+
type: t.string().optional()
|
|
1389
|
+
}],
|
|
1390
|
+
options: {
|
|
1391
|
+
root: rootOption,
|
|
1392
|
+
trace: {
|
|
1393
|
+
...traceOption,
|
|
1394
|
+
description: "Add one row per name per layer holding it, with its outcome."
|
|
1395
|
+
}
|
|
1396
|
+
},
|
|
1397
|
+
async run(args) {
|
|
1398
|
+
try {
|
|
1399
|
+
const home = homedir();
|
|
1400
|
+
const display = (path) => collapseHome(home, path);
|
|
1401
|
+
const listing = listReferences(await layersFor(args, home), {
|
|
1402
|
+
display,
|
|
1403
|
+
pattern: args.pattern
|
|
1404
|
+
});
|
|
1405
|
+
const traced = args.trace && listing.layers.length > 0;
|
|
1406
|
+
if (!listing.references.length && !traced && !listing.warnings.length) return empty(args.pattern ? `0 references match "${args.pattern}"` : "0 references — no layer holds one", { help: [...listing.defaults ? [listing.defaults] : [], args.pattern ? "Run `reference list` with no pattern for every name, or `reference search <query>` by topic." : "Run `reference create <name>` to start one."] });
|
|
1407
|
+
const report = { references: listing.references.map((row) => compact({
|
|
1408
|
+
name: row.name,
|
|
1409
|
+
description: row.description,
|
|
1410
|
+
format: row.format,
|
|
1411
|
+
status: row.status === "found" ? void 0 : row.status,
|
|
1412
|
+
code: row.code,
|
|
1413
|
+
options: row.options
|
|
1414
|
+
})) };
|
|
1415
|
+
if (listing.defaults) report.defaults = listing.defaults;
|
|
1416
|
+
if (args.trace) report.layers = listing.layers.map((row) => compact({
|
|
1417
|
+
...row,
|
|
1418
|
+
path: display(row.path)
|
|
1419
|
+
}));
|
|
1420
|
+
if (listing.warnings.length) report.warnings = listing.warnings;
|
|
1421
|
+
return result(report);
|
|
1422
|
+
} catch (error) {
|
|
1423
|
+
return asCliError(error, "Reference listing failed.");
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
//#endregion
|
|
1429
|
+
//#region src/references/reference-load.command.ts
|
|
1430
|
+
function loadEntry(resolved, home, { metadata, trace }) {
|
|
1431
|
+
if (resolved.status === "found" && !resolved.warnings.length && !resolved.projectSections.length && !metadata && !trace && resolved.format === "markdown") return resolved.content;
|
|
1432
|
+
return compact({
|
|
1433
|
+
name: resolved.name,
|
|
1434
|
+
status: resolved.status,
|
|
1435
|
+
content: resolved.content,
|
|
1436
|
+
projectSections: resolved.projectSections.map(({ folder, plugin, content }) => plugin === void 0 ? {
|
|
1437
|
+
folder,
|
|
1438
|
+
content
|
|
1439
|
+
} : {
|
|
1440
|
+
folder,
|
|
1441
|
+
plugin,
|
|
1442
|
+
content
|
|
1443
|
+
}),
|
|
1444
|
+
format: resolved.format === "markdown" ? void 0 : resolved.format,
|
|
1445
|
+
code: resolved.code,
|
|
1446
|
+
options: resolved.options,
|
|
1447
|
+
metadata: metadata ? resolved.metadata : void 0,
|
|
1448
|
+
warnings: resolved.warnings,
|
|
1449
|
+
trace: trace ? displayTrace(resolved.trace, home) : void 0
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
/** One entry per name asked, as the array itself rather than under a `result` key. */
|
|
1453
|
+
const bareArray = {
|
|
1454
|
+
toon: (entries) => encode(entries, "toon"),
|
|
1455
|
+
json: (entries) => encode(entries, "json")
|
|
1456
|
+
};
|
|
1457
|
+
/** One found name prints alone; several, and any name not `found`, print as `<reference>` tags in the order asked. */
|
|
1458
|
+
function loadText(resolved) {
|
|
1459
|
+
const tag = (entry) => entry.status === "found" ? referenceTag(entry.name, deliveredText(entry)) : notFoundTag(entry);
|
|
1460
|
+
if (resolved.length === 1) {
|
|
1461
|
+
const [only] = resolved;
|
|
1462
|
+
return document(only.status === "found" ? deliveredText(only) : notFoundTag(only));
|
|
1463
|
+
}
|
|
1464
|
+
return document(resolved.map(tag).join("\n\n"));
|
|
1465
|
+
}
|
|
1466
|
+
function loadCommand(layersFor) {
|
|
1467
|
+
return command({
|
|
1468
|
+
name: "load",
|
|
1469
|
+
description: "Print one or more references, each composed from its contributions as their extends and merge declare.",
|
|
1470
|
+
arguments: [{
|
|
1471
|
+
name: "names",
|
|
1472
|
+
description: "Reference names, without the `.md` extension; `<plugin>/<name>` builds that plugin’s own stack.",
|
|
1473
|
+
type: t.array(t.string())
|
|
1474
|
+
}],
|
|
1475
|
+
options: {
|
|
1476
|
+
root: rootOption,
|
|
1477
|
+
default: defaultOption,
|
|
1478
|
+
metadata: {
|
|
1479
|
+
description: "Make every entry a record with its status and composed frontmatter.",
|
|
1480
|
+
type: t.boolean().optional()
|
|
1481
|
+
},
|
|
1482
|
+
trace: traceOption
|
|
1483
|
+
},
|
|
1484
|
+
async run(args) {
|
|
1485
|
+
const format = this.format;
|
|
1486
|
+
try {
|
|
1487
|
+
if (!args.names.length) throw new ReferenceFailure("name-missing", "name at least one reference to load.", { help: "Run `reference load <name>...`, or `reference list` for every name." });
|
|
1488
|
+
for (const name of args.names) requireName(name);
|
|
1489
|
+
const home = homedir();
|
|
1490
|
+
const layers = await layersFor(args, home);
|
|
1491
|
+
const display = (path) => collapseHome(home, path);
|
|
1492
|
+
const lookup = interfaceLookup(layers, { display });
|
|
1493
|
+
const resolved = args.names.map((name) => {
|
|
1494
|
+
const entry = resolveReference(parseReferenceName(name), layers, { display });
|
|
1495
|
+
const checked = checkResolved(entry, lookup);
|
|
1496
|
+
entry.warnings.push(...checked.warnings);
|
|
1497
|
+
for (const failure of checked.failures) {
|
|
1498
|
+
if (failure.check === "compose") continue;
|
|
1499
|
+
entry.warnings.push({
|
|
1500
|
+
code: "interface-check",
|
|
1501
|
+
message: `it fails its interface check (${failure.check}): ${failure.message}`
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
return entry;
|
|
1505
|
+
});
|
|
1506
|
+
if (format === "text") {
|
|
1507
|
+
for (const entry of resolved) {
|
|
1508
|
+
writeWarnings(entry.warnings);
|
|
1509
|
+
if (args.trace) process.stderr.write(traceText(entry, home));
|
|
1510
|
+
}
|
|
1511
|
+
return loadText(resolved);
|
|
1512
|
+
}
|
|
1513
|
+
const flags = {
|
|
1514
|
+
metadata: Boolean(args.metadata),
|
|
1515
|
+
trace: Boolean(args.trace)
|
|
1516
|
+
};
|
|
1517
|
+
return result(resolved.map((entry) => loadEntry(entry, home, flags)), { render: bareArray });
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
return asCliError(error, "Reference lookup failed.");
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
//#endregion
|
|
1525
|
+
//#region src/references/reference-search.command.ts
|
|
1526
|
+
function searchCommand(layersFor) {
|
|
1527
|
+
return command({
|
|
1528
|
+
name: "search",
|
|
1529
|
+
description: "Find references by name, description, tags, headings, or body, best match first.",
|
|
1530
|
+
arguments: [{
|
|
1531
|
+
name: "query",
|
|
1532
|
+
description: "What the reference is about.",
|
|
1533
|
+
type: t.string()
|
|
1534
|
+
}],
|
|
1535
|
+
options: {
|
|
1536
|
+
root: rootOption,
|
|
1537
|
+
trace: {
|
|
1538
|
+
...traceOption,
|
|
1539
|
+
description: "Add the tier and plugin or skill of each match."
|
|
1540
|
+
}
|
|
1541
|
+
},
|
|
1542
|
+
async run(args) {
|
|
1543
|
+
const query = args.query.trim();
|
|
1544
|
+
try {
|
|
1545
|
+
const home = homedir();
|
|
1546
|
+
const { matches, defaults } = searchReferences(query, await layersFor(args, home), { display: (path) => collapseHome(home, path) });
|
|
1547
|
+
if (!matches.length) return empty(`0 references match "${query}"`, { help: [...defaults ? [defaults] : [], "Try other words, or run `reference list` for every name."] });
|
|
1548
|
+
const report = compact({
|
|
1549
|
+
references: matches.map(({ name, description, match, folder, tier, plugin, skill, loadedBy }) => compact(args.trace ? {
|
|
1550
|
+
name,
|
|
1551
|
+
description,
|
|
1552
|
+
match,
|
|
1553
|
+
folder,
|
|
1554
|
+
loadedBy,
|
|
1555
|
+
tier,
|
|
1556
|
+
plugin,
|
|
1557
|
+
skill
|
|
1558
|
+
} : {
|
|
1559
|
+
name,
|
|
1560
|
+
description,
|
|
1561
|
+
match,
|
|
1562
|
+
folder,
|
|
1563
|
+
loadedBy
|
|
1564
|
+
})),
|
|
1565
|
+
defaults
|
|
1566
|
+
});
|
|
1567
|
+
return result(report);
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
return asCliError(error, "Reference search failed.");
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
//#endregion
|
|
1575
|
+
//#region src/references/reference-where.command.ts
|
|
1576
|
+
function whereCommand(layersFor) {
|
|
1577
|
+
return command({
|
|
1578
|
+
name: "where",
|
|
1579
|
+
description: "Give the repo and user files, and with --in a folder's project section, a customization or a selection of a reference is written to, who each reaches and what hides it, the main document's heading paths, and the read-only plugins and default the stack uses.",
|
|
1580
|
+
arguments: [{
|
|
1581
|
+
name: "name",
|
|
1582
|
+
description: "Reference name, without the `.md` extension.",
|
|
1583
|
+
type: t.string()
|
|
1584
|
+
}],
|
|
1585
|
+
options: {
|
|
1586
|
+
root: rootOption,
|
|
1587
|
+
default: defaultOption,
|
|
1588
|
+
in: {
|
|
1589
|
+
...inOption,
|
|
1590
|
+
description: "A folder strictly below the root: also give its project section's file."
|
|
1591
|
+
},
|
|
1592
|
+
trace: {
|
|
1593
|
+
...traceOption,
|
|
1594
|
+
description: "Add every layer checked, with its outcome."
|
|
1595
|
+
}
|
|
1596
|
+
},
|
|
1597
|
+
async run(args) {
|
|
1598
|
+
const format = this.format;
|
|
1599
|
+
try {
|
|
1600
|
+
const home = homedir();
|
|
1601
|
+
const display = (path) => collapseHome(home, path);
|
|
1602
|
+
const layers = await layersFor(args, home);
|
|
1603
|
+
const report = whereReference(args.name, layers, {
|
|
1604
|
+
root: layers.root ?? givenRoot(args.root),
|
|
1605
|
+
display,
|
|
1606
|
+
in: args.in
|
|
1607
|
+
});
|
|
1608
|
+
const output = compact({
|
|
1609
|
+
name: report.name,
|
|
1610
|
+
status: report.status,
|
|
1611
|
+
files: report.files,
|
|
1612
|
+
headings: report.headings,
|
|
1613
|
+
used: report.used,
|
|
1614
|
+
options: report.options,
|
|
1615
|
+
hint: report.used.length ? `${report.used.join(", ")} ${report.used.length === 1 ? "is" : "are"} read-only; a customization goes in one of the files.` : void 0,
|
|
1616
|
+
layers: args.trace ? report.layers.map((layer) => ({
|
|
1617
|
+
...layer,
|
|
1618
|
+
path: display(layer.path)
|
|
1619
|
+
})) : void 0,
|
|
1620
|
+
warnings: report.warnings
|
|
1621
|
+
});
|
|
1622
|
+
if (format === "text") writeWarnings(report.warnings);
|
|
1623
|
+
return result(output);
|
|
1624
|
+
} catch (error) {
|
|
1625
|
+
return asCliError(error, "Reference placement lookup failed.");
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
//#endregion
|
|
1631
|
+
//#region src/references/reference.command.ts
|
|
1632
|
+
/** A factory, so each host CLI names itself as the plugin whose own references come first. */
|
|
1633
|
+
function createReferenceCommands({ plugin } = {}) {
|
|
1634
|
+
const layersFor = layerSource(plugin);
|
|
1635
|
+
return {
|
|
1636
|
+
load: loadCommand(layersFor),
|
|
1637
|
+
list: listCommand(layersFor),
|
|
1638
|
+
search: searchCommand(layersFor),
|
|
1639
|
+
where: whereCommand(layersFor),
|
|
1640
|
+
create: createCommand(layersFor),
|
|
1641
|
+
customize: customizeCommand(layersFor),
|
|
1642
|
+
delete: deleteCommand(layersFor),
|
|
1643
|
+
check: checkCommand(layersFor),
|
|
1644
|
+
implementations: implementationsCommand(layersFor)
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
function createReferenceCommand(options = {}) {
|
|
1648
|
+
const { load, list, search, where, create, customize, delete: remove, check, implementations } = createReferenceCommands(options);
|
|
1649
|
+
return command({
|
|
1650
|
+
name: "reference",
|
|
1651
|
+
description: "Read reference documents by name, composed from the managed, repo, user, plugin, and default tiers, and create, customize, or delete a contribution.",
|
|
1652
|
+
commands: [
|
|
1653
|
+
load,
|
|
1654
|
+
list,
|
|
1655
|
+
search,
|
|
1656
|
+
where,
|
|
1657
|
+
create,
|
|
1658
|
+
customize,
|
|
1659
|
+
remove,
|
|
1660
|
+
implementations,
|
|
1661
|
+
check
|
|
1662
|
+
]
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
//#endregion
|
|
1405
1666
|
export { createAssetCommand, createReferenceCommand, createReferenceCommands };
|