agentloom 0.1.5 → 0.1.7
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 +92 -6
- package/dist/cli.js +11 -4
- package/dist/commands/add.js +14 -0
- package/dist/commands/delete.js +89 -3
- package/dist/commands/entity-utils.js +3 -0
- package/dist/commands/find.js +146 -12
- package/dist/commands/rule.d.ts +2 -0
- package/dist/commands/rule.js +86 -0
- package/dist/commands/sync.js +13 -4
- package/dist/commands/update.js +90 -7
- package/dist/core/argv.js +2 -0
- package/dist/core/commands.d.ts +12 -0
- package/dist/core/commands.js +106 -6
- package/dist/core/copy.js +12 -12
- package/dist/core/importer.d.ts +10 -0
- package/dist/core/importer.js +941 -46
- package/dist/core/lockfile.js +8 -0
- package/dist/core/manifest.js +1 -1
- package/dist/core/migration.js +650 -66
- package/dist/core/provider-entity-validation.d.ts +8 -0
- package/dist/core/provider-entity-validation.js +34 -0
- package/dist/core/provider-paths.d.ts +8 -1
- package/dist/core/provider-paths.js +69 -5
- package/dist/core/router.js +17 -3
- package/dist/core/rules.d.ts +34 -0
- package/dist/core/rules.js +149 -0
- package/dist/core/scope.js +1 -0
- package/dist/core/skills.d.ts +4 -0
- package/dist/core/skills.js +47 -12
- package/dist/core/sources.d.ts +7 -0
- package/dist/core/sources.js +146 -22
- package/dist/core/telemetry.d.ts +1 -1
- package/dist/core/telemetry.js +16 -0
- package/dist/sync/index.js +403 -17
- package/dist/types.d.ts +5 -1
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
const COPILOT_AGENT_FILE = /\.agent\.md$/i;
|
|
3
|
+
const COPILOT_COMMAND_FILE = /\.prompt\.md$/i;
|
|
4
|
+
const GENERIC_AGENT_FILE = /\.md$/i;
|
|
5
|
+
const GENERIC_COMMAND_FILE = /(?:\.prompt)?\.(md|mdc)$/i;
|
|
6
|
+
const GEMINI_COMMAND_FILE = /\.(toml|md|mdc)$/i;
|
|
7
|
+
const EXCLUDED_ENTITY_STEMS = new Set(["readme"]);
|
|
8
|
+
export function isProviderEntityFileName(options) {
|
|
9
|
+
const normalizedName = path.basename(options.fileName);
|
|
10
|
+
const stem = normalizeEntityStem(normalizedName);
|
|
11
|
+
if (!stem || EXCLUDED_ENTITY_STEMS.has(stem)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
if (options.provider === "copilot") {
|
|
15
|
+
if (options.entity === "agent") {
|
|
16
|
+
return COPILOT_AGENT_FILE.test(normalizedName);
|
|
17
|
+
}
|
|
18
|
+
return COPILOT_COMMAND_FILE.test(normalizedName);
|
|
19
|
+
}
|
|
20
|
+
if (options.entity === "agent") {
|
|
21
|
+
return GENERIC_AGENT_FILE.test(normalizedName);
|
|
22
|
+
}
|
|
23
|
+
if (options.provider === "gemini") {
|
|
24
|
+
return GEMINI_COMMAND_FILE.test(normalizedName);
|
|
25
|
+
}
|
|
26
|
+
return GENERIC_COMMAND_FILE.test(normalizedName);
|
|
27
|
+
}
|
|
28
|
+
function normalizeEntityStem(fileName) {
|
|
29
|
+
return fileName
|
|
30
|
+
.toLowerCase()
|
|
31
|
+
.replace(/\.agent\.md$/i, "")
|
|
32
|
+
.replace(/\.prompt\.(md|mdc)$/i, "")
|
|
33
|
+
.replace(/\.(md|mdc|toml)$/i, "");
|
|
34
|
+
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { Provider, ScopePaths } from "../types.js";
|
|
2
2
|
export declare function getProviderAgentsDir(paths: ScopePaths, provider: Provider): string;
|
|
3
3
|
export declare function getProviderCommandsDir(paths: ScopePaths, provider: Provider): string;
|
|
4
|
-
export declare function getProviderSkillsPaths(paths: ScopePaths, providers: Provider[]): string[];
|
|
4
|
+
export declare function getProviderSkillsPaths(paths: ScopePaths, providers: readonly Provider[]): string[];
|
|
5
|
+
export declare function getCursorRulesDir(paths: ScopePaths): string;
|
|
6
|
+
export declare function getAgentsInstructionPath(paths: ScopePaths): string;
|
|
7
|
+
export declare function getClaudeInstructionPath(paths: ScopePaths): string;
|
|
8
|
+
export declare function getGeminiInstructionPath(paths: ScopePaths): string;
|
|
9
|
+
export declare function getCopilotInstructionPath(paths: ScopePaths): string;
|
|
10
|
+
export declare function getOpenCodeInstructionPath(paths: ScopePaths): string;
|
|
11
|
+
export declare function getRuleInstructionPaths(paths: ScopePaths, providers: readonly Provider[]): string[];
|
|
5
12
|
export declare function getCursorMcpPath(paths: ScopePaths): string;
|
|
6
13
|
export declare function getClaudeMcpPath(paths: ScopePaths): string;
|
|
7
14
|
export declare function getClaudeSettingsPath(paths: ScopePaths): string;
|
|
@@ -25,7 +25,7 @@ export function getProviderAgentsDir(paths, provider) {
|
|
|
25
25
|
case "copilot":
|
|
26
26
|
return paths.scope === "local"
|
|
27
27
|
? path.join(workspaceRoot, ".github", "agents")
|
|
28
|
-
: path.join(home, ".
|
|
28
|
+
: path.join(home, ".copilot", "agents");
|
|
29
29
|
case "pi":
|
|
30
30
|
return paths.scope === "local"
|
|
31
31
|
? path.join(workspaceRoot, ".pi", "agents")
|
|
@@ -59,7 +59,7 @@ export function getProviderCommandsDir(paths, provider) {
|
|
|
59
59
|
case "copilot":
|
|
60
60
|
return paths.scope === "local"
|
|
61
61
|
? path.join(workspaceRoot, ".github", "prompts")
|
|
62
|
-
: path.join(home, ".
|
|
62
|
+
: path.join(home, ".copilot", "prompts");
|
|
63
63
|
case "pi":
|
|
64
64
|
return paths.scope === "local"
|
|
65
65
|
? path.join(workspaceRoot, ".pi", "prompts")
|
|
@@ -70,12 +70,16 @@ export function getProviderCommandsDir(paths, provider) {
|
|
|
70
70
|
}
|
|
71
71
|
export function getProviderSkillsPaths(paths, providers) {
|
|
72
72
|
const targets = new Set();
|
|
73
|
-
|
|
74
|
-
if (hasClaudeStyleProvider) {
|
|
73
|
+
if (providers.includes("claude")) {
|
|
75
74
|
targets.add(paths.scope === "local"
|
|
76
75
|
? path.join(paths.workspaceRoot, ".claude", "skills")
|
|
77
76
|
: path.join(paths.homeDir, ".claude", "skills"));
|
|
78
77
|
}
|
|
78
|
+
if (providers.includes("copilot")) {
|
|
79
|
+
targets.add(paths.scope === "local"
|
|
80
|
+
? path.join(paths.workspaceRoot, ".github", "skills")
|
|
81
|
+
: path.join(paths.homeDir, ".copilot", "skills"));
|
|
82
|
+
}
|
|
79
83
|
if (providers.includes("cursor")) {
|
|
80
84
|
targets.add(paths.scope === "local"
|
|
81
85
|
? path.join(paths.workspaceRoot, ".cursor", "skills")
|
|
@@ -88,6 +92,66 @@ export function getProviderSkillsPaths(paths, providers) {
|
|
|
88
92
|
}
|
|
89
93
|
return [...targets];
|
|
90
94
|
}
|
|
95
|
+
export function getCursorRulesDir(paths) {
|
|
96
|
+
return paths.scope === "local"
|
|
97
|
+
? path.join(paths.workspaceRoot, ".cursor", "rules")
|
|
98
|
+
: path.join(paths.homeDir, ".cursor", "rules");
|
|
99
|
+
}
|
|
100
|
+
export function getAgentsInstructionPath(paths) {
|
|
101
|
+
return path.join(paths.workspaceRoot, "AGENTS.md");
|
|
102
|
+
}
|
|
103
|
+
export function getClaudeInstructionPath(paths) {
|
|
104
|
+
return paths.scope === "local"
|
|
105
|
+
? path.join(paths.workspaceRoot, "CLAUDE.md")
|
|
106
|
+
: path.join(paths.homeDir, ".claude", "CLAUDE.md");
|
|
107
|
+
}
|
|
108
|
+
export function getGeminiInstructionPath(paths) {
|
|
109
|
+
return paths.scope === "local"
|
|
110
|
+
? path.join(paths.workspaceRoot, "GEMINI.md")
|
|
111
|
+
: path.join(paths.homeDir, ".gemini", "GEMINI.md");
|
|
112
|
+
}
|
|
113
|
+
export function getCopilotInstructionPath(paths) {
|
|
114
|
+
return paths.scope === "local"
|
|
115
|
+
? path.join(paths.workspaceRoot, ".github", "copilot-instructions.md")
|
|
116
|
+
: path.join(paths.homeDir, ".copilot", "copilot-instructions.md");
|
|
117
|
+
}
|
|
118
|
+
export function getOpenCodeInstructionPath(paths) {
|
|
119
|
+
return paths.scope === "local"
|
|
120
|
+
? path.join(paths.workspaceRoot, "AGENTS.md")
|
|
121
|
+
: path.join(paths.homeDir, ".config", "opencode", "AGENTS.md");
|
|
122
|
+
}
|
|
123
|
+
export function getRuleInstructionPaths(paths, providers) {
|
|
124
|
+
const targets = new Set();
|
|
125
|
+
if (paths.scope === "local") {
|
|
126
|
+
targets.add(getAgentsInstructionPath(paths));
|
|
127
|
+
}
|
|
128
|
+
for (const provider of providers) {
|
|
129
|
+
if (provider === "claude") {
|
|
130
|
+
targets.add(getClaudeInstructionPath(paths));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (provider === "gemini") {
|
|
134
|
+
targets.add(getGeminiInstructionPath(paths));
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (provider === "copilot") {
|
|
138
|
+
targets.add(getCopilotInstructionPath(paths));
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (provider === "opencode") {
|
|
142
|
+
targets.add(getOpenCodeInstructionPath(paths));
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (provider === "codex" && paths.scope === "local") {
|
|
146
|
+
targets.add(getAgentsInstructionPath(paths));
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (provider === "pi" && paths.scope === "local") {
|
|
150
|
+
targets.add(getAgentsInstructionPath(paths));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return [...targets];
|
|
154
|
+
}
|
|
91
155
|
export function getCursorMcpPath(paths) {
|
|
92
156
|
return paths.scope === "local"
|
|
93
157
|
? path.join(paths.workspaceRoot, ".cursor", "mcp.json")
|
|
@@ -101,7 +165,7 @@ export function getClaudeMcpPath(paths) {
|
|
|
101
165
|
export function getClaudeSettingsPath(paths) {
|
|
102
166
|
return paths.scope === "local"
|
|
103
167
|
? path.join(paths.workspaceRoot, ".claude", "settings.json")
|
|
104
|
-
: path.join(paths.homeDir, ".claude.json");
|
|
168
|
+
: path.join(paths.homeDir, ".claude", "settings.json");
|
|
105
169
|
}
|
|
106
170
|
export function getOpenCodeConfigPath(paths) {
|
|
107
171
|
return paths.scope === "local"
|
package/dist/core/router.js
CHANGED
|
@@ -7,7 +7,20 @@ const AGGREGATE_VERBS = new Set([
|
|
|
7
7
|
"delete",
|
|
8
8
|
"init",
|
|
9
9
|
]);
|
|
10
|
-
const ENTITY_NOUNS = new Set([
|
|
10
|
+
const ENTITY_NOUNS = new Set([
|
|
11
|
+
"agent",
|
|
12
|
+
"command",
|
|
13
|
+
"mcp",
|
|
14
|
+
"rule",
|
|
15
|
+
"skill",
|
|
16
|
+
]);
|
|
17
|
+
const ENTITY_NOUN_ALIASES = {
|
|
18
|
+
agents: "agent",
|
|
19
|
+
commands: "command",
|
|
20
|
+
mcps: "mcp",
|
|
21
|
+
rules: "rule",
|
|
22
|
+
skills: "skill",
|
|
23
|
+
};
|
|
11
24
|
const ENTITY_VERBS = new Set([
|
|
12
25
|
"add",
|
|
13
26
|
"list",
|
|
@@ -18,9 +31,10 @@ const ENTITY_VERBS = new Set([
|
|
|
18
31
|
]);
|
|
19
32
|
const MCP_SERVER_VERBS = new Set(["add", "list", "delete"]);
|
|
20
33
|
export function parseCommandRoute(argv) {
|
|
21
|
-
const
|
|
22
|
-
if (!
|
|
34
|
+
const rawRoot = argv[0]?.trim().toLowerCase();
|
|
35
|
+
if (!rawRoot)
|
|
23
36
|
return null;
|
|
37
|
+
const root = ENTITY_NOUN_ALIASES[rawRoot] ?? rawRoot;
|
|
24
38
|
if (AGGREGATE_VERBS.has(root)) {
|
|
25
39
|
return {
|
|
26
40
|
mode: "aggregate",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface CanonicalRuleFile {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
fileName: string;
|
|
5
|
+
sourcePath: string;
|
|
6
|
+
content: string;
|
|
7
|
+
body: string;
|
|
8
|
+
frontmatter: Record<string, unknown> & {
|
|
9
|
+
name: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface ManagedRuleBlock {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
body: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function parseRulesDir(rulesDir: string): CanonicalRuleFile[];
|
|
18
|
+
export declare function parseRuleMarkdown(content: string, sourcePath?: string): {
|
|
19
|
+
name: string;
|
|
20
|
+
body: string;
|
|
21
|
+
frontmatter: Record<string, unknown> & {
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export declare function normalizeRuleSelector(selector: string): string;
|
|
26
|
+
export declare function resolveRuleSelections(rules: CanonicalRuleFile[], selectors: string[]): {
|
|
27
|
+
selected: CanonicalRuleFile[];
|
|
28
|
+
unmatched: string[];
|
|
29
|
+
};
|
|
30
|
+
export declare function renderRuleForCursor(rule: CanonicalRuleFile): string;
|
|
31
|
+
export declare function renderManagedRuleBlock(rule: CanonicalRuleFile): string;
|
|
32
|
+
export declare function parseManagedRuleBlocks(content: string): ManagedRuleBlock[];
|
|
33
|
+
export declare function upsertManagedRuleBlocks(existingContent: string, rules: CanonicalRuleFile[]): string;
|
|
34
|
+
export declare function stripRuleFileExtension(fileName: string): string;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
import YAML from "yaml";
|
|
5
|
+
import { listMarkdownFiles, slugify } from "./fs.js";
|
|
6
|
+
const MANAGED_BLOCK_PATTERN = /<!--\s*agentloom:([a-z0-9._-]+):start\s*-->[\s\S]*?<!--\s*agentloom:\1:end\s*-->\s*/gi;
|
|
7
|
+
const MANAGED_BLOCK_CAPTURE_PATTERN = /<!--\s*agentloom:([a-z0-9._-]+):start\s*-->\s*([\s\S]*?)\s*<!--\s*agentloom:\1:end\s*-->/gi;
|
|
8
|
+
export function parseRulesDir(rulesDir) {
|
|
9
|
+
if (!fs.existsSync(rulesDir))
|
|
10
|
+
return [];
|
|
11
|
+
return listMarkdownFiles(rulesDir)
|
|
12
|
+
.sort((a, b) => a.localeCompare(b))
|
|
13
|
+
.map((sourcePath) => {
|
|
14
|
+
const content = fs.readFileSync(sourcePath, "utf8");
|
|
15
|
+
const fileName = path.basename(sourcePath);
|
|
16
|
+
const parsed = parseRuleMarkdown(content, sourcePath);
|
|
17
|
+
return {
|
|
18
|
+
id: stripRuleFileExtension(fileName),
|
|
19
|
+
name: parsed.name,
|
|
20
|
+
fileName,
|
|
21
|
+
sourcePath,
|
|
22
|
+
content,
|
|
23
|
+
body: parsed.body,
|
|
24
|
+
frontmatter: parsed.frontmatter,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export function parseRuleMarkdown(content, sourcePath = "<rule>") {
|
|
29
|
+
const parsed = matter(content);
|
|
30
|
+
if (!parsed.data ||
|
|
31
|
+
typeof parsed.data !== "object" ||
|
|
32
|
+
Array.isArray(parsed.data)) {
|
|
33
|
+
throw new Error(`Rule "${sourcePath}" must include YAML frontmatter with required "name".`);
|
|
34
|
+
}
|
|
35
|
+
const frontmatter = parsed.data;
|
|
36
|
+
if (typeof frontmatter.name !== "string" || frontmatter.name.trim() === "") {
|
|
37
|
+
throw new Error(`Rule "${sourcePath}" is missing required frontmatter.name.`);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
name: frontmatter.name.trim(),
|
|
41
|
+
body: parsed.content.trimStart(),
|
|
42
|
+
frontmatter: {
|
|
43
|
+
...frontmatter,
|
|
44
|
+
name: frontmatter.name.trim(),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function normalizeRuleSelector(selector) {
|
|
49
|
+
const trimmed = selector.trim().replace(/^\/+/, "");
|
|
50
|
+
const withoutExt = stripRuleFileExtension(trimmed);
|
|
51
|
+
const normalized = slugify(withoutExt);
|
|
52
|
+
return normalized || withoutExt.toLowerCase();
|
|
53
|
+
}
|
|
54
|
+
export function resolveRuleSelections(rules, selectors) {
|
|
55
|
+
const selectedById = new Map();
|
|
56
|
+
const unmatched = [];
|
|
57
|
+
for (const selector of selectors) {
|
|
58
|
+
const normalizedSelector = normalizeRuleSelector(selector);
|
|
59
|
+
if (!normalizedSelector) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const matches = rules.filter((rule) => {
|
|
63
|
+
const exactCandidates = new Set([
|
|
64
|
+
rule.id.toLowerCase(),
|
|
65
|
+
stripRuleFileExtension(rule.fileName).toLowerCase(),
|
|
66
|
+
]);
|
|
67
|
+
const slugCandidates = new Set([
|
|
68
|
+
normalizeRuleSelector(rule.id),
|
|
69
|
+
normalizeRuleSelector(stripRuleFileExtension(rule.fileName)),
|
|
70
|
+
normalizeRuleSelector(rule.name),
|
|
71
|
+
]);
|
|
72
|
+
return (exactCandidates.has(normalizedSelector) ||
|
|
73
|
+
slugCandidates.has(normalizedSelector));
|
|
74
|
+
});
|
|
75
|
+
if (matches.length === 0) {
|
|
76
|
+
unmatched.push(selector);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
for (const match of matches) {
|
|
80
|
+
selectedById.set(match.id, match);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
selected: [...selectedById.values()],
|
|
85
|
+
unmatched,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function renderRuleForCursor(rule) {
|
|
89
|
+
const fm = YAML.stringify(rule.frontmatter, { lineWidth: 0 }).trimEnd();
|
|
90
|
+
const body = rule.body.trimStart();
|
|
91
|
+
return `---\n${fm}\n---\n\n${body}${body.endsWith("\n") ? "" : "\n"}`;
|
|
92
|
+
}
|
|
93
|
+
export function renderManagedRuleBlock(rule) {
|
|
94
|
+
const body = rule.body.trim();
|
|
95
|
+
const heading = `## ${rule.name}`;
|
|
96
|
+
const content = body ? `${heading}\n\n${body}` : heading;
|
|
97
|
+
return `<!-- agentloom:${rule.id}:start -->\n${content}\n<!-- agentloom:${rule.id}:end -->`;
|
|
98
|
+
}
|
|
99
|
+
export function parseManagedRuleBlocks(content) {
|
|
100
|
+
const blocks = [];
|
|
101
|
+
content.replace(MANAGED_BLOCK_CAPTURE_PATTERN, (_match, rawId, rawInner) => {
|
|
102
|
+
const id = String(rawId).trim();
|
|
103
|
+
const inner = String(rawInner).trim();
|
|
104
|
+
const headingMatch = inner.match(/^##\s+(.+?)(?:\r?\n([\s\S]*))?$/);
|
|
105
|
+
const name = headingMatch?.[1]?.trim() || id;
|
|
106
|
+
const body = (headingMatch?.[2] ?? inner).replace(/^\r?\n/, "").trim();
|
|
107
|
+
blocks.push({
|
|
108
|
+
id,
|
|
109
|
+
name,
|
|
110
|
+
body: headingMatch ? body : inner,
|
|
111
|
+
});
|
|
112
|
+
return "";
|
|
113
|
+
});
|
|
114
|
+
return blocks;
|
|
115
|
+
}
|
|
116
|
+
export function upsertManagedRuleBlocks(existingContent, rules) {
|
|
117
|
+
const byId = new Map(rules.map((rule) => [rule.id, rule]));
|
|
118
|
+
const seen = new Set();
|
|
119
|
+
let nextContent = existingContent.replace(MANAGED_BLOCK_PATTERN, (match, id) => {
|
|
120
|
+
const normalizedId = String(id).trim();
|
|
121
|
+
const rule = byId.get(normalizedId);
|
|
122
|
+
if (!rule) {
|
|
123
|
+
return "";
|
|
124
|
+
}
|
|
125
|
+
if (seen.has(normalizedId)) {
|
|
126
|
+
return "";
|
|
127
|
+
}
|
|
128
|
+
seen.add(normalizedId);
|
|
129
|
+
const suffix = /\n$/.test(match) ? "\n" : "";
|
|
130
|
+
return `${renderManagedRuleBlock(rule)}${suffix}`;
|
|
131
|
+
});
|
|
132
|
+
const missing = rules
|
|
133
|
+
.filter((rule) => !seen.has(rule.id))
|
|
134
|
+
.map((rule) => renderManagedRuleBlock(rule));
|
|
135
|
+
if (missing.length === 0) {
|
|
136
|
+
return nextContent;
|
|
137
|
+
}
|
|
138
|
+
if (nextContent.trim().length === 0) {
|
|
139
|
+
return `${missing.join("\n\n")}\n`;
|
|
140
|
+
}
|
|
141
|
+
const trailingNewline = nextContent.endsWith("\n") ? "" : "\n";
|
|
142
|
+
return `${nextContent}${trailingNewline}\n${missing.join("\n\n")}\n`;
|
|
143
|
+
}
|
|
144
|
+
export function stripRuleFileExtension(fileName) {
|
|
145
|
+
const ext = path.extname(fileName);
|
|
146
|
+
if (!ext)
|
|
147
|
+
return fileName;
|
|
148
|
+
return fileName.slice(0, -ext.length);
|
|
149
|
+
}
|
package/dist/core/scope.js
CHANGED
|
@@ -15,6 +15,7 @@ export function buildScopePaths(cwd, scope, homeDir = os.homedir()) {
|
|
|
15
15
|
agentsRoot,
|
|
16
16
|
agentsDir: path.join(agentsRoot, "agents"),
|
|
17
17
|
commandsDir: path.join(agentsRoot, "commands"),
|
|
18
|
+
rulesDir: path.join(agentsRoot, "rules"),
|
|
18
19
|
skillsDir: path.join(agentsRoot, "skills"),
|
|
19
20
|
mcpPath: path.join(agentsRoot, "mcp.json"),
|
|
20
21
|
lockPath: path.join(agentsRoot, "agents.lock.json"),
|
package/dist/core/skills.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Provider, ScopePaths } from "../types.js";
|
|
2
2
|
export interface CanonicalSkill {
|
|
3
3
|
name: string;
|
|
4
|
+
aliases: string[];
|
|
5
|
+
sourceDirName: string;
|
|
4
6
|
sourcePath: string;
|
|
5
7
|
skillPath: string;
|
|
6
8
|
layout: "nested" | "root";
|
|
@@ -8,6 +10,7 @@ export interface CanonicalSkill {
|
|
|
8
10
|
export declare const ROOT_SKILL_ARTIFACT_DIRS: readonly ["references", "assets", "scripts", "templates", "examples"];
|
|
9
11
|
export declare function parseSkillsDir(skillsDir: string): CanonicalSkill[];
|
|
10
12
|
export declare function normalizeSkillSelector(value: string): string;
|
|
13
|
+
export declare function resolveSkillSelector(skills: CanonicalSkill[], selector: string): CanonicalSkill | null;
|
|
11
14
|
export declare function resolveSkillSelections(skills: CanonicalSkill[], selectors: string[]): {
|
|
12
15
|
selected: CanonicalSkill[];
|
|
13
16
|
unmatched: string[];
|
|
@@ -21,3 +24,4 @@ export declare function applySkillProviderSideEffects(options: {
|
|
|
21
24
|
dryRun?: boolean;
|
|
22
25
|
warn?: (message: string) => void;
|
|
23
26
|
}): void;
|
|
27
|
+
export declare function getLegacyCopilotSkillDirs(paths: ScopePaths): string[];
|
package/dist/core/skills.js
CHANGED
|
@@ -22,8 +22,12 @@ export function parseSkillsDir(skillsDir) {
|
|
|
22
22
|
const skillFile = path.join(skillDir, "SKILL.md");
|
|
23
23
|
if (!fs.existsSync(skillFile))
|
|
24
24
|
continue;
|
|
25
|
+
const raw = fs.readFileSync(skillFile, "utf8");
|
|
26
|
+
const canonicalName = extractSkillName(raw) || entry.name;
|
|
25
27
|
skills.push({
|
|
26
|
-
name:
|
|
28
|
+
name: canonicalName,
|
|
29
|
+
aliases: buildSkillAliases(canonicalName, entry.name),
|
|
30
|
+
sourceDirName: entry.name,
|
|
27
31
|
sourcePath: skillDir,
|
|
28
32
|
skillPath: skillFile,
|
|
29
33
|
layout: "nested",
|
|
@@ -37,9 +41,12 @@ export function parseSkillsDir(skillsDir) {
|
|
|
37
41
|
return [];
|
|
38
42
|
}
|
|
39
43
|
const raw = fs.readFileSync(rootSkillFile, "utf8");
|
|
44
|
+
const canonicalName = extractSkillName(raw) || path.basename(skillsDir);
|
|
40
45
|
return [
|
|
41
46
|
{
|
|
42
|
-
name:
|
|
47
|
+
name: canonicalName,
|
|
48
|
+
aliases: buildSkillAliases(canonicalName, path.basename(skillsDir)),
|
|
49
|
+
sourceDirName: path.basename(skillsDir),
|
|
43
50
|
sourcePath: skillsDir,
|
|
44
51
|
skillPath: rootSkillFile,
|
|
45
52
|
layout: "root",
|
|
@@ -61,6 +68,16 @@ function extractSkillName(raw) {
|
|
|
61
68
|
export function normalizeSkillSelector(value) {
|
|
62
69
|
return slugify(value.trim().replace(/\/+$/, "")).toLowerCase();
|
|
63
70
|
}
|
|
71
|
+
export function resolveSkillSelector(skills, selector) {
|
|
72
|
+
const normalizedSelector = normalizeSkillSelector(selector);
|
|
73
|
+
if (!normalizedSelector)
|
|
74
|
+
return null;
|
|
75
|
+
const canonicalMatch = skills.find((skill) => normalizeSkillSelector(skill.name) === normalizedSelector) ?? null;
|
|
76
|
+
if (canonicalMatch) {
|
|
77
|
+
return canonicalMatch;
|
|
78
|
+
}
|
|
79
|
+
return (skills.find((skill) => normalizeSkillSelector(skill.sourceDirName) === normalizedSelector) ?? null);
|
|
80
|
+
}
|
|
64
81
|
export function resolveSkillSelections(skills, selectors) {
|
|
65
82
|
const normalizedSelectors = selectors
|
|
66
83
|
.map((item) => item.trim())
|
|
@@ -70,14 +87,12 @@ export function resolveSkillSelections(skills, selectors) {
|
|
|
70
87
|
const selectedMap = new Map();
|
|
71
88
|
const unmatched = [];
|
|
72
89
|
for (const selector of normalizedSelectors) {
|
|
73
|
-
const
|
|
74
|
-
if (
|
|
90
|
+
const match = resolveSkillSelector(skills, selector);
|
|
91
|
+
if (!match) {
|
|
75
92
|
unmatched.push(selector);
|
|
76
93
|
continue;
|
|
77
94
|
}
|
|
78
|
-
|
|
79
|
-
selectedMap.set(match.name, match);
|
|
80
|
-
}
|
|
95
|
+
selectedMap.set(match.name, match);
|
|
81
96
|
}
|
|
82
97
|
return {
|
|
83
98
|
selected: [...selectedMap.values()],
|
|
@@ -125,13 +140,21 @@ export function skillContentMatchesTarget(skill, targetDir) {
|
|
|
125
140
|
}
|
|
126
141
|
export function applySkillProviderSideEffects(options) {
|
|
127
142
|
const pathsToSymlink = getProviderSkillsPaths(options.paths, options.providers);
|
|
128
|
-
if (
|
|
143
|
+
if (options.providers.includes("copilot")) {
|
|
144
|
+
const copilotTargets = getProviderSkillsPaths(options.paths, ["copilot"]);
|
|
145
|
+
const hasCurrentCopilotTarget = copilotTargets.some((targetPath) => fs.existsSync(targetPath));
|
|
146
|
+
if (!hasCurrentCopilotTarget) {
|
|
147
|
+
pathsToSymlink.push(...getLegacyCopilotSkillDirs(options.paths));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const uniquePathsToSymlink = [...new Set(pathsToSymlink)];
|
|
151
|
+
if (uniquePathsToSymlink.length === 0)
|
|
129
152
|
return;
|
|
130
153
|
const canonicalSkillsDir = options.paths.skillsDir;
|
|
131
154
|
if (!options.dryRun) {
|
|
132
155
|
ensureDir(canonicalSkillsDir);
|
|
133
156
|
}
|
|
134
|
-
for (const targetSkillsDir of
|
|
157
|
+
for (const targetSkillsDir of uniquePathsToSymlink) {
|
|
135
158
|
enforceProviderSkillsSymlink({
|
|
136
159
|
targetSkillsDir,
|
|
137
160
|
canonicalSkillsDir,
|
|
@@ -140,6 +163,17 @@ export function applySkillProviderSideEffects(options) {
|
|
|
140
163
|
});
|
|
141
164
|
}
|
|
142
165
|
}
|
|
166
|
+
export function getLegacyCopilotSkillDirs(paths) {
|
|
167
|
+
const legacyDirs = [
|
|
168
|
+
paths.scope === "local"
|
|
169
|
+
? path.join(paths.workspaceRoot, ".claude", "skills")
|
|
170
|
+
: path.join(paths.homeDir, ".claude", "skills"),
|
|
171
|
+
];
|
|
172
|
+
if (paths.scope === "global") {
|
|
173
|
+
legacyDirs.push(path.join(paths.homeDir, ".github", "skills"));
|
|
174
|
+
}
|
|
175
|
+
return legacyDirs;
|
|
176
|
+
}
|
|
143
177
|
function enforceProviderSkillsSymlink(options) {
|
|
144
178
|
const resolvedCanonical = realPathOrResolved(options.canonicalSkillsDir);
|
|
145
179
|
const targetDir = options.targetSkillsDir;
|
|
@@ -176,9 +210,7 @@ function enforceProviderSkillsSymlink(options) {
|
|
|
176
210
|
function migrateProviderSkillsIntoCanonical(options) {
|
|
177
211
|
const providerSkills = parseSkillsDir(options.providerSkillsDir);
|
|
178
212
|
for (const skill of providerSkills) {
|
|
179
|
-
const targetSkillDirName = skill.
|
|
180
|
-
? path.basename(skill.sourcePath)
|
|
181
|
-
: slugify(skill.name) || "skill";
|
|
213
|
+
const targetSkillDirName = slugify(skill.name) || "skill";
|
|
182
214
|
const targetSkillDir = path.join(options.canonicalSkillsDir, targetSkillDirName);
|
|
183
215
|
if (fs.existsSync(targetSkillDir)) {
|
|
184
216
|
const sameContent = skill.layout === "nested"
|
|
@@ -199,6 +231,9 @@ function migrateProviderSkillsIntoCanonical(options) {
|
|
|
199
231
|
copyRootSkillArtifacts(skill.sourcePath, targetSkillDir);
|
|
200
232
|
}
|
|
201
233
|
}
|
|
234
|
+
function buildSkillAliases(name, sourceDirName) {
|
|
235
|
+
return [...new Set([name, sourceDirName].filter(Boolean))];
|
|
236
|
+
}
|
|
202
237
|
function moveDirectory(sourceDir, targetDir) {
|
|
203
238
|
ensureDir(path.dirname(targetDir));
|
|
204
239
|
try {
|
package/dist/core/sources.d.ts
CHANGED
|
@@ -16,7 +16,14 @@ export declare function prepareSource(options: {
|
|
|
16
16
|
ref?: string;
|
|
17
17
|
subdir?: string;
|
|
18
18
|
}): PreparedSource;
|
|
19
|
+
export declare function discoverPluginSourceRoots(importRoot: string): string[];
|
|
19
20
|
export declare function discoverSourceAgentsDir(importRoot: string): string | null;
|
|
21
|
+
export declare function discoverSourceAgentsDirs(importRoot: string): string[];
|
|
20
22
|
export declare function discoverSourceMcpPath(importRoot: string): string | null;
|
|
23
|
+
export declare function discoverSourceMcpPaths(importRoot: string): string[];
|
|
24
|
+
export declare function discoverSourceCommandsDirs(importRoot: string): string[];
|
|
21
25
|
export declare function discoverSourceCommandsDir(importRoot: string): string | null;
|
|
22
26
|
export declare function discoverSourceSkillsDir(importRoot: string): string | null;
|
|
27
|
+
export declare function discoverSourceSkillsDirs(importRoot: string): string[];
|
|
28
|
+
export declare function discoverSourceRulesDir(importRoot: string): string | null;
|
|
29
|
+
export declare function discoverSourceRulesDirs(importRoot: string): string[];
|