facult 1.0.3 → 1.2.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/src/index.ts CHANGED
@@ -2,12 +2,28 @@
2
2
 
3
3
  import { join } from "node:path";
4
4
  import { getAllAdapters } from "./adapters";
5
+ import { aiCommand } from "./ai";
5
6
  import { auditCommand } from "./audit";
7
+ import { autosyncCommand } from "./autosync";
8
+ import {
9
+ type CapabilityScopeMode,
10
+ parseCliContextArgs,
11
+ resolveCliContextRoot,
12
+ } from "./cli-context";
6
13
  import { consolidateCommand } from "./consolidate";
14
+ import { doctorCommand } from "./doctor";
7
15
  import { disableCommand, enableCommand } from "./enable-disable";
16
+ import type { AssetScope, AssetSourceKind } from "./graph";
17
+ import {
18
+ graphDependencies,
19
+ graphDependents,
20
+ loadGraph,
21
+ resolveGraphNode,
22
+ } from "./graph-query";
8
23
  import type {
9
24
  AgentEntry,
10
25
  FacultIndex,
26
+ InstructionEntry,
11
27
  McpEntry,
12
28
  SkillEntry,
13
29
  SnippetEntry,
@@ -23,9 +39,11 @@ import { migrateCommand } from "./migrate";
23
39
  import type { QueryFilters } from "./query";
24
40
  import {
25
41
  filterAgents,
42
+ filterInstructions,
26
43
  filterMcp,
27
44
  filterSkills,
28
45
  filterSnippets,
46
+ findCapabilities,
29
47
  loadIndex,
30
48
  } from "./query";
31
49
  import {
@@ -42,9 +60,15 @@ import { snippetsCommand } from "./snippets-cli";
42
60
  import { trustCommand, untrustCommand } from "./trust";
43
61
  import { parseJsonLenient } from "./util/json";
44
62
 
45
- type ListKind = "skills" | "mcp" | "agents" | "snippets";
63
+ type ListKind = "skills" | "mcp" | "agents" | "snippets" | "instructions";
46
64
 
47
- const LIST_KINDS: ListKind[] = ["skills", "mcp", "agents", "snippets"];
65
+ const LIST_KINDS: ListKind[] = [
66
+ "skills",
67
+ "mcp",
68
+ "agents",
69
+ "snippets",
70
+ "instructions",
71
+ ];
48
72
 
49
73
  export interface ListCommandOptions {
50
74
  kind: ListKind;
@@ -52,6 +76,25 @@ export interface ListCommandOptions {
52
76
  json: boolean;
53
77
  }
54
78
 
79
+ export interface FindCommandOptions {
80
+ text: string;
81
+ json: boolean;
82
+ }
83
+
84
+ type GraphCommandKind = "show" | "deps" | "dependents";
85
+
86
+ interface ContextualCommandOptions {
87
+ rootArg?: string;
88
+ scopeMode: CapabilityScopeMode;
89
+ sourceKind?: AssetSourceKind;
90
+ }
91
+
92
+ interface GraphCommandOptions {
93
+ kind: GraphCommandKind;
94
+ target: string;
95
+ json: boolean;
96
+ }
97
+
55
98
  function printHelp() {
56
99
  console.log(`facult — inspect local agent configs for skills + MCP servers
57
100
 
@@ -61,11 +104,16 @@ Usage:
61
104
  facult audit --non-interactive [name|mcp:<name>] [--severity <level>] [--rules <path>] [--from <path>] [--json]
62
105
  facult audit --non-interactive [name|mcp:<name>] --with <claude|codex> [--from <path>] [--max-items <n|all>] [--json]
63
106
  facult migrate [--from <path>] [--dry-run] [--move] [--write-config]
107
+ facult doctor [--repair]
64
108
  facult consolidate [--force] [--auto <mode>] [scan options]
65
109
  facult index [--force]
66
- facult list [skills|mcp|agents|snippets] [--enabled-for TOOL] [--untrusted] [--flagged] [--pending] [--json]
110
+ facult list [skills|mcp|agents|snippets|instructions] [--enabled-for TOOL] [--untrusted] [--flagged] [--pending] [--json]
67
111
  facult show <name>
68
112
  facult show mcp:<name> [--show-secrets]
113
+ facult show instruction:<name>
114
+ facult find <query> [--json]
115
+ facult graph <show|deps|dependents> <asset> [--json]
116
+ facult ai <writeback|evolve> [args...]
69
117
  facult adapters
70
118
  facult trust <name> [moreNames...]
71
119
  facult untrust <name> [moreNames...]
@@ -75,6 +123,7 @@ Usage:
75
123
  facult enable <name> [moreNames...] [--for <tools>]
76
124
  facult disable <name> [moreNames...] [--for <tools>]
77
125
  facult sync [tool] [--dry-run]
126
+ facult autosync <cmd> [args...]
78
127
  facult search <query> [--index <name>] [--limit <n>]
79
128
  facult install <index:item> [--as <name>] [--dry-run] [--force] [--strict-source-trust]
80
129
  facult update [--apply] [--strict-source-trust]
@@ -89,11 +138,15 @@ Usage:
89
138
  Commands:
90
139
  scan Scan common config locations (Cursor, Claude, Claude Desktop, etc.)
91
140
  audit Security audits (interactive by default; use --non-interactive for scripts)
92
- migrate Copy/move a legacy canonical store to ~/agents/.facult
141
+ migrate Copy/move a legacy canonical store to the current canonical root
142
+ doctor Inspect and repair local facult state
93
143
  consolidate Deduplicate and copy skills + MCP configs (interactive or --auto)
94
144
  index Build a queryable index from the canonical store (see FACULT_ROOT_DIR)
95
- list List indexed skills, MCP servers, agents, or snippets
145
+ list List indexed skills, MCP servers, agents, snippets, or instructions
96
146
  show Show a single indexed entry, including file contents
147
+ find Search local indexed capabilities across asset types
148
+ graph Inspect explicit capability graph nodes and dependencies
149
+ ai Record, group, and evolve AI writeback into reviewable proposals
97
150
  adapters List registered tool adapters
98
151
  trust Mark a skill or MCP server as trusted (annotation only)
99
152
  untrust Remove trusted annotation
@@ -103,6 +156,7 @@ Commands:
103
156
  enable Enable skills or MCP servers for tools
104
157
  disable Disable skills or MCP servers for tools
105
158
  sync Sync managed tools with canonical configs
159
+ autosync Install/manage a background autosync service
106
160
  search Search remote indices (builtin + provider aliases + configured)
107
161
  install Install an item from a remote index
108
162
  update Check/apply updates for remotely installed items
@@ -140,6 +194,11 @@ Options:
140
194
  --self (update) run self-update flow instead of remote item updates
141
195
  --strict-source-trust Enforce trust-only remote install/update actions
142
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
143
202
  `);
144
203
  }
145
204
 
@@ -147,13 +206,18 @@ function printListHelp() {
147
206
  console.log(`facult list — list indexed entries from the canonical store
148
207
 
149
208
  Usage:
150
- facult list [skills|mcp|agents|snippets] [options]
209
+ facult list [skills|mcp|agents|snippets|instructions] [options]
151
210
 
152
211
  Options:
153
212
  --enabled-for TOOL Only include entries enabled for a tool
154
213
  --untrusted Only include entries that are not trusted
155
214
  --flagged Only include entries flagged by audit
156
215
  --pending Only include entries pending audit
216
+ --root PATH Select a canonical .ai root explicitly
217
+ --global Force the global canonical root
218
+ --project Force the nearest repo-local .ai root
219
+ --scope SCOPE merged|global|project (default: merged)
220
+ --source KIND builtin|global|project
157
221
  --json Print JSON array
158
222
  `);
159
223
  }
@@ -164,9 +228,49 @@ function printShowHelp() {
164
228
  Usage:
165
229
  facult show <name>
166
230
  facult show mcp:<name> [--show-secrets]
231
+ facult show instruction:<name>
167
232
 
168
233
  Options:
169
234
  --show-secrets (mcp) Print raw secret values (unsafe)
235
+ --root PATH Select a canonical .ai root explicitly
236
+ --global Force the global canonical root
237
+ --project Force the nearest repo-local .ai root
238
+ --scope SCOPE merged|global|project (default: merged)
239
+ --source KIND builtin|global|project
240
+ `);
241
+ }
242
+
243
+ function printFindHelp() {
244
+ console.log(`facult find — search local indexed capabilities across asset types
245
+
246
+ Usage:
247
+ facult find <query> [--json]
248
+
249
+ Options:
250
+ --root PATH Select a canonical .ai root explicitly
251
+ --global Force the global canonical root
252
+ --project Force the nearest repo-local .ai root
253
+ --scope SCOPE merged|global|project (default: merged)
254
+ --source KIND builtin|global|project
255
+ --json Print JSON array
256
+ `);
257
+ }
258
+
259
+ function printGraphHelp() {
260
+ console.log(`facult graph — inspect explicit capability graph nodes and relations
261
+
262
+ Usage:
263
+ facult graph show <asset> [--json]
264
+ facult graph deps <asset> [--json]
265
+ facult graph dependents <asset> [--json]
266
+
267
+ Options:
268
+ --root PATH Select a canonical .ai root explicitly
269
+ --global Force the global canonical root
270
+ --project Force the nearest repo-local .ai root
271
+ --scope SCOPE merged|global|project (default: merged)
272
+ --source KIND builtin|global|project
273
+ --json Print JSON
170
274
  `);
171
275
  }
172
276
 
@@ -241,15 +345,111 @@ export function parseListArgs(argv: string[]): ListCommandOptions {
241
345
  return { kind, filters, json };
242
346
  }
243
347
 
348
+ export function parseFindArgs(argv: string[]): FindCommandOptions {
349
+ let json = false;
350
+ const terms: string[] = [];
351
+
352
+ for (const arg of argv) {
353
+ if (!arg) {
354
+ continue;
355
+ }
356
+ if (arg === "--json") {
357
+ json = true;
358
+ continue;
359
+ }
360
+ if (arg.startsWith("-")) {
361
+ throw new Error(`Unknown option: ${arg}`);
362
+ }
363
+ terms.push(arg);
364
+ }
365
+
366
+ const text = terms.join(" ").trim();
367
+ if (!text) {
368
+ throw new Error("find requires a query");
369
+ }
370
+
371
+ return { text, json };
372
+ }
373
+
374
+ function parseGraphArgs(argv: string[]): GraphCommandOptions {
375
+ const [kind, ...rest] = argv;
376
+ if (!(kind === "show" || kind === "deps" || kind === "dependents")) {
377
+ throw new Error(`Unknown graph command: ${kind ?? "<missing>"}`);
378
+ }
379
+
380
+ let json = false;
381
+ let target: string | null = null;
382
+ for (const arg of rest) {
383
+ if (!arg) {
384
+ continue;
385
+ }
386
+ if (arg === "--json") {
387
+ json = true;
388
+ continue;
389
+ }
390
+ if (arg.startsWith("-")) {
391
+ throw new Error(`Unknown option: ${arg}`);
392
+ }
393
+ if (target) {
394
+ throw new Error(`graph ${kind} accepts a single asset selector`);
395
+ }
396
+ target = arg;
397
+ }
398
+
399
+ if (!target) {
400
+ throw new Error(`graph ${kind} requires an asset selector`);
401
+ }
402
+
403
+ return { kind, target, json };
404
+ }
405
+
406
+ function scopeFilterForMode(
407
+ scopeMode: CapabilityScopeMode
408
+ ): AssetScope | undefined {
409
+ return scopeMode === "project" ? "project" : undefined;
410
+ }
411
+
412
+ function resolveContextualOptions(
413
+ argv: string[],
414
+ opts?: { allowSource?: boolean }
415
+ ): { argv: string[]; context: ContextualCommandOptions } {
416
+ const parsed = parseCliContextArgs(argv, {
417
+ allowSource: opts?.allowSource,
418
+ });
419
+ return {
420
+ argv: parsed.argv,
421
+ context: {
422
+ rootArg: parsed.rootArg,
423
+ scopeMode: parsed.scope,
424
+ sourceKind: parsed.sourceKind,
425
+ },
426
+ };
427
+ }
428
+
244
429
  async function listCommand(argv: string[]) {
245
- if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
430
+ const { argv: contextualArgv, context } = resolveContextualOptions(argv, {
431
+ allowSource: true,
432
+ });
433
+
434
+ if (
435
+ contextualArgv.includes("--help") ||
436
+ contextualArgv.includes("-h") ||
437
+ contextualArgv[0] === "help"
438
+ ) {
246
439
  printListHelp();
247
440
  return;
248
441
  }
249
442
 
250
443
  let opts: ListCommandOptions;
251
444
  try {
252
- opts = parseListArgs(argv);
445
+ opts = parseListArgs(contextualArgv);
446
+ if (context.sourceKind) {
447
+ opts.filters.sourceKind = context.sourceKind;
448
+ }
449
+ const scopeFilter = scopeFilterForMode(context.scopeMode);
450
+ if (scopeFilter) {
451
+ opts.filters.scope = scopeFilter;
452
+ }
253
453
  } catch (err) {
254
454
  console.error(err instanceof Error ? err.message : String(err));
255
455
  process.exitCode = 1;
@@ -258,14 +458,25 @@ async function listCommand(argv: string[]) {
258
458
 
259
459
  let index: FacultIndex;
260
460
  try {
261
- index = await loadIndex();
461
+ index = await loadIndex({
462
+ rootDir: resolveCliContextRoot({
463
+ rootArg: context.rootArg,
464
+ scope: context.scopeMode,
465
+ cwd: process.cwd(),
466
+ }),
467
+ });
262
468
  } catch (err) {
263
469
  console.error(err instanceof Error ? err.message : String(err));
264
470
  process.exitCode = 1;
265
471
  return;
266
472
  }
267
473
 
268
- let entries: SkillEntry[] | McpEntry[] | AgentEntry[] | SnippetEntry[] = [];
474
+ let entries:
475
+ | SkillEntry[]
476
+ | McpEntry[]
477
+ | AgentEntry[]
478
+ | SnippetEntry[]
479
+ | InstructionEntry[] = [];
269
480
 
270
481
  switch (opts.kind) {
271
482
  case "skills":
@@ -280,6 +491,9 @@ async function listCommand(argv: string[]) {
280
491
  case "snippets":
281
492
  entries = filterSnippets(index.snippets ?? {}, opts.filters);
282
493
  break;
494
+ case "instructions":
495
+ entries = filterInstructions(index.instructions ?? {}, opts.filters);
496
+ break;
283
497
  default:
284
498
  entries = [];
285
499
  break;
@@ -301,7 +515,7 @@ async function listCommand(argv: string[]) {
301
515
  const trustedLabel = meta.trusted === true ? "trusted" : "untrusted";
302
516
  const auditLabel = (meta.auditStatus ?? "pending").trim().toLowerCase();
303
517
  console.log(
304
- `${skill.name}${desc}\t[${trustedLabel}; audit=${auditLabel}]`
518
+ `${skill.name}${desc}\t[${trustedLabel}; audit=${auditLabel}]${formatSourceMeta(skill)}`
305
519
  );
306
520
  } else if (opts.kind === "mcp") {
307
521
  const meta = entry as McpEntry & {
@@ -310,9 +524,11 @@ async function listCommand(argv: string[]) {
310
524
  };
311
525
  const trustedLabel = meta.trusted === true ? "trusted" : "untrusted";
312
526
  const auditLabel = (meta.auditStatus ?? "pending").trim().toLowerCase();
313
- console.log(`${entry.name}\t[${trustedLabel}; audit=${auditLabel}]`);
527
+ console.log(
528
+ `${entry.name}\t[${trustedLabel}; audit=${auditLabel}]${formatSourceMeta(entry)}`
529
+ );
314
530
  } else {
315
- console.log(entry.name);
531
+ console.log(`${entry.name}${formatSourceMeta(entry)}`);
316
532
  }
317
533
  }
318
534
  }
@@ -325,6 +541,60 @@ async function readEntryContents(entryPath: string): Promise<string> {
325
541
  return file.text();
326
542
  }
327
543
 
544
+ async function findCommand(argv: string[]) {
545
+ const { argv: contextualArgv, context } = resolveContextualOptions(argv, {
546
+ allowSource: true,
547
+ });
548
+
549
+ if (
550
+ contextualArgv.includes("--help") ||
551
+ contextualArgv.includes("-h") ||
552
+ contextualArgv[0] === "help"
553
+ ) {
554
+ printFindHelp();
555
+ return;
556
+ }
557
+
558
+ let opts: FindCommandOptions;
559
+ try {
560
+ opts = parseFindArgs(contextualArgv);
561
+ } catch (err) {
562
+ console.error(err instanceof Error ? err.message : String(err));
563
+ process.exitCode = 1;
564
+ return;
565
+ }
566
+
567
+ let index: FacultIndex;
568
+ try {
569
+ index = await loadIndex({
570
+ rootDir: resolveCliContextRoot({
571
+ rootArg: context.rootArg,
572
+ scope: context.scopeMode,
573
+ cwd: process.cwd(),
574
+ }),
575
+ });
576
+ } catch (err) {
577
+ console.error(err instanceof Error ? err.message : String(err));
578
+ process.exitCode = 1;
579
+ return;
580
+ }
581
+
582
+ const matches = findCapabilities(index, {
583
+ text: opts.text,
584
+ sourceKind: context.sourceKind,
585
+ scope: scopeFilterForMode(context.scopeMode),
586
+ });
587
+ if (opts.json) {
588
+ console.log(`${JSON.stringify(matches, null, 2)}`);
589
+ return;
590
+ }
591
+
592
+ for (const entry of matches) {
593
+ const desc = entry.description ? `\t${entry.description}` : "";
594
+ console.log(`${entry.kind}:${entry.name}${desc}${formatSourceMeta(entry)}`);
595
+ }
596
+ }
597
+
328
598
  const SECRET_KEY_RE = /(TOKEN|KEY|SECRET|PASSWORD|PASS|BEARER)/i;
329
599
  const SECRETY_STRING_RE =
330
600
  /\b(sk-[A-Za-z0-9]{10,}|ghp_[A-Za-z0-9]{10,}|github_pat_[A-Za-z0-9_]{10,})\b/g;
@@ -333,6 +603,21 @@ function redactPossibleSecrets(value: string): string {
333
603
  return value.replace(SECRETY_STRING_RE, "<redacted>");
334
604
  }
335
605
 
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
+
336
621
  function sanitizeForDisplay(value: unknown): unknown {
337
622
  if (typeof value === "string") {
338
623
  return redactPossibleSecrets(value);
@@ -355,14 +640,22 @@ function sanitizeForDisplay(value: unknown): unknown {
355
640
  }
356
641
 
357
642
  async function showCommand(argv: string[]) {
358
- if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
643
+ const { argv: contextualArgv, context } = resolveContextualOptions(argv, {
644
+ allowSource: true,
645
+ });
646
+
647
+ if (
648
+ contextualArgv.includes("--help") ||
649
+ contextualArgv.includes("-h") ||
650
+ contextualArgv[0] === "help"
651
+ ) {
359
652
  printShowHelp();
360
653
  return;
361
654
  }
362
655
 
363
656
  let showSecrets = false;
364
657
  let raw: string | null = null;
365
- for (const arg of argv) {
658
+ for (const arg of contextualArgv) {
366
659
  if (!arg) {
367
660
  continue;
368
661
  }
@@ -390,7 +683,13 @@ async function showCommand(argv: string[]) {
390
683
 
391
684
  let index: FacultIndex;
392
685
  try {
393
- index = await loadIndex();
686
+ index = await loadIndex({
687
+ rootDir: resolveCliContextRoot({
688
+ rootArg: context.rootArg,
689
+ scope: context.scopeMode,
690
+ cwd: process.cwd(),
691
+ }),
692
+ });
394
693
  } catch (err) {
395
694
  console.error(err instanceof Error ? err.message : String(err));
396
695
  process.exitCode = 1;
@@ -403,25 +702,60 @@ async function showCommand(argv: string[]) {
403
702
  if (raw.startsWith("mcp:")) {
404
703
  kind = "mcp";
405
704
  name = raw.slice("mcp:".length);
406
- }
407
-
408
- let entry: SkillEntry | McpEntry | AgentEntry | SnippetEntry | null = null;
705
+ } else if (raw.startsWith("instruction:")) {
706
+ kind = "instructions";
707
+ name = raw.slice("instruction:".length);
708
+ } else if (raw.startsWith("instructions:")) {
709
+ kind = "instructions";
710
+ name = raw.slice("instructions:".length);
711
+ }
712
+
713
+ let entry:
714
+ | SkillEntry
715
+ | McpEntry
716
+ | AgentEntry
717
+ | SnippetEntry
718
+ | InstructionEntry
719
+ | null = null;
409
720
  const skill = index.skills[name];
410
721
  const mcpServer = index.mcp?.servers?.[name];
411
722
  const agent = index.agents?.[name];
412
723
  const snippet = index.snippets?.[name];
724
+ const instruction = index.instructions?.[name];
725
+ const matchesContext = (candidate: {
726
+ sourceKind?: string;
727
+ scope?: string;
728
+ }): boolean => {
729
+ if (context.sourceKind && candidate.sourceKind !== context.sourceKind) {
730
+ return false;
731
+ }
732
+ const scopeFilter = scopeFilterForMode(context.scopeMode);
733
+ if (scopeFilter && candidate.scope !== scopeFilter) {
734
+ return false;
735
+ }
736
+ return true;
737
+ };
413
738
 
414
- if (kind === "skills" && skill) {
739
+ if (kind === "skills" && skill && matchesContext(skill)) {
415
740
  entry = skill;
416
- } else if (kind === "mcp" && mcpServer) {
741
+ } else if (kind === "mcp" && mcpServer && matchesContext(mcpServer)) {
417
742
  entry = mcpServer;
418
- } else if (kind === "skills" && agent) {
743
+ } else if (kind === "skills" && agent && matchesContext(agent)) {
419
744
  kind = "agents";
420
745
  entry = agent;
421
- } else if (kind === "skills" && snippet) {
746
+ } else if (kind === "skills" && snippet && matchesContext(snippet)) {
422
747
  kind = "snippets";
423
748
  entry = snippet;
424
- } else if (kind === "skills" && mcpServer) {
749
+ } else if (
750
+ kind === "instructions" &&
751
+ instruction &&
752
+ matchesContext(instruction)
753
+ ) {
754
+ entry = instruction;
755
+ } else if (kind === "skills" && instruction && matchesContext(instruction)) {
756
+ kind = "instructions";
757
+ entry = instruction;
758
+ } else if (kind === "skills" && mcpServer && matchesContext(mcpServer)) {
425
759
  kind = "mcp";
426
760
  entry = mcpServer;
427
761
  }
@@ -468,6 +802,91 @@ async function showCommand(argv: string[]) {
468
802
  console.log(displayContents);
469
803
  }
470
804
 
805
+ async function graphCommand(argv: string[]) {
806
+ const { argv: contextualArgv, context } = resolveContextualOptions(argv, {
807
+ allowSource: true,
808
+ });
809
+
810
+ if (
811
+ contextualArgv.includes("--help") ||
812
+ contextualArgv.includes("-h") ||
813
+ contextualArgv[0] === "help"
814
+ ) {
815
+ printGraphHelp();
816
+ return;
817
+ }
818
+
819
+ let opts: GraphCommandOptions;
820
+ try {
821
+ opts = parseGraphArgs(contextualArgv);
822
+ } catch (err) {
823
+ console.error(err instanceof Error ? err.message : String(err));
824
+ process.exitCode = 1;
825
+ return;
826
+ }
827
+
828
+ try {
829
+ const graph = await loadGraph({
830
+ rootDir: resolveCliContextRoot({
831
+ rootArg: context.rootArg,
832
+ scope: context.scopeMode,
833
+ cwd: process.cwd(),
834
+ }),
835
+ });
836
+ const node = resolveGraphNode(graph, opts.target, {
837
+ sourceKind: context.sourceKind,
838
+ scope: scopeFilterForMode(context.scopeMode),
839
+ });
840
+ if (!node) {
841
+ throw new Error(`Graph node not found: ${opts.target}`);
842
+ }
843
+
844
+ const deps = graphDependencies(graph, node.id);
845
+ const dependents = graphDependents(graph, node.id);
846
+
847
+ if (opts.json) {
848
+ if (opts.kind === "show") {
849
+ console.log(
850
+ JSON.stringify({ node, dependencies: deps, dependents }, null, 2)
851
+ );
852
+ } else if (opts.kind === "deps") {
853
+ console.log(JSON.stringify(deps, null, 2));
854
+ } else {
855
+ console.log(JSON.stringify(dependents, null, 2));
856
+ }
857
+ return;
858
+ }
859
+
860
+ if (opts.kind === "show") {
861
+ console.log(node.id);
862
+ console.log(JSON.stringify(node, null, 2));
863
+ console.log("\nDependencies:");
864
+ for (const dep of deps) {
865
+ console.log(
866
+ `- ${dep.edge.kind}\t${dep.node.id}\t(${dep.edge.locator})`
867
+ );
868
+ }
869
+ console.log("\nDependents:");
870
+ for (const dependent of dependents) {
871
+ console.log(
872
+ `- ${dependent.edge.kind}\t${dependent.node.id}\t(${dependent.edge.locator})`
873
+ );
874
+ }
875
+ return;
876
+ }
877
+
878
+ const relations = opts.kind === "deps" ? deps : dependents;
879
+ for (const relation of relations) {
880
+ console.log(
881
+ `${relation.edge.kind}\t${relation.node.id}\t(${relation.edge.locator})`
882
+ );
883
+ }
884
+ } catch (err) {
885
+ console.error(err instanceof Error ? err.message : String(err));
886
+ process.exitCode = 1;
887
+ }
888
+ }
889
+
471
890
  function adaptersCommand(argv: string[]) {
472
891
  if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
473
892
  console.log(
@@ -509,6 +928,9 @@ async function main(argv: string[]) {
509
928
  case "migrate":
510
929
  await migrateCommand(rest);
511
930
  return;
931
+ case "doctor":
932
+ await doctorCommand(rest);
933
+ return;
512
934
  case "consolidate":
513
935
  await consolidateCommand(rest);
514
936
  return;
@@ -521,6 +943,15 @@ async function main(argv: string[]) {
521
943
  case "show":
522
944
  await showCommand(rest);
523
945
  return;
946
+ case "find":
947
+ await findCommand(rest);
948
+ return;
949
+ case "graph":
950
+ await graphCommand(rest);
951
+ return;
952
+ case "ai":
953
+ await aiCommand(rest);
954
+ return;
524
955
  case "adapters":
525
956
  await adaptersCommand(rest);
526
957
  return;
@@ -548,6 +979,9 @@ async function main(argv: string[]) {
548
979
  case "sync":
549
980
  await syncCommand(rest);
550
981
  return;
982
+ case "autosync":
983
+ await autosyncCommand(rest);
984
+ return;
551
985
  case "search":
552
986
  await searchCommand(rest);
553
987
  return;