facult 2.7.4 → 2.8.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/README.md +23 -2
- package/package.json +1 -1
- package/src/adapters/codex.ts +1 -1
- package/src/ai.ts +44 -3
- package/src/doctor.ts +81 -6
- package/src/index.ts +138 -21
- package/src/inventory.ts +886 -0
- package/src/manage.ts +724 -71
- package/src/mcp-config.ts +5 -0
- package/src/project-sync.ts +15 -2
- package/src/scan.ts +47 -1
- package/src/status.ts +268 -0
package/README.md
CHANGED
|
@@ -43,6 +43,7 @@ Recommended global install:
|
|
|
43
43
|
brew tap hack-dance/tap
|
|
44
44
|
brew install hack-dance/tap/fclt
|
|
45
45
|
fclt --help
|
|
46
|
+
fclt --version
|
|
46
47
|
```
|
|
47
48
|
|
|
48
49
|
Package-manager install:
|
|
@@ -89,10 +90,26 @@ fclt self-update --version 0.0.1
|
|
|
89
90
|
|
|
90
91
|
```bash
|
|
91
92
|
fclt scan --show-duplicates
|
|
93
|
+
fclt status
|
|
94
|
+
fclt inventory --json
|
|
92
95
|
```
|
|
93
96
|
|
|
94
97
|
`scan` is read-only. It inspects local configs and reports what `fclt` found without changing files.
|
|
95
98
|
|
|
99
|
+
`status` reports the active canonical root, managed-tool state, generated index/graph state, writeback/proposal queue state, and high-signal sync risks.
|
|
100
|
+
|
|
101
|
+
`inventory` is the stable machine-readable discovery surface for agent harnesses. It returns a JSON catalog of discovered MCP servers, skills, and instruction/rule assets across known tool configs and configured scan roots. MCP definitions are redacted by default, including env values, inline `KEY=value` args, bearer tokens, and secret-looking URL query params, but include safe auth metadata such as env keys, env references, and whether inline secret values were found.
|
|
102
|
+
|
|
103
|
+
Useful inventory slices:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
fclt inventory --json --global
|
|
107
|
+
fclt inventory --json --project
|
|
108
|
+
fclt inventory --json --tool codex
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Use `mcpCapabilities` for the de-duplicated agent-facing MCP view. Use `mcpServers` when you need raw per-source occurrences for diagnostics.
|
|
112
|
+
|
|
96
113
|
If you want a repo-local `.ai`:
|
|
97
114
|
|
|
98
115
|
```bash
|
|
@@ -137,6 +154,8 @@ If you run these commands inside a repo that has `<repo>/.ai`, `fclt` targets th
|
|
|
137
154
|
|
|
138
155
|
```bash
|
|
139
156
|
fclt list skills
|
|
157
|
+
fclt inventory --json
|
|
158
|
+
fclt status --json
|
|
140
159
|
fclt show instruction:WRITING
|
|
141
160
|
fclt show mcp:github
|
|
142
161
|
fclt find verification
|
|
@@ -344,13 +363,14 @@ version = 1
|
|
|
344
363
|
[project_sync.codex]
|
|
345
364
|
skills = ["hack-cli", "hack-tickets"]
|
|
346
365
|
agents = ["review-operator"]
|
|
366
|
+
automations = ["project-check"]
|
|
347
367
|
mcp_servers = ["github"]
|
|
348
368
|
global_docs = true
|
|
349
369
|
tool_rules = true
|
|
350
370
|
tool_config = true
|
|
351
371
|
```
|
|
352
372
|
|
|
353
|
-
That policy applies to project-managed tool renders, including assets inherited from the merged global index. If you want a global skill inside
|
|
373
|
+
That policy applies to project-managed tool renders, including assets inherited from the merged global index. If you want a global skill or shared Codex automation inside project-managed output, name it explicitly here. `fclt doctor --repair` can materialize repo-local project assets into `config.local.toml` for already-managed project roots.
|
|
354
374
|
|
|
355
375
|
### Snippets
|
|
356
376
|
|
|
@@ -513,6 +533,7 @@ Recommended security flow:
|
|
|
513
533
|
- Inventory and discovery
|
|
514
534
|
```bash
|
|
515
535
|
fclt scan [--from <path>] [--json] [--show-duplicates]
|
|
536
|
+
fclt inventory [--from <path>] [--json] [--show-secrets]
|
|
516
537
|
fclt list [skills|mcp|agents|snippets|instructions] [--enabled-for <tool>] [--untrusted] [--flagged] [--pending]
|
|
517
538
|
fclt show <name>
|
|
518
539
|
fclt show instruction:<name>
|
|
@@ -603,7 +624,7 @@ When Codex is in managed mode, canonical automation sources live under:
|
|
|
603
624
|
- `~/.ai/automations/<name>/...` for global automation state
|
|
604
625
|
- `<repo>/.ai/automations/<name>/...` for project-scoped canonical state
|
|
605
626
|
|
|
606
|
-
Managed sync renders
|
|
627
|
+
Managed sync renders global canonical automation directories into the shared live Codex automation store at `~/.codex/automations/` and only removes automation files that were previously rendered by the same canonical root. Project-scoped automation sources are default-deny; add their names to `[project_sync.codex].automations` before project managed sync can render them into that shared live store.
|
|
607
628
|
|
|
608
629
|
Example project automation:
|
|
609
630
|
|
package/package.json
CHANGED
package/src/adapters/codex.ts
CHANGED
|
@@ -12,7 +12,7 @@ export const codexAdapter: ToolAdapter = {
|
|
|
12
12
|
mcp: "~/.codex/mcp.json",
|
|
13
13
|
skills: ["~/.agents/skills", "~/.codex/skills"],
|
|
14
14
|
agents: "~/.codex/agents",
|
|
15
|
-
config: "~/.config
|
|
15
|
+
config: "~/.codex/config.toml",
|
|
16
16
|
}),
|
|
17
17
|
parseMcp: (config) => parseMcpConfig(config),
|
|
18
18
|
generateMcp: (canonical) => generateMcpConfig(canonical, "mcpServers"),
|
package/src/ai.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
1
2
|
import { appendFile, mkdir, readdir, readFile } from "node:fs/promises";
|
|
2
|
-
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
4
|
import { ensureAiGraphPath } from "./ai-state";
|
|
4
5
|
import { parseCliContextArgs, resolveCliContextRoot } from "./cli-context";
|
|
5
6
|
import type { AssetScope, GraphNodeKind } from "./graph";
|
|
@@ -242,7 +243,17 @@ function canonicalRefToPath(args: {
|
|
|
242
243
|
return join(facultRootDir(args.homeDir), args.ref.slice("@ai/".length));
|
|
243
244
|
}
|
|
244
245
|
if (args.ref.startsWith("@project/")) {
|
|
245
|
-
|
|
246
|
+
const relPath = args.ref.slice("@project/".length);
|
|
247
|
+
const canonicalPath = join(args.rootDir, relPath);
|
|
248
|
+
const projectRoot = projectRootFromAiRoot(args.rootDir, args.homeDir);
|
|
249
|
+
if (projectRoot) {
|
|
250
|
+
const projectPath = join(projectRoot, relPath);
|
|
251
|
+
if (existsSync(canonicalPath)) {
|
|
252
|
+
return canonicalPath;
|
|
253
|
+
}
|
|
254
|
+
return existsSync(projectPath) ? projectPath : canonicalPath;
|
|
255
|
+
}
|
|
256
|
+
return canonicalPath;
|
|
246
257
|
}
|
|
247
258
|
return null;
|
|
248
259
|
}
|
|
@@ -337,7 +348,27 @@ async function resolveAssetSelection(args: {
|
|
|
337
348
|
});
|
|
338
349
|
const node = resolveGraphNode(graph, args.asset);
|
|
339
350
|
if (!node) {
|
|
340
|
-
|
|
351
|
+
const projectRoot = projectRootFromAiRoot(args.rootDir, args.homeDir);
|
|
352
|
+
if (projectRoot) {
|
|
353
|
+
const resolvedPath = resolve(projectRoot, args.asset);
|
|
354
|
+
const relPath = relative(projectRoot, resolvedPath);
|
|
355
|
+
if (
|
|
356
|
+
relPath &&
|
|
357
|
+
!relPath.startsWith("..") &&
|
|
358
|
+
!relPath.includes("\0") &&
|
|
359
|
+
(await fileExists(resolvedPath))
|
|
360
|
+
) {
|
|
361
|
+
const normalizedRef = relPath.replaceAll("\\", "/");
|
|
362
|
+
return {
|
|
363
|
+
assetRef: `@project/${normalizedRef}`,
|
|
364
|
+
assetId: `file:project:${normalizedRef}`,
|
|
365
|
+
assetType: "file",
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
throw new Error(
|
|
370
|
+
`Asset not found in graph: ${args.asset}. Run "fclt graph show <selector>" to check indexed assets, or use a project-relative file path that exists.`
|
|
371
|
+
);
|
|
341
372
|
}
|
|
342
373
|
return {
|
|
343
374
|
assetRef: node.canonicalRef ?? node.id,
|
|
@@ -1514,6 +1545,11 @@ async function writebackCommand(argv: string[]) {
|
|
|
1514
1545
|
return;
|
|
1515
1546
|
}
|
|
1516
1547
|
|
|
1548
|
+
if (parsed.argv.includes("--help") || parsed.argv.includes("-h")) {
|
|
1549
|
+
console.log(writebackHelp());
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1517
1553
|
const rootDir = resolveCliContextRoot({
|
|
1518
1554
|
rootArg: parsed.rootArg,
|
|
1519
1555
|
scope: parsed.scope,
|
|
@@ -1625,6 +1661,11 @@ async function evolveCommand(argv: string[]) {
|
|
|
1625
1661
|
return;
|
|
1626
1662
|
}
|
|
1627
1663
|
|
|
1664
|
+
if (parsed.argv.includes("--help") || parsed.argv.includes("-h")) {
|
|
1665
|
+
console.log(evolveHelp());
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1628
1669
|
const rootDir = resolveCliContextRoot({
|
|
1629
1670
|
rootArg: parsed.rootArg,
|
|
1630
1671
|
scope: parsed.scope,
|
package/src/doctor.ts
CHANGED
|
@@ -103,6 +103,51 @@ async function pathExists(pathValue: string): Promise<boolean> {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
async function hasCanonicalSource(rootDir: string): Promise<boolean> {
|
|
107
|
+
const fileCandidates = [
|
|
108
|
+
"config.toml",
|
|
109
|
+
"config.local.toml",
|
|
110
|
+
"AGENTS.global.md",
|
|
111
|
+
"AGENTS.override.global.md",
|
|
112
|
+
];
|
|
113
|
+
for (const relPath of fileCandidates) {
|
|
114
|
+
if (await pathExists(join(rootDir, relPath))) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const dirCandidates = [
|
|
120
|
+
"agents",
|
|
121
|
+
"automations",
|
|
122
|
+
"instructions",
|
|
123
|
+
"mcp",
|
|
124
|
+
"rules",
|
|
125
|
+
"skills",
|
|
126
|
+
"snippets",
|
|
127
|
+
"tools",
|
|
128
|
+
];
|
|
129
|
+
for (const relPath of dirCandidates) {
|
|
130
|
+
const entries = await readdir(join(rootDir, relPath)).catch(
|
|
131
|
+
() => [] as string[]
|
|
132
|
+
);
|
|
133
|
+
if (entries.some((entry) => !entry.startsWith("."))) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function isGeneratedOnlyProjectRoot(args: {
|
|
142
|
+
home: string;
|
|
143
|
+
rootDir: string;
|
|
144
|
+
}): Promise<boolean> {
|
|
145
|
+
if (projectRootFromAiRoot(args.rootDir, args.home) == null) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return !(await hasCanonicalSource(args.rootDir));
|
|
149
|
+
}
|
|
150
|
+
|
|
106
151
|
async function hashFile(pathValue: string): Promise<string> {
|
|
107
152
|
const data = await readFile(pathValue);
|
|
108
153
|
return createHash("sha256").update(data).digest("hex");
|
|
@@ -344,6 +389,23 @@ async function listProjectAgentNames(rootDir: string): Promise<string[]> {
|
|
|
344
389
|
.sort((a, b) => a.localeCompare(b));
|
|
345
390
|
}
|
|
346
391
|
|
|
392
|
+
async function listProjectAutomationNames(rootDir: string): Promise<string[]> {
|
|
393
|
+
const automationsDir = join(rootDir, "automations");
|
|
394
|
+
const entries = await readdir(automationsDir, { withFileTypes: true }).catch(
|
|
395
|
+
() => [] as import("node:fs").Dirent[]
|
|
396
|
+
);
|
|
397
|
+
const names: string[] = [];
|
|
398
|
+
for (const entry of entries) {
|
|
399
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (await pathExists(join(automationsDir, entry.name, "automation.toml"))) {
|
|
403
|
+
names.push(entry.name);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return names.sort((a, b) => a.localeCompare(b));
|
|
407
|
+
}
|
|
408
|
+
|
|
347
409
|
async function listProjectMcpNames(rootDir: string): Promise<string[]> {
|
|
348
410
|
const trackedPaths = [
|
|
349
411
|
join(rootDir, "mcp", "servers.json"),
|
|
@@ -404,6 +466,7 @@ async function planProjectSyncPolicyRepair(args: {
|
|
|
404
466
|
{
|
|
405
467
|
skills?: string[];
|
|
406
468
|
agents?: string[];
|
|
469
|
+
automations?: string[];
|
|
407
470
|
mcpServers?: string[];
|
|
408
471
|
globalDocs?: boolean;
|
|
409
472
|
toolRules?: boolean;
|
|
@@ -426,18 +489,21 @@ async function planProjectSyncPolicyRepair(args: {
|
|
|
426
489
|
const configuredTools = new Set(
|
|
427
490
|
await loadConfiguredProjectSyncTools({ rootDir: args.rootDir })
|
|
428
491
|
);
|
|
429
|
-
const [skills, agents, mcpServers, globalDocs] =
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
492
|
+
const [skills, agents, automations, mcpServers, globalDocs] =
|
|
493
|
+
await Promise.all([
|
|
494
|
+
listProjectSkillNames(args.rootDir),
|
|
495
|
+
listProjectAgentNames(args.rootDir),
|
|
496
|
+
listProjectAutomationNames(args.rootDir),
|
|
497
|
+
listProjectMcpNames(args.rootDir),
|
|
498
|
+
hasProjectGlobalDocs(args.rootDir),
|
|
499
|
+
]);
|
|
435
500
|
|
|
436
501
|
const toolPolicies: Record<
|
|
437
502
|
string,
|
|
438
503
|
{
|
|
439
504
|
skills?: string[];
|
|
440
505
|
agents?: string[];
|
|
506
|
+
automations?: string[];
|
|
441
507
|
mcpServers?: string[];
|
|
442
508
|
globalDocs?: boolean;
|
|
443
509
|
toolRules?: boolean;
|
|
@@ -457,6 +523,7 @@ async function planProjectSyncPolicyRepair(args: {
|
|
|
457
523
|
if (
|
|
458
524
|
skills.length === 0 &&
|
|
459
525
|
agents.length === 0 &&
|
|
526
|
+
automations.length === 0 &&
|
|
460
527
|
mcpServers.length === 0 &&
|
|
461
528
|
!globalDocs &&
|
|
462
529
|
!toolRules &&
|
|
@@ -468,6 +535,7 @@ async function planProjectSyncPolicyRepair(args: {
|
|
|
468
535
|
toolPolicies[tool] = {
|
|
469
536
|
...(skills.length > 0 ? { skills } : {}),
|
|
470
537
|
...(agents.length > 0 ? { agents } : {}),
|
|
538
|
+
...(automations.length > 0 ? { automations } : {}),
|
|
471
539
|
...(mcpServers.length > 0 ? { mcpServers } : {}),
|
|
472
540
|
...(globalDocs ? { globalDocs: true } : {}),
|
|
473
541
|
...(toolRules ? { toolRules: true } : {}),
|
|
@@ -627,6 +695,13 @@ export async function doctorCommand(argv: string[]) {
|
|
|
627
695
|
`Project sync is still implicit for managed tools (${projectSyncRepairTools.join(", ")}). Run \`fclt doctor --repair\` to write explicit [project_sync.<tool>] entries.`
|
|
628
696
|
);
|
|
629
697
|
}
|
|
698
|
+
if (await isGeneratedOnlyProjectRoot({ home, rootDir })) {
|
|
699
|
+
console.log(
|
|
700
|
+
"Project .ai root contains generated state only. Canonical project source is missing, so managed project sync should be treated as unsafe until source is initialized, restored, or management is detached."
|
|
701
|
+
);
|
|
702
|
+
process.exitCode = 1;
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
630
705
|
|
|
631
706
|
if (result.source === "generated") {
|
|
632
707
|
console.log("AI index is healthy.");
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "./graph-query";
|
|
26
26
|
import type {
|
|
27
27
|
AgentEntry,
|
|
28
|
+
AutomationEntry,
|
|
28
29
|
FacultIndex,
|
|
29
30
|
InstructionEntry,
|
|
30
31
|
McpEntry,
|
|
@@ -43,12 +44,19 @@ import {
|
|
|
43
44
|
} from "./query";
|
|
44
45
|
import { parseJsonLenient } from "./util/json";
|
|
45
46
|
|
|
46
|
-
type ListKind =
|
|
47
|
+
type ListKind =
|
|
48
|
+
| "skills"
|
|
49
|
+
| "mcp"
|
|
50
|
+
| "agents"
|
|
51
|
+
| "automations"
|
|
52
|
+
| "snippets"
|
|
53
|
+
| "instructions";
|
|
47
54
|
|
|
48
55
|
const LIST_KINDS: ListKind[] = [
|
|
49
56
|
"skills",
|
|
50
57
|
"mcp",
|
|
51
58
|
"agents",
|
|
59
|
+
"automations",
|
|
52
60
|
"snippets",
|
|
53
61
|
"instructions",
|
|
54
62
|
];
|
|
@@ -65,6 +73,7 @@ export interface FindCommandOptions {
|
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
type GraphCommandKind = "show" | "deps" | "dependents";
|
|
76
|
+
type ShowKind = ListKind | "mcp";
|
|
68
77
|
|
|
69
78
|
interface ContextualCommandOptions {
|
|
70
79
|
rootArg?: string;
|
|
@@ -99,6 +108,14 @@ function printHelp() {
|
|
|
99
108
|
headers: ["Command", "Purpose"],
|
|
100
109
|
rows: [
|
|
101
110
|
["scan", "Scan local tool configs and discovered assets"],
|
|
111
|
+
[
|
|
112
|
+
"inventory",
|
|
113
|
+
"Print a JSON inventory of usable skills, instructions, and MCP servers",
|
|
114
|
+
],
|
|
115
|
+
[
|
|
116
|
+
"status",
|
|
117
|
+
"Show active roots, managed tools, graph/index, and sync risks",
|
|
118
|
+
],
|
|
102
119
|
[
|
|
103
120
|
"audit",
|
|
104
121
|
"Run security audits with interactive or scripted flows",
|
|
@@ -177,7 +194,7 @@ function printListHelp() {
|
|
|
177
194
|
title: "Usage",
|
|
178
195
|
lines: renderBullets([
|
|
179
196
|
renderCode(
|
|
180
|
-
"fclt list [skills|mcp|agents|snippets|instructions] [options]"
|
|
197
|
+
"fclt list [skills|mcp|agents|automations|snippets|instructions] [options]"
|
|
181
198
|
),
|
|
182
199
|
renderCode("fclt list"),
|
|
183
200
|
]),
|
|
@@ -494,6 +511,32 @@ function auditBadge(status?: string): string {
|
|
|
494
511
|
return renderBadge("audit pending", "warn");
|
|
495
512
|
}
|
|
496
513
|
|
|
514
|
+
function showKindForToken(token: string): ShowKind | null {
|
|
515
|
+
switch (token) {
|
|
516
|
+
case "agent":
|
|
517
|
+
case "agents":
|
|
518
|
+
return "agents";
|
|
519
|
+
case "automation":
|
|
520
|
+
case "automations":
|
|
521
|
+
return "automations";
|
|
522
|
+
case "instruction":
|
|
523
|
+
case "instructions":
|
|
524
|
+
return "instructions";
|
|
525
|
+
case "mcp":
|
|
526
|
+
case "mcp-server":
|
|
527
|
+
case "mcp-servers":
|
|
528
|
+
return "mcp";
|
|
529
|
+
case "skill":
|
|
530
|
+
case "skills":
|
|
531
|
+
return "skills";
|
|
532
|
+
case "snippet":
|
|
533
|
+
case "snippets":
|
|
534
|
+
return "snippets";
|
|
535
|
+
default:
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
497
540
|
function displayDescription(value?: string): string {
|
|
498
541
|
const normalized = value
|
|
499
542
|
?.trim()
|
|
@@ -571,6 +614,7 @@ async function listCommand(argv: string[]) {
|
|
|
571
614
|
| SkillEntry[]
|
|
572
615
|
| McpEntry[]
|
|
573
616
|
| AgentEntry[]
|
|
617
|
+
| AutomationEntry[]
|
|
574
618
|
| SnippetEntry[]
|
|
575
619
|
| InstructionEntry[] = [];
|
|
576
620
|
|
|
@@ -584,6 +628,20 @@ async function listCommand(argv: string[]) {
|
|
|
584
628
|
case "agents":
|
|
585
629
|
entries = filterAgents(index.agents ?? {}, opts.filters);
|
|
586
630
|
break;
|
|
631
|
+
case "automations":
|
|
632
|
+
entries = Object.values(index.automations ?? {}).filter((entry) => {
|
|
633
|
+
if (
|
|
634
|
+
opts.filters.sourceKind &&
|
|
635
|
+
entry.sourceKind !== opts.filters.sourceKind
|
|
636
|
+
) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
if (opts.filters.scope && entry.scope !== opts.filters.scope) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
return true;
|
|
643
|
+
});
|
|
644
|
+
break;
|
|
587
645
|
case "snippets":
|
|
588
646
|
entries = filterSnippets(index.snippets ?? {}, opts.filters);
|
|
589
647
|
break;
|
|
@@ -646,11 +704,17 @@ async function listCommand(argv: string[]) {
|
|
|
646
704
|
};
|
|
647
705
|
}
|
|
648
706
|
|
|
649
|
-
const detailEntry = entry as
|
|
707
|
+
const detailEntry = entry as
|
|
708
|
+
| AgentEntry
|
|
709
|
+
| AutomationEntry
|
|
710
|
+
| SnippetEntry
|
|
711
|
+
| InstructionEntry;
|
|
650
712
|
return {
|
|
651
713
|
title: entry.name,
|
|
652
714
|
meta: sourceLabel(entry),
|
|
653
|
-
description: displayDescription(
|
|
715
|
+
description: displayDescription(
|
|
716
|
+
"description" in detailEntry ? detailEntry.description : undefined
|
|
717
|
+
),
|
|
654
718
|
};
|
|
655
719
|
});
|
|
656
720
|
|
|
@@ -834,45 +898,59 @@ async function showCommand(argv: string[]) {
|
|
|
834
898
|
return;
|
|
835
899
|
}
|
|
836
900
|
|
|
901
|
+
const rootDir = resolveCliContextRoot({
|
|
902
|
+
rootArg: context.rootArg,
|
|
903
|
+
scope: context.scopeMode,
|
|
904
|
+
cwd: process.cwd(),
|
|
905
|
+
});
|
|
906
|
+
|
|
837
907
|
let index: FacultIndex;
|
|
838
908
|
try {
|
|
839
|
-
index = await loadIndex({
|
|
840
|
-
rootDir: resolveCliContextRoot({
|
|
841
|
-
rootArg: context.rootArg,
|
|
842
|
-
scope: context.scopeMode,
|
|
843
|
-
cwd: process.cwd(),
|
|
844
|
-
}),
|
|
845
|
-
});
|
|
909
|
+
index = await loadIndex({ rootDir });
|
|
846
910
|
} catch (err) {
|
|
847
911
|
console.error(err instanceof Error ? err.message : String(err));
|
|
848
912
|
process.exitCode = 1;
|
|
849
913
|
return;
|
|
850
914
|
}
|
|
851
915
|
|
|
852
|
-
let kind:
|
|
916
|
+
let kind: ShowKind = "skills";
|
|
853
917
|
let name = raw;
|
|
918
|
+
const colonIndex = raw.indexOf(":");
|
|
919
|
+
if (colonIndex > 0) {
|
|
920
|
+
const tokenKind = showKindForToken(raw.slice(0, colonIndex));
|
|
921
|
+
if (tokenKind) {
|
|
922
|
+
kind = tokenKind;
|
|
923
|
+
name = raw.slice(colonIndex + 1);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
854
926
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
927
|
+
try {
|
|
928
|
+
const graph = await loadGraph({ rootDir });
|
|
929
|
+
const node = resolveGraphNode(graph, raw, {
|
|
930
|
+
sourceKind: context.sourceKind,
|
|
931
|
+
scope: scopeFilterForMode(context.scopeMode),
|
|
932
|
+
});
|
|
933
|
+
const graphKind = node ? showKindForToken(node.kind) : null;
|
|
934
|
+
if (node && graphKind) {
|
|
935
|
+
kind = graphKind;
|
|
936
|
+
name = node.name;
|
|
937
|
+
}
|
|
938
|
+
} catch {
|
|
939
|
+
// A missing or stale graph should not make basic index-backed show fail.
|
|
864
940
|
}
|
|
865
941
|
|
|
866
942
|
let entry:
|
|
867
943
|
| SkillEntry
|
|
868
944
|
| McpEntry
|
|
869
945
|
| AgentEntry
|
|
946
|
+
| AutomationEntry
|
|
870
947
|
| SnippetEntry
|
|
871
948
|
| InstructionEntry
|
|
872
949
|
| null = null;
|
|
873
950
|
const skill = index.skills[name];
|
|
874
951
|
const mcpServer = index.mcp?.servers?.[name];
|
|
875
952
|
const agent = index.agents?.[name];
|
|
953
|
+
const automation = index.automations?.[name];
|
|
876
954
|
const snippet = index.snippets?.[name];
|
|
877
955
|
const instruction = index.instructions?.[name];
|
|
878
956
|
const matchesContext = (candidate: {
|
|
@@ -896,6 +974,15 @@ async function showCommand(argv: string[]) {
|
|
|
896
974
|
} else if (kind === "skills" && agent && matchesContext(agent)) {
|
|
897
975
|
kind = "agents";
|
|
898
976
|
entry = agent;
|
|
977
|
+
} else if (
|
|
978
|
+
kind === "automations" &&
|
|
979
|
+
automation &&
|
|
980
|
+
matchesContext(automation)
|
|
981
|
+
) {
|
|
982
|
+
entry = automation;
|
|
983
|
+
} else if (kind === "skills" && automation && matchesContext(automation)) {
|
|
984
|
+
kind = "automations";
|
|
985
|
+
entry = automation;
|
|
899
986
|
} else if (kind === "skills" && snippet && matchesContext(snippet)) {
|
|
900
987
|
kind = "snippets";
|
|
901
988
|
entry = snippet;
|
|
@@ -1105,6 +1192,7 @@ async function graphCommand(argv: string[]) {
|
|
|
1105
1192
|
}
|
|
1106
1193
|
|
|
1107
1194
|
async function adaptersCommand(argv: string[]) {
|
|
1195
|
+
const json = argv.includes("--json");
|
|
1108
1196
|
if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
|
|
1109
1197
|
console.log(
|
|
1110
1198
|
renderPage({
|
|
@@ -1122,6 +1210,21 @@ async function adaptersCommand(argv: string[]) {
|
|
|
1122
1210
|
}
|
|
1123
1211
|
const { getAllAdapters } = await import("./adapters");
|
|
1124
1212
|
const adapters = getAllAdapters();
|
|
1213
|
+
if (json) {
|
|
1214
|
+
console.log(
|
|
1215
|
+
JSON.stringify(
|
|
1216
|
+
adapters.map((adapter) => ({
|
|
1217
|
+
id: adapter.id,
|
|
1218
|
+
name: adapter.name,
|
|
1219
|
+
versions: adapter.versions,
|
|
1220
|
+
defaultPaths: adapter.getDefaultPaths?.() ?? {},
|
|
1221
|
+
})),
|
|
1222
|
+
null,
|
|
1223
|
+
2
|
|
1224
|
+
)
|
|
1225
|
+
);
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1125
1228
|
if (!adapters.length) {
|
|
1126
1229
|
console.log(
|
|
1127
1230
|
renderPage({
|
|
@@ -1158,6 +1261,12 @@ async function main(argv: string[]) {
|
|
|
1158
1261
|
return;
|
|
1159
1262
|
}
|
|
1160
1263
|
|
|
1264
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
1265
|
+
const { packageVersion } = await import("./status");
|
|
1266
|
+
console.log(await packageVersion());
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1161
1270
|
// Convenience: allow `fclt --show-duplicates` as shorthand for `fclt scan --show-duplicates`.
|
|
1162
1271
|
if (cmd === "--show-duplicates") {
|
|
1163
1272
|
const { scanCommand } = await import("./scan");
|
|
@@ -1169,6 +1278,14 @@ async function main(argv: string[]) {
|
|
|
1169
1278
|
case "scan":
|
|
1170
1279
|
await import("./scan").then(({ scanCommand }) => scanCommand(rest));
|
|
1171
1280
|
return;
|
|
1281
|
+
case "inventory":
|
|
1282
|
+
await import("./inventory").then(({ inventoryCommand }) =>
|
|
1283
|
+
inventoryCommand(rest)
|
|
1284
|
+
);
|
|
1285
|
+
return;
|
|
1286
|
+
case "status":
|
|
1287
|
+
await import("./status").then(({ statusCommand }) => statusCommand(rest));
|
|
1288
|
+
return;
|
|
1172
1289
|
case "audit":
|
|
1173
1290
|
await import("./audit").then(({ auditCommand }) => auditCommand(rest));
|
|
1174
1291
|
return;
|