@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 package-skills/ folder
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"), "package-skills/\n");
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, package-skills/ only)..."));
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 listSkillFolders(directory) {
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, configRepos, resolvedConfig, config, repos, cwd, targetDirs, _i, targetDirs_2, dir, sources, tempDirs, _a, repos_1, rawUrl, cloneDir, _b, tempDirs_1, tmpDir;
190
- return __generator(this, function (_c) {
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
- if (fs.existsSync(resolvedConfig)) {
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, " (package-skills/)"),
221
- directory: path.join(cloneDir, "package-skills"),
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/package-skills": ["my-skill"] });
80
- var sources = [{ label: "local package-skills/", directory: "/local/package-skills", symlink: true }];
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/package-skills/my-skill"), ".agents/skills/my-skill");
84
- (0, vitest_1.expect)(fs.symlinkSync).toHaveBeenCalledWith(path.resolve("/local/package-skills/my-skill"), ".claude/skills/my-skill");
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/package-skills": ["remote-skill"] });
89
- var sources = [{ label: "external repo", directory: "/remote/package-skills", symlink: false }];
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/package-skills/remote-skill"), ".agents/skills/remote-skill", {
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/package-skills": ["real-skill"] });
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 package-skills/", directory: "/local/package-skills", symlink: true }];
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/package-skills/real-skill"), vitest_1.expect.any(String));
105
- (0, vitest_1.expect)(fs.symlinkSync).not.toHaveBeenCalledWith(path.resolve("/local/package-skills/not-a-skill.md"), vitest_1.expect.any(String));
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 project-skills/", directory: "/local/project-skills", symlink: true }];
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/project-skills": [] }); // dir exists but has no entries
119
- var sources = [{ label: "local project-skills/", directory: "/local/project-skills", symlink: true }];
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 package-skills/ folder", function () {
126
- mockFilesystem({}); // cloned tmp dir exists but contains no package-skills/ subdirectory
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 (package-skills/)", directory: "/tmp/cloned-repo/package-skills", symlink: false },
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/package-skills": ["my-skill"] });
138
- var sources = [{ label: "local package-skills/", directory: "/local/package-skills", symlink: true }];
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/package-skills": ["my-skill"] });
146
- var sources = [{ label: "local package-skills/", directory: "/local/package-skills", symlink: true }];
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/package-skills/my-skill"))));
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/package-skills": ["remote-skill"] });
152
- var sources = [{ label: "external repo", directory: "/remote/package-skills", symlink: false }];
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/package-skills/remote-skill"))));
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/package-skills": ["my-skill"] }, [".agents/skills/my-skill", ".claude/skills/my-skill"]);
160
- var sources = [{ label: "local package-skills/", directory: "/local/package-skills", symlink: true }];
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/project-skills": ["shared-skill"],
189
- "/remote/package-skills": ["shared-skill"],
180
+ "/local/skills": ["shared-skill"],
181
+ "/remote/skills": ["shared-skill"],
190
182
  });
191
183
  var sources = [
192
- { label: "local project-skills/", directory: "/local/project-skills", symlink: true },
193
- { label: "external repo", directory: "/remote/package-skills", symlink: false },
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/package-skills": ["shared-skill"],
206
- "/remote2/package-skills": ["shared-skill"],
197
+ "/remote1/skills": ["shared-skill"],
198
+ "/remote2/skills": ["shared-skill"],
207
199
  });
208
200
  var sources = [
209
- { label: "external repo1", directory: "/remote1/package-skills", symlink: false },
210
- { label: "external repo2", directory: "/remote2/package-skills", symlink: false },
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/package-skills/shared-skill"), vitest_1.expect.any(String), { recursive: true });
216
- (0, vitest_1.expect)(fs.cpSync).not.toHaveBeenCalledWith(path.resolve("/remote2/package-skills/shared-skill"), vitest_1.expect.any(String), vitest_1.expect.any(Object));
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-canary-20260312162039",
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-canary-20260312162039"
35
+ "@comet/eslint-config": "8.20.0"
34
36
  },
35
37
  "engines": {
36
38
  "node": ">=22.0.0"