agents.yaml 0.2.0 → 0.2.2
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 +2 -0
- package/README.md +18 -0
- package/dist/index.mjs +54 -20
- package/package.json +3 -1
- package/src/agents-file.ts +185 -170
- package/src/discover.bench.ts +219 -0
- package/src/discover.test.ts +164 -105
- package/src/discover.ts +163 -123
- package/src/index.ts +4 -4
- package/src/paths.ts +13 -8
- package/src/run.ts +296 -198
package/src/run.ts
CHANGED
|
@@ -1,20 +1,35 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { MultiSelectPrompt } from "@clack/core"
|
|
2
|
+
import * as clack from "@clack/prompts"
|
|
3
|
+
import { styleText } from "node:util"
|
|
2
4
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from
|
|
9
|
-
import { describeAgentDocument, discoverAgentDocuments } from
|
|
10
|
-
import { cwd, formatProjectPath, resolveFromRoot } from
|
|
11
|
-
|
|
12
|
-
type Command =
|
|
5
|
+
addDocuments,
|
|
6
|
+
initProject,
|
|
7
|
+
loadAgentsFile,
|
|
8
|
+
removeDocuments,
|
|
9
|
+
validateAgentsFile,
|
|
10
|
+
} from "./agents-file.ts"
|
|
11
|
+
import { describeAgentDocument, discoverAgentDocuments } from "./discover.ts"
|
|
12
|
+
import { cwd, formatProjectPath, resolveFromRoot } from "./paths.ts"
|
|
13
|
+
|
|
14
|
+
type Command =
|
|
15
|
+
| "add"
|
|
16
|
+
| "discover"
|
|
17
|
+
| "help"
|
|
18
|
+
| "init"
|
|
19
|
+
| "remove"
|
|
20
|
+
| "validate"
|
|
21
|
+
| "version"
|
|
13
22
|
|
|
14
23
|
type ParsedArgs = {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
command: Command | undefined
|
|
25
|
+
values: string[]
|
|
26
|
+
flags: Map<string, string | boolean>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type DocumentOption = {
|
|
30
|
+
value: string
|
|
31
|
+
label: string
|
|
32
|
+
disabled?: boolean
|
|
18
33
|
}
|
|
19
34
|
|
|
20
35
|
const helpText = `agents
|
|
@@ -22,7 +37,7 @@ const helpText = `agents
|
|
|
22
37
|
Usage:
|
|
23
38
|
agents
|
|
24
39
|
agents init [--force]
|
|
25
|
-
agents discover [--json]
|
|
40
|
+
agents discover [--json] [--include-dot-directories]
|
|
26
41
|
agents add <path...>
|
|
27
42
|
agents remove <path...>
|
|
28
43
|
agents validate [--json]
|
|
@@ -30,214 +45,297 @@ Usage:
|
|
|
30
45
|
agents.yaml is a curated table of contents for promoted AGENTS.md guidance.`
|
|
31
46
|
|
|
32
47
|
export async function run(argv: string[]): Promise<void> {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
const parsed = parseArgs(argv)
|
|
49
|
+
const root = cwd()
|
|
50
|
+
|
|
51
|
+
switch (parsed.command) {
|
|
52
|
+
case undefined:
|
|
53
|
+
await interactive(root)
|
|
54
|
+
return
|
|
55
|
+
case "help":
|
|
56
|
+
console.log(helpText)
|
|
57
|
+
return
|
|
58
|
+
case "version":
|
|
59
|
+
console.log("0.1.0")
|
|
60
|
+
return
|
|
61
|
+
case "init":
|
|
62
|
+
await commandInit(root, parsed.flags.get("force") === true)
|
|
63
|
+
return
|
|
64
|
+
case "discover":
|
|
65
|
+
await commandDiscover(root, {
|
|
66
|
+
json: parsed.flags.get("json") === true,
|
|
67
|
+
includeDotDirectories:
|
|
68
|
+
parsed.flags.get("include-dot-directories") === true,
|
|
69
|
+
})
|
|
70
|
+
return
|
|
71
|
+
case "add":
|
|
72
|
+
await commandAdd(root, parsed.values)
|
|
73
|
+
return
|
|
74
|
+
case "remove":
|
|
75
|
+
await commandRemove(root, parsed.values)
|
|
76
|
+
return
|
|
77
|
+
case "validate":
|
|
78
|
+
await commandValidate(root, parsed.flags.get("json") === true)
|
|
79
|
+
return
|
|
80
|
+
}
|
|
62
81
|
}
|
|
63
82
|
|
|
64
83
|
function parseArgs(argv: string[]): ParsedArgs {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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 }
|
|
104
123
|
}
|
|
105
124
|
|
|
106
125
|
function isCommand(value: string): value is Command {
|
|
107
|
-
|
|
126
|
+
return [
|
|
127
|
+
"add",
|
|
128
|
+
"discover",
|
|
129
|
+
"help",
|
|
130
|
+
"init",
|
|
131
|
+
"remove",
|
|
132
|
+
"validate",
|
|
133
|
+
"version",
|
|
134
|
+
].includes(value)
|
|
108
135
|
}
|
|
109
136
|
|
|
110
137
|
async function commandInit(root: string, force: boolean): Promise<void> {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
138
|
+
clack.intro("agents init")
|
|
139
|
+
const result = await initProject(root, { force })
|
|
140
|
+
clack.note(result.messages.join("\n"), "Updated")
|
|
141
|
+
clack.outro("Project breadcrumb is ready.")
|
|
115
142
|
}
|
|
116
143
|
|
|
117
|
-
async function commandDiscover(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
144
|
+
async function commandDiscover(
|
|
145
|
+
root: string,
|
|
146
|
+
options: { json: boolean; includeDotDirectories: boolean },
|
|
147
|
+
): Promise<void> {
|
|
148
|
+
const documents = await discoverAgentDocuments(root, {
|
|
149
|
+
includeDotDirectories: options.includeDotDirectories,
|
|
150
|
+
})
|
|
151
|
+
if (options.json) {
|
|
152
|
+
console.log(JSON.stringify(documents, null, 2))
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
clack.intro("agents discover")
|
|
157
|
+
if (documents.length === 0) {
|
|
158
|
+
clack.outro("No supplemental AGENTS.md files found.")
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
clack.note(formatDocumentList(documents), `Found ${documents.length}`)
|
|
163
|
+
clack.outro("Use agents add <path> to enable one.")
|
|
132
164
|
}
|
|
133
165
|
|
|
134
166
|
async function commandAdd(root: string, paths: string[]): Promise<void> {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
167
|
+
if (paths.length === 0) {
|
|
168
|
+
throw new Error("add requires at least one AGENTS.md path")
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const documents = await Promise.all(
|
|
172
|
+
paths.map((path) =>
|
|
173
|
+
describeAgentDocument(
|
|
174
|
+
root,
|
|
175
|
+
formatProjectPath(root, resolveFromRoot(root, path)),
|
|
176
|
+
),
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
const file = await addDocuments(root, documents)
|
|
181
|
+
clack.intro("agents add")
|
|
182
|
+
clack.note(formatDocumentList(file.documents), "Promoted documents")
|
|
183
|
+
clack.outro(
|
|
184
|
+
`Added ${documents.length} document${documents.length === 1 ? "" : "s"}.`,
|
|
185
|
+
)
|
|
149
186
|
}
|
|
150
187
|
|
|
151
188
|
function formatDocumentList(
|
|
152
|
-
|
|
189
|
+
documents: { path: string; description?: string | undefined }[],
|
|
153
190
|
): string {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
191
|
+
return documents
|
|
192
|
+
.map((doc) =>
|
|
193
|
+
doc.description ? `${doc.path}\n ${doc.description}` : doc.path,
|
|
194
|
+
)
|
|
195
|
+
.join("\n")
|
|
157
196
|
}
|
|
158
197
|
|
|
159
198
|
async function commandRemove(root: string, paths: string[]): Promise<void> {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
199
|
+
if (paths.length === 0) {
|
|
200
|
+
throw new Error("remove requires at least one path")
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const normalizedPaths = paths.map((path) =>
|
|
204
|
+
formatProjectPath(root, resolveFromRoot(root, path)),
|
|
205
|
+
)
|
|
206
|
+
const result = await removeDocuments(root, normalizedPaths)
|
|
207
|
+
clack.intro("agents remove")
|
|
208
|
+
clack.note(
|
|
209
|
+
result.removed.join("\n") || "No matching documents were listed.",
|
|
210
|
+
"Removed",
|
|
211
|
+
)
|
|
212
|
+
clack.outro(
|
|
213
|
+
`agents.yaml now has ${result.file.documents.length} promoted document${result.file.documents.length === 1 ? "" : "s"}.`,
|
|
214
|
+
)
|
|
171
215
|
}
|
|
172
216
|
|
|
173
217
|
async function commandValidate(root: string, json: boolean): Promise<void> {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
218
|
+
const result = await validateAgentsFile(root)
|
|
219
|
+
if (json) {
|
|
220
|
+
console.log(JSON.stringify(result, null, 2))
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
clack.intro("agents validate")
|
|
225
|
+
if (result.errors.length > 0) {
|
|
226
|
+
clack.note(result.errors.join("\n"), "Errors")
|
|
227
|
+
}
|
|
228
|
+
if (result.warnings.length > 0) {
|
|
229
|
+
clack.note(result.warnings.join("\n"), "Warnings")
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
clack.outro(
|
|
233
|
+
result.ok ? "agents.yaml is valid." : "agents.yaml needs attention.",
|
|
234
|
+
)
|
|
235
|
+
if (!result.ok) {
|
|
236
|
+
process.exitCode = 1
|
|
237
|
+
}
|
|
192
238
|
}
|
|
193
239
|
|
|
194
240
|
async function interactive(root: string): Promise<void> {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
241
|
+
clack.intro("agents")
|
|
242
|
+
const action = await clack.select({
|
|
243
|
+
message: "What would you like to do?",
|
|
244
|
+
options: [
|
|
245
|
+
{ value: "discover", label: "Discover and enable AGENTS.md files" },
|
|
246
|
+
{ value: "validate", label: "Validate agents.yaml" },
|
|
247
|
+
{ value: "init", label: "Initialize breadcrumb files" },
|
|
248
|
+
],
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
if (clack.isCancel(action)) {
|
|
252
|
+
clack.cancel("Cancelled.")
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (action === "init") {
|
|
257
|
+
await commandInit(root, false)
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (action === "validate") {
|
|
262
|
+
await commandValidate(root, false)
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const existing = await loadAgentsFile(root)
|
|
267
|
+
const discovered = await discoverAgentDocuments(root)
|
|
268
|
+
const candidates = discovered.filter(
|
|
269
|
+
(doc) => !existing.documents.some((active) => active.path === doc.path),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
if (candidates.length === 0) {
|
|
273
|
+
clack.outro("No unlisted supplemental AGENTS.md files found.")
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const selected = await chooseDocumentsToEnable(
|
|
278
|
+
candidates.map((doc) => ({ value: doc.path, label: doc.path })),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if (
|
|
282
|
+
clack.isCancel(selected) ||
|
|
283
|
+
selected === undefined ||
|
|
284
|
+
selected.length === 0
|
|
285
|
+
) {
|
|
286
|
+
clack.cancel("No documents selected.")
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
await commandAdd(root, selected)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function chooseDocumentsToEnable(
|
|
294
|
+
options: DocumentOption[],
|
|
295
|
+
): Promise<string[] | symbol | undefined> {
|
|
296
|
+
return new MultiSelectPrompt<DocumentOption>({
|
|
297
|
+
options,
|
|
298
|
+
required: false,
|
|
299
|
+
render() {
|
|
300
|
+
const prefix = `${styleText("cyan", clack.S_BAR)} `
|
|
301
|
+
const selected = this.value ?? []
|
|
302
|
+
|
|
303
|
+
return `${styleText("gray", clack.S_BAR)}
|
|
304
|
+
${clack.symbol(this.state)} Choose documents to enable
|
|
305
|
+
${prefix}${clack.limitOptions({
|
|
306
|
+
options: this.options,
|
|
307
|
+
cursor: this.cursor,
|
|
308
|
+
columnPadding: prefix.length,
|
|
309
|
+
style: (option, active) =>
|
|
310
|
+
styleDocumentOption(option, {
|
|
311
|
+
active,
|
|
312
|
+
selected: selected.includes(option.value),
|
|
313
|
+
}),
|
|
314
|
+
}).join(`
|
|
315
|
+
${prefix}`)}
|
|
316
|
+
${styleText("cyan", clack.S_BAR_END)}
|
|
317
|
+
`
|
|
318
|
+
},
|
|
319
|
+
}).prompt()
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function styleDocumentOption(
|
|
323
|
+
option: DocumentOption,
|
|
324
|
+
state: { active: boolean; selected: boolean },
|
|
325
|
+
): string {
|
|
326
|
+
if (option.disabled) {
|
|
327
|
+
return `${styleText("gray", clack.S_CHECKBOX_INACTIVE)} ${styleText(
|
|
328
|
+
["strikethrough", "gray"],
|
|
329
|
+
option.label,
|
|
330
|
+
)}`
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const checkbox = state.selected
|
|
334
|
+
? styleText("green", clack.S_CHECKBOX_SELECTED)
|
|
335
|
+
: state.active
|
|
336
|
+
? styleText("cyan", clack.S_CHECKBOX_ACTIVE)
|
|
337
|
+
: styleText("dim", clack.S_CHECKBOX_INACTIVE)
|
|
338
|
+
const cursor = state.active ? styleText("cyan", ">") : " "
|
|
339
|
+
const label = state.active ? option.label : styleText("dim", option.label)
|
|
340
|
+
return `${cursor} ${checkbox} ${label}`
|
|
243
341
|
}
|