agents.yaml 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/README.md +25 -9
- package/dist/index.mjs +134 -78
- package/package.json +11 -10
- package/src/cli.ts +140 -0
- package/src/index.ts +1 -1
- package/src/run.test.ts +226 -0
- package/src/run.ts +41 -100
- package/dist/index.d.mts +0 -1
package/AGENTS.md
CHANGED
|
@@ -21,3 +21,7 @@ The CLI can help discover package and local `AGENTS.md` files, add selected path
|
|
|
21
21
|
Discovery only considers direct dependencies under a project's `node_modules`; nested dependency `AGENTS.md` files are not automatically activated.
|
|
22
22
|
|
|
23
23
|
Discovery skips dot-prefixed directories by default. Use `agents discover --include-dot-directories` when hidden project directories should be scanned too.
|
|
24
|
+
|
|
25
|
+
The CLI uses Comline for command definitions, options, variadic path arguments, help, and shell completion. Clack handles the interactive flow. Pass full runtime argv to `run`; Comline removes the runtime and entry-point arguments.
|
|
26
|
+
|
|
27
|
+
`add` and `remove` require one or more paths. Use `--` before dash-prefixed paths. Unknown or misplaced options fail before command execution. Both text and JSON validation set a nonzero exit status when the index is invalid.
|
package/README.md
CHANGED
|
@@ -19,14 +19,35 @@ agents init
|
|
|
19
19
|
agents discover
|
|
20
20
|
agents discover --include-dot-directories
|
|
21
21
|
agents add ./node_modules/react/AGENTS.md
|
|
22
|
+
agents add ./react/AGENTS.md "./my package/AGENTS.md"
|
|
23
|
+
agents remove ./react/AGENTS.md "./my package/AGENTS.md"
|
|
22
24
|
agents validate
|
|
25
|
+
agents validate --json
|
|
26
|
+
agents --help
|
|
27
|
+
agents --version
|
|
23
28
|
```
|
|
24
29
|
|
|
25
30
|
Run `agents` with no command for the interactive flow.
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
`add` and `remove` accept one or more paths. Use `--` before paths that start with a dash. Unknown commands and flags, flags used with the wrong command, and missing required paths produce an error before the command runs.
|
|
33
|
+
|
|
34
|
+
Boolean switches accept `true`, `false`, `1`, and `0`, either with `=` or as the next argument. Repeated switches use the last value. Validation exits with status 1 for an invalid document index, including with `--json`.
|
|
35
|
+
|
|
36
|
+
Discovery skips dot-prefixed directories by default so local caches and tool state do not dominate scan time. Use `--include-dot-directories` when you need to search those directories too.
|
|
37
|
+
|
|
38
|
+
## Shell Completion
|
|
39
|
+
|
|
40
|
+
Generate a completion script with `agents completion <target>`, or install it into your shell's configured completion directory:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
agents completion install bash
|
|
44
|
+
agents completion install zsh
|
|
45
|
+
agents completion install fish
|
|
46
|
+
agents completion install nushell
|
|
47
|
+
agents completion install carapace
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Choose the target you use, then open a new shell. Completion setup requires the target shell's completion system to be enabled; setup errors explain any missing requirements. Commands and flags complete automatically. `add` completes filesystem paths, and `remove` suggests paths listed in `agents.yaml`, including after the first path.
|
|
30
51
|
|
|
31
52
|
## Benchmark
|
|
32
53
|
|
|
@@ -34,12 +55,7 @@ to search those directories too.
|
|
|
34
55
|
pnpm --filter agents.yaml bench
|
|
35
56
|
```
|
|
36
57
|
|
|
37
|
-
The benchmark creates a temporary discovery fixture, compares default discovery
|
|
38
|
-
against `--include-dot-directories`, prints median/min/max timings, and removes
|
|
39
|
-
the fixture when it exits. Fixture size can be tuned with
|
|
40
|
-
`AGENTS_BENCH_HIDDEN_DIRS`, `AGENTS_BENCH_FILES_PER_HIDDEN_DIR`,
|
|
41
|
-
`AGENTS_BENCH_VISIBLE_PACKAGES`, `AGENTS_BENCH_ITERATIONS`, and
|
|
42
|
-
`AGENTS_BENCH_WARMUPS`.
|
|
58
|
+
The benchmark creates a temporary discovery fixture, compares default discovery against `--include-dot-directories`, prints median/min/max timings, and removes the fixture when it exits. Fixture size can be tuned with `AGENTS_BENCH_HIDDEN_DIRS`, `AGENTS_BENCH_FILES_PER_HIDDEN_DIR`, `AGENTS_BENCH_VISIBLE_PACKAGES`, `AGENTS_BENCH_ITERATIONS`, and `AGENTS_BENCH_WARMUPS`.
|
|
43
59
|
|
|
44
60
|
## File Format
|
|
45
61
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { MultiSelectPrompt } from "@clack/core";
|
|
3
3
|
import * as clack from "@clack/prompts";
|
|
4
|
+
import { cli, completionResponse, help, interpretArguments, optional, options, parseBooleanOption, required } from "comline";
|
|
4
5
|
import { styleText } from "node:util";
|
|
5
6
|
import { access, opendir, readFile, writeFile } from "node:fs/promises";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import * as YAML from "yaml";
|
|
8
9
|
import { z } from "zod";
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
9
11
|
//#region src/paths.ts
|
|
10
12
|
function cwd() {
|
|
11
13
|
return process.cwd();
|
|
@@ -147,7 +149,8 @@ async function initProject(root, options) {
|
|
|
147
149
|
messages.push("AGENTS.md already mentions agents.yaml");
|
|
148
150
|
return { messages };
|
|
149
151
|
}
|
|
150
|
-
|
|
152
|
+
const next = source.trimEnd().length === 0 ? `# Project Instructions\n\n${breadcrumb}\n` : `${source.trimEnd()}\n\n${breadcrumb}\n`;
|
|
153
|
+
await writeFile(projectAgentsPath, next, "utf8");
|
|
151
154
|
messages.push("updated AGENTS.md");
|
|
152
155
|
} catch (error) {
|
|
153
156
|
if (!isNotFound(error)) throw error;
|
|
@@ -163,8 +166,104 @@ function isNotFound(error) {
|
|
|
163
166
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
164
167
|
}
|
|
165
168
|
//#endregion
|
|
169
|
+
//#region src/cli.ts
|
|
170
|
+
function parseBooleanSwitch(value) {
|
|
171
|
+
const last = value.split(",").at(-1) ?? "";
|
|
172
|
+
if (![
|
|
173
|
+
"",
|
|
174
|
+
"true",
|
|
175
|
+
"false",
|
|
176
|
+
"0",
|
|
177
|
+
"1"
|
|
178
|
+
].includes(last)) throw new Error(`Expected a boolean switch, received ${JSON.stringify(last)}`);
|
|
179
|
+
return parseBooleanOption(last);
|
|
180
|
+
}
|
|
181
|
+
function booleanSwitch(description, example) {
|
|
182
|
+
return {
|
|
183
|
+
description,
|
|
184
|
+
example,
|
|
185
|
+
parse: parseBooleanSwitch,
|
|
186
|
+
required: false
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const commonSchema = z.object({
|
|
190
|
+
help: z.boolean().default(false),
|
|
191
|
+
version: z.boolean().default(false)
|
|
192
|
+
});
|
|
193
|
+
const commonOptions = {
|
|
194
|
+
help: {
|
|
195
|
+
...booleanSwitch("Show usage", "--help"),
|
|
196
|
+
flag: "h"
|
|
197
|
+
},
|
|
198
|
+
version: {
|
|
199
|
+
...booleanSwitch("Show the installed version", "--version"),
|
|
200
|
+
flag: "v"
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
const jsonOption = booleanSwitch("Print JSON", "--json");
|
|
204
|
+
function common(description) {
|
|
205
|
+
return options(description, commonSchema, commonOptions);
|
|
206
|
+
}
|
|
207
|
+
const agents = cli({
|
|
208
|
+
cliName: "agents",
|
|
209
|
+
cliDescription: "Discover and curate agent-readable documentation in agents.yaml.",
|
|
210
|
+
routes: optional({
|
|
211
|
+
init: null,
|
|
212
|
+
discover: null,
|
|
213
|
+
add: required({ "$...paths": null }),
|
|
214
|
+
remove: required({ "$...paths": null }),
|
|
215
|
+
validate: null,
|
|
216
|
+
help: null,
|
|
217
|
+
version: null
|
|
218
|
+
}),
|
|
219
|
+
routeOptions: {
|
|
220
|
+
"": common("Choose an action interactively"),
|
|
221
|
+
init: options("Initialize breadcrumb files", commonSchema.extend({ force: z.boolean().default(false) }), {
|
|
222
|
+
...commonOptions,
|
|
223
|
+
force: booleanSwitch("Append the breadcrumb even if already mentioned", "--force")
|
|
224
|
+
}),
|
|
225
|
+
discover: options("Discover supplemental AGENTS.md files", commonSchema.extend({
|
|
226
|
+
json: z.boolean().default(false),
|
|
227
|
+
"include-dot-directories": z.boolean().default(false)
|
|
228
|
+
}), {
|
|
229
|
+
...commonOptions,
|
|
230
|
+
json: jsonOption,
|
|
231
|
+
"include-dot-directories": booleanSwitch("Search hidden directories", "--include-dot-directories")
|
|
232
|
+
}),
|
|
233
|
+
"add/$...paths": common("Promote one or more AGENTS.md files"),
|
|
234
|
+
"remove/$...paths": common("Remove one or more promoted paths"),
|
|
235
|
+
validate: options("Validate agents.yaml", commonSchema.extend({ json: z.boolean().default(false) }), {
|
|
236
|
+
...commonOptions,
|
|
237
|
+
json: jsonOption
|
|
238
|
+
}),
|
|
239
|
+
help: common("Show usage"),
|
|
240
|
+
version: common("Show the installed version")
|
|
241
|
+
},
|
|
242
|
+
positionalCompletions: {
|
|
243
|
+
"add/$...paths": { fileSystem: "files" },
|
|
244
|
+
"remove/$...paths": { provide: async () => {
|
|
245
|
+
const { documents } = await loadAgentsFile(cwd());
|
|
246
|
+
return documents.map(({ path, description }) => ({
|
|
247
|
+
value: path,
|
|
248
|
+
...description ? { description } : {}
|
|
249
|
+
}));
|
|
250
|
+
} }
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
function earlyCommand(argv) {
|
|
254
|
+
const { options: occurrences } = interpretArguments(agents.definition, argv.slice(2));
|
|
255
|
+
for (const key of ["help", "version"]) {
|
|
256
|
+
const occurrence = occurrences.findLast((option) => option.key === key);
|
|
257
|
+
if (occurrence && parseBooleanSwitch(occurrence.value)) return key;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function packageVersion() {
|
|
261
|
+
const source = readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
262
|
+
return z.object({ version: z.string() }).parse(JSON.parse(source)).version;
|
|
263
|
+
}
|
|
264
|
+
//#endregion
|
|
166
265
|
//#region src/discover.ts
|
|
167
|
-
const skippedDirectories = new Set([
|
|
266
|
+
const skippedDirectories = /* @__PURE__ */ new Set([
|
|
168
267
|
".git",
|
|
169
268
|
".hg",
|
|
170
269
|
".svn",
|
|
@@ -262,98 +361,54 @@ function isPackageJson(value) {
|
|
|
262
361
|
}
|
|
263
362
|
//#endregion
|
|
264
363
|
//#region src/run.ts
|
|
265
|
-
const helpText = `agents
|
|
266
|
-
|
|
267
|
-
Usage:
|
|
268
|
-
agents
|
|
269
|
-
agents init [--force]
|
|
270
|
-
agents discover [--json] [--include-dot-directories]
|
|
271
|
-
agents add <path...>
|
|
272
|
-
agents remove <path...>
|
|
273
|
-
agents validate [--json]
|
|
274
|
-
|
|
275
|
-
agents.yaml is a curated table of contents for promoted AGENTS.md guidance.`;
|
|
276
364
|
async function run(argv) {
|
|
277
|
-
const
|
|
365
|
+
const completion = await completionResponse(agents.definition, argv);
|
|
366
|
+
if (completion !== void 0) {
|
|
367
|
+
process.stdout.write(completion);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const early = earlyCommand(argv);
|
|
371
|
+
if (early === "help") {
|
|
372
|
+
console.log(help(agents.definition));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (early === "version") {
|
|
376
|
+
console.log(packageVersion());
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const { inputs, warnings } = agents(argv);
|
|
380
|
+
if (warnings.length > 0) throw new Error(warnings.map((warning) => warning.message).join("\n"));
|
|
278
381
|
const root = cwd();
|
|
279
|
-
switch (
|
|
280
|
-
case
|
|
382
|
+
switch (inputs.case) {
|
|
383
|
+
case "":
|
|
281
384
|
await interactive(root);
|
|
282
385
|
return;
|
|
283
386
|
case "help":
|
|
284
|
-
console.log(
|
|
387
|
+
console.log(help(agents.definition));
|
|
285
388
|
return;
|
|
286
389
|
case "version":
|
|
287
|
-
console.log(
|
|
390
|
+
console.log(packageVersion());
|
|
288
391
|
return;
|
|
289
392
|
case "init":
|
|
290
|
-
await commandInit(root,
|
|
393
|
+
await commandInit(root, inputs.opts.force);
|
|
291
394
|
return;
|
|
292
395
|
case "discover":
|
|
293
396
|
await commandDiscover(root, {
|
|
294
|
-
json:
|
|
295
|
-
includeDotDirectories:
|
|
397
|
+
json: inputs.opts.json,
|
|
398
|
+
includeDotDirectories: inputs.opts["include-dot-directories"]
|
|
296
399
|
});
|
|
297
400
|
return;
|
|
298
|
-
case "add":
|
|
299
|
-
await commandAdd(root,
|
|
401
|
+
case "add/$...paths":
|
|
402
|
+
await commandAdd(root, inputs.params.paths);
|
|
300
403
|
return;
|
|
301
|
-
case "remove":
|
|
302
|
-
await commandRemove(root,
|
|
404
|
+
case "remove/$...paths":
|
|
405
|
+
await commandRemove(root, inputs.params.paths);
|
|
303
406
|
return;
|
|
304
407
|
case "validate":
|
|
305
|
-
await commandValidate(root,
|
|
408
|
+
await commandValidate(root, inputs.opts.json);
|
|
306
409
|
return;
|
|
307
410
|
}
|
|
308
411
|
}
|
|
309
|
-
function parseArgs(argv) {
|
|
310
|
-
const flags = /* @__PURE__ */ new Map();
|
|
311
|
-
const values = [];
|
|
312
|
-
let command;
|
|
313
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
314
|
-
const arg = argv[index];
|
|
315
|
-
if (!arg) continue;
|
|
316
|
-
if (arg === "--help" || arg === "-h") {
|
|
317
|
-
command = "help";
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
if (arg === "--version" || arg === "-v") {
|
|
321
|
-
command = "version";
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
if (arg.startsWith("--")) {
|
|
325
|
-
const [rawName, inlineValue] = arg.slice(2).split("=", 2);
|
|
326
|
-
if (!rawName) continue;
|
|
327
|
-
if (inlineValue !== void 0) {
|
|
328
|
-
flags.set(rawName, inlineValue);
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
|
-
flags.set(rawName, true);
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
if (!command && isCommand(arg)) {
|
|
335
|
-
command = arg;
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
values.push(arg);
|
|
339
|
-
}
|
|
340
|
-
return {
|
|
341
|
-
command,
|
|
342
|
-
values,
|
|
343
|
-
flags
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
function isCommand(value) {
|
|
347
|
-
return [
|
|
348
|
-
"add",
|
|
349
|
-
"discover",
|
|
350
|
-
"help",
|
|
351
|
-
"init",
|
|
352
|
-
"remove",
|
|
353
|
-
"validate",
|
|
354
|
-
"version"
|
|
355
|
-
].includes(value);
|
|
356
|
-
}
|
|
357
412
|
async function commandInit(root, force) {
|
|
358
413
|
clack.intro("agents init");
|
|
359
414
|
const result = await initProject(root, { force });
|
|
@@ -394,6 +449,7 @@ async function commandRemove(root, paths) {
|
|
|
394
449
|
}
|
|
395
450
|
async function commandValidate(root, json) {
|
|
396
451
|
const result = await validateAgentsFile(root);
|
|
452
|
+
if (!result.ok) process.exitCode = 1;
|
|
397
453
|
if (json) {
|
|
398
454
|
console.log(JSON.stringify(result, null, 2));
|
|
399
455
|
return;
|
|
@@ -402,7 +458,6 @@ async function commandValidate(root, json) {
|
|
|
402
458
|
if (result.errors.length > 0) clack.note(result.errors.join("\n"), "Errors");
|
|
403
459
|
if (result.warnings.length > 0) clack.note(result.warnings.join("\n"), "Warnings");
|
|
404
460
|
clack.outro(result.ok ? "agents.yaml is valid." : "agents.yaml needs attention.");
|
|
405
|
-
if (!result.ok) process.exitCode = 1;
|
|
406
461
|
}
|
|
407
462
|
async function interactive(root) {
|
|
408
463
|
clack.intro("agents");
|
|
@@ -451,8 +506,8 @@ async function interactive(root) {
|
|
|
451
506
|
}
|
|
452
507
|
await commandAdd(root, selected);
|
|
453
508
|
}
|
|
454
|
-
function chooseDocumentsToEnable(options) {
|
|
455
|
-
|
|
509
|
+
async function chooseDocumentsToEnable(options) {
|
|
510
|
+
const selected = await new MultiSelectPrompt({
|
|
456
511
|
options,
|
|
457
512
|
required: false,
|
|
458
513
|
render() {
|
|
@@ -474,6 +529,7 @@ ${styleText("cyan", clack.S_BAR_END)}
|
|
|
474
529
|
`;
|
|
475
530
|
}
|
|
476
531
|
}).prompt();
|
|
532
|
+
return typeof selected === "symbol" ? clack.CANCEL_SYMBOL : selected;
|
|
477
533
|
}
|
|
478
534
|
function styleDocumentOption(option, state) {
|
|
479
535
|
if (option.disabled) return `${styleText("gray", clack.S_CHECKBOX_INACTIVE)} ${styleText(["strikethrough", "gray"], option.label)}`;
|
|
@@ -482,7 +538,7 @@ function styleDocumentOption(option, state) {
|
|
|
482
538
|
}
|
|
483
539
|
//#endregion
|
|
484
540
|
//#region src/index.ts
|
|
485
|
-
run(process.argv
|
|
541
|
+
run(process.argv).catch((error) => {
|
|
486
542
|
const message = error instanceof Error ? error.message : String(error);
|
|
487
543
|
console.error(`agents: ${message}`);
|
|
488
544
|
process.exitCode = 1;
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents.yaml",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A CLI for discovering and curating agent-readable documentation in agents.yaml.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/jeremybanka/agents.yaml.git"
|
|
8
8
|
},
|
|
9
|
+
"type": "module",
|
|
9
10
|
"bin": {
|
|
10
11
|
"agents": "./dist/index.mjs"
|
|
11
12
|
},
|
|
@@ -15,20 +16,20 @@
|
|
|
15
16
|
"README.md",
|
|
16
17
|
"AGENTS.md"
|
|
17
18
|
],
|
|
18
|
-
"type": "module",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@clack/core": "1.
|
|
21
|
-
"@clack/prompts": "1.
|
|
22
|
-
"
|
|
23
|
-
"
|
|
20
|
+
"@clack/core": "1.5.1",
|
|
21
|
+
"@clack/prompts": "1.8.1",
|
|
22
|
+
"comline": "0.8.1",
|
|
23
|
+
"yaml": "2.9.1",
|
|
24
|
+
"zod": "4.6.5"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
|
-
"@types/node": "
|
|
27
|
-
"typescript": "
|
|
27
|
+
"@types/node": "26.6.1",
|
|
28
|
+
"typescript": "7.0.2"
|
|
28
29
|
},
|
|
29
30
|
"engines": {
|
|
30
|
-
"node": "26.
|
|
31
|
-
"pnpm": "
|
|
31
|
+
"node": "26.9.0",
|
|
32
|
+
"pnpm": "12.4.2"
|
|
32
33
|
},
|
|
33
34
|
"scripts": {
|
|
34
35
|
"bench": "node src/discover.bench.ts",
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cli,
|
|
3
|
+
interpretArguments,
|
|
4
|
+
optional,
|
|
5
|
+
options,
|
|
6
|
+
parseBooleanOption,
|
|
7
|
+
required,
|
|
8
|
+
type CliOption,
|
|
9
|
+
} from "comline"
|
|
10
|
+
import { readFileSync } from "node:fs"
|
|
11
|
+
import { z } from "zod"
|
|
12
|
+
import { loadAgentsFile } from "./agents-file.ts"
|
|
13
|
+
import { cwd } from "./paths.ts"
|
|
14
|
+
|
|
15
|
+
function parseBooleanSwitch(value: string): boolean {
|
|
16
|
+
// Comline joins repeated occurrences with commas. The last switch wins.
|
|
17
|
+
const last = value.split(",").at(-1) ?? ""
|
|
18
|
+
if (!["", "true", "false", "0", "1"].includes(last)) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Expected a boolean switch, received ${JSON.stringify(last)}`,
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
return parseBooleanOption(last)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function booleanSwitch(
|
|
27
|
+
description: string,
|
|
28
|
+
example: string,
|
|
29
|
+
): CliOption<boolean> {
|
|
30
|
+
return { description, example, parse: parseBooleanSwitch, required: false }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const commonSchema = z.object({
|
|
34
|
+
help: z.boolean().default(false),
|
|
35
|
+
version: z.boolean().default(false),
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const commonOptions = {
|
|
39
|
+
help: {
|
|
40
|
+
...booleanSwitch("Show usage", "--help"),
|
|
41
|
+
flag: "h",
|
|
42
|
+
},
|
|
43
|
+
version: {
|
|
44
|
+
...booleanSwitch("Show the installed version", "--version"),
|
|
45
|
+
flag: "v",
|
|
46
|
+
},
|
|
47
|
+
} satisfies Record<string, CliOption<boolean>>
|
|
48
|
+
|
|
49
|
+
const jsonOption = booleanSwitch("Print JSON", "--json")
|
|
50
|
+
|
|
51
|
+
function common(description: string) {
|
|
52
|
+
return options(description, commonSchema, commonOptions)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const agents = cli({
|
|
56
|
+
cliName: "agents",
|
|
57
|
+
cliDescription:
|
|
58
|
+
"Discover and curate agent-readable documentation in agents.yaml.",
|
|
59
|
+
routes: optional({
|
|
60
|
+
init: null,
|
|
61
|
+
discover: null,
|
|
62
|
+
add: required({ "$...paths": null }),
|
|
63
|
+
remove: required({ "$...paths": null }),
|
|
64
|
+
validate: null,
|
|
65
|
+
help: null,
|
|
66
|
+
version: null,
|
|
67
|
+
}),
|
|
68
|
+
routeOptions: {
|
|
69
|
+
"": common("Choose an action interactively"),
|
|
70
|
+
init: options(
|
|
71
|
+
"Initialize breadcrumb files",
|
|
72
|
+
commonSchema.extend({ force: z.boolean().default(false) }),
|
|
73
|
+
{
|
|
74
|
+
...commonOptions,
|
|
75
|
+
force: booleanSwitch(
|
|
76
|
+
"Append the breadcrumb even if already mentioned",
|
|
77
|
+
"--force",
|
|
78
|
+
),
|
|
79
|
+
},
|
|
80
|
+
),
|
|
81
|
+
discover: options(
|
|
82
|
+
"Discover supplemental AGENTS.md files",
|
|
83
|
+
commonSchema.extend({
|
|
84
|
+
json: z.boolean().default(false),
|
|
85
|
+
"include-dot-directories": z.boolean().default(false),
|
|
86
|
+
}),
|
|
87
|
+
{
|
|
88
|
+
...commonOptions,
|
|
89
|
+
json: jsonOption,
|
|
90
|
+
"include-dot-directories": booleanSwitch(
|
|
91
|
+
"Search hidden directories",
|
|
92
|
+
"--include-dot-directories",
|
|
93
|
+
),
|
|
94
|
+
},
|
|
95
|
+
),
|
|
96
|
+
"add/$...paths": common("Promote one or more AGENTS.md files"),
|
|
97
|
+
"remove/$...paths": common("Remove one or more promoted paths"),
|
|
98
|
+
validate: options(
|
|
99
|
+
"Validate agents.yaml",
|
|
100
|
+
commonSchema.extend({ json: z.boolean().default(false) }),
|
|
101
|
+
{ ...commonOptions, json: jsonOption },
|
|
102
|
+
),
|
|
103
|
+
help: common("Show usage"),
|
|
104
|
+
version: common("Show the installed version"),
|
|
105
|
+
},
|
|
106
|
+
positionalCompletions: {
|
|
107
|
+
"add/$...paths": { fileSystem: "files" },
|
|
108
|
+
"remove/$...paths": {
|
|
109
|
+
provide: async () => {
|
|
110
|
+
const { documents } = await loadAgentsFile(cwd())
|
|
111
|
+
return documents.map(({ path, description }) => ({
|
|
112
|
+
value: path,
|
|
113
|
+
...(description ? { description } : {}),
|
|
114
|
+
}))
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
export function earlyCommand(argv: string[]): "help" | "version" | undefined {
|
|
121
|
+
// Interpret before validation so `add --help` does not require a path.
|
|
122
|
+
// Using Comline's occurrences also respects grouped flags and literal `--`.
|
|
123
|
+
const { options: occurrences } = interpretArguments(
|
|
124
|
+
agents.definition,
|
|
125
|
+
argv.slice(2),
|
|
126
|
+
)
|
|
127
|
+
for (const key of ["help", "version"] as const) {
|
|
128
|
+
const occurrence = occurrences.findLast((option) => option.key === key)
|
|
129
|
+
if (occurrence && parseBooleanSwitch(occurrence.value)) return key
|
|
130
|
+
}
|
|
131
|
+
return undefined
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function packageVersion(): string {
|
|
135
|
+
const source = readFileSync(
|
|
136
|
+
new URL("../package.json", import.meta.url),
|
|
137
|
+
"utf8",
|
|
138
|
+
)
|
|
139
|
+
return z.object({ version: z.string() }).parse(JSON.parse(source)).version
|
|
140
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { run } from "./run.ts"
|
|
4
4
|
|
|
5
|
-
run(process.argv
|
|
5
|
+
run(process.argv).catch((error: unknown) => {
|
|
6
6
|
const message = error instanceof Error ? error.message : String(error)
|
|
7
7
|
console.error(`agents: ${message}`)
|
|
8
8
|
process.exitCode = 1
|
package/src/run.test.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import * as clack from "@clack/prompts"
|
|
2
|
+
import { spawnSync } from "node:child_process"
|
|
3
|
+
import {
|
|
4
|
+
mkdir,
|
|
5
|
+
mkdtemp,
|
|
6
|
+
readFile,
|
|
7
|
+
readdir,
|
|
8
|
+
rm,
|
|
9
|
+
writeFile,
|
|
10
|
+
} from "node:fs/promises"
|
|
11
|
+
import { tmpdir } from "node:os"
|
|
12
|
+
import path from "node:path"
|
|
13
|
+
import { fileURLToPath } from "node:url"
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
|
15
|
+
import { loadAgentsFile, saveAgentsFile } from "./agents-file.ts"
|
|
16
|
+
import { agents } from "./cli.ts"
|
|
17
|
+
import { run } from "./run.ts"
|
|
18
|
+
|
|
19
|
+
vi.mock("@clack/prompts", async (importOriginal) => ({
|
|
20
|
+
...(await importOriginal<typeof import("@clack/prompts")>()),
|
|
21
|
+
intro: vi.fn(),
|
|
22
|
+
select: vi.fn(),
|
|
23
|
+
cancel: vi.fn(),
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
const entry = fileURLToPath(new URL("./index.ts", import.meta.url))
|
|
27
|
+
let root: string
|
|
28
|
+
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
root = await mkdtemp(path.join(tmpdir(), "agents-cli-"))
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
afterEach(async () => {
|
|
34
|
+
vi.clearAllMocks()
|
|
35
|
+
await rm(root, { recursive: true, force: true })
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
function invoke(...args: string[]) {
|
|
39
|
+
const result = spawnSync(process.execPath, [entry, ...args], {
|
|
40
|
+
cwd: root,
|
|
41
|
+
encoding: "utf8",
|
|
42
|
+
env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: undefined },
|
|
43
|
+
timeout: 10_000,
|
|
44
|
+
})
|
|
45
|
+
if (result.error) throw result.error
|
|
46
|
+
return result
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe("agents command-line interface", () => {
|
|
50
|
+
it("keeps the no-command interactive flow and cancellation", async () => {
|
|
51
|
+
vi.mocked(clack.select).mockResolvedValueOnce(clack.CANCEL_SYMBOL)
|
|
52
|
+
await run([process.execPath, entry])
|
|
53
|
+
expect(clack.select).toHaveBeenCalledWith(
|
|
54
|
+
expect.objectContaining({ message: "What would you like to do?" }),
|
|
55
|
+
)
|
|
56
|
+
expect(clack.cancel).toHaveBeenCalledWith("Cancelled.")
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it("adds and removes multiple paths without splitting special characters", async () => {
|
|
60
|
+
const paths = ["./space name/AGENTS.md", "./comma,equals=/AGENTS.md"]
|
|
61
|
+
for (const document of paths) {
|
|
62
|
+
await mkdir(path.dirname(path.join(root, document)), { recursive: true })
|
|
63
|
+
await writeFile(path.join(root, document), "# Instructions\n")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
expect(invoke("init").status).toBe(0)
|
|
67
|
+
expect(invoke("add", ...paths).status).toBe(0)
|
|
68
|
+
const added = await loadAgentsFile(root)
|
|
69
|
+
expect(added.documents.map((document) => document.path).sort()).toEqual(
|
|
70
|
+
paths.toSorted(),
|
|
71
|
+
)
|
|
72
|
+
expect(invoke("remove", ...paths).status).toBe(0)
|
|
73
|
+
expect((await loadAgentsFile(root)).documents).toEqual([])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it("treats help and version spellings after -- as literal paths", async () => {
|
|
77
|
+
const paths = ["--help", "-v", "-dash/AGENTS.md"]
|
|
78
|
+
expect(invoke("add", "--", ...paths).status).toBe(0)
|
|
79
|
+
expect(
|
|
80
|
+
(await loadAgentsFile(root)).documents
|
|
81
|
+
.map((document) => document.path)
|
|
82
|
+
.sort(),
|
|
83
|
+
).toEqual(paths.map((document) => `./${document}`).sort())
|
|
84
|
+
expect(invoke("remove", "--", ...paths).status).toBe(0)
|
|
85
|
+
expect((await loadAgentsFile(root)).documents).toEqual([])
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it.each([
|
|
89
|
+
["add"],
|
|
90
|
+
["remove"],
|
|
91
|
+
["discovr"],
|
|
92
|
+
["--unknown"],
|
|
93
|
+
["init", "--json"],
|
|
94
|
+
["init", "--force=maybe"],
|
|
95
|
+
["add", "./AGENTS.md", "--typo"],
|
|
96
|
+
["validate", "--json", "--force"],
|
|
97
|
+
])(
|
|
98
|
+
"rejects invalid usage without executing a command: %j",
|
|
99
|
+
async (...args) => {
|
|
100
|
+
const result = invoke(...args)
|
|
101
|
+
expect(result.status).toBe(1)
|
|
102
|
+
expect(result.stdout).toBe("")
|
|
103
|
+
expect(result.stderr).toContain("agents:")
|
|
104
|
+
expect(await readdir(root)).toEqual([])
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
it.each([
|
|
109
|
+
["help"],
|
|
110
|
+
["--help"],
|
|
111
|
+
["-h"],
|
|
112
|
+
["add", "--help"],
|
|
113
|
+
["remove", "-h"],
|
|
114
|
+
["add", "-vh"],
|
|
115
|
+
])("shows generated help without requiring paths: %j", async (...args) => {
|
|
116
|
+
// Help must work even in a project with an invalid document index.
|
|
117
|
+
await writeFile(path.join(root, "agents.yaml"), "not: [valid yaml")
|
|
118
|
+
const result = invoke(...args)
|
|
119
|
+
expect(result.status).toBe(0)
|
|
120
|
+
expect(result.stderr).toBe("")
|
|
121
|
+
expect(result.stdout).toContain("add <paths...>")
|
|
122
|
+
expect(result.stdout).toContain("remove <paths...>")
|
|
123
|
+
expect(result.stdout).toContain("--include-dot-directories")
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it.each([["version"], ["--version"], ["-v"], ["add", "--version"]])(
|
|
127
|
+
"prints the package version: %j",
|
|
128
|
+
async (...args) => {
|
|
129
|
+
const manifest = JSON.parse(
|
|
130
|
+
await readFile(new URL("../package.json", import.meta.url), "utf8"),
|
|
131
|
+
) as { version: string }
|
|
132
|
+
const result = invoke(...args)
|
|
133
|
+
expect(result.status).toBe(0)
|
|
134
|
+
expect(result.stderr).toBe("")
|
|
135
|
+
expect(result.stdout).toBe(`${manifest.version}\n`)
|
|
136
|
+
},
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
it("honors explicit booleans and the last repeated switch", async () => {
|
|
140
|
+
expect(invoke("init").status).toBe(0)
|
|
141
|
+
const breadcrumb = await readFile(path.join(root, "AGENTS.md"), "utf8")
|
|
142
|
+
expect(
|
|
143
|
+
invoke("init", "--force", "--force=false", "--force=false").status,
|
|
144
|
+
).toBe(0)
|
|
145
|
+
expect(await readFile(path.join(root, "AGENTS.md"), "utf8")).toBe(
|
|
146
|
+
breadcrumb,
|
|
147
|
+
)
|
|
148
|
+
expect(invoke("init", "--force=false", "--force").status).toBe(0)
|
|
149
|
+
expect(
|
|
150
|
+
(await readFile(path.join(root, "AGENTS.md"), "utf8")).match(/Consult/g),
|
|
151
|
+
).toHaveLength(2)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it("discovers hidden directories on request and keeps JSON output clean", async () => {
|
|
155
|
+
await mkdir(path.join(root, ".hidden"))
|
|
156
|
+
await writeFile(
|
|
157
|
+
path.join(root, ".hidden", "AGENTS.md"),
|
|
158
|
+
"# Hidden instructions\n",
|
|
159
|
+
)
|
|
160
|
+
const ordinary = invoke("discover", "--json")
|
|
161
|
+
expect(ordinary.status).toBe(0)
|
|
162
|
+
expect(JSON.parse(ordinary.stdout)).toEqual([])
|
|
163
|
+
const hidden = invoke("--json", "discover", "--include-dot-directories")
|
|
164
|
+
expect(hidden.status).toBe(0)
|
|
165
|
+
expect(hidden.stderr).toBe("")
|
|
166
|
+
expect(JSON.parse(hidden.stdout)).toEqual([{ path: "./.hidden/AGENTS.md" }])
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it("sets the validation exit status in both text and JSON modes", async () => {
|
|
170
|
+
expect(invoke("init").status).toBe(0)
|
|
171
|
+
const valid = invoke("validate", "--json")
|
|
172
|
+
expect(valid.status).toBe(0)
|
|
173
|
+
expect(JSON.parse(valid.stdout)).toMatchObject({ ok: true, errors: [] })
|
|
174
|
+
await saveAgentsFile(root, {
|
|
175
|
+
version: 1,
|
|
176
|
+
documents: [{ path: "./missing/AGENTS.md" }],
|
|
177
|
+
})
|
|
178
|
+
const invalid = invoke("validate", "--json")
|
|
179
|
+
expect(invalid.status).toBe(1)
|
|
180
|
+
expect(invalid.stderr).toBe("")
|
|
181
|
+
expect(JSON.parse(invalid.stdout)).toMatchObject({
|
|
182
|
+
ok: false,
|
|
183
|
+
errors: [expect.stringContaining("file does not exist")],
|
|
184
|
+
})
|
|
185
|
+
expect(invoke("validate").status).toBe(1)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it("generates completion scripts without loading the document index or changing files", async () => {
|
|
189
|
+
await writeFile(path.join(root, "agents.yaml"), "not: [valid yaml")
|
|
190
|
+
const result = invoke("completion", "bash")
|
|
191
|
+
expect(result.status).toBe(0)
|
|
192
|
+
expect(result.stderr).toBe("")
|
|
193
|
+
expect(result.stdout).toContain("_comline")
|
|
194
|
+
expect(result.stdout).not.toContain("What would you like to do?")
|
|
195
|
+
expect(await readdir(root)).toEqual(["agents.yaml"])
|
|
196
|
+
expect(await readFile(path.join(root, "agents.yaml"), "utf8")).toBe(
|
|
197
|
+
"not: [valid yaml",
|
|
198
|
+
)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it("completes listed paths after each remove argument without removing documents", async () => {
|
|
202
|
+
const file = {
|
|
203
|
+
version: 1 as const,
|
|
204
|
+
documents: [
|
|
205
|
+
{ path: "./one/AGENTS.md" },
|
|
206
|
+
{ path: "./two/AGENTS.md", description: "Second document" },
|
|
207
|
+
],
|
|
208
|
+
}
|
|
209
|
+
await saveAgentsFile(root, file)
|
|
210
|
+
const result = invoke("__complete", "remove", "./one/AGENTS.md", "./t")
|
|
211
|
+
expect(result.status).toBe(0)
|
|
212
|
+
expect(result.stderr).toBe("")
|
|
213
|
+
expect(result.stdout).toContain("./two/AGENTS.md\tSecond document")
|
|
214
|
+
expect(await loadAgentsFile(root)).toEqual(file)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it("offers filesystem completion for every add argument", async () => {
|
|
218
|
+
for (const words of [
|
|
219
|
+
["add", ""],
|
|
220
|
+
["add", "./one/AGENTS.md", ""],
|
|
221
|
+
]) {
|
|
222
|
+
const result = await agents.complete({ words })
|
|
223
|
+
expect(result.fileSystem).toBe("files")
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
})
|
package/src/run.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MultiSelectPrompt } from "@clack/core"
|
|
2
2
|
import * as clack from "@clack/prompts"
|
|
3
|
+
import { completionResponse, help } from "comline"
|
|
3
4
|
import { styleText } from "node:util"
|
|
4
5
|
import {
|
|
5
6
|
addDocuments,
|
|
@@ -8,132 +9,70 @@ import {
|
|
|
8
9
|
removeDocuments,
|
|
9
10
|
validateAgentsFile,
|
|
10
11
|
} from "./agents-file.ts"
|
|
12
|
+
import { agents, earlyCommand, packageVersion } from "./cli.ts"
|
|
11
13
|
import { describeAgentDocument, discoverAgentDocuments } from "./discover.ts"
|
|
12
14
|
import { cwd, formatProjectPath, resolveFromRoot } from "./paths.ts"
|
|
13
15
|
|
|
14
|
-
type Command =
|
|
15
|
-
| "add"
|
|
16
|
-
| "discover"
|
|
17
|
-
| "help"
|
|
18
|
-
| "init"
|
|
19
|
-
| "remove"
|
|
20
|
-
| "validate"
|
|
21
|
-
| "version"
|
|
22
|
-
|
|
23
|
-
type ParsedArgs = {
|
|
24
|
-
command: Command | undefined
|
|
25
|
-
values: string[]
|
|
26
|
-
flags: Map<string, string | boolean>
|
|
27
|
-
}
|
|
28
|
-
|
|
29
16
|
type DocumentOption = {
|
|
30
17
|
value: string
|
|
31
18
|
label: string
|
|
32
19
|
disabled?: boolean
|
|
33
20
|
}
|
|
34
21
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
agents add <path...>
|
|
42
|
-
agents remove <path...>
|
|
43
|
-
agents validate [--json]
|
|
22
|
+
export async function run(argv: string[]): Promise<void> {
|
|
23
|
+
const completion = await completionResponse(agents.definition, argv)
|
|
24
|
+
if (completion !== undefined) {
|
|
25
|
+
process.stdout.write(completion)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
44
28
|
|
|
45
|
-
|
|
29
|
+
const early = earlyCommand(argv)
|
|
30
|
+
if (early === "help") {
|
|
31
|
+
console.log(help(agents.definition))
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
if (early === "version") {
|
|
35
|
+
console.log(packageVersion())
|
|
36
|
+
return
|
|
37
|
+
}
|
|
46
38
|
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
const { inputs, warnings } = agents(argv)
|
|
40
|
+
if (warnings.length > 0) {
|
|
41
|
+
throw new Error(warnings.map((warning) => warning.message).join("\n"))
|
|
42
|
+
}
|
|
49
43
|
const root = cwd()
|
|
50
44
|
|
|
51
|
-
switch (
|
|
52
|
-
case
|
|
45
|
+
switch (inputs.case) {
|
|
46
|
+
case "":
|
|
53
47
|
await interactive(root)
|
|
54
48
|
return
|
|
55
49
|
case "help":
|
|
56
|
-
console.log(
|
|
50
|
+
console.log(help(agents.definition))
|
|
57
51
|
return
|
|
58
52
|
case "version":
|
|
59
|
-
console.log(
|
|
53
|
+
console.log(packageVersion())
|
|
60
54
|
return
|
|
61
55
|
case "init":
|
|
62
|
-
await commandInit(root,
|
|
56
|
+
await commandInit(root, inputs.opts.force)
|
|
63
57
|
return
|
|
64
58
|
case "discover":
|
|
65
59
|
await commandDiscover(root, {
|
|
66
|
-
json:
|
|
67
|
-
includeDotDirectories:
|
|
68
|
-
parsed.flags.get("include-dot-directories") === true,
|
|
60
|
+
json: inputs.opts.json,
|
|
61
|
+
includeDotDirectories: inputs.opts["include-dot-directories"],
|
|
69
62
|
})
|
|
70
63
|
return
|
|
71
|
-
case "add":
|
|
72
|
-
await commandAdd(root,
|
|
64
|
+
case "add/$...paths":
|
|
65
|
+
await commandAdd(root, inputs.params.paths)
|
|
73
66
|
return
|
|
74
|
-
case "remove":
|
|
75
|
-
await commandRemove(root,
|
|
67
|
+
case "remove/$...paths":
|
|
68
|
+
await commandRemove(root, inputs.params.paths)
|
|
76
69
|
return
|
|
77
70
|
case "validate":
|
|
78
|
-
await commandValidate(root,
|
|
71
|
+
await commandValidate(root, inputs.opts.json)
|
|
79
72
|
return
|
|
80
73
|
}
|
|
81
74
|
}
|
|
82
75
|
|
|
83
|
-
function parseArgs(argv: string[]): ParsedArgs {
|
|
84
|
-
const flags = new Map<string, string | boolean>()
|
|
85
|
-
const values: string[] = []
|
|
86
|
-
let command: Command | undefined
|
|
87
|
-
|
|
88
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
89
|
-
const arg = argv[index]
|
|
90
|
-
if (!arg) continue
|
|
91
|
-
|
|
92
|
-
if (arg === "--help" || arg === "-h") {
|
|
93
|
-
command = "help"
|
|
94
|
-
continue
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (arg === "--version" || arg === "-v") {
|
|
98
|
-
command = "version"
|
|
99
|
-
continue
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (arg.startsWith("--")) {
|
|
103
|
-
const [rawName, inlineValue] = arg.slice(2).split("=", 2)
|
|
104
|
-
if (!rawName) continue
|
|
105
|
-
if (inlineValue !== undefined) {
|
|
106
|
-
flags.set(rawName, inlineValue)
|
|
107
|
-
continue
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
flags.set(rawName, true)
|
|
111
|
-
continue
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!command && isCommand(arg)) {
|
|
115
|
-
command = arg
|
|
116
|
-
continue
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
values.push(arg)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return { command, values, flags }
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function isCommand(value: string): value is Command {
|
|
126
|
-
return [
|
|
127
|
-
"add",
|
|
128
|
-
"discover",
|
|
129
|
-
"help",
|
|
130
|
-
"init",
|
|
131
|
-
"remove",
|
|
132
|
-
"validate",
|
|
133
|
-
"version",
|
|
134
|
-
].includes(value)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
76
|
async function commandInit(root: string, force: boolean): Promise<void> {
|
|
138
77
|
clack.intro("agents init")
|
|
139
78
|
const result = await initProject(root, { force })
|
|
@@ -216,6 +155,9 @@ async function commandRemove(root: string, paths: string[]): Promise<void> {
|
|
|
216
155
|
|
|
217
156
|
async function commandValidate(root: string, json: boolean): Promise<void> {
|
|
218
157
|
const result = await validateAgentsFile(root)
|
|
158
|
+
if (!result.ok) {
|
|
159
|
+
process.exitCode = 1
|
|
160
|
+
}
|
|
219
161
|
if (json) {
|
|
220
162
|
console.log(JSON.stringify(result, null, 2))
|
|
221
163
|
return
|
|
@@ -232,9 +174,6 @@ async function commandValidate(root: string, json: boolean): Promise<void> {
|
|
|
232
174
|
clack.outro(
|
|
233
175
|
result.ok ? "agents.yaml is valid." : "agents.yaml needs attention.",
|
|
234
176
|
)
|
|
235
|
-
if (!result.ok) {
|
|
236
|
-
process.exitCode = 1
|
|
237
|
-
}
|
|
238
177
|
}
|
|
239
178
|
|
|
240
179
|
async function interactive(root: string): Promise<void> {
|
|
@@ -290,10 +229,10 @@ async function interactive(root: string): Promise<void> {
|
|
|
290
229
|
await commandAdd(root, selected)
|
|
291
230
|
}
|
|
292
231
|
|
|
293
|
-
function chooseDocumentsToEnable(
|
|
232
|
+
async function chooseDocumentsToEnable(
|
|
294
233
|
options: DocumentOption[],
|
|
295
|
-
): Promise<string[] |
|
|
296
|
-
|
|
234
|
+
): Promise<string[] | typeof clack.CANCEL_SYMBOL | undefined> {
|
|
235
|
+
const selected = await new MultiSelectPrompt<DocumentOption>({
|
|
297
236
|
options,
|
|
298
237
|
required: false,
|
|
299
238
|
render() {
|
|
@@ -317,6 +256,8 @@ ${styleText("cyan", clack.S_BAR_END)}
|
|
|
317
256
|
`
|
|
318
257
|
},
|
|
319
258
|
}).prompt()
|
|
259
|
+
|
|
260
|
+
return typeof selected === "symbol" ? clack.CANCEL_SYMBOL : selected
|
|
320
261
|
}
|
|
321
262
|
|
|
322
263
|
function styleDocumentOption(
|
package/dist/index.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|