agentloom 0.1.8 → 0.1.10
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 +2 -1
- package/dist/commands/agent.js +2 -1
- package/dist/commands/command.js +2 -1
- package/dist/commands/delete.js +80 -55
- package/dist/commands/mcp.js +4 -2
- package/dist/commands/rule.js +2 -1
- package/dist/commands/skills.js +2 -1
- package/dist/commands/update.js +37 -3
- package/dist/core/copy.js +4 -4
- package/dist/core/importer.js +389 -39
- package/dist/core/migration.js +33 -10
- package/dist/core/router.js +10 -3
- package/dist/sync/index.js +63 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ Global scope uses `~/.agents` with the same file layout.
|
|
|
60
60
|
- `agentloom update [source]`
|
|
61
61
|
- `agentloom upgrade`
|
|
62
62
|
- `agentloom sync`
|
|
63
|
-
- `agentloom delete <source|name
|
|
63
|
+
- `agentloom delete <source|name...>`
|
|
64
64
|
|
|
65
65
|
Aggregate `add` imports discoverable entities from a source (agents, commands, rules, MCP servers, skills). In interactive sessions, each entity supports two tracking modes:
|
|
66
66
|
|
|
@@ -117,6 +117,7 @@ agentloom mcp add farnoodma/agents --mcps browser
|
|
|
117
117
|
agentloom rule add farnoodma/agents --rules always-test
|
|
118
118
|
agentloom skill add farnoodma/agents --skills pr-review
|
|
119
119
|
agentloom delete farnoodma/agents
|
|
120
|
+
agentloom command delete review audit
|
|
120
121
|
agentloom mcp server add browser-tools --command npx --arg browser-tools-mcp
|
|
121
122
|
```
|
|
122
123
|
|
package/dist/commands/agent.js
CHANGED
|
@@ -7,7 +7,8 @@ import { runScopedFindCommand } from "./find.js";
|
|
|
7
7
|
import { runScopedSyncCommand } from "./sync.js";
|
|
8
8
|
import { runScopedUpdateCommand } from "./update.js";
|
|
9
9
|
export async function runAgentCommand(argv, cwd) {
|
|
10
|
-
const
|
|
10
|
+
const rawAction = argv._[1];
|
|
11
|
+
const action = rawAction === "remove" ? "delete" : rawAction;
|
|
11
12
|
if (argv.help || !action) {
|
|
12
13
|
console.log("Usage:\n agentloom agent <add|list|delete|find|update|sync> [options]");
|
|
13
14
|
return;
|
package/dist/commands/command.js
CHANGED
|
@@ -7,7 +7,8 @@ import { runScopedFindCommand } from "./find.js";
|
|
|
7
7
|
import { runScopedSyncCommand } from "./sync.js";
|
|
8
8
|
import { runScopedUpdateCommand } from "./update.js";
|
|
9
9
|
export async function runCommandCommand(argv, cwd) {
|
|
10
|
-
const
|
|
10
|
+
const rawAction = argv._[1];
|
|
11
|
+
const action = rawAction === "remove" ? "delete" : rawAction;
|
|
11
12
|
if (argv.help) {
|
|
12
13
|
if (action === "add") {
|
|
13
14
|
console.log(getCommandAddHelpText());
|
package/dist/commands/delete.js
CHANGED
|
@@ -2,16 +2,26 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { multiselect, isCancel, cancel, select } from "@clack/prompts";
|
|
4
4
|
import { parseAgentsDir } from "../core/agents.js";
|
|
5
|
-
import {
|
|
5
|
+
import { getStringArrayFlag } from "../core/argv.js";
|
|
6
|
+
import { parseCommandsDir, stripCommandFileExtension, } from "../core/commands.js";
|
|
6
7
|
import { formatUsageError } from "../core/copy.js";
|
|
7
8
|
import { readLockfile, writeLockfile } from "../core/lockfile.js";
|
|
8
9
|
import { readCanonicalMcp, writeCanonicalMcp } from "../core/mcp.js";
|
|
10
|
+
import { getProviderCommandsDir } from "../core/provider-paths.js";
|
|
9
11
|
import { normalizeRuleSelector, parseRulesDir, stripRuleFileExtension, } from "../core/rules.js";
|
|
10
12
|
import { parseSourceSpec } from "../core/sources.js";
|
|
11
13
|
import { parseSkillsDir } from "../core/skills.js";
|
|
12
14
|
import { getNonInteractiveMode, resolvePathsForCommand, runPostMutationSync, } from "./entity-utils.js";
|
|
13
15
|
const ALL_ENTITIES = ["agent", "command", "mcp", "rule", "skill"];
|
|
14
16
|
const MULTISELECT_HELP_TEXT = "↑↓ move, space select, enter confirm";
|
|
17
|
+
class MissingEntityError extends Error {
|
|
18
|
+
candidate;
|
|
19
|
+
constructor(candidate, message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.candidate = candidate;
|
|
22
|
+
this.name = "MissingEntityError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
15
25
|
function withMultiselectHelp(message) {
|
|
16
26
|
return `${message}\n${MULTISELECT_HELP_TEXT}`;
|
|
17
27
|
}
|
|
@@ -32,13 +42,13 @@ export async function runScopedDeleteCommand(options) {
|
|
|
32
42
|
});
|
|
33
43
|
}
|
|
34
44
|
async function runEntityAwareDelete(options) {
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
45
|
+
const candidates = getDeleteCandidates(options.argv, options.sourceIndex);
|
|
46
|
+
if (candidates.length === 0) {
|
|
37
47
|
throw new Error(formatUsageError({
|
|
38
|
-
issue: "Missing required <source|name
|
|
48
|
+
issue: "Missing required <source|name...>.",
|
|
39
49
|
usage: options.target === "all"
|
|
40
|
-
? "agentloom delete <source|name
|
|
41
|
-
: `agentloom ${options.target} delete <source|name
|
|
50
|
+
? "agentloom delete <source|name...> [options]"
|
|
51
|
+
: `agentloom ${options.target} delete <source|name...> [options]`,
|
|
42
52
|
example: options.target === "all"
|
|
43
53
|
? "agentloom delete farnoodma/agents"
|
|
44
54
|
: `agentloom ${options.target} delete reviewer`,
|
|
@@ -46,61 +56,69 @@ async function runEntityAwareDelete(options) {
|
|
|
46
56
|
}
|
|
47
57
|
const nonInteractive = getNonInteractiveMode(options.argv);
|
|
48
58
|
const paths = await resolvePathsForCommand(options.argv, options.cwd);
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
target: options.target,
|
|
57
|
-
sourceMode,
|
|
58
|
-
nonInteractive,
|
|
59
|
-
});
|
|
60
|
-
if (entities.length === 0) {
|
|
61
|
-
console.log("No entities selected for deletion.");
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
if (sourceMode) {
|
|
65
|
-
await deleteBySource({
|
|
66
|
-
paths,
|
|
67
|
-
sourceFilter: candidate,
|
|
68
|
-
lockfile,
|
|
69
|
-
lockEntries: sourceMatches,
|
|
70
|
-
entities,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
await deleteByName({
|
|
75
|
-
candidate,
|
|
59
|
+
const explicitSourceFilters = getStringArrayFlag(options.argv.source);
|
|
60
|
+
const missingCandidates = [];
|
|
61
|
+
for (const candidate of candidates) {
|
|
62
|
+
const lockfile = readLockfile(paths);
|
|
63
|
+
const sourceMatches = lockfile.entries.filter((entry) => matchesSource(entry, candidate));
|
|
64
|
+
const sourceMode = sourceMatches.length > 0 || explicitSourceFilters.length > 0;
|
|
65
|
+
const entities = await resolveEntitiesForDelete({
|
|
76
66
|
argv: options.argv,
|
|
77
|
-
|
|
78
|
-
|
|
67
|
+
target: options.target,
|
|
68
|
+
sourceMode,
|
|
79
69
|
nonInteractive,
|
|
80
70
|
});
|
|
71
|
+
if (entities.length === 0) {
|
|
72
|
+
console.log("No entities selected for deletion.");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (sourceMode) {
|
|
76
|
+
await deleteBySource({
|
|
77
|
+
paths,
|
|
78
|
+
sourceFilter: candidate,
|
|
79
|
+
lockfile,
|
|
80
|
+
lockEntries: sourceMatches,
|
|
81
|
+
entities,
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
await deleteByName({
|
|
87
|
+
candidate,
|
|
88
|
+
argv: options.argv,
|
|
89
|
+
paths,
|
|
90
|
+
entities,
|
|
91
|
+
nonInteractive,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error instanceof MissingEntityError) {
|
|
96
|
+
missingCandidates.push(error.candidate);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
81
101
|
}
|
|
82
102
|
await runPostMutationSync({
|
|
83
103
|
argv: options.argv,
|
|
84
104
|
paths,
|
|
85
105
|
target: options.target,
|
|
86
106
|
});
|
|
107
|
+
if (missingCandidates.length > 0) {
|
|
108
|
+
console.warn(`Couldn't delete these because they don't exist: ${[...new Set(missingCandidates)].join(", ")}`);
|
|
109
|
+
}
|
|
87
110
|
}
|
|
88
|
-
function
|
|
89
|
-
const fromSourceFlag =
|
|
90
|
-
|
|
91
|
-
: undefined;
|
|
92
|
-
if (fromSourceFlag)
|
|
111
|
+
function getDeleteCandidates(argv, sourceIndex) {
|
|
112
|
+
const fromSourceFlag = getStringArrayFlag(argv.source);
|
|
113
|
+
if (fromSourceFlag.length > 0)
|
|
93
114
|
return fromSourceFlag;
|
|
94
|
-
const fromNameFlag =
|
|
95
|
-
|
|
96
|
-
: undefined;
|
|
97
|
-
if (fromNameFlag)
|
|
115
|
+
const fromNameFlag = getStringArrayFlag(argv.name);
|
|
116
|
+
if (fromNameFlag.length > 0)
|
|
98
117
|
return fromNameFlag;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return positional.trim();
|
|
118
|
+
return argv._.slice(sourceIndex)
|
|
119
|
+
.filter((item) => typeof item === "string")
|
|
120
|
+
.map((item) => item.trim())
|
|
121
|
+
.filter(Boolean);
|
|
104
122
|
}
|
|
105
123
|
async function resolveEntitiesForDelete(options) {
|
|
106
124
|
if (options.target !== "all")
|
|
@@ -196,7 +214,7 @@ async function deleteByName(options) {
|
|
|
196
214
|
const matches = detectNameMatches(options.paths, options.candidate);
|
|
197
215
|
const matchingEntities = selectedEntities.filter((entity) => matches.includes(entity));
|
|
198
216
|
if (matchingEntities.length === 0) {
|
|
199
|
-
throw new
|
|
217
|
+
throw new MissingEntityError(options.candidate, `No installed entity named "${options.candidate}" found.`);
|
|
200
218
|
}
|
|
201
219
|
if (matchingEntities.length > 1) {
|
|
202
220
|
if (options.nonInteractive) {
|
|
@@ -230,7 +248,7 @@ async function deleteByName(options) {
|
|
|
230
248
|
else if (entity === "mcp") {
|
|
231
249
|
const existing = Object.keys(mcp.mcpServers).find((name) => normalizeName(name) === normalizeName(options.candidate));
|
|
232
250
|
if (!existing) {
|
|
233
|
-
throw new
|
|
251
|
+
throw new MissingEntityError(options.candidate, `MCP server "${options.candidate}" was not found.`);
|
|
234
252
|
}
|
|
235
253
|
delete mcp.mcpServers[existing];
|
|
236
254
|
}
|
|
@@ -261,7 +279,7 @@ function deleteAgentByName(paths, name) {
|
|
|
261
279
|
normalizeName(agent.fileName.replace(/\.md$/i, "")) ===
|
|
262
280
|
normalizeName(name));
|
|
263
281
|
if (!target) {
|
|
264
|
-
throw new
|
|
282
|
+
throw new MissingEntityError(name, `Agent "${name}" was not found in canonical agents.`);
|
|
265
283
|
}
|
|
266
284
|
removeIfExists(target.sourcePath);
|
|
267
285
|
}
|
|
@@ -271,16 +289,17 @@ function deleteCommandByName(paths, name) {
|
|
|
271
289
|
normalizeName(command.fileName.replace(/\.(md|mdc)$/i, "")) ===
|
|
272
290
|
normalizeName(name));
|
|
273
291
|
if (!target) {
|
|
274
|
-
throw new
|
|
292
|
+
throw new MissingEntityError(name, `Command "${name}" was not found in canonical commands.`);
|
|
275
293
|
}
|
|
276
294
|
removeIfExists(target.sourcePath);
|
|
295
|
+
removeLegacyGeminiCommandVariants(paths, target.fileName);
|
|
277
296
|
}
|
|
278
297
|
function deleteSkillByName(paths, name) {
|
|
279
298
|
const skills = parseSkillsDir(paths.skillsDir);
|
|
280
299
|
const target = skills.find((skill) => normalizeName(skill.name) === normalizeName(name) ||
|
|
281
300
|
normalizeName(path.basename(skill.sourcePath)) === normalizeName(name));
|
|
282
301
|
if (!target) {
|
|
283
|
-
throw new
|
|
302
|
+
throw new MissingEntityError(name, `Skill "${name}" was not found in canonical skills.`);
|
|
284
303
|
}
|
|
285
304
|
removeIfExists(target.sourcePath);
|
|
286
305
|
}
|
|
@@ -288,7 +307,7 @@ function deleteRuleByName(paths, name) {
|
|
|
288
307
|
const rules = parseRulesDir(paths.rulesDir);
|
|
289
308
|
const target = rules.find((rule) => ruleMatchesCandidate(rule, name));
|
|
290
309
|
if (!target) {
|
|
291
|
-
throw new
|
|
310
|
+
throw new MissingEntityError(name, `Rule "${name}" was not found in canonical rules.`);
|
|
292
311
|
}
|
|
293
312
|
removeIfExists(target.sourcePath);
|
|
294
313
|
return target;
|
|
@@ -298,6 +317,12 @@ function removeIfExists(filePath) {
|
|
|
298
317
|
return;
|
|
299
318
|
fs.rmSync(filePath, { recursive: true, force: true });
|
|
300
319
|
}
|
|
320
|
+
function removeLegacyGeminiCommandVariants(paths, fileName) {
|
|
321
|
+
const commandsDir = getProviderCommandsDir(paths, "gemini");
|
|
322
|
+
const stem = stripCommandFileExtension(path.basename(fileName));
|
|
323
|
+
removeIfExists(path.join(commandsDir, `${stem}.md`));
|
|
324
|
+
removeIfExists(path.join(commandsDir, `${stem}.mdc`));
|
|
325
|
+
}
|
|
301
326
|
function normalizeName(value) {
|
|
302
327
|
return value.trim().toLowerCase();
|
|
303
328
|
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -9,7 +9,8 @@ import { runScopedFindCommand } from "./find.js";
|
|
|
9
9
|
import { runScopedSyncCommand } from "./sync.js";
|
|
10
10
|
import { runScopedUpdateCommand } from "./update.js";
|
|
11
11
|
export async function runMcpCommand(argv, cwd) {
|
|
12
|
-
const
|
|
12
|
+
const rawAction = argv._[1];
|
|
13
|
+
const action = rawAction === "remove" ? "delete" : rawAction;
|
|
13
14
|
if (argv.help) {
|
|
14
15
|
if (action === "add") {
|
|
15
16
|
console.log(getMcpAddHelpText());
|
|
@@ -93,7 +94,8 @@ export async function runMcpCommand(argv, cwd) {
|
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
96
|
async function runMcpServerCommand(argv, cwd) {
|
|
96
|
-
const
|
|
97
|
+
const rawAction = argv._[2];
|
|
98
|
+
const action = rawAction === "remove" ? "delete" : rawAction;
|
|
97
99
|
if (argv.help || !action) {
|
|
98
100
|
console.log(getMcpServerHelpText());
|
|
99
101
|
return;
|
package/dist/commands/rule.js
CHANGED
|
@@ -7,7 +7,8 @@ import { runScopedFindCommand } from "./find.js";
|
|
|
7
7
|
import { runScopedSyncCommand } from "./sync.js";
|
|
8
8
|
import { runScopedUpdateCommand } from "./update.js";
|
|
9
9
|
export async function runRuleCommand(argv, cwd) {
|
|
10
|
-
const
|
|
10
|
+
const rawAction = argv._[1];
|
|
11
|
+
const action = rawAction === "remove" ? "delete" : rawAction;
|
|
11
12
|
if (argv.help || !action) {
|
|
12
13
|
console.log("Usage:\n agentloom rule <add|list|delete|find|update|sync> [options]");
|
|
13
14
|
return;
|
package/dist/commands/skills.js
CHANGED
|
@@ -8,7 +8,8 @@ import { runScopedFindCommand } from "./find.js";
|
|
|
8
8
|
import { runScopedSyncCommand } from "./sync.js";
|
|
9
9
|
import { runScopedUpdateCommand } from "./update.js";
|
|
10
10
|
export async function runSkillCommand(argv, cwd) {
|
|
11
|
-
const
|
|
11
|
+
const rawAction = argv._[1];
|
|
12
|
+
const action = rawAction === "remove" ? "delete" : rawAction;
|
|
12
13
|
if (argv.help || !action) {
|
|
13
14
|
console.log("Usage:\n agentloom skill <add|list|delete|find|update|sync> [options]");
|
|
14
15
|
return;
|
package/dist/commands/update.js
CHANGED
|
@@ -5,6 +5,7 @@ import { normalizeCommandSelector, stripCommandFileExtension, } from "../core/co
|
|
|
5
5
|
import { normalizeRuleSelector, stripRuleFileExtension, } from "../core/rules.js";
|
|
6
6
|
import { readLockfile } from "../core/lockfile.js";
|
|
7
7
|
import { prepareSource, parseSourceSpec } from "../core/sources.js";
|
|
8
|
+
import { sendAddTelemetryEvent } from "../core/telemetry.js";
|
|
8
9
|
import { getUpdateHelpText } from "../core/copy.js";
|
|
9
10
|
import { resolveProvidersForSync } from "../sync/index.js";
|
|
10
11
|
import { getNonInteractiveMode, resolvePathsForCommand, runPostMutationSync, } from "./entity-utils.js";
|
|
@@ -58,21 +59,28 @@ async function runEntityAwareUpdate(options) {
|
|
|
58
59
|
console.log("No lock entries matched the requested source filter.");
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
62
|
+
console.log(`Checking ${entries.length} lock entr${entries.length === 1 ? "y" : "ies"} for updates...`);
|
|
61
63
|
let updated = 0;
|
|
62
64
|
let skipped = 0;
|
|
63
|
-
for (const entry of entries) {
|
|
65
|
+
for (const [index, entry] of entries.entries()) {
|
|
66
|
+
const progressPrefix = formatUpdateProgressPrefix(index, entries.length);
|
|
67
|
+
const entryLabel = formatLockEntryLabel(entry);
|
|
64
68
|
if (!entryIncludesTarget(entry, options.target)) {
|
|
69
|
+
console.log(`${progressPrefix} Skipping ${entryLabel} (does not track ${options.target}).`);
|
|
65
70
|
skipped += 1;
|
|
66
71
|
continue;
|
|
67
72
|
}
|
|
73
|
+
console.log(`${progressPrefix} Checking ${entryLabel}...`);
|
|
68
74
|
const probe = prepareSource({
|
|
69
75
|
source: entry.source,
|
|
70
76
|
ref: entry.requestedRef,
|
|
71
77
|
subdir: entry.subdir,
|
|
72
78
|
});
|
|
73
|
-
const
|
|
79
|
+
const latestCommit = probe.resolvedCommit;
|
|
80
|
+
const hasNewCommit = latestCommit !== entry.resolvedCommit;
|
|
74
81
|
probe.cleanup();
|
|
75
82
|
if (!hasNewCommit) {
|
|
83
|
+
console.log(`${progressPrefix} Up to date at ${formatShortCommit(entry.resolvedCommit)}.`);
|
|
76
84
|
skipped += 1;
|
|
77
85
|
continue;
|
|
78
86
|
}
|
|
@@ -82,9 +90,11 @@ async function runEntityAwareUpdate(options) {
|
|
|
82
90
|
!updatePlan.importMcp &&
|
|
83
91
|
!updatePlan.importRules &&
|
|
84
92
|
!updatePlan.importSkills) {
|
|
93
|
+
console.log(`${progressPrefix} Skipping ${entryLabel} (no tracked entities selected for update).`);
|
|
85
94
|
skipped += 1;
|
|
86
95
|
continue;
|
|
87
96
|
}
|
|
97
|
+
console.log(`${progressPrefix} Updating ${entryLabel} (${formatShortCommit(entry.resolvedCommit)} -> ${formatShortCommit(latestCommit)})...`);
|
|
88
98
|
try {
|
|
89
99
|
const importOptions = {
|
|
90
100
|
source: entry.source,
|
|
@@ -136,7 +146,12 @@ async function runEntityAwareUpdate(options) {
|
|
|
136
146
|
if (updatePlan.skillRenameMap) {
|
|
137
147
|
importOptions.skillRenameMap = updatePlan.skillRenameMap;
|
|
138
148
|
}
|
|
139
|
-
await importSource(importOptions);
|
|
149
|
+
const summary = await importSource(importOptions);
|
|
150
|
+
await sendAddTelemetryEvent({
|
|
151
|
+
rawSource: entry.source,
|
|
152
|
+
summary,
|
|
153
|
+
});
|
|
154
|
+
console.log(`${progressPrefix} Updated ${entryLabel} to ${formatShortCommit(summary.resolvedCommit)}.`);
|
|
140
155
|
updated += 1;
|
|
141
156
|
}
|
|
142
157
|
catch (err) {
|
|
@@ -158,6 +173,25 @@ async function runEntityAwareUpdate(options) {
|
|
|
158
173
|
});
|
|
159
174
|
}
|
|
160
175
|
}
|
|
176
|
+
function formatUpdateProgressPrefix(index, total) {
|
|
177
|
+
return `[${index + 1}/${total}]`;
|
|
178
|
+
}
|
|
179
|
+
function formatLockEntryLabel(entry) {
|
|
180
|
+
const base = entry.source;
|
|
181
|
+
const refSuffix = typeof entry.requestedRef === "string" && entry.requestedRef.length > 0
|
|
182
|
+
? `@${entry.requestedRef}`
|
|
183
|
+
: "";
|
|
184
|
+
const subdirSuffix = typeof entry.subdir === "string" && entry.subdir.length > 0
|
|
185
|
+
? ` (${entry.subdir})`
|
|
186
|
+
: "";
|
|
187
|
+
return `${base}${refSuffix}${subdirSuffix}`;
|
|
188
|
+
}
|
|
189
|
+
function formatShortCommit(commit) {
|
|
190
|
+
const normalized = commit.trim();
|
|
191
|
+
if (normalized.length <= 12)
|
|
192
|
+
return normalized;
|
|
193
|
+
return normalized.slice(0, 12);
|
|
194
|
+
}
|
|
161
195
|
function buildEntryUpdatePlan(entry, target) {
|
|
162
196
|
const includeAgents = shouldUpdateEntity(entry, "agent", target);
|
|
163
197
|
const includeCommands = shouldUpdateEntity(entry, "command", target);
|
package/dist/core/copy.js
CHANGED
|
@@ -14,7 +14,7 @@ Aggregate commands:
|
|
|
14
14
|
update [source] Refresh lockfile-managed imports
|
|
15
15
|
upgrade Install the latest CLI release
|
|
16
16
|
sync Migrate provider configs then generate provider outputs
|
|
17
|
-
delete <source|name
|
|
17
|
+
delete <source|name...> Delete imported entities by source or name(s)
|
|
18
18
|
|
|
19
19
|
Entity commands:
|
|
20
20
|
agent <add|list|delete|find|update|sync>
|
|
@@ -40,8 +40,8 @@ Common options:
|
|
|
40
40
|
--rules <csv> Rule selectors for add/delete
|
|
41
41
|
--skills <csv> Skill selectors for add/delete
|
|
42
42
|
--selection-mode <mode> Add mode: all (default) or custom
|
|
43
|
-
--source <
|
|
44
|
-
--name <
|
|
43
|
+
--source <csv> Explicit source filter(s) for update/delete
|
|
44
|
+
--name <csv> Explicit name filter(s) for delete
|
|
45
45
|
--entity <type> Delete disambiguation for aggregate delete
|
|
46
46
|
|
|
47
47
|
Examples:
|
|
@@ -192,7 +192,7 @@ export function getCommandDeleteHelpText() {
|
|
|
192
192
|
return `Delete command imports by source or name.
|
|
193
193
|
|
|
194
194
|
Usage:
|
|
195
|
-
agentloom command delete <source|name
|
|
195
|
+
agentloom command delete <source|name...> [options]
|
|
196
196
|
`;
|
|
197
197
|
}
|
|
198
198
|
export function getMcpHelpText() {
|
package/dist/core/importer.js
CHANGED
|
@@ -417,6 +417,8 @@ export async function importSource(options) {
|
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
|
+
const selectedSubsetOfSourceAgents = sourceAgents.length > 0 &&
|
|
421
|
+
selection.selectedAgents.length < sourceAgents.length;
|
|
420
422
|
const selectedSubsetOfSourceCommands = sourceCommands.length > 0 &&
|
|
421
423
|
selectedSourceCommandFiles.length < sourceCommands.length;
|
|
422
424
|
const selectedSubsetOfSourceMcp = sourceMcpServerNames.length > 0 &&
|
|
@@ -441,18 +443,48 @@ export async function importSource(options) {
|
|
|
441
443
|
? selectedSourceSkills
|
|
442
444
|
: undefined;
|
|
443
445
|
const lockfile = readLockfile(options.paths);
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
446
|
+
const importedEntities = [
|
|
447
|
+
shouldImportAgents ? "agent" : null,
|
|
448
|
+
shouldImportCommands ? "command" : null,
|
|
449
|
+
shouldImportMcp ? "mcp" : null,
|
|
450
|
+
shouldImportRules ? "rule" : null,
|
|
451
|
+
shouldImportSkills ? "skill" : null,
|
|
452
|
+
].filter(Boolean);
|
|
453
|
+
const singleEntityImport = importedEntities.length === 1 ? importedEntities[0] : undefined;
|
|
454
|
+
const isAgentOnlyImport = singleEntityImport === "agent";
|
|
455
|
+
const isCommandOnlyImport = singleEntityImport === "command";
|
|
456
|
+
const isMcpOnlyImport = singleEntityImport === "mcp";
|
|
457
|
+
const isRuleOnlyImport = singleEntityImport === "rule";
|
|
458
|
+
const isSkillOnlyImport = singleEntityImport === "skill";
|
|
459
|
+
const relaxedSingleEntityEntries = singleEntityImport
|
|
460
|
+
? findRelaxedEntityEntries(lockfile.entries, {
|
|
451
461
|
source: prepared.spec.source,
|
|
452
462
|
sourceType: prepared.spec.type,
|
|
453
463
|
subdir: options.subdir,
|
|
454
464
|
requestedAgents: options.agents,
|
|
465
|
+
entity: singleEntityImport,
|
|
455
466
|
})
|
|
467
|
+
: [];
|
|
468
|
+
let relaxedSingleEntityEntry = relaxedSingleEntityEntries[0];
|
|
469
|
+
if (relaxedSingleEntityEntries.length > 1) {
|
|
470
|
+
const canonicalEntry = relaxedSingleEntityEntries[0];
|
|
471
|
+
const redundantEntries = relaxedSingleEntityEntries.slice(1);
|
|
472
|
+
const collapsibleRedundantEntries = redundantEntries.filter((entry) => !isMixedEntryForEntity(entry, singleEntityImport));
|
|
473
|
+
const consolidatedEntry = mergeRelaxedEntityEntriesForLock({
|
|
474
|
+
canonicalEntry,
|
|
475
|
+
redundantEntries: collapsibleRedundantEntries,
|
|
476
|
+
entity: singleEntityImport,
|
|
477
|
+
});
|
|
478
|
+
const canonicalEntryIndex = lockfile.entries.indexOf(canonicalEntry);
|
|
479
|
+
if (canonicalEntryIndex >= 0) {
|
|
480
|
+
lockfile.entries[canonicalEntryIndex] = consolidatedEntry;
|
|
481
|
+
}
|
|
482
|
+
const redundantEntriesSet = new Set(collapsibleRedundantEntries);
|
|
483
|
+
lockfile.entries = lockfile.entries.filter((entry) => !redundantEntriesSet.has(entry));
|
|
484
|
+
relaxedSingleEntityEntry = consolidatedEntry;
|
|
485
|
+
}
|
|
486
|
+
const existingEntry = singleEntityImport
|
|
487
|
+
? relaxedSingleEntityEntry
|
|
456
488
|
: findMatchingLockEntry(lockfile.entries, {
|
|
457
489
|
source: prepared.spec.source,
|
|
458
490
|
sourceType: prepared.spec.type,
|
|
@@ -472,8 +504,34 @@ export async function importSource(options) {
|
|
|
472
504
|
(existingEntry?.importedAgents.length ?? 0) === 0 &&
|
|
473
505
|
(existingEntry?.importedMcpServers.length ?? 0) === 0 &&
|
|
474
506
|
(existingEntry?.importedSkills.length ?? 0) === 0;
|
|
507
|
+
const shouldMergeAgentOnlyEntry = isAgentOnlyImport &&
|
|
508
|
+
Boolean(existingEntry) &&
|
|
509
|
+
(selection.requestedAgentsForLock !== undefined ||
|
|
510
|
+
selectedSubsetOfSourceAgents);
|
|
511
|
+
const shouldMergeMcpOnlyEntry = isMcpOnlyImport &&
|
|
512
|
+
Boolean(existingEntry) &&
|
|
513
|
+
(existingEntry?.importedAgents.length ?? 0) === 0 &&
|
|
514
|
+
(existingEntry?.importedCommands.length ?? 0) === 0 &&
|
|
515
|
+
(existingEntry?.importedRules.length ?? 0) === 0 &&
|
|
516
|
+
(existingEntry?.importedSkills.length ?? 0) === 0 &&
|
|
517
|
+
(shouldPersistMcpSelection || selectedSubsetOfSourceMcp);
|
|
518
|
+
const shouldMergeRuleOnlyEntry = isRuleOnlyImport &&
|
|
519
|
+
Boolean(existingEntry) &&
|
|
520
|
+
(existingEntry?.importedAgents.length ?? 0) === 0 &&
|
|
521
|
+
(existingEntry?.importedCommands.length ?? 0) === 0 &&
|
|
522
|
+
(existingEntry?.importedMcpServers.length ?? 0) === 0 &&
|
|
523
|
+
(existingEntry?.importedSkills.length ?? 0) === 0 &&
|
|
524
|
+
(shouldPersistRuleSelection || selectedSubsetOfSourceRules);
|
|
525
|
+
const shouldMergeSkillOnlyEntry = isSkillOnlyImport &&
|
|
526
|
+
Boolean(existingEntry) &&
|
|
527
|
+
(shouldPersistSkillSelection || selectedSubsetOfSourceSkills);
|
|
475
528
|
const lockImportedAgents = shouldImportAgents
|
|
476
|
-
?
|
|
529
|
+
? shouldMergeAgentOnlyEntry
|
|
530
|
+
? uniqueStrings([
|
|
531
|
+
...(existingEntry?.importedAgents ?? []),
|
|
532
|
+
...importedAgents,
|
|
533
|
+
])
|
|
534
|
+
: importedAgents
|
|
477
535
|
: (existingEntry?.importedAgents ?? []);
|
|
478
536
|
const lockImportedCommands = shouldImportCommands
|
|
479
537
|
? shouldMergeCommandOnlyEntry
|
|
@@ -484,13 +542,30 @@ export async function importSource(options) {
|
|
|
484
542
|
: importedCommands
|
|
485
543
|
: (existingEntry?.importedCommands ?? []);
|
|
486
544
|
const lockImportedMcpServers = shouldImportMcp
|
|
487
|
-
?
|
|
545
|
+
? shouldMergeMcpOnlyEntry
|
|
546
|
+
? uniqueStrings([
|
|
547
|
+
...(existingEntry?.importedMcpServers ?? []),
|
|
548
|
+
...importedMcpServers,
|
|
549
|
+
])
|
|
550
|
+
: importedMcpServers
|
|
488
551
|
: (existingEntry?.importedMcpServers ?? []);
|
|
489
552
|
const lockImportedRules = shouldImportRules
|
|
490
|
-
?
|
|
553
|
+
? shouldMergeRuleOnlyEntry
|
|
554
|
+
? uniqueStrings([
|
|
555
|
+
...(existingEntry?.importedRules ?? []),
|
|
556
|
+
...importedRules,
|
|
557
|
+
])
|
|
558
|
+
: importedRules
|
|
491
559
|
: (existingEntry?.importedRules ?? []);
|
|
492
560
|
const lockImportedSkills = shouldImportSkills
|
|
493
|
-
?
|
|
561
|
+
? shouldMergeSkillOnlyEntry
|
|
562
|
+
? mergeImportedSkills({
|
|
563
|
+
existingImportedSkills: existingEntry?.importedSkills,
|
|
564
|
+
importedSkills,
|
|
565
|
+
selectedSkills,
|
|
566
|
+
existingSkillRenameMap: existingEntry?.skillRenameMap,
|
|
567
|
+
})
|
|
568
|
+
: importedSkills
|
|
494
569
|
: (existingEntry?.importedSkills ?? []);
|
|
495
570
|
let lockSelectedSourceCommands;
|
|
496
571
|
if (shouldImportCommands) {
|
|
@@ -516,29 +591,92 @@ export async function importSource(options) {
|
|
|
516
591
|
else {
|
|
517
592
|
lockSelectedSourceCommands = existingEntry?.selectedSourceCommands;
|
|
518
593
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
594
|
+
let lockSelectedSourceMcpServers;
|
|
595
|
+
if (shouldImportMcp) {
|
|
596
|
+
if (shouldMergeMcpOnlyEntry) {
|
|
597
|
+
if (shouldPersistMcpSelection || selectedSubsetOfSourceMcp) {
|
|
598
|
+
lockSelectedSourceMcpServers = uniqueStrings([
|
|
599
|
+
...(existingEntry?.selectedSourceMcpServers ?? []),
|
|
600
|
+
...selectedSourceMcpServers,
|
|
601
|
+
]);
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
lockSelectedSourceMcpServers = undefined;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
else if (shouldPersistMcpSelection || selectedSubsetOfSourceMcp) {
|
|
608
|
+
lockSelectedSourceMcpServers = [...selectedSourceMcpServers];
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
lockSelectedSourceMcpServers = undefined;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
lockSelectedSourceMcpServers = existingEntry?.selectedSourceMcpServers;
|
|
616
|
+
}
|
|
617
|
+
let lockSelectedSourceRules;
|
|
618
|
+
if (shouldImportRules) {
|
|
619
|
+
if (shouldMergeRuleOnlyEntry) {
|
|
620
|
+
if (shouldPersistRuleSelection || selectedSubsetOfSourceRules) {
|
|
621
|
+
lockSelectedSourceRules = uniqueStrings([
|
|
622
|
+
...(existingEntry?.selectedSourceRules ?? []),
|
|
623
|
+
...selectedSourceRules,
|
|
624
|
+
]);
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
lockSelectedSourceRules = undefined;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
else if (shouldPersistRuleSelection || selectedSubsetOfSourceRules) {
|
|
631
|
+
lockSelectedSourceRules = [...selectedSourceRules];
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
lockSelectedSourceRules = undefined;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
lockSelectedSourceRules = existingEntry?.selectedSourceRules;
|
|
639
|
+
}
|
|
640
|
+
let lockSelectedSourceSkills;
|
|
641
|
+
if (shouldImportSkills) {
|
|
642
|
+
if (shouldMergeSkillOnlyEntry) {
|
|
643
|
+
if (shouldPersistSkillSelection || selectedSubsetOfSourceSkills) {
|
|
644
|
+
lockSelectedSourceSkills = uniqueStrings([
|
|
645
|
+
...(existingEntry?.selectedSourceSkills ?? []),
|
|
646
|
+
...selectedSourceSkills,
|
|
647
|
+
]);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
lockSelectedSourceSkills = undefined;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
else if (shouldPersistSkillSelection || selectedSubsetOfSourceSkills) {
|
|
654
|
+
lockSelectedSourceSkills = [...selectedSourceSkills];
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
lockSelectedSourceSkills = undefined;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
lockSelectedSourceSkills = existingEntry?.selectedSourceSkills;
|
|
662
|
+
}
|
|
534
663
|
const lockRuleRenameMap = shouldImportRules
|
|
535
|
-
?
|
|
664
|
+
? shouldMergeRuleOnlyEntry
|
|
665
|
+
? mergeRuleRenameMaps(existingEntry?.ruleRenameMap, importedRuleRenameMap)
|
|
666
|
+
: normalizeRuleRenameMap(importedRuleRenameMap)
|
|
536
667
|
: existingEntry?.ruleRenameMap;
|
|
537
668
|
const lockSkillsProviders = shouldImportSkills
|
|
538
|
-
?
|
|
669
|
+
? shouldMergeSkillOnlyEntry
|
|
670
|
+
? normalizeSkillsProviders([
|
|
671
|
+
...(existingEntry?.skillsProviders ?? []),
|
|
672
|
+
...(skillsProvidersForLock ?? []),
|
|
673
|
+
])
|
|
674
|
+
: (skillsProvidersForLock ?? existingEntry?.skillsProviders)
|
|
539
675
|
: existingEntry?.skillsProviders;
|
|
540
676
|
const lockSkillRenameMap = shouldImportSkills
|
|
541
|
-
?
|
|
677
|
+
? shouldMergeSkillOnlyEntry
|
|
678
|
+
? mergeSkillRenameMaps(existingEntry?.skillRenameMap, importedSkillRenameMap)
|
|
679
|
+
: normalizeSkillRenameMap(importedSkillRenameMap)
|
|
542
680
|
: existingEntry?.skillRenameMap;
|
|
543
681
|
let lockCommandRenameMap;
|
|
544
682
|
if (shouldImportCommands) {
|
|
@@ -553,7 +691,12 @@ export async function importSource(options) {
|
|
|
553
691
|
lockCommandRenameMap = existingEntry?.commandRenameMap;
|
|
554
692
|
}
|
|
555
693
|
const lockRequestedAgents = shouldImportAgents
|
|
556
|
-
?
|
|
694
|
+
? shouldMergeAgentOnlyEntry
|
|
695
|
+
? uniqueStrings([
|
|
696
|
+
...(existingEntry?.requestedAgents ?? []),
|
|
697
|
+
...(selection.requestedAgentsForLock ?? []),
|
|
698
|
+
])
|
|
699
|
+
: selection.requestedAgentsForLock
|
|
557
700
|
: existingEntry?.requestedAgents;
|
|
558
701
|
const trackedEntities = computeTrackedEntitiesForLock({
|
|
559
702
|
requestedAgents: lockRequestedAgents,
|
|
@@ -1305,18 +1448,171 @@ function findMatchingLockEntry(entries, key) {
|
|
|
1305
1448
|
wildcardWhenRightIsUndefined: true,
|
|
1306
1449
|
}));
|
|
1307
1450
|
}
|
|
1308
|
-
function
|
|
1451
|
+
function findRelaxedEntityEntries(entries, key) {
|
|
1309
1452
|
const matches = entries.filter((entry) => entry.source === key.source &&
|
|
1310
1453
|
entry.sourceType === key.sourceType &&
|
|
1311
1454
|
entry.subdir === key.subdir &&
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1455
|
+
(key.entity === "agent" ||
|
|
1456
|
+
sameRequestedAgentsForMatch(entry.requestedAgents, key.requestedAgents)));
|
|
1457
|
+
if (matches.length <= 1)
|
|
1458
|
+
return matches;
|
|
1459
|
+
return [...matches].sort((left, right) => {
|
|
1460
|
+
const leftMixed = isMixedEntryForEntity(left, key.entity) ? 1 : 0;
|
|
1461
|
+
const rightMixed = isMixedEntryForEntity(right, key.entity) ? 1 : 0;
|
|
1462
|
+
if (leftMixed !== rightMixed) {
|
|
1463
|
+
return rightMixed - leftMixed;
|
|
1464
|
+
}
|
|
1465
|
+
const leftScore = scoreEntryForEntity(left, key.entity);
|
|
1466
|
+
const rightScore = scoreEntryForEntity(right, key.entity);
|
|
1467
|
+
if (leftScore !== rightScore) {
|
|
1468
|
+
return rightScore - leftScore;
|
|
1469
|
+
}
|
|
1470
|
+
return 0;
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
function isMixedEntryForEntity(entry, entity) {
|
|
1474
|
+
return ((entity !== "agent" &&
|
|
1475
|
+
(entry.importedAgents.length > 0 ||
|
|
1476
|
+
(entry.requestedAgents?.length ?? 0) > 0)) ||
|
|
1477
|
+
(entity !== "command" &&
|
|
1478
|
+
(entry.importedCommands.length > 0 ||
|
|
1479
|
+
(entry.selectedSourceCommands?.length ?? 0) > 0 ||
|
|
1480
|
+
Object.keys(entry.commandRenameMap ?? {}).length > 0)) ||
|
|
1481
|
+
(entity !== "mcp" &&
|
|
1482
|
+
(entry.importedMcpServers.length > 0 ||
|
|
1483
|
+
(entry.selectedSourceMcpServers?.length ?? 0) > 0)) ||
|
|
1484
|
+
(entity !== "rule" &&
|
|
1485
|
+
(entry.importedRules.length > 0 ||
|
|
1486
|
+
(entry.selectedSourceRules?.length ?? 0) > 0 ||
|
|
1487
|
+
Object.keys(entry.ruleRenameMap ?? {}).length > 0)) ||
|
|
1488
|
+
(entity !== "skill" &&
|
|
1489
|
+
(entry.importedSkills.length > 0 ||
|
|
1490
|
+
(entry.selectedSourceSkills?.length ?? 0) > 0 ||
|
|
1491
|
+
(entry.skillsProviders?.length ?? 0) > 0 ||
|
|
1492
|
+
Object.keys(entry.skillRenameMap ?? {}).length > 0)));
|
|
1493
|
+
}
|
|
1494
|
+
function scoreEntryForEntity(entry, entity) {
|
|
1495
|
+
if (entity === "agent") {
|
|
1496
|
+
return (entry.importedAgents.length * 100 + (entry.requestedAgents?.length ?? 0));
|
|
1497
|
+
}
|
|
1498
|
+
if (entity === "command") {
|
|
1499
|
+
return (entry.importedCommands.length * 100 +
|
|
1500
|
+
(entry.selectedSourceCommands?.length ?? 0) * 10 +
|
|
1501
|
+
Object.keys(entry.commandRenameMap ?? {}).length);
|
|
1502
|
+
}
|
|
1503
|
+
if (entity === "mcp") {
|
|
1504
|
+
return (entry.importedMcpServers.length * 100 +
|
|
1505
|
+
(entry.selectedSourceMcpServers?.length ?? 0) * 10);
|
|
1506
|
+
}
|
|
1507
|
+
if (entity === "rule") {
|
|
1508
|
+
return (entry.importedRules.length * 100 +
|
|
1509
|
+
(entry.selectedSourceRules?.length ?? 0) * 10 +
|
|
1510
|
+
Object.keys(entry.ruleRenameMap ?? {}).length);
|
|
1511
|
+
}
|
|
1512
|
+
return (entry.importedSkills.length * 100 +
|
|
1513
|
+
(entry.selectedSourceSkills?.length ?? 0) * 10 +
|
|
1514
|
+
(entry.skillsProviders?.length ?? 0) * 3 +
|
|
1515
|
+
Object.keys(entry.skillRenameMap ?? {}).length);
|
|
1516
|
+
}
|
|
1517
|
+
function mergeRelaxedEntityEntriesForLock(options) {
|
|
1518
|
+
if (options.redundantEntries.length === 0) {
|
|
1519
|
+
return options.canonicalEntry;
|
|
1520
|
+
}
|
|
1521
|
+
let mergedEntry = {
|
|
1522
|
+
...options.canonicalEntry,
|
|
1523
|
+
};
|
|
1524
|
+
if (options.entity === "agent") {
|
|
1525
|
+
const mergedRequestedAgents = uniqueStrings([
|
|
1526
|
+
...(mergedEntry.requestedAgents ?? []),
|
|
1527
|
+
...options.redundantEntries.flatMap((entry) => entry.requestedAgents ?? []),
|
|
1528
|
+
]);
|
|
1529
|
+
mergedEntry = {
|
|
1530
|
+
...mergedEntry,
|
|
1531
|
+
importedAgents: uniqueStrings([
|
|
1532
|
+
...mergedEntry.importedAgents,
|
|
1533
|
+
...options.redundantEntries.flatMap((entry) => entry.importedAgents),
|
|
1534
|
+
]),
|
|
1535
|
+
requestedAgents: mergedRequestedAgents.length > 0 ? mergedRequestedAgents : undefined,
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
else if (options.entity === "command") {
|
|
1539
|
+
const mergedSelectedSourceCommands = uniqueStrings([
|
|
1540
|
+
...(mergedEntry.selectedSourceCommands ?? []),
|
|
1541
|
+
...options.redundantEntries.flatMap((entry) => entry.selectedSourceCommands ?? []),
|
|
1542
|
+
]);
|
|
1543
|
+
const mergedCommandRenameMap = Object.assign({}, mergedEntry.commandRenameMap ?? {}, ...options.redundantEntries.map((entry) => entry.commandRenameMap ?? {}));
|
|
1544
|
+
mergedEntry = {
|
|
1545
|
+
...mergedEntry,
|
|
1546
|
+
importedCommands: uniqueStrings([
|
|
1547
|
+
...mergedEntry.importedCommands,
|
|
1548
|
+
...options.redundantEntries.flatMap((entry) => entry.importedCommands),
|
|
1549
|
+
]),
|
|
1550
|
+
selectedSourceCommands: mergedSelectedSourceCommands.length > 0
|
|
1551
|
+
? mergedSelectedSourceCommands
|
|
1552
|
+
: undefined,
|
|
1553
|
+
commandRenameMap: normalizeCommandRenameMap(mergedCommandRenameMap),
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
else if (options.entity === "mcp") {
|
|
1557
|
+
const mergedSelectedSourceMcpServers = uniqueStrings([
|
|
1558
|
+
...(mergedEntry.selectedSourceMcpServers ?? []),
|
|
1559
|
+
...options.redundantEntries.flatMap((entry) => entry.selectedSourceMcpServers ?? []),
|
|
1560
|
+
]);
|
|
1561
|
+
mergedEntry = {
|
|
1562
|
+
...mergedEntry,
|
|
1563
|
+
importedMcpServers: uniqueStrings([
|
|
1564
|
+
...mergedEntry.importedMcpServers,
|
|
1565
|
+
...options.redundantEntries.flatMap((entry) => entry.importedMcpServers),
|
|
1566
|
+
]),
|
|
1567
|
+
selectedSourceMcpServers: mergedSelectedSourceMcpServers.length > 0
|
|
1568
|
+
? mergedSelectedSourceMcpServers
|
|
1569
|
+
: undefined,
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
else if (options.entity === "rule") {
|
|
1573
|
+
const mergedSelectedSourceRules = uniqueStrings([
|
|
1574
|
+
...(mergedEntry.selectedSourceRules ?? []),
|
|
1575
|
+
...options.redundantEntries.flatMap((entry) => entry.selectedSourceRules ?? []),
|
|
1576
|
+
]);
|
|
1577
|
+
const mergedRuleRenameMap = Object.assign({}, mergedEntry.ruleRenameMap ?? {}, ...options.redundantEntries.map((entry) => entry.ruleRenameMap ?? {}));
|
|
1578
|
+
mergedEntry = {
|
|
1579
|
+
...mergedEntry,
|
|
1580
|
+
importedRules: uniqueStrings([
|
|
1581
|
+
...mergedEntry.importedRules,
|
|
1582
|
+
...options.redundantEntries.flatMap((entry) => entry.importedRules),
|
|
1583
|
+
]),
|
|
1584
|
+
selectedSourceRules: mergedSelectedSourceRules.length > 0
|
|
1585
|
+
? mergedSelectedSourceRules
|
|
1586
|
+
: undefined,
|
|
1587
|
+
ruleRenameMap: normalizeRuleRenameMap(mergedRuleRenameMap),
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
else {
|
|
1591
|
+
const mergedSelectedSourceSkills = uniqueStrings([
|
|
1592
|
+
...(mergedEntry.selectedSourceSkills ?? []),
|
|
1593
|
+
...options.redundantEntries.flatMap((entry) => entry.selectedSourceSkills ?? []),
|
|
1594
|
+
]);
|
|
1595
|
+
const mergedSkillsProviders = normalizeSkillsProviders([
|
|
1596
|
+
...(mergedEntry.skillsProviders ?? []),
|
|
1597
|
+
...options.redundantEntries.flatMap((entry) => entry.skillsProviders ?? []),
|
|
1598
|
+
]);
|
|
1599
|
+
const mergedSkillRenameMap = Object.assign({}, mergedEntry.skillRenameMap ?? {}, ...options.redundantEntries.map((entry) => entry.skillRenameMap ?? {}));
|
|
1600
|
+
mergedEntry = {
|
|
1601
|
+
...mergedEntry,
|
|
1602
|
+
importedSkills: uniqueStrings([
|
|
1603
|
+
...mergedEntry.importedSkills,
|
|
1604
|
+
...options.redundantEntries.flatMap((entry) => entry.importedSkills),
|
|
1605
|
+
]),
|
|
1606
|
+
selectedSourceSkills: mergedSelectedSourceSkills.length > 0
|
|
1607
|
+
? mergedSelectedSourceSkills
|
|
1608
|
+
: undefined,
|
|
1609
|
+
skillsProviders: mergedSkillsProviders && mergedSkillsProviders.length > 0
|
|
1610
|
+
? mergedSkillsProviders
|
|
1611
|
+
: undefined,
|
|
1612
|
+
skillRenameMap: normalizeSkillRenameMap(mergedSkillRenameMap),
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
return mergedEntry;
|
|
1320
1616
|
}
|
|
1321
1617
|
function sameRequestedAgentsForMatch(left, right) {
|
|
1322
1618
|
const normalizedLeft = normalizeRequestedAgentsForMatch(left);
|
|
@@ -1421,6 +1717,13 @@ function mergeCommandRenameMaps(existing, updates) {
|
|
|
1421
1717
|
};
|
|
1422
1718
|
return normalizeCommandRenameMap(merged);
|
|
1423
1719
|
}
|
|
1720
|
+
function mergeRuleRenameMaps(existing, updates) {
|
|
1721
|
+
const merged = {
|
|
1722
|
+
...(existing ?? {}),
|
|
1723
|
+
...(updates ?? {}),
|
|
1724
|
+
};
|
|
1725
|
+
return normalizeRuleRenameMap(merged);
|
|
1726
|
+
}
|
|
1424
1727
|
function resolveMappedTargetFileName(sourceFileName, renameMap) {
|
|
1425
1728
|
if (!renameMap)
|
|
1426
1729
|
return undefined;
|
|
@@ -1495,6 +1798,53 @@ function normalizeSkillRenameMap(renameMap) {
|
|
|
1495
1798
|
return undefined;
|
|
1496
1799
|
return Object.fromEntries(normalizedEntries);
|
|
1497
1800
|
}
|
|
1801
|
+
function mergeSkillRenameMaps(existing, updates) {
|
|
1802
|
+
const merged = {
|
|
1803
|
+
...(existing ?? {}),
|
|
1804
|
+
...(updates ?? {}),
|
|
1805
|
+
};
|
|
1806
|
+
return normalizeSkillRenameMap(merged);
|
|
1807
|
+
}
|
|
1808
|
+
function mergeImportedSkills(options) {
|
|
1809
|
+
if (!options.existingImportedSkills ||
|
|
1810
|
+
options.existingImportedSkills.length === 0) {
|
|
1811
|
+
return [...options.importedSkills];
|
|
1812
|
+
}
|
|
1813
|
+
const coveredSelectors = new Set();
|
|
1814
|
+
for (const skill of options.selectedSkills) {
|
|
1815
|
+
const byName = normalizeSkillSelector(skill.name);
|
|
1816
|
+
if (byName)
|
|
1817
|
+
coveredSelectors.add(byName);
|
|
1818
|
+
const bySourceDir = normalizeSkillSelector(skill.sourceDirName);
|
|
1819
|
+
if (bySourceDir)
|
|
1820
|
+
coveredSelectors.add(bySourceDir);
|
|
1821
|
+
}
|
|
1822
|
+
if (coveredSelectors.size === 0) {
|
|
1823
|
+
return [...options.importedSkills];
|
|
1824
|
+
}
|
|
1825
|
+
const selectedImportedTargets = new Set();
|
|
1826
|
+
for (const [sourceSelector, importedName] of Object.entries(options.existingSkillRenameMap ?? {})) {
|
|
1827
|
+
const normalizedSourceSelector = normalizeSkillSelector(sourceSelector);
|
|
1828
|
+
const normalizedImportedName = normalizeSkillSelector(importedName);
|
|
1829
|
+
if (!normalizedSourceSelector ||
|
|
1830
|
+
!normalizedImportedName ||
|
|
1831
|
+
!coveredSelectors.has(normalizedSourceSelector)) {
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
selectedImportedTargets.add(normalizedImportedName);
|
|
1835
|
+
}
|
|
1836
|
+
const retained = options.existingImportedSkills.filter((importedSkillName) => {
|
|
1837
|
+
const selector = normalizeSkillSelector(importedSkillName);
|
|
1838
|
+
if (!selector)
|
|
1839
|
+
return true;
|
|
1840
|
+
if (coveredSelectors.has(selector))
|
|
1841
|
+
return false;
|
|
1842
|
+
if (selectedImportedTargets.has(selector))
|
|
1843
|
+
return false;
|
|
1844
|
+
return true;
|
|
1845
|
+
});
|
|
1846
|
+
return uniqueStrings([...retained, ...options.importedSkills]);
|
|
1847
|
+
}
|
|
1498
1848
|
function resolveMappedTargetSkillName(sourceSkill, selectedSkills, renameMap) {
|
|
1499
1849
|
if (!renameMap)
|
|
1500
1850
|
return undefined;
|
package/dist/core/migration.js
CHANGED
|
@@ -914,6 +914,15 @@ async function migrateMcp(options, summary) {
|
|
|
914
914
|
writeCanonicalMcp(options.paths, merged);
|
|
915
915
|
}
|
|
916
916
|
}
|
|
917
|
+
const MANAGED_MCP_CANONICAL_KEYS_BY_PROVIDER = {
|
|
918
|
+
cursor: ["url", "command", "args", "env"],
|
|
919
|
+
claude: ["type", "url", "command", "args", "env"],
|
|
920
|
+
codex: ["url", "command", "args", "env"],
|
|
921
|
+
opencode: ["url", "command", "args", "env"],
|
|
922
|
+
gemini: ["url", "command", "args", "env"],
|
|
923
|
+
copilot: ["type", "url", "command", "args", "env", "tools"],
|
|
924
|
+
pi: ["url", "command", "args", "env"],
|
|
925
|
+
};
|
|
917
926
|
function collectProviderMcpServers(paths, providers) {
|
|
918
927
|
let detected = 0;
|
|
919
928
|
const servers = new Map();
|
|
@@ -930,13 +939,13 @@ function collectProviderMcpServers(paths, providers) {
|
|
|
930
939
|
}
|
|
931
940
|
function readProviderMcp(paths, provider) {
|
|
932
941
|
if (provider === "cursor") {
|
|
933
|
-
return readJsonMcpServers(getCursorMcpPath(paths));
|
|
942
|
+
return readJsonMcpServers(getCursorMcpPath(paths), provider);
|
|
934
943
|
}
|
|
935
944
|
if (provider === "claude") {
|
|
936
|
-
return readJsonMcpServers(getClaudeMcpPath(paths));
|
|
945
|
+
return readJsonMcpServers(getClaudeMcpPath(paths), provider);
|
|
937
946
|
}
|
|
938
947
|
if (provider === "copilot") {
|
|
939
|
-
return readJsonMcpServers(getCopilotMcpPath(paths));
|
|
948
|
+
return readJsonMcpServers(getCopilotMcpPath(paths), provider);
|
|
940
949
|
}
|
|
941
950
|
if (provider === "opencode") {
|
|
942
951
|
return readOpenCodeMcp(getOpenCodeConfigPath(paths));
|
|
@@ -948,16 +957,16 @@ function readProviderMcp(paths, provider) {
|
|
|
948
957
|
return readCodexMcp(getCodexConfigPath(paths));
|
|
949
958
|
}
|
|
950
959
|
if (provider === "pi") {
|
|
951
|
-
return readJsonMcpServers(getPiMcpPath(paths));
|
|
960
|
+
return readJsonMcpServers(getPiMcpPath(paths), provider);
|
|
952
961
|
}
|
|
953
962
|
return {};
|
|
954
963
|
}
|
|
955
|
-
function readJsonMcpServers(filePath) {
|
|
964
|
+
function readJsonMcpServers(filePath, provider) {
|
|
956
965
|
const parsed = readJsonIfExists(filePath);
|
|
957
966
|
if (!parsed || !isObject(parsed.mcpServers)) {
|
|
958
967
|
return {};
|
|
959
968
|
}
|
|
960
|
-
return
|
|
969
|
+
return normalizeManagedMcpServerRecord(parsed.mcpServers, MANAGED_MCP_CANONICAL_KEYS_BY_PROVIDER[provider]);
|
|
961
970
|
}
|
|
962
971
|
function readOpenCodeMcp(filePath) {
|
|
963
972
|
const parsed = readJsonIfExists(filePath);
|
|
@@ -1013,17 +1022,31 @@ function readCodexMcp(filePath) {
|
|
|
1013
1022
|
const parsed = raw.trim() ? TOML.parse(raw) : {};
|
|
1014
1023
|
if (!isObject(parsed.mcp_servers))
|
|
1015
1024
|
return {};
|
|
1016
|
-
return
|
|
1025
|
+
return normalizeManagedMcpServerRecord(parsed.mcp_servers, MANAGED_MCP_CANONICAL_KEYS_BY_PROVIDER.codex);
|
|
1017
1026
|
}
|
|
1018
|
-
function
|
|
1027
|
+
function normalizeManagedMcpServerRecord(raw, allowedKeys) {
|
|
1019
1028
|
const servers = {};
|
|
1020
1029
|
for (const [name, config] of Object.entries(raw)) {
|
|
1021
|
-
|
|
1030
|
+
const normalized = pickManagedMcpFields(config, allowedKeys);
|
|
1031
|
+
if (!normalized)
|
|
1022
1032
|
continue;
|
|
1023
|
-
servers[name] =
|
|
1033
|
+
servers[name] = normalized;
|
|
1024
1034
|
}
|
|
1025
1035
|
return servers;
|
|
1026
1036
|
}
|
|
1037
|
+
function pickManagedMcpFields(config, allowedKeys) {
|
|
1038
|
+
if (!isObject(config)) {
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
const next = {};
|
|
1042
|
+
for (const key of allowedKeys) {
|
|
1043
|
+
const value = config[key];
|
|
1044
|
+
if (value !== undefined) {
|
|
1045
|
+
next[key] = cloneRecord(value);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return Object.keys(next).length > 0 ? next : null;
|
|
1049
|
+
}
|
|
1027
1050
|
function normalizeCanonicalServer(server) {
|
|
1028
1051
|
if (!server) {
|
|
1029
1052
|
return {
|
package/dist/core/router.js
CHANGED
|
@@ -21,6 +21,9 @@ const ENTITY_NOUN_ALIASES = {
|
|
|
21
21
|
rules: "rule",
|
|
22
22
|
skills: "skill",
|
|
23
23
|
};
|
|
24
|
+
const VERB_ALIASES = {
|
|
25
|
+
remove: "delete",
|
|
26
|
+
};
|
|
24
27
|
const ENTITY_VERBS = new Set([
|
|
25
28
|
"add",
|
|
26
29
|
"list",
|
|
@@ -34,7 +37,7 @@ export function parseCommandRoute(argv) {
|
|
|
34
37
|
const rawRoot = argv[0]?.trim().toLowerCase();
|
|
35
38
|
if (!rawRoot)
|
|
36
39
|
return null;
|
|
37
|
-
const root = ENTITY_NOUN_ALIASES[rawRoot] ?? rawRoot;
|
|
40
|
+
const root = ENTITY_NOUN_ALIASES[rawRoot] ?? VERB_ALIASES[rawRoot] ?? rawRoot;
|
|
38
41
|
if (AGGREGATE_VERBS.has(root)) {
|
|
39
42
|
return {
|
|
40
43
|
mode: "aggregate",
|
|
@@ -44,7 +47,8 @@ export function parseCommandRoute(argv) {
|
|
|
44
47
|
if (!ENTITY_NOUNS.has(root)) {
|
|
45
48
|
return null;
|
|
46
49
|
}
|
|
47
|
-
const
|
|
50
|
+
const rawAction = argv[1]?.trim().toLowerCase();
|
|
51
|
+
const action = rawAction ? (VERB_ALIASES[rawAction] ?? rawAction) : rawAction;
|
|
48
52
|
if (!action || action === "--help" || action === "-h" || action === "help") {
|
|
49
53
|
return {
|
|
50
54
|
mode: "entity",
|
|
@@ -53,7 +57,10 @@ export function parseCommandRoute(argv) {
|
|
|
53
57
|
};
|
|
54
58
|
}
|
|
55
59
|
if (root === "mcp" && action === "server") {
|
|
56
|
-
const
|
|
60
|
+
const rawServerVerb = argv[2]?.trim().toLowerCase();
|
|
61
|
+
const serverVerb = rawServerVerb
|
|
62
|
+
? (VERB_ALIASES[rawServerVerb] ?? rawServerVerb)
|
|
63
|
+
: rawServerVerb;
|
|
57
64
|
if (!serverVerb ||
|
|
58
65
|
serverVerb === "--help" ||
|
|
59
66
|
serverVerb === "-h" ||
|
package/dist/sync/index.js
CHANGED
|
@@ -361,8 +361,10 @@ function syncProviderMcp(options) {
|
|
|
361
361
|
const resolved = resolveMcpForProvider(options.mcp, provider);
|
|
362
362
|
if (provider === "cursor") {
|
|
363
363
|
const outputPath = getCursorMcpPath(options.paths);
|
|
364
|
+
const existing = readJsonIfExists(outputPath) ?? {};
|
|
364
365
|
const payload = {
|
|
365
|
-
|
|
366
|
+
...existing,
|
|
367
|
+
mcpServers: mergeManagedMcpServerEntries(existing.mcpServers, mapMcpServers(resolved, ["url", "command", "args", "env"]), ["url", "command", "args", "env"]),
|
|
366
368
|
};
|
|
367
369
|
maybeWriteJson(outputPath, payload, options.dryRun);
|
|
368
370
|
options.generated.add(outputPath);
|
|
@@ -381,19 +383,24 @@ function syncProviderMcp(options) {
|
|
|
381
383
|
continue;
|
|
382
384
|
}
|
|
383
385
|
const mcpPath = getClaudeMcpPath(options.paths);
|
|
384
|
-
const
|
|
386
|
+
const existingMcp = readJsonIfExists(mcpPath) ?? {};
|
|
387
|
+
const managedClaudeServers = mapMcpServers(resolved, [
|
|
385
388
|
"type",
|
|
386
389
|
"url",
|
|
387
390
|
"command",
|
|
388
391
|
"args",
|
|
389
392
|
"env",
|
|
390
393
|
]);
|
|
391
|
-
for (const [serverName, config] of Object.entries(
|
|
394
|
+
for (const [serverName, config] of Object.entries(managedClaudeServers)) {
|
|
392
395
|
if (!("type" in config) && typeof config.url === "string") {
|
|
393
396
|
config.type = "http";
|
|
394
397
|
}
|
|
395
398
|
}
|
|
396
|
-
|
|
399
|
+
const claudeServers = mergeManagedMcpServerEntries(existingMcp.mcpServers, managedClaudeServers, ["type", "url", "command", "args", "env"]);
|
|
400
|
+
maybeWriteJson(mcpPath, {
|
|
401
|
+
...existingMcp,
|
|
402
|
+
mcpServers: claudeServers,
|
|
403
|
+
}, options.dryRun);
|
|
397
404
|
options.generated.add(mcpPath);
|
|
398
405
|
settings.enabledMcpjsonServers = Object.keys(claudeServers).sort();
|
|
399
406
|
maybeWriteJson(settingsPath, settings, options.dryRun);
|
|
@@ -403,28 +410,34 @@ function syncProviderMcp(options) {
|
|
|
403
410
|
if (provider === "opencode") {
|
|
404
411
|
const outputPath = getOpenCodeConfigPath(options.paths);
|
|
405
412
|
const existing = readJsonIfExists(outputPath) ?? {};
|
|
406
|
-
const
|
|
413
|
+
const managedMcp = {};
|
|
407
414
|
for (const [serverName, config] of Object.entries(resolved)) {
|
|
408
415
|
if (typeof config.url === "string") {
|
|
409
|
-
|
|
416
|
+
managedMcp[serverName] = {
|
|
410
417
|
type: "remote",
|
|
411
418
|
url: config.url,
|
|
412
419
|
};
|
|
413
420
|
}
|
|
414
421
|
else {
|
|
415
|
-
|
|
422
|
+
managedMcp[serverName] = {
|
|
416
423
|
type: "local",
|
|
417
424
|
command: config.command,
|
|
418
425
|
args: Array.isArray(config.args) ? config.args : undefined,
|
|
419
426
|
};
|
|
420
427
|
}
|
|
421
428
|
if (isObject(config.env)) {
|
|
422
|
-
|
|
429
|
+
managedMcp[serverName].environment = config.env;
|
|
423
430
|
}
|
|
424
431
|
}
|
|
425
432
|
const payload = {
|
|
426
433
|
...existing,
|
|
427
|
-
mcp,
|
|
434
|
+
mcp: mergeManagedMcpServerEntries(existing.mcp, managedMcp, [
|
|
435
|
+
"type",
|
|
436
|
+
"url",
|
|
437
|
+
"command",
|
|
438
|
+
"args",
|
|
439
|
+
"environment",
|
|
440
|
+
]),
|
|
428
441
|
};
|
|
429
442
|
maybeWriteJson(outputPath, payload, options.dryRun);
|
|
430
443
|
options.generated.add(outputPath);
|
|
@@ -453,7 +466,7 @@ function syncProviderMcp(options) {
|
|
|
453
466
|
const payload = {
|
|
454
467
|
...existing,
|
|
455
468
|
experimental,
|
|
456
|
-
mcpServers,
|
|
469
|
+
mcpServers: mergeManagedMcpServerEntries(existing.mcpServers, mcpServers, ["httpUrl", "command", "args", "env"]),
|
|
457
470
|
};
|
|
458
471
|
maybeWriteJson(outputPath, payload, options.dryRun);
|
|
459
472
|
options.generated.add(outputPath);
|
|
@@ -461,6 +474,8 @@ function syncProviderMcp(options) {
|
|
|
461
474
|
}
|
|
462
475
|
if (provider === "copilot") {
|
|
463
476
|
const profileMcpPath = getCopilotMcpPath(options.paths);
|
|
477
|
+
const managedKeys = ["type", "url", "command", "args", "env", "tools"];
|
|
478
|
+
const existingProfileMcp = readJsonIfExists(profileMcpPath) ?? {};
|
|
464
479
|
const copilotServers = mapMcpServers(resolved, [
|
|
465
480
|
"type",
|
|
466
481
|
"url",
|
|
@@ -477,20 +492,25 @@ function syncProviderMcp(options) {
|
|
|
477
492
|
config.type = config.url ? "http" : "local";
|
|
478
493
|
}
|
|
479
494
|
}
|
|
480
|
-
maybeWriteJson(profileMcpPath, {
|
|
495
|
+
maybeWriteJson(profileMcpPath, {
|
|
496
|
+
...existingProfileMcp,
|
|
497
|
+
mcpServers: mergeManagedMcpServerEntries(existingProfileMcp.mcpServers, copilotServers, managedKeys),
|
|
498
|
+
}, options.dryRun);
|
|
481
499
|
options.generated.add(profileMcpPath);
|
|
482
500
|
if (options.paths.scope === "global") {
|
|
483
501
|
const settingsPath = getVsCodeSettingsPath(options.paths.homeDir);
|
|
484
502
|
const settings = readJsonIfExists(settingsPath) ?? {};
|
|
485
|
-
settings["mcp.servers"] = copilotServers;
|
|
503
|
+
settings["mcp.servers"] = mergeManagedMcpServerEntries(settings["mcp.servers"], copilotServers, managedKeys);
|
|
486
504
|
maybeWriteJson(settingsPath, settings, options.dryRun);
|
|
487
505
|
options.generated.add(settingsPath);
|
|
488
506
|
}
|
|
489
507
|
}
|
|
490
508
|
if (provider === "pi") {
|
|
491
509
|
const outputPath = getPiMcpPath(options.paths);
|
|
510
|
+
const existing = readJsonIfExists(outputPath) ?? {};
|
|
492
511
|
const payload = {
|
|
493
|
-
|
|
512
|
+
...existing,
|
|
513
|
+
mcpServers: mergeManagedMcpServerEntries(existing.mcpServers, mapMcpServers(resolved, ["url", "command", "args", "env"]), ["url", "command", "args", "env"]),
|
|
494
514
|
};
|
|
495
515
|
maybeWriteJson(outputPath, payload, options.dryRun);
|
|
496
516
|
options.generated.add(outputPath);
|
|
@@ -813,22 +833,19 @@ function syncCodex(options) {
|
|
|
813
833
|
let nextServers = [...trackedServers];
|
|
814
834
|
if (options.includeMcp) {
|
|
815
835
|
const previousServers = new Set(trackedServers);
|
|
836
|
+
const managedCodexMcpServers = mapMcpServers(options.resolvedMcp, [
|
|
837
|
+
"url",
|
|
838
|
+
"command",
|
|
839
|
+
"args",
|
|
840
|
+
"env",
|
|
841
|
+
]);
|
|
816
842
|
for (const oldServer of previousServers) {
|
|
817
843
|
if (!Object.prototype.hasOwnProperty.call(options.resolvedMcp, oldServer)) {
|
|
818
844
|
delete mcpServers[oldServer];
|
|
819
845
|
}
|
|
820
846
|
}
|
|
821
|
-
for (const [serverName, config] of Object.entries(
|
|
822
|
-
|
|
823
|
-
if (typeof config.url === "string")
|
|
824
|
-
mapped.url = config.url;
|
|
825
|
-
if (typeof config.command === "string")
|
|
826
|
-
mapped.command = config.command;
|
|
827
|
-
if (Array.isArray(config.args))
|
|
828
|
-
mapped.args = config.args;
|
|
829
|
-
if (isObject(config.env))
|
|
830
|
-
mapped.env = config.env;
|
|
831
|
-
mcpServers[serverName] = mapped;
|
|
847
|
+
for (const [serverName, config] of Object.entries(managedCodexMcpServers)) {
|
|
848
|
+
mcpServers[serverName] = mergeManagedMcpServerEntry(mcpServers[serverName], config, ["url", "command", "args", "env"]);
|
|
832
849
|
}
|
|
833
850
|
parsed.mcp_servers = mcpServers;
|
|
834
851
|
nextServers = Object.keys(options.resolvedMcp).sort();
|
|
@@ -895,6 +912,28 @@ function mapMcpServers(servers, allowedKeys) {
|
|
|
895
912
|
}
|
|
896
913
|
return mapped;
|
|
897
914
|
}
|
|
915
|
+
function mergeManagedMcpServerEntries(existingServers, nextServers, managedKeys) {
|
|
916
|
+
const merged = {};
|
|
917
|
+
for (const [serverName, config] of Object.entries(nextServers)) {
|
|
918
|
+
merged[serverName] = mergeManagedMcpServerEntry(isObject(existingServers) ? existingServers[serverName] : undefined, config, managedKeys);
|
|
919
|
+
}
|
|
920
|
+
return merged;
|
|
921
|
+
}
|
|
922
|
+
function mergeManagedMcpServerEntry(existingConfig, nextConfig, managedKeys) {
|
|
923
|
+
const merged = isObject(existingConfig) ? cloneSyncValue(existingConfig) : {};
|
|
924
|
+
for (const key of managedKeys) {
|
|
925
|
+
delete merged[key];
|
|
926
|
+
}
|
|
927
|
+
for (const [key, value] of Object.entries(nextConfig)) {
|
|
928
|
+
if (value !== undefined) {
|
|
929
|
+
merged[key] = cloneSyncValue(value);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return merged;
|
|
933
|
+
}
|
|
934
|
+
function cloneSyncValue(value) {
|
|
935
|
+
return JSON.parse(JSON.stringify(value));
|
|
936
|
+
}
|
|
898
937
|
function maybeWriteJson(filePath, payload, dryRun) {
|
|
899
938
|
if (dryRun)
|
|
900
939
|
return;
|