facult 2.7.0 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -337
- package/package.json +1 -1
- package/src/adapters/codex.ts +1 -1
- package/src/builtin.ts +7 -1
- package/src/doctor.ts +327 -0
- package/src/global-docs.ts +43 -2
- package/src/index.ts +60 -53
- package/src/manage.ts +880 -37
- package/src/project-sync.ts +288 -0
package/src/manage.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import {
|
|
2
3
|
cp,
|
|
3
4
|
lstat,
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
legacyFacultStateDirForRoot,
|
|
44
45
|
projectRootFromAiRoot,
|
|
45
46
|
} from "./paths";
|
|
47
|
+
import { loadProjectToolSyncPolicy } from "./project-sync";
|
|
46
48
|
|
|
47
49
|
export interface ManagedToolState {
|
|
48
50
|
tool: string;
|
|
@@ -50,6 +52,8 @@ export interface ManagedToolState {
|
|
|
50
52
|
skillsDir?: string;
|
|
51
53
|
mcpConfig?: string;
|
|
52
54
|
agentsDir?: string;
|
|
55
|
+
pluginsDir?: string;
|
|
56
|
+
pluginMarketplacePath?: string;
|
|
53
57
|
automationDir?: string;
|
|
54
58
|
toolHome?: string;
|
|
55
59
|
globalAgentsPath?: string;
|
|
@@ -59,6 +63,8 @@ export interface ManagedToolState {
|
|
|
59
63
|
skillsBackup?: string | null;
|
|
60
64
|
mcpBackup?: string | null;
|
|
61
65
|
agentsBackup?: string | null;
|
|
66
|
+
pluginsBackup?: string | null;
|
|
67
|
+
pluginMarketplaceBackup?: string | null;
|
|
62
68
|
globalAgentsBackup?: string | null;
|
|
63
69
|
globalAgentsOverrideBackup?: string | null;
|
|
64
70
|
rulesBackup?: string | null;
|
|
@@ -82,6 +88,8 @@ export interface ToolPaths {
|
|
|
82
88
|
skillsDir?: string;
|
|
83
89
|
mcpConfig?: string;
|
|
84
90
|
agentsDir?: string;
|
|
91
|
+
pluginsDir?: string;
|
|
92
|
+
pluginMarketplacePath?: string;
|
|
85
93
|
automationDir?: string;
|
|
86
94
|
toolHome?: string;
|
|
87
95
|
rulesDir?: string;
|
|
@@ -117,6 +125,44 @@ function homePath(home: string, ...parts: string[]): string {
|
|
|
117
125
|
return join(home, ...parts);
|
|
118
126
|
}
|
|
119
127
|
|
|
128
|
+
function codexLiveRoot(home: string, rootDir?: string): string {
|
|
129
|
+
const projectRoot = rootDir ? projectRootFromAiRoot(rootDir, home) : null;
|
|
130
|
+
return projectRoot ?? home;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function codexPluginsDir(home: string, rootDir?: string): string {
|
|
134
|
+
return join(codexLiveRoot(home, rootDir), "plugins");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function codexSkillsDir(home: string, rootDir?: string): string {
|
|
138
|
+
return join(codexLiveRoot(home, rootDir), ".agents", "skills");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function codexPluginMarketplacePath(home: string, rootDir?: string): string {
|
|
142
|
+
return join(
|
|
143
|
+
codexLiveRoot(home, rootDir),
|
|
144
|
+
".agents",
|
|
145
|
+
"plugins",
|
|
146
|
+
"marketplace.json"
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function codexLegacySkillsDir(home: string, rootDir?: string): string {
|
|
151
|
+
return join(codexLiveRoot(home, rootDir), ".codex", "skills");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function codexLegacyPluginsDir(home: string, rootDir?: string): string {
|
|
155
|
+
return join(codexLiveRoot(home, rootDir), ".codex", "plugins");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function codexCanonicalPluginsRoot(rootDir: string): string {
|
|
159
|
+
return join(rootDir, "tools", "codex", "plugins");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function codexCanonicalPluginMarketplacePath(rootDir: string): string {
|
|
163
|
+
return join(codexCanonicalPluginsRoot(rootDir), "marketplace.json");
|
|
164
|
+
}
|
|
165
|
+
|
|
120
166
|
function expandHomePath(pathValue: string, home: string): string {
|
|
121
167
|
if (pathValue === "~") {
|
|
122
168
|
return home;
|
|
@@ -152,6 +198,79 @@ function renderedHash(text: string): string {
|
|
|
152
198
|
return contentHash(normalizeText(text));
|
|
153
199
|
}
|
|
154
200
|
|
|
201
|
+
type ManagedTargetContent = string | Uint8Array;
|
|
202
|
+
|
|
203
|
+
function byteHash(content: Uint8Array): string {
|
|
204
|
+
return createHash("sha256").update(content).digest("hex");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function targetContentHash(
|
|
208
|
+
content: ManagedTargetContent,
|
|
209
|
+
options?: { normalizeText?: boolean }
|
|
210
|
+
): string {
|
|
211
|
+
if (typeof content === "string") {
|
|
212
|
+
return options?.normalizeText === false
|
|
213
|
+
? byteHash(Buffer.from(content))
|
|
214
|
+
: renderedHash(content);
|
|
215
|
+
}
|
|
216
|
+
return byteHash(content);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function readTargetHash(
|
|
220
|
+
pathValue: string,
|
|
221
|
+
options?: { normalizeText?: boolean }
|
|
222
|
+
): Promise<string | null> {
|
|
223
|
+
if (!(await fileExists(pathValue))) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
if (options?.normalizeText === false) {
|
|
227
|
+
return byteHash(await Bun.file(pathValue).bytes());
|
|
228
|
+
}
|
|
229
|
+
return renderedHash(await Bun.file(pathValue).text());
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function normalizeCodexMarketplaceText(text: string): string {
|
|
233
|
+
try {
|
|
234
|
+
const parsed = JSON.parse(text) as unknown;
|
|
235
|
+
if (!isPlainObject(parsed)) {
|
|
236
|
+
return text.endsWith("\n") ? text : `${text}\n`;
|
|
237
|
+
}
|
|
238
|
+
const plugins = Array.isArray(parsed.plugins) ? parsed.plugins : null;
|
|
239
|
+
if (plugins) {
|
|
240
|
+
parsed.plugins = plugins.map((entry) => {
|
|
241
|
+
if (!isPlainObject(entry)) {
|
|
242
|
+
return entry;
|
|
243
|
+
}
|
|
244
|
+
const source = isPlainObject(entry.source) ? { ...entry.source } : null;
|
|
245
|
+
if (
|
|
246
|
+
source?.source === "local" &&
|
|
247
|
+
typeof source.path === "string" &&
|
|
248
|
+
source.path.startsWith("./.codex/plugins/")
|
|
249
|
+
) {
|
|
250
|
+
source.path = source.path.replace("./.codex/plugins/", "./plugins/");
|
|
251
|
+
}
|
|
252
|
+
return source ? { ...entry, source } : entry;
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return `${JSON.stringify(parsed, null, 2)}\n`;
|
|
256
|
+
} catch {
|
|
257
|
+
return text.endsWith("\n") ? text : `${text}\n`;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function isSafeCodexPluginName(name: string): boolean {
|
|
262
|
+
const trimmed = name.trim();
|
|
263
|
+
return (
|
|
264
|
+
trimmed.length > 0 &&
|
|
265
|
+
trimmed !== "." &&
|
|
266
|
+
trimmed !== ".." &&
|
|
267
|
+
!trimmed.includes("..") &&
|
|
268
|
+
!trimmed.includes("/") &&
|
|
269
|
+
!trimmed.includes("\\") &&
|
|
270
|
+
basename(trimmed) === trimmed
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
155
274
|
function defaultToolPaths(
|
|
156
275
|
home: string,
|
|
157
276
|
rootDir?: string
|
|
@@ -168,9 +287,13 @@ function defaultToolPaths(
|
|
|
168
287
|
},
|
|
169
288
|
codex: {
|
|
170
289
|
tool: "codex",
|
|
171
|
-
skillsDir:
|
|
290
|
+
skillsDir: codexSkillsDir(home, rootDir),
|
|
172
291
|
mcpConfig: toolBase(".codex", "mcp.json"),
|
|
173
292
|
agentsDir: toolBase(".codex", "agents"),
|
|
293
|
+
pluginsDir: projectRoot ? undefined : codexPluginsDir(home, rootDir),
|
|
294
|
+
pluginMarketplacePath: projectRoot
|
|
295
|
+
? undefined
|
|
296
|
+
: codexPluginMarketplacePath(home, rootDir),
|
|
174
297
|
automationDir: homePath(home, ".codex", "automations"),
|
|
175
298
|
toolHome: toolBase(".codex"),
|
|
176
299
|
rulesDir: toolBase(".codex", "rules"),
|
|
@@ -270,6 +393,7 @@ async function resolveToolPaths(
|
|
|
270
393
|
override?: Record<string, ToolPaths>
|
|
271
394
|
): Promise<ToolPaths | null> {
|
|
272
395
|
const defaults = defaultToolPaths(home, rootDir);
|
|
396
|
+
const projectRoot = rootDir ? projectRootFromAiRoot(rootDir, home) : null;
|
|
273
397
|
if (override?.[tool]) {
|
|
274
398
|
const base = defaults[tool] ?? null;
|
|
275
399
|
return base ? { ...base, ...override[tool] } : (override[tool] ?? null);
|
|
@@ -286,6 +410,10 @@ async function resolveToolPaths(
|
|
|
286
410
|
return base;
|
|
287
411
|
}
|
|
288
412
|
|
|
413
|
+
if (projectRoot) {
|
|
414
|
+
return base;
|
|
415
|
+
}
|
|
416
|
+
|
|
289
417
|
const adapterPaths = getAdapter("codex")?.getDefaultPaths?.();
|
|
290
418
|
const adapterConfig = adapterPaths?.config
|
|
291
419
|
? expandHomePath(adapterPaths.config, home)
|
|
@@ -635,6 +763,96 @@ async function canonicalAutomationsExist(rootDir: string): Promise<boolean> {
|
|
|
635
763
|
}
|
|
636
764
|
}
|
|
637
765
|
|
|
766
|
+
interface CanonicalPluginEntry {
|
|
767
|
+
name: string;
|
|
768
|
+
sourceDir: string;
|
|
769
|
+
files: Map<string, Uint8Array>;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
async function listRelativeFilesWithDotfiles(root: string): Promise<string[]> {
|
|
773
|
+
const out: string[] = [];
|
|
774
|
+
|
|
775
|
+
async function visit(currentDir: string, prefix = ""): Promise<void> {
|
|
776
|
+
const entries = await readdir(currentDir, { withFileTypes: true }).catch(
|
|
777
|
+
() => [] as import("node:fs").Dirent[]
|
|
778
|
+
);
|
|
779
|
+
for (const entry of entries) {
|
|
780
|
+
const relPath = prefix ? join(prefix, entry.name) : entry.name;
|
|
781
|
+
const fullPath = join(currentDir, entry.name);
|
|
782
|
+
if (entry.isDirectory()) {
|
|
783
|
+
await visit(fullPath, relPath);
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
if (entry.isFile()) {
|
|
787
|
+
out.push(relPath);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
await visit(root);
|
|
793
|
+
return out.sort();
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
async function loadCanonicalCodexPlugins(
|
|
797
|
+
rootDir: string
|
|
798
|
+
): Promise<CanonicalPluginEntry[]> {
|
|
799
|
+
const pluginsRoot = codexCanonicalPluginsRoot(rootDir);
|
|
800
|
+
const entries = await readdir(pluginsRoot, { withFileTypes: true }).catch(
|
|
801
|
+
() => [] as import("node:fs").Dirent[]
|
|
802
|
+
);
|
|
803
|
+
const out: CanonicalPluginEntry[] = [];
|
|
804
|
+
|
|
805
|
+
for (const entry of entries) {
|
|
806
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
const sourceDir = join(pluginsRoot, entry.name);
|
|
810
|
+
if (!(await fileExists(join(sourceDir, ".codex-plugin", "plugin.json")))) {
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
const files = new Map<string, Uint8Array>();
|
|
814
|
+
for (const relPath of await listRelativeFilesWithDotfiles(sourceDir)) {
|
|
815
|
+
files.set(relPath, await Bun.file(join(sourceDir, relPath)).bytes());
|
|
816
|
+
}
|
|
817
|
+
out.push({ name: entry.name, sourceDir, files });
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
async function canonicalCodexPluginsExist(rootDir: string): Promise<boolean> {
|
|
824
|
+
if (await fileExists(codexCanonicalPluginMarketplacePath(rootDir))) {
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
return (await loadCanonicalCodexPlugins(rootDir)).length > 0;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
async function loadCanonicalCodexMarketplaceText(
|
|
831
|
+
rootDir: string
|
|
832
|
+
): Promise<{ text: string | null; sourcePath: string }> {
|
|
833
|
+
const sourcePath = codexCanonicalPluginMarketplacePath(rootDir);
|
|
834
|
+
const raw = await readTextOrNull(sourcePath);
|
|
835
|
+
return {
|
|
836
|
+
text: raw == null ? null : normalizeCodexMarketplaceText(raw),
|
|
837
|
+
sourcePath,
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
async function hashDirectoryTree(root: string): Promise<string | null> {
|
|
842
|
+
if (!(await fileExists(root))) {
|
|
843
|
+
return null;
|
|
844
|
+
}
|
|
845
|
+
const files = await listRelativeFilesWithDotfiles(root);
|
|
846
|
+
const hash = createHash("sha256");
|
|
847
|
+
for (const relPath of files) {
|
|
848
|
+
hash.update(relPath);
|
|
849
|
+
hash.update("\0");
|
|
850
|
+
hash.update(await Bun.file(join(root, relPath)).bytes());
|
|
851
|
+
hash.update("\0");
|
|
852
|
+
}
|
|
853
|
+
return hash.digest("hex");
|
|
854
|
+
}
|
|
855
|
+
|
|
638
856
|
async function loadMergedIndex(
|
|
639
857
|
homeDir: string,
|
|
640
858
|
rootDir: string
|
|
@@ -656,7 +874,11 @@ async function loadEnabledSkillEntries(args: {
|
|
|
656
874
|
tool: string;
|
|
657
875
|
}): Promise<{ name: string; path: string }[]> {
|
|
658
876
|
const index = await loadMergedIndex(args.homeDir, args.rootDir);
|
|
659
|
-
const
|
|
877
|
+
const projectPolicy = await loadProjectToolSyncPolicy(args);
|
|
878
|
+
const useBuiltinDefaults = await builtinSyncDefaultsEnabled(
|
|
879
|
+
args.rootDir,
|
|
880
|
+
args.homeDir
|
|
881
|
+
);
|
|
660
882
|
const out: { name: string; path: string }[] = [];
|
|
661
883
|
|
|
662
884
|
for (const [name, entry] of Object.entries(index.skills)) {
|
|
@@ -674,6 +896,15 @@ async function loadEnabledSkillEntries(args: {
|
|
|
674
896
|
) {
|
|
675
897
|
continue;
|
|
676
898
|
}
|
|
899
|
+
if (
|
|
900
|
+
projectPolicy &&
|
|
901
|
+
!(
|
|
902
|
+
projectPolicy.skills.includes("*") ||
|
|
903
|
+
projectPolicy.skills.includes(name)
|
|
904
|
+
)
|
|
905
|
+
) {
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
677
908
|
out.push({ name, path: skill.path });
|
|
678
909
|
}
|
|
679
910
|
|
|
@@ -681,6 +912,10 @@ async function loadEnabledSkillEntries(args: {
|
|
|
681
912
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
682
913
|
}
|
|
683
914
|
|
|
915
|
+
if (projectPolicy) {
|
|
916
|
+
return [];
|
|
917
|
+
}
|
|
918
|
+
|
|
684
919
|
return (await listSkillDirs(join(args.rootDir, "skills"))).map((name) => ({
|
|
685
920
|
name,
|
|
686
921
|
path: join(args.rootDir, "skills", name),
|
|
@@ -690,9 +925,14 @@ async function loadEnabledSkillEntries(args: {
|
|
|
690
925
|
async function loadManagedAgentEntries(args: {
|
|
691
926
|
homeDir: string;
|
|
692
927
|
rootDir: string;
|
|
928
|
+
tool: string;
|
|
693
929
|
}): Promise<{ name: string; sourcePath: string; raw: string }[]> {
|
|
694
930
|
const index = await loadMergedIndex(args.homeDir, args.rootDir);
|
|
695
|
-
const
|
|
931
|
+
const projectPolicy = await loadProjectToolSyncPolicy(args);
|
|
932
|
+
const useBuiltinDefaults = await builtinSyncDefaultsEnabled(
|
|
933
|
+
args.rootDir,
|
|
934
|
+
args.homeDir
|
|
935
|
+
);
|
|
696
936
|
const out: { name: string; sourcePath: string; raw: string }[] = [];
|
|
697
937
|
|
|
698
938
|
for (const [name, entry] of Object.entries(index.agents)) {
|
|
@@ -704,6 +944,15 @@ async function loadManagedAgentEntries(args: {
|
|
|
704
944
|
) {
|
|
705
945
|
continue;
|
|
706
946
|
}
|
|
947
|
+
if (
|
|
948
|
+
projectPolicy &&
|
|
949
|
+
!(
|
|
950
|
+
projectPolicy.agents.includes("*") ||
|
|
951
|
+
projectPolicy.agents.includes(name)
|
|
952
|
+
)
|
|
953
|
+
) {
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
707
956
|
const raw = await readTextIfExists(agent.path);
|
|
708
957
|
if (raw == null) {
|
|
709
958
|
continue;
|
|
@@ -715,6 +964,10 @@ async function loadManagedAgentEntries(args: {
|
|
|
715
964
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
716
965
|
}
|
|
717
966
|
|
|
967
|
+
if (projectPolicy) {
|
|
968
|
+
return [];
|
|
969
|
+
}
|
|
970
|
+
|
|
718
971
|
return await loadCanonicalAgents(args.rootDir);
|
|
719
972
|
}
|
|
720
973
|
|
|
@@ -734,7 +987,7 @@ async function planAgentFileChanges({
|
|
|
734
987
|
contents: Map<string, string>;
|
|
735
988
|
sources: Map<string, string>;
|
|
736
989
|
}> {
|
|
737
|
-
const agents = await loadManagedAgentEntries({ homeDir, rootDir });
|
|
990
|
+
const agents = await loadManagedAgentEntries({ homeDir, rootDir, tool });
|
|
738
991
|
const contents = new Map<string, string>();
|
|
739
992
|
const sources = new Map<string, string>();
|
|
740
993
|
const desiredPaths = new Set<string>();
|
|
@@ -896,6 +1149,10 @@ async function listSkillDirs(skillsRoot: string): Promise<string[]> {
|
|
|
896
1149
|
}
|
|
897
1150
|
}
|
|
898
1151
|
|
|
1152
|
+
async function canonicalSkillsExist(rootDir: string): Promise<boolean> {
|
|
1153
|
+
return (await listSkillDirs(join(rootDir, "skills"))).length > 0;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
899
1156
|
async function loadEnabledSkillNames({
|
|
900
1157
|
homeDir,
|
|
901
1158
|
rootDir,
|
|
@@ -937,18 +1194,34 @@ function canonicalServerToToolConfig(server: unknown): unknown {
|
|
|
937
1194
|
return out;
|
|
938
1195
|
}
|
|
939
1196
|
|
|
940
|
-
function filterServersForTool(
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1197
|
+
async function filterServersForTool(args: {
|
|
1198
|
+
homeDir: string;
|
|
1199
|
+
rootDir: string;
|
|
1200
|
+
servers: Record<string, unknown>;
|
|
1201
|
+
tool: string;
|
|
1202
|
+
}): Promise<Record<string, unknown>> {
|
|
1203
|
+
const projectPolicy = await loadProjectToolSyncPolicy({
|
|
1204
|
+
homeDir: args.homeDir,
|
|
1205
|
+
rootDir: args.rootDir,
|
|
1206
|
+
tool: args.tool,
|
|
1207
|
+
});
|
|
944
1208
|
const out: Record<string, unknown> = {};
|
|
945
|
-
for (const [name, cfg] of Object.entries(servers)) {
|
|
1209
|
+
for (const [name, cfg] of Object.entries(args.servers)) {
|
|
946
1210
|
if (isPlainObject(cfg)) {
|
|
947
1211
|
const enabledFor = cfg.enabledFor;
|
|
948
|
-
if (Array.isArray(enabledFor) && !enabledFor.includes(tool)) {
|
|
1212
|
+
if (Array.isArray(enabledFor) && !enabledFor.includes(args.tool)) {
|
|
949
1213
|
continue;
|
|
950
1214
|
}
|
|
951
1215
|
}
|
|
1216
|
+
if (
|
|
1217
|
+
projectPolicy &&
|
|
1218
|
+
!(
|
|
1219
|
+
projectPolicy.mcpServers.includes("*") ||
|
|
1220
|
+
projectPolicy.mcpServers.includes(name)
|
|
1221
|
+
)
|
|
1222
|
+
) {
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
952
1225
|
out[name] = canonicalServerToToolConfig(cfg);
|
|
953
1226
|
}
|
|
954
1227
|
return out;
|
|
@@ -1178,6 +1451,8 @@ interface ExistingManagedItem {
|
|
|
1178
1451
|
| "skill"
|
|
1179
1452
|
| "agent"
|
|
1180
1453
|
| "automation"
|
|
1454
|
+
| "plugin"
|
|
1455
|
+
| "plugin-marketplace"
|
|
1181
1456
|
| "global-doc"
|
|
1182
1457
|
| "rule"
|
|
1183
1458
|
| "tool-config"
|
|
@@ -2008,10 +2283,12 @@ async function syncSkillSymlinks({
|
|
|
2008
2283
|
}
|
|
2009
2284
|
|
|
2010
2285
|
async function planMcpWrite({
|
|
2286
|
+
homeDir,
|
|
2011
2287
|
mcpConfigPath,
|
|
2012
2288
|
rootDir,
|
|
2013
2289
|
tool,
|
|
2014
2290
|
}: {
|
|
2291
|
+
homeDir: string;
|
|
2015
2292
|
mcpConfigPath: string;
|
|
2016
2293
|
rootDir: string;
|
|
2017
2294
|
tool: string;
|
|
@@ -2019,7 +2296,12 @@ async function planMcpWrite({
|
|
|
2019
2296
|
const { servers } = await loadCanonicalMcpState(rootDir, {
|
|
2020
2297
|
includeLocal: true,
|
|
2021
2298
|
});
|
|
2022
|
-
const filtered = filterServersForTool(
|
|
2299
|
+
const filtered = await filterServersForTool({
|
|
2300
|
+
homeDir,
|
|
2301
|
+
rootDir,
|
|
2302
|
+
servers,
|
|
2303
|
+
tool,
|
|
2304
|
+
});
|
|
2023
2305
|
const contents = `${JSON.stringify({ mcpServers: filtered }, null, 2)}\n`;
|
|
2024
2306
|
|
|
2025
2307
|
if (!(await fileExists(mcpConfigPath))) {
|
|
@@ -2034,17 +2316,19 @@ async function planMcpWrite({
|
|
|
2034
2316
|
}
|
|
2035
2317
|
|
|
2036
2318
|
async function syncMcpConfig({
|
|
2319
|
+
homeDir,
|
|
2037
2320
|
mcpConfigPath,
|
|
2038
2321
|
rootDir,
|
|
2039
2322
|
tool,
|
|
2040
2323
|
dryRun,
|
|
2041
2324
|
}: {
|
|
2325
|
+
homeDir: string;
|
|
2042
2326
|
mcpConfigPath: string;
|
|
2043
2327
|
rootDir: string;
|
|
2044
2328
|
tool: string;
|
|
2045
2329
|
dryRun?: boolean;
|
|
2046
2330
|
}): Promise<{ needsWrite: boolean }> {
|
|
2047
|
-
const plan = await planMcpWrite({ mcpConfigPath, rootDir, tool });
|
|
2331
|
+
const plan = await planMcpWrite({ homeDir, mcpConfigPath, rootDir, tool });
|
|
2048
2332
|
if (dryRun) {
|
|
2049
2333
|
return { needsWrite: plan.needsWrite };
|
|
2050
2334
|
}
|
|
@@ -2056,10 +2340,12 @@ async function syncMcpConfig({
|
|
|
2056
2340
|
}
|
|
2057
2341
|
|
|
2058
2342
|
async function writeToolMcpConfig({
|
|
2343
|
+
homeDir,
|
|
2059
2344
|
mcpConfigPath,
|
|
2060
2345
|
rootDir,
|
|
2061
2346
|
tool,
|
|
2062
2347
|
}: {
|
|
2348
|
+
homeDir: string;
|
|
2063
2349
|
mcpConfigPath: string;
|
|
2064
2350
|
rootDir: string;
|
|
2065
2351
|
tool: string;
|
|
@@ -2067,7 +2353,12 @@ async function writeToolMcpConfig({
|
|
|
2067
2353
|
const { servers } = await loadCanonicalMcpState(rootDir, {
|
|
2068
2354
|
includeLocal: true,
|
|
2069
2355
|
});
|
|
2070
|
-
const filtered = filterServersForTool(
|
|
2356
|
+
const filtered = await filterServersForTool({
|
|
2357
|
+
homeDir,
|
|
2358
|
+
rootDir,
|
|
2359
|
+
servers,
|
|
2360
|
+
tool,
|
|
2361
|
+
});
|
|
2071
2362
|
await ensureDir(dirname(mcpConfigPath));
|
|
2072
2363
|
await Bun.write(
|
|
2073
2364
|
mcpConfigPath,
|
|
@@ -2089,19 +2380,34 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2089
2380
|
throw new Error(`Unknown tool: ${tool}`);
|
|
2090
2381
|
}
|
|
2091
2382
|
|
|
2092
|
-
const existingSkillPlan =
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2383
|
+
const existingSkillPlan =
|
|
2384
|
+
toolPaths.skillsDir || tool === "codex"
|
|
2385
|
+
? mergeManagedImportPlans(
|
|
2386
|
+
asManagedSkillPlan(
|
|
2387
|
+
toolPaths.skillsDir
|
|
2388
|
+
? await planExistingToolSkillAdoption({
|
|
2389
|
+
rootDir,
|
|
2390
|
+
toolSkillsDir: toolPaths.skillsDir,
|
|
2391
|
+
})
|
|
2392
|
+
: {
|
|
2393
|
+
adopt: [],
|
|
2394
|
+
identical: [],
|
|
2395
|
+
conflicts: [],
|
|
2396
|
+
ignored: [],
|
|
2397
|
+
}
|
|
2398
|
+
),
|
|
2399
|
+
tool === "codex"
|
|
2400
|
+
? asManagedSkillPlan(
|
|
2401
|
+
await planExistingToolSkillAdoption({
|
|
2402
|
+
rootDir,
|
|
2403
|
+
toolSkillsDir: codexLegacySkillsDir(home, rootDir),
|
|
2404
|
+
})
|
|
2405
|
+
)
|
|
2406
|
+
: emptyManagedImportPlan()
|
|
2407
|
+
)
|
|
2408
|
+
: emptyManagedImportPlan();
|
|
2103
2409
|
const existingImportPlan = mergeManagedImportPlans(
|
|
2104
|
-
|
|
2410
|
+
existingSkillPlan,
|
|
2105
2411
|
toolPaths.agentsDir
|
|
2106
2412
|
? await planExistingToolAgentAdoption({
|
|
2107
2413
|
tool,
|
|
@@ -2136,6 +2442,15 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2136
2442
|
toolConfigPath: toolPaths.toolConfig,
|
|
2137
2443
|
})
|
|
2138
2444
|
: emptyManagedImportPlan(),
|
|
2445
|
+
tool === "codex" &&
|
|
2446
|
+
(toolPaths.pluginsDir || toolPaths.pluginMarketplacePath)
|
|
2447
|
+
? await planExistingCodexPluginAdoption({
|
|
2448
|
+
homeDir: home,
|
|
2449
|
+
rootDir,
|
|
2450
|
+
pluginsDir: toolPaths.pluginsDir,
|
|
2451
|
+
pluginMarketplacePath: toolPaths.pluginMarketplacePath,
|
|
2452
|
+
})
|
|
2453
|
+
: emptyManagedImportPlan(),
|
|
2139
2454
|
toolPaths.mcpConfig
|
|
2140
2455
|
? await planExistingMcpAdoption({
|
|
2141
2456
|
rootDir,
|
|
@@ -2157,6 +2472,8 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2157
2472
|
toolPaths.toolHome ||
|
|
2158
2473
|
toolPaths.rulesDir ||
|
|
2159
2474
|
toolPaths.toolConfig ||
|
|
2475
|
+
toolPaths.pluginsDir ||
|
|
2476
|
+
toolPaths.pluginMarketplacePath ||
|
|
2160
2477
|
toolPaths.mcpConfig) &&
|
|
2161
2478
|
!opts.adoptExisting &&
|
|
2162
2479
|
(existingImportPlan.adopt.length > 0 ||
|
|
@@ -2203,7 +2520,10 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2203
2520
|
? await adoptSkillsIntoCanonicalStore({
|
|
2204
2521
|
homeDir: home,
|
|
2205
2522
|
rootDir,
|
|
2206
|
-
skillSourceDirs: [
|
|
2523
|
+
skillSourceDirs: [
|
|
2524
|
+
toolPaths.skillsDir,
|
|
2525
|
+
...(tool === "codex" ? [codexLegacySkillsDir(home, rootDir)] : []),
|
|
2526
|
+
],
|
|
2207
2527
|
})
|
|
2208
2528
|
: [];
|
|
2209
2529
|
|
|
@@ -2226,6 +2546,26 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2226
2546
|
});
|
|
2227
2547
|
}
|
|
2228
2548
|
}
|
|
2549
|
+
if (tool === "codex" && opts.adoptExisting) {
|
|
2550
|
+
const legacySkillsDir = codexLegacySkillsDir(home, rootDir);
|
|
2551
|
+
const result = await adoptExistingToolSkills({
|
|
2552
|
+
rootDir,
|
|
2553
|
+
toolSkillsDir: legacySkillsDir,
|
|
2554
|
+
conflictMode: importConflictMode,
|
|
2555
|
+
});
|
|
2556
|
+
for (const name of result.adopted) {
|
|
2557
|
+
if (!adoptedSkills.includes(name)) {
|
|
2558
|
+
adoptedSkills.push(name);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
if (result.adopted.length > 0) {
|
|
2562
|
+
await buildIndex({
|
|
2563
|
+
homeDir: home,
|
|
2564
|
+
rootDir,
|
|
2565
|
+
force: false,
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2229
2569
|
if (toolPaths.agentsDir && opts.adoptExisting) {
|
|
2230
2570
|
const result = await adoptExistingToolAgents({
|
|
2231
2571
|
tool,
|
|
@@ -2279,6 +2619,20 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2279
2619
|
});
|
|
2280
2620
|
adoptedSkills.push(...result.map((item) => `${item.kind}:${item.name}`));
|
|
2281
2621
|
}
|
|
2622
|
+
if (
|
|
2623
|
+
tool === "codex" &&
|
|
2624
|
+
opts.adoptExisting &&
|
|
2625
|
+
(toolPaths.pluginsDir || toolPaths.pluginMarketplacePath)
|
|
2626
|
+
) {
|
|
2627
|
+
const result = await adoptExistingCodexPlugins({
|
|
2628
|
+
homeDir: home,
|
|
2629
|
+
rootDir,
|
|
2630
|
+
pluginsDir: toolPaths.pluginsDir,
|
|
2631
|
+
pluginMarketplacePath: toolPaths.pluginMarketplacePath,
|
|
2632
|
+
conflictMode: importConflictMode,
|
|
2633
|
+
});
|
|
2634
|
+
adoptedSkills.push(...result.map((item) => `${item.kind}:${item.name}`));
|
|
2635
|
+
}
|
|
2282
2636
|
if (adoptedSkills.length > 0) {
|
|
2283
2637
|
await buildIndex({
|
|
2284
2638
|
homeDir: home,
|
|
@@ -2327,6 +2681,14 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2327
2681
|
toolConfigPath: toolPaths.toolConfig,
|
|
2328
2682
|
})
|
|
2329
2683
|
: null;
|
|
2684
|
+
const pluginPreview =
|
|
2685
|
+
tool === "codex" && toolPaths.pluginsDir && toolPaths.pluginMarketplacePath
|
|
2686
|
+
? await planCodexPluginFileChanges({
|
|
2687
|
+
rootDir,
|
|
2688
|
+
pluginsDir: toolPaths.pluginsDir,
|
|
2689
|
+
pluginMarketplacePath: toolPaths.pluginMarketplacePath,
|
|
2690
|
+
})
|
|
2691
|
+
: null;
|
|
2330
2692
|
|
|
2331
2693
|
const skillsBackup = toolPaths.skillsDir
|
|
2332
2694
|
? await backupPath(toolPaths.skillsDir, opts.now)
|
|
@@ -2357,6 +2719,15 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2357
2719
|
toolPaths.toolConfig && toolConfigPreview?.managedConfig
|
|
2358
2720
|
? await backupPath(toolPaths.toolConfig, opts.now)
|
|
2359
2721
|
: null;
|
|
2722
|
+
const pluginsBackup =
|
|
2723
|
+
toolPaths.pluginsDir && pluginPreview?.contents.size
|
|
2724
|
+
? await backupPath(toolPaths.pluginsDir, opts.now)
|
|
2725
|
+
: null;
|
|
2726
|
+
const pluginMarketplaceBackup =
|
|
2727
|
+
toolPaths.pluginMarketplacePath &&
|
|
2728
|
+
pluginPreview?.contents.has(toolPaths.pluginMarketplacePath)
|
|
2729
|
+
? await backupPath(toolPaths.pluginMarketplacePath, opts.now)
|
|
2730
|
+
: null;
|
|
2360
2731
|
|
|
2361
2732
|
if (toolPaths.skillsDir) {
|
|
2362
2733
|
await ensureEmptyDir(toolPaths.skillsDir);
|
|
@@ -2378,6 +2749,7 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2378
2749
|
|
|
2379
2750
|
if (toolPaths.mcpConfig) {
|
|
2380
2751
|
await writeToolMcpConfig({
|
|
2752
|
+
homeDir: home,
|
|
2381
2753
|
mcpConfigPath: toolPaths.mcpConfig,
|
|
2382
2754
|
rootDir,
|
|
2383
2755
|
tool,
|
|
@@ -2433,12 +2805,41 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2433
2805
|
});
|
|
2434
2806
|
}
|
|
2435
2807
|
|
|
2808
|
+
if (
|
|
2809
|
+
pluginPreview &&
|
|
2810
|
+
toolPaths.pluginsDir &&
|
|
2811
|
+
(pluginPreview.contents.size > 0 || pluginPreview.remove.length > 0)
|
|
2812
|
+
) {
|
|
2813
|
+
await ensureDir(toolPaths.pluginsDir);
|
|
2814
|
+
if (
|
|
2815
|
+
toolPaths.pluginMarketplacePath &&
|
|
2816
|
+
pluginPreview.contents.has(toolPaths.pluginMarketplacePath)
|
|
2817
|
+
) {
|
|
2818
|
+
await ensureDir(dirname(toolPaths.pluginMarketplacePath));
|
|
2819
|
+
}
|
|
2820
|
+
await applyRenderedRemoves(pluginPreview.remove);
|
|
2821
|
+
await applyRenderedWrites({
|
|
2822
|
+
contents: pluginPreview.contents,
|
|
2823
|
+
targets: Array.from(pluginPreview.contents.keys()),
|
|
2824
|
+
});
|
|
2825
|
+
await pruneEmptyParents(pluginPreview.remove, toolPaths.pluginsDir);
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2436
2828
|
state.tools[tool] = {
|
|
2437
2829
|
tool,
|
|
2438
2830
|
managedAt: nowIso(opts.now),
|
|
2439
2831
|
skillsDir: toolPaths.skillsDir,
|
|
2440
2832
|
mcpConfig: toolPaths.mcpConfig,
|
|
2441
2833
|
agentsDir: toolPaths.agentsDir,
|
|
2834
|
+
pluginsDir:
|
|
2835
|
+
pluginPreview?.contents.size && toolPaths.pluginsDir
|
|
2836
|
+
? toolPaths.pluginsDir
|
|
2837
|
+
: undefined,
|
|
2838
|
+
pluginMarketplacePath:
|
|
2839
|
+
toolPaths.pluginMarketplacePath &&
|
|
2840
|
+
pluginPreview?.contents.has(toolPaths.pluginMarketplacePath)
|
|
2841
|
+
? toolPaths.pluginMarketplacePath
|
|
2842
|
+
: undefined,
|
|
2442
2843
|
automationDir: toolPaths.automationDir,
|
|
2443
2844
|
toolHome: globalDocsPreview?.managedTargets.length
|
|
2444
2845
|
? toolPaths.toolHome
|
|
@@ -2460,6 +2861,8 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2460
2861
|
skillsBackup,
|
|
2461
2862
|
mcpBackup,
|
|
2462
2863
|
agentsBackup,
|
|
2864
|
+
pluginsBackup,
|
|
2865
|
+
pluginMarketplaceBackup,
|
|
2463
2866
|
globalAgentsBackup,
|
|
2464
2867
|
globalAgentsOverrideBackup,
|
|
2465
2868
|
rulesBackup,
|
|
@@ -2525,6 +2928,17 @@ export async function manageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2525
2928
|
});
|
|
2526
2929
|
}
|
|
2527
2930
|
|
|
2931
|
+
if (pluginPreview) {
|
|
2932
|
+
updateRenderedTargetState({
|
|
2933
|
+
entry: managedEntry,
|
|
2934
|
+
writtenTargets: Array.from(pluginPreview.contents.keys()),
|
|
2935
|
+
removedTargets: pluginPreview.remove,
|
|
2936
|
+
contents: pluginPreview.contents,
|
|
2937
|
+
sources: pluginPreview.sources,
|
|
2938
|
+
normalizeText: false,
|
|
2939
|
+
});
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2528
2942
|
await saveManagedState(state, home, rootDir);
|
|
2529
2943
|
|
|
2530
2944
|
for (const name of adoptedSkills) {
|
|
@@ -2601,6 +3015,20 @@ export async function unmanageTool(tool: string, opts: ManageOptions = {}) {
|
|
|
2601
3015
|
});
|
|
2602
3016
|
}
|
|
2603
3017
|
|
|
3018
|
+
if (entry.pluginsDir) {
|
|
3019
|
+
await restoreBackup({
|
|
3020
|
+
original: entry.pluginsDir,
|
|
3021
|
+
backup: entry.pluginsBackup ?? null,
|
|
3022
|
+
});
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
if (entry.pluginMarketplacePath) {
|
|
3026
|
+
await restoreBackup({
|
|
3027
|
+
original: entry.pluginMarketplacePath,
|
|
3028
|
+
backup: entry.pluginMarketplaceBackup ?? null,
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
|
|
2604
3032
|
if (entry.automationDir) {
|
|
2605
3033
|
const automationTargets = Object.keys(entry.renderedTargets ?? {}).filter(
|
|
2606
3034
|
(targetPath) => targetPath.startsWith(join(entry.automationDir!, ""))
|
|
@@ -2681,6 +3109,19 @@ async function repairManagedToolEntry(args: {
|
|
|
2681
3109
|
const next: ManagedToolState = { ...args.entry };
|
|
2682
3110
|
let changed = false;
|
|
2683
3111
|
|
|
3112
|
+
if (
|
|
3113
|
+
tool === "codex" &&
|
|
3114
|
+
toolPaths.skillsDir &&
|
|
3115
|
+
(await canonicalSkillsExist(rootDir)) &&
|
|
3116
|
+
next.skillsDir !== toolPaths.skillsDir
|
|
3117
|
+
) {
|
|
3118
|
+
if (!next.skillsBackup) {
|
|
3119
|
+
next.skillsBackup = await backupPath(toolPaths.skillsDir);
|
|
3120
|
+
}
|
|
3121
|
+
next.skillsDir = toolPaths.skillsDir;
|
|
3122
|
+
changed = true;
|
|
3123
|
+
}
|
|
3124
|
+
|
|
2684
3125
|
if (
|
|
2685
3126
|
!next.agentsDir &&
|
|
2686
3127
|
toolPaths.agentsDir &&
|
|
@@ -2700,6 +3141,43 @@ async function repairManagedToolEntry(args: {
|
|
|
2700
3141
|
changed = true;
|
|
2701
3142
|
}
|
|
2702
3143
|
|
|
3144
|
+
if (
|
|
3145
|
+
tool === "codex" &&
|
|
3146
|
+
!(next.pluginsDir && next.pluginMarketplacePath) &&
|
|
3147
|
+
toolPaths.pluginsDir &&
|
|
3148
|
+
toolPaths.pluginMarketplacePath &&
|
|
3149
|
+
(await canonicalCodexPluginsExist(rootDir))
|
|
3150
|
+
) {
|
|
3151
|
+
if (!next.pluginsDir) {
|
|
3152
|
+
next.pluginsBackup = await backupPath(toolPaths.pluginsDir);
|
|
3153
|
+
next.pluginsDir = toolPaths.pluginsDir;
|
|
3154
|
+
changed = true;
|
|
3155
|
+
}
|
|
3156
|
+
if (!next.pluginMarketplacePath) {
|
|
3157
|
+
next.pluginMarketplaceBackup = await backupPath(
|
|
3158
|
+
toolPaths.pluginMarketplacePath
|
|
3159
|
+
);
|
|
3160
|
+
next.pluginMarketplacePath = toolPaths.pluginMarketplacePath;
|
|
3161
|
+
changed = true;
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
if (
|
|
3166
|
+
tool === "codex" &&
|
|
3167
|
+
!toolPaths.pluginsDir &&
|
|
3168
|
+
!toolPaths.pluginMarketplacePath &&
|
|
3169
|
+
(next.pluginsDir ||
|
|
3170
|
+
next.pluginMarketplacePath ||
|
|
3171
|
+
next.pluginsBackup ||
|
|
3172
|
+
next.pluginMarketplaceBackup)
|
|
3173
|
+
) {
|
|
3174
|
+
next.pluginsDir = undefined;
|
|
3175
|
+
next.pluginMarketplacePath = undefined;
|
|
3176
|
+
next.pluginsBackup = undefined;
|
|
3177
|
+
next.pluginMarketplaceBackup = undefined;
|
|
3178
|
+
changed = true;
|
|
3179
|
+
}
|
|
3180
|
+
|
|
2703
3181
|
if (toolPaths.toolHome && !next.toolHome) {
|
|
2704
3182
|
const preview = await syncToolGlobalDocs({
|
|
2705
3183
|
homeDir,
|
|
@@ -2783,10 +3261,11 @@ async function planRenderedTargetConflicts(args: {
|
|
|
2783
3261
|
entry: ManagedToolState;
|
|
2784
3262
|
desiredWrites: string[];
|
|
2785
3263
|
desiredRemoves: string[];
|
|
2786
|
-
desiredContents: Map<string,
|
|
3264
|
+
desiredContents: Map<string, ManagedTargetContent>;
|
|
2787
3265
|
desiredSources: Map<string, string>;
|
|
2788
3266
|
conflictMode?: "warn" | "overwrite";
|
|
2789
3267
|
protectAllSources?: boolean;
|
|
3268
|
+
normalizeText?: boolean;
|
|
2790
3269
|
}): Promise<RenderedApplyPlan> {
|
|
2791
3270
|
if (args.conflictMode === "overwrite") {
|
|
2792
3271
|
return {
|
|
@@ -2824,17 +3303,19 @@ async function planRenderedTargetConflicts(args: {
|
|
|
2824
3303
|
}
|
|
2825
3304
|
|
|
2826
3305
|
const prior = previous[targetPath];
|
|
2827
|
-
const
|
|
2828
|
-
|
|
3306
|
+
const currentHash = await readTargetHash(targetPath, {
|
|
3307
|
+
normalizeText: args.normalizeText,
|
|
3308
|
+
});
|
|
3309
|
+
if (currentHash == null) {
|
|
2829
3310
|
if (args.desiredWrites.includes(targetPath)) {
|
|
2830
3311
|
write.push(targetPath);
|
|
2831
3312
|
}
|
|
2832
3313
|
continue;
|
|
2833
3314
|
}
|
|
2834
|
-
|
|
2835
|
-
const currentHash = renderedHash(current);
|
|
2836
3315
|
const desiredHash = args.desiredContents.get(targetPath)
|
|
2837
|
-
?
|
|
3316
|
+
? targetContentHash(args.desiredContents.get(targetPath)!, {
|
|
3317
|
+
normalizeText: args.normalizeText,
|
|
3318
|
+
})
|
|
2838
3319
|
: null;
|
|
2839
3320
|
if (prior?.hash) {
|
|
2840
3321
|
if (
|
|
@@ -2907,7 +3388,7 @@ function logRenderedConflicts(
|
|
|
2907
3388
|
}
|
|
2908
3389
|
|
|
2909
3390
|
async function applyRenderedWrites(args: {
|
|
2910
|
-
contents: Map<string,
|
|
3391
|
+
contents: Map<string, ManagedTargetContent>;
|
|
2911
3392
|
targets: string[];
|
|
2912
3393
|
}) {
|
|
2913
3394
|
for (const pathValue of args.targets) {
|
|
@@ -2918,7 +3399,9 @@ async function applyRenderedWrites(args: {
|
|
|
2918
3399
|
await mkdir(dirname(pathValue), { recursive: true });
|
|
2919
3400
|
await Bun.write(
|
|
2920
3401
|
pathValue,
|
|
2921
|
-
desired.endsWith("\n")
|
|
3402
|
+
typeof desired === "string" && !desired.endsWith("\n")
|
|
3403
|
+
? `${desired}\n`
|
|
3404
|
+
: desired
|
|
2922
3405
|
);
|
|
2923
3406
|
}
|
|
2924
3407
|
}
|
|
@@ -2951,8 +3434,9 @@ function updateRenderedTargetState(args: {
|
|
|
2951
3434
|
entry: ManagedToolState;
|
|
2952
3435
|
writtenTargets: string[];
|
|
2953
3436
|
removedTargets: string[];
|
|
2954
|
-
contents: Map<string,
|
|
3437
|
+
contents: Map<string, ManagedTargetContent>;
|
|
2955
3438
|
sources: Map<string, string>;
|
|
3439
|
+
normalizeText?: boolean;
|
|
2956
3440
|
}) {
|
|
2957
3441
|
const next = { ...(args.entry.renderedTargets ?? {}) };
|
|
2958
3442
|
for (const pathValue of args.removedTargets) {
|
|
@@ -2965,7 +3449,9 @@ function updateRenderedTargetState(args: {
|
|
|
2965
3449
|
continue;
|
|
2966
3450
|
}
|
|
2967
3451
|
next[pathValue] = {
|
|
2968
|
-
hash:
|
|
3452
|
+
hash: targetContentHash(contents, {
|
|
3453
|
+
normalizeText: args.normalizeText,
|
|
3454
|
+
}),
|
|
2969
3455
|
sourcePath,
|
|
2970
3456
|
sourceKind: renderedSourceKindForPath(sourcePath),
|
|
2971
3457
|
};
|
|
@@ -3012,6 +3498,8 @@ function logSyncDryRun({
|
|
|
3012
3498
|
rulesConflicts,
|
|
3013
3499
|
configPlan,
|
|
3014
3500
|
configConflicts,
|
|
3501
|
+
pluginPlan,
|
|
3502
|
+
pluginConflicts,
|
|
3015
3503
|
}: {
|
|
3016
3504
|
tool: string;
|
|
3017
3505
|
entry: ManagedToolState;
|
|
@@ -3027,6 +3515,8 @@ function logSyncDryRun({
|
|
|
3027
3515
|
rulesConflicts: RenderedConflict[];
|
|
3028
3516
|
configPlan: { write: boolean; remove: boolean; targetPath: string };
|
|
3029
3517
|
configConflicts: RenderedConflict[];
|
|
3518
|
+
pluginPlan: { write: string[]; remove: string[] };
|
|
3519
|
+
pluginConflicts: RenderedConflict[];
|
|
3030
3520
|
}) {
|
|
3031
3521
|
for (const name of skillPlan.add) {
|
|
3032
3522
|
console.log(`${tool}: would add skill ${name}`);
|
|
@@ -3069,6 +3559,13 @@ function logSyncDryRun({
|
|
|
3069
3559
|
console.log(`${tool}: would remove tool config ${configPlan.targetPath}`);
|
|
3070
3560
|
}
|
|
3071
3561
|
logRenderedConflicts(tool, configConflicts, true);
|
|
3562
|
+
for (const p of pluginPlan.write) {
|
|
3563
|
+
console.log(`${tool}: would write plugin asset ${p}`);
|
|
3564
|
+
}
|
|
3565
|
+
for (const p of pluginPlan.remove) {
|
|
3566
|
+
console.log(`${tool}: would remove plugin asset ${p}`);
|
|
3567
|
+
}
|
|
3568
|
+
logRenderedConflicts(tool, pluginConflicts, true);
|
|
3072
3569
|
if (mcpPlan.needsWrite && entry.mcpConfig) {
|
|
3073
3570
|
console.log(`${tool}: would update mcp config ${entry.mcpConfig}`);
|
|
3074
3571
|
}
|
|
@@ -3085,12 +3582,15 @@ function logSyncDryRun({
|
|
|
3085
3582
|
rulesPlan.remove.length === 0 &&
|
|
3086
3583
|
!configPlan.write &&
|
|
3087
3584
|
!configPlan.remove &&
|
|
3585
|
+
pluginPlan.write.length === 0 &&
|
|
3586
|
+
pluginPlan.remove.length === 0 &&
|
|
3088
3587
|
!mcpPlan.needsWrite &&
|
|
3089
3588
|
agentConflicts.length === 0 &&
|
|
3090
3589
|
automationConflicts.length === 0 &&
|
|
3091
3590
|
globalDocsConflicts.length === 0 &&
|
|
3092
3591
|
rulesConflicts.length === 0 &&
|
|
3093
|
-
configConflicts.length === 0
|
|
3592
|
+
configConflicts.length === 0 &&
|
|
3593
|
+
pluginConflicts.length === 0
|
|
3094
3594
|
) {
|
|
3095
3595
|
console.log(`${tool}: no changes`);
|
|
3096
3596
|
}
|
|
@@ -3175,6 +3675,24 @@ async function repairManagedCanonicalContent(args: {
|
|
|
3175
3675
|
adopted.push(...items.map((item) => `${item.kind}:${item.name}`));
|
|
3176
3676
|
}
|
|
3177
3677
|
|
|
3678
|
+
if (
|
|
3679
|
+
args.tool === "codex" &&
|
|
3680
|
+
(args.entry.pluginsBackup ||
|
|
3681
|
+
args.entry.pluginsDir ||
|
|
3682
|
+
args.entry.pluginMarketplaceBackup ||
|
|
3683
|
+
args.entry.pluginMarketplacePath)
|
|
3684
|
+
) {
|
|
3685
|
+
const items = await adoptExistingCodexPlugins({
|
|
3686
|
+
homeDir: args.homeDir,
|
|
3687
|
+
rootDir: args.rootDir,
|
|
3688
|
+
pluginsDir: args.entry.pluginsBackup ?? args.entry.pluginsDir,
|
|
3689
|
+
pluginMarketplacePath:
|
|
3690
|
+
args.entry.pluginMarketplaceBackup ?? args.entry.pluginMarketplacePath,
|
|
3691
|
+
conflictMode: "keep-canonical",
|
|
3692
|
+
});
|
|
3693
|
+
adopted.push(...items.map((item) => `${item.kind}:${item.name}`));
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3178
3696
|
if (adopted.length > 0) {
|
|
3179
3697
|
await buildIndex({
|
|
3180
3698
|
homeDir: args.homeDir,
|
|
@@ -3186,6 +3704,289 @@ async function repairManagedCanonicalContent(args: {
|
|
|
3186
3704
|
return adopted;
|
|
3187
3705
|
}
|
|
3188
3706
|
|
|
3707
|
+
async function discoverExistingCodexPluginEntries(args: {
|
|
3708
|
+
homeDir: string;
|
|
3709
|
+
rootDir: string;
|
|
3710
|
+
pluginMarketplacePath?: string;
|
|
3711
|
+
pluginsDir?: string;
|
|
3712
|
+
}): Promise<
|
|
3713
|
+
{
|
|
3714
|
+
name: string;
|
|
3715
|
+
livePath: string;
|
|
3716
|
+
sourcePath: string;
|
|
3717
|
+
}[]
|
|
3718
|
+
> {
|
|
3719
|
+
if (projectRootFromAiRoot(args.rootDir, args.homeDir) != null) {
|
|
3720
|
+
return [];
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
const results = new Map<
|
|
3724
|
+
string,
|
|
3725
|
+
{ name: string; livePath: string; sourcePath: string }
|
|
3726
|
+
>();
|
|
3727
|
+
const liveRoot = codexLiveRoot(args.homeDir, args.rootDir);
|
|
3728
|
+
const marketplaceRaw = args.pluginMarketplacePath
|
|
3729
|
+
? await readTextOrNull(args.pluginMarketplacePath)
|
|
3730
|
+
: null;
|
|
3731
|
+
if (marketplaceRaw) {
|
|
3732
|
+
try {
|
|
3733
|
+
const parsed = JSON.parse(marketplaceRaw) as unknown;
|
|
3734
|
+
const plugins =
|
|
3735
|
+
isPlainObject(parsed) && Array.isArray(parsed.plugins)
|
|
3736
|
+
? parsed.plugins
|
|
3737
|
+
: [];
|
|
3738
|
+
for (const entry of plugins) {
|
|
3739
|
+
if (
|
|
3740
|
+
!isPlainObject(entry) ||
|
|
3741
|
+
typeof entry.name !== "string" ||
|
|
3742
|
+
!isSafeCodexPluginName(entry.name)
|
|
3743
|
+
) {
|
|
3744
|
+
continue;
|
|
3745
|
+
}
|
|
3746
|
+
const source = isPlainObject(entry.source) ? entry.source : null;
|
|
3747
|
+
if (!(source?.source === "local" && typeof source.path === "string")) {
|
|
3748
|
+
continue;
|
|
3749
|
+
}
|
|
3750
|
+
const pathValue = source.path.trim();
|
|
3751
|
+
if (
|
|
3752
|
+
!(
|
|
3753
|
+
pathValue === `./plugins/${entry.name}` ||
|
|
3754
|
+
pathValue === `./.codex/plugins/${entry.name}`
|
|
3755
|
+
)
|
|
3756
|
+
) {
|
|
3757
|
+
continue;
|
|
3758
|
+
}
|
|
3759
|
+
const livePath = join(liveRoot, pathValue.slice(2));
|
|
3760
|
+
if (
|
|
3761
|
+
!(await fileExists(join(livePath, ".codex-plugin", "plugin.json")))
|
|
3762
|
+
) {
|
|
3763
|
+
continue;
|
|
3764
|
+
}
|
|
3765
|
+
results.set(entry.name, {
|
|
3766
|
+
name: entry.name,
|
|
3767
|
+
livePath,
|
|
3768
|
+
sourcePath: pathValue,
|
|
3769
|
+
});
|
|
3770
|
+
}
|
|
3771
|
+
} catch {
|
|
3772
|
+
// Ignore malformed marketplace files during adoption planning.
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
for (const candidateRoot of [
|
|
3777
|
+
args.pluginsDir,
|
|
3778
|
+
codexLegacyPluginsDir(args.homeDir, args.rootDir),
|
|
3779
|
+
]) {
|
|
3780
|
+
if (!(candidateRoot && (await fileExists(candidateRoot)))) {
|
|
3781
|
+
continue;
|
|
3782
|
+
}
|
|
3783
|
+
const entries = await readdir(candidateRoot, { withFileTypes: true }).catch(
|
|
3784
|
+
() => [] as import("node:fs").Dirent[]
|
|
3785
|
+
);
|
|
3786
|
+
for (const entry of entries) {
|
|
3787
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
3788
|
+
continue;
|
|
3789
|
+
}
|
|
3790
|
+
const livePath = join(candidateRoot, entry.name);
|
|
3791
|
+
if (!(await fileExists(join(livePath, ".codex-plugin", "plugin.json")))) {
|
|
3792
|
+
continue;
|
|
3793
|
+
}
|
|
3794
|
+
if (results.has(entry.name)) {
|
|
3795
|
+
continue;
|
|
3796
|
+
}
|
|
3797
|
+
const relativePrefix =
|
|
3798
|
+
candidateRoot === args.pluginsDir ? "./plugins/" : "./.codex/plugins/";
|
|
3799
|
+
results.set(entry.name, {
|
|
3800
|
+
name: entry.name,
|
|
3801
|
+
livePath,
|
|
3802
|
+
sourcePath: `${relativePrefix}${entry.name}`,
|
|
3803
|
+
});
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
return Array.from(results.values()).sort((a, b) =>
|
|
3808
|
+
a.name.localeCompare(b.name)
|
|
3809
|
+
);
|
|
3810
|
+
}
|
|
3811
|
+
|
|
3812
|
+
async function planExistingCodexPluginAdoption(args: {
|
|
3813
|
+
homeDir: string;
|
|
3814
|
+
rootDir: string;
|
|
3815
|
+
pluginMarketplacePath?: string;
|
|
3816
|
+
pluginsDir?: string;
|
|
3817
|
+
}): Promise<ExistingManagedImportPlan> {
|
|
3818
|
+
const plan = emptyManagedImportPlan();
|
|
3819
|
+
const canonicalMarketplacePath = codexCanonicalPluginMarketplacePath(
|
|
3820
|
+
args.rootDir
|
|
3821
|
+
);
|
|
3822
|
+
const marketplaceRaw = args.pluginMarketplacePath
|
|
3823
|
+
? await readTextOrNull(args.pluginMarketplacePath)
|
|
3824
|
+
: null;
|
|
3825
|
+
if (marketplaceRaw != null) {
|
|
3826
|
+
const normalizedLive = normalizeCodexMarketplaceText(marketplaceRaw);
|
|
3827
|
+
const canonicalRaw = await readTextOrNull(canonicalMarketplacePath);
|
|
3828
|
+
const item: ExistingManagedItem = {
|
|
3829
|
+
kind: "plugin-marketplace",
|
|
3830
|
+
name: "codex/plugins/marketplace.json",
|
|
3831
|
+
livePath: args.pluginMarketplacePath!,
|
|
3832
|
+
canonicalPath: canonicalMarketplacePath,
|
|
3833
|
+
};
|
|
3834
|
+
if (canonicalRaw == null) {
|
|
3835
|
+
plan.adopt.push(item);
|
|
3836
|
+
} else if (normalizeCodexMarketplaceText(canonicalRaw) === normalizedLive) {
|
|
3837
|
+
plan.identical.push(item);
|
|
3838
|
+
} else {
|
|
3839
|
+
plan.conflicts.push(item);
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
for (const plugin of await discoverExistingCodexPluginEntries(args)) {
|
|
3844
|
+
const canonicalPath = join(
|
|
3845
|
+
codexCanonicalPluginsRoot(args.rootDir),
|
|
3846
|
+
plugin.name
|
|
3847
|
+
);
|
|
3848
|
+
const canonicalHash = await hashDirectoryTree(canonicalPath);
|
|
3849
|
+
const liveHash = await hashDirectoryTree(plugin.livePath);
|
|
3850
|
+
const item: ExistingManagedItem = {
|
|
3851
|
+
kind: "plugin",
|
|
3852
|
+
name: plugin.name,
|
|
3853
|
+
livePath: plugin.livePath,
|
|
3854
|
+
canonicalPath,
|
|
3855
|
+
};
|
|
3856
|
+
if (canonicalHash == null) {
|
|
3857
|
+
plan.adopt.push(item);
|
|
3858
|
+
} else if (canonicalHash === liveHash) {
|
|
3859
|
+
plan.identical.push(item);
|
|
3860
|
+
} else {
|
|
3861
|
+
plan.conflicts.push(item);
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
return mergeManagedImportPlans(plan);
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
async function adoptExistingCodexPlugins(args: {
|
|
3869
|
+
homeDir: string;
|
|
3870
|
+
rootDir: string;
|
|
3871
|
+
pluginMarketplacePath?: string;
|
|
3872
|
+
pluginsDir?: string;
|
|
3873
|
+
conflictMode: "keep-canonical" | "keep-existing";
|
|
3874
|
+
}): Promise<ExistingManagedItem[]> {
|
|
3875
|
+
const adopted: ExistingManagedItem[] = [];
|
|
3876
|
+
const canonicalMarketplacePath = codexCanonicalPluginMarketplacePath(
|
|
3877
|
+
args.rootDir
|
|
3878
|
+
);
|
|
3879
|
+
const marketplaceRaw = args.pluginMarketplacePath
|
|
3880
|
+
? await readTextOrNull(args.pluginMarketplacePath)
|
|
3881
|
+
: null;
|
|
3882
|
+
if (marketplaceRaw != null) {
|
|
3883
|
+
const normalizedLive = normalizeCodexMarketplaceText(marketplaceRaw);
|
|
3884
|
+
const canonicalRaw = await readTextOrNull(canonicalMarketplacePath);
|
|
3885
|
+
if (canonicalRaw == null || args.conflictMode === "keep-existing") {
|
|
3886
|
+
await ensureDir(dirname(canonicalMarketplacePath));
|
|
3887
|
+
await Bun.write(canonicalMarketplacePath, normalizedLive);
|
|
3888
|
+
adopted.push({
|
|
3889
|
+
kind: "plugin-marketplace",
|
|
3890
|
+
name: "codex/plugins/marketplace.json",
|
|
3891
|
+
livePath: args.pluginMarketplacePath!,
|
|
3892
|
+
canonicalPath: canonicalMarketplacePath,
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
|
|
3897
|
+
for (const plugin of await discoverExistingCodexPluginEntries(args)) {
|
|
3898
|
+
const canonicalPath = join(
|
|
3899
|
+
codexCanonicalPluginsRoot(args.rootDir),
|
|
3900
|
+
plugin.name
|
|
3901
|
+
);
|
|
3902
|
+
const canonicalHash = await hashDirectoryTree(canonicalPath);
|
|
3903
|
+
const liveHash = await hashDirectoryTree(plugin.livePath);
|
|
3904
|
+
if (
|
|
3905
|
+
canonicalHash != null &&
|
|
3906
|
+
canonicalHash !== liveHash &&
|
|
3907
|
+
args.conflictMode !== "keep-existing"
|
|
3908
|
+
) {
|
|
3909
|
+
continue;
|
|
3910
|
+
}
|
|
3911
|
+
await ensureDir(dirname(canonicalPath));
|
|
3912
|
+
await rm(canonicalPath, { recursive: true, force: true });
|
|
3913
|
+
await cp(plugin.livePath, canonicalPath, { recursive: true });
|
|
3914
|
+
adopted.push({
|
|
3915
|
+
kind: "plugin",
|
|
3916
|
+
name: plugin.name,
|
|
3917
|
+
livePath: plugin.livePath,
|
|
3918
|
+
canonicalPath,
|
|
3919
|
+
});
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3922
|
+
return adopted;
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
async function planCodexPluginFileChanges(args: {
|
|
3926
|
+
rootDir: string;
|
|
3927
|
+
pluginsDir: string;
|
|
3928
|
+
pluginMarketplacePath?: string;
|
|
3929
|
+
previouslyManagedTargets?: string[];
|
|
3930
|
+
}): Promise<{
|
|
3931
|
+
add: string[];
|
|
3932
|
+
remove: string[];
|
|
3933
|
+
contents: Map<string, ManagedTargetContent>;
|
|
3934
|
+
sources: Map<string, string>;
|
|
3935
|
+
}> {
|
|
3936
|
+
const contents = new Map<string, ManagedTargetContent>();
|
|
3937
|
+
const sources = new Map<string, string>();
|
|
3938
|
+
const desiredPaths = new Set<string>();
|
|
3939
|
+
|
|
3940
|
+
const marketplace = await loadCanonicalCodexMarketplaceText(args.rootDir);
|
|
3941
|
+
if (marketplace.text != null && args.pluginMarketplacePath) {
|
|
3942
|
+
desiredPaths.add(args.pluginMarketplacePath);
|
|
3943
|
+
contents.set(args.pluginMarketplacePath, marketplace.text);
|
|
3944
|
+
sources.set(args.pluginMarketplacePath, marketplace.sourcePath);
|
|
3945
|
+
}
|
|
3946
|
+
|
|
3947
|
+
for (const plugin of await loadCanonicalCodexPlugins(args.rootDir)) {
|
|
3948
|
+
for (const [relPath, bytes] of plugin.files.entries()) {
|
|
3949
|
+
const targetPath = join(args.pluginsDir, plugin.name, relPath);
|
|
3950
|
+
desiredPaths.add(targetPath);
|
|
3951
|
+
contents.set(targetPath, bytes);
|
|
3952
|
+
sources.set(targetPath, join(plugin.sourceDir, relPath));
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3956
|
+
const add = new Set<string>();
|
|
3957
|
+
for (const targetPath of desiredPaths) {
|
|
3958
|
+
const currentHash = await readTargetHash(targetPath, {
|
|
3959
|
+
normalizeText: false,
|
|
3960
|
+
});
|
|
3961
|
+
const desired = contents.get(targetPath);
|
|
3962
|
+
if (desired == null) {
|
|
3963
|
+
continue;
|
|
3964
|
+
}
|
|
3965
|
+
if (currentHash !== targetContentHash(desired, { normalizeText: false })) {
|
|
3966
|
+
add.add(targetPath);
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3970
|
+
const remove = Array.from(
|
|
3971
|
+
new Set(
|
|
3972
|
+
(args.previouslyManagedTargets ?? []).filter((targetPath) => {
|
|
3973
|
+
const inManagedRoot =
|
|
3974
|
+
(args.pluginMarketplacePath != null &&
|
|
3975
|
+
targetPath === args.pluginMarketplacePath) ||
|
|
3976
|
+
targetPath.startsWith(join(args.pluginsDir, ""));
|
|
3977
|
+
return inManagedRoot && !desiredPaths.has(targetPath);
|
|
3978
|
+
})
|
|
3979
|
+
)
|
|
3980
|
+
).sort();
|
|
3981
|
+
|
|
3982
|
+
return {
|
|
3983
|
+
add: Array.from(add).sort(),
|
|
3984
|
+
remove,
|
|
3985
|
+
contents,
|
|
3986
|
+
sources,
|
|
3987
|
+
};
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3189
3990
|
async function syncManagedToolEntry({
|
|
3190
3991
|
homeDir,
|
|
3191
3992
|
tool,
|
|
@@ -3243,6 +4044,7 @@ async function syncManagedToolEntry({
|
|
|
3243
4044
|
|
|
3244
4045
|
const mcpPlan = entry.mcpConfig
|
|
3245
4046
|
? await syncMcpConfig({
|
|
4047
|
+
homeDir,
|
|
3246
4048
|
mcpConfigPath: entry.mcpConfig,
|
|
3247
4049
|
rootDir,
|
|
3248
4050
|
tool,
|
|
@@ -3302,6 +4104,15 @@ async function syncManagedToolEntry({
|
|
|
3302
4104
|
managedConfig: false,
|
|
3303
4105
|
targetPath: "",
|
|
3304
4106
|
};
|
|
4107
|
+
const pluginPlan =
|
|
4108
|
+
tool === "codex" && entry.pluginsDir
|
|
4109
|
+
? await planCodexPluginFileChanges({
|
|
4110
|
+
rootDir,
|
|
4111
|
+
pluginsDir: entry.pluginsDir,
|
|
4112
|
+
pluginMarketplacePath: entry.pluginMarketplacePath,
|
|
4113
|
+
previouslyManagedTargets: Object.keys(entry.renderedTargets ?? {}),
|
|
4114
|
+
})
|
|
4115
|
+
: { add: [], remove: [], contents: new Map(), sources: new Map() };
|
|
3305
4116
|
|
|
3306
4117
|
const agentRendered = await planRenderedTargetConflicts({
|
|
3307
4118
|
entry,
|
|
@@ -3355,6 +4166,16 @@ async function syncManagedToolEntry({
|
|
|
3355
4166
|
desiredSources: configSources,
|
|
3356
4167
|
conflictMode: builtinConflictMode,
|
|
3357
4168
|
});
|
|
4169
|
+
const pluginRendered = await planRenderedTargetConflicts({
|
|
4170
|
+
entry,
|
|
4171
|
+
desiredWrites: pluginPlan.add,
|
|
4172
|
+
desiredRemoves: pluginPlan.remove,
|
|
4173
|
+
desiredContents: pluginPlan.contents,
|
|
4174
|
+
desiredSources: pluginPlan.sources,
|
|
4175
|
+
conflictMode: builtinConflictMode,
|
|
4176
|
+
protectAllSources: true,
|
|
4177
|
+
normalizeText: false,
|
|
4178
|
+
});
|
|
3358
4179
|
|
|
3359
4180
|
if (dryRun) {
|
|
3360
4181
|
logSyncDryRun({
|
|
@@ -3382,6 +4203,11 @@ async function syncManagedToolEntry({
|
|
|
3382
4203
|
targetPath: configPlan.targetPath,
|
|
3383
4204
|
},
|
|
3384
4205
|
configConflicts: configRendered.conflicts,
|
|
4206
|
+
pluginPlan: {
|
|
4207
|
+
write: pluginRendered.write,
|
|
4208
|
+
remove: pluginRendered.remove,
|
|
4209
|
+
},
|
|
4210
|
+
pluginConflicts: pluginRendered.conflicts,
|
|
3385
4211
|
});
|
|
3386
4212
|
} else {
|
|
3387
4213
|
await applyRenderedRemoves(agentRendered.remove);
|
|
@@ -3412,11 +4238,20 @@ async function syncManagedToolEntry({
|
|
|
3412
4238
|
contents: configContents,
|
|
3413
4239
|
targets: configRendered.write,
|
|
3414
4240
|
});
|
|
4241
|
+
await applyRenderedRemoves(pluginRendered.remove);
|
|
4242
|
+
await applyRenderedWrites({
|
|
4243
|
+
contents: pluginPlan.contents,
|
|
4244
|
+
targets: pluginRendered.write,
|
|
4245
|
+
});
|
|
4246
|
+
if (entry.pluginsDir) {
|
|
4247
|
+
await pruneEmptyParents(pluginRendered.remove, entry.pluginsDir);
|
|
4248
|
+
}
|
|
3415
4249
|
logRenderedConflicts(tool, agentRendered.conflicts);
|
|
3416
4250
|
logRenderedConflicts(tool, automationRendered.conflicts);
|
|
3417
4251
|
logRenderedConflicts(tool, globalDocsRendered.conflicts);
|
|
3418
4252
|
logRenderedConflicts(tool, rulesRendered.conflicts);
|
|
3419
4253
|
logRenderedConflicts(tool, configRendered.conflicts);
|
|
4254
|
+
logRenderedConflicts(tool, pluginRendered.conflicts);
|
|
3420
4255
|
|
|
3421
4256
|
updateRenderedTargetState({
|
|
3422
4257
|
entry,
|
|
@@ -3453,6 +4288,14 @@ async function syncManagedToolEntry({
|
|
|
3453
4288
|
contents: configContents,
|
|
3454
4289
|
sources: configSources,
|
|
3455
4290
|
});
|
|
4291
|
+
updateRenderedTargetState({
|
|
4292
|
+
entry,
|
|
4293
|
+
writtenTargets: pluginRendered.write,
|
|
4294
|
+
removedTargets: pluginRendered.remove,
|
|
4295
|
+
contents: pluginPlan.contents,
|
|
4296
|
+
sources: pluginPlan.sources,
|
|
4297
|
+
normalizeText: false,
|
|
4298
|
+
});
|
|
3456
4299
|
|
|
3457
4300
|
for (const name of adoptedSkills) {
|
|
3458
4301
|
console.log(
|