facult 2.6.0 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -337
- package/package.json +1 -1
- package/src/adapters/codex.ts +1 -1
- package/src/audit/agent.ts +26 -24
- package/src/audit/fix.ts +875 -0
- package/src/audit/index.ts +51 -2
- package/src/audit/safe.ts +596 -0
- package/src/audit/static.ts +151 -34
- package/src/audit/status.ts +21 -0
- package/src/audit/suppressions.ts +266 -0
- package/src/audit/tui.ts +784 -174
- package/src/audit/update-index.ts +4 -17
- package/src/builtin.ts +7 -1
- package/src/cli-ui.ts +375 -0
- package/src/consolidate.ts +151 -55
- package/src/doctor.ts +327 -0
- package/src/global-docs.ts +43 -2
- package/src/index.ts +571 -292
- package/src/manage.ts +931 -88
- package/src/mcp-config.ts +132 -0
- package/src/project-sync.ts +288 -0
- package/src/remote.ts +387 -117
- package/src/trust.ts +119 -11
- package/src/util/git.ts +95 -0
package/src/index.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { getAllAdapters } from "./adapters";
|
|
5
|
-
import { aiCommand } from "./ai";
|
|
6
|
-
import { auditCommand } from "./audit";
|
|
7
|
-
import { autosyncCommand } from "./autosync";
|
|
8
4
|
import {
|
|
9
5
|
type CapabilityScopeMode,
|
|
10
6
|
parseCliContextArgs,
|
|
11
7
|
resolveCliContextRoot,
|
|
12
8
|
} from "./cli-context";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
import {
|
|
10
|
+
renderBadge,
|
|
11
|
+
renderBullets,
|
|
12
|
+
renderCatalog,
|
|
13
|
+
renderCode,
|
|
14
|
+
renderJsonBlock,
|
|
15
|
+
renderKeyValue,
|
|
16
|
+
renderPage,
|
|
17
|
+
renderTable,
|
|
18
|
+
} from "./cli-ui";
|
|
16
19
|
import type { AssetScope, AssetSourceKind } from "./graph";
|
|
17
20
|
import {
|
|
18
21
|
graphDependencies,
|
|
@@ -28,14 +31,6 @@ import type {
|
|
|
28
31
|
SkillEntry,
|
|
29
32
|
SnippetEntry,
|
|
30
33
|
} from "./index-builder";
|
|
31
|
-
import { indexCommand } from "./index-builder";
|
|
32
|
-
import {
|
|
33
|
-
manageCommand,
|
|
34
|
-
managedCommand,
|
|
35
|
-
syncCommand,
|
|
36
|
-
unmanageCommand,
|
|
37
|
-
} from "./manage";
|
|
38
|
-
import { migrateCommand } from "./migrate";
|
|
39
34
|
import type { QueryFilters } from "./query";
|
|
40
35
|
import {
|
|
41
36
|
filterAgents,
|
|
@@ -46,18 +41,6 @@ import {
|
|
|
46
41
|
findCapabilities,
|
|
47
42
|
loadIndex,
|
|
48
43
|
} from "./query";
|
|
49
|
-
import {
|
|
50
|
-
installCommand,
|
|
51
|
-
searchCommand,
|
|
52
|
-
sourcesCommand,
|
|
53
|
-
templatesCommand,
|
|
54
|
-
updateCommand,
|
|
55
|
-
verifySourceCommand,
|
|
56
|
-
} from "./remote";
|
|
57
|
-
import { scanCommand } from "./scan";
|
|
58
|
-
import { selfUpdateCommand } from "./self-update";
|
|
59
|
-
import { snippetsCommand } from "./snippets-cli";
|
|
60
|
-
import { trustCommand, untrustCommand } from "./trust";
|
|
61
44
|
import { parseJsonLenient } from "./util/json";
|
|
62
45
|
|
|
63
46
|
type ListKind = "skills" | "mcp" | "agents" | "snippets" | "instructions";
|
|
@@ -96,182 +79,232 @@ interface GraphCommandOptions {
|
|
|
96
79
|
}
|
|
97
80
|
|
|
98
81
|
function printHelp() {
|
|
99
|
-
console.log(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
--enabled-for Filter list to entries enabled for a specific tool
|
|
186
|
-
--untrusted Filter list to entries that are not trusted
|
|
187
|
-
--flagged Filter list to entries flagged by audit
|
|
188
|
-
--pending Filter list to entries pending audit
|
|
189
|
-
--for Comma-separated list of tools for enable/disable
|
|
190
|
-
--dry-run Show what sync would change
|
|
191
|
-
--as Install/scaffold target name override
|
|
192
|
-
--limit Max results for search
|
|
193
|
-
--apply Apply updates (update command)
|
|
194
|
-
--self (update) run self-update flow instead of remote item updates
|
|
195
|
-
--strict-source-trust Enforce trust-only remote install/update actions
|
|
196
|
-
--show-secrets (show) Print raw secret values (unsafe)
|
|
197
|
-
--root Select a canonical .ai root explicitly
|
|
198
|
-
--global Force the global canonical root
|
|
199
|
-
--project Force the nearest repo-local .ai root
|
|
200
|
-
--scope Capability view scope: merged|global|project
|
|
201
|
-
--source Filter to builtin|global|project asset provenance
|
|
202
|
-
`);
|
|
82
|
+
console.log(
|
|
83
|
+
renderPage({
|
|
84
|
+
title: "fclt",
|
|
85
|
+
subtitle:
|
|
86
|
+
"Manage canonical AI capability, rendered tool surfaces, and evolution state.",
|
|
87
|
+
sections: [
|
|
88
|
+
{
|
|
89
|
+
title: "Usage",
|
|
90
|
+
lines: renderBullets([
|
|
91
|
+
`${renderCode("fclt list")} defaults to ${renderCode("skills")} when you do not specify a type.`,
|
|
92
|
+
`${renderCode("fclt graph <asset>")} is shorthand for ${renderCode("fclt graph show <asset>")}.`,
|
|
93
|
+
`${renderCode("fclt templates init ...")} is the main entry for scaffolding new canonical capability.`,
|
|
94
|
+
]),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
title: "Core Commands",
|
|
98
|
+
lines: renderTable({
|
|
99
|
+
headers: ["Command", "Purpose"],
|
|
100
|
+
rows: [
|
|
101
|
+
["scan", "Scan local tool configs and discovered assets"],
|
|
102
|
+
[
|
|
103
|
+
"audit",
|
|
104
|
+
"Run security audits with interactive or scripted flows",
|
|
105
|
+
],
|
|
106
|
+
[
|
|
107
|
+
"consolidate",
|
|
108
|
+
"Import existing skills and MCP configs into canonical state",
|
|
109
|
+
],
|
|
110
|
+
["index", "Rebuild the generated capability index"],
|
|
111
|
+
[
|
|
112
|
+
"list",
|
|
113
|
+
"List indexed skills, MCP, agents, snippets, or instructions",
|
|
114
|
+
],
|
|
115
|
+
["show", "Inspect one indexed asset and its source contents"],
|
|
116
|
+
["find", "Search indexed capability across asset types"],
|
|
117
|
+
["graph", "Inspect capability graph nodes, deps, and dependents"],
|
|
118
|
+
[
|
|
119
|
+
"templates",
|
|
120
|
+
"Scaffold skills, MCP, agents, snippets, and automations",
|
|
121
|
+
],
|
|
122
|
+
["search/install/update", "Work with remote capability indices"],
|
|
123
|
+
[
|
|
124
|
+
"manage/sync",
|
|
125
|
+
"Enter managed mode and render tool-native output",
|
|
126
|
+
],
|
|
127
|
+
["ai", "Capture writeback and evolve canonical assets"],
|
|
128
|
+
],
|
|
129
|
+
}),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
title: "Common Options",
|
|
133
|
+
lines: renderTable({
|
|
134
|
+
headers: ["Option", "Meaning"],
|
|
135
|
+
rows: [
|
|
136
|
+
[
|
|
137
|
+
"--json",
|
|
138
|
+
"Machine-readable output instead of formatted terminal UI",
|
|
139
|
+
],
|
|
140
|
+
["--dry-run", "Show intended writes without mutating files"],
|
|
141
|
+
[
|
|
142
|
+
"--root / --global / --project",
|
|
143
|
+
"Pick the canonical root explicitly",
|
|
144
|
+
],
|
|
145
|
+
[
|
|
146
|
+
"--scope / --source",
|
|
147
|
+
"Narrow merged views by scope or provenance",
|
|
148
|
+
],
|
|
149
|
+
[
|
|
150
|
+
"--non-interactive / --yes",
|
|
151
|
+
"Suppress prompts where the command supports inferred defaults",
|
|
152
|
+
],
|
|
153
|
+
],
|
|
154
|
+
}),
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
title: "Examples",
|
|
158
|
+
lines: renderBullets([
|
|
159
|
+
renderCode("fclt list"),
|
|
160
|
+
renderCode("fclt graph skills:capability-evolution"),
|
|
161
|
+
renderCode("fclt templates init skill review-checklist"),
|
|
162
|
+
renderCode("fclt templates init agent writeback-curator"),
|
|
163
|
+
]),
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
})
|
|
167
|
+
);
|
|
203
168
|
}
|
|
204
169
|
|
|
205
170
|
function printListHelp() {
|
|
206
|
-
console.log(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
171
|
+
console.log(
|
|
172
|
+
renderPage({
|
|
173
|
+
title: "fclt list",
|
|
174
|
+
subtitle: "List indexed entries from the canonical store.",
|
|
175
|
+
sections: [
|
|
176
|
+
{
|
|
177
|
+
title: "Usage",
|
|
178
|
+
lines: renderBullets([
|
|
179
|
+
renderCode(
|
|
180
|
+
"fclt list [skills|mcp|agents|snippets|instructions] [options]"
|
|
181
|
+
),
|
|
182
|
+
renderCode("fclt list"),
|
|
183
|
+
]),
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
title: "Options",
|
|
187
|
+
lines: renderTable({
|
|
188
|
+
headers: ["Option", "Meaning"],
|
|
189
|
+
rows: [
|
|
190
|
+
[
|
|
191
|
+
"--enabled-for TOOL",
|
|
192
|
+
"Only include entries enabled for one tool",
|
|
193
|
+
],
|
|
194
|
+
["--untrusted", "Only include entries without trust approval"],
|
|
195
|
+
["--flagged", "Only include entries flagged by audit"],
|
|
196
|
+
["--pending", "Only include entries still pending audit"],
|
|
197
|
+
["--root / --global / --project", "Choose the canonical root"],
|
|
198
|
+
["--scope", "merged, global, or project"],
|
|
199
|
+
["--source", "builtin, global, or project provenance"],
|
|
200
|
+
["--json", "Print the raw JSON array"],
|
|
201
|
+
],
|
|
202
|
+
}),
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
})
|
|
206
|
+
);
|
|
223
207
|
}
|
|
224
208
|
|
|
225
209
|
function printShowHelp() {
|
|
226
|
-
console.log(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
210
|
+
console.log(
|
|
211
|
+
renderPage({
|
|
212
|
+
title: "fclt show",
|
|
213
|
+
subtitle: "Inspect one indexed entry and the source file behind it.",
|
|
214
|
+
sections: [
|
|
215
|
+
{
|
|
216
|
+
title: "Usage",
|
|
217
|
+
lines: renderBullets([
|
|
218
|
+
renderCode("fclt show <name>"),
|
|
219
|
+
renderCode("fclt show mcp:<name> [--show-secrets]"),
|
|
220
|
+
renderCode("fclt show instruction:<name>"),
|
|
221
|
+
]),
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
title: "Options",
|
|
225
|
+
lines: renderTable({
|
|
226
|
+
headers: ["Option", "Meaning"],
|
|
227
|
+
rows: [
|
|
228
|
+
[
|
|
229
|
+
"--show-secrets",
|
|
230
|
+
"For MCP configs, print raw secrets instead of redacting",
|
|
231
|
+
],
|
|
232
|
+
["--root / --global / --project", "Choose the canonical root"],
|
|
233
|
+
["--scope", "merged, global, or project"],
|
|
234
|
+
["--source", "builtin, global, or project provenance"],
|
|
235
|
+
],
|
|
236
|
+
}),
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
})
|
|
240
|
+
);
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
function printFindHelp() {
|
|
244
|
-
console.log(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
244
|
+
console.log(
|
|
245
|
+
renderPage({
|
|
246
|
+
title: "fclt find",
|
|
247
|
+
subtitle:
|
|
248
|
+
"Search indexed capability across skills, MCP, agents, snippets, and instructions.",
|
|
249
|
+
sections: [
|
|
250
|
+
{
|
|
251
|
+
title: "Usage",
|
|
252
|
+
lines: renderBullets([renderCode("fclt find <query> [--json]")]),
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
title: "Options",
|
|
256
|
+
lines: renderTable({
|
|
257
|
+
headers: ["Option", "Meaning"],
|
|
258
|
+
rows: [
|
|
259
|
+
["--root / --global / --project", "Choose the canonical root"],
|
|
260
|
+
["--scope", "merged, global, or project"],
|
|
261
|
+
["--source", "builtin, global, or project provenance"],
|
|
262
|
+
["--json", "Print the raw JSON array"],
|
|
263
|
+
],
|
|
264
|
+
}),
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
})
|
|
268
|
+
);
|
|
257
269
|
}
|
|
258
270
|
|
|
259
271
|
function printGraphHelp() {
|
|
260
|
-
console.log(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
272
|
+
console.log(
|
|
273
|
+
renderPage({
|
|
274
|
+
title: "fclt graph",
|
|
275
|
+
subtitle: "Inspect explicit capability graph nodes and relations.",
|
|
276
|
+
sections: [
|
|
277
|
+
{
|
|
278
|
+
title: "Usage",
|
|
279
|
+
lines: renderBullets([
|
|
280
|
+
renderCode("fclt graph <asset> [--json]"),
|
|
281
|
+
renderCode("fclt graph show <asset> [--json]"),
|
|
282
|
+
renderCode("fclt graph deps <asset> [--json]"),
|
|
283
|
+
renderCode("fclt graph dependents <asset> [--json]"),
|
|
284
|
+
]),
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
title: "Notes",
|
|
288
|
+
lines: renderBullets([
|
|
289
|
+
`${renderCode("fclt graph <asset>")} defaults to ${renderCode("show")}.`,
|
|
290
|
+
"Selectors can be canonical refs, names, or graph node ids.",
|
|
291
|
+
]),
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
title: "Options",
|
|
295
|
+
lines: renderTable({
|
|
296
|
+
headers: ["Option", "Meaning"],
|
|
297
|
+
rows: [
|
|
298
|
+
["--root / --global / --project", "Choose the canonical root"],
|
|
299
|
+
["--scope", "merged, global, or project"],
|
|
300
|
+
["--source", "builtin, global, or project provenance"],
|
|
301
|
+
["--json", "Print raw graph JSON"],
|
|
302
|
+
],
|
|
303
|
+
}),
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
})
|
|
307
|
+
);
|
|
275
308
|
}
|
|
276
309
|
|
|
277
310
|
function parseListKind(argv: string[]): { kind: ListKind; startIndex: number } {
|
|
@@ -371,15 +404,16 @@ export function parseFindArgs(argv: string[]): FindCommandOptions {
|
|
|
371
404
|
return { text, json };
|
|
372
405
|
}
|
|
373
406
|
|
|
374
|
-
function parseGraphArgs(argv: string[]): GraphCommandOptions {
|
|
375
|
-
const [
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
407
|
+
export function parseGraphArgs(argv: string[]): GraphCommandOptions {
|
|
408
|
+
const [first, ...rest] = argv;
|
|
409
|
+
const hasExplicitKind =
|
|
410
|
+
first === "show" || first === "deps" || first === "dependents";
|
|
411
|
+
const kind: GraphCommandKind = hasExplicitKind ? first : "show";
|
|
412
|
+
const args = hasExplicitKind ? rest : argv;
|
|
379
413
|
|
|
380
414
|
let json = false;
|
|
381
415
|
let target: string | null = null;
|
|
382
|
-
for (const arg of
|
|
416
|
+
for (const arg of args) {
|
|
383
417
|
if (!arg) {
|
|
384
418
|
continue;
|
|
385
419
|
}
|
|
@@ -409,6 +443,68 @@ function scopeFilterForMode(
|
|
|
409
443
|
return scopeMode === "project" ? "project" : undefined;
|
|
410
444
|
}
|
|
411
445
|
|
|
446
|
+
function sourceLabel(entry: { sourceKind?: string; scope?: string }): string {
|
|
447
|
+
const source = entry.sourceKind?.trim();
|
|
448
|
+
const scope = entry.scope?.trim();
|
|
449
|
+
if (source && scope) {
|
|
450
|
+
return `${source}/${scope}`;
|
|
451
|
+
}
|
|
452
|
+
return source ?? scope ?? "merged";
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function describeFilters(filters: QueryFilters): string {
|
|
456
|
+
const parts: string[] = [];
|
|
457
|
+
|
|
458
|
+
if (filters.enabledFor) {
|
|
459
|
+
parts.push(`enabled for ${filters.enabledFor}`);
|
|
460
|
+
}
|
|
461
|
+
if (filters.untrusted) {
|
|
462
|
+
parts.push("untrusted only");
|
|
463
|
+
}
|
|
464
|
+
if (filters.flagged) {
|
|
465
|
+
parts.push("flagged only");
|
|
466
|
+
}
|
|
467
|
+
if (filters.pending) {
|
|
468
|
+
parts.push("pending only");
|
|
469
|
+
}
|
|
470
|
+
if (filters.sourceKind) {
|
|
471
|
+
parts.push(`source ${filters.sourceKind}`);
|
|
472
|
+
}
|
|
473
|
+
if (filters.scope) {
|
|
474
|
+
parts.push(`scope ${filters.scope}`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return parts.join(" • ");
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function trustBadge(trusted?: boolean): string {
|
|
481
|
+
return trusted
|
|
482
|
+
? renderBadge("trusted", "success")
|
|
483
|
+
: renderBadge("untrusted", "warn");
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function auditBadge(status?: string): string {
|
|
487
|
+
const normalized = (status ?? "pending").trim().toLowerCase();
|
|
488
|
+
if (normalized === "passed") {
|
|
489
|
+
return renderBadge("audit passed", "success");
|
|
490
|
+
}
|
|
491
|
+
if (normalized === "flagged") {
|
|
492
|
+
return renderBadge("audit flagged", "danger");
|
|
493
|
+
}
|
|
494
|
+
return renderBadge("audit pending", "warn");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function displayDescription(value?: string): string {
|
|
498
|
+
const normalized = value
|
|
499
|
+
?.trim()
|
|
500
|
+
.replaceAll('\\"', '"')
|
|
501
|
+
.replace(INLINE_NAME_DESCRIPTION_RE, "");
|
|
502
|
+
if (!normalized || normalized === ">") {
|
|
503
|
+
return "No description.";
|
|
504
|
+
}
|
|
505
|
+
return normalized;
|
|
506
|
+
}
|
|
507
|
+
|
|
412
508
|
function resolveContextualOptions(
|
|
413
509
|
argv: string[],
|
|
414
510
|
opts?: { allowSource?: boolean }
|
|
@@ -504,33 +600,70 @@ async function listCommand(argv: string[]) {
|
|
|
504
600
|
return;
|
|
505
601
|
}
|
|
506
602
|
|
|
507
|
-
|
|
603
|
+
if (entries.length === 0) {
|
|
604
|
+
console.log(
|
|
605
|
+
renderPage({
|
|
606
|
+
title: `fclt list ${opts.kind}`,
|
|
607
|
+
subtitle: "No matching entries.",
|
|
608
|
+
sections: [
|
|
609
|
+
{
|
|
610
|
+
title: "Next Steps",
|
|
611
|
+
lines: renderBullets([
|
|
612
|
+
renderCode("fclt index --force"),
|
|
613
|
+
renderCode("fclt templates list"),
|
|
614
|
+
]),
|
|
615
|
+
},
|
|
616
|
+
],
|
|
617
|
+
footer: describeFilters(opts.filters)
|
|
618
|
+
? [describeFilters(opts.filters)]
|
|
619
|
+
: undefined,
|
|
620
|
+
})
|
|
621
|
+
);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const items = entries.map((entry) => {
|
|
508
626
|
if (opts.kind === "skills") {
|
|
509
627
|
const skill = entry as SkillEntry;
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
auditStatus
|
|
628
|
+
return {
|
|
629
|
+
title: skill.name,
|
|
630
|
+
meta: sourceLabel(skill),
|
|
631
|
+
badges: [trustBadge(skill.trusted), auditBadge(skill.auditStatus)],
|
|
632
|
+
description: displayDescription(skill.description),
|
|
514
633
|
};
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
trusted
|
|
523
|
-
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (opts.kind === "mcp") {
|
|
637
|
+
const server = entry as McpEntry;
|
|
638
|
+
return {
|
|
639
|
+
title: server.name,
|
|
640
|
+
meta: sourceLabel(server),
|
|
641
|
+
badges: [trustBadge(server.trusted), auditBadge(server.auditStatus)],
|
|
642
|
+
description:
|
|
643
|
+
Array.isArray(server.enabledFor) && server.enabledFor.length > 0
|
|
644
|
+
? `Enabled for ${server.enabledFor.join(", ")}.`
|
|
645
|
+
: "No enabled-for restrictions recorded.",
|
|
524
646
|
};
|
|
525
|
-
const trustedLabel = meta.trusted === true ? "trusted" : "untrusted";
|
|
526
|
-
const auditLabel = (meta.auditStatus ?? "pending").trim().toLowerCase();
|
|
527
|
-
console.log(
|
|
528
|
-
`${entry.name}\t[${trustedLabel}; audit=${auditLabel}]${formatSourceMeta(entry)}`
|
|
529
|
-
);
|
|
530
|
-
} else {
|
|
531
|
-
console.log(`${entry.name}${formatSourceMeta(entry)}`);
|
|
532
647
|
}
|
|
533
|
-
|
|
648
|
+
|
|
649
|
+
const detailEntry = entry as AgentEntry | SnippetEntry | InstructionEntry;
|
|
650
|
+
return {
|
|
651
|
+
title: entry.name,
|
|
652
|
+
meta: sourceLabel(entry),
|
|
653
|
+
description: displayDescription(detailEntry.description),
|
|
654
|
+
};
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
console.log(
|
|
658
|
+
renderPage({
|
|
659
|
+
title: `fclt list ${opts.kind}`,
|
|
660
|
+
subtitle: `${entries.length} matching entr${entries.length === 1 ? "y" : "ies"}`,
|
|
661
|
+
sections: [{ title: "Entries", lines: renderCatalog(items) }],
|
|
662
|
+
footer: describeFilters(opts.filters)
|
|
663
|
+
? [describeFilters(opts.filters)]
|
|
664
|
+
: undefined,
|
|
665
|
+
})
|
|
666
|
+
);
|
|
534
667
|
}
|
|
535
668
|
|
|
536
669
|
async function readEntryContents(entryPath: string): Promise<string> {
|
|
@@ -589,35 +722,55 @@ async function findCommand(argv: string[]) {
|
|
|
589
722
|
return;
|
|
590
723
|
}
|
|
591
724
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
725
|
+
if (matches.length === 0) {
|
|
726
|
+
console.log(
|
|
727
|
+
renderPage({
|
|
728
|
+
title: "fclt find",
|
|
729
|
+
subtitle: `No matches for "${opts.text}".`,
|
|
730
|
+
sections: [
|
|
731
|
+
{
|
|
732
|
+
title: "Try",
|
|
733
|
+
lines: renderBullets([
|
|
734
|
+
renderCode("fclt list"),
|
|
735
|
+
renderCode("fclt index --force"),
|
|
736
|
+
]),
|
|
737
|
+
},
|
|
738
|
+
],
|
|
739
|
+
})
|
|
740
|
+
);
|
|
741
|
+
return;
|
|
595
742
|
}
|
|
743
|
+
|
|
744
|
+
console.log(
|
|
745
|
+
renderPage({
|
|
746
|
+
title: "fclt find",
|
|
747
|
+
subtitle: `${matches.length} match${matches.length === 1 ? "" : "es"} for "${opts.text}"`,
|
|
748
|
+
sections: [
|
|
749
|
+
{
|
|
750
|
+
title: "Results",
|
|
751
|
+
lines: renderCatalog(
|
|
752
|
+
matches.map((entry) => ({
|
|
753
|
+
title: `${entry.kind}:${entry.name}`,
|
|
754
|
+
meta: sourceLabel(entry),
|
|
755
|
+
description: displayDescription(entry.description),
|
|
756
|
+
}))
|
|
757
|
+
),
|
|
758
|
+
},
|
|
759
|
+
],
|
|
760
|
+
})
|
|
761
|
+
);
|
|
596
762
|
}
|
|
597
763
|
|
|
598
764
|
const SECRET_KEY_RE = /(TOKEN|KEY|SECRET|PASSWORD|PASS|BEARER)/i;
|
|
599
765
|
const SECRETY_STRING_RE =
|
|
600
766
|
/\b(sk-[A-Za-z0-9]{10,}|ghp_[A-Za-z0-9]{10,}|github_pat_[A-Za-z0-9_]{10,})\b/g;
|
|
767
|
+
const INLINE_NAME_DESCRIPTION_RE = /^name:\s+\S+\s+description:\s*/i;
|
|
768
|
+
const TRAILING_NEWLINE_RE = /\n$/;
|
|
601
769
|
|
|
602
770
|
function redactPossibleSecrets(value: string): string {
|
|
603
771
|
return value.replace(SECRETY_STRING_RE, "<redacted>");
|
|
604
772
|
}
|
|
605
773
|
|
|
606
|
-
function formatSourceMeta(entry: {
|
|
607
|
-
sourceKind?: string;
|
|
608
|
-
scope?: string;
|
|
609
|
-
}): string {
|
|
610
|
-
const source = entry.sourceKind?.trim();
|
|
611
|
-
const scope = entry.scope?.trim();
|
|
612
|
-
if (!(source || scope)) {
|
|
613
|
-
return "";
|
|
614
|
-
}
|
|
615
|
-
if (source && scope) {
|
|
616
|
-
return `\t[${source}/${scope}]`;
|
|
617
|
-
}
|
|
618
|
-
return `\t[${source ?? scope}]`;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
774
|
function sanitizeForDisplay(value: unknown): unknown {
|
|
622
775
|
if (typeof value === "string") {
|
|
623
776
|
return redactPossibleSecrets(value);
|
|
@@ -796,10 +949,28 @@ async function showCommand(argv: string[]) {
|
|
|
796
949
|
}
|
|
797
950
|
}
|
|
798
951
|
|
|
799
|
-
console.log(
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
952
|
+
console.log(
|
|
953
|
+
renderPage({
|
|
954
|
+
title: `fclt show ${kind}:${entry.name}`,
|
|
955
|
+
subtitle: contentPath,
|
|
956
|
+
sections: [
|
|
957
|
+
{
|
|
958
|
+
title: "Metadata",
|
|
959
|
+
lines: renderJsonBlock(displayEntry),
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
title: "Contents",
|
|
963
|
+
lines: displayContents.replace(TRAILING_NEWLINE_RE, "").split("\n"),
|
|
964
|
+
},
|
|
965
|
+
],
|
|
966
|
+
footer:
|
|
967
|
+
kind === "mcp" && !showSecrets
|
|
968
|
+
? [
|
|
969
|
+
"Secrets are redacted. Re-run with --show-secrets only when you need raw values.",
|
|
970
|
+
]
|
|
971
|
+
: undefined,
|
|
972
|
+
})
|
|
973
|
+
);
|
|
803
974
|
}
|
|
804
975
|
|
|
805
976
|
async function graphCommand(argv: string[]) {
|
|
@@ -858,51 +1029,126 @@ async function graphCommand(argv: string[]) {
|
|
|
858
1029
|
}
|
|
859
1030
|
|
|
860
1031
|
if (opts.kind === "show") {
|
|
861
|
-
console.log(
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1032
|
+
console.log(
|
|
1033
|
+
renderPage({
|
|
1034
|
+
title: `fclt graph ${node.id}`,
|
|
1035
|
+
subtitle: `${node.kind} • ${sourceLabel(node)}`,
|
|
1036
|
+
sections: [
|
|
1037
|
+
{
|
|
1038
|
+
title: "Node",
|
|
1039
|
+
lines: renderKeyValue([
|
|
1040
|
+
["id", node.id],
|
|
1041
|
+
["kind", node.kind],
|
|
1042
|
+
["name", node.name],
|
|
1043
|
+
["path", node.path ?? "—"],
|
|
1044
|
+
["canonicalRef", node.canonicalRef ?? "—"],
|
|
1045
|
+
]),
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
title: "Dependencies",
|
|
1049
|
+
lines:
|
|
1050
|
+
deps.length > 0
|
|
1051
|
+
? renderCatalog(
|
|
1052
|
+
deps.map((dep) => ({
|
|
1053
|
+
title: dep.node.id,
|
|
1054
|
+
meta: dep.edge.kind,
|
|
1055
|
+
details: [dep.edge.locator],
|
|
1056
|
+
}))
|
|
1057
|
+
)
|
|
1058
|
+
: ["No dependencies."],
|
|
1059
|
+
},
|
|
1060
|
+
{
|
|
1061
|
+
title: "Dependents",
|
|
1062
|
+
lines:
|
|
1063
|
+
dependents.length > 0
|
|
1064
|
+
? renderCatalog(
|
|
1065
|
+
dependents.map((dependent) => ({
|
|
1066
|
+
title: dependent.node.id,
|
|
1067
|
+
meta: dependent.edge.kind,
|
|
1068
|
+
details: [dependent.edge.locator],
|
|
1069
|
+
}))
|
|
1070
|
+
)
|
|
1071
|
+
: ["No dependents."],
|
|
1072
|
+
},
|
|
1073
|
+
],
|
|
1074
|
+
})
|
|
1075
|
+
);
|
|
875
1076
|
return;
|
|
876
1077
|
}
|
|
877
1078
|
|
|
878
1079
|
const relations = opts.kind === "deps" ? deps : dependents;
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1080
|
+
console.log(
|
|
1081
|
+
renderPage({
|
|
1082
|
+
title: `fclt graph ${opts.kind}`,
|
|
1083
|
+
subtitle: `${relations.length} relation${relations.length === 1 ? "" : "s"} for ${node.id}`,
|
|
1084
|
+
sections: [
|
|
1085
|
+
{
|
|
1086
|
+
title: opts.kind === "deps" ? "Dependencies" : "Dependents",
|
|
1087
|
+
lines:
|
|
1088
|
+
relations.length > 0
|
|
1089
|
+
? renderCatalog(
|
|
1090
|
+
relations.map((relation) => ({
|
|
1091
|
+
title: relation.node.id,
|
|
1092
|
+
meta: relation.edge.kind,
|
|
1093
|
+
details: [relation.edge.locator],
|
|
1094
|
+
}))
|
|
1095
|
+
)
|
|
1096
|
+
: ["No relations found."],
|
|
1097
|
+
},
|
|
1098
|
+
],
|
|
1099
|
+
})
|
|
1100
|
+
);
|
|
884
1101
|
} catch (err) {
|
|
885
1102
|
console.error(err instanceof Error ? err.message : String(err));
|
|
886
1103
|
process.exitCode = 1;
|
|
887
1104
|
}
|
|
888
1105
|
}
|
|
889
1106
|
|
|
890
|
-
function adaptersCommand(argv: string[]) {
|
|
1107
|
+
async function adaptersCommand(argv: string[]) {
|
|
891
1108
|
if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
|
|
892
1109
|
console.log(
|
|
893
|
-
|
|
1110
|
+
renderPage({
|
|
1111
|
+
title: "fclt adapters",
|
|
1112
|
+
subtitle: "List registered tool adapters.",
|
|
1113
|
+
sections: [
|
|
1114
|
+
{
|
|
1115
|
+
title: "Usage",
|
|
1116
|
+
lines: renderBullets([renderCode("fclt adapters")]),
|
|
1117
|
+
},
|
|
1118
|
+
],
|
|
1119
|
+
})
|
|
894
1120
|
);
|
|
895
1121
|
return;
|
|
896
1122
|
}
|
|
1123
|
+
const { getAllAdapters } = await import("./adapters");
|
|
897
1124
|
const adapters = getAllAdapters();
|
|
898
1125
|
if (!adapters.length) {
|
|
899
|
-
console.log(
|
|
1126
|
+
console.log(
|
|
1127
|
+
renderPage({
|
|
1128
|
+
title: "fclt adapters",
|
|
1129
|
+
subtitle: "No adapters registered.",
|
|
1130
|
+
sections: [],
|
|
1131
|
+
})
|
|
1132
|
+
);
|
|
900
1133
|
return;
|
|
901
1134
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1135
|
+
console.log(
|
|
1136
|
+
renderPage({
|
|
1137
|
+
title: "fclt adapters",
|
|
1138
|
+
subtitle: `${adapters.length} registered adapter${adapters.length === 1 ? "" : "s"}`,
|
|
1139
|
+
sections: [
|
|
1140
|
+
{
|
|
1141
|
+
title: "Adapters",
|
|
1142
|
+
lines: renderCatalog(
|
|
1143
|
+
adapters.map((adapter) => ({
|
|
1144
|
+
title: adapter.id,
|
|
1145
|
+
description: `Versions: ${adapter.versions.join(", ")}`,
|
|
1146
|
+
}))
|
|
1147
|
+
),
|
|
1148
|
+
},
|
|
1149
|
+
],
|
|
1150
|
+
})
|
|
1151
|
+
);
|
|
906
1152
|
}
|
|
907
1153
|
|
|
908
1154
|
async function main(argv: string[]) {
|
|
@@ -914,28 +1160,35 @@ async function main(argv: string[]) {
|
|
|
914
1160
|
|
|
915
1161
|
// Convenience: allow `fclt --show-duplicates` as shorthand for `fclt scan --show-duplicates`.
|
|
916
1162
|
if (cmd === "--show-duplicates") {
|
|
1163
|
+
const { scanCommand } = await import("./scan");
|
|
917
1164
|
await scanCommand([cmd, ...rest]);
|
|
918
1165
|
return;
|
|
919
1166
|
}
|
|
920
1167
|
|
|
921
1168
|
switch (cmd) {
|
|
922
1169
|
case "scan":
|
|
923
|
-
await scanCommand(rest);
|
|
1170
|
+
await import("./scan").then(({ scanCommand }) => scanCommand(rest));
|
|
924
1171
|
return;
|
|
925
1172
|
case "audit":
|
|
926
|
-
await auditCommand(rest);
|
|
1173
|
+
await import("./audit").then(({ auditCommand }) => auditCommand(rest));
|
|
927
1174
|
return;
|
|
928
1175
|
case "migrate":
|
|
929
|
-
await
|
|
1176
|
+
await import("./migrate").then(({ migrateCommand }) =>
|
|
1177
|
+
migrateCommand(rest)
|
|
1178
|
+
);
|
|
930
1179
|
return;
|
|
931
1180
|
case "doctor":
|
|
932
|
-
await doctorCommand(rest);
|
|
1181
|
+
await import("./doctor").then(({ doctorCommand }) => doctorCommand(rest));
|
|
933
1182
|
return;
|
|
934
1183
|
case "consolidate":
|
|
935
|
-
await
|
|
1184
|
+
await import("./consolidate").then(({ consolidateCommand }) =>
|
|
1185
|
+
consolidateCommand(rest)
|
|
1186
|
+
);
|
|
936
1187
|
return;
|
|
937
1188
|
case "index":
|
|
938
|
-
await
|
|
1189
|
+
await import("./index-builder").then(({ indexCommand }) =>
|
|
1190
|
+
indexCommand(rest)
|
|
1191
|
+
);
|
|
939
1192
|
return;
|
|
940
1193
|
case "list":
|
|
941
1194
|
await listCommand(rest);
|
|
@@ -950,65 +1203,91 @@ async function main(argv: string[]) {
|
|
|
950
1203
|
await graphCommand(rest);
|
|
951
1204
|
return;
|
|
952
1205
|
case "ai":
|
|
953
|
-
await aiCommand(rest);
|
|
1206
|
+
await import("./ai").then(({ aiCommand }) => aiCommand(rest));
|
|
954
1207
|
return;
|
|
955
1208
|
case "adapters":
|
|
956
1209
|
await adaptersCommand(rest);
|
|
957
1210
|
return;
|
|
958
1211
|
case "trust":
|
|
959
|
-
await trustCommand(rest);
|
|
1212
|
+
await import("./trust").then(({ trustCommand }) => trustCommand(rest));
|
|
960
1213
|
return;
|
|
961
1214
|
case "untrust":
|
|
962
|
-
await
|
|
1215
|
+
await import("./trust").then(({ untrustCommand }) =>
|
|
1216
|
+
untrustCommand(rest)
|
|
1217
|
+
);
|
|
963
1218
|
return;
|
|
964
1219
|
case "manage":
|
|
965
|
-
await manageCommand(rest);
|
|
1220
|
+
await import("./manage").then(({ manageCommand }) => manageCommand(rest));
|
|
966
1221
|
return;
|
|
967
1222
|
case "unmanage":
|
|
968
|
-
await
|
|
1223
|
+
await import("./manage").then(({ unmanageCommand }) =>
|
|
1224
|
+
unmanageCommand(rest)
|
|
1225
|
+
);
|
|
969
1226
|
return;
|
|
970
1227
|
case "managed":
|
|
971
|
-
await
|
|
1228
|
+
await import("./manage").then(({ managedCommand }) =>
|
|
1229
|
+
managedCommand(rest)
|
|
1230
|
+
);
|
|
972
1231
|
return;
|
|
973
1232
|
case "enable":
|
|
974
|
-
await
|
|
1233
|
+
await import("./enable-disable").then(({ enableCommand }) =>
|
|
1234
|
+
enableCommand(rest)
|
|
1235
|
+
);
|
|
975
1236
|
return;
|
|
976
1237
|
case "disable":
|
|
977
|
-
await
|
|
1238
|
+
await import("./enable-disable").then(({ disableCommand }) =>
|
|
1239
|
+
disableCommand(rest)
|
|
1240
|
+
);
|
|
978
1241
|
return;
|
|
979
1242
|
case "sync":
|
|
980
|
-
await syncCommand(rest);
|
|
1243
|
+
await import("./manage").then(({ syncCommand }) => syncCommand(rest));
|
|
981
1244
|
return;
|
|
982
1245
|
case "autosync":
|
|
983
|
-
await
|
|
1246
|
+
await import("./autosync").then(({ autosyncCommand }) =>
|
|
1247
|
+
autosyncCommand(rest)
|
|
1248
|
+
);
|
|
984
1249
|
return;
|
|
985
1250
|
case "search":
|
|
986
|
-
await searchCommand(rest);
|
|
1251
|
+
await import("./remote").then(({ searchCommand }) => searchCommand(rest));
|
|
987
1252
|
return;
|
|
988
1253
|
case "install":
|
|
989
|
-
await
|
|
1254
|
+
await import("./remote").then(({ installCommand }) =>
|
|
1255
|
+
installCommand(rest)
|
|
1256
|
+
);
|
|
990
1257
|
return;
|
|
991
1258
|
case "update":
|
|
992
1259
|
if (rest.includes("--self")) {
|
|
993
|
-
await
|
|
1260
|
+
await import("./self-update").then(({ selfUpdateCommand }) =>
|
|
1261
|
+
selfUpdateCommand(rest.filter((arg) => arg !== "--self"))
|
|
1262
|
+
);
|
|
994
1263
|
return;
|
|
995
1264
|
}
|
|
996
|
-
await updateCommand(rest);
|
|
1265
|
+
await import("./remote").then(({ updateCommand }) => updateCommand(rest));
|
|
997
1266
|
return;
|
|
998
1267
|
case "self-update":
|
|
999
|
-
await
|
|
1268
|
+
await import("./self-update").then(({ selfUpdateCommand }) =>
|
|
1269
|
+
selfUpdateCommand(rest)
|
|
1270
|
+
);
|
|
1000
1271
|
return;
|
|
1001
1272
|
case "verify-source":
|
|
1002
|
-
await
|
|
1273
|
+
await import("./remote").then(({ verifySourceCommand }) =>
|
|
1274
|
+
verifySourceCommand(rest)
|
|
1275
|
+
);
|
|
1003
1276
|
return;
|
|
1004
1277
|
case "templates":
|
|
1005
|
-
await
|
|
1278
|
+
await import("./remote").then(({ templatesCommand }) =>
|
|
1279
|
+
templatesCommand(rest)
|
|
1280
|
+
);
|
|
1006
1281
|
return;
|
|
1007
1282
|
case "sources":
|
|
1008
|
-
await
|
|
1283
|
+
await import("./remote").then(({ sourcesCommand }) =>
|
|
1284
|
+
sourcesCommand(rest)
|
|
1285
|
+
);
|
|
1009
1286
|
return;
|
|
1010
1287
|
case "snippets":
|
|
1011
|
-
await
|
|
1288
|
+
await import("./snippets-cli").then(({ snippetsCommand }) =>
|
|
1289
|
+
snippetsCommand(rest)
|
|
1290
|
+
);
|
|
1012
1291
|
return;
|
|
1013
1292
|
default:
|
|
1014
1293
|
console.error(`Unknown command: ${cmd}`);
|