agentloom 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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/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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { parseAgentsDir } from "../core/agents.js";
|
|
2
|
+
import { formatUsageError } from "../core/copy.js";
|
|
3
|
+
import { runScopedAddCommand } from "./add.js";
|
|
4
|
+
import { runScopedDeleteCommand } from "./delete.js";
|
|
5
|
+
import { resolvePathsForCommand } from "./entity-utils.js";
|
|
6
|
+
import { runScopedFindCommand } from "./find.js";
|
|
7
|
+
import { runScopedSyncCommand } from "./sync.js";
|
|
8
|
+
import { runScopedUpdateCommand } from "./update.js";
|
|
9
|
+
export async function runAgentCommand(argv, cwd) {
|
|
10
|
+
const action = argv._[1];
|
|
11
|
+
if (argv.help || !action) {
|
|
12
|
+
console.log("Usage:\n agentloom agent <add|list|delete|find|update|sync> [options]");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (action !== "add" &&
|
|
16
|
+
action !== "list" &&
|
|
17
|
+
action !== "delete" &&
|
|
18
|
+
action !== "find" &&
|
|
19
|
+
action !== "update" &&
|
|
20
|
+
action !== "sync") {
|
|
21
|
+
throw new Error(formatUsageError({
|
|
22
|
+
issue: "Invalid agent command.",
|
|
23
|
+
usage: "agentloom agent <add|list|delete|find|update|sync> [options]",
|
|
24
|
+
example: "agentloom agent add farnoodma/agents",
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
if (action === "list") {
|
|
28
|
+
const paths = await resolvePathsForCommand(argv, cwd);
|
|
29
|
+
const agents = parseAgentsDir(paths.agentsDir);
|
|
30
|
+
if (Boolean(argv.json)) {
|
|
31
|
+
console.log(JSON.stringify({
|
|
32
|
+
version: 1,
|
|
33
|
+
agents: agents.map((agent) => ({
|
|
34
|
+
name: agent.name,
|
|
35
|
+
fileName: agent.fileName,
|
|
36
|
+
})),
|
|
37
|
+
}, null, 2));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (agents.length === 0) {
|
|
41
|
+
console.log("No canonical agents configured.");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
for (const agent of agents) {
|
|
45
|
+
console.log(`${agent.name} (${agent.fileName})`);
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (action === "add") {
|
|
50
|
+
await runScopedAddCommand({
|
|
51
|
+
argv,
|
|
52
|
+
cwd,
|
|
53
|
+
entity: "agent",
|
|
54
|
+
sourceIndex: 2,
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (action === "delete") {
|
|
59
|
+
await runScopedDeleteCommand({
|
|
60
|
+
argv,
|
|
61
|
+
cwd,
|
|
62
|
+
entity: "agent",
|
|
63
|
+
sourceIndex: 2,
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (action === "find") {
|
|
68
|
+
await runScopedFindCommand(argv, "agent");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (action === "update") {
|
|
72
|
+
await runScopedUpdateCommand({
|
|
73
|
+
argv,
|
|
74
|
+
cwd,
|
|
75
|
+
entity: "agent",
|
|
76
|
+
sourceIndex: 2,
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
await runScopedSyncCommand({
|
|
81
|
+
argv,
|
|
82
|
+
cwd,
|
|
83
|
+
target: "agent",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { parseCommandsDir } from "../core/commands.js";
|
|
2
|
+
import { formatUsageError, getCommandAddHelpText, getCommandDeleteHelpText, getCommandHelpText, getCommandListHelpText, } from "../core/copy.js";
|
|
3
|
+
import { runScopedAddCommand } from "./add.js";
|
|
4
|
+
import { runScopedDeleteCommand } from "./delete.js";
|
|
5
|
+
import { resolvePathsForCommand } from "./entity-utils.js";
|
|
6
|
+
import { runScopedFindCommand } from "./find.js";
|
|
7
|
+
import { runScopedSyncCommand } from "./sync.js";
|
|
8
|
+
import { runScopedUpdateCommand } from "./update.js";
|
|
9
|
+
export async function runCommandCommand(argv, cwd) {
|
|
10
|
+
const action = argv._[1];
|
|
11
|
+
if (argv.help) {
|
|
12
|
+
if (action === "add") {
|
|
13
|
+
console.log(getCommandAddHelpText());
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (action === "list") {
|
|
17
|
+
console.log(getCommandListHelpText());
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (action === "delete") {
|
|
21
|
+
console.log(getCommandDeleteHelpText());
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
console.log(getCommandHelpText());
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!action) {
|
|
28
|
+
console.log(getCommandHelpText());
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (action !== "add" &&
|
|
32
|
+
action !== "list" &&
|
|
33
|
+
action !== "delete" &&
|
|
34
|
+
action !== "find" &&
|
|
35
|
+
action !== "update" &&
|
|
36
|
+
action !== "sync") {
|
|
37
|
+
throw new Error(formatUsageError({
|
|
38
|
+
issue: "Invalid command command.",
|
|
39
|
+
usage: "agentloom command <add|list|delete|find|update|sync> [options]",
|
|
40
|
+
example: "agentloom command add farnoodma/agents",
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
if (action === "list") {
|
|
44
|
+
const paths = await resolvePathsForCommand(argv, cwd);
|
|
45
|
+
const commands = parseCommandsDir(paths.commandsDir);
|
|
46
|
+
if (Boolean(argv.json)) {
|
|
47
|
+
console.log(JSON.stringify({
|
|
48
|
+
version: 1,
|
|
49
|
+
commands: commands.map((command) => command.fileName),
|
|
50
|
+
}, null, 2));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (commands.length === 0) {
|
|
54
|
+
console.log("No canonical command files configured.");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
for (const command of commands) {
|
|
58
|
+
console.log(command.fileName);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (action === "add") {
|
|
63
|
+
await runScopedAddCommand({
|
|
64
|
+
argv,
|
|
65
|
+
cwd,
|
|
66
|
+
entity: "command",
|
|
67
|
+
sourceIndex: 2,
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (action === "delete") {
|
|
72
|
+
await runScopedDeleteCommand({
|
|
73
|
+
argv,
|
|
74
|
+
cwd,
|
|
75
|
+
entity: "command",
|
|
76
|
+
sourceIndex: 2,
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (action === "find") {
|
|
81
|
+
await runScopedFindCommand(argv, "command");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (action === "update") {
|
|
85
|
+
await runScopedUpdateCommand({
|
|
86
|
+
argv,
|
|
87
|
+
cwd,
|
|
88
|
+
entity: "command",
|
|
89
|
+
sourceIndex: 2,
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
await runScopedSyncCommand({
|
|
94
|
+
argv,
|
|
95
|
+
cwd,
|
|
96
|
+
target: "command",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ParsedArgs } from "minimist";
|
|
2
|
+
import type { EntityType } from "../types.js";
|
|
3
|
+
export declare function runDeleteCommand(argv: ParsedArgs, cwd: string): Promise<void>;
|
|
4
|
+
export declare function runScopedDeleteCommand(options: {
|
|
5
|
+
argv: ParsedArgs;
|
|
6
|
+
cwd: string;
|
|
7
|
+
entity: EntityType;
|
|
8
|
+
sourceIndex: number;
|
|
9
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { multiselect, isCancel, cancel, select } from "@clack/prompts";
|
|
4
|
+
import { parseAgentsDir } from "../core/agents.js";
|
|
5
|
+
import { parseCommandsDir } from "../core/commands.js";
|
|
6
|
+
import { formatUsageError } from "../core/copy.js";
|
|
7
|
+
import { readLockfile, writeLockfile } from "../core/lockfile.js";
|
|
8
|
+
import { readCanonicalMcp, writeCanonicalMcp } from "../core/mcp.js";
|
|
9
|
+
import { parseSourceSpec } from "../core/sources.js";
|
|
10
|
+
import { parseSkillsDir } from "../core/skills.js";
|
|
11
|
+
import { getNonInteractiveMode, resolvePathsForCommand, runPostMutationSync, } from "./entity-utils.js";
|
|
12
|
+
const ALL_ENTITIES = ["agent", "command", "mcp", "skill"];
|
|
13
|
+
const MULTISELECT_HELP_TEXT = "↑↓ move, space select, enter confirm";
|
|
14
|
+
function withMultiselectHelp(message) {
|
|
15
|
+
return `${message}\n${MULTISELECT_HELP_TEXT}`;
|
|
16
|
+
}
|
|
17
|
+
export async function runDeleteCommand(argv, cwd) {
|
|
18
|
+
await runEntityAwareDelete({
|
|
19
|
+
argv,
|
|
20
|
+
cwd,
|
|
21
|
+
target: "all",
|
|
22
|
+
sourceIndex: 1,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export async function runScopedDeleteCommand(options) {
|
|
26
|
+
await runEntityAwareDelete({
|
|
27
|
+
argv: options.argv,
|
|
28
|
+
cwd: options.cwd,
|
|
29
|
+
target: options.entity,
|
|
30
|
+
sourceIndex: options.sourceIndex,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async function runEntityAwareDelete(options) {
|
|
34
|
+
const candidate = getDeleteCandidate(options.argv, options.sourceIndex);
|
|
35
|
+
if (!candidate) {
|
|
36
|
+
throw new Error(formatUsageError({
|
|
37
|
+
issue: "Missing required <source|name>.",
|
|
38
|
+
usage: options.target === "all"
|
|
39
|
+
? "agentloom delete <source|name> [options]"
|
|
40
|
+
: `agentloom ${options.target} delete <source|name> [options]`,
|
|
41
|
+
example: options.target === "all"
|
|
42
|
+
? "agentloom delete farnoodma/agents"
|
|
43
|
+
: `agentloom ${options.target} delete reviewer`,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
const nonInteractive = getNonInteractiveMode(options.argv);
|
|
47
|
+
const paths = await resolvePathsForCommand(options.argv, options.cwd);
|
|
48
|
+
const lockfile = readLockfile(paths);
|
|
49
|
+
const sourceMatches = lockfile.entries.filter((entry) => matchesSource(entry, candidate));
|
|
50
|
+
const sourceMode = sourceMatches.length > 0 ||
|
|
51
|
+
(typeof options.argv.source === "string" &&
|
|
52
|
+
options.argv.source.trim() !== "");
|
|
53
|
+
const entities = await resolveEntitiesForDelete({
|
|
54
|
+
argv: options.argv,
|
|
55
|
+
target: options.target,
|
|
56
|
+
sourceMode,
|
|
57
|
+
nonInteractive,
|
|
58
|
+
});
|
|
59
|
+
if (entities.length === 0) {
|
|
60
|
+
console.log("No entities selected for deletion.");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (sourceMode) {
|
|
64
|
+
await deleteBySource({
|
|
65
|
+
paths,
|
|
66
|
+
sourceFilter: candidate,
|
|
67
|
+
lockfile,
|
|
68
|
+
lockEntries: sourceMatches,
|
|
69
|
+
entities,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
await deleteByName({
|
|
74
|
+
candidate,
|
|
75
|
+
argv: options.argv,
|
|
76
|
+
paths,
|
|
77
|
+
entities,
|
|
78
|
+
nonInteractive,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
await runPostMutationSync({
|
|
82
|
+
argv: options.argv,
|
|
83
|
+
paths,
|
|
84
|
+
target: options.target,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function getDeleteCandidate(argv, sourceIndex) {
|
|
88
|
+
const fromSourceFlag = typeof argv.source === "string" && argv.source.trim().length > 0
|
|
89
|
+
? argv.source.trim()
|
|
90
|
+
: undefined;
|
|
91
|
+
if (fromSourceFlag)
|
|
92
|
+
return fromSourceFlag;
|
|
93
|
+
const fromNameFlag = typeof argv.name === "string" && argv.name.trim().length > 0
|
|
94
|
+
? argv.name.trim()
|
|
95
|
+
: undefined;
|
|
96
|
+
if (fromNameFlag)
|
|
97
|
+
return fromNameFlag;
|
|
98
|
+
const positional = argv._[sourceIndex];
|
|
99
|
+
if (typeof positional !== "string" || positional.trim().length === 0) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
return positional.trim();
|
|
103
|
+
}
|
|
104
|
+
async function resolveEntitiesForDelete(options) {
|
|
105
|
+
if (options.target !== "all")
|
|
106
|
+
return [options.target];
|
|
107
|
+
const entityFlag = typeof options.argv.entity === "string" ? options.argv.entity.trim() : "";
|
|
108
|
+
if (entityFlag) {
|
|
109
|
+
if (!ALL_ENTITIES.includes(entityFlag)) {
|
|
110
|
+
throw new Error(`Unknown --entity value "${entityFlag}". Expected one of: ${ALL_ENTITIES.join(", ")}.`);
|
|
111
|
+
}
|
|
112
|
+
return [entityFlag];
|
|
113
|
+
}
|
|
114
|
+
if (options.sourceMode) {
|
|
115
|
+
if (options.nonInteractive) {
|
|
116
|
+
return [...ALL_ENTITIES];
|
|
117
|
+
}
|
|
118
|
+
const selected = await multiselect({
|
|
119
|
+
message: withMultiselectHelp("Delete from which entities?"),
|
|
120
|
+
options: ALL_ENTITIES.map((entity) => ({
|
|
121
|
+
value: entity,
|
|
122
|
+
label: entity,
|
|
123
|
+
})),
|
|
124
|
+
initialValues: [...ALL_ENTITIES],
|
|
125
|
+
});
|
|
126
|
+
if (isCancel(selected)) {
|
|
127
|
+
cancel("Operation cancelled.");
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
return Array.isArray(selected)
|
|
131
|
+
? selected.map((value) => value)
|
|
132
|
+
: [];
|
|
133
|
+
}
|
|
134
|
+
return [...ALL_ENTITIES];
|
|
135
|
+
}
|
|
136
|
+
function matchesSource(entry, input) {
|
|
137
|
+
if (entry.source === input)
|
|
138
|
+
return true;
|
|
139
|
+
try {
|
|
140
|
+
const spec = parseSourceSpec(input);
|
|
141
|
+
return entry.source === spec.source;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function deleteBySource(options) {
|
|
148
|
+
if (options.lockEntries.length === 0) {
|
|
149
|
+
throw new Error("No matching lock entries found for the provided source.");
|
|
150
|
+
}
|
|
151
|
+
const mcp = readCanonicalMcp(options.paths);
|
|
152
|
+
for (const entry of options.lockEntries) {
|
|
153
|
+
if (options.entities.includes("agent")) {
|
|
154
|
+
for (const imported of entry.importedAgents) {
|
|
155
|
+
removeIfExists(path.join(options.paths.agentsRoot, imported));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (options.entities.includes("command")) {
|
|
159
|
+
for (const imported of entry.importedCommands) {
|
|
160
|
+
removeIfExists(path.join(options.paths.agentsRoot, imported));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (options.entities.includes("mcp")) {
|
|
164
|
+
for (const server of entry.importedMcpServers) {
|
|
165
|
+
delete mcp.mcpServers[server];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (options.entities.includes("skill")) {
|
|
169
|
+
for (const skill of entry.importedSkills) {
|
|
170
|
+
removeIfExists(path.join(options.paths.skillsDir, skill));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (options.entities.includes("mcp")) {
|
|
175
|
+
writeCanonicalMcp(options.paths, mcp);
|
|
176
|
+
}
|
|
177
|
+
options.lockfile.entries = options.lockfile.entries
|
|
178
|
+
.map((entry) => {
|
|
179
|
+
if (!matchesSource(entry, options.sourceFilter))
|
|
180
|
+
return entry;
|
|
181
|
+
return removeEntityDataFromEntry(entry, options.entities);
|
|
182
|
+
})
|
|
183
|
+
.filter((entry) => Boolean(entry));
|
|
184
|
+
writeLockfile(options.paths, options.lockfile);
|
|
185
|
+
}
|
|
186
|
+
async function deleteByName(options) {
|
|
187
|
+
let selectedEntities = options.entities;
|
|
188
|
+
if (selectedEntities.length > 1) {
|
|
189
|
+
const matches = detectNameMatches(options.paths, options.candidate);
|
|
190
|
+
const matchingEntities = selectedEntities.filter((entity) => matches.includes(entity));
|
|
191
|
+
if (matchingEntities.length === 0) {
|
|
192
|
+
throw new Error(`No installed entity named "${options.candidate}" found.`);
|
|
193
|
+
}
|
|
194
|
+
if (matchingEntities.length > 1) {
|
|
195
|
+
if (options.nonInteractive) {
|
|
196
|
+
throw new Error(`Name "${options.candidate}" matches multiple entities (${matchingEntities.join(", ")}). Use --entity for non-interactive deletion.`);
|
|
197
|
+
}
|
|
198
|
+
const picked = await select({
|
|
199
|
+
message: `Name "${options.candidate}" exists in multiple entities. Which should be deleted?`,
|
|
200
|
+
options: matchingEntities.map((entity) => ({
|
|
201
|
+
value: entity,
|
|
202
|
+
label: entity,
|
|
203
|
+
})),
|
|
204
|
+
});
|
|
205
|
+
if (isCancel(picked)) {
|
|
206
|
+
cancel("Operation cancelled.");
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
selectedEntities = [picked];
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
selectedEntities = matchingEntities;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const mcp = readCanonicalMcp(options.paths);
|
|
216
|
+
for (const entity of selectedEntities) {
|
|
217
|
+
if (entity === "agent") {
|
|
218
|
+
deleteAgentByName(options.paths, options.candidate);
|
|
219
|
+
}
|
|
220
|
+
else if (entity === "command") {
|
|
221
|
+
deleteCommandByName(options.paths, options.candidate);
|
|
222
|
+
}
|
|
223
|
+
else if (entity === "mcp") {
|
|
224
|
+
const existing = Object.keys(mcp.mcpServers).find((name) => normalizeName(name) === normalizeName(options.candidate));
|
|
225
|
+
if (!existing) {
|
|
226
|
+
throw new Error(`MCP server "${options.candidate}" was not found.`);
|
|
227
|
+
}
|
|
228
|
+
delete mcp.mcpServers[existing];
|
|
229
|
+
}
|
|
230
|
+
else if (entity === "skill") {
|
|
231
|
+
deleteSkillByName(options.paths, options.candidate);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (selectedEntities.includes("mcp")) {
|
|
235
|
+
writeCanonicalMcp(options.paths, mcp);
|
|
236
|
+
}
|
|
237
|
+
const lockfile = readLockfile(options.paths);
|
|
238
|
+
lockfile.entries = lockfile.entries
|
|
239
|
+
.map((entry) => removeNameFromEntry(entry, selectedEntities, options.candidate))
|
|
240
|
+
.filter((entry) => Boolean(entry));
|
|
241
|
+
writeLockfile(options.paths, lockfile);
|
|
242
|
+
}
|
|
243
|
+
function deleteAgentByName(paths, name) {
|
|
244
|
+
const agents = parseAgentsDir(paths.agentsDir);
|
|
245
|
+
const target = agents.find((agent) => normalizeName(agent.name) === normalizeName(name) ||
|
|
246
|
+
normalizeName(agent.fileName.replace(/\.md$/i, "")) ===
|
|
247
|
+
normalizeName(name));
|
|
248
|
+
if (!target) {
|
|
249
|
+
throw new Error(`Agent "${name}" was not found in canonical agents.`);
|
|
250
|
+
}
|
|
251
|
+
removeIfExists(target.sourcePath);
|
|
252
|
+
}
|
|
253
|
+
function deleteCommandByName(paths, name) {
|
|
254
|
+
const commands = parseCommandsDir(paths.commandsDir);
|
|
255
|
+
const target = commands.find((command) => normalizeName(command.fileName) === normalizeName(name) ||
|
|
256
|
+
normalizeName(command.fileName.replace(/\.(md|mdc)$/i, "")) ===
|
|
257
|
+
normalizeName(name));
|
|
258
|
+
if (!target) {
|
|
259
|
+
throw new Error(`Command "${name}" was not found in canonical commands.`);
|
|
260
|
+
}
|
|
261
|
+
removeIfExists(target.sourcePath);
|
|
262
|
+
}
|
|
263
|
+
function deleteSkillByName(paths, name) {
|
|
264
|
+
const skills = parseSkillsDir(paths.skillsDir);
|
|
265
|
+
const target = skills.find((skill) => normalizeName(skill.name) === normalizeName(name) ||
|
|
266
|
+
normalizeName(path.basename(skill.sourcePath)) === normalizeName(name));
|
|
267
|
+
if (!target) {
|
|
268
|
+
throw new Error(`Skill "${name}" was not found in canonical skills.`);
|
|
269
|
+
}
|
|
270
|
+
removeIfExists(target.sourcePath);
|
|
271
|
+
}
|
|
272
|
+
function removeIfExists(filePath) {
|
|
273
|
+
if (!fs.existsSync(filePath))
|
|
274
|
+
return;
|
|
275
|
+
fs.rmSync(filePath, { recursive: true, force: true });
|
|
276
|
+
}
|
|
277
|
+
function normalizeName(value) {
|
|
278
|
+
return value.trim().toLowerCase();
|
|
279
|
+
}
|
|
280
|
+
function detectNameMatches(paths, candidate) {
|
|
281
|
+
const normalized = normalizeName(candidate);
|
|
282
|
+
const matches = [];
|
|
283
|
+
const hasAgent = parseAgentsDir(paths.agentsDir).some((agent) => normalizeName(agent.name) === normalized ||
|
|
284
|
+
normalizeName(agent.fileName.replace(/\.md$/i, "")) === normalized);
|
|
285
|
+
if (hasAgent)
|
|
286
|
+
matches.push("agent");
|
|
287
|
+
const hasCommand = parseCommandsDir(paths.commandsDir).some((command) => normalizeName(command.fileName) === normalized ||
|
|
288
|
+
normalizeName(command.fileName.replace(/\.(md|mdc)$/i, "")) ===
|
|
289
|
+
normalized);
|
|
290
|
+
if (hasCommand)
|
|
291
|
+
matches.push("command");
|
|
292
|
+
const mcp = readCanonicalMcp(paths);
|
|
293
|
+
const hasMcp = Object.keys(mcp.mcpServers).some((name) => normalizeName(name) === normalized);
|
|
294
|
+
if (hasMcp)
|
|
295
|
+
matches.push("mcp");
|
|
296
|
+
const hasSkill = parseSkillsDir(paths.skillsDir).some((skill) => normalizeName(skill.name) === normalized);
|
|
297
|
+
if (hasSkill)
|
|
298
|
+
matches.push("skill");
|
|
299
|
+
return matches;
|
|
300
|
+
}
|
|
301
|
+
function removeEntityDataFromEntry(entry, entities) {
|
|
302
|
+
let next = { ...entry };
|
|
303
|
+
if (entities.includes("agent")) {
|
|
304
|
+
next = {
|
|
305
|
+
...next,
|
|
306
|
+
importedAgents: [],
|
|
307
|
+
requestedAgents: undefined,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (entities.includes("command")) {
|
|
311
|
+
next = {
|
|
312
|
+
...next,
|
|
313
|
+
importedCommands: [],
|
|
314
|
+
selectedSourceCommands: undefined,
|
|
315
|
+
commandRenameMap: undefined,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (entities.includes("mcp")) {
|
|
319
|
+
next = {
|
|
320
|
+
...next,
|
|
321
|
+
importedMcpServers: [],
|
|
322
|
+
selectedSourceMcpServers: undefined,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (entities.includes("skill")) {
|
|
326
|
+
next = {
|
|
327
|
+
...next,
|
|
328
|
+
importedSkills: [],
|
|
329
|
+
selectedSourceSkills: undefined,
|
|
330
|
+
skillsProviders: undefined,
|
|
331
|
+
skillRenameMap: undefined,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
return finalizeEntry(next);
|
|
335
|
+
}
|
|
336
|
+
function removeNameFromEntry(entry, entities, candidate) {
|
|
337
|
+
let next = { ...entry };
|
|
338
|
+
const normalized = normalizeName(candidate);
|
|
339
|
+
if (entities.includes("agent")) {
|
|
340
|
+
next.importedAgents = next.importedAgents.filter((item) => {
|
|
341
|
+
const base = path.basename(item).replace(/\.md$/i, "");
|
|
342
|
+
return normalizeName(base) !== normalized;
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (entities.includes("command")) {
|
|
346
|
+
const before = [...next.importedCommands];
|
|
347
|
+
next.importedCommands = next.importedCommands.filter((item) => {
|
|
348
|
+
const base = path.basename(item).replace(/\.(md|mdc)$/i, "");
|
|
349
|
+
return normalizeName(base) !== normalized;
|
|
350
|
+
});
|
|
351
|
+
if (next.importedCommands.length !== before.length) {
|
|
352
|
+
const remainingImportedNames = new Set(next.importedCommands.map((item) => path.basename(item)));
|
|
353
|
+
if (next.commandRenameMap) {
|
|
354
|
+
const filteredRenameEntries = Object.entries(next.commandRenameMap).filter(([, importedName]) => remainingImportedNames.has(path.basename(importedName)));
|
|
355
|
+
next.commandRenameMap =
|
|
356
|
+
filteredRenameEntries.length > 0
|
|
357
|
+
? Object.fromEntries(filteredRenameEntries)
|
|
358
|
+
: undefined;
|
|
359
|
+
}
|
|
360
|
+
const selectorsFromRenameMap = next.commandRenameMap
|
|
361
|
+
? Object.keys(next.commandRenameMap)
|
|
362
|
+
: [];
|
|
363
|
+
next.selectedSourceCommands =
|
|
364
|
+
selectorsFromRenameMap.length > 0
|
|
365
|
+
? selectorsFromRenameMap
|
|
366
|
+
: next.importedCommands.map((item) => path.basename(item));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (entities.includes("mcp")) {
|
|
370
|
+
const before = [...next.importedMcpServers];
|
|
371
|
+
next.importedMcpServers = next.importedMcpServers.filter((name) => normalizeName(name) !== normalized);
|
|
372
|
+
if (next.importedMcpServers.length !== before.length) {
|
|
373
|
+
next.selectedSourceMcpServers = [...next.importedMcpServers];
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (entities.includes("skill")) {
|
|
377
|
+
const renameEntriesBeforeRemoval = Object.entries(next.skillRenameMap ?? {});
|
|
378
|
+
const removedSkillTargets = next.importedSkills.filter((name) => normalizeName(name) === normalized);
|
|
379
|
+
next.importedSkills = next.importedSkills.filter((name) => normalizeName(name) !== normalized);
|
|
380
|
+
if (next.skillRenameMap) {
|
|
381
|
+
const remainingImportedSkills = new Set(next.importedSkills.map((skillName) => normalizeName(skillName)));
|
|
382
|
+
const filteredRenameEntries = Object.entries(next.skillRenameMap).filter(([, importedName]) => remainingImportedSkills.has(normalizeName(importedName)));
|
|
383
|
+
next.skillRenameMap =
|
|
384
|
+
filteredRenameEntries.length > 0
|
|
385
|
+
? Object.fromEntries(filteredRenameEntries)
|
|
386
|
+
: undefined;
|
|
387
|
+
}
|
|
388
|
+
const removedSourceSelectors = new Set();
|
|
389
|
+
for (const removedTarget of removedSkillTargets) {
|
|
390
|
+
for (const [sourceSelector, importedName] of renameEntriesBeforeRemoval) {
|
|
391
|
+
if (normalizeName(importedName) === normalizeName(removedTarget)) {
|
|
392
|
+
removedSourceSelectors.add(normalizeName(sourceSelector));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
removedSourceSelectors.add(normalizeName(removedTarget));
|
|
396
|
+
}
|
|
397
|
+
if (next.selectedSourceSkills) {
|
|
398
|
+
next.selectedSourceSkills = next.selectedSourceSkills.filter((name) => !removedSourceSelectors.has(normalizeName(name)));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return finalizeEntry(next);
|
|
402
|
+
}
|
|
403
|
+
function finalizeEntry(entry) {
|
|
404
|
+
const hasOtherEntities = entry.importedAgents.length > 0 ||
|
|
405
|
+
entry.importedMcpServers.length > 0 ||
|
|
406
|
+
entry.importedSkills.length > 0;
|
|
407
|
+
if (entry.importedCommands.length === 0) {
|
|
408
|
+
entry.commandRenameMap = undefined;
|
|
409
|
+
entry.selectedSourceCommands = hasOtherEntities ? [] : undefined;
|
|
410
|
+
}
|
|
411
|
+
if (entry.importedSkills.length === 0) {
|
|
412
|
+
entry.skillRenameMap = undefined;
|
|
413
|
+
entry.skillsProviders = undefined;
|
|
414
|
+
}
|
|
415
|
+
const trackedEntities = (entry.trackedEntities ?? []).filter((entity) => {
|
|
416
|
+
if (entity === "agent") {
|
|
417
|
+
return entry.importedAgents.length > 0 || Boolean(entry.requestedAgents);
|
|
418
|
+
}
|
|
419
|
+
if (entity === "command") {
|
|
420
|
+
return (entry.importedCommands.length > 0 ||
|
|
421
|
+
Boolean(entry.selectedSourceCommands) ||
|
|
422
|
+
Boolean(entry.commandRenameMap));
|
|
423
|
+
}
|
|
424
|
+
if (entity === "mcp") {
|
|
425
|
+
return (entry.importedMcpServers.length > 0 ||
|
|
426
|
+
Boolean(entry.selectedSourceMcpServers));
|
|
427
|
+
}
|
|
428
|
+
return (entry.importedSkills.length > 0 ||
|
|
429
|
+
Boolean(entry.selectedSourceSkills) ||
|
|
430
|
+
Boolean(entry.skillsProviders) ||
|
|
431
|
+
Boolean(entry.skillRenameMap));
|
|
432
|
+
});
|
|
433
|
+
const next = {
|
|
434
|
+
...entry,
|
|
435
|
+
trackedEntities: trackedEntities.length > 0 ? trackedEntities : undefined,
|
|
436
|
+
};
|
|
437
|
+
if (next.importedAgents.length === 0 &&
|
|
438
|
+
next.importedCommands.length === 0 &&
|
|
439
|
+
next.importedMcpServers.length === 0 &&
|
|
440
|
+
next.importedSkills.length === 0) {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
return next;
|
|
444
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ParsedArgs } from "minimist";
|
|
2
|
+
import type { EntityType, Provider, ScopePaths } from "../types.js";
|
|
3
|
+
export declare function isInteractiveSession(): boolean;
|
|
4
|
+
export declare function getNonInteractiveMode(argv: ParsedArgs): boolean;
|
|
5
|
+
export declare function resolvePathsForCommand(argv: ParsedArgs, cwd: string): Promise<ScopePaths>;
|
|
6
|
+
export declare function getEntitySelectors(argv: ParsedArgs, entity: EntityType): string[];
|
|
7
|
+
export declare function runPostMutationSync(options: {
|
|
8
|
+
argv: ParsedArgs;
|
|
9
|
+
paths: ScopePaths;
|
|
10
|
+
target: EntityType | "all";
|
|
11
|
+
providers?: Provider[];
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
export declare function markScopeAsUsed(paths: ScopePaths): void;
|