@comet/cli 8.20.0-canary-20260312162039 → 8.20.0
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.
|
@@ -4,9 +4,12 @@ export interface SkillSource {
|
|
|
4
4
|
directory: string;
|
|
5
5
|
/** If true, create symlinks; if false, copy files (used for tmp clone dirs) */
|
|
6
6
|
symlink: boolean;
|
|
7
|
+
/** If true, skills with metadata.internal: true in their SKILL.md are excluded */
|
|
8
|
+
filterInternal?: boolean;
|
|
7
9
|
}
|
|
8
10
|
export interface InstallOptions {
|
|
9
11
|
dryRun: boolean;
|
|
10
12
|
}
|
|
13
|
+
export declare function isInternalSkill(skillFolderPath: string): boolean;
|
|
11
14
|
export declare function installSkills(sources: SkillSource[], targetDirs: string[], { dryRun }: InstallOptions): void;
|
|
12
15
|
export declare const installAgentSkillsCommand: Command;
|
|
@@ -68,22 +68,15 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
68
68
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
|
-
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
72
|
-
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
73
|
-
if (ar || !(i in from)) {
|
|
74
|
-
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
75
|
-
ar[i] = from[i];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return to.concat(ar || Array.prototype.slice.call(from));
|
|
79
|
-
};
|
|
80
71
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
81
72
|
exports.installAgentSkillsCommand = void 0;
|
|
73
|
+
exports.isInternalSkill = isInternalSkill;
|
|
82
74
|
exports.installSkills = installSkills;
|
|
83
75
|
/* eslint-disable no-console */
|
|
84
76
|
var child_process_1 = require("child_process");
|
|
85
77
|
var commander_1 = require("commander");
|
|
86
78
|
var fs = __importStar(require("fs"));
|
|
79
|
+
var yaml = __importStar(require("js-yaml"));
|
|
87
80
|
var os = __importStar(require("os"));
|
|
88
81
|
var path = __importStar(require("path"));
|
|
89
82
|
function parseRepoUrl(rawUrl) {
|
|
@@ -96,14 +89,14 @@ function parseRepoUrl(rawUrl) {
|
|
|
96
89
|
function cloneRepo(rawUrl) {
|
|
97
90
|
var _a = parseRepoUrl(rawUrl), repoUrl = _a.repoUrl, ref = _a.ref;
|
|
98
91
|
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "comet-agent-skills-"));
|
|
99
|
-
// Use sparse checkout to fetch only the
|
|
92
|
+
// Use sparse checkout to fetch only the skills/ folder
|
|
100
93
|
(0, child_process_1.execFileSync)("git", ["init", tmpDir], { stdio: "pipe" });
|
|
101
94
|
(0, child_process_1.execFileSync)("git", ["-C", tmpDir, "remote", "add", "origin", "--", repoUrl], { stdio: "pipe" });
|
|
102
95
|
(0, child_process_1.execFileSync)("git", ["-C", tmpDir, "config", "core.sparseCheckout", "true"], { stdio: "pipe" });
|
|
103
|
-
fs.writeFileSync(path.join(tmpDir, ".git", "info", "sparse-checkout"), "
|
|
96
|
+
fs.writeFileSync(path.join(tmpDir, ".git", "info", "sparse-checkout"), "skills/\n");
|
|
104
97
|
var fetchRef = ref !== null && ref !== void 0 ? ref : "HEAD";
|
|
105
98
|
try {
|
|
106
|
-
console.log("Fetching ".concat(repoUrl, " ref \"").concat(fetchRef, "\" (sparse,
|
|
99
|
+
console.log("Fetching ".concat(repoUrl, " ref \"").concat(fetchRef, "\" (sparse, skills/ only)..."));
|
|
107
100
|
(0, child_process_1.execFileSync)("git", ["-C", tmpDir, "fetch", "--depth", "1", "origin", fetchRef], { stdio: "pipe" });
|
|
108
101
|
(0, child_process_1.execFileSync)("git", ["-C", tmpDir, "checkout", "FETCH_HEAD"], { stdio: "pipe" });
|
|
109
102
|
}
|
|
@@ -123,20 +116,43 @@ function pathExists(p) {
|
|
|
123
116
|
return false;
|
|
124
117
|
}
|
|
125
118
|
}
|
|
126
|
-
function
|
|
119
|
+
function isInternalSkill(skillFolderPath) {
|
|
120
|
+
var _a;
|
|
121
|
+
var skillMdPath = path.join(skillFolderPath, "SKILL.md");
|
|
122
|
+
var skillMdContent;
|
|
123
|
+
try {
|
|
124
|
+
skillMdContent = fs.readFileSync(skillMdPath, "utf-8");
|
|
125
|
+
}
|
|
126
|
+
catch (_b) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
var frontmatterMatch = skillMdContent.match(/^---\n([\s\S]*?)\n---/);
|
|
130
|
+
if (!frontmatterMatch)
|
|
131
|
+
return false;
|
|
132
|
+
try {
|
|
133
|
+
var parsed = yaml.load(frontmatterMatch[1]);
|
|
134
|
+
return ((_a = parsed === null || parsed === void 0 ? void 0 : parsed.metadata) === null || _a === void 0 ? void 0 : _a.internal) === true;
|
|
135
|
+
}
|
|
136
|
+
catch (_c) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function listSkillFolders(directory, filterInternal) {
|
|
141
|
+
if (filterInternal === void 0) { filterInternal = false; }
|
|
127
142
|
if (!fs.existsSync(directory))
|
|
128
143
|
return [];
|
|
129
144
|
return fs
|
|
130
145
|
.readdirSync(directory)
|
|
131
146
|
.filter(function (f) { return fs.statSync(path.join(directory, f)).isDirectory(); })
|
|
147
|
+
.filter(function (folder) { return !filterInternal || !isInternalSkill(path.join(directory, folder)); })
|
|
132
148
|
.sort();
|
|
133
149
|
}
|
|
134
150
|
function installSkills(sources, targetDirs, _a) {
|
|
135
151
|
var dryRun = _a.dryRun;
|
|
136
152
|
var installed = new Set();
|
|
137
153
|
for (var _i = 0, sources_1 = sources; _i < sources_1.length; _i++) {
|
|
138
|
-
var _b = sources_1[_i], label = _b.label, directory = _b.directory, symlink = _b.symlink;
|
|
139
|
-
var folders = listSkillFolders(directory);
|
|
154
|
+
var _b = sources_1[_i], label = _b.label, directory = _b.directory, symlink = _b.symlink, filterInternal = _b.filterInternal;
|
|
155
|
+
var folders = listSkillFolders(directory, filterInternal);
|
|
140
156
|
if (folders.length === 0) {
|
|
141
157
|
console.log("No skills found in ".concat(label));
|
|
142
158
|
continue;
|
|
@@ -186,18 +202,12 @@ exports.installAgentSkillsCommand = new commander_1.Command("install-agent-skill
|
|
|
186
202
|
.option("--config <path>", "Path to a JSON config file specifying repos to install skills from", "agent-skills.json")
|
|
187
203
|
.option("--dry-run", "Show which symlinks/copies would be created without making changes", false)
|
|
188
204
|
.action(function (options) { return __awaiter(void 0, void 0, void 0, function () {
|
|
189
|
-
var configPath, dryRun,
|
|
190
|
-
|
|
205
|
+
var configPath, dryRun, resolvedConfig, repos, cwd, targetDirs, _i, targetDirs_2, dir, sources, tempDirs, _a, repos_1, rawUrl, cloneDir, _b, tempDirs_1, tmpDir;
|
|
206
|
+
var _c;
|
|
207
|
+
return __generator(this, function (_d) {
|
|
191
208
|
configPath = options.config, dryRun = options.dryRun;
|
|
192
|
-
configRepos = [];
|
|
193
209
|
resolvedConfig = path.resolve(configPath);
|
|
194
|
-
|
|
195
|
-
config = loadConfig(configPath);
|
|
196
|
-
if (config.repos) {
|
|
197
|
-
configRepos.push.apply(configRepos, config.repos);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
repos = __spreadArray([], configRepos, true);
|
|
210
|
+
repos = fs.existsSync(resolvedConfig) ? ((_c = loadConfig(configPath).repos) !== null && _c !== void 0 ? _c : []) : [];
|
|
201
211
|
console.log("=== Installing agent skills".concat(dryRun ? " (dry run)" : "", " ==="));
|
|
202
212
|
cwd = process.cwd();
|
|
203
213
|
targetDirs = [path.join(cwd, ".agents", "skills"), path.join(cwd, ".claude", "skills")];
|
|
@@ -206,10 +216,7 @@ exports.installAgentSkillsCommand = new commander_1.Command("install-agent-skill
|
|
|
206
216
|
dir = targetDirs_2[_i];
|
|
207
217
|
fs.mkdirSync(dir, { recursive: true });
|
|
208
218
|
}
|
|
209
|
-
sources = [
|
|
210
|
-
{ label: "local project-skills/", directory: path.join(cwd, "project-skills"), symlink: true },
|
|
211
|
-
{ label: "local package-skills/", directory: path.join(cwd, "package-skills"), symlink: true },
|
|
212
|
-
];
|
|
219
|
+
sources = [{ label: "local skills/", directory: path.join(cwd, "skills"), symlink: true }];
|
|
213
220
|
tempDirs = [];
|
|
214
221
|
try {
|
|
215
222
|
for (_a = 0, repos_1 = repos; _a < repos_1.length; _a++) {
|
|
@@ -217,9 +224,10 @@ exports.installAgentSkillsCommand = new commander_1.Command("install-agent-skill
|
|
|
217
224
|
cloneDir = cloneRepo(rawUrl);
|
|
218
225
|
tempDirs.push(cloneDir);
|
|
219
226
|
sources.push({
|
|
220
|
-
label: "external ".concat(rawUrl
|
|
221
|
-
directory: path.join(cloneDir, "
|
|
227
|
+
label: "external ".concat(rawUrl),
|
|
228
|
+
directory: path.join(cloneDir, "skills"),
|
|
222
229
|
symlink: false,
|
|
230
|
+
filterInternal: true,
|
|
223
231
|
});
|
|
224
232
|
}
|
|
225
233
|
installSkills(sources, targetDirs, { dryRun: dryRun });
|
|
@@ -46,8 +46,9 @@ var targetDirs = [".agents/skills", ".claude/skills"];
|
|
|
46
46
|
* containing the listed skill folder names. Destination paths do not exist
|
|
47
47
|
* unless listed under a target dir key.
|
|
48
48
|
*/
|
|
49
|
-
function mockFilesystem(skillMap, existingDests) {
|
|
49
|
+
function mockFilesystem(skillMap, existingDests, skillMdContents) {
|
|
50
50
|
if (existingDests === void 0) { existingDests = []; }
|
|
51
|
+
if (skillMdContents === void 0) { skillMdContents = {}; }
|
|
51
52
|
vitest_1.vi.mocked(fs.existsSync).mockImplementation(function (p) { return String(p) in skillMap; });
|
|
52
53
|
vitest_1.vi.mocked(fs.readdirSync).mockImplementation(function (p) { var _a; return (_a = skillMap[String(p)]) !== null && _a !== void 0 ? _a : []; });
|
|
53
54
|
vitest_1.vi.mocked(fs.statSync).mockImplementation(function (p) {
|
|
@@ -62,11 +63,20 @@ function mockFilesystem(skillMap, existingDests) {
|
|
|
62
63
|
return {};
|
|
63
64
|
throw Object.assign(new Error("ENOENT"), { code: "ENOENT" });
|
|
64
65
|
});
|
|
66
|
+
vitest_1.vi.mocked(fs.readFileSync).mockImplementation(function (p) {
|
|
67
|
+
var content = skillMdContents[String(p)];
|
|
68
|
+
if (content !== undefined)
|
|
69
|
+
return content;
|
|
70
|
+
throw Object.assign(new Error("ENOENT"), { code: "ENOENT" });
|
|
71
|
+
});
|
|
65
72
|
}
|
|
66
73
|
(0, vitest_1.beforeEach)(function () {
|
|
67
74
|
vitest_1.vi.mocked(fs.symlinkSync).mockImplementation(function () { return undefined; });
|
|
68
75
|
vitest_1.vi.mocked(fs.cpSync).mockImplementation(function () { return undefined; });
|
|
69
76
|
vitest_1.vi.mocked(fs.rmSync).mockImplementation(function () { return undefined; });
|
|
77
|
+
vitest_1.vi.mocked(fs.readFileSync).mockImplementation(function () {
|
|
78
|
+
throw Object.assign(new Error("ENOENT"), { code: "ENOENT" });
|
|
79
|
+
});
|
|
70
80
|
vitest_1.vi.spyOn(console, "log").mockImplementation(function () { return undefined; });
|
|
71
81
|
vitest_1.vi.spyOn(console, "warn").mockImplementation(function () { return undefined; });
|
|
72
82
|
});
|
|
@@ -76,56 +86,56 @@ function mockFilesystem(skillMap, existingDests) {
|
|
|
76
86
|
});
|
|
77
87
|
(0, vitest_1.describe)("installSkills – symlink vs copy", function () {
|
|
78
88
|
(0, vitest_1.it)("symlinks skill folders into target folder if symlink: true", function () {
|
|
79
|
-
mockFilesystem({ "/local/
|
|
80
|
-
var sources = [{ label: "local
|
|
89
|
+
mockFilesystem({ "/local/skills": ["my-skill"] });
|
|
90
|
+
var sources = [{ label: "local skills/", directory: "/local/skills", symlink: true }];
|
|
81
91
|
(0, install_agent_skills_1.installSkills)(sources, targetDirs, defaultOptions);
|
|
82
92
|
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledTimes(2);
|
|
83
|
-
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledWith(path.resolve("/local/
|
|
84
|
-
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledWith(path.resolve("/local/
|
|
93
|
+
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledWith(path.resolve("/local/skills/my-skill"), ".agents/skills/my-skill");
|
|
94
|
+
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledWith(path.resolve("/local/skills/my-skill"), ".claude/skills/my-skill");
|
|
85
95
|
(0, vitest_1.expect)(fs.cpSync).not.toHaveBeenCalled();
|
|
86
96
|
});
|
|
87
97
|
(0, vitest_1.it)("copies skill folders into target folder if symlink: false", function () {
|
|
88
|
-
mockFilesystem({ "/remote/
|
|
89
|
-
var sources = [{ label: "external repo", directory: "/remote/
|
|
98
|
+
mockFilesystem({ "/remote/skills": ["remote-skill"] });
|
|
99
|
+
var sources = [{ label: "external repo", directory: "/remote/skills", symlink: false }];
|
|
90
100
|
(0, install_agent_skills_1.installSkills)(sources, targetDirs, defaultOptions);
|
|
91
101
|
(0, vitest_1.expect)(fs.cpSync).toHaveBeenCalledTimes(2);
|
|
92
|
-
(0, vitest_1.expect)(fs.cpSync).toHaveBeenCalledWith(path.resolve("/remote/
|
|
102
|
+
(0, vitest_1.expect)(fs.cpSync).toHaveBeenCalledWith(path.resolve("/remote/skills/remote-skill"), ".agents/skills/remote-skill", {
|
|
93
103
|
recursive: true,
|
|
94
104
|
});
|
|
95
105
|
(0, vitest_1.expect)(fs.symlinkSync).not.toHaveBeenCalled();
|
|
96
106
|
});
|
|
97
107
|
(0, vitest_1.it)("ignores files in the source directory, only installs subdirectories", function () {
|
|
98
|
-
mockFilesystem({ "/local/
|
|
108
|
+
mockFilesystem({ "/local/skills": ["real-skill"] });
|
|
99
109
|
// statSync returns isDirectory: false for "not-a-skill.md" (not in the skillMap)
|
|
100
110
|
vitest_1.vi.mocked(fs.readdirSync).mockImplementation(function () { return ["real-skill", "not-a-skill.md"]; });
|
|
101
|
-
var sources = [{ label: "local
|
|
111
|
+
var sources = [{ label: "local skills/", directory: "/local/skills", symlink: true }];
|
|
102
112
|
(0, install_agent_skills_1.installSkills)(sources, targetDirs, defaultOptions);
|
|
103
113
|
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledTimes(2);
|
|
104
|
-
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledWith(path.resolve("/local/
|
|
105
|
-
(0, vitest_1.expect)(fs.symlinkSync).not.toHaveBeenCalledWith(path.resolve("/local/
|
|
114
|
+
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledWith(path.resolve("/local/skills/real-skill"), vitest_1.expect.any(String));
|
|
115
|
+
(0, vitest_1.expect)(fs.symlinkSync).not.toHaveBeenCalledWith(path.resolve("/local/skills/not-a-skill.md"), vitest_1.expect.any(String));
|
|
106
116
|
});
|
|
107
117
|
});
|
|
108
118
|
(0, vitest_1.describe)("installSkills – missing source directory", function () {
|
|
109
119
|
(0, vitest_1.it)("logs 'No skills found' and does nothing when source directory does not exist", function () {
|
|
110
120
|
mockFilesystem({}); // existsSync returns false for all paths
|
|
111
|
-
var sources = [{ label: "local
|
|
121
|
+
var sources = [{ label: "local skills/", directory: "/local/skills", symlink: true }];
|
|
112
122
|
(0, install_agent_skills_1.installSkills)(sources, targetDirs, defaultOptions);
|
|
113
123
|
(0, vitest_1.expect)(fs.symlinkSync).not.toHaveBeenCalled();
|
|
114
124
|
(0, vitest_1.expect)(fs.cpSync).not.toHaveBeenCalled();
|
|
115
125
|
(0, vitest_1.expect)(console.log).toHaveBeenCalledWith(vitest_1.expect.stringContaining("No skills found"));
|
|
116
126
|
});
|
|
117
127
|
(0, vitest_1.it)("logs 'No skills found' and does nothing when source directory is empty", function () {
|
|
118
|
-
mockFilesystem({ "/local/
|
|
119
|
-
var sources = [{ label: "local
|
|
128
|
+
mockFilesystem({ "/local/skills": [] }); // dir exists but has no entries
|
|
129
|
+
var sources = [{ label: "local skills/", directory: "/local/skills", symlink: true }];
|
|
120
130
|
(0, install_agent_skills_1.installSkills)(sources, targetDirs, defaultOptions);
|
|
121
131
|
(0, vitest_1.expect)(fs.symlinkSync).not.toHaveBeenCalled();
|
|
122
132
|
(0, vitest_1.expect)(fs.cpSync).not.toHaveBeenCalled();
|
|
123
133
|
(0, vitest_1.expect)(console.log).toHaveBeenCalledWith(vitest_1.expect.stringContaining("No skills found"));
|
|
124
134
|
});
|
|
125
|
-
(0, vitest_1.it)("logs 'No skills found' and does nothing when cloned external repo has no
|
|
126
|
-
mockFilesystem({}); // cloned tmp dir exists but contains no
|
|
135
|
+
(0, vitest_1.it)("logs 'No skills found' and does nothing when cloned external repo has no skills/ folder", function () {
|
|
136
|
+
mockFilesystem({}); // cloned tmp dir exists but contains no skills/ subdirectory
|
|
127
137
|
var sources = [
|
|
128
|
-
{ label: "external git@github.com:org/repo.git (
|
|
138
|
+
{ label: "external git@github.com:org/repo.git (skills/)", directory: "/tmp/cloned-repo/skills", symlink: false },
|
|
129
139
|
];
|
|
130
140
|
(0, install_agent_skills_1.installSkills)(sources, targetDirs, defaultOptions);
|
|
131
141
|
(0, vitest_1.expect)(fs.cpSync).not.toHaveBeenCalled();
|
|
@@ -134,63 +144,45 @@ function mockFilesystem(skillMap, existingDests) {
|
|
|
134
144
|
});
|
|
135
145
|
(0, vitest_1.describe)("installSkills – dry-run mode", function () {
|
|
136
146
|
(0, vitest_1.it)("does not write any files or symlinks", function () {
|
|
137
|
-
mockFilesystem({ "/local/
|
|
138
|
-
var sources = [{ label: "local
|
|
147
|
+
mockFilesystem({ "/local/skills": ["my-skill"] });
|
|
148
|
+
var sources = [{ label: "local skills/", directory: "/local/skills", symlink: true }];
|
|
139
149
|
(0, install_agent_skills_1.installSkills)(sources, targetDirs, { dryRun: true });
|
|
140
150
|
(0, vitest_1.expect)(fs.symlinkSync).not.toHaveBeenCalled();
|
|
141
151
|
(0, vitest_1.expect)(fs.cpSync).not.toHaveBeenCalled();
|
|
142
152
|
(0, vitest_1.expect)(fs.rmSync).not.toHaveBeenCalled();
|
|
143
153
|
});
|
|
144
154
|
(0, vitest_1.it)("logs what would be symlinked", function () {
|
|
145
|
-
mockFilesystem({ "/local/
|
|
146
|
-
var sources = [{ label: "local
|
|
155
|
+
mockFilesystem({ "/local/skills": ["my-skill"] });
|
|
156
|
+
var sources = [{ label: "local skills/", directory: "/local/skills", symlink: true }];
|
|
147
157
|
(0, install_agent_skills_1.installSkills)(sources, targetDirs, { dryRun: true });
|
|
148
|
-
(0, vitest_1.expect)(console.log).toHaveBeenCalledWith(vitest_1.expect.stringContaining("[dry-run] Would symlink: ".concat(path.resolve("/local/
|
|
158
|
+
(0, vitest_1.expect)(console.log).toHaveBeenCalledWith(vitest_1.expect.stringContaining("[dry-run] Would symlink: ".concat(path.resolve("/local/skills/my-skill"))));
|
|
149
159
|
});
|
|
150
160
|
(0, vitest_1.it)("logs what would be copied for external sources", function () {
|
|
151
|
-
mockFilesystem({ "/remote/
|
|
152
|
-
var sources = [{ label: "external repo", directory: "/remote/
|
|
161
|
+
mockFilesystem({ "/remote/skills": ["remote-skill"] });
|
|
162
|
+
var sources = [{ label: "external repo", directory: "/remote/skills", symlink: false }];
|
|
153
163
|
(0, install_agent_skills_1.installSkills)(sources, targetDirs, { dryRun: true });
|
|
154
|
-
(0, vitest_1.expect)(console.log).toHaveBeenCalledWith(vitest_1.expect.stringContaining("[dry-run] Would copy: ".concat(path.resolve("/remote/
|
|
164
|
+
(0, vitest_1.expect)(console.log).toHaveBeenCalledWith(vitest_1.expect.stringContaining("[dry-run] Would copy: ".concat(path.resolve("/remote/skills/remote-skill"))));
|
|
155
165
|
});
|
|
156
166
|
});
|
|
157
167
|
(0, vitest_1.describe)("installSkills – overwrites existing destinations", function () {
|
|
158
168
|
(0, vitest_1.it)("removes existing destination before writing", function () {
|
|
159
|
-
mockFilesystem({ "/local/
|
|
160
|
-
var sources = [{ label: "local
|
|
169
|
+
mockFilesystem({ "/local/skills": ["my-skill"] }, [".agents/skills/my-skill", ".claude/skills/my-skill"]);
|
|
170
|
+
var sources = [{ label: "local skills/", directory: "/local/skills", symlink: true }];
|
|
161
171
|
(0, install_agent_skills_1.installSkills)(sources, targetDirs, defaultOptions);
|
|
162
172
|
(0, vitest_1.expect)(fs.rmSync).toHaveBeenCalledWith(".agents/skills/my-skill", { recursive: true, force: true });
|
|
163
173
|
(0, vitest_1.expect)(fs.rmSync).toHaveBeenCalledWith(".claude/skills/my-skill", { recursive: true, force: true });
|
|
164
174
|
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledTimes(2);
|
|
165
175
|
});
|
|
166
176
|
});
|
|
167
|
-
(0, vitest_1.describe)("installSkills – conflict: local (project-skills) vs local (package-skills)", function () {
|
|
168
|
-
(0, vitest_1.it)("project-skills wins over package-skills: symlinks project skill, skips package skill with a CONFLICT warning", function () {
|
|
169
|
-
mockFilesystem({
|
|
170
|
-
"/local/project-skills": ["shared-skill"],
|
|
171
|
-
"/local/package-skills": ["shared-skill"],
|
|
172
|
-
});
|
|
173
|
-
var sources = [
|
|
174
|
-
{ label: "local project-skills/", directory: "/local/project-skills", symlink: true },
|
|
175
|
-
{ label: "local package-skills/", directory: "/local/package-skills", symlink: true },
|
|
176
|
-
];
|
|
177
|
-
(0, install_agent_skills_1.installSkills)(sources, targetDirs, defaultOptions);
|
|
178
|
-
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledTimes(2);
|
|
179
|
-
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledWith(path.resolve("/local/project-skills/shared-skill"), vitest_1.expect.any(String));
|
|
180
|
-
(0, vitest_1.expect)(fs.symlinkSync).not.toHaveBeenCalledWith(path.resolve("/local/package-skills/shared-skill"), vitest_1.expect.any(String));
|
|
181
|
-
(0, vitest_1.expect)(console.warn).toHaveBeenCalledWith(vitest_1.expect.stringContaining('CONFLICT: "shared-skill"'));
|
|
182
|
-
(0, vitest_1.expect)(console.warn).toHaveBeenCalledWith(vitest_1.expect.stringContaining("local package-skills/"));
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
177
|
(0, vitest_1.describe)("installSkills – conflict: local vs remote", function () {
|
|
186
178
|
(0, vitest_1.it)("local skill wins: symlinks local, skips remote with a CONFLICT warning", function () {
|
|
187
179
|
mockFilesystem({
|
|
188
|
-
"/local/
|
|
189
|
-
"/remote/
|
|
180
|
+
"/local/skills": ["shared-skill"],
|
|
181
|
+
"/remote/skills": ["shared-skill"],
|
|
190
182
|
});
|
|
191
183
|
var sources = [
|
|
192
|
-
{ label: "local
|
|
193
|
-
{ label: "external repo", directory: "/remote/
|
|
184
|
+
{ label: "local skills/", directory: "/local/skills", symlink: true },
|
|
185
|
+
{ label: "external repo", directory: "/remote/skills", symlink: false },
|
|
194
186
|
];
|
|
195
187
|
(0, install_agent_skills_1.installSkills)(sources, targetDirs, defaultOptions);
|
|
196
188
|
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalled();
|
|
@@ -202,19 +194,85 @@ function mockFilesystem(skillMap, existingDests) {
|
|
|
202
194
|
(0, vitest_1.describe)("installSkills – conflict: remote vs remote", function () {
|
|
203
195
|
(0, vitest_1.it)("first remote wins: copies remote1 skill, skips remote2 with a CONFLICT warning", function () {
|
|
204
196
|
mockFilesystem({
|
|
205
|
-
"/remote1/
|
|
206
|
-
"/remote2/
|
|
197
|
+
"/remote1/skills": ["shared-skill"],
|
|
198
|
+
"/remote2/skills": ["shared-skill"],
|
|
207
199
|
});
|
|
208
200
|
var sources = [
|
|
209
|
-
{ label: "external repo1", directory: "/remote1/
|
|
210
|
-
{ label: "external repo2", directory: "/remote2/
|
|
201
|
+
{ label: "external repo1", directory: "/remote1/skills", symlink: false },
|
|
202
|
+
{ label: "external repo2", directory: "/remote2/skills", symlink: false },
|
|
211
203
|
];
|
|
212
204
|
(0, install_agent_skills_1.installSkills)(sources, targetDirs, defaultOptions);
|
|
213
205
|
// Only 2 calls (one per target dir from remote1), not 4
|
|
214
206
|
(0, vitest_1.expect)(fs.cpSync).toHaveBeenCalledTimes(2);
|
|
215
|
-
(0, vitest_1.expect)(fs.cpSync).toHaveBeenCalledWith(path.resolve("/remote1/
|
|
216
|
-
(0, vitest_1.expect)(fs.cpSync).not.toHaveBeenCalledWith(path.resolve("/remote2/
|
|
207
|
+
(0, vitest_1.expect)(fs.cpSync).toHaveBeenCalledWith(path.resolve("/remote1/skills/shared-skill"), vitest_1.expect.any(String), { recursive: true });
|
|
208
|
+
(0, vitest_1.expect)(fs.cpSync).not.toHaveBeenCalledWith(path.resolve("/remote2/skills/shared-skill"), vitest_1.expect.any(String), vitest_1.expect.any(Object));
|
|
217
209
|
(0, vitest_1.expect)(console.warn).toHaveBeenCalledWith(vitest_1.expect.stringContaining('CONFLICT: "shared-skill"'));
|
|
218
210
|
(0, vitest_1.expect)(console.warn).toHaveBeenCalledWith(vitest_1.expect.stringContaining("external repo2"));
|
|
219
211
|
});
|
|
220
212
|
});
|
|
213
|
+
(0, vitest_1.describe)("isInternalSkill", function () {
|
|
214
|
+
(0, vitest_1.it)("returns true when metadata.internal is true", function () {
|
|
215
|
+
mockFilesystem({}, [], { "/skills/my-skill/SKILL.md": "---\nmetadata:\n internal: true\n---\n# My Skill" });
|
|
216
|
+
(0, vitest_1.expect)((0, install_agent_skills_1.isInternalSkill)("/skills/my-skill")).toBe(true);
|
|
217
|
+
});
|
|
218
|
+
(0, vitest_1.it)("returns false when metadata.internal is false", function () {
|
|
219
|
+
mockFilesystem({}, [], { "/skills/my-skill/SKILL.md": "---\nmetadata:\n internal: false\n---\n# My Skill" });
|
|
220
|
+
(0, vitest_1.expect)((0, install_agent_skills_1.isInternalSkill)("/skills/my-skill")).toBe(false);
|
|
221
|
+
});
|
|
222
|
+
(0, vitest_1.it)("returns false when frontmatter has no metadata field", function () {
|
|
223
|
+
mockFilesystem({}, [], { "/skills/my-skill/SKILL.md": "---\ntitle: My Skill\n---\n# My Skill" });
|
|
224
|
+
(0, vitest_1.expect)((0, install_agent_skills_1.isInternalSkill)("/skills/my-skill")).toBe(false);
|
|
225
|
+
});
|
|
226
|
+
(0, vitest_1.it)("returns false when SKILL.md has no frontmatter", function () {
|
|
227
|
+
mockFilesystem({}, [], { "/skills/my-skill/SKILL.md": "# My Skill\nJust plain markdown, no frontmatter." });
|
|
228
|
+
(0, vitest_1.expect)((0, install_agent_skills_1.isInternalSkill)("/skills/my-skill")).toBe(false);
|
|
229
|
+
});
|
|
230
|
+
(0, vitest_1.it)("returns false when SKILL.md does not exist", function () {
|
|
231
|
+
mockFilesystem({}, [], {});
|
|
232
|
+
(0, vitest_1.expect)((0, install_agent_skills_1.isInternalSkill)("/skills/my-skill")).toBe(false);
|
|
233
|
+
});
|
|
234
|
+
(0, vitest_1.it)("returns false when SKILL.md frontmatter contains malformed YAML", function () {
|
|
235
|
+
mockFilesystem({}, [], { "/skills/my-skill/SKILL.md": "---\n: invalid: yaml: [\n---\n# My Skill" });
|
|
236
|
+
(0, vitest_1.expect)((0, install_agent_skills_1.isInternalSkill)("/skills/my-skill")).toBe(false);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
(0, vitest_1.describe)("installSkills – filterInternal", function () {
|
|
240
|
+
(0, vitest_1.it)("excludes internal skills when filterInternal: true, installs only non-internal skills", function () {
|
|
241
|
+
mockFilesystem({ "/remote/skills": ["public-skill", "internal-skill"] }, [], {
|
|
242
|
+
"/remote/skills/internal-skill/SKILL.md": "---\nmetadata:\n internal: true\n---\n# Internal Skill",
|
|
243
|
+
});
|
|
244
|
+
var sources = [{ label: "external repo (skills/)", directory: "/remote/skills", symlink: false, filterInternal: true }];
|
|
245
|
+
(0, install_agent_skills_1.installSkills)(sources, targetDirs, defaultOptions);
|
|
246
|
+
(0, vitest_1.expect)(fs.cpSync).toHaveBeenCalledTimes(2);
|
|
247
|
+
(0, vitest_1.expect)(fs.cpSync).toHaveBeenCalledWith(path.resolve("/remote/skills/public-skill"), vitest_1.expect.any(String), { recursive: true });
|
|
248
|
+
(0, vitest_1.expect)(fs.cpSync).not.toHaveBeenCalledWith(path.resolve("/remote/skills/internal-skill"), vitest_1.expect.any(String), vitest_1.expect.any(Object));
|
|
249
|
+
});
|
|
250
|
+
(0, vitest_1.it)("installs all skills including internal ones when filterInternal is omitted", function () {
|
|
251
|
+
mockFilesystem({ "/local/skills": ["public-skill", "internal-skill"] }, [], {
|
|
252
|
+
"/local/skills/internal-skill/SKILL.md": "---\nmetadata:\n internal: true\n---\n# Internal Skill",
|
|
253
|
+
});
|
|
254
|
+
var sources = [{ label: "local skills/", directory: "/local/skills", symlink: true }];
|
|
255
|
+
(0, install_agent_skills_1.installSkills)(sources, targetDirs, defaultOptions);
|
|
256
|
+
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledTimes(4);
|
|
257
|
+
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledWith(path.resolve("/local/skills/public-skill"), vitest_1.expect.any(String));
|
|
258
|
+
(0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledWith(path.resolve("/local/skills/internal-skill"), vitest_1.expect.any(String));
|
|
259
|
+
});
|
|
260
|
+
(0, vitest_1.it)("logs 'No skills found' when filterInternal: true and all skills are internal", function () {
|
|
261
|
+
mockFilesystem({ "/remote/skills": ["internal-skill"] }, [], {
|
|
262
|
+
"/remote/skills/internal-skill/SKILL.md": "---\nmetadata:\n internal: true\n---\n# Internal Skill",
|
|
263
|
+
});
|
|
264
|
+
var sources = [{ label: "external repo (skills/)", directory: "/remote/skills", symlink: false, filterInternal: true }];
|
|
265
|
+
(0, install_agent_skills_1.installSkills)(sources, targetDirs, defaultOptions);
|
|
266
|
+
(0, vitest_1.expect)(fs.cpSync).not.toHaveBeenCalled();
|
|
267
|
+
(0, vitest_1.expect)(console.log).toHaveBeenCalledWith(vitest_1.expect.stringContaining("No skills found"));
|
|
268
|
+
});
|
|
269
|
+
(0, vitest_1.it)("excludes internal skills from dry-run output when filterInternal: true", function () {
|
|
270
|
+
mockFilesystem({ "/remote/skills": ["public-skill", "internal-skill"] }, [], {
|
|
271
|
+
"/remote/skills/internal-skill/SKILL.md": "---\nmetadata:\n internal: true\n---\n# Internal Skill",
|
|
272
|
+
});
|
|
273
|
+
var sources = [{ label: "external repo (skills/)", directory: "/remote/skills", symlink: false, filterInternal: true }];
|
|
274
|
+
(0, install_agent_skills_1.installSkills)(sources, targetDirs, { dryRun: true });
|
|
275
|
+
(0, vitest_1.expect)(console.log).toHaveBeenCalledWith(vitest_1.expect.stringContaining("public-skill"));
|
|
276
|
+
(0, vitest_1.expect)(console.log).not.toHaveBeenCalledWith(vitest_1.expect.stringContaining("internal-skill"));
|
|
277
|
+
});
|
|
278
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comet/cli",
|
|
3
|
-
"version": "8.20.0
|
|
3
|
+
"version": "8.20.0",
|
|
4
4
|
"description": "A collection of CLI tools for Comet projects",
|
|
5
5
|
"repository": {
|
|
6
6
|
"directory": "packages/cli",
|
|
@@ -20,17 +20,19 @@
|
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"commander": "^9.5.0",
|
|
23
|
+
"js-yaml": "^4.1.0",
|
|
23
24
|
"prettier": "^3.6.2",
|
|
24
25
|
"ts-node": "^10.9.2"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
28
|
+
"@types/js-yaml": "^4.0.9",
|
|
27
29
|
"@types/node": "^24.10.9",
|
|
28
30
|
"eslint": "^9.39.2",
|
|
29
31
|
"npm-run-all2": "^8.0.4",
|
|
30
32
|
"rimraf": "^6.1.2",
|
|
31
33
|
"typescript": "^5.9.3",
|
|
32
34
|
"vitest": "^4.0.16",
|
|
33
|
-
"@comet/eslint-config": "8.20.0
|
|
35
|
+
"@comet/eslint-config": "8.20.0"
|
|
34
36
|
},
|
|
35
37
|
"engines": {
|
|
36
38
|
"node": ">=22.0.0"
|