agentloom 0.1.0 → 0.1.2
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 +91 -72
- package/bin/cli.mjs +3 -2
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +149 -17
- package/dist/commands/add.d.ts +7 -0
- package/dist/commands/add.js +122 -31
- package/dist/commands/agent.d.ts +2 -0
- package/dist/commands/agent.js +85 -0
- package/dist/commands/command.d.ts +2 -0
- package/dist/commands/command.js +98 -0
- package/dist/commands/delete.d.ts +9 -0
- package/dist/commands/delete.js +444 -0
- package/dist/commands/entity-utils.d.ts +13 -0
- package/dist/commands/entity-utils.js +58 -0
- package/dist/commands/find.d.ts +21 -0
- package/dist/commands/find.js +944 -0
- package/dist/commands/mcp.js +133 -55
- package/dist/commands/skills.d.ts +2 -1
- package/dist/commands/skills.js +105 -9
- package/dist/commands/sync.d.ts +6 -0
- package/dist/commands/sync.js +12 -10
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.js +286 -21
- package/dist/core/argv.d.ts +2 -1
- package/dist/core/argv.js +42 -2
- package/dist/core/commands.d.ts +13 -0
- package/dist/core/commands.js +65 -0
- package/dist/core/copy.d.ts +6 -0
- package/dist/core/copy.js +126 -65
- package/dist/core/importer.d.ts +28 -1
- package/dist/core/importer.js +1104 -41
- package/dist/core/lockfile.js +86 -3
- package/dist/core/manage-agents-bootstrap.d.ts +10 -0
- package/dist/core/manage-agents-bootstrap.js +40 -0
- package/dist/core/manifest.js +7 -1
- package/dist/core/router.d.ts +16 -0
- package/dist/core/router.js +66 -0
- package/dist/core/scope.d.ts +1 -1
- package/dist/core/scope.js +12 -8
- package/dist/core/settings.d.ts +4 -3
- package/dist/core/settings.js +10 -8
- package/dist/core/skills.d.ts +23 -0
- package/dist/core/skills.js +328 -0
- package/dist/core/sources.d.ts +3 -1
- package/dist/core/sources.js +31 -1
- package/dist/core/telemetry.d.ts +26 -0
- package/dist/core/telemetry.js +124 -0
- package/dist/core/version-notifier.d.ts +1 -0
- package/dist/core/version-notifier.js +22 -4
- package/dist/sync/index.d.ts +7 -1
- package/dist/sync/index.js +395 -131
- package/dist/types.d.ts +16 -1
- package/package.json +5 -4
package/dist/core/importer.js
CHANGED
|
@@ -1,90 +1,491 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { cancel, isCancel, select, text as promptText } from "@clack/prompts";
|
|
3
|
+
import { cancel, isCancel, multiselect, select, text as promptText, } from "@clack/prompts";
|
|
4
4
|
import { buildAgentMarkdown, parseAgentsDir, targetFileNameForAgent, } from "./agents.js";
|
|
5
|
+
import { normalizeCommandSelector, parseCommandsDir, resolveCommandSelections, } from "./commands.js";
|
|
6
|
+
import { applySkillProviderSideEffects, copySkillArtifacts, normalizeSkillSelector, parseSkillsDir, resolveSkillSelections, skillContentMatchesTarget, } from "./skills.js";
|
|
5
7
|
import { ensureDir, hashContent, readJsonIfExists, relativePosix, slugify, writeTextAtomic, } from "./fs.js";
|
|
8
|
+
import { ALL_PROVIDERS } from "../types.js";
|
|
6
9
|
import { readLockfile, upsertLockEntry, writeLockfile } from "./lockfile.js";
|
|
7
10
|
import { readCanonicalMcp, writeCanonicalMcp } from "./mcp.js";
|
|
8
|
-
import { discoverSourceAgentsDir, discoverSourceMcpPath, prepareSource, } from "./sources.js";
|
|
11
|
+
import { discoverSourceAgentsDir, discoverSourceCommandsDir, discoverSourceMcpPath, discoverSourceSkillsDir, prepareSource, } from "./sources.js";
|
|
9
12
|
export class NonInteractiveConflictError extends Error {
|
|
10
13
|
constructor(message) {
|
|
11
14
|
super(message);
|
|
12
15
|
this.name = "NonInteractiveConflictError";
|
|
13
16
|
}
|
|
14
17
|
}
|
|
18
|
+
const MULTISELECT_HELP_TEXT = "↑↓ move, space select, enter confirm";
|
|
19
|
+
function withMultiselectHelp(message) {
|
|
20
|
+
return `${message}\n${MULTISELECT_HELP_TEXT}`;
|
|
21
|
+
}
|
|
15
22
|
export async function importSource(options) {
|
|
23
|
+
const shouldImportAgents = options.importAgents ?? true;
|
|
24
|
+
const requireAgents = options.requireAgents ?? shouldImportAgents;
|
|
25
|
+
const shouldImportCommands = options.importCommands ?? true;
|
|
26
|
+
const shouldImportMcp = options.importMcp ?? true;
|
|
27
|
+
const shouldImportSkills = options.importSkills ?? false;
|
|
28
|
+
if (!shouldImportAgents &&
|
|
29
|
+
!shouldImportCommands &&
|
|
30
|
+
!shouldImportMcp &&
|
|
31
|
+
!shouldImportSkills) {
|
|
32
|
+
throw new Error("No import targets selected.");
|
|
33
|
+
}
|
|
16
34
|
const prepared = prepareSource({
|
|
17
35
|
source: options.source,
|
|
18
36
|
ref: options.ref,
|
|
19
37
|
subdir: options.subdir,
|
|
20
38
|
});
|
|
39
|
+
const normalizedSubdir = options.subdir?.replace(/^\/+|\/+$/g, "");
|
|
40
|
+
const sourceLocation = prepared.spec.type === "github"
|
|
41
|
+
? `https://github.com/${prepared.spec.source}/tree/${prepared.resolvedCommit}${normalizedSubdir ? `/${normalizedSubdir}` : ""}`
|
|
42
|
+
: options.subdir
|
|
43
|
+
? `${options.source} (subdir: ${options.subdir})`
|
|
44
|
+
: options.source;
|
|
21
45
|
try {
|
|
22
|
-
const sourceAgentsDir =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
46
|
+
const sourceAgentsDir = shouldImportAgents
|
|
47
|
+
? discoverSourceAgentsDir(prepared.importRoot)
|
|
48
|
+
: null;
|
|
49
|
+
const sourceCommandsDir = shouldImportCommands
|
|
50
|
+
? discoverSourceCommandsDir(prepared.importRoot)
|
|
51
|
+
: null;
|
|
52
|
+
const sourceMcpPath = shouldImportMcp
|
|
53
|
+
? discoverSourceMcpPath(prepared.importRoot)
|
|
54
|
+
: null;
|
|
55
|
+
const sourceSkillsDir = shouldImportSkills
|
|
56
|
+
? discoverSourceSkillsDir(prepared.importRoot)
|
|
57
|
+
: null;
|
|
58
|
+
const sourceAgents = sourceAgentsDir ? parseAgentsDir(sourceAgentsDir) : [];
|
|
59
|
+
const sourceCommands = sourceCommandsDir
|
|
60
|
+
? parseCommandsDir(sourceCommandsDir)
|
|
61
|
+
: [];
|
|
62
|
+
const sourceMcp = sourceMcpPath
|
|
63
|
+
? normalizeMcp(readJsonIfExists(sourceMcpPath))
|
|
64
|
+
: null;
|
|
65
|
+
const sourceSkills = sourceSkillsDir ? parseSkillsDir(sourceSkillsDir) : [];
|
|
66
|
+
const hasExplicitCommandSelection = (options.commandSelectors?.length ?? 0) > 0;
|
|
67
|
+
const isAggregateImport = shouldImportAgents &&
|
|
68
|
+
shouldImportCommands &&
|
|
69
|
+
shouldImportMcp &&
|
|
70
|
+
shouldImportSkills;
|
|
71
|
+
if (shouldImportAgents && requireAgents && !sourceAgentsDir) {
|
|
72
|
+
throw new Error(`No source agents directory found under ${prepared.importRoot} (expected agents/ or .agents/agents/).`);
|
|
73
|
+
}
|
|
74
|
+
if (shouldImportAgents && requireAgents && sourceAgents.length === 0) {
|
|
26
75
|
throw new Error(`No agent files found in ${sourceAgentsDir}.`);
|
|
27
76
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
77
|
+
if (shouldImportCommands && options.requireCommands && !sourceCommandsDir) {
|
|
78
|
+
throw new Error(`No source commands directory found under ${prepared.importRoot} (expected .agents/commands/, commands/, or prompts/).`);
|
|
79
|
+
}
|
|
80
|
+
if (shouldImportCommands &&
|
|
81
|
+
options.requireCommands &&
|
|
82
|
+
sourceCommands.length === 0) {
|
|
83
|
+
throw new Error(`No command files found in ${sourceCommandsDir}.`);
|
|
84
|
+
}
|
|
85
|
+
if (shouldImportMcp && options.requireMcp && !sourceMcpPath) {
|
|
86
|
+
throw new Error(`No source mcp.json found under ${prepared.importRoot} (expected mcp.json or .agents/mcp.json).`);
|
|
87
|
+
}
|
|
88
|
+
if (shouldImportSkills && options.requireSkills && !sourceSkillsDir) {
|
|
89
|
+
throw new Error(`No source skills directory found under ${prepared.importRoot} (expected .agents/skills/, skills/, or root SKILL.md).`);
|
|
90
|
+
}
|
|
91
|
+
if (shouldImportSkills &&
|
|
92
|
+
options.requireSkills &&
|
|
93
|
+
sourceSkills.length === 0) {
|
|
94
|
+
throw new Error(`No skills found in ${sourceSkillsDir}.`);
|
|
95
|
+
}
|
|
96
|
+
if (isAggregateImport &&
|
|
97
|
+
sourceAgents.length === 0 &&
|
|
98
|
+
sourceCommands.length === 0 &&
|
|
99
|
+
sourceSkills.length === 0 &&
|
|
100
|
+
Object.keys(sourceMcp?.mcpServers ?? {}).length === 0) {
|
|
101
|
+
throw new Error(`No importable entities found in source "${sourceLocation}".\nExpected agents/, .agents/agents/, commands/, .agents/commands/, prompts/, mcp.json/.agents/mcp.json, skills/, .agents/skills/, or root SKILL.md.`);
|
|
102
|
+
}
|
|
103
|
+
const shouldResolveAgents = shouldImportAgents &&
|
|
104
|
+
(sourceAgents.length > 0 ||
|
|
105
|
+
(options.agents?.length ?? 0) > 0 ||
|
|
106
|
+
requireAgents);
|
|
107
|
+
const selection = shouldResolveAgents
|
|
108
|
+
? await resolveAgentsToImport({
|
|
109
|
+
sourceAgents,
|
|
110
|
+
requestedAgents: options.agents,
|
|
40
111
|
yes: !!options.yes,
|
|
41
112
|
nonInteractive: !!options.nonInteractive,
|
|
42
|
-
|
|
113
|
+
promptForAgentSelection: options.promptForAgentSelection ?? true,
|
|
114
|
+
selectionMode: options.selectionMode,
|
|
115
|
+
})
|
|
116
|
+
: { selectedAgents: [] };
|
|
117
|
+
let selectedSourceCommands = [];
|
|
118
|
+
let selectedSourceCommandFiles = [];
|
|
119
|
+
let commandSelectionMode = "all";
|
|
120
|
+
if (shouldImportCommands && sourceCommandsDir) {
|
|
121
|
+
const commandSelection = await resolveCommandsToImport({
|
|
122
|
+
sourceCommands,
|
|
123
|
+
selectors: options.commandSelectors ?? [],
|
|
124
|
+
promptForCommands: Boolean(options.promptForCommands),
|
|
125
|
+
nonInteractive: Boolean(options.nonInteractive),
|
|
126
|
+
selectionMode: options.selectionMode,
|
|
43
127
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const content = buildAgentMarkdown(agent.frontmatter, agent.body);
|
|
48
|
-
writeTextAtomic(targetPath, content);
|
|
49
|
-
importedAgents.push(relativePosix(options.paths.agentsRoot, targetPath));
|
|
50
|
-
importedAgentHashes.push(hashContent(content));
|
|
128
|
+
selectedSourceCommands = commandSelection.selectedCommands;
|
|
129
|
+
commandSelectionMode = commandSelection.selectionMode;
|
|
130
|
+
selectedSourceCommandFiles = selectedSourceCommands.map((command) => command.fileName);
|
|
51
131
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
132
|
+
let selectedSourceMcpServers = [];
|
|
133
|
+
let sourceMcpServerNames = sourceMcp
|
|
134
|
+
? Object.keys(sourceMcp.mcpServers).sort()
|
|
135
|
+
: [];
|
|
136
|
+
let mcpSelectionMode = "all";
|
|
137
|
+
if (shouldImportMcp && sourceMcp) {
|
|
138
|
+
const mcpSelection = await resolveMcpServersToImport({
|
|
57
139
|
sourceMcp,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
nonInteractive:
|
|
140
|
+
selectors: options.mcpSelectors ?? [],
|
|
141
|
+
promptForMcp: options.promptForMcp ?? true,
|
|
142
|
+
nonInteractive: Boolean(options.nonInteractive),
|
|
143
|
+
selectionMode: options.selectionMode,
|
|
144
|
+
});
|
|
145
|
+
selectedSourceMcpServers = mcpSelection.selectedServerNames;
|
|
146
|
+
mcpSelectionMode = mcpSelection.selectionMode;
|
|
147
|
+
}
|
|
148
|
+
let selectedSkills = [];
|
|
149
|
+
let selectedSourceSkills = [];
|
|
150
|
+
let skillSelectionMode = "all";
|
|
151
|
+
if (shouldImportSkills && sourceSkillsDir) {
|
|
152
|
+
const skillSelection = await resolveSkillsToImport({
|
|
153
|
+
sourceSkills,
|
|
154
|
+
selectors: options.skillSelectors ?? [],
|
|
155
|
+
promptForSkills: options.promptForSkills ?? true,
|
|
156
|
+
nonInteractive: Boolean(options.nonInteractive),
|
|
157
|
+
selectionMode: options.selectionMode,
|
|
61
158
|
});
|
|
62
|
-
|
|
63
|
-
|
|
159
|
+
selectedSkills = skillSelection.selectedSkills;
|
|
160
|
+
skillSelectionMode = skillSelection.selectionMode;
|
|
161
|
+
selectedSourceSkills = selectedSkills.map((skill) => skill.name);
|
|
64
162
|
}
|
|
163
|
+
const importedAgents = [];
|
|
164
|
+
if (shouldImportAgents && selection.selectedAgents.length > 0) {
|
|
165
|
+
ensureDir(options.paths.agentsDir);
|
|
166
|
+
for (const [index, agent] of selection.selectedAgents.entries()) {
|
|
167
|
+
let targetFileName = targetFileNameForAgent(agent);
|
|
168
|
+
if (options.rename &&
|
|
169
|
+
selection.selectedAgents.length === 1 &&
|
|
170
|
+
!(shouldImportCommands && hasExplicitCommandSelection)) {
|
|
171
|
+
targetFileName = `${slugify(options.rename) || "agent"}.md`;
|
|
172
|
+
}
|
|
173
|
+
const resolvedFileName = await resolveAgentConflict({
|
|
174
|
+
targetFileName,
|
|
175
|
+
agentContent: buildAgentMarkdown(agent.frontmatter, agent.body),
|
|
176
|
+
paths: options.paths,
|
|
177
|
+
yes: !!options.yes,
|
|
178
|
+
nonInteractive: !!options.nonInteractive,
|
|
179
|
+
promptLabel: `${agent.name} (${index + 1}/${selection.selectedAgents.length})`,
|
|
180
|
+
});
|
|
181
|
+
if (!resolvedFileName)
|
|
182
|
+
continue;
|
|
183
|
+
const targetPath = path.join(options.paths.agentsDir, resolvedFileName);
|
|
184
|
+
const content = buildAgentMarkdown(agent.frontmatter, agent.body);
|
|
185
|
+
writeTextAtomic(targetPath, content);
|
|
186
|
+
importedAgents.push(relativePosix(options.paths.agentsRoot, targetPath));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const importedCommands = [];
|
|
190
|
+
const importedCommandRenameMap = {};
|
|
191
|
+
if (shouldImportCommands && sourceCommandsDir) {
|
|
192
|
+
if (selectedSourceCommands.length > 0) {
|
|
193
|
+
ensureDir(options.paths.commandsDir);
|
|
194
|
+
}
|
|
195
|
+
for (const [index, command] of selectedSourceCommands.entries()) {
|
|
196
|
+
let targetFileName = command.fileName;
|
|
197
|
+
const mappedTargetFileName = resolveMappedTargetFileName(command.fileName, options.commandRenameMap);
|
|
198
|
+
if (mappedTargetFileName) {
|
|
199
|
+
targetFileName = mappedTargetFileName;
|
|
200
|
+
}
|
|
201
|
+
else if (options.rename &&
|
|
202
|
+
selectedSourceCommands.length === 1 &&
|
|
203
|
+
(!shouldImportAgents || hasExplicitCommandSelection)) {
|
|
204
|
+
targetFileName = `${slugify(options.rename) || "command"}.md`;
|
|
205
|
+
}
|
|
206
|
+
const resolvedFileName = await resolveCommandConflict({
|
|
207
|
+
targetFileName,
|
|
208
|
+
commandContent: command.content,
|
|
209
|
+
paths: options.paths,
|
|
210
|
+
yes: !!options.yes,
|
|
211
|
+
nonInteractive: !!options.nonInteractive,
|
|
212
|
+
promptLabel: `${command.fileName} (${index + 1}/${selectedSourceCommands.length})`,
|
|
213
|
+
});
|
|
214
|
+
if (!resolvedFileName)
|
|
215
|
+
continue;
|
|
216
|
+
const targetPath = path.join(options.paths.commandsDir, resolvedFileName);
|
|
217
|
+
writeTextAtomic(targetPath, command.content);
|
|
218
|
+
importedCommands.push(relativePosix(options.paths.agentsRoot, targetPath));
|
|
219
|
+
importedCommandRenameMap[command.fileName] = resolvedFileName;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const importedMcpServers = [];
|
|
223
|
+
if (shouldImportMcp && sourceMcp) {
|
|
224
|
+
const selectedSourceMcp = {
|
|
225
|
+
version: 1,
|
|
226
|
+
mcpServers: Object.fromEntries(selectedSourceMcpServers.map((serverName) => [
|
|
227
|
+
serverName,
|
|
228
|
+
sourceMcp.mcpServers[serverName] ?? {},
|
|
229
|
+
])),
|
|
230
|
+
};
|
|
231
|
+
if (selectedSourceMcpServers.length > 0) {
|
|
232
|
+
const targetMcp = readCanonicalMcp(options.paths);
|
|
233
|
+
const merged = await resolveMcpConflict({
|
|
234
|
+
sourceMcp: selectedSourceMcp,
|
|
235
|
+
targetMcp,
|
|
236
|
+
yes: !!options.yes,
|
|
237
|
+
nonInteractive: !!options.nonInteractive,
|
|
238
|
+
});
|
|
239
|
+
writeCanonicalMcp(options.paths, merged);
|
|
240
|
+
}
|
|
241
|
+
importedMcpServers.push(...selectedSourceMcpServers);
|
|
242
|
+
}
|
|
243
|
+
const importedSkills = [];
|
|
244
|
+
const telemetrySkills = [];
|
|
245
|
+
const importedSkillRenameMap = {};
|
|
246
|
+
let skillsProvidersForLock;
|
|
247
|
+
if (shouldImportSkills && sourceSkillsDir) {
|
|
248
|
+
if (selectedSkills.length > 0) {
|
|
249
|
+
ensureDir(options.paths.skillsDir);
|
|
250
|
+
}
|
|
251
|
+
for (const [index, sourceSkill] of selectedSkills.entries()) {
|
|
252
|
+
let targetSkillDirName = slugify(sourceSkill.name) || "skill";
|
|
253
|
+
const mappedTargetSkillDirName = resolveMappedTargetSkillName(sourceSkill.name, options.skillRenameMap);
|
|
254
|
+
if (mappedTargetSkillDirName) {
|
|
255
|
+
targetSkillDirName = mappedTargetSkillDirName;
|
|
256
|
+
}
|
|
257
|
+
else if (options.rename &&
|
|
258
|
+
selectedSkills.length === 1 &&
|
|
259
|
+
importedAgents.length === 0 &&
|
|
260
|
+
importedCommands.length === 0 &&
|
|
261
|
+
importedMcpServers.length === 0) {
|
|
262
|
+
targetSkillDirName = slugify(options.rename) || "skill";
|
|
263
|
+
}
|
|
264
|
+
const resolvedSkillDirName = await resolveSkillConflict({
|
|
265
|
+
sourceSkill,
|
|
266
|
+
targetSkillDirName,
|
|
267
|
+
paths: options.paths,
|
|
268
|
+
yes: !!options.yes,
|
|
269
|
+
nonInteractive: !!options.nonInteractive,
|
|
270
|
+
promptLabel: `${sourceSkill.name} (${index + 1}/${selectedSkills.length})`,
|
|
271
|
+
});
|
|
272
|
+
if (!resolvedSkillDirName)
|
|
273
|
+
continue;
|
|
274
|
+
const targetSkillDir = path.join(options.paths.skillsDir, resolvedSkillDirName);
|
|
275
|
+
if (!skillContentMatchesTarget(sourceSkill, targetSkillDir)) {
|
|
276
|
+
fs.rmSync(targetSkillDir, { recursive: true, force: true });
|
|
277
|
+
copySkillArtifacts(sourceSkill, targetSkillDir);
|
|
278
|
+
}
|
|
279
|
+
importedSkills.push(resolvedSkillDirName);
|
|
280
|
+
telemetrySkills.push({
|
|
281
|
+
name: sourceSkill.name,
|
|
282
|
+
filePath: relativePosix(prepared.rootPath, sourceSkill.skillPath),
|
|
283
|
+
});
|
|
284
|
+
importedSkillRenameMap[sourceSkill.name] = resolvedSkillDirName;
|
|
285
|
+
}
|
|
286
|
+
if (importedSkills.length > 0) {
|
|
287
|
+
const resolvedSkillsProviders = normalizeSkillsProviders(options.skillsProviders && options.skillsProviders.length > 0
|
|
288
|
+
? options.skillsProviders
|
|
289
|
+
: await options.resolveSkillsProviders?.());
|
|
290
|
+
if (resolvedSkillsProviders && resolvedSkillsProviders.length > 0) {
|
|
291
|
+
applySkillProviderSideEffects({
|
|
292
|
+
paths: options.paths,
|
|
293
|
+
providers: resolvedSkillsProviders,
|
|
294
|
+
warn(message) {
|
|
295
|
+
console.warn(`Warning: ${message}`);
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
skillsProvidersForLock = resolvedSkillsProviders;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const selectedSubsetOfSourceCommands = sourceCommands.length > 0 &&
|
|
303
|
+
selectedSourceCommandFiles.length < sourceCommands.length;
|
|
304
|
+
const selectedSubsetOfSourceMcp = sourceMcpServerNames.length > 0 &&
|
|
305
|
+
selectedSourceMcpServers.length < sourceMcpServerNames.length;
|
|
306
|
+
const selectedSubsetOfSourceSkills = sourceSkills.length > 0 &&
|
|
307
|
+
selectedSourceSkills.length < sourceSkills.length;
|
|
308
|
+
const shouldPersistCommandSelection = shouldImportCommands && commandSelectionMode === "custom";
|
|
309
|
+
const shouldPersistMcpSelection = shouldImportMcp && mcpSelectionMode === "custom";
|
|
310
|
+
const shouldPersistSkillSelection = shouldImportSkills && skillSelectionMode === "custom";
|
|
311
|
+
const selectedSourceCommandsForLock = shouldPersistCommandSelection || selectedSubsetOfSourceCommands
|
|
312
|
+
? selectedSourceCommandFiles
|
|
313
|
+
: undefined;
|
|
314
|
+
const selectedSourceMcpServersForLock = shouldPersistMcpSelection || selectedSubsetOfSourceMcp
|
|
315
|
+
? selectedSourceMcpServers
|
|
316
|
+
: undefined;
|
|
317
|
+
const selectedSourceSkillsForLock = shouldPersistSkillSelection || selectedSubsetOfSourceSkills
|
|
318
|
+
? selectedSourceSkills
|
|
319
|
+
: undefined;
|
|
65
320
|
const lockfile = readLockfile(options.paths);
|
|
321
|
+
const isCommandOnlyImport = !shouldImportAgents &&
|
|
322
|
+
shouldImportCommands &&
|
|
323
|
+
!shouldImportMcp &&
|
|
324
|
+
!shouldImportSkills;
|
|
325
|
+
const existingEntry = isCommandOnlyImport
|
|
326
|
+
? findRelaxedCommandEntry(lockfile.entries, {
|
|
327
|
+
source: prepared.spec.source,
|
|
328
|
+
sourceType: prepared.spec.type,
|
|
329
|
+
subdir: options.subdir,
|
|
330
|
+
requestedAgents: options.agents,
|
|
331
|
+
})
|
|
332
|
+
: findMatchingLockEntry(lockfile.entries, {
|
|
333
|
+
source: prepared.spec.source,
|
|
334
|
+
sourceType: prepared.spec.type,
|
|
335
|
+
subdir: options.subdir,
|
|
336
|
+
requestedAgents: shouldImportAgents
|
|
337
|
+
? selection.requestedAgentsForLock
|
|
338
|
+
: options.agents,
|
|
339
|
+
selectedSourceCommands: selectedSourceCommandsForLock,
|
|
340
|
+
selectedSourceMcpServers: selectedSourceMcpServersForLock,
|
|
341
|
+
selectedSourceSkills: selectedSourceSkillsForLock,
|
|
342
|
+
skillsProviders: skillsProvidersForLock,
|
|
343
|
+
});
|
|
344
|
+
const shouldMergeCommandOnlyEntry = isCommandOnlyImport &&
|
|
345
|
+
Boolean(existingEntry) &&
|
|
346
|
+
(existingEntry?.importedAgents.length ?? 0) === 0 &&
|
|
347
|
+
(existingEntry?.importedMcpServers.length ?? 0) === 0 &&
|
|
348
|
+
(existingEntry?.importedSkills.length ?? 0) === 0;
|
|
349
|
+
const lockImportedAgents = shouldImportAgents
|
|
350
|
+
? importedAgents
|
|
351
|
+
: (existingEntry?.importedAgents ?? []);
|
|
352
|
+
const lockImportedCommands = shouldImportCommands
|
|
353
|
+
? shouldMergeCommandOnlyEntry
|
|
354
|
+
? uniqueStrings([
|
|
355
|
+
...(existingEntry?.importedCommands ?? []),
|
|
356
|
+
...importedCommands,
|
|
357
|
+
])
|
|
358
|
+
: importedCommands
|
|
359
|
+
: (existingEntry?.importedCommands ?? []);
|
|
360
|
+
const lockImportedMcpServers = shouldImportMcp
|
|
361
|
+
? importedMcpServers
|
|
362
|
+
: (existingEntry?.importedMcpServers ?? []);
|
|
363
|
+
const lockImportedSkills = shouldImportSkills
|
|
364
|
+
? importedSkills
|
|
365
|
+
: (existingEntry?.importedSkills ?? []);
|
|
366
|
+
let lockSelectedSourceCommands;
|
|
367
|
+
if (shouldImportCommands) {
|
|
368
|
+
if (shouldMergeCommandOnlyEntry) {
|
|
369
|
+
if (shouldPersistCommandSelection || selectedSubsetOfSourceCommands) {
|
|
370
|
+
lockSelectedSourceCommands = uniqueStrings([
|
|
371
|
+
...(existingEntry?.selectedSourceCommands ?? []),
|
|
372
|
+
...selectedSourceCommandFiles,
|
|
373
|
+
]);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
lockSelectedSourceCommands = undefined;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
else if (shouldPersistCommandSelection ||
|
|
380
|
+
selectedSubsetOfSourceCommands) {
|
|
381
|
+
lockSelectedSourceCommands = [...selectedSourceCommandFiles];
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
lockSelectedSourceCommands = undefined;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
lockSelectedSourceCommands = existingEntry?.selectedSourceCommands;
|
|
389
|
+
}
|
|
390
|
+
const lockSelectedSourceMcpServers = shouldImportMcp
|
|
391
|
+
? shouldPersistMcpSelection || selectedSubsetOfSourceMcp
|
|
392
|
+
? [...selectedSourceMcpServers]
|
|
393
|
+
: undefined
|
|
394
|
+
: existingEntry?.selectedSourceMcpServers;
|
|
395
|
+
const lockSelectedSourceSkills = shouldImportSkills
|
|
396
|
+
? shouldPersistSkillSelection || selectedSubsetOfSourceSkills
|
|
397
|
+
? [...selectedSourceSkills]
|
|
398
|
+
: undefined
|
|
399
|
+
: existingEntry?.selectedSourceSkills;
|
|
400
|
+
const lockSkillsProviders = shouldImportSkills
|
|
401
|
+
? (skillsProvidersForLock ?? existingEntry?.skillsProviders)
|
|
402
|
+
: existingEntry?.skillsProviders;
|
|
403
|
+
const lockSkillRenameMap = shouldImportSkills
|
|
404
|
+
? normalizeSkillRenameMap(importedSkillRenameMap)
|
|
405
|
+
: existingEntry?.skillRenameMap;
|
|
406
|
+
let lockCommandRenameMap;
|
|
407
|
+
if (shouldImportCommands) {
|
|
408
|
+
if (shouldMergeCommandOnlyEntry) {
|
|
409
|
+
lockCommandRenameMap = mergeCommandRenameMaps(existingEntry?.commandRenameMap, importedCommandRenameMap);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
lockCommandRenameMap = normalizeCommandRenameMap(importedCommandRenameMap);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
lockCommandRenameMap = existingEntry?.commandRenameMap;
|
|
417
|
+
}
|
|
418
|
+
const lockRequestedAgents = shouldImportAgents
|
|
419
|
+
? selection.requestedAgentsForLock
|
|
420
|
+
: existingEntry?.requestedAgents;
|
|
421
|
+
const trackedEntities = computeTrackedEntitiesForLock({
|
|
422
|
+
requestedAgents: lockRequestedAgents,
|
|
423
|
+
importedAgents: lockImportedAgents,
|
|
424
|
+
importedCommands: lockImportedCommands,
|
|
425
|
+
selectedSourceCommands: lockSelectedSourceCommands,
|
|
426
|
+
commandRenameMap: lockCommandRenameMap,
|
|
427
|
+
importedMcpServers: lockImportedMcpServers,
|
|
428
|
+
selectedSourceMcpServers: lockSelectedSourceMcpServers,
|
|
429
|
+
importedSkills: lockImportedSkills,
|
|
430
|
+
selectedSourceSkills: lockSelectedSourceSkills,
|
|
431
|
+
skillsProviders: lockSkillsProviders,
|
|
432
|
+
skillRenameMap: lockSkillRenameMap,
|
|
433
|
+
});
|
|
66
434
|
const contentHash = hashContent(JSON.stringify({
|
|
67
|
-
agents:
|
|
68
|
-
|
|
435
|
+
agents: lockImportedAgents,
|
|
436
|
+
commands: lockImportedCommands,
|
|
437
|
+
selectedSourceCommands: lockSelectedSourceCommands ?? [],
|
|
438
|
+
commandRenameMap: lockCommandRenameMap ?? {},
|
|
439
|
+
mcp: lockImportedMcpServers,
|
|
440
|
+
selectedSourceMcpServers: lockSelectedSourceMcpServers ?? [],
|
|
441
|
+
skills: lockImportedSkills,
|
|
442
|
+
selectedSourceSkills: lockSelectedSourceSkills ?? [],
|
|
443
|
+
skillsProviders: lockSkillsProviders ?? [],
|
|
444
|
+
skillRenameMap: lockSkillRenameMap ?? {},
|
|
445
|
+
trackedEntities: trackedEntities ?? [],
|
|
69
446
|
}));
|
|
70
447
|
const lockEntry = {
|
|
71
448
|
source: prepared.spec.source,
|
|
72
449
|
sourceType: prepared.spec.type,
|
|
73
450
|
requestedRef: options.ref,
|
|
451
|
+
requestedAgents: lockRequestedAgents,
|
|
74
452
|
resolvedCommit: prepared.resolvedCommit,
|
|
75
453
|
subdir: options.subdir,
|
|
76
454
|
importedAt: new Date().toISOString(),
|
|
77
|
-
importedAgents,
|
|
78
|
-
|
|
455
|
+
importedAgents: lockImportedAgents,
|
|
456
|
+
importedCommands: lockImportedCommands,
|
|
457
|
+
selectedSourceCommands: lockSelectedSourceCommands,
|
|
458
|
+
commandRenameMap: lockCommandRenameMap,
|
|
459
|
+
importedMcpServers: lockImportedMcpServers,
|
|
460
|
+
selectedSourceMcpServers: lockSelectedSourceMcpServers,
|
|
461
|
+
importedSkills: lockImportedSkills,
|
|
462
|
+
selectedSourceSkills: lockSelectedSourceSkills,
|
|
463
|
+
skillsProviders: lockSkillsProviders,
|
|
464
|
+
skillRenameMap: lockSkillRenameMap,
|
|
465
|
+
trackedEntities,
|
|
79
466
|
contentHash,
|
|
80
467
|
};
|
|
81
|
-
|
|
468
|
+
if (existingEntry) {
|
|
469
|
+
const existingIndex = lockfile.entries.indexOf(existingEntry);
|
|
470
|
+
if (existingIndex >= 0) {
|
|
471
|
+
lockfile.entries[existingIndex] = lockEntry;
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
upsertLockEntry(lockfile, lockEntry);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
upsertLockEntry(lockfile, lockEntry);
|
|
479
|
+
}
|
|
82
480
|
writeLockfile(options.paths, lockfile);
|
|
83
481
|
return {
|
|
84
482
|
source: prepared.spec.source,
|
|
85
483
|
sourceType: prepared.spec.type,
|
|
86
484
|
importedAgents,
|
|
485
|
+
importedCommands,
|
|
87
486
|
importedMcpServers,
|
|
487
|
+
importedSkills,
|
|
488
|
+
telemetrySkills: telemetrySkills.length > 0 ? telemetrySkills : undefined,
|
|
88
489
|
resolvedCommit: prepared.resolvedCommit,
|
|
89
490
|
};
|
|
90
491
|
}
|
|
@@ -92,6 +493,364 @@ export async function importSource(options) {
|
|
|
92
493
|
prepared.cleanup();
|
|
93
494
|
}
|
|
94
495
|
}
|
|
496
|
+
async function resolveAgentsToImport(options) {
|
|
497
|
+
const requestedAgents = normalizeRequestedAgents(options.requestedAgents);
|
|
498
|
+
if (requestedAgents && requestedAgents.length > 0) {
|
|
499
|
+
const selected = resolveRequestedAgents(options.sourceAgents, requestedAgents);
|
|
500
|
+
if (selected.length === 0) {
|
|
501
|
+
throw new Error("No agents matched the requested --agent filters.");
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
selectedAgents: selected,
|
|
505
|
+
// Persist stable selectors so updates keep targeting the same source agents
|
|
506
|
+
// even if a previously-fallback selector later gains a new exact match.
|
|
507
|
+
requestedAgentsForLock: getStableRequestedAgentsForLock({
|
|
508
|
+
selectedAgents: selected,
|
|
509
|
+
sourceAgents: options.sourceAgents,
|
|
510
|
+
}),
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const selectionMode = await resolveSelectionMode({
|
|
514
|
+
entityLabel: "agents",
|
|
515
|
+
selectionMode: options.selectionMode,
|
|
516
|
+
promptForSelection: options.promptForAgentSelection,
|
|
517
|
+
nonInteractive: options.nonInteractive,
|
|
518
|
+
});
|
|
519
|
+
if (selectionMode === "all") {
|
|
520
|
+
return { selectedAgents: options.sourceAgents };
|
|
521
|
+
}
|
|
522
|
+
if (options.yes ||
|
|
523
|
+
options.nonInteractive ||
|
|
524
|
+
options.sourceAgents.length <= 1 ||
|
|
525
|
+
!options.promptForAgentSelection) {
|
|
526
|
+
return {
|
|
527
|
+
selectedAgents: options.sourceAgents,
|
|
528
|
+
requestedAgentsForLock: getStableRequestedAgentsForLock({
|
|
529
|
+
selectedAgents: options.sourceAgents,
|
|
530
|
+
sourceAgents: options.sourceAgents,
|
|
531
|
+
}),
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
const selected = await promptAgentSelection(options.sourceAgents);
|
|
535
|
+
if (selected.length === 0) {
|
|
536
|
+
throw new Error("No agents selected. Use --agent <name> or rerun and select at least one agent.");
|
|
537
|
+
}
|
|
538
|
+
const requestedAgentsForLock = getStableRequestedAgentsForLock({
|
|
539
|
+
selectedAgents: selected,
|
|
540
|
+
sourceAgents: options.sourceAgents,
|
|
541
|
+
});
|
|
542
|
+
return { selectedAgents: selected, requestedAgentsForLock };
|
|
543
|
+
}
|
|
544
|
+
function normalizeRequestedAgents(requestedAgents) {
|
|
545
|
+
if (!requestedAgents || requestedAgents.length === 0)
|
|
546
|
+
return undefined;
|
|
547
|
+
const normalized = requestedAgents.map((item) => item.trim()).filter(Boolean);
|
|
548
|
+
return normalized.length > 0 ? [...new Set(normalized)] : undefined;
|
|
549
|
+
}
|
|
550
|
+
function getStableRequestedAgentsForLock(options) {
|
|
551
|
+
const nameCounts = new Map();
|
|
552
|
+
for (const agent of options.sourceAgents) {
|
|
553
|
+
const normalizedName = normalizeAgentSelector(agent.name);
|
|
554
|
+
if (!normalizedName)
|
|
555
|
+
continue;
|
|
556
|
+
nameCounts.set(normalizedName, (nameCounts.get(normalizedName) ?? 0) + 1);
|
|
557
|
+
}
|
|
558
|
+
const selectors = options.selectedAgents.map((agent) => {
|
|
559
|
+
const normalizedName = normalizeAgentSelector(agent.name);
|
|
560
|
+
if (normalizedName && nameCounts.get(normalizedName) === 1) {
|
|
561
|
+
return agent.name;
|
|
562
|
+
}
|
|
563
|
+
return agent.fileName.replace(/\.md$/i, "");
|
|
564
|
+
});
|
|
565
|
+
return normalizeRequestedAgents(selectors);
|
|
566
|
+
}
|
|
567
|
+
function resolveRequestedAgents(sourceAgents, requestedAgents) {
|
|
568
|
+
const selectedPaths = new Set();
|
|
569
|
+
const missing = [];
|
|
570
|
+
const ambiguous = [];
|
|
571
|
+
for (const requestedAgent of requestedAgents) {
|
|
572
|
+
const exactMatches = sourceAgents.filter((agent) => agentMatchesSelector(agent, requestedAgent, false));
|
|
573
|
+
const matches = exactMatches.length > 0
|
|
574
|
+
? exactMatches
|
|
575
|
+
: sourceAgents.filter((agent) => agentMatchesSelector(agent, requestedAgent, true));
|
|
576
|
+
if (matches.length === 0) {
|
|
577
|
+
missing.push(requestedAgent);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
if (matches.length > 1) {
|
|
581
|
+
ambiguous.push(`${requestedAgent} (${matches.map((agent) => agent.name).join(", ")})`);
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (matches[0]) {
|
|
585
|
+
selectedPaths.add(matches[0].sourcePath);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (missing.length > 0) {
|
|
589
|
+
throw new Error(`Requested agent(s) not found: ${missing.join(", ")}. Available agents: ${sourceAgents.map((agent) => agent.name).join(", ")}.`);
|
|
590
|
+
}
|
|
591
|
+
if (ambiguous.length > 0) {
|
|
592
|
+
throw new Error(`Requested agent selector is ambiguous: ${ambiguous.join("; ")}. Use the exact frontmatter name.`);
|
|
593
|
+
}
|
|
594
|
+
return sourceAgents.filter((agent) => selectedPaths.has(agent.sourcePath));
|
|
595
|
+
}
|
|
596
|
+
function agentMatchesSelector(agent, selector, includeSlugFallback) {
|
|
597
|
+
const normalizedSelector = normalizeAgentSelector(selector);
|
|
598
|
+
if (!normalizedSelector)
|
|
599
|
+
return false;
|
|
600
|
+
if (getExactAgentSelectorCandidates(agent).has(normalizedSelector)) {
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
if (!includeSlugFallback)
|
|
604
|
+
return false;
|
|
605
|
+
const selectorSlug = normalizeAgentSelector(slugify(selector));
|
|
606
|
+
return (selectorSlug !== "" &&
|
|
607
|
+
getSlugAgentSelectorCandidates(agent).has(selectorSlug));
|
|
608
|
+
}
|
|
609
|
+
function getExactAgentSelectorCandidates(agent) {
|
|
610
|
+
const fileBaseName = agent.fileName.replace(/\.md$/i, "");
|
|
611
|
+
return new Set([
|
|
612
|
+
agent.name,
|
|
613
|
+
fileBaseName,
|
|
614
|
+
path.basename(agent.sourcePath).replace(/\.md$/i, ""),
|
|
615
|
+
]
|
|
616
|
+
.map(normalizeAgentSelector)
|
|
617
|
+
.filter(Boolean));
|
|
618
|
+
}
|
|
619
|
+
function getSlugAgentSelectorCandidates(agent) {
|
|
620
|
+
const fileBaseName = agent.fileName.replace(/\.md$/i, "");
|
|
621
|
+
return new Set([
|
|
622
|
+
slugify(agent.name),
|
|
623
|
+
slugify(fileBaseName),
|
|
624
|
+
slugify(path.basename(agent.sourcePath).replace(/\.md$/i, "")),
|
|
625
|
+
]
|
|
626
|
+
.map(normalizeAgentSelector)
|
|
627
|
+
.filter(Boolean));
|
|
628
|
+
}
|
|
629
|
+
function normalizeAgentSelector(value) {
|
|
630
|
+
return value.trim().toLowerCase();
|
|
631
|
+
}
|
|
632
|
+
async function promptAgentSelection(sourceAgents) {
|
|
633
|
+
const choice = await multiselect({
|
|
634
|
+
message: withMultiselectHelp("Select agents to import"),
|
|
635
|
+
options: sourceAgents.map((agent) => ({
|
|
636
|
+
value: agent.sourcePath,
|
|
637
|
+
label: agent.name,
|
|
638
|
+
hint: agent.description,
|
|
639
|
+
})),
|
|
640
|
+
initialValues: sourceAgents.map((agent) => agent.sourcePath),
|
|
641
|
+
});
|
|
642
|
+
if (isCancel(choice)) {
|
|
643
|
+
cancel("Operation cancelled.");
|
|
644
|
+
process.exit(1);
|
|
645
|
+
}
|
|
646
|
+
const selected = new Set(Array.isArray(choice) ? choice.map(String) : []);
|
|
647
|
+
return sourceAgents.filter((agent) => selected.has(agent.sourcePath));
|
|
648
|
+
}
|
|
649
|
+
function findMatchingLockEntry(entries, key) {
|
|
650
|
+
return entries.find((entry) => entry.source === key.source &&
|
|
651
|
+
entry.sourceType === key.sourceType &&
|
|
652
|
+
entry.subdir === key.subdir &&
|
|
653
|
+
sameRequestedAgentsForMatch(entry.requestedAgents, key.requestedAgents) &&
|
|
654
|
+
sameStringSelectionForMatch(entry.selectedSourceCommands, key.selectedSourceCommands) &&
|
|
655
|
+
sameStringSelectionForMatch(entry.selectedSourceMcpServers, key.selectedSourceMcpServers) &&
|
|
656
|
+
sameStringSelectionForMatch(entry.selectedSourceSkills, key.selectedSourceSkills, { wildcardWhenRightIsUndefined: true }) &&
|
|
657
|
+
sameStringSelectionForMatch(entry.skillsProviders, key.skillsProviders, {
|
|
658
|
+
wildcardWhenRightIsUndefined: true,
|
|
659
|
+
}));
|
|
660
|
+
}
|
|
661
|
+
function findRelaxedCommandEntry(entries, key) {
|
|
662
|
+
const matches = entries.filter((entry) => entry.source === key.source &&
|
|
663
|
+
entry.sourceType === key.sourceType &&
|
|
664
|
+
entry.subdir === key.subdir &&
|
|
665
|
+
sameRequestedAgentsForMatch(entry.requestedAgents, key.requestedAgents));
|
|
666
|
+
if (matches.length === 0)
|
|
667
|
+
return undefined;
|
|
668
|
+
const mixed = matches.find((entry) => entry.importedAgents.length > 0 ||
|
|
669
|
+
entry.importedMcpServers.length > 0 ||
|
|
670
|
+
entry.importedSkills.length > 0);
|
|
671
|
+
return mixed ?? matches[0];
|
|
672
|
+
}
|
|
673
|
+
function sameRequestedAgentsForMatch(left, right) {
|
|
674
|
+
const normalizedLeft = normalizeRequestedAgentsForMatch(left);
|
|
675
|
+
const normalizedRight = normalizeRequestedAgentsForMatch(right);
|
|
676
|
+
if (normalizedLeft.length !== normalizedRight.length) {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
return normalizedLeft.every((value, index) => value === normalizedRight[index]);
|
|
680
|
+
}
|
|
681
|
+
function normalizeRequestedAgentsForMatch(value) {
|
|
682
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
683
|
+
return [];
|
|
684
|
+
return [
|
|
685
|
+
...new Set(value.map((item) => item.trim().toLowerCase()).filter(Boolean)),
|
|
686
|
+
].sort();
|
|
687
|
+
}
|
|
688
|
+
function sameStringSelectionForMatch(left, right, options = {}) {
|
|
689
|
+
if (options.wildcardWhenRightIsUndefined && right === undefined) {
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
const normalizedLeft = normalizeRequestedAgentsForMatch(left);
|
|
693
|
+
const normalizedRight = normalizeRequestedAgentsForMatch(right);
|
|
694
|
+
if (normalizedLeft.length !== normalizedRight.length) {
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
return normalizedLeft.every((value, index) => value === normalizedRight[index]);
|
|
698
|
+
}
|
|
699
|
+
function uniqueStrings(values) {
|
|
700
|
+
return [...new Set(values)];
|
|
701
|
+
}
|
|
702
|
+
function computeTrackedEntitiesForLock(options) {
|
|
703
|
+
const tracked = [];
|
|
704
|
+
if (options.importedAgents.length > 0 ||
|
|
705
|
+
(options.requestedAgents?.length ?? 0) > 0) {
|
|
706
|
+
tracked.push("agent");
|
|
707
|
+
}
|
|
708
|
+
if (options.importedCommands.length > 0 ||
|
|
709
|
+
(options.selectedSourceCommands?.length ?? 0) > 0 ||
|
|
710
|
+
Object.keys(options.commandRenameMap ?? {}).length > 0) {
|
|
711
|
+
tracked.push("command");
|
|
712
|
+
}
|
|
713
|
+
if (options.importedMcpServers.length > 0 ||
|
|
714
|
+
(options.selectedSourceMcpServers?.length ?? 0) > 0) {
|
|
715
|
+
tracked.push("mcp");
|
|
716
|
+
}
|
|
717
|
+
if (options.importedSkills.length > 0 ||
|
|
718
|
+
(options.selectedSourceSkills?.length ?? 0) > 0 ||
|
|
719
|
+
(options.skillsProviders?.length ?? 0) > 0 ||
|
|
720
|
+
Object.keys(options.skillRenameMap ?? {}).length > 0) {
|
|
721
|
+
tracked.push("skill");
|
|
722
|
+
}
|
|
723
|
+
return tracked.length > 0 ? tracked : undefined;
|
|
724
|
+
}
|
|
725
|
+
function normalizeCommandRenameMap(renameMap) {
|
|
726
|
+
if (!renameMap)
|
|
727
|
+
return undefined;
|
|
728
|
+
const normalized = Object.fromEntries(Object.entries(renameMap).filter(([sourceFileName, importedFileName]) => sourceFileName.trim().length > 0 && importedFileName.trim().length > 0));
|
|
729
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
730
|
+
}
|
|
731
|
+
function mergeCommandRenameMaps(existing, updates) {
|
|
732
|
+
const merged = {
|
|
733
|
+
...(existing ?? {}),
|
|
734
|
+
...(updates ?? {}),
|
|
735
|
+
};
|
|
736
|
+
return normalizeCommandRenameMap(merged);
|
|
737
|
+
}
|
|
738
|
+
function resolveMappedTargetFileName(sourceFileName, renameMap) {
|
|
739
|
+
if (!renameMap)
|
|
740
|
+
return undefined;
|
|
741
|
+
const normalizedSourceName = normalizeCommandSelector(sourceFileName);
|
|
742
|
+
for (const [sourceSelector, importedName] of Object.entries(renameMap)) {
|
|
743
|
+
if (normalizeCommandSelector(sourceSelector) !== normalizedSourceName) {
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
const importedBaseName = path.basename(importedName.trim());
|
|
747
|
+
if (!importedBaseName)
|
|
748
|
+
return undefined;
|
|
749
|
+
const importedExtension = path.extname(importedBaseName);
|
|
750
|
+
if (importedExtension)
|
|
751
|
+
return importedBaseName;
|
|
752
|
+
const sourceExtension = path.extname(sourceFileName) || ".md";
|
|
753
|
+
return `${slugify(importedBaseName) || "command"}${sourceExtension}`;
|
|
754
|
+
}
|
|
755
|
+
return undefined;
|
|
756
|
+
}
|
|
757
|
+
function normalizeSkillRenameMap(renameMap) {
|
|
758
|
+
if (!renameMap)
|
|
759
|
+
return undefined;
|
|
760
|
+
const normalizedEntries = Object.entries(renameMap)
|
|
761
|
+
.map(([sourceSelector, importedName]) => {
|
|
762
|
+
const normalizedSourceSelector = normalizeSkillSelector(sourceSelector);
|
|
763
|
+
const normalizedImportedName = slugify(path.basename(importedName.trim()));
|
|
764
|
+
if (!normalizedSourceSelector || !normalizedImportedName) {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
return [normalizedSourceSelector, normalizedImportedName];
|
|
768
|
+
})
|
|
769
|
+
.filter((entry) => entry !== null);
|
|
770
|
+
if (normalizedEntries.length === 0)
|
|
771
|
+
return undefined;
|
|
772
|
+
return Object.fromEntries(normalizedEntries);
|
|
773
|
+
}
|
|
774
|
+
function resolveMappedTargetSkillName(sourceSkillName, renameMap) {
|
|
775
|
+
if (!renameMap)
|
|
776
|
+
return undefined;
|
|
777
|
+
const normalizedSourceName = normalizeSkillSelector(sourceSkillName);
|
|
778
|
+
if (!normalizedSourceName)
|
|
779
|
+
return undefined;
|
|
780
|
+
for (const [sourceSelector, importedName] of Object.entries(renameMap)) {
|
|
781
|
+
if (normalizeSkillSelector(sourceSelector) !== normalizedSourceName) {
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
return slugify(path.basename(importedName.trim())) || "skill";
|
|
785
|
+
}
|
|
786
|
+
return undefined;
|
|
787
|
+
}
|
|
788
|
+
function normalizeSkillsProviders(providers) {
|
|
789
|
+
if (!providers || providers.length === 0)
|
|
790
|
+
return undefined;
|
|
791
|
+
const selected = new Set();
|
|
792
|
+
for (const provider of providers) {
|
|
793
|
+
const normalized = provider.trim().toLowerCase();
|
|
794
|
+
if (ALL_PROVIDERS.includes(normalized)) {
|
|
795
|
+
selected.add(normalized);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return selected.size > 0 ? [...selected] : undefined;
|
|
799
|
+
}
|
|
800
|
+
async function resolveSkillConflict(options) {
|
|
801
|
+
const targetPath = path.join(options.paths.skillsDir, options.targetSkillDirName);
|
|
802
|
+
if (!fs.existsSync(targetPath))
|
|
803
|
+
return options.targetSkillDirName;
|
|
804
|
+
if (!fs.statSync(targetPath).isDirectory()) {
|
|
805
|
+
throw new Error(`Cannot import skill ${options.promptLabel}: ${targetPath} exists and is not a directory.`);
|
|
806
|
+
}
|
|
807
|
+
if (skillContentMatchesTarget(options.sourceSkill, targetPath)) {
|
|
808
|
+
return options.targetSkillDirName;
|
|
809
|
+
}
|
|
810
|
+
if (options.yes) {
|
|
811
|
+
return options.targetSkillDirName;
|
|
812
|
+
}
|
|
813
|
+
if (options.nonInteractive) {
|
|
814
|
+
throw new NonInteractiveConflictError(`Conflict for skill "${options.targetSkillDirName}". Use --yes or run interactively.`);
|
|
815
|
+
}
|
|
816
|
+
const choice = await select({
|
|
817
|
+
message: `Skill conflict for ${options.promptLabel}`,
|
|
818
|
+
options: [
|
|
819
|
+
{ value: "overwrite", label: `Overwrite ${options.targetSkillDirName}` },
|
|
820
|
+
{ value: "skip", label: "Skip this skill" },
|
|
821
|
+
{ value: "rename", label: "Rename imported skill" },
|
|
822
|
+
],
|
|
823
|
+
});
|
|
824
|
+
if (isCancel(choice)) {
|
|
825
|
+
cancel("Operation cancelled.");
|
|
826
|
+
process.exit(1);
|
|
827
|
+
}
|
|
828
|
+
if (choice === "skip")
|
|
829
|
+
return null;
|
|
830
|
+
if (choice === "rename") {
|
|
831
|
+
const entered = await promptText({
|
|
832
|
+
message: `New directory name for ${options.promptLabel}`,
|
|
833
|
+
placeholder: options.targetSkillDirName,
|
|
834
|
+
validate(value) {
|
|
835
|
+
if (!value.trim())
|
|
836
|
+
return "Name is required.";
|
|
837
|
+
if (/[\\/]/.test(value))
|
|
838
|
+
return "Use a simple directory name.";
|
|
839
|
+
return undefined;
|
|
840
|
+
},
|
|
841
|
+
});
|
|
842
|
+
if (isCancel(entered)) {
|
|
843
|
+
cancel("Operation cancelled.");
|
|
844
|
+
process.exit(1);
|
|
845
|
+
}
|
|
846
|
+
const renamed = slugify(String(entered)) || "skill";
|
|
847
|
+
return resolveSkillConflict({
|
|
848
|
+
...options,
|
|
849
|
+
targetSkillDirName: renamed,
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
return options.targetSkillDirName;
|
|
853
|
+
}
|
|
95
854
|
async function resolveAgentConflict(options) {
|
|
96
855
|
const targetPath = path.join(options.paths.agentsDir, options.targetFileName);
|
|
97
856
|
if (!fs.existsSync(targetPath))
|
|
@@ -143,6 +902,310 @@ async function resolveAgentConflict(options) {
|
|
|
143
902
|
}
|
|
144
903
|
return options.targetFileName;
|
|
145
904
|
}
|
|
905
|
+
async function resolveCommandConflict(options) {
|
|
906
|
+
const targetPath = path.join(options.paths.commandsDir, options.targetFileName);
|
|
907
|
+
if (!fs.existsSync(targetPath))
|
|
908
|
+
return options.targetFileName;
|
|
909
|
+
const existing = fs.readFileSync(targetPath, "utf8");
|
|
910
|
+
if (existing === options.commandContent)
|
|
911
|
+
return options.targetFileName;
|
|
912
|
+
if (options.yes) {
|
|
913
|
+
return options.targetFileName;
|
|
914
|
+
}
|
|
915
|
+
if (options.nonInteractive) {
|
|
916
|
+
throw new NonInteractiveConflictError(`Conflict for ${options.targetFileName}. Use --yes or run interactively.`);
|
|
917
|
+
}
|
|
918
|
+
const choice = await select({
|
|
919
|
+
message: `Command conflict for ${options.promptLabel}`,
|
|
920
|
+
options: [
|
|
921
|
+
{ value: "overwrite", label: `Overwrite ${options.targetFileName}` },
|
|
922
|
+
{ value: "skip", label: "Skip this command" },
|
|
923
|
+
{ value: "rename", label: "Rename imported command" },
|
|
924
|
+
],
|
|
925
|
+
});
|
|
926
|
+
if (isCancel(choice)) {
|
|
927
|
+
cancel("Operation cancelled.");
|
|
928
|
+
process.exit(1);
|
|
929
|
+
}
|
|
930
|
+
if (choice === "skip")
|
|
931
|
+
return null;
|
|
932
|
+
if (choice === "rename") {
|
|
933
|
+
const entered = await promptText({
|
|
934
|
+
message: `New filename (without extension) for ${options.promptLabel}`,
|
|
935
|
+
placeholder: options.targetFileName.replace(/\.(md|mdc)$/i, ""),
|
|
936
|
+
validate(value) {
|
|
937
|
+
if (!value.trim())
|
|
938
|
+
return "Name is required.";
|
|
939
|
+
if (/[\\/]/.test(value))
|
|
940
|
+
return "Use a simple filename.";
|
|
941
|
+
return undefined;
|
|
942
|
+
},
|
|
943
|
+
});
|
|
944
|
+
if (isCancel(entered)) {
|
|
945
|
+
cancel("Operation cancelled.");
|
|
946
|
+
process.exit(1);
|
|
947
|
+
}
|
|
948
|
+
const extension = path.extname(options.targetFileName) || ".md";
|
|
949
|
+
const renamedFileName = `${slugify(String(entered)) || "command"}${extension}`;
|
|
950
|
+
return resolveCommandConflict({
|
|
951
|
+
...options,
|
|
952
|
+
targetFileName: renamedFileName,
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
return options.targetFileName;
|
|
956
|
+
}
|
|
957
|
+
async function resolveCommandsToImport(options) {
|
|
958
|
+
const selectors = options.selectors
|
|
959
|
+
.map((selector) => selector.trim())
|
|
960
|
+
.filter(Boolean);
|
|
961
|
+
if (selectors.length > 0) {
|
|
962
|
+
const { selected, unmatched } = resolveCommandSelections(options.sourceCommands, selectors);
|
|
963
|
+
if (unmatched.length > 0) {
|
|
964
|
+
throw new Error(`Command(s) not found in source: ${unmatched.join(", ")}. Available: ${options.sourceCommands.map((item) => item.fileName).join(", ")}`);
|
|
965
|
+
}
|
|
966
|
+
return {
|
|
967
|
+
selectedCommands: selected,
|
|
968
|
+
selectionMode: "custom",
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
const selectionResolution = await resolveSelectionModeWithSkip({
|
|
972
|
+
entityLabel: "commands",
|
|
973
|
+
selectionMode: options.selectionMode,
|
|
974
|
+
promptForSelection: options.promptForCommands,
|
|
975
|
+
nonInteractive: options.nonInteractive,
|
|
976
|
+
});
|
|
977
|
+
const selectionMode = selectionResolution.selectionMode;
|
|
978
|
+
if (selectionResolution.skipImport) {
|
|
979
|
+
return {
|
|
980
|
+
selectedCommands: [],
|
|
981
|
+
selectionMode: "custom",
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
if (selectionMode === "all" ||
|
|
985
|
+
!options.promptForCommands ||
|
|
986
|
+
options.nonInteractive) {
|
|
987
|
+
return {
|
|
988
|
+
selectedCommands: options.sourceCommands,
|
|
989
|
+
selectionMode,
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
const selected = await multiselect({
|
|
993
|
+
message: withMultiselectHelp("Select commands to import"),
|
|
994
|
+
options: options.sourceCommands.map((item) => ({
|
|
995
|
+
value: item.fileName,
|
|
996
|
+
label: item.fileName,
|
|
997
|
+
})),
|
|
998
|
+
initialValues: options.sourceCommands.map((item) => item.fileName),
|
|
999
|
+
});
|
|
1000
|
+
if (isCancel(selected)) {
|
|
1001
|
+
cancel("Operation cancelled.");
|
|
1002
|
+
process.exit(1);
|
|
1003
|
+
}
|
|
1004
|
+
const selectedNames = Array.isArray(selected)
|
|
1005
|
+
? new Set(selected.map((value) => String(value)))
|
|
1006
|
+
: new Set();
|
|
1007
|
+
return {
|
|
1008
|
+
selectedCommands: options.sourceCommands.filter((item) => selectedNames.has(item.fileName)),
|
|
1009
|
+
selectionMode,
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
async function resolveMcpServersToImport(options) {
|
|
1013
|
+
const available = Object.keys(options.sourceMcp.mcpServers).sort();
|
|
1014
|
+
const selectors = options.selectors
|
|
1015
|
+
.map((item) => item.trim())
|
|
1016
|
+
.filter(Boolean);
|
|
1017
|
+
if (selectors.length > 0) {
|
|
1018
|
+
const selected = new Set();
|
|
1019
|
+
const unmatched = [];
|
|
1020
|
+
for (const selector of selectors) {
|
|
1021
|
+
if (!Object.prototype.hasOwnProperty.call(options.sourceMcp.mcpServers, selector)) {
|
|
1022
|
+
unmatched.push(selector);
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
selected.add(selector);
|
|
1026
|
+
}
|
|
1027
|
+
if (unmatched.length > 0) {
|
|
1028
|
+
throw new Error(`MCP server(s) not found in source: ${unmatched.join(", ")}. Available: ${available.join(", ")}`);
|
|
1029
|
+
}
|
|
1030
|
+
return {
|
|
1031
|
+
selectedServerNames: [...selected],
|
|
1032
|
+
selectionMode: "custom",
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
const selectionResolution = await resolveSelectionModeWithSkip({
|
|
1036
|
+
entityLabel: "MCP servers",
|
|
1037
|
+
selectionMode: options.selectionMode,
|
|
1038
|
+
promptForSelection: options.promptForMcp,
|
|
1039
|
+
nonInteractive: options.nonInteractive,
|
|
1040
|
+
});
|
|
1041
|
+
const selectionMode = selectionResolution.selectionMode;
|
|
1042
|
+
if (selectionResolution.skipImport) {
|
|
1043
|
+
return {
|
|
1044
|
+
selectedServerNames: [],
|
|
1045
|
+
selectionMode: "custom",
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
if (selectionMode === "all" ||
|
|
1049
|
+
!options.promptForMcp ||
|
|
1050
|
+
options.nonInteractive) {
|
|
1051
|
+
return {
|
|
1052
|
+
selectedServerNames: available,
|
|
1053
|
+
selectionMode,
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
const selected = await multiselect({
|
|
1057
|
+
message: withMultiselectHelp("Select MCP servers to import"),
|
|
1058
|
+
options: available.map((serverName) => ({
|
|
1059
|
+
value: serverName,
|
|
1060
|
+
label: serverName,
|
|
1061
|
+
})),
|
|
1062
|
+
initialValues: available,
|
|
1063
|
+
});
|
|
1064
|
+
if (isCancel(selected)) {
|
|
1065
|
+
cancel("Operation cancelled.");
|
|
1066
|
+
process.exit(1);
|
|
1067
|
+
}
|
|
1068
|
+
const selectedNames = Array.isArray(selected)
|
|
1069
|
+
? new Set(selected.map((value) => String(value)))
|
|
1070
|
+
: new Set();
|
|
1071
|
+
return {
|
|
1072
|
+
selectedServerNames: available.filter((serverName) => selectedNames.has(serverName)),
|
|
1073
|
+
selectionMode,
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
async function resolveSkillsToImport(options) {
|
|
1077
|
+
const selectors = options.selectors
|
|
1078
|
+
.map((item) => item.trim())
|
|
1079
|
+
.filter(Boolean);
|
|
1080
|
+
if (selectors.length > 0) {
|
|
1081
|
+
const { selected, unmatched } = resolveSkillSelections(options.sourceSkills, selectors);
|
|
1082
|
+
if (unmatched.length > 0) {
|
|
1083
|
+
throw new Error(`Skill(s) not found in source: ${unmatched.join(", ")}. Available: ${options.sourceSkills.map((skill) => skill.name).join(", ")}`);
|
|
1084
|
+
}
|
|
1085
|
+
return {
|
|
1086
|
+
selectedSkills: selected,
|
|
1087
|
+
selectionMode: "custom",
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
const selectionResolution = await resolveSelectionModeWithSkip({
|
|
1091
|
+
entityLabel: "skills",
|
|
1092
|
+
selectionMode: options.selectionMode,
|
|
1093
|
+
promptForSelection: options.promptForSkills,
|
|
1094
|
+
nonInteractive: options.nonInteractive,
|
|
1095
|
+
});
|
|
1096
|
+
const selectionMode = selectionResolution.selectionMode;
|
|
1097
|
+
if (selectionResolution.skipImport) {
|
|
1098
|
+
return {
|
|
1099
|
+
selectedSkills: [],
|
|
1100
|
+
selectionMode: "custom",
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
if (selectionMode === "all" ||
|
|
1104
|
+
!options.promptForSkills ||
|
|
1105
|
+
options.nonInteractive) {
|
|
1106
|
+
return {
|
|
1107
|
+
selectedSkills: options.sourceSkills,
|
|
1108
|
+
selectionMode,
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
const selected = await multiselect({
|
|
1112
|
+
message: withMultiselectHelp("Select skills to import"),
|
|
1113
|
+
options: options.sourceSkills.map((skill) => ({
|
|
1114
|
+
value: skill.name,
|
|
1115
|
+
label: skill.name,
|
|
1116
|
+
})),
|
|
1117
|
+
initialValues: options.sourceSkills.map((skill) => skill.name),
|
|
1118
|
+
});
|
|
1119
|
+
if (isCancel(selected)) {
|
|
1120
|
+
cancel("Operation cancelled.");
|
|
1121
|
+
process.exit(1);
|
|
1122
|
+
}
|
|
1123
|
+
const selectedNames = Array.isArray(selected)
|
|
1124
|
+
? new Set(selected.map((value) => String(value)))
|
|
1125
|
+
: new Set();
|
|
1126
|
+
return {
|
|
1127
|
+
selectedSkills: options.sourceSkills.filter((skill) => selectedNames.has(skill.name)),
|
|
1128
|
+
selectionMode,
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
async function resolveSelectionMode(options) {
|
|
1132
|
+
if (options.selectionMode) {
|
|
1133
|
+
return options.selectionMode;
|
|
1134
|
+
}
|
|
1135
|
+
if (!options.promptForSelection || options.nonInteractive) {
|
|
1136
|
+
return "all";
|
|
1137
|
+
}
|
|
1138
|
+
const choice = await select({
|
|
1139
|
+
message: `How should ${options.entityLabel} be tracked for future updates?`,
|
|
1140
|
+
options: [
|
|
1141
|
+
{
|
|
1142
|
+
value: "all",
|
|
1143
|
+
label: "Sync everything from source",
|
|
1144
|
+
hint: "Include new items on update",
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
value: "custom",
|
|
1148
|
+
label: "Use custom selection",
|
|
1149
|
+
hint: "Update only currently selected items",
|
|
1150
|
+
},
|
|
1151
|
+
],
|
|
1152
|
+
initialValue: "all",
|
|
1153
|
+
});
|
|
1154
|
+
if (isCancel(choice)) {
|
|
1155
|
+
cancel("Operation cancelled.");
|
|
1156
|
+
process.exit(1);
|
|
1157
|
+
}
|
|
1158
|
+
return choice === "custom" ? "custom" : "all";
|
|
1159
|
+
}
|
|
1160
|
+
async function resolveSelectionModeWithSkip(options) {
|
|
1161
|
+
if (options.selectionMode) {
|
|
1162
|
+
return {
|
|
1163
|
+
selectionMode: options.selectionMode,
|
|
1164
|
+
skipImport: false,
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
if (!options.promptForSelection || options.nonInteractive) {
|
|
1168
|
+
return {
|
|
1169
|
+
selectionMode: "all",
|
|
1170
|
+
skipImport: false,
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
const choice = await select({
|
|
1174
|
+
message: `How should ${options.entityLabel} be tracked for future updates?`,
|
|
1175
|
+
options: [
|
|
1176
|
+
{
|
|
1177
|
+
value: "all",
|
|
1178
|
+
label: "Sync everything from source",
|
|
1179
|
+
hint: "Include new items on update",
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
value: "custom",
|
|
1183
|
+
label: "Use custom selection",
|
|
1184
|
+
hint: "Update only currently selected items",
|
|
1185
|
+
},
|
|
1186
|
+
{
|
|
1187
|
+
value: "skip",
|
|
1188
|
+
label: `Skip importing ${options.entityLabel}`,
|
|
1189
|
+
hint: "Track none from this source",
|
|
1190
|
+
},
|
|
1191
|
+
],
|
|
1192
|
+
initialValue: "all",
|
|
1193
|
+
});
|
|
1194
|
+
if (isCancel(choice)) {
|
|
1195
|
+
cancel("Operation cancelled.");
|
|
1196
|
+
process.exit(1);
|
|
1197
|
+
}
|
|
1198
|
+
if (choice === "skip") {
|
|
1199
|
+
return {
|
|
1200
|
+
selectionMode: "custom",
|
|
1201
|
+
skipImport: true,
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
return {
|
|
1205
|
+
selectionMode: choice === "custom" ? "custom" : "all",
|
|
1206
|
+
skipImport: false,
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
146
1209
|
function normalizeMcp(raw) {
|
|
147
1210
|
if (!raw) {
|
|
148
1211
|
return {
|