facult 1.0.3 → 1.1.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 +200 -10
- package/package.json +1 -1
- package/src/adapters/codex.ts +1 -0
- package/src/adapters/types.ts +1 -0
- package/src/agents.ts +180 -0
- package/src/ai-state.ts +55 -0
- package/src/audit/update-index.ts +12 -10
- package/src/autosync.ts +959 -0
- package/src/doctor.ts +128 -0
- package/src/enable-disable.ts +12 -7
- package/src/global-docs.ts +461 -0
- package/src/index-builder.ts +7 -5
- package/src/index.ts +13 -1
- package/src/manage.ts +591 -6
- package/src/paths.ts +48 -16
- package/src/query.ts +15 -6
- package/src/remote.ts +5 -1
- package/src/snippets.ts +106 -0
- package/src/trust.ts +12 -11
package/src/manage.ts
CHANGED
|
@@ -8,8 +8,15 @@ import {
|
|
|
8
8
|
symlink,
|
|
9
9
|
} from "node:fs/promises";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
|
-
import { dirname, join } from "node:path";
|
|
11
|
+
import { basename, dirname, join } from "node:path";
|
|
12
12
|
import { getAdapter } from "./adapters";
|
|
13
|
+
import { renderCanonicalText } from "./agents";
|
|
14
|
+
import { ensureAiIndexPath } from "./ai-state";
|
|
15
|
+
import {
|
|
16
|
+
syncToolConfig,
|
|
17
|
+
syncToolGlobalDocs,
|
|
18
|
+
syncToolRules,
|
|
19
|
+
} from "./global-docs";
|
|
13
20
|
import { facultRootDir } from "./paths";
|
|
14
21
|
|
|
15
22
|
export interface ManagedToolState {
|
|
@@ -17,8 +24,19 @@ export interface ManagedToolState {
|
|
|
17
24
|
managedAt: string;
|
|
18
25
|
skillsDir?: string;
|
|
19
26
|
mcpConfig?: string;
|
|
27
|
+
agentsDir?: string;
|
|
28
|
+
toolHome?: string;
|
|
29
|
+
globalAgentsPath?: string;
|
|
30
|
+
globalAgentsOverridePath?: string;
|
|
31
|
+
rulesDir?: string;
|
|
32
|
+
toolConfig?: string;
|
|
20
33
|
skillsBackup?: string | null;
|
|
21
34
|
mcpBackup?: string | null;
|
|
35
|
+
agentsBackup?: string | null;
|
|
36
|
+
globalAgentsBackup?: string | null;
|
|
37
|
+
globalAgentsOverrideBackup?: string | null;
|
|
38
|
+
rulesBackup?: string | null;
|
|
39
|
+
toolConfigBackup?: string | null;
|
|
22
40
|
}
|
|
23
41
|
|
|
24
42
|
export interface ManagedState {
|
|
@@ -30,6 +48,10 @@ export interface ToolPaths {
|
|
|
30
48
|
tool: string;
|
|
31
49
|
skillsDir?: string;
|
|
32
50
|
mcpConfig?: string;
|
|
51
|
+
agentsDir?: string;
|
|
52
|
+
toolHome?: string;
|
|
53
|
+
rulesDir?: string;
|
|
54
|
+
toolConfig?: string;
|
|
33
55
|
}
|
|
34
56
|
|
|
35
57
|
export interface ManageOptions {
|
|
@@ -90,6 +112,10 @@ function defaultToolPaths(home: string): Record<string, ToolPaths> {
|
|
|
90
112
|
tool: "codex",
|
|
91
113
|
skillsDir: homePath(home, ".codex", "skills"),
|
|
92
114
|
mcpConfig: homePath(home, ".codex", "mcp.json"),
|
|
115
|
+
agentsDir: homePath(home, ".codex", "agents"),
|
|
116
|
+
toolHome: homePath(home, ".codex"),
|
|
117
|
+
rulesDir: homePath(home, ".codex", "rules"),
|
|
118
|
+
toolConfig: homePath(home, ".codex", "config.toml"),
|
|
93
119
|
},
|
|
94
120
|
claude: {
|
|
95
121
|
tool: "claude",
|
|
@@ -130,13 +156,18 @@ function defaultToolPaths(home: string): Record<string, ToolPaths> {
|
|
|
130
156
|
}
|
|
131
157
|
const paths = adapter.getDefaultPaths();
|
|
132
158
|
const rawSkills = paths?.skills;
|
|
159
|
+
const rawAgents = paths?.agents;
|
|
133
160
|
const skillsDir = Array.isArray(rawSkills)
|
|
134
161
|
? rawSkills[0]
|
|
135
162
|
: (rawSkills ?? undefined);
|
|
163
|
+
const agentsDir = Array.isArray(rawAgents)
|
|
164
|
+
? rawAgents[0]
|
|
165
|
+
: (rawAgents ?? undefined);
|
|
136
166
|
|
|
137
167
|
return {
|
|
138
168
|
tool,
|
|
139
169
|
skillsDir: skillsDir ? expandHomePath(skillsDir, home) : undefined,
|
|
170
|
+
agentsDir: agentsDir ? expandHomePath(agentsDir, home) : undefined,
|
|
140
171
|
mcpConfig: paths?.mcp ? expandHomePath(paths.mcp, home) : undefined,
|
|
141
172
|
};
|
|
142
173
|
};
|
|
@@ -255,6 +286,154 @@ function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
|
255
286
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
256
287
|
}
|
|
257
288
|
|
|
289
|
+
async function readTextIfExists(p: string): Promise<string | null> {
|
|
290
|
+
if (!(await fileExists(p))) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
return await Bun.file(p).text();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function loadCanonicalAgents(
|
|
297
|
+
rootDir: string
|
|
298
|
+
): Promise<{ name: string; sourcePath: string; raw: string }[]> {
|
|
299
|
+
const agentsRoot = homePath(rootDir, "agents");
|
|
300
|
+
const entries = await readdir(agentsRoot, { withFileTypes: true }).catch(
|
|
301
|
+
() => [] as import("node:fs").Dirent[]
|
|
302
|
+
);
|
|
303
|
+
const out: { name: string; sourcePath: string; raw: string }[] = [];
|
|
304
|
+
|
|
305
|
+
for (const entry of entries) {
|
|
306
|
+
if (entry.name.startsWith(".")) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
const sourcePath = entry.isDirectory()
|
|
310
|
+
? homePath(agentsRoot, entry.name, "agent.toml")
|
|
311
|
+
: entry.isFile() && entry.name.endsWith(".toml")
|
|
312
|
+
? homePath(agentsRoot, entry.name)
|
|
313
|
+
: null;
|
|
314
|
+
if (!sourcePath) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const raw = await readTextIfExists(sourcePath);
|
|
318
|
+
if (raw == null) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const name = entry.isDirectory()
|
|
322
|
+
? entry.name
|
|
323
|
+
: basename(entry.name, ".toml");
|
|
324
|
+
out.push({
|
|
325
|
+
name,
|
|
326
|
+
sourcePath,
|
|
327
|
+
raw,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function planAgentFileChanges({
|
|
335
|
+
agentsDir,
|
|
336
|
+
homeDir,
|
|
337
|
+
rootDir,
|
|
338
|
+
tool,
|
|
339
|
+
}: {
|
|
340
|
+
agentsDir: string;
|
|
341
|
+
homeDir: string;
|
|
342
|
+
rootDir: string;
|
|
343
|
+
tool: string;
|
|
344
|
+
}): Promise<{
|
|
345
|
+
add: string[];
|
|
346
|
+
remove: string[];
|
|
347
|
+
contents: Map<string, string>;
|
|
348
|
+
}> {
|
|
349
|
+
const agents = await loadCanonicalAgents(rootDir);
|
|
350
|
+
const contents = new Map<string, string>();
|
|
351
|
+
const desiredPaths = new Set<string>();
|
|
352
|
+
|
|
353
|
+
for (const agent of agents) {
|
|
354
|
+
const target = homePath(agentsDir, `${agent.name}.toml`);
|
|
355
|
+
const rendered = await renderCanonicalText(agent.raw, {
|
|
356
|
+
homeDir,
|
|
357
|
+
rootDir,
|
|
358
|
+
targetTool: tool,
|
|
359
|
+
targetPath: target,
|
|
360
|
+
});
|
|
361
|
+
desiredPaths.add(target);
|
|
362
|
+
contents.set(target, rendered);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const existing = await readdir(agentsDir, { withFileTypes: true }).catch(
|
|
366
|
+
() => [] as import("node:fs").Dirent[]
|
|
367
|
+
);
|
|
368
|
+
const add = new Set<string>();
|
|
369
|
+
const remove = new Set<string>();
|
|
370
|
+
|
|
371
|
+
for (const entry of existing) {
|
|
372
|
+
if (!(entry.isFile() && entry.name.endsWith(".toml"))) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
const p = homePath(agentsDir, entry.name);
|
|
376
|
+
if (!desiredPaths.has(p)) {
|
|
377
|
+
remove.add(p);
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
const desired = contents.get(p);
|
|
381
|
+
const current = await readTextIfExists(p);
|
|
382
|
+
if (desired != null && current !== desired) {
|
|
383
|
+
add.add(p);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
for (const p of desiredPaths) {
|
|
388
|
+
const current = await readTextIfExists(p);
|
|
389
|
+
const desired = contents.get(p);
|
|
390
|
+
if (desired != null && current !== desired) {
|
|
391
|
+
add.add(p);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
add: Array.from(add).sort(),
|
|
397
|
+
remove: Array.from(remove).sort(),
|
|
398
|
+
contents,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async function syncAgentFiles({
|
|
403
|
+
agentsDir,
|
|
404
|
+
homeDir,
|
|
405
|
+
rootDir,
|
|
406
|
+
tool,
|
|
407
|
+
dryRun,
|
|
408
|
+
}: {
|
|
409
|
+
agentsDir: string;
|
|
410
|
+
homeDir: string;
|
|
411
|
+
rootDir: string;
|
|
412
|
+
tool: string;
|
|
413
|
+
dryRun?: boolean;
|
|
414
|
+
}): Promise<{ add: string[]; remove: string[] }> {
|
|
415
|
+
const plan = await planAgentFileChanges({
|
|
416
|
+
agentsDir,
|
|
417
|
+
homeDir,
|
|
418
|
+
rootDir,
|
|
419
|
+
tool,
|
|
420
|
+
});
|
|
421
|
+
if (dryRun) {
|
|
422
|
+
return { add: plan.add, remove: plan.remove };
|
|
423
|
+
}
|
|
424
|
+
await ensureDir(agentsDir);
|
|
425
|
+
for (const p of plan.remove) {
|
|
426
|
+
await rm(p, { force: true });
|
|
427
|
+
}
|
|
428
|
+
for (const p of plan.add) {
|
|
429
|
+
const desired = plan.contents.get(p);
|
|
430
|
+
if (desired != null) {
|
|
431
|
+
await Bun.write(p, desired.endsWith("\n") ? desired : `${desired}\n`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return { add: plan.add, remove: plan.remove };
|
|
435
|
+
}
|
|
436
|
+
|
|
258
437
|
async function listSkillDirs(skillsRoot: string): Promise<string[]> {
|
|
259
438
|
try {
|
|
260
439
|
const entries = await readdir(skillsRoot, { withFileTypes: true });
|
|
@@ -293,13 +472,19 @@ function skillNamesFromIndex(
|
|
|
293
472
|
}
|
|
294
473
|
|
|
295
474
|
async function loadEnabledSkillNames({
|
|
475
|
+
homeDir,
|
|
296
476
|
rootDir,
|
|
297
477
|
tool,
|
|
298
478
|
}: {
|
|
479
|
+
homeDir: string;
|
|
299
480
|
rootDir: string;
|
|
300
481
|
tool: string;
|
|
301
482
|
}): Promise<string[]> {
|
|
302
|
-
const indexPath =
|
|
483
|
+
const { path: indexPath } = await ensureAiIndexPath({
|
|
484
|
+
homeDir,
|
|
485
|
+
rootDir,
|
|
486
|
+
repair: true,
|
|
487
|
+
});
|
|
303
488
|
if (await fileExists(indexPath)) {
|
|
304
489
|
try {
|
|
305
490
|
const txt = await Bun.file(indexPath).text();
|
|
@@ -405,16 +590,22 @@ async function ensureEmptyDir(p: string) {
|
|
|
405
590
|
}
|
|
406
591
|
|
|
407
592
|
async function createSkillSymlinks({
|
|
593
|
+
homeDir,
|
|
408
594
|
toolSkillsDir,
|
|
409
595
|
rootDir,
|
|
410
596
|
tool,
|
|
411
597
|
}: {
|
|
598
|
+
homeDir: string;
|
|
412
599
|
toolSkillsDir: string;
|
|
413
600
|
rootDir: string;
|
|
414
601
|
tool: string;
|
|
415
602
|
}) {
|
|
416
603
|
await ensureDir(toolSkillsDir);
|
|
417
|
-
const skillNames = await loadEnabledSkillNames({
|
|
604
|
+
const skillNames = await loadEnabledSkillNames({
|
|
605
|
+
homeDir,
|
|
606
|
+
rootDir,
|
|
607
|
+
tool,
|
|
608
|
+
});
|
|
418
609
|
for (const name of skillNames) {
|
|
419
610
|
const target = join(rootDir, "skills", name);
|
|
420
611
|
if (!(await fileExists(target))) {
|
|
@@ -435,15 +626,17 @@ async function createSkillSymlinks({
|
|
|
435
626
|
}
|
|
436
627
|
|
|
437
628
|
async function planSkillSymlinkChanges({
|
|
629
|
+
homeDir,
|
|
438
630
|
toolSkillsDir,
|
|
439
631
|
rootDir,
|
|
440
632
|
tool,
|
|
441
633
|
}: {
|
|
634
|
+
homeDir: string;
|
|
442
635
|
toolSkillsDir: string;
|
|
443
636
|
rootDir: string;
|
|
444
637
|
tool: string;
|
|
445
638
|
}): Promise<{ add: string[]; remove: string[] }> {
|
|
446
|
-
const desired = await loadEnabledSkillNames({ rootDir, tool });
|
|
639
|
+
const desired = await loadEnabledSkillNames({ homeDir, rootDir, tool });
|
|
447
640
|
const desiredSet = new Set(desired);
|
|
448
641
|
const existing = await readdir(toolSkillsDir, { withFileTypes: true }).catch(
|
|
449
642
|
() => [] as import("node:fs").Dirent[]
|
|
@@ -493,17 +686,24 @@ async function planSkillSymlinkChanges({
|
|
|
493
686
|
}
|
|
494
687
|
|
|
495
688
|
async function syncSkillSymlinks({
|
|
689
|
+
homeDir,
|
|
496
690
|
toolSkillsDir,
|
|
497
691
|
rootDir,
|
|
498
692
|
tool,
|
|
499
693
|
dryRun,
|
|
500
694
|
}: {
|
|
695
|
+
homeDir: string;
|
|
501
696
|
toolSkillsDir: string;
|
|
502
697
|
rootDir: string;
|
|
503
698
|
tool: string;
|
|
504
699
|
dryRun?: boolean;
|
|
505
700
|
}): Promise<{ add: string[]; remove: string[] }> {
|
|
506
|
-
const plan = await planSkillSymlinkChanges({
|
|
701
|
+
const plan = await planSkillSymlinkChanges({
|
|
702
|
+
homeDir,
|
|
703
|
+
toolSkillsDir,
|
|
704
|
+
rootDir,
|
|
705
|
+
tool,
|
|
706
|
+
});
|
|
507
707
|
if (dryRun) {
|
|
508
708
|
return plan;
|
|
509
709
|
}
|
|
@@ -601,6 +801,33 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
601
801
|
if (!toolPaths) {
|
|
602
802
|
throw new Error(`Unknown tool: ${tool}`);
|
|
603
803
|
}
|
|
804
|
+
const globalDocsPreview = toolPaths.toolHome
|
|
805
|
+
? await syncToolGlobalDocs({
|
|
806
|
+
homeDir: home,
|
|
807
|
+
rootDir,
|
|
808
|
+
tool,
|
|
809
|
+
toolHome: toolPaths.toolHome,
|
|
810
|
+
dryRun: true,
|
|
811
|
+
})
|
|
812
|
+
: null;
|
|
813
|
+
const rulesPreview = toolPaths.rulesDir
|
|
814
|
+
? await syncToolRules({
|
|
815
|
+
homeDir: home,
|
|
816
|
+
rootDir,
|
|
817
|
+
tool,
|
|
818
|
+
rulesDir: toolPaths.rulesDir,
|
|
819
|
+
dryRun: true,
|
|
820
|
+
})
|
|
821
|
+
: null;
|
|
822
|
+
const toolConfigPreview = toolPaths.toolConfig
|
|
823
|
+
? await syncToolConfig({
|
|
824
|
+
homeDir: home,
|
|
825
|
+
rootDir,
|
|
826
|
+
tool,
|
|
827
|
+
toolConfigPath: toolPaths.toolConfig,
|
|
828
|
+
dryRun: true,
|
|
829
|
+
})
|
|
830
|
+
: null;
|
|
604
831
|
|
|
605
832
|
const skillsBackup = toolPaths.skillsDir
|
|
606
833
|
? await backupPath(toolPaths.skillsDir, opts.now)
|
|
@@ -608,16 +835,49 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
608
835
|
const mcpBackup = toolPaths.mcpConfig
|
|
609
836
|
? await backupPath(toolPaths.mcpConfig, opts.now)
|
|
610
837
|
: null;
|
|
838
|
+
const agentsBackup = toolPaths.agentsDir
|
|
839
|
+
? await backupPath(toolPaths.agentsDir, opts.now)
|
|
840
|
+
: null;
|
|
841
|
+
const globalAgentsBackup =
|
|
842
|
+
toolPaths.toolHome &&
|
|
843
|
+
globalDocsPreview?.managedTargets.includes(
|
|
844
|
+
join(toolPaths.toolHome, "AGENTS.md")
|
|
845
|
+
)
|
|
846
|
+
? await backupPath(join(toolPaths.toolHome, "AGENTS.md"), opts.now)
|
|
847
|
+
: null;
|
|
848
|
+
const globalAgentsOverrideBackup =
|
|
849
|
+
toolPaths.toolHome &&
|
|
850
|
+
globalDocsPreview?.managedTargets.includes(
|
|
851
|
+
join(toolPaths.toolHome, "AGENTS.override.md")
|
|
852
|
+
)
|
|
853
|
+
? await backupPath(
|
|
854
|
+
join(toolPaths.toolHome, "AGENTS.override.md"),
|
|
855
|
+
opts.now
|
|
856
|
+
)
|
|
857
|
+
: null;
|
|
858
|
+
const rulesBackup =
|
|
859
|
+
toolPaths.rulesDir && rulesPreview?.managedRulesDir
|
|
860
|
+
? await backupPath(toolPaths.rulesDir, opts.now)
|
|
861
|
+
: null;
|
|
862
|
+
const toolConfigBackup =
|
|
863
|
+
toolPaths.toolConfig && toolConfigPreview?.managedConfig
|
|
864
|
+
? await backupPath(toolPaths.toolConfig, opts.now)
|
|
865
|
+
: null;
|
|
611
866
|
|
|
612
867
|
if (toolPaths.skillsDir) {
|
|
613
868
|
await ensureEmptyDir(toolPaths.skillsDir);
|
|
614
869
|
await createSkillSymlinks({
|
|
870
|
+
homeDir: home,
|
|
615
871
|
toolSkillsDir: toolPaths.skillsDir,
|
|
616
872
|
rootDir,
|
|
617
873
|
tool,
|
|
618
874
|
});
|
|
619
875
|
}
|
|
620
876
|
|
|
877
|
+
if (toolPaths.agentsDir) {
|
|
878
|
+
await ensureEmptyDir(toolPaths.agentsDir);
|
|
879
|
+
}
|
|
880
|
+
|
|
621
881
|
if (toolPaths.mcpConfig) {
|
|
622
882
|
await writeToolMcpConfig({
|
|
623
883
|
mcpConfigPath: toolPaths.mcpConfig,
|
|
@@ -626,13 +886,76 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
626
886
|
});
|
|
627
887
|
}
|
|
628
888
|
|
|
889
|
+
if (toolPaths.agentsDir) {
|
|
890
|
+
await syncAgentFiles({
|
|
891
|
+
agentsDir: toolPaths.agentsDir,
|
|
892
|
+
homeDir: home,
|
|
893
|
+
rootDir,
|
|
894
|
+
tool,
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (toolPaths.toolHome && globalDocsPreview) {
|
|
899
|
+
await ensureDir(toolPaths.toolHome);
|
|
900
|
+
await syncToolGlobalDocs({
|
|
901
|
+
homeDir: home,
|
|
902
|
+
rootDir,
|
|
903
|
+
tool,
|
|
904
|
+
toolHome: toolPaths.toolHome,
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (toolPaths.rulesDir && rulesPreview?.managedRulesDir) {
|
|
909
|
+
await ensureEmptyDir(toolPaths.rulesDir);
|
|
910
|
+
await syncToolRules({
|
|
911
|
+
homeDir: home,
|
|
912
|
+
rootDir,
|
|
913
|
+
tool,
|
|
914
|
+
rulesDir: toolPaths.rulesDir,
|
|
915
|
+
previouslyManaged: true,
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if (toolPaths.toolConfig && toolConfigPreview?.managedConfig) {
|
|
920
|
+
await syncToolConfig({
|
|
921
|
+
homeDir: home,
|
|
922
|
+
rootDir,
|
|
923
|
+
tool,
|
|
924
|
+
toolConfigPath: toolPaths.toolConfig,
|
|
925
|
+
existingConfigPath: toolConfigBackup ?? undefined,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
|
|
629
929
|
state.tools[tool] = {
|
|
630
930
|
tool,
|
|
631
931
|
managedAt: nowIso(opts.now),
|
|
632
932
|
skillsDir: toolPaths.skillsDir,
|
|
633
933
|
mcpConfig: toolPaths.mcpConfig,
|
|
934
|
+
agentsDir: toolPaths.agentsDir,
|
|
935
|
+
toolHome: globalDocsPreview?.managedTargets.length
|
|
936
|
+
? toolPaths.toolHome
|
|
937
|
+
: undefined,
|
|
938
|
+
globalAgentsPath: globalDocsPreview?.managedTargets.includes(
|
|
939
|
+
join(toolPaths.toolHome ?? "", "AGENTS.md")
|
|
940
|
+
)
|
|
941
|
+
? join(toolPaths.toolHome ?? "", "AGENTS.md")
|
|
942
|
+
: undefined,
|
|
943
|
+
globalAgentsOverridePath: globalDocsPreview?.managedTargets.includes(
|
|
944
|
+
join(toolPaths.toolHome ?? "", "AGENTS.override.md")
|
|
945
|
+
)
|
|
946
|
+
? join(toolPaths.toolHome ?? "", "AGENTS.override.md")
|
|
947
|
+
: undefined,
|
|
948
|
+
rulesDir: rulesPreview?.managedRulesDir ? toolPaths.rulesDir : undefined,
|
|
949
|
+
toolConfig: toolConfigPreview?.managedConfig
|
|
950
|
+
? toolPaths.toolConfig
|
|
951
|
+
: undefined,
|
|
634
952
|
skillsBackup,
|
|
635
953
|
mcpBackup,
|
|
954
|
+
agentsBackup,
|
|
955
|
+
globalAgentsBackup,
|
|
956
|
+
globalAgentsOverrideBackup,
|
|
957
|
+
rulesBackup,
|
|
958
|
+
toolConfigBackup,
|
|
636
959
|
};
|
|
637
960
|
|
|
638
961
|
await saveManagedState(state, home);
|
|
@@ -697,6 +1020,41 @@ export async function unmanageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
697
1020
|
});
|
|
698
1021
|
}
|
|
699
1022
|
|
|
1023
|
+
if (entry.agentsDir) {
|
|
1024
|
+
await restoreBackup({
|
|
1025
|
+
original: entry.agentsDir,
|
|
1026
|
+
backup: entry.agentsBackup ?? null,
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
if (entry.globalAgentsPath) {
|
|
1031
|
+
await restoreBackup({
|
|
1032
|
+
original: entry.globalAgentsPath,
|
|
1033
|
+
backup: entry.globalAgentsBackup ?? null,
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (entry.globalAgentsOverridePath) {
|
|
1038
|
+
await restoreBackup({
|
|
1039
|
+
original: entry.globalAgentsOverridePath,
|
|
1040
|
+
backup: entry.globalAgentsOverrideBackup ?? null,
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
if (entry.rulesDir) {
|
|
1045
|
+
await restoreBackup({
|
|
1046
|
+
original: entry.rulesDir,
|
|
1047
|
+
backup: entry.rulesBackup ?? null,
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (entry.toolConfig) {
|
|
1052
|
+
await restoreBackup({
|
|
1053
|
+
original: entry.toolConfig,
|
|
1054
|
+
backup: entry.toolConfigBackup ?? null,
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
|
|
700
1058
|
const nextTools: ManagedState["tools"] = {};
|
|
701
1059
|
for (const [name, config] of Object.entries(state.tools)) {
|
|
702
1060
|
if (name === tool) {
|
|
@@ -715,16 +1073,122 @@ export async function listManagedTools(
|
|
|
715
1073
|
return Object.keys(state.tools).sort();
|
|
716
1074
|
}
|
|
717
1075
|
|
|
1076
|
+
async function canonicalAgentsExist(rootDir: string): Promise<boolean> {
|
|
1077
|
+
try {
|
|
1078
|
+
const agents = await loadCanonicalAgents(rootDir);
|
|
1079
|
+
return agents.length > 0;
|
|
1080
|
+
} catch {
|
|
1081
|
+
return false;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
async function repairManagedToolEntry(args: {
|
|
1086
|
+
homeDir: string;
|
|
1087
|
+
rootDir: string;
|
|
1088
|
+
tool: string;
|
|
1089
|
+
entry: ManagedToolState;
|
|
1090
|
+
}): Promise<{ entry: ManagedToolState; changed: boolean }> {
|
|
1091
|
+
const { homeDir, rootDir, tool } = args;
|
|
1092
|
+
const toolPaths = await resolveToolPaths(tool, homeDir);
|
|
1093
|
+
if (!toolPaths) {
|
|
1094
|
+
return { entry: args.entry, changed: false };
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
const next: ManagedToolState = { ...args.entry };
|
|
1098
|
+
let changed = false;
|
|
1099
|
+
|
|
1100
|
+
if (
|
|
1101
|
+
!next.agentsDir &&
|
|
1102
|
+
toolPaths.agentsDir &&
|
|
1103
|
+
(await canonicalAgentsExist(rootDir))
|
|
1104
|
+
) {
|
|
1105
|
+
next.agentsBackup = await backupPath(toolPaths.agentsDir);
|
|
1106
|
+
next.agentsDir = toolPaths.agentsDir;
|
|
1107
|
+
changed = true;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
if (toolPaths.toolHome && !next.toolHome) {
|
|
1111
|
+
const preview = await syncToolGlobalDocs({
|
|
1112
|
+
homeDir,
|
|
1113
|
+
rootDir,
|
|
1114
|
+
tool,
|
|
1115
|
+
toolHome: toolPaths.toolHome,
|
|
1116
|
+
dryRun: true,
|
|
1117
|
+
});
|
|
1118
|
+
if (preview.managedTargets.length > 0) {
|
|
1119
|
+
next.toolHome = toolPaths.toolHome;
|
|
1120
|
+
const agentsPath = join(toolPaths.toolHome, "AGENTS.md");
|
|
1121
|
+
const overridePath = join(toolPaths.toolHome, "AGENTS.override.md");
|
|
1122
|
+
if (
|
|
1123
|
+
preview.managedTargets.includes(agentsPath) &&
|
|
1124
|
+
!next.globalAgentsPath
|
|
1125
|
+
) {
|
|
1126
|
+
next.globalAgentsBackup = await backupPath(agentsPath);
|
|
1127
|
+
next.globalAgentsPath = agentsPath;
|
|
1128
|
+
changed = true;
|
|
1129
|
+
}
|
|
1130
|
+
if (
|
|
1131
|
+
preview.managedTargets.includes(overridePath) &&
|
|
1132
|
+
!next.globalAgentsOverridePath
|
|
1133
|
+
) {
|
|
1134
|
+
next.globalAgentsOverrideBackup = await backupPath(overridePath);
|
|
1135
|
+
next.globalAgentsOverridePath = overridePath;
|
|
1136
|
+
changed = true;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
if (!next.rulesDir && toolPaths.rulesDir) {
|
|
1142
|
+
const preview = await syncToolRules({
|
|
1143
|
+
homeDir,
|
|
1144
|
+
rootDir,
|
|
1145
|
+
tool,
|
|
1146
|
+
rulesDir: toolPaths.rulesDir,
|
|
1147
|
+
dryRun: true,
|
|
1148
|
+
});
|
|
1149
|
+
if (preview.managedRulesDir) {
|
|
1150
|
+
next.rulesBackup = await backupPath(toolPaths.rulesDir);
|
|
1151
|
+
next.rulesDir = toolPaths.rulesDir;
|
|
1152
|
+
changed = true;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
if (!next.toolConfig && toolPaths.toolConfig) {
|
|
1157
|
+
const preview = await syncToolConfig({
|
|
1158
|
+
homeDir,
|
|
1159
|
+
rootDir,
|
|
1160
|
+
tool,
|
|
1161
|
+
toolConfigPath: toolPaths.toolConfig,
|
|
1162
|
+
dryRun: true,
|
|
1163
|
+
});
|
|
1164
|
+
if (preview.managedConfig) {
|
|
1165
|
+
next.toolConfigBackup = await backupPath(toolPaths.toolConfig);
|
|
1166
|
+
next.toolConfig = toolPaths.toolConfig;
|
|
1167
|
+
changed = true;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
return { entry: next, changed };
|
|
1172
|
+
}
|
|
1173
|
+
|
|
718
1174
|
function logSyncDryRun({
|
|
719
1175
|
tool,
|
|
720
1176
|
entry,
|
|
721
1177
|
skillPlan,
|
|
722
1178
|
mcpPlan,
|
|
1179
|
+
agentPlan,
|
|
1180
|
+
globalDocsPlan,
|
|
1181
|
+
rulesPlan,
|
|
1182
|
+
configPlan,
|
|
723
1183
|
}: {
|
|
724
1184
|
tool: string;
|
|
725
1185
|
entry: ManagedToolState;
|
|
726
1186
|
skillPlan: { add: string[]; remove: string[] };
|
|
727
1187
|
mcpPlan: { needsWrite: boolean };
|
|
1188
|
+
agentPlan: { add: string[]; remove: string[] };
|
|
1189
|
+
globalDocsPlan: { write: string[]; remove: string[] };
|
|
1190
|
+
rulesPlan: { write: string[]; remove: string[] };
|
|
1191
|
+
configPlan: { write: boolean; remove: boolean; targetPath: string };
|
|
728
1192
|
}) {
|
|
729
1193
|
for (const name of skillPlan.add) {
|
|
730
1194
|
console.log(`${tool}: would add skill ${name}`);
|
|
@@ -732,12 +1196,44 @@ function logSyncDryRun({
|
|
|
732
1196
|
for (const name of skillPlan.remove) {
|
|
733
1197
|
console.log(`${tool}: would remove skill ${name}`);
|
|
734
1198
|
}
|
|
1199
|
+
for (const p of agentPlan.add) {
|
|
1200
|
+
console.log(`${tool}: would write agent ${p}`);
|
|
1201
|
+
}
|
|
1202
|
+
for (const p of agentPlan.remove) {
|
|
1203
|
+
console.log(`${tool}: would remove agent ${p}`);
|
|
1204
|
+
}
|
|
1205
|
+
for (const p of globalDocsPlan.write) {
|
|
1206
|
+
console.log(`${tool}: would write global doc ${p}`);
|
|
1207
|
+
}
|
|
1208
|
+
for (const p of globalDocsPlan.remove) {
|
|
1209
|
+
console.log(`${tool}: would remove global doc ${p}`);
|
|
1210
|
+
}
|
|
1211
|
+
for (const p of rulesPlan.write) {
|
|
1212
|
+
console.log(`${tool}: would write rule ${p}`);
|
|
1213
|
+
}
|
|
1214
|
+
for (const p of rulesPlan.remove) {
|
|
1215
|
+
console.log(`${tool}: would remove rule ${p}`);
|
|
1216
|
+
}
|
|
1217
|
+
if (configPlan.write) {
|
|
1218
|
+
console.log(`${tool}: would write tool config ${configPlan.targetPath}`);
|
|
1219
|
+
}
|
|
1220
|
+
if (configPlan.remove) {
|
|
1221
|
+
console.log(`${tool}: would remove tool config ${configPlan.targetPath}`);
|
|
1222
|
+
}
|
|
735
1223
|
if (mcpPlan.needsWrite && entry.mcpConfig) {
|
|
736
1224
|
console.log(`${tool}: would update mcp config ${entry.mcpConfig}`);
|
|
737
1225
|
}
|
|
738
1226
|
if (
|
|
739
1227
|
skillPlan.add.length === 0 &&
|
|
740
1228
|
skillPlan.remove.length === 0 &&
|
|
1229
|
+
agentPlan.add.length === 0 &&
|
|
1230
|
+
agentPlan.remove.length === 0 &&
|
|
1231
|
+
globalDocsPlan.write.length === 0 &&
|
|
1232
|
+
globalDocsPlan.remove.length === 0 &&
|
|
1233
|
+
rulesPlan.write.length === 0 &&
|
|
1234
|
+
rulesPlan.remove.length === 0 &&
|
|
1235
|
+
!configPlan.write &&
|
|
1236
|
+
!configPlan.remove &&
|
|
741
1237
|
!mcpPlan.needsWrite
|
|
742
1238
|
) {
|
|
743
1239
|
console.log(`${tool}: no changes`);
|
|
@@ -745,11 +1241,13 @@ function logSyncDryRun({
|
|
|
745
1241
|
}
|
|
746
1242
|
|
|
747
1243
|
async function syncManagedToolEntry({
|
|
1244
|
+
homeDir,
|
|
748
1245
|
tool,
|
|
749
1246
|
entry,
|
|
750
1247
|
rootDir,
|
|
751
1248
|
dryRun,
|
|
752
1249
|
}: {
|
|
1250
|
+
homeDir: string;
|
|
753
1251
|
tool: string;
|
|
754
1252
|
entry: ManagedToolState;
|
|
755
1253
|
rootDir: string;
|
|
@@ -757,6 +1255,7 @@ async function syncManagedToolEntry({
|
|
|
757
1255
|
}) {
|
|
758
1256
|
const skillPlan = entry.skillsDir
|
|
759
1257
|
? await syncSkillSymlinks({
|
|
1258
|
+
homeDir,
|
|
760
1259
|
toolSkillsDir: entry.skillsDir,
|
|
761
1260
|
rootDir,
|
|
762
1261
|
tool,
|
|
@@ -764,6 +1263,16 @@ async function syncManagedToolEntry({
|
|
|
764
1263
|
})
|
|
765
1264
|
: { add: [], remove: [] };
|
|
766
1265
|
|
|
1266
|
+
const agentPlan = entry.agentsDir
|
|
1267
|
+
? await syncAgentFiles({
|
|
1268
|
+
agentsDir: entry.agentsDir,
|
|
1269
|
+
homeDir,
|
|
1270
|
+
rootDir,
|
|
1271
|
+
tool,
|
|
1272
|
+
dryRun,
|
|
1273
|
+
})
|
|
1274
|
+
: { add: [], remove: [] };
|
|
1275
|
+
|
|
767
1276
|
const mcpPlan = entry.mcpConfig
|
|
768
1277
|
? await syncMcpConfig({
|
|
769
1278
|
mcpConfigPath: entry.mcpConfig,
|
|
@@ -773,8 +1282,60 @@ async function syncManagedToolEntry({
|
|
|
773
1282
|
})
|
|
774
1283
|
: { needsWrite: false };
|
|
775
1284
|
|
|
1285
|
+
const globalDocsPlan = entry.toolHome
|
|
1286
|
+
? await syncToolGlobalDocs({
|
|
1287
|
+
homeDir,
|
|
1288
|
+
rootDir,
|
|
1289
|
+
tool,
|
|
1290
|
+
toolHome: entry.toolHome,
|
|
1291
|
+
previouslyManagedTargets: [
|
|
1292
|
+
entry.globalAgentsPath,
|
|
1293
|
+
entry.globalAgentsOverridePath,
|
|
1294
|
+
].filter((value): value is string => Boolean(value)),
|
|
1295
|
+
dryRun,
|
|
1296
|
+
})
|
|
1297
|
+
: { write: [], remove: [], contents: new Map(), managedTargets: [] };
|
|
1298
|
+
|
|
1299
|
+
const rulesPlan = entry.rulesDir
|
|
1300
|
+
? await syncToolRules({
|
|
1301
|
+
homeDir,
|
|
1302
|
+
rootDir,
|
|
1303
|
+
tool,
|
|
1304
|
+
rulesDir: entry.rulesDir,
|
|
1305
|
+
previouslyManaged: true,
|
|
1306
|
+
dryRun,
|
|
1307
|
+
})
|
|
1308
|
+
: { write: [], remove: [], contents: new Map(), managedRulesDir: false };
|
|
1309
|
+
|
|
1310
|
+
const configPlan = entry.toolConfig
|
|
1311
|
+
? await syncToolConfig({
|
|
1312
|
+
homeDir,
|
|
1313
|
+
rootDir,
|
|
1314
|
+
tool,
|
|
1315
|
+
toolConfigPath: entry.toolConfig,
|
|
1316
|
+
existingConfigPath: entry.toolConfigBackup ?? undefined,
|
|
1317
|
+
previouslyManaged: true,
|
|
1318
|
+
dryRun,
|
|
1319
|
+
})
|
|
1320
|
+
: {
|
|
1321
|
+
write: false,
|
|
1322
|
+
remove: false,
|
|
1323
|
+
contents: null,
|
|
1324
|
+
managedConfig: false,
|
|
1325
|
+
targetPath: "",
|
|
1326
|
+
};
|
|
1327
|
+
|
|
776
1328
|
if (dryRun) {
|
|
777
|
-
logSyncDryRun({
|
|
1329
|
+
logSyncDryRun({
|
|
1330
|
+
tool,
|
|
1331
|
+
entry,
|
|
1332
|
+
skillPlan,
|
|
1333
|
+
mcpPlan,
|
|
1334
|
+
agentPlan,
|
|
1335
|
+
globalDocsPlan,
|
|
1336
|
+
rulesPlan,
|
|
1337
|
+
configPlan,
|
|
1338
|
+
});
|
|
778
1339
|
} else {
|
|
779
1340
|
console.log(`${tool} synced`);
|
|
780
1341
|
}
|
|
@@ -790,12 +1351,36 @@ export async function syncManagedTools(opts: SyncOptions = {}) {
|
|
|
790
1351
|
throw new Error("No managed tools to sync.");
|
|
791
1352
|
}
|
|
792
1353
|
|
|
1354
|
+
if (!opts.dryRun) {
|
|
1355
|
+
let changed = false;
|
|
1356
|
+
for (const tool of tools) {
|
|
1357
|
+
const entry = state.tools[tool];
|
|
1358
|
+
if (!entry) {
|
|
1359
|
+
throw new Error(`${tool} is not managed`);
|
|
1360
|
+
}
|
|
1361
|
+
const repaired = await repairManagedToolEntry({
|
|
1362
|
+
homeDir: home,
|
|
1363
|
+
rootDir,
|
|
1364
|
+
tool,
|
|
1365
|
+
entry,
|
|
1366
|
+
});
|
|
1367
|
+
if (repaired.changed) {
|
|
1368
|
+
state.tools[tool] = repaired.entry;
|
|
1369
|
+
changed = true;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
if (changed) {
|
|
1373
|
+
await saveManagedState(state, home);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
793
1377
|
for (const tool of tools) {
|
|
794
1378
|
const entry = state.tools[tool];
|
|
795
1379
|
if (!entry) {
|
|
796
1380
|
throw new Error(`${tool} is not managed`);
|
|
797
1381
|
}
|
|
798
1382
|
await syncManagedToolEntry({
|
|
1383
|
+
homeDir: home,
|
|
799
1384
|
tool,
|
|
800
1385
|
entry,
|
|
801
1386
|
rootDir,
|