skill-master 0.1.4 → 0.1.6
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/dist/cli.js +401 -97
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -111,6 +111,12 @@ var SkillParseError = class extends SkillManagerError {
|
|
|
111
111
|
this.name = "SkillParseError";
|
|
112
112
|
}
|
|
113
113
|
};
|
|
114
|
+
var SourceParseError = class extends SkillManagerError {
|
|
115
|
+
constructor(source, detail) {
|
|
116
|
+
super(`Failed to parse source "${source}"${detail ? ": " + detail : ""}`);
|
|
117
|
+
this.name = "SourceParseError";
|
|
118
|
+
}
|
|
119
|
+
};
|
|
114
120
|
|
|
115
121
|
// src/utils/logger.ts
|
|
116
122
|
import chalk from "chalk";
|
|
@@ -152,20 +158,89 @@ function tableRow(...cols) {
|
|
|
152
158
|
|
|
153
159
|
// src/core/git-source.ts
|
|
154
160
|
var execFileAsync = promisify(execFile);
|
|
161
|
+
function parseSource(source) {
|
|
162
|
+
if (source.startsWith("git@") || source.startsWith("git://")) {
|
|
163
|
+
return { type: "git", url: source };
|
|
164
|
+
}
|
|
165
|
+
if (source.startsWith("https://") || source.startsWith("http://")) {
|
|
166
|
+
const ghTree = source.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)(?:\/(.+))?/);
|
|
167
|
+
if (ghTree) {
|
|
168
|
+
const url = `https://github.com/${ghTree[1]}/${ghTree[2]}.git`;
|
|
169
|
+
return { type: "git", url, ref: ghTree[3], subpath: ghTree[4] };
|
|
170
|
+
}
|
|
171
|
+
const ghBlob = source.match(/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)/);
|
|
172
|
+
if (ghBlob) {
|
|
173
|
+
const url = `https://github.com/${ghBlob[1]}/${ghBlob[2]}.git`;
|
|
174
|
+
const filePath = ghBlob[4];
|
|
175
|
+
const subpath = filePath.includes("/") ? filePath.slice(0, filePath.lastIndexOf("/")) : void 0;
|
|
176
|
+
return { type: "git", url, ref: ghBlob[3], subpath };
|
|
177
|
+
}
|
|
178
|
+
const glTree = source.match(/gitlab\.com\/([^/]+)\/([^/]+)\/-\/tree\/([^/]+)(?:\/(.+))?/);
|
|
179
|
+
if (glTree) {
|
|
180
|
+
const url = `https://gitlab.com/${glTree[1]}/${glTree[2]}.git`;
|
|
181
|
+
return { type: "git", url, ref: glTree[3], subpath: glTree[4] };
|
|
182
|
+
}
|
|
183
|
+
if (source.includes("github.com/") || source.includes("gitlab.com/")) {
|
|
184
|
+
return { type: "git", url: source };
|
|
185
|
+
}
|
|
186
|
+
return { type: "git", url: source };
|
|
187
|
+
}
|
|
188
|
+
if (source.includes("github.com/") || source.includes("gitlab.com/")) {
|
|
189
|
+
return parseSource("https://" + source);
|
|
190
|
+
}
|
|
191
|
+
if (existsSync2(source) || source.startsWith("/") || source.startsWith("./") || source.startsWith("../")) {
|
|
192
|
+
return { type: "local", path: source };
|
|
193
|
+
}
|
|
194
|
+
let skillFilter;
|
|
195
|
+
let shorthand = source;
|
|
196
|
+
const atIdx = shorthand.indexOf("@");
|
|
197
|
+
if (atIdx > 0 && !shorthand.includes("/") === false) {
|
|
198
|
+
const lastAt = shorthand.lastIndexOf("@");
|
|
199
|
+
if (lastAt > 0) {
|
|
200
|
+
const afterAt = shorthand.slice(lastAt + 1);
|
|
201
|
+
const beforeAt = shorthand.slice(0, lastAt);
|
|
202
|
+
if (afterAt && !afterAt.includes("/")) {
|
|
203
|
+
skillFilter = afterAt;
|
|
204
|
+
shorthand = beforeAt;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(shorthand)) {
|
|
209
|
+
return {
|
|
210
|
+
type: "git",
|
|
211
|
+
url: `https://github.com/${shorthand}.git`,
|
|
212
|
+
skillFilter
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const segments = shorthand.split("/");
|
|
216
|
+
if (segments.length >= 3 && /^[a-zA-Z0-9_.-]+$/.test(segments[0]) && /^[a-zA-Z0-9_.-]+$/.test(segments[1])) {
|
|
217
|
+
const owner = segments[0];
|
|
218
|
+
const repo = segments[1];
|
|
219
|
+
const subpath = segments.slice(2).join("/");
|
|
220
|
+
return {
|
|
221
|
+
type: "git",
|
|
222
|
+
url: `https://github.com/${owner}/${repo}.git`,
|
|
223
|
+
subpath,
|
|
224
|
+
skillFilter
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
throw new SourceParseError(source, "Unable to determine source type");
|
|
228
|
+
}
|
|
155
229
|
function isGitUrl(source) {
|
|
156
|
-
|
|
230
|
+
try {
|
|
231
|
+
return parseSource(source).type === "git";
|
|
232
|
+
} catch {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
157
235
|
}
|
|
158
236
|
function normalizeGitUrl(source) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
237
|
+
const parsed = parseSource(source);
|
|
238
|
+
if (parsed.type !== "git" || !parsed.url) return source;
|
|
239
|
+
let url = parsed.url;
|
|
240
|
+
if (url.startsWith("https://") && !url.endsWith(".git")) {
|
|
241
|
+
url += ".git";
|
|
164
242
|
}
|
|
165
|
-
|
|
166
|
-
return `https://github.com/${source}.git`;
|
|
167
|
-
}
|
|
168
|
-
return source;
|
|
243
|
+
return url;
|
|
169
244
|
}
|
|
170
245
|
function parseGitUrl(url) {
|
|
171
246
|
const treeMatch = url.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/(.+)/);
|
|
@@ -261,8 +336,20 @@ function inferCapabilities(allowedTools) {
|
|
|
261
336
|
return [...caps];
|
|
262
337
|
}
|
|
263
338
|
async function findSkillDirectory(dir) {
|
|
339
|
+
const dirs = await findAllSkillDirectories(dir);
|
|
340
|
+
return dirs.length > 0 ? dirs[0] : null;
|
|
341
|
+
}
|
|
342
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build"]);
|
|
343
|
+
async function findAllSkillDirectories(dir, fullDepth = false) {
|
|
344
|
+
if (fullDepth) {
|
|
345
|
+
const results2 = /* @__PURE__ */ new Set();
|
|
346
|
+
await walkForSkills(dir, 0, 5, results2);
|
|
347
|
+
return [...results2];
|
|
348
|
+
}
|
|
349
|
+
const results = [];
|
|
264
350
|
if (existsSync3(join2(dir, "SKILL.md"))) {
|
|
265
|
-
|
|
351
|
+
results.push(dir);
|
|
352
|
+
return results;
|
|
266
353
|
}
|
|
267
354
|
const skillsRoot = join2(dir, ".claude", "skills");
|
|
268
355
|
if (existsSync3(skillsRoot)) {
|
|
@@ -272,7 +359,7 @@ async function findSkillDirectory(dir) {
|
|
|
272
359
|
if (entry.isDirectory()) {
|
|
273
360
|
const skillMdPath = join2(skillsRoot, entry.name, "SKILL.md");
|
|
274
361
|
if (existsSync3(skillMdPath)) {
|
|
275
|
-
|
|
362
|
+
results.push(join2(skillsRoot, entry.name));
|
|
276
363
|
}
|
|
277
364
|
}
|
|
278
365
|
}
|
|
@@ -285,13 +372,28 @@ async function findSkillDirectory(dir) {
|
|
|
285
372
|
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
286
373
|
const skillMdPath = join2(dir, entry.name, "SKILL.md");
|
|
287
374
|
if (existsSync3(skillMdPath)) {
|
|
288
|
-
|
|
375
|
+
results.push(join2(dir, entry.name));
|
|
289
376
|
}
|
|
290
377
|
}
|
|
291
378
|
}
|
|
292
379
|
} catch {
|
|
293
380
|
}
|
|
294
|
-
return
|
|
381
|
+
return results;
|
|
382
|
+
}
|
|
383
|
+
async function walkForSkills(dir, depth, maxDepth, results) {
|
|
384
|
+
if (depth > maxDepth) return;
|
|
385
|
+
if (existsSync3(join2(dir, "SKILL.md"))) {
|
|
386
|
+
results.add(dir);
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
390
|
+
for (const entry of entries) {
|
|
391
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && !SKIP_DIRS.has(entry.name)) {
|
|
392
|
+
await walkForSkills(join2(dir, entry.name), depth + 1, maxDepth, results);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
295
397
|
}
|
|
296
398
|
async function readSkillMd(dir) {
|
|
297
399
|
const content = await readTextSafe(join2(dir, "SKILL.md"));
|
|
@@ -606,6 +708,9 @@ function detectPlatform(cwd) {
|
|
|
606
708
|
function getAgentSkillPath(cwd, agent, name) {
|
|
607
709
|
return join3(cwd, AGENTS[agent].skillsDir, name);
|
|
608
710
|
}
|
|
711
|
+
function getAgentGlobalSkillPath(agent, name) {
|
|
712
|
+
return join3(AGENTS[agent].globalSkillsDir, name);
|
|
713
|
+
}
|
|
609
714
|
|
|
610
715
|
// src/utils/paths.ts
|
|
611
716
|
var AGENTS_HOME = join4(homedir2(), ".agents");
|
|
@@ -720,12 +825,38 @@ function getEnvEditPath(skillName) {
|
|
|
720
825
|
// src/core/registry.ts
|
|
721
826
|
import { existsSync as existsSync5 } from "fs";
|
|
722
827
|
function createEmptyRegistry() {
|
|
723
|
-
return { version:
|
|
828
|
+
return { version: 2, skills: {} };
|
|
829
|
+
}
|
|
830
|
+
function migrateEntryV1(v1) {
|
|
831
|
+
return {
|
|
832
|
+
source: v1.source,
|
|
833
|
+
version: v1.version,
|
|
834
|
+
installed_at: v1.installed_at,
|
|
835
|
+
updated_at: v1.updated_at,
|
|
836
|
+
agents: [{
|
|
837
|
+
agent: v1.agent,
|
|
838
|
+
agent_path: v1.agent_path,
|
|
839
|
+
global: v1.agent_path.includes("/.agents/") || v1.agent_path.includes("\\.agents\\")
|
|
840
|
+
}],
|
|
841
|
+
env_keys: v1.env_keys,
|
|
842
|
+
capabilities: v1.capabilities,
|
|
843
|
+
canonical_path: v1.canonical_path
|
|
844
|
+
};
|
|
724
845
|
}
|
|
725
|
-
function
|
|
726
|
-
if (!data || typeof data !== "object") return
|
|
846
|
+
function validateAndMigrate(data) {
|
|
847
|
+
if (!data || typeof data !== "object") return null;
|
|
727
848
|
const reg = data;
|
|
728
|
-
|
|
849
|
+
if (typeof reg.skills !== "object" || reg.skills === null) return null;
|
|
850
|
+
if (reg.version === 2) return data;
|
|
851
|
+
if (reg.version === 1) {
|
|
852
|
+
const v1Skills = reg.skills;
|
|
853
|
+
const v2Skills = {};
|
|
854
|
+
for (const [name, entry] of Object.entries(v1Skills)) {
|
|
855
|
+
v2Skills[name] = migrateEntryV1(entry);
|
|
856
|
+
}
|
|
857
|
+
return { version: 2, skills: v2Skills };
|
|
858
|
+
}
|
|
859
|
+
return null;
|
|
729
860
|
}
|
|
730
861
|
async function readRegistry() {
|
|
731
862
|
if (!existsSync5(REGISTRY_PATH)) {
|
|
@@ -735,14 +866,46 @@ async function readRegistry() {
|
|
|
735
866
|
if (!data) {
|
|
736
867
|
throw new RegistryCorruptError("Failed to parse registry.json");
|
|
737
868
|
}
|
|
738
|
-
|
|
869
|
+
const registry = validateAndMigrate(data);
|
|
870
|
+
if (!registry) {
|
|
739
871
|
throw new RegistryCorruptError("Invalid registry structure");
|
|
740
872
|
}
|
|
741
|
-
|
|
873
|
+
if (data.version !== registry.version) {
|
|
874
|
+
await atomicWriteJson(REGISTRY_PATH, registry);
|
|
875
|
+
}
|
|
876
|
+
return registry;
|
|
742
877
|
}
|
|
743
878
|
async function updateRegistry(skillName, entry) {
|
|
744
879
|
const registry = await readRegistry();
|
|
745
|
-
registry.skills[skillName]
|
|
880
|
+
const existing = registry.skills[skillName];
|
|
881
|
+
if (existing) {
|
|
882
|
+
for (const newAgent of entry.agents) {
|
|
883
|
+
const idx = existing.agents.findIndex((a) => a.agent === newAgent.agent);
|
|
884
|
+
if (idx >= 0) {
|
|
885
|
+
existing.agents[idx] = newAgent;
|
|
886
|
+
} else {
|
|
887
|
+
existing.agents.push(newAgent);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
existing.source = entry.source;
|
|
891
|
+
existing.version = entry.version;
|
|
892
|
+
existing.updated_at = entry.updated_at;
|
|
893
|
+
existing.env_keys = entry.env_keys;
|
|
894
|
+
existing.capabilities = entry.capabilities;
|
|
895
|
+
existing.canonical_path = entry.canonical_path;
|
|
896
|
+
} else {
|
|
897
|
+
registry.skills[skillName] = entry;
|
|
898
|
+
}
|
|
899
|
+
await atomicWriteJson(REGISTRY_PATH, registry);
|
|
900
|
+
}
|
|
901
|
+
async function removeAgentFromRegistry(skillName, agent) {
|
|
902
|
+
const registry = await readRegistry();
|
|
903
|
+
const entry = registry.skills[skillName];
|
|
904
|
+
if (!entry) return;
|
|
905
|
+
entry.agents = entry.agents.filter((a) => a.agent !== agent);
|
|
906
|
+
if (entry.agents.length === 0) {
|
|
907
|
+
delete registry.skills[skillName];
|
|
908
|
+
}
|
|
746
909
|
await atomicWriteJson(REGISTRY_PATH, registry);
|
|
747
910
|
}
|
|
748
911
|
async function removeFromRegistry(skillName) {
|
|
@@ -762,7 +925,7 @@ async function getRegistryEntry(skillName) {
|
|
|
762
925
|
// src/core/installer.ts
|
|
763
926
|
var TOTAL_STEPS = 9;
|
|
764
927
|
async function installSkill(options) {
|
|
765
|
-
const { source, cwd, copy = false, force = false } = options;
|
|
928
|
+
const { source, cwd, copy = false, force = false, global: isGlobal = false } = options;
|
|
766
929
|
step(1, TOTAL_STEPS, "Fetching skill source...");
|
|
767
930
|
let sourceDir;
|
|
768
931
|
if (source.type === "git") {
|
|
@@ -791,7 +954,7 @@ async function installSkill(options) {
|
|
|
791
954
|
const agent = options.agent ?? detectPlatform(cwd);
|
|
792
955
|
info(`Target platform: ${agent}`);
|
|
793
956
|
step(5, TOTAL_STEPS, "Backing up .env...");
|
|
794
|
-
const agentSkillDir = getAgentSkillPath(cwd, agent, skillName);
|
|
957
|
+
const agentSkillDir = isGlobal ? getAgentGlobalSkillPath(agent, skillName) : getAgentSkillPath(cwd, agent, skillName);
|
|
795
958
|
const envBackup = await backupEnv(skillName, agentSkillDir);
|
|
796
959
|
if (envBackup) {
|
|
797
960
|
success(`Backed up ${Object.keys(envBackup).length} env key(s)`);
|
|
@@ -817,7 +980,7 @@ async function installSkill(options) {
|
|
|
817
980
|
}
|
|
818
981
|
}
|
|
819
982
|
step(8, TOTAL_STEPS, `Linking to ${agent} skills directory...`);
|
|
820
|
-
const agentPath = getAgentSkillPath(cwd, agent, skillName);
|
|
983
|
+
const agentPath = isGlobal ? getAgentGlobalSkillPath(agent, skillName) : getAgentSkillPath(cwd, agent, skillName);
|
|
821
984
|
const linkType = await symlinkOrCopy(canonicalPath, agentPath, copy);
|
|
822
985
|
success(`${linkType === "symlink" ? "Symlinked" : "Copied"} to ${agentPath}`);
|
|
823
986
|
step(9, TOTAL_STEPS, "Updating registry...");
|
|
@@ -825,16 +988,20 @@ async function installSkill(options) {
|
|
|
825
988
|
const envExampleContent = await readTextSafe(join6(canonicalPath, ".env.example"));
|
|
826
989
|
const envKeys = envExampleContent ? extractEnvKeys(envExampleContent) : [];
|
|
827
990
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
991
|
+
const agentInstall = {
|
|
992
|
+
agent,
|
|
993
|
+
agent_path: agentPath,
|
|
994
|
+
global: isGlobal
|
|
995
|
+
};
|
|
828
996
|
const entry = {
|
|
829
997
|
source: source.type === "git" ? source.url : source.path,
|
|
830
998
|
version: parsed.frontmatter.version,
|
|
831
999
|
installed_at: now,
|
|
832
1000
|
updated_at: now,
|
|
833
|
-
|
|
1001
|
+
agents: [agentInstall],
|
|
834
1002
|
env_keys: envKeys,
|
|
835
1003
|
capabilities,
|
|
836
|
-
canonical_path: canonicalPath
|
|
837
|
-
agent_path: agentPath
|
|
1004
|
+
canonical_path: canonicalPath
|
|
838
1005
|
};
|
|
839
1006
|
await updateRegistry(skillName, entry);
|
|
840
1007
|
blank();
|
|
@@ -842,6 +1009,8 @@ async function installSkill(options) {
|
|
|
842
1009
|
}
|
|
843
1010
|
|
|
844
1011
|
// src/commands/add.ts
|
|
1012
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1013
|
+
import { basename as basename2, join as join7 } from "path";
|
|
845
1014
|
function parseAddFlags(args) {
|
|
846
1015
|
const flags = {
|
|
847
1016
|
global: false,
|
|
@@ -852,7 +1021,8 @@ function parseAddFlags(args) {
|
|
|
852
1021
|
all: false,
|
|
853
1022
|
fullDepth: false,
|
|
854
1023
|
copy: false,
|
|
855
|
-
force: false
|
|
1024
|
+
force: false,
|
|
1025
|
+
help: false
|
|
856
1026
|
};
|
|
857
1027
|
let source = null;
|
|
858
1028
|
let i = 0;
|
|
@@ -870,12 +1040,17 @@ function parseAddFlags(args) {
|
|
|
870
1040
|
flags.skill.push(val);
|
|
871
1041
|
break;
|
|
872
1042
|
default:
|
|
873
|
-
|
|
1043
|
+
throw new Error(`Unknown option: --${key}`);
|
|
874
1044
|
}
|
|
875
1045
|
i++;
|
|
876
1046
|
continue;
|
|
877
1047
|
}
|
|
878
1048
|
switch (arg) {
|
|
1049
|
+
case "-h":
|
|
1050
|
+
case "--help":
|
|
1051
|
+
flags.help = true;
|
|
1052
|
+
i++;
|
|
1053
|
+
break;
|
|
879
1054
|
case "-g":
|
|
880
1055
|
case "--global":
|
|
881
1056
|
flags.global = true;
|
|
@@ -926,6 +1101,8 @@ function parseAddFlags(args) {
|
|
|
926
1101
|
default:
|
|
927
1102
|
if (!arg.startsWith("-") && source === null) {
|
|
928
1103
|
source = arg;
|
|
1104
|
+
} else if (arg.startsWith("-")) {
|
|
1105
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
929
1106
|
}
|
|
930
1107
|
i++;
|
|
931
1108
|
break;
|
|
@@ -938,40 +1115,117 @@ function parseAddFlags(args) {
|
|
|
938
1115
|
}
|
|
939
1116
|
return { source, flags };
|
|
940
1117
|
}
|
|
1118
|
+
function printAddHelp() {
|
|
1119
|
+
console.log("Usage: skill-master add <source> [options]");
|
|
1120
|
+
console.log("");
|
|
1121
|
+
console.log("Options:");
|
|
1122
|
+
console.log(" -h, --help Show this help message");
|
|
1123
|
+
console.log(" -g, --global Install globally (~/.agents/)");
|
|
1124
|
+
console.log(" -a, --agent <agents> Target agents (space-separated)");
|
|
1125
|
+
console.log(" -s, --skill <skills> Select skills (space-separated)");
|
|
1126
|
+
console.log(" -y, --yes Skip confirmations");
|
|
1127
|
+
console.log(" -l, --list List available skills without installing");
|
|
1128
|
+
console.log(" --all Install all skills to all agents");
|
|
1129
|
+
console.log(" --full-depth Search all subdirectories");
|
|
1130
|
+
console.log(" --copy Copy instead of symlink");
|
|
1131
|
+
console.log(" --force Force reinstall");
|
|
1132
|
+
}
|
|
941
1133
|
async function add(args) {
|
|
942
1134
|
if (args.length === 0) {
|
|
943
|
-
|
|
944
|
-
console.log("");
|
|
945
|
-
console.log("Options:");
|
|
946
|
-
console.log(" -g, --global Install globally (~/.agents/)");
|
|
947
|
-
console.log(" -a, --agent <agents> Target agents (space-separated)");
|
|
948
|
-
console.log(" -s, --skill <skills> Select skills (space-separated)");
|
|
949
|
-
console.log(" -y, --yes Skip confirmations");
|
|
950
|
-
console.log(" -l, --list List available skills without installing");
|
|
951
|
-
console.log(" --all Install all skills to all agents");
|
|
952
|
-
console.log(" --full-depth Search all subdirectories");
|
|
953
|
-
console.log(" --copy Copy instead of symlink");
|
|
954
|
-
console.log(" --force Force reinstall");
|
|
1135
|
+
printAddHelp();
|
|
955
1136
|
process.exit(1);
|
|
956
1137
|
}
|
|
957
1138
|
const { source, flags } = parseAddFlags(args);
|
|
1139
|
+
if (flags.help) {
|
|
1140
|
+
printAddHelp();
|
|
1141
|
+
process.exit(0);
|
|
1142
|
+
}
|
|
958
1143
|
if (!source) {
|
|
959
1144
|
error("No source specified. Provide a GitHub URL, owner/repo, or local path.");
|
|
960
1145
|
process.exit(1);
|
|
961
1146
|
}
|
|
962
|
-
const skillSource = isGitUrl(source) ? { type: "git", url: source } : { type: "local", path: source };
|
|
963
1147
|
const cwd = process.cwd();
|
|
1148
|
+
const parsed = parseSource(source);
|
|
1149
|
+
if (parsed.skillFilter && !flags.skill.includes(parsed.skillFilter)) {
|
|
1150
|
+
flags.skill.push(parsed.skillFilter);
|
|
1151
|
+
}
|
|
1152
|
+
let sourceDir;
|
|
1153
|
+
if (parsed.type === "git") {
|
|
1154
|
+
step(1, 9, "Fetching skill source...");
|
|
1155
|
+
sourceDir = await cloneRepo(parsed.url, parsed.ref);
|
|
1156
|
+
if (parsed.subpath) {
|
|
1157
|
+
const sub = join7(sourceDir, parsed.subpath);
|
|
1158
|
+
if (existsSync7(sub)) {
|
|
1159
|
+
sourceDir = sub;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
} else {
|
|
1163
|
+
sourceDir = parsed.path;
|
|
1164
|
+
if (!existsSync7(sourceDir)) {
|
|
1165
|
+
throw new SkillNotFoundError(sourceDir);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
const allSkillDirs = await findAllSkillDirectories(sourceDir, flags.fullDepth);
|
|
1169
|
+
if (allSkillDirs.length === 0) {
|
|
1170
|
+
throw new SkillNotFoundError(`No SKILL.md found in ${sourceDir}`);
|
|
1171
|
+
}
|
|
1172
|
+
if (flags.list) {
|
|
1173
|
+
blank();
|
|
1174
|
+
tableHeader("Skill", "Version", "Description");
|
|
1175
|
+
for (const dir of allSkillDirs) {
|
|
1176
|
+
const sk = await readSkillMd(dir);
|
|
1177
|
+
if (sk) {
|
|
1178
|
+
tableRow(
|
|
1179
|
+
sk.frontmatter.name,
|
|
1180
|
+
sk.frontmatter.version ?? "-",
|
|
1181
|
+
sk.frontmatter.description ?? "-"
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
blank();
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
let targetDirs = allSkillDirs;
|
|
1189
|
+
if (flags.skill.length > 0 && !flags.skill.includes("*")) {
|
|
1190
|
+
const requested = new Set(flags.skill.map((s) => s.toLowerCase()));
|
|
1191
|
+
const filtered = [];
|
|
1192
|
+
for (const dir of allSkillDirs) {
|
|
1193
|
+
const sk = await readSkillMd(dir);
|
|
1194
|
+
if (!sk) continue;
|
|
1195
|
+
const name = sk.frontmatter.name.toLowerCase();
|
|
1196
|
+
const dirName = basename2(dir).toLowerCase();
|
|
1197
|
+
if (requested.has(name) || requested.has(dirName)) {
|
|
1198
|
+
filtered.push(dir);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
if (filtered.length === 0) {
|
|
1202
|
+
const available = [];
|
|
1203
|
+
for (const dir of allSkillDirs) {
|
|
1204
|
+
const sk = await readSkillMd(dir);
|
|
1205
|
+
if (sk) available.push(sk.frontmatter.name);
|
|
1206
|
+
}
|
|
1207
|
+
error(
|
|
1208
|
+
`No matching skills found for: ${flags.skill.join(", ")}
|
|
1209
|
+
Available skills: ${available.join(", ")}`
|
|
1210
|
+
);
|
|
1211
|
+
process.exit(1);
|
|
1212
|
+
}
|
|
1213
|
+
targetDirs = filtered;
|
|
1214
|
+
}
|
|
964
1215
|
const agents = flags.agent.length > 0 ? flags.agent : [void 0];
|
|
965
1216
|
try {
|
|
966
|
-
for (const
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1217
|
+
for (const dir of targetDirs) {
|
|
1218
|
+
for (const agent of agents) {
|
|
1219
|
+
await installSkill({
|
|
1220
|
+
source: { type: "local", path: dir },
|
|
1221
|
+
agent,
|
|
1222
|
+
cwd,
|
|
1223
|
+
global: flags.global,
|
|
1224
|
+
copy: flags.copy,
|
|
1225
|
+
force: flags.force,
|
|
1226
|
+
yes: flags.yes
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
975
1229
|
}
|
|
976
1230
|
} catch (err) {
|
|
977
1231
|
error(err.message);
|
|
@@ -995,12 +1249,15 @@ async function update(args) {
|
|
|
995
1249
|
info(`Updating skill: ${skillName}`);
|
|
996
1250
|
info(`Source: ${entry.source}`);
|
|
997
1251
|
const source = isGitUrl(entry.source) ? { type: "git", url: entry.source } : { type: "local", path: entry.source };
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1252
|
+
for (const agentRecord of entry.agents) {
|
|
1253
|
+
await installSkill({
|
|
1254
|
+
source,
|
|
1255
|
+
agent: agentRecord.agent,
|
|
1256
|
+
cwd: process.cwd(),
|
|
1257
|
+
global: agentRecord.global,
|
|
1258
|
+
force: true
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1004
1261
|
success(`Skill "${skillName}" updated successfully!`);
|
|
1005
1262
|
} catch (err) {
|
|
1006
1263
|
error(err.message);
|
|
@@ -1016,7 +1273,8 @@ function parseRemoveFlags(args) {
|
|
|
1016
1273
|
skill: [],
|
|
1017
1274
|
yes: false,
|
|
1018
1275
|
all: false,
|
|
1019
|
-
purge: false
|
|
1276
|
+
purge: false,
|
|
1277
|
+
help: false
|
|
1020
1278
|
};
|
|
1021
1279
|
const names = [];
|
|
1022
1280
|
let i = 0;
|
|
@@ -1034,12 +1292,17 @@ function parseRemoveFlags(args) {
|
|
|
1034
1292
|
flags.skill.push(val);
|
|
1035
1293
|
break;
|
|
1036
1294
|
default:
|
|
1037
|
-
|
|
1295
|
+
throw new Error(`Unknown option: --${key}`);
|
|
1038
1296
|
}
|
|
1039
1297
|
i++;
|
|
1040
1298
|
continue;
|
|
1041
1299
|
}
|
|
1042
1300
|
switch (arg) {
|
|
1301
|
+
case "-h":
|
|
1302
|
+
case "--help":
|
|
1303
|
+
flags.help = true;
|
|
1304
|
+
i++;
|
|
1305
|
+
break;
|
|
1043
1306
|
case "-g":
|
|
1044
1307
|
case "--global":
|
|
1045
1308
|
flags.global = true;
|
|
@@ -1077,6 +1340,8 @@ function parseRemoveFlags(args) {
|
|
|
1077
1340
|
default:
|
|
1078
1341
|
if (!arg.startsWith("-")) {
|
|
1079
1342
|
names.push(arg);
|
|
1343
|
+
} else {
|
|
1344
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
1080
1345
|
}
|
|
1081
1346
|
i++;
|
|
1082
1347
|
break;
|
|
@@ -1087,20 +1352,28 @@ function parseRemoveFlags(args) {
|
|
|
1087
1352
|
}
|
|
1088
1353
|
return { names, flags };
|
|
1089
1354
|
}
|
|
1355
|
+
function printRemoveHelp() {
|
|
1356
|
+
console.log("Usage: skill-master remove [skills...] [options]");
|
|
1357
|
+
console.log("");
|
|
1358
|
+
console.log("Options:");
|
|
1359
|
+
console.log(" -h, --help Show this help message");
|
|
1360
|
+
console.log(" -g, --global Remove from global (~/.agents/)");
|
|
1361
|
+
console.log(" -a, --agent <agents> Target agents (space-separated)");
|
|
1362
|
+
console.log(" -s, --skill <skills> Select skills (space-separated)");
|
|
1363
|
+
console.log(" -y, --yes Skip confirmations");
|
|
1364
|
+
console.log(" --all Remove all skills");
|
|
1365
|
+
console.log(" --purge Also remove config data");
|
|
1366
|
+
}
|
|
1090
1367
|
async function remove(args) {
|
|
1091
1368
|
if (args.length === 0) {
|
|
1092
|
-
|
|
1093
|
-
console.log("");
|
|
1094
|
-
console.log("Options:");
|
|
1095
|
-
console.log(" -g, --global Remove from global (~/.agents/)");
|
|
1096
|
-
console.log(" -a, --agent <agents> Target agents (space-separated)");
|
|
1097
|
-
console.log(" -s, --skill <skills> Select skills (space-separated)");
|
|
1098
|
-
console.log(" -y, --yes Skip confirmations");
|
|
1099
|
-
console.log(" --all Remove all skills");
|
|
1100
|
-
console.log(" --purge Also remove config data");
|
|
1369
|
+
printRemoveHelp();
|
|
1101
1370
|
process.exit(1);
|
|
1102
1371
|
}
|
|
1103
1372
|
const { names, flags } = parseRemoveFlags(args);
|
|
1373
|
+
if (flags.help) {
|
|
1374
|
+
printRemoveHelp();
|
|
1375
|
+
process.exit(0);
|
|
1376
|
+
}
|
|
1104
1377
|
let skillNames;
|
|
1105
1378
|
if (flags.all) {
|
|
1106
1379
|
const registry = await listRegistry();
|
|
@@ -1121,15 +1394,39 @@ async function remove(args) {
|
|
|
1121
1394
|
throw new SkillNotFoundError(skillName);
|
|
1122
1395
|
}
|
|
1123
1396
|
info(`Removing skill: ${skillName}`);
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1397
|
+
if (flags.agent.length > 0) {
|
|
1398
|
+
for (const agentName of flags.agent) {
|
|
1399
|
+
const agentRecord = entry.agents.find((a) => a.agent === agentName);
|
|
1400
|
+
if (!agentRecord) {
|
|
1401
|
+
warn(`Agent "${agentName}" not found for skill "${skillName}"`);
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
await removePath(agentRecord.agent_path);
|
|
1405
|
+
success(`Removed ${agentName} path: ${agentRecord.agent_path}`);
|
|
1406
|
+
await removeAgentFromRegistry(skillName, agentName);
|
|
1407
|
+
}
|
|
1408
|
+
const updated = await getRegistryEntry(skillName);
|
|
1409
|
+
if (!updated) {
|
|
1410
|
+
await removePath(entry.canonical_path);
|
|
1411
|
+
success(`Removed canonical path: ${entry.canonical_path}`);
|
|
1412
|
+
if (flags.purge) {
|
|
1413
|
+
await removePath(getSkillConfigPath(skillName));
|
|
1414
|
+
success("Purged config directory");
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
} else {
|
|
1418
|
+
for (const agentRecord of entry.agents) {
|
|
1419
|
+
await removePath(agentRecord.agent_path);
|
|
1420
|
+
success(`Removed ${agentRecord.agent} path: ${agentRecord.agent_path}`);
|
|
1421
|
+
}
|
|
1422
|
+
await removePath(entry.canonical_path);
|
|
1423
|
+
success(`Removed canonical path: ${entry.canonical_path}`);
|
|
1424
|
+
if (flags.purge) {
|
|
1425
|
+
await removePath(getSkillConfigPath(skillName));
|
|
1426
|
+
success("Purged config directory");
|
|
1427
|
+
}
|
|
1428
|
+
await removeFromRegistry(skillName);
|
|
1131
1429
|
}
|
|
1132
|
-
await removeFromRegistry(skillName);
|
|
1133
1430
|
success(`Skill "${skillName}" removed successfully!`);
|
|
1134
1431
|
}
|
|
1135
1432
|
} catch (err) {
|
|
@@ -1255,17 +1552,20 @@ async function list(args = []) {
|
|
|
1255
1552
|
const skills = await listRegistry();
|
|
1256
1553
|
let entries = Object.entries(skills);
|
|
1257
1554
|
if (flags.agent.length > 0) {
|
|
1258
|
-
entries = entries.filter(
|
|
1555
|
+
entries = entries.filter(
|
|
1556
|
+
([, entry]) => entry.agents.some((a) => flags.agent.includes(a.agent))
|
|
1557
|
+
);
|
|
1259
1558
|
}
|
|
1260
1559
|
if (entries.length === 0) {
|
|
1261
1560
|
info("No skills installed");
|
|
1262
1561
|
return;
|
|
1263
1562
|
}
|
|
1264
1563
|
blank();
|
|
1265
|
-
tableHeader("Skill", "Version", "Platform", "Installed");
|
|
1564
|
+
tableHeader("Skill", "Version", "Platform(s)", "Installed");
|
|
1266
1565
|
for (const [name, entry] of entries) {
|
|
1267
1566
|
const date = new Date(entry.installed_at).toLocaleDateString();
|
|
1268
|
-
|
|
1567
|
+
const platforms = entry.agents.map((a) => a.agent).join(", ");
|
|
1568
|
+
tableRow(name, entry.version ?? "-", platforms, date);
|
|
1269
1569
|
}
|
|
1270
1570
|
blank();
|
|
1271
1571
|
}
|
|
@@ -1286,12 +1586,14 @@ async function info2(args) {
|
|
|
1286
1586
|
blank();
|
|
1287
1587
|
info(`Skill: ${skillName}`);
|
|
1288
1588
|
kv("Version", entry.version ?? "-");
|
|
1289
|
-
kv("Platform", entry.agent);
|
|
1589
|
+
kv("Platform(s)", entry.agents.map((a) => a.agent).join(", "));
|
|
1290
1590
|
kv("Source", entry.source);
|
|
1291
1591
|
kv("Installed", new Date(entry.installed_at).toLocaleString());
|
|
1292
1592
|
kv("Updated", new Date(entry.updated_at).toLocaleString());
|
|
1293
1593
|
kv("Canonical Path", entry.canonical_path);
|
|
1294
|
-
|
|
1594
|
+
for (const a of entry.agents) {
|
|
1595
|
+
kv(` ${a.agent} Path`, `${a.agent_path}${a.global ? " (global)" : ""}`);
|
|
1596
|
+
}
|
|
1295
1597
|
kv("Capabilities", entry.capabilities.join(", "));
|
|
1296
1598
|
kv("Env Keys", entry.env_keys.join(", ") || "none");
|
|
1297
1599
|
kv("Env Status", envStatus);
|
|
@@ -1303,7 +1605,7 @@ async function info2(args) {
|
|
|
1303
1605
|
}
|
|
1304
1606
|
|
|
1305
1607
|
// src/commands/doctor.ts
|
|
1306
|
-
import { existsSync as
|
|
1608
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1307
1609
|
async function doctor() {
|
|
1308
1610
|
blank();
|
|
1309
1611
|
info("Running diagnostics...");
|
|
@@ -1312,7 +1614,7 @@ async function doctor() {
|
|
|
1312
1614
|
info("Checking directory structure...");
|
|
1313
1615
|
const dirs = [AGENTS_HOME, CONFIG_DIR, SKILLS_DIR];
|
|
1314
1616
|
for (const dir of dirs) {
|
|
1315
|
-
if (
|
|
1617
|
+
if (existsSync8(dir)) {
|
|
1316
1618
|
success(`\u2713 ${dir}`);
|
|
1317
1619
|
} else {
|
|
1318
1620
|
warn(`\u2717 ${dir} (missing)`);
|
|
@@ -1321,7 +1623,7 @@ async function doctor() {
|
|
|
1321
1623
|
}
|
|
1322
1624
|
blank();
|
|
1323
1625
|
info("Checking registry...");
|
|
1324
|
-
if (
|
|
1626
|
+
if (existsSync8(REGISTRY_PATH)) {
|
|
1325
1627
|
success(`\u2713 ${REGISTRY_PATH}`);
|
|
1326
1628
|
try {
|
|
1327
1629
|
const skills = await listRegistry();
|
|
@@ -1339,18 +1641,20 @@ async function doctor() {
|
|
|
1339
1641
|
const skills = await listRegistry();
|
|
1340
1642
|
for (const [name, entry] of Object.entries(skills)) {
|
|
1341
1643
|
info(`Skill: ${name}`);
|
|
1342
|
-
if (
|
|
1644
|
+
if (existsSync8(entry.canonical_path)) {
|
|
1343
1645
|
success(` \u2713 Canonical path exists`);
|
|
1344
1646
|
} else {
|
|
1345
1647
|
error(` \u2717 Canonical path missing: ${entry.canonical_path}`);
|
|
1346
1648
|
issues++;
|
|
1347
1649
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1650
|
+
for (const agentRecord of entry.agents) {
|
|
1651
|
+
if (existsSync8(agentRecord.agent_path)) {
|
|
1652
|
+
const isLink = await isSymlink(agentRecord.agent_path);
|
|
1653
|
+
success(` \u2713 ${agentRecord.agent} path exists (${isLink ? "symlink" : "copy"}${agentRecord.global ? ", global" : ""})`);
|
|
1654
|
+
} else {
|
|
1655
|
+
error(` \u2717 ${agentRecord.agent} path missing: ${agentRecord.agent_path}`);
|
|
1656
|
+
issues++;
|
|
1657
|
+
}
|
|
1354
1658
|
}
|
|
1355
1659
|
const envStatus = await getEnvStatus(name, entry.env_keys);
|
|
1356
1660
|
if (envStatus === "configured") {
|
|
@@ -1424,8 +1728,8 @@ async function find(args) {
|
|
|
1424
1728
|
}
|
|
1425
1729
|
|
|
1426
1730
|
// src/commands/init.ts
|
|
1427
|
-
import { existsSync as
|
|
1428
|
-
import { join as
|
|
1731
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1732
|
+
import { join as join8, basename as basename3 } from "path";
|
|
1429
1733
|
var SKILL_MD_TEMPLATE = `---
|
|
1430
1734
|
name: {{NAME}}
|
|
1431
1735
|
version: 0.1.0
|
|
@@ -1451,14 +1755,14 @@ async function init(args) {
|
|
|
1451
1755
|
let targetDir;
|
|
1452
1756
|
let skillName;
|
|
1453
1757
|
if (nameArg) {
|
|
1454
|
-
targetDir =
|
|
1758
|
+
targetDir = join8(cwd, nameArg);
|
|
1455
1759
|
skillName = nameArg;
|
|
1456
1760
|
} else {
|
|
1457
1761
|
targetDir = cwd;
|
|
1458
|
-
skillName =
|
|
1762
|
+
skillName = basename3(cwd);
|
|
1459
1763
|
}
|
|
1460
|
-
const skillMdPath =
|
|
1461
|
-
if (
|
|
1764
|
+
const skillMdPath = join8(targetDir, "SKILL.md");
|
|
1765
|
+
if (existsSync9(skillMdPath)) {
|
|
1462
1766
|
error(`SKILL.md already exists at ${skillMdPath}`);
|
|
1463
1767
|
process.exit(1);
|
|
1464
1768
|
}
|