facult 1.0.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/LICENSE +21 -0
- package/README.md +383 -0
- package/bin/facult.cjs +302 -0
- package/package.json +78 -0
- package/src/adapters/claude-cli.ts +18 -0
- package/src/adapters/claude-desktop.ts +15 -0
- package/src/adapters/clawdbot.ts +18 -0
- package/src/adapters/codex.ts +19 -0
- package/src/adapters/cursor.ts +18 -0
- package/src/adapters/index.ts +69 -0
- package/src/adapters/mcp.ts +270 -0
- package/src/adapters/reference.ts +9 -0
- package/src/adapters/skills.ts +47 -0
- package/src/adapters/types.ts +42 -0
- package/src/adapters/version.ts +18 -0
- package/src/audit/agent.ts +1071 -0
- package/src/audit/index.ts +74 -0
- package/src/audit/static.ts +1130 -0
- package/src/audit/tui.ts +704 -0
- package/src/audit/types.ts +68 -0
- package/src/audit/update-index.ts +115 -0
- package/src/conflicts.ts +135 -0
- package/src/consolidate-conflict-action.ts +57 -0
- package/src/consolidate.ts +1637 -0
- package/src/enable-disable.ts +349 -0
- package/src/index-builder.ts +562 -0
- package/src/index.ts +589 -0
- package/src/manage.ts +894 -0
- package/src/migrate.ts +272 -0
- package/src/paths.ts +238 -0
- package/src/quarantine.ts +217 -0
- package/src/query.ts +186 -0
- package/src/remote-manifest-integrity.ts +367 -0
- package/src/remote-providers.ts +905 -0
- package/src/remote-source-policy.ts +237 -0
- package/src/remote-sources.ts +162 -0
- package/src/remote-types.ts +136 -0
- package/src/remote.ts +1970 -0
- package/src/scan.ts +2427 -0
- package/src/schema.ts +39 -0
- package/src/self-update.ts +408 -0
- package/src/snippets-cli.ts +293 -0
- package/src/snippets.ts +706 -0
- package/src/source-trust.ts +203 -0
- package/src/trust-list.ts +232 -0
- package/src/trust.ts +170 -0
- package/src/tui.ts +118 -0
- package/src/util/codex-toml.ts +126 -0
- package/src/util/json.ts +32 -0
- package/src/util/skills.ts +55 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { getAllAdapters } from "./adapters";
|
|
5
|
+
import { auditCommand } from "./audit";
|
|
6
|
+
import { consolidateCommand } from "./consolidate";
|
|
7
|
+
import { disableCommand, enableCommand } from "./enable-disable";
|
|
8
|
+
import type {
|
|
9
|
+
AgentEntry,
|
|
10
|
+
FacultIndex,
|
|
11
|
+
McpEntry,
|
|
12
|
+
SkillEntry,
|
|
13
|
+
SnippetEntry,
|
|
14
|
+
} from "./index-builder";
|
|
15
|
+
import { indexCommand } from "./index-builder";
|
|
16
|
+
import {
|
|
17
|
+
manageCommand,
|
|
18
|
+
managedCommand,
|
|
19
|
+
syncCommand,
|
|
20
|
+
unmanageCommand,
|
|
21
|
+
} from "./manage";
|
|
22
|
+
import { migrateCommand } from "./migrate";
|
|
23
|
+
import type { QueryFilters } from "./query";
|
|
24
|
+
import {
|
|
25
|
+
filterAgents,
|
|
26
|
+
filterMcp,
|
|
27
|
+
filterSkills,
|
|
28
|
+
filterSnippets,
|
|
29
|
+
loadIndex,
|
|
30
|
+
} from "./query";
|
|
31
|
+
import {
|
|
32
|
+
installCommand,
|
|
33
|
+
searchCommand,
|
|
34
|
+
sourcesCommand,
|
|
35
|
+
templatesCommand,
|
|
36
|
+
updateCommand,
|
|
37
|
+
verifySourceCommand,
|
|
38
|
+
} from "./remote";
|
|
39
|
+
import { scanCommand } from "./scan";
|
|
40
|
+
import { selfUpdateCommand } from "./self-update";
|
|
41
|
+
import { snippetsCommand } from "./snippets-cli";
|
|
42
|
+
import { trustCommand, untrustCommand } from "./trust";
|
|
43
|
+
import { parseJsonLenient } from "./util/json";
|
|
44
|
+
|
|
45
|
+
type ListKind = "skills" | "mcp" | "agents" | "snippets";
|
|
46
|
+
|
|
47
|
+
const LIST_KINDS: ListKind[] = ["skills", "mcp", "agents", "snippets"];
|
|
48
|
+
|
|
49
|
+
export interface ListCommandOptions {
|
|
50
|
+
kind: ListKind;
|
|
51
|
+
filters: QueryFilters;
|
|
52
|
+
json: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function printHelp() {
|
|
56
|
+
console.log(`facult — inspect local agent configs for skills + MCP servers
|
|
57
|
+
|
|
58
|
+
Usage:
|
|
59
|
+
facult scan [--json] [--show-duplicates] [--tui] [--from <path>]
|
|
60
|
+
facult audit [--from <path>]
|
|
61
|
+
facult audit --non-interactive [name|mcp:<name>] [--severity <level>] [--rules <path>] [--from <path>] [--json]
|
|
62
|
+
facult audit --non-interactive [name|mcp:<name>] --with <claude|codex> [--from <path>] [--max-items <n|all>] [--json]
|
|
63
|
+
facult migrate [--from <path>] [--dry-run] [--move] [--write-config]
|
|
64
|
+
facult consolidate [--force] [--auto <mode>] [scan options]
|
|
65
|
+
facult index [--force]
|
|
66
|
+
facult list [skills|mcp|agents|snippets] [--enabled-for TOOL] [--untrusted] [--flagged] [--pending] [--json]
|
|
67
|
+
facult show <name>
|
|
68
|
+
facult show mcp:<name> [--show-secrets]
|
|
69
|
+
facult adapters
|
|
70
|
+
facult trust <name> [moreNames...]
|
|
71
|
+
facult untrust <name> [moreNames...]
|
|
72
|
+
facult manage <tool>
|
|
73
|
+
facult unmanage <tool>
|
|
74
|
+
facult managed
|
|
75
|
+
facult enable <name> [moreNames...] [--for <tools>]
|
|
76
|
+
facult disable <name> [moreNames...] [--for <tools>]
|
|
77
|
+
facult sync [tool] [--dry-run]
|
|
78
|
+
facult search <query> [--index <name>] [--limit <n>]
|
|
79
|
+
facult install <index:item> [--as <name>] [--dry-run] [--force] [--strict-source-trust]
|
|
80
|
+
facult update [--apply] [--strict-source-trust]
|
|
81
|
+
facult update --self [--version <x.y.z|latest>] [--dry-run]
|
|
82
|
+
facult self-update [--version <x.y.z|latest>] [--dry-run]
|
|
83
|
+
facult verify-source <name> [--json]
|
|
84
|
+
facult sources <cmd> [args...]
|
|
85
|
+
facult templates <cmd> [args...]
|
|
86
|
+
facult snippets <cmd> [args...]
|
|
87
|
+
facult --show-duplicates
|
|
88
|
+
|
|
89
|
+
Commands:
|
|
90
|
+
scan Scan common config locations (Cursor, Claude, Claude Desktop, etc.)
|
|
91
|
+
audit Security audits (interactive by default; use --non-interactive for scripts)
|
|
92
|
+
migrate Copy/move a legacy canonical store to ~/agents/.facult
|
|
93
|
+
consolidate Deduplicate and copy skills + MCP configs (interactive or --auto)
|
|
94
|
+
index Build a queryable index from the canonical store (see FACULT_ROOT_DIR)
|
|
95
|
+
list List indexed skills, MCP servers, agents, or snippets
|
|
96
|
+
show Show a single indexed entry, including file contents
|
|
97
|
+
adapters List registered tool adapters
|
|
98
|
+
trust Mark a skill or MCP server as trusted (annotation only)
|
|
99
|
+
untrust Remove trusted annotation
|
|
100
|
+
manage Back up tool config and enter managed mode
|
|
101
|
+
unmanage Restore backups and exit managed mode
|
|
102
|
+
managed List tools in managed mode
|
|
103
|
+
enable Enable skills or MCP servers for tools
|
|
104
|
+
disable Disable skills or MCP servers for tools
|
|
105
|
+
sync Sync managed tools with canonical configs
|
|
106
|
+
search Search remote indices (builtin + provider aliases + configured)
|
|
107
|
+
install Install an item from a remote index
|
|
108
|
+
update Check/apply updates for remotely installed items
|
|
109
|
+
self-update Update facult itself based on install method
|
|
110
|
+
verify-source Verify source trust and manifest integrity/signature status
|
|
111
|
+
sources Manage source trust policy for remote indices
|
|
112
|
+
templates Scaffold DX-first templates (skills/instructions/MCP/snippets)
|
|
113
|
+
snippets Sync reusable snippet blocks into config files
|
|
114
|
+
|
|
115
|
+
Options:
|
|
116
|
+
--json Print full JSON (ScanResult or list output)
|
|
117
|
+
--show-duplicates Print duplicates for skills, MCP servers, and hook assets
|
|
118
|
+
--tui Render scan output in an interactive TUI (skills list)
|
|
119
|
+
--from Add one or more additional scan roots (repeatable): --from ~/dev
|
|
120
|
+
--from-ignore (scan) Ignore directories by basename under --from roots (repeatable)
|
|
121
|
+
--from-no-default-ignore (scan) Disable the default ignore list for --from scans
|
|
122
|
+
--from-max-visits (scan) Max directories visited per --from root before truncating
|
|
123
|
+
--from-max-results (scan) Max discovered paths per --from root before truncating
|
|
124
|
+
--non-interactive (audit) Run static/agent audit non-interactively (for scripts)
|
|
125
|
+
--severity Minimum severity to include in audit output (low|medium|high|critical)
|
|
126
|
+
--rules Path to an audit rules YAML file (default: ~/.facult/audit-rules.yaml)
|
|
127
|
+
--with (audit) Agent tool: claude|codex
|
|
128
|
+
--max-items (audit) Max items to send to the agent (n|all)
|
|
129
|
+
--force Re-copy items already consolidated OR rebuild index from scratch
|
|
130
|
+
--auto Auto-resolve consolidate conflicts: keep-newest, keep-current, keep-incoming
|
|
131
|
+
--enabled-for Filter list to entries enabled for a specific tool
|
|
132
|
+
--untrusted Filter list to entries that are not trusted
|
|
133
|
+
--flagged Filter list to entries flagged by audit
|
|
134
|
+
--pending Filter list to entries pending audit
|
|
135
|
+
--for Comma-separated list of tools for enable/disable
|
|
136
|
+
--dry-run Show what sync would change
|
|
137
|
+
--as Install/scaffold target name override
|
|
138
|
+
--limit Max results for search
|
|
139
|
+
--apply Apply updates (update command)
|
|
140
|
+
--self (update) run self-update flow instead of remote item updates
|
|
141
|
+
--strict-source-trust Enforce trust-only remote install/update actions
|
|
142
|
+
--show-secrets (show) Print raw secret values (unsafe)
|
|
143
|
+
`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function printListHelp() {
|
|
147
|
+
console.log(`facult list — list indexed entries from the canonical store
|
|
148
|
+
|
|
149
|
+
Usage:
|
|
150
|
+
facult list [skills|mcp|agents|snippets] [options]
|
|
151
|
+
|
|
152
|
+
Options:
|
|
153
|
+
--enabled-for TOOL Only include entries enabled for a tool
|
|
154
|
+
--untrusted Only include entries that are not trusted
|
|
155
|
+
--flagged Only include entries flagged by audit
|
|
156
|
+
--pending Only include entries pending audit
|
|
157
|
+
--json Print JSON array
|
|
158
|
+
`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function printShowHelp() {
|
|
162
|
+
console.log(`facult show — show a single indexed entry (and file contents)
|
|
163
|
+
|
|
164
|
+
Usage:
|
|
165
|
+
facult show <name>
|
|
166
|
+
facult show mcp:<name> [--show-secrets]
|
|
167
|
+
|
|
168
|
+
Options:
|
|
169
|
+
--show-secrets (mcp) Print raw secret values (unsafe)
|
|
170
|
+
`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function parseListKind(argv: string[]): { kind: ListKind; startIndex: number } {
|
|
174
|
+
const first = argv[0];
|
|
175
|
+
if (!first || first.startsWith("-")) {
|
|
176
|
+
return { kind: "skills", startIndex: 0 };
|
|
177
|
+
}
|
|
178
|
+
if (LIST_KINDS.includes(first as ListKind)) {
|
|
179
|
+
return { kind: first as ListKind, startIndex: 1 };
|
|
180
|
+
}
|
|
181
|
+
throw new Error(`Unknown list type: ${first}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function parseEnabledForArg(
|
|
185
|
+
arg: string,
|
|
186
|
+
nextArg?: string
|
|
187
|
+
): { tool: string; advance: number } | null {
|
|
188
|
+
if (arg === "--enabled-for") {
|
|
189
|
+
if (!nextArg) {
|
|
190
|
+
throw new Error("--enabled-for requires a tool name");
|
|
191
|
+
}
|
|
192
|
+
return { tool: nextArg, advance: 1 };
|
|
193
|
+
}
|
|
194
|
+
if (arg.startsWith("--enabled-for=")) {
|
|
195
|
+
const tool = arg.slice("--enabled-for=".length);
|
|
196
|
+
if (!tool) {
|
|
197
|
+
throw new Error("--enabled-for requires a tool name");
|
|
198
|
+
}
|
|
199
|
+
return { tool, advance: 0 };
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function parseListArgs(argv: string[]): ListCommandOptions {
|
|
205
|
+
const { kind, startIndex } = parseListKind(argv);
|
|
206
|
+
const filters: QueryFilters = {};
|
|
207
|
+
let json = false;
|
|
208
|
+
|
|
209
|
+
for (let i = startIndex; i < argv.length; i++) {
|
|
210
|
+
const arg = argv[i];
|
|
211
|
+
if (!arg) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (arg === "--json") {
|
|
215
|
+
json = true;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (arg === "--untrusted") {
|
|
219
|
+
filters.untrusted = true;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (arg === "--flagged") {
|
|
223
|
+
filters.flagged = true;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (arg === "--pending") {
|
|
227
|
+
filters.pending = true;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const enabledFor = parseEnabledForArg(arg, argv[i + 1]);
|
|
232
|
+
if (enabledFor) {
|
|
233
|
+
filters.enabledFor = enabledFor.tool;
|
|
234
|
+
i += enabledFor.advance;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { kind, filters, json };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function listCommand(argv: string[]) {
|
|
245
|
+
if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
|
|
246
|
+
printListHelp();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let opts: ListCommandOptions;
|
|
251
|
+
try {
|
|
252
|
+
opts = parseListArgs(argv);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
255
|
+
process.exitCode = 1;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
let index: FacultIndex;
|
|
260
|
+
try {
|
|
261
|
+
index = await loadIndex();
|
|
262
|
+
} catch (err) {
|
|
263
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
264
|
+
process.exitCode = 1;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let entries: SkillEntry[] | McpEntry[] | AgentEntry[] | SnippetEntry[] = [];
|
|
269
|
+
|
|
270
|
+
switch (opts.kind) {
|
|
271
|
+
case "skills":
|
|
272
|
+
entries = filterSkills(index.skills, opts.filters);
|
|
273
|
+
break;
|
|
274
|
+
case "mcp":
|
|
275
|
+
entries = filterMcp(index.mcp?.servers ?? {}, opts.filters);
|
|
276
|
+
break;
|
|
277
|
+
case "agents":
|
|
278
|
+
entries = filterAgents(index.agents ?? {}, opts.filters);
|
|
279
|
+
break;
|
|
280
|
+
case "snippets":
|
|
281
|
+
entries = filterSnippets(index.snippets ?? {}, opts.filters);
|
|
282
|
+
break;
|
|
283
|
+
default:
|
|
284
|
+
entries = [];
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (opts.json) {
|
|
289
|
+
console.log(`${JSON.stringify(entries, null, 2)}`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
for (const entry of entries) {
|
|
294
|
+
if (opts.kind === "skills") {
|
|
295
|
+
const skill = entry as SkillEntry;
|
|
296
|
+
const desc = skill.description ? `\t${skill.description}` : "";
|
|
297
|
+
const meta = skill as SkillEntry & {
|
|
298
|
+
trusted?: boolean;
|
|
299
|
+
auditStatus?: string;
|
|
300
|
+
};
|
|
301
|
+
const trustedLabel = meta.trusted === true ? "trusted" : "untrusted";
|
|
302
|
+
const auditLabel = (meta.auditStatus ?? "pending").trim().toLowerCase();
|
|
303
|
+
console.log(
|
|
304
|
+
`${skill.name}${desc}\t[${trustedLabel}; audit=${auditLabel}]`
|
|
305
|
+
);
|
|
306
|
+
} else if (opts.kind === "mcp") {
|
|
307
|
+
const meta = entry as McpEntry & {
|
|
308
|
+
trusted?: boolean;
|
|
309
|
+
auditStatus?: string;
|
|
310
|
+
};
|
|
311
|
+
const trustedLabel = meta.trusted === true ? "trusted" : "untrusted";
|
|
312
|
+
const auditLabel = (meta.auditStatus ?? "pending").trim().toLowerCase();
|
|
313
|
+
console.log(`${entry.name}\t[${trustedLabel}; audit=${auditLabel}]`);
|
|
314
|
+
} else {
|
|
315
|
+
console.log(entry.name);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function readEntryContents(entryPath: string): Promise<string> {
|
|
321
|
+
const file = Bun.file(entryPath);
|
|
322
|
+
if (!(await file.exists())) {
|
|
323
|
+
throw new Error(`File not found: ${entryPath}`);
|
|
324
|
+
}
|
|
325
|
+
return file.text();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const SECRET_KEY_RE = /(TOKEN|KEY|SECRET|PASSWORD|PASS|BEARER)/i;
|
|
329
|
+
const SECRETY_STRING_RE =
|
|
330
|
+
/\b(sk-[A-Za-z0-9]{10,}|ghp_[A-Za-z0-9]{10,}|github_pat_[A-Za-z0-9_]{10,})\b/g;
|
|
331
|
+
|
|
332
|
+
function redactPossibleSecrets(value: string): string {
|
|
333
|
+
return value.replace(SECRETY_STRING_RE, "<redacted>");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function sanitizeForDisplay(value: unknown): unknown {
|
|
337
|
+
if (typeof value === "string") {
|
|
338
|
+
return redactPossibleSecrets(value);
|
|
339
|
+
}
|
|
340
|
+
if (Array.isArray(value)) {
|
|
341
|
+
return value.map(sanitizeForDisplay);
|
|
342
|
+
}
|
|
343
|
+
if (!value || typeof value !== "object") {
|
|
344
|
+
return value;
|
|
345
|
+
}
|
|
346
|
+
const out: Record<string, unknown> = {};
|
|
347
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
|
348
|
+
if (SECRET_KEY_RE.test(k)) {
|
|
349
|
+
out[k] = "<redacted>";
|
|
350
|
+
} else {
|
|
351
|
+
out[k] = sanitizeForDisplay(v);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return out;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function showCommand(argv: string[]) {
|
|
358
|
+
if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
|
|
359
|
+
printShowHelp();
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
let showSecrets = false;
|
|
364
|
+
let raw: string | null = null;
|
|
365
|
+
for (const arg of argv) {
|
|
366
|
+
if (!arg) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (arg === "--show-secrets") {
|
|
370
|
+
showSecrets = true;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (arg.startsWith("-")) {
|
|
374
|
+
console.error(`Unknown option: ${arg}`);
|
|
375
|
+
process.exitCode = 1;
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (raw) {
|
|
379
|
+
console.error("show accepts a single name");
|
|
380
|
+
process.exitCode = 1;
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
raw = arg;
|
|
384
|
+
}
|
|
385
|
+
if (!raw) {
|
|
386
|
+
console.error("show requires a name");
|
|
387
|
+
process.exitCode = 1;
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
let index: FacultIndex;
|
|
392
|
+
try {
|
|
393
|
+
index = await loadIndex();
|
|
394
|
+
} catch (err) {
|
|
395
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
396
|
+
process.exitCode = 1;
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let kind: ListKind | "mcp" = "skills";
|
|
401
|
+
let name = raw;
|
|
402
|
+
|
|
403
|
+
if (raw.startsWith("mcp:")) {
|
|
404
|
+
kind = "mcp";
|
|
405
|
+
name = raw.slice("mcp:".length);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
let entry: SkillEntry | McpEntry | AgentEntry | SnippetEntry | null = null;
|
|
409
|
+
const skill = index.skills[name];
|
|
410
|
+
const mcpServer = index.mcp?.servers?.[name];
|
|
411
|
+
const agent = index.agents?.[name];
|
|
412
|
+
const snippet = index.snippets?.[name];
|
|
413
|
+
|
|
414
|
+
if (kind === "skills" && skill) {
|
|
415
|
+
entry = skill;
|
|
416
|
+
} else if (kind === "mcp" && mcpServer) {
|
|
417
|
+
entry = mcpServer;
|
|
418
|
+
} else if (kind === "skills" && agent) {
|
|
419
|
+
kind = "agents";
|
|
420
|
+
entry = agent;
|
|
421
|
+
} else if (kind === "skills" && snippet) {
|
|
422
|
+
kind = "snippets";
|
|
423
|
+
entry = snippet;
|
|
424
|
+
} else if (kind === "skills" && mcpServer) {
|
|
425
|
+
kind = "mcp";
|
|
426
|
+
entry = mcpServer;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (!entry) {
|
|
430
|
+
console.error(`Entry not found: ${raw}`);
|
|
431
|
+
process.exitCode = 1;
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
let contentPath = entry.path;
|
|
436
|
+
if (kind === "skills") {
|
|
437
|
+
contentPath = join(entry.path, "SKILL.md");
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
let contents = "";
|
|
441
|
+
try {
|
|
442
|
+
contents = await readEntryContents(contentPath);
|
|
443
|
+
} catch (err) {
|
|
444
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
445
|
+
process.exitCode = 1;
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const displayEntry =
|
|
450
|
+
kind === "mcp" && !showSecrets ? sanitizeForDisplay(entry) : entry;
|
|
451
|
+
let displayContents = contents;
|
|
452
|
+
if (kind === "mcp" && !showSecrets) {
|
|
453
|
+
if (contentPath.endsWith(".json")) {
|
|
454
|
+
try {
|
|
455
|
+
const parsed = parseJsonLenient(contents);
|
|
456
|
+
displayContents = `${JSON.stringify(sanitizeForDisplay(parsed), null, 2)}\n`;
|
|
457
|
+
} catch {
|
|
458
|
+
displayContents = redactPossibleSecrets(contents);
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
displayContents = redactPossibleSecrets(contents);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
console.log(`${kind}:${entry.name}`);
|
|
466
|
+
console.log(JSON.stringify(displayEntry, null, 2));
|
|
467
|
+
console.log("\n---\n");
|
|
468
|
+
console.log(displayContents);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function adaptersCommand(argv: string[]) {
|
|
472
|
+
if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
|
|
473
|
+
console.log(
|
|
474
|
+
"facult adapters — list registered tool adapters\n\nUsage:\n facult adapters\n"
|
|
475
|
+
);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const adapters = getAllAdapters();
|
|
479
|
+
if (!adapters.length) {
|
|
480
|
+
console.log("No adapters registered.");
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
for (const adapter of adapters) {
|
|
484
|
+
const versions = adapter.versions.join(", ");
|
|
485
|
+
console.log(`${adapter.id}\t${versions}`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function main(argv: string[]) {
|
|
490
|
+
const [cmd, ...rest] = argv;
|
|
491
|
+
if (!cmd || cmd === "-h" || cmd === "--help" || cmd === "help") {
|
|
492
|
+
printHelp();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Convenience: allow `facult --show-duplicates` as shorthand for `facult scan --show-duplicates`.
|
|
497
|
+
if (cmd === "--show-duplicates") {
|
|
498
|
+
await scanCommand([cmd, ...rest]);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
switch (cmd) {
|
|
503
|
+
case "scan":
|
|
504
|
+
await scanCommand(rest);
|
|
505
|
+
return;
|
|
506
|
+
case "audit":
|
|
507
|
+
await auditCommand(rest);
|
|
508
|
+
return;
|
|
509
|
+
case "migrate":
|
|
510
|
+
await migrateCommand(rest);
|
|
511
|
+
return;
|
|
512
|
+
case "consolidate":
|
|
513
|
+
await consolidateCommand(rest);
|
|
514
|
+
return;
|
|
515
|
+
case "index":
|
|
516
|
+
await indexCommand(rest);
|
|
517
|
+
return;
|
|
518
|
+
case "list":
|
|
519
|
+
await listCommand(rest);
|
|
520
|
+
return;
|
|
521
|
+
case "show":
|
|
522
|
+
await showCommand(rest);
|
|
523
|
+
return;
|
|
524
|
+
case "adapters":
|
|
525
|
+
await adaptersCommand(rest);
|
|
526
|
+
return;
|
|
527
|
+
case "trust":
|
|
528
|
+
await trustCommand(rest);
|
|
529
|
+
return;
|
|
530
|
+
case "untrust":
|
|
531
|
+
await untrustCommand(rest);
|
|
532
|
+
return;
|
|
533
|
+
case "manage":
|
|
534
|
+
await manageCommand(rest);
|
|
535
|
+
return;
|
|
536
|
+
case "unmanage":
|
|
537
|
+
await unmanageCommand(rest);
|
|
538
|
+
return;
|
|
539
|
+
case "managed":
|
|
540
|
+
await managedCommand(rest);
|
|
541
|
+
return;
|
|
542
|
+
case "enable":
|
|
543
|
+
await enableCommand(rest);
|
|
544
|
+
return;
|
|
545
|
+
case "disable":
|
|
546
|
+
await disableCommand(rest);
|
|
547
|
+
return;
|
|
548
|
+
case "sync":
|
|
549
|
+
await syncCommand(rest);
|
|
550
|
+
return;
|
|
551
|
+
case "search":
|
|
552
|
+
await searchCommand(rest);
|
|
553
|
+
return;
|
|
554
|
+
case "install":
|
|
555
|
+
await installCommand(rest);
|
|
556
|
+
return;
|
|
557
|
+
case "update":
|
|
558
|
+
if (rest.includes("--self")) {
|
|
559
|
+
await selfUpdateCommand(rest.filter((arg) => arg !== "--self"));
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
await updateCommand(rest);
|
|
563
|
+
return;
|
|
564
|
+
case "self-update":
|
|
565
|
+
await selfUpdateCommand(rest);
|
|
566
|
+
return;
|
|
567
|
+
case "verify-source":
|
|
568
|
+
await verifySourceCommand(rest);
|
|
569
|
+
return;
|
|
570
|
+
case "templates":
|
|
571
|
+
await templatesCommand(rest);
|
|
572
|
+
return;
|
|
573
|
+
case "sources":
|
|
574
|
+
await sourcesCommand(rest);
|
|
575
|
+
return;
|
|
576
|
+
case "snippets":
|
|
577
|
+
await snippetsCommand(rest);
|
|
578
|
+
return;
|
|
579
|
+
default:
|
|
580
|
+
console.error(`Unknown command: ${cmd}`);
|
|
581
|
+
printHelp();
|
|
582
|
+
process.exitCode = 1;
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (import.meta.main) {
|
|
588
|
+
await main(process.argv.slice(2));
|
|
589
|
+
}
|