facult 2.7.3 → 2.7.7
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 +3 -2
- package/package.json +1 -1
- package/src/adapters/codex.ts +1 -1
- package/src/ai.ts +34 -3
- package/src/doctor.ts +81 -6
- package/src/index.ts +116 -21
- package/src/manage.ts +724 -71
- package/src/paths.ts +21 -1
- package/src/project-sync.ts +15 -2
- package/src/scan.ts +5 -1
package/README.md
CHANGED
|
@@ -344,13 +344,14 @@ version = 1
|
|
|
344
344
|
[project_sync.codex]
|
|
345
345
|
skills = ["hack-cli", "hack-tickets"]
|
|
346
346
|
agents = ["review-operator"]
|
|
347
|
+
automations = ["project-check"]
|
|
347
348
|
mcp_servers = ["github"]
|
|
348
349
|
global_docs = true
|
|
349
350
|
tool_rules = true
|
|
350
351
|
tool_config = true
|
|
351
352
|
```
|
|
352
353
|
|
|
353
|
-
That policy applies to project-managed tool renders, including assets inherited from the merged global index. If you want a global skill inside
|
|
354
|
+
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
355
|
|
|
355
356
|
### Snippets
|
|
356
357
|
|
|
@@ -603,7 +604,7 @@ When Codex is in managed mode, canonical automation sources live under:
|
|
|
603
604
|
- `~/.ai/automations/<name>/...` for global automation state
|
|
604
605
|
- `<repo>/.ai/automations/<name>/...` for project-scoped canonical state
|
|
605
606
|
|
|
606
|
-
Managed sync renders
|
|
607
|
+
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
608
|
|
|
608
609
|
Example project automation:
|
|
609
610
|
|
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,
|
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;
|
|
@@ -177,7 +186,7 @@ function printListHelp() {
|
|
|
177
186
|
title: "Usage",
|
|
178
187
|
lines: renderBullets([
|
|
179
188
|
renderCode(
|
|
180
|
-
"fclt list [skills|mcp|agents|snippets|instructions] [options]"
|
|
189
|
+
"fclt list [skills|mcp|agents|automations|snippets|instructions] [options]"
|
|
181
190
|
),
|
|
182
191
|
renderCode("fclt list"),
|
|
183
192
|
]),
|
|
@@ -494,6 +503,32 @@ function auditBadge(status?: string): string {
|
|
|
494
503
|
return renderBadge("audit pending", "warn");
|
|
495
504
|
}
|
|
496
505
|
|
|
506
|
+
function showKindForToken(token: string): ShowKind | null {
|
|
507
|
+
switch (token) {
|
|
508
|
+
case "agent":
|
|
509
|
+
case "agents":
|
|
510
|
+
return "agents";
|
|
511
|
+
case "automation":
|
|
512
|
+
case "automations":
|
|
513
|
+
return "automations";
|
|
514
|
+
case "instruction":
|
|
515
|
+
case "instructions":
|
|
516
|
+
return "instructions";
|
|
517
|
+
case "mcp":
|
|
518
|
+
case "mcp-server":
|
|
519
|
+
case "mcp-servers":
|
|
520
|
+
return "mcp";
|
|
521
|
+
case "skill":
|
|
522
|
+
case "skills":
|
|
523
|
+
return "skills";
|
|
524
|
+
case "snippet":
|
|
525
|
+
case "snippets":
|
|
526
|
+
return "snippets";
|
|
527
|
+
default:
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
497
532
|
function displayDescription(value?: string): string {
|
|
498
533
|
const normalized = value
|
|
499
534
|
?.trim()
|
|
@@ -571,6 +606,7 @@ async function listCommand(argv: string[]) {
|
|
|
571
606
|
| SkillEntry[]
|
|
572
607
|
| McpEntry[]
|
|
573
608
|
| AgentEntry[]
|
|
609
|
+
| AutomationEntry[]
|
|
574
610
|
| SnippetEntry[]
|
|
575
611
|
| InstructionEntry[] = [];
|
|
576
612
|
|
|
@@ -584,6 +620,20 @@ async function listCommand(argv: string[]) {
|
|
|
584
620
|
case "agents":
|
|
585
621
|
entries = filterAgents(index.agents ?? {}, opts.filters);
|
|
586
622
|
break;
|
|
623
|
+
case "automations":
|
|
624
|
+
entries = Object.values(index.automations ?? {}).filter((entry) => {
|
|
625
|
+
if (
|
|
626
|
+
opts.filters.sourceKind &&
|
|
627
|
+
entry.sourceKind !== opts.filters.sourceKind
|
|
628
|
+
) {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
if (opts.filters.scope && entry.scope !== opts.filters.scope) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
return true;
|
|
635
|
+
});
|
|
636
|
+
break;
|
|
587
637
|
case "snippets":
|
|
588
638
|
entries = filterSnippets(index.snippets ?? {}, opts.filters);
|
|
589
639
|
break;
|
|
@@ -646,11 +696,17 @@ async function listCommand(argv: string[]) {
|
|
|
646
696
|
};
|
|
647
697
|
}
|
|
648
698
|
|
|
649
|
-
const detailEntry = entry as
|
|
699
|
+
const detailEntry = entry as
|
|
700
|
+
| AgentEntry
|
|
701
|
+
| AutomationEntry
|
|
702
|
+
| SnippetEntry
|
|
703
|
+
| InstructionEntry;
|
|
650
704
|
return {
|
|
651
705
|
title: entry.name,
|
|
652
706
|
meta: sourceLabel(entry),
|
|
653
|
-
description: displayDescription(
|
|
707
|
+
description: displayDescription(
|
|
708
|
+
"description" in detailEntry ? detailEntry.description : undefined
|
|
709
|
+
),
|
|
654
710
|
};
|
|
655
711
|
});
|
|
656
712
|
|
|
@@ -834,45 +890,59 @@ async function showCommand(argv: string[]) {
|
|
|
834
890
|
return;
|
|
835
891
|
}
|
|
836
892
|
|
|
893
|
+
const rootDir = resolveCliContextRoot({
|
|
894
|
+
rootArg: context.rootArg,
|
|
895
|
+
scope: context.scopeMode,
|
|
896
|
+
cwd: process.cwd(),
|
|
897
|
+
});
|
|
898
|
+
|
|
837
899
|
let index: FacultIndex;
|
|
838
900
|
try {
|
|
839
|
-
index = await loadIndex({
|
|
840
|
-
rootDir: resolveCliContextRoot({
|
|
841
|
-
rootArg: context.rootArg,
|
|
842
|
-
scope: context.scopeMode,
|
|
843
|
-
cwd: process.cwd(),
|
|
844
|
-
}),
|
|
845
|
-
});
|
|
901
|
+
index = await loadIndex({ rootDir });
|
|
846
902
|
} catch (err) {
|
|
847
903
|
console.error(err instanceof Error ? err.message : String(err));
|
|
848
904
|
process.exitCode = 1;
|
|
849
905
|
return;
|
|
850
906
|
}
|
|
851
907
|
|
|
852
|
-
let kind:
|
|
908
|
+
let kind: ShowKind = "skills";
|
|
853
909
|
let name = raw;
|
|
910
|
+
const colonIndex = raw.indexOf(":");
|
|
911
|
+
if (colonIndex > 0) {
|
|
912
|
+
const tokenKind = showKindForToken(raw.slice(0, colonIndex));
|
|
913
|
+
if (tokenKind) {
|
|
914
|
+
kind = tokenKind;
|
|
915
|
+
name = raw.slice(colonIndex + 1);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
854
918
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
919
|
+
try {
|
|
920
|
+
const graph = await loadGraph({ rootDir });
|
|
921
|
+
const node = resolveGraphNode(graph, raw, {
|
|
922
|
+
sourceKind: context.sourceKind,
|
|
923
|
+
scope: scopeFilterForMode(context.scopeMode),
|
|
924
|
+
});
|
|
925
|
+
const graphKind = node ? showKindForToken(node.kind) : null;
|
|
926
|
+
if (node && graphKind) {
|
|
927
|
+
kind = graphKind;
|
|
928
|
+
name = node.name;
|
|
929
|
+
}
|
|
930
|
+
} catch {
|
|
931
|
+
// A missing or stale graph should not make basic index-backed show fail.
|
|
864
932
|
}
|
|
865
933
|
|
|
866
934
|
let entry:
|
|
867
935
|
| SkillEntry
|
|
868
936
|
| McpEntry
|
|
869
937
|
| AgentEntry
|
|
938
|
+
| AutomationEntry
|
|
870
939
|
| SnippetEntry
|
|
871
940
|
| InstructionEntry
|
|
872
941
|
| null = null;
|
|
873
942
|
const skill = index.skills[name];
|
|
874
943
|
const mcpServer = index.mcp?.servers?.[name];
|
|
875
944
|
const agent = index.agents?.[name];
|
|
945
|
+
const automation = index.automations?.[name];
|
|
876
946
|
const snippet = index.snippets?.[name];
|
|
877
947
|
const instruction = index.instructions?.[name];
|
|
878
948
|
const matchesContext = (candidate: {
|
|
@@ -896,6 +966,15 @@ async function showCommand(argv: string[]) {
|
|
|
896
966
|
} else if (kind === "skills" && agent && matchesContext(agent)) {
|
|
897
967
|
kind = "agents";
|
|
898
968
|
entry = agent;
|
|
969
|
+
} else if (
|
|
970
|
+
kind === "automations" &&
|
|
971
|
+
automation &&
|
|
972
|
+
matchesContext(automation)
|
|
973
|
+
) {
|
|
974
|
+
entry = automation;
|
|
975
|
+
} else if (kind === "skills" && automation && matchesContext(automation)) {
|
|
976
|
+
kind = "automations";
|
|
977
|
+
entry = automation;
|
|
899
978
|
} else if (kind === "skills" && snippet && matchesContext(snippet)) {
|
|
900
979
|
kind = "snippets";
|
|
901
980
|
entry = snippet;
|
|
@@ -1105,6 +1184,7 @@ async function graphCommand(argv: string[]) {
|
|
|
1105
1184
|
}
|
|
1106
1185
|
|
|
1107
1186
|
async function adaptersCommand(argv: string[]) {
|
|
1187
|
+
const json = argv.includes("--json");
|
|
1108
1188
|
if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
|
|
1109
1189
|
console.log(
|
|
1110
1190
|
renderPage({
|
|
@@ -1122,6 +1202,21 @@ async function adaptersCommand(argv: string[]) {
|
|
|
1122
1202
|
}
|
|
1123
1203
|
const { getAllAdapters } = await import("./adapters");
|
|
1124
1204
|
const adapters = getAllAdapters();
|
|
1205
|
+
if (json) {
|
|
1206
|
+
console.log(
|
|
1207
|
+
JSON.stringify(
|
|
1208
|
+
adapters.map((adapter) => ({
|
|
1209
|
+
id: adapter.id,
|
|
1210
|
+
name: adapter.name,
|
|
1211
|
+
versions: adapter.versions,
|
|
1212
|
+
defaultPaths: adapter.getDefaultPaths?.() ?? {},
|
|
1213
|
+
})),
|
|
1214
|
+
null,
|
|
1215
|
+
2
|
|
1216
|
+
)
|
|
1217
|
+
);
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1125
1220
|
if (!adapters.length) {
|
|
1126
1221
|
console.log(
|
|
1127
1222
|
renderPage({
|