skills 1.7.1 → 1.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -525
- package/dist/cli.mjs +839 -331
- package/docs/agents.md +135 -0
- package/docs/commands.md +343 -0
- package/docs/creating-skills.md +140 -0
- package/docs/troubleshooting.md +40 -0
- package/package.json +2 -1
package/dist/cli.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { esm_default } from "./_chunks/libs/simple-git.mjs";
|
|
|
7
7
|
import { xdgConfig } from "./_chunks/libs/xdg-basedir.mjs";
|
|
8
8
|
import { require_dist } from "./_chunks/libs/@vercel/detect-agent.mjs";
|
|
9
9
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
10
|
-
import { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
|
|
10
|
+
import { basename, dirname, extname, isAbsolute, join, normalize, posix, relative, resolve, sep } from "path";
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
12
12
|
import { stripVTControlCharacters } from "node:util";
|
|
13
13
|
import { createWriteStream } from "node:fs";
|
|
@@ -17,7 +17,7 @@ import * as readline from "readline";
|
|
|
17
17
|
import { Writable } from "stream";
|
|
18
18
|
import { promisify } from "util";
|
|
19
19
|
import { execFile, spawn, spawnSync } from "child_process";
|
|
20
|
-
import { access, chmod, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
|
|
20
|
+
import { access, chmod, cp, glob, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
|
|
21
21
|
import { parse, stringify } from "yaml";
|
|
22
22
|
import { createHash } from "crypto";
|
|
23
23
|
import { cp as cp$1, mkdir as mkdir$1, mkdtemp as mkdtemp$1, readFile as readFile$1, rm as rm$1, stat as stat$1, writeFile as writeFile$1 } from "node:fs/promises";
|
|
@@ -1015,7 +1015,7 @@ async function cleanupTempDir(dir) {
|
|
|
1015
1015
|
});
|
|
1016
1016
|
}
|
|
1017
1017
|
function parseFrontmatter(raw) {
|
|
1018
|
-
const match = raw.match(
|
|
1018
|
+
const match = raw.match(/^\uFEFF?---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
1019
1019
|
if (!match) return {
|
|
1020
1020
|
data: {},
|
|
1021
1021
|
content: raw
|
|
@@ -1112,14 +1112,13 @@ async function readLocalLock(cwd) {
|
|
|
1112
1112
|
async function writeLocalLock(lock, cwd) {
|
|
1113
1113
|
const lockDir = cwd || process.cwd();
|
|
1114
1114
|
const lockPath = getLocalLockPath(lockDir);
|
|
1115
|
-
const sortedSkills = {
|
|
1116
|
-
for (const key of Object.keys(lock.skills).sort()) {
|
|
1115
|
+
const sortedSkills = Object.fromEntries(Object.keys(lock.skills).sort().map((key) => {
|
|
1117
1116
|
const entry = lock.skills[key];
|
|
1118
|
-
|
|
1117
|
+
return [key, entry.sourceType === "local" ? {
|
|
1119
1118
|
...entry,
|
|
1120
1119
|
source: getPortableLocalSource(entry.source, lockDir)
|
|
1121
|
-
} : entry;
|
|
1122
|
-
}
|
|
1120
|
+
} : entry];
|
|
1121
|
+
}));
|
|
1123
1122
|
const sorted = {
|
|
1124
1123
|
version: lock.version,
|
|
1125
1124
|
skills: sortedSkills
|
|
@@ -1165,12 +1164,15 @@ async function collectFiles(baseDir, currentDir, results) {
|
|
|
1165
1164
|
}
|
|
1166
1165
|
async function addSkillToLocalLock(skillName, entry, cwd) {
|
|
1167
1166
|
const lock = await readLocalLock(cwd);
|
|
1168
|
-
lock.skills
|
|
1167
|
+
lock.skills = {
|
|
1168
|
+
...lock.skills,
|
|
1169
|
+
[skillName]: entry
|
|
1170
|
+
};
|
|
1169
1171
|
await writeLocalLock(lock, cwd);
|
|
1170
1172
|
}
|
|
1171
1173
|
async function removeSkillFromLocalLock(skillName, cwd) {
|
|
1172
1174
|
const lock = await readLocalLock(cwd);
|
|
1173
|
-
if (!(
|
|
1175
|
+
if (!Object.hasOwn(lock.skills, skillName)) return false;
|
|
1174
1176
|
delete lock.skills[skillName];
|
|
1175
1177
|
await writeLocalLock(lock, cwd);
|
|
1176
1178
|
return true;
|
|
@@ -1572,7 +1574,7 @@ const agents = {
|
|
|
1572
1574
|
name: "codex",
|
|
1573
1575
|
displayName: "Codex",
|
|
1574
1576
|
skillsDir: ".agents/skills",
|
|
1575
|
-
globalSkillsDir: join(
|
|
1577
|
+
globalSkillsDir: join(home, ".agents", "skills"),
|
|
1576
1578
|
detectInstalled: async () => {
|
|
1577
1579
|
return existsSync(codexHome) || existsSync("/etc/codex");
|
|
1578
1580
|
}
|
|
@@ -1692,8 +1694,8 @@ const agents = {
|
|
|
1692
1694
|
fx: {
|
|
1693
1695
|
name: "fx",
|
|
1694
1696
|
displayName: "fx",
|
|
1695
|
-
skillsDir: ".
|
|
1696
|
-
globalSkillsDir: join(home, ".
|
|
1697
|
+
skillsDir: ".agents/skills",
|
|
1698
|
+
globalSkillsDir: join(home, ".agents/skills"),
|
|
1697
1699
|
detectInstalled: async () => {
|
|
1698
1700
|
return existsSync(join(home, ".fx"));
|
|
1699
1701
|
}
|
|
@@ -2316,26 +2318,34 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
2316
2318
|
mode: "copy"
|
|
2317
2319
|
};
|
|
2318
2320
|
}
|
|
2319
|
-
|
|
2321
|
+
const sourceIsCanonical = resolve(skill.path) === resolve(canonicalDir);
|
|
2322
|
+
if (!sourceIsCanonical && pathsOverlap(skill.path, canonicalDir)) return {
|
|
2320
2323
|
success: true,
|
|
2321
2324
|
path: canonicalDir,
|
|
2322
2325
|
canonicalPath: canonicalDir,
|
|
2323
|
-
mode:
|
|
2326
|
+
mode: installMode,
|
|
2324
2327
|
skipped: true
|
|
2325
2328
|
};
|
|
2326
|
-
|
|
2327
|
-
|
|
2329
|
+
if (!sourceIsCanonical) if (installMode === "link") {
|
|
2330
|
+
if (!await createSymlink(skill.path, canonicalDir)) {
|
|
2331
|
+
await cleanAndCreateDirectory(canonicalDir);
|
|
2332
|
+
await copyDirectory(skill.path, canonicalDir, agentType);
|
|
2333
|
+
}
|
|
2334
|
+
} else {
|
|
2335
|
+
await cleanAndCreateDirectory(canonicalDir);
|
|
2336
|
+
await copyDirectory(skill.path, canonicalDir, agentType);
|
|
2337
|
+
}
|
|
2328
2338
|
if (isGlobal && isUniversalAgent(agentType)) return {
|
|
2329
2339
|
success: true,
|
|
2330
2340
|
path: canonicalDir,
|
|
2331
2341
|
canonicalPath: canonicalDir,
|
|
2332
|
-
mode:
|
|
2342
|
+
mode: installMode
|
|
2333
2343
|
};
|
|
2334
2344
|
if (shouldSkipProjectAgentSymlink(agentType, isGlobal, cwd, options.createMissingAgentRoot ?? false)) return {
|
|
2335
2345
|
success: true,
|
|
2336
2346
|
path: canonicalDir,
|
|
2337
2347
|
canonicalPath: canonicalDir,
|
|
2338
|
-
mode:
|
|
2348
|
+
mode: installMode,
|
|
2339
2349
|
skipped: true,
|
|
2340
2350
|
skipReason: "missing-agent-project-directory"
|
|
2341
2351
|
};
|
|
@@ -2346,7 +2356,7 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
2346
2356
|
success: true,
|
|
2347
2357
|
path: agentDir,
|
|
2348
2358
|
canonicalPath: canonicalDir,
|
|
2349
|
-
mode:
|
|
2359
|
+
mode: installMode,
|
|
2350
2360
|
symlinkFailed: true
|
|
2351
2361
|
};
|
|
2352
2362
|
}
|
|
@@ -2354,7 +2364,7 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
2354
2364
|
success: true,
|
|
2355
2365
|
path: agentDir,
|
|
2356
2366
|
canonicalPath: canonicalDir,
|
|
2357
|
-
mode:
|
|
2367
|
+
mode: installMode
|
|
2358
2368
|
};
|
|
2359
2369
|
} catch (error) {
|
|
2360
2370
|
return {
|
|
@@ -4172,7 +4182,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
4172
4182
|
tree
|
|
4173
4183
|
};
|
|
4174
4184
|
}
|
|
4175
|
-
var version$1 = "1.7.
|
|
4185
|
+
var version$1 = "1.7.3";
|
|
4176
4186
|
const NOTION_API_VERSION = "2026-03-11";
|
|
4177
4187
|
const NTN_TIMEOUT_MS = 3e4;
|
|
4178
4188
|
const NTN_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
@@ -4376,18 +4386,18 @@ async function fetchNotionSkillDirectory(pageId, options = {}) {
|
|
|
4376
4386
|
}
|
|
4377
4387
|
async function prepareNotionSkillSource(pageId, options = {}) {
|
|
4378
4388
|
const download = options.download ?? downloadNotionDirectory;
|
|
4379
|
-
const spinner$
|
|
4380
|
-
spinner$
|
|
4389
|
+
const spinner$5 = spinner();
|
|
4390
|
+
spinner$5.start("Fetching Notion skill with ntn…");
|
|
4381
4391
|
try {
|
|
4382
4392
|
const [directory, workspace] = await Promise.all([fetchNotionSkillDirectory(pageId, { runNtn: options.runNtn }), fetchNotionWorkspaceName({ runNtn: options.runNtn })]);
|
|
4383
4393
|
const downloaded = await download(directory.url);
|
|
4384
|
-
spinner$
|
|
4394
|
+
spinner$5.stop(`Downloaded skill from Notion${inWorkspace(workspace)}`);
|
|
4385
4395
|
return {
|
|
4386
4396
|
rootDir: downloaded.rootDir,
|
|
4387
4397
|
tempDir: downloaded.tempDir
|
|
4388
4398
|
};
|
|
4389
4399
|
} catch (error) {
|
|
4390
|
-
spinner$
|
|
4400
|
+
spinner$5.stop(import_picocolors.default.red("Failed to prepare Notion skill"));
|
|
4391
4401
|
throw error;
|
|
4392
4402
|
}
|
|
4393
4403
|
}
|
|
@@ -4411,17 +4421,17 @@ function printAvailablePacks(packs) {
|
|
|
4411
4421
|
}
|
|
4412
4422
|
}
|
|
4413
4423
|
async function prepareNotionPackSource(options = {}) {
|
|
4414
|
-
const spinner$
|
|
4415
|
-
spinner$
|
|
4424
|
+
const spinner$6 = spinner();
|
|
4425
|
+
spinner$6.start("Fetching Notion packs with ntn…");
|
|
4416
4426
|
let packs;
|
|
4417
4427
|
let workspace = null;
|
|
4418
4428
|
try {
|
|
4419
4429
|
[packs, workspace] = await Promise.all([fetchNotionPacks({ runNtn: options.runNtn }), fetchNotionWorkspaceName({ runNtn: options.runNtn })]);
|
|
4420
4430
|
} catch (error) {
|
|
4421
|
-
spinner$
|
|
4431
|
+
spinner$6.stop(import_picocolors.default.red("Failed to load Notion packs"));
|
|
4422
4432
|
throw error;
|
|
4423
4433
|
}
|
|
4424
|
-
spinner$
|
|
4434
|
+
spinner$6.stop(`Found ${import_picocolors.default.green(packs.length)} Notion pack${packs.length === 1 ? "" : "s"}${inWorkspace(workspace)}`);
|
|
4425
4435
|
if (packs.length === 0) throw new Error("Notion returned no packs for the authenticated workspace");
|
|
4426
4436
|
if (options.list) {
|
|
4427
4437
|
printAvailablePacks(packs);
|
|
@@ -4460,7 +4470,7 @@ async function prepareNotionPackSource(options = {}) {
|
|
|
4460
4470
|
const download = options.download ?? downloadNotionDirectory;
|
|
4461
4471
|
const discover = options.discover ?? discoverSkills;
|
|
4462
4472
|
let skillCount = 0;
|
|
4463
|
-
spinner$
|
|
4473
|
+
spinner$6.start("Preparing selected Notion packs…");
|
|
4464
4474
|
try {
|
|
4465
4475
|
await mkdir$1(packsDir, { recursive: true });
|
|
4466
4476
|
for (const [index, pack] of selectedPacks.entries()) {
|
|
@@ -4501,7 +4511,7 @@ async function prepareNotionPackSource(options = {}) {
|
|
|
4501
4511
|
metadata: { pluginRoot: "./packs" },
|
|
4502
4512
|
plugins: manifestPlugins
|
|
4503
4513
|
}, null, 2)}\n`, "utf8");
|
|
4504
|
-
spinner$
|
|
4514
|
+
spinner$6.stop(`Prepared ${import_picocolors.default.green(selectedPacks.length)} Notion pack${selectedPacks.length === 1 ? "" : "s"} with ${import_picocolors.default.green(skillCount)} skill${skillCount === 1 ? "" : "s"}`);
|
|
4505
4515
|
return {
|
|
4506
4516
|
rootDir: stagingDir,
|
|
4507
4517
|
tempDir: stagingDir,
|
|
@@ -4509,7 +4519,7 @@ async function prepareNotionPackSource(options = {}) {
|
|
|
4509
4519
|
skillCount
|
|
4510
4520
|
};
|
|
4511
4521
|
} catch (error) {
|
|
4512
|
-
spinner$
|
|
4522
|
+
spinner$6.stop(import_picocolors.default.red("Failed to prepare Notion packs"));
|
|
4513
4523
|
await cleanupTempDir(stagingDir).catch(() => {});
|
|
4514
4524
|
throw error;
|
|
4515
4525
|
}
|
|
@@ -4685,7 +4695,8 @@ async function promptForAgents(message, choices) {
|
|
|
4685
4695
|
const defaultValues = [
|
|
4686
4696
|
"claude-code",
|
|
4687
4697
|
"opencode",
|
|
4688
|
-
"codex"
|
|
4698
|
+
"codex",
|
|
4699
|
+
"grok"
|
|
4689
4700
|
].filter((a) => validAgents.includes(a));
|
|
4690
4701
|
let initialValues = [];
|
|
4691
4702
|
if (lastSelected && lastSelected.length > 0) initialValues = lastSelected.filter((a) => validAgents.includes(a));
|
|
@@ -5048,6 +5059,182 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
5048
5059
|
await promptForFindSkills(options, targetAgents);
|
|
5049
5060
|
return true;
|
|
5050
5061
|
}
|
|
5062
|
+
async function resolveSkills(parsed, options, spinner) {
|
|
5063
|
+
const { includeInternal } = options;
|
|
5064
|
+
let skills;
|
|
5065
|
+
let blobResult = null;
|
|
5066
|
+
let tempDir = null;
|
|
5067
|
+
try {
|
|
5068
|
+
if (parsed.type === "local") {
|
|
5069
|
+
spinner.start("Discovering skills…");
|
|
5070
|
+
skills = await discoverSkills(parsed.localPath, parsed.subpath, {
|
|
5071
|
+
includeInternal,
|
|
5072
|
+
fullDepth: options.fullDepth
|
|
5073
|
+
});
|
|
5074
|
+
} else if (parsed.type === "well-known" || parsed.type === "download") {
|
|
5075
|
+
spinner.start("Downloading source...");
|
|
5076
|
+
const downloaded = await downloadSource(parsed.url);
|
|
5077
|
+
tempDir = downloaded.tempDir;
|
|
5078
|
+
spinner.stop(`Downloaded ${downloaded.kind === "skill-md" ? "SKILL.md file" : "archive"}`);
|
|
5079
|
+
spinner.start("Discovering skills...");
|
|
5080
|
+
skills = await discoverSkills(downloaded.rootDir, parsed.subpath, {
|
|
5081
|
+
includeInternal,
|
|
5082
|
+
fullDepth: options.fullDepth
|
|
5083
|
+
});
|
|
5084
|
+
} else if (parsed.type === "github" && !options.fullDepth) {
|
|
5085
|
+
let attemptedBlobInstall = false;
|
|
5086
|
+
const BLOB_ALLOWED_OWNERS = [
|
|
5087
|
+
"vercel",
|
|
5088
|
+
"vercel-labs",
|
|
5089
|
+
"heygen-com",
|
|
5090
|
+
"remotion-dev"
|
|
5091
|
+
];
|
|
5092
|
+
const ownerRepo = getOwnerRepo(parsed);
|
|
5093
|
+
const owner = ownerRepo?.split("/")[0]?.toLowerCase();
|
|
5094
|
+
const isSelfHostedRepo = !!ownerRepo && Object.hasOwn(BLOB_ALLOWED_REPOS, ownerRepo.toLowerCase());
|
|
5095
|
+
if (ownerRepo && owner && (isSelfHostedRepo || BLOB_ALLOWED_OWNERS.includes(owner))) {
|
|
5096
|
+
attemptedBlobInstall = true;
|
|
5097
|
+
spinner.start("Fetching skills…");
|
|
5098
|
+
blobResult = await tryBlobInstall(ownerRepo, {
|
|
5099
|
+
subpath: parsed.subpath,
|
|
5100
|
+
skillFilter: parsed.skillFilter,
|
|
5101
|
+
ref: parsed.ref,
|
|
5102
|
+
getToken: getGitHubToken,
|
|
5103
|
+
includeInternal
|
|
5104
|
+
});
|
|
5105
|
+
}
|
|
5106
|
+
if (blobResult) {
|
|
5107
|
+
skills = blobResult.skills;
|
|
5108
|
+
spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
|
|
5109
|
+
} else {
|
|
5110
|
+
if (attemptedBlobInstall) spinner.message("Cloning repository…");
|
|
5111
|
+
else spinner.start("Cloning repository…");
|
|
5112
|
+
tempDir = await cloneRepo(parsed.url, parsed.ref);
|
|
5113
|
+
spinner.stop("Repository cloned");
|
|
5114
|
+
spinner.start("Discovering skills…");
|
|
5115
|
+
skills = await discoverSkills(tempDir, parsed.subpath, {
|
|
5116
|
+
includeInternal,
|
|
5117
|
+
fullDepth: options.fullDepth
|
|
5118
|
+
});
|
|
5119
|
+
}
|
|
5120
|
+
} else {
|
|
5121
|
+
spinner.start("Cloning repository…");
|
|
5122
|
+
tempDir = await cloneRepo(parsed.url, parsed.ref);
|
|
5123
|
+
spinner.stop("Repository cloned");
|
|
5124
|
+
spinner.start("Discovering skills…");
|
|
5125
|
+
skills = await discoverSkills(tempDir, parsed.subpath, {
|
|
5126
|
+
includeInternal,
|
|
5127
|
+
fullDepth: options.fullDepth
|
|
5128
|
+
});
|
|
5129
|
+
}
|
|
5130
|
+
} catch (error) {
|
|
5131
|
+
await cleanup(tempDir);
|
|
5132
|
+
throw error;
|
|
5133
|
+
}
|
|
5134
|
+
return {
|
|
5135
|
+
skills,
|
|
5136
|
+
tempDir,
|
|
5137
|
+
blobResult
|
|
5138
|
+
};
|
|
5139
|
+
}
|
|
5140
|
+
async function installToTargets(resolved, skills, targets, options) {
|
|
5141
|
+
const results = [];
|
|
5142
|
+
for (const skill of skills) for (const target of targets) {
|
|
5143
|
+
const { agent, subagent } = target;
|
|
5144
|
+
const installOptions = {
|
|
5145
|
+
global: options.global,
|
|
5146
|
+
mode: options.mode,
|
|
5147
|
+
eveSubagent: subagent,
|
|
5148
|
+
createMissingAgentRoot: options.createMissingAgentRoot(agent)
|
|
5149
|
+
};
|
|
5150
|
+
let result;
|
|
5151
|
+
if (resolved.blobResult && "files" in skill) {
|
|
5152
|
+
const blobSkill = skill;
|
|
5153
|
+
result = await installBlobSkillForAgent({
|
|
5154
|
+
installName: blobSkill.name,
|
|
5155
|
+
files: blobSkill.files
|
|
5156
|
+
}, agent, installOptions);
|
|
5157
|
+
} else result = await installSkillForAgent(skill, agent, installOptions);
|
|
5158
|
+
results.push({
|
|
5159
|
+
skill: getSkillDisplayName(skill),
|
|
5160
|
+
agent: targetDisplayName(target),
|
|
5161
|
+
pluginName: skill.pluginName,
|
|
5162
|
+
...result
|
|
5163
|
+
});
|
|
5164
|
+
}
|
|
5165
|
+
return results;
|
|
5166
|
+
}
|
|
5167
|
+
function getSkillRepoPaths(resolved, skills) {
|
|
5168
|
+
const { tempDir, blobResult } = resolved;
|
|
5169
|
+
const skillFiles = {};
|
|
5170
|
+
for (const skill of skills) if (blobResult && "repoPath" in skill) skillFiles[skill.name] = skill.repoPath;
|
|
5171
|
+
else if (tempDir && skill.path === tempDir) skillFiles[skill.name] = "SKILL.md";
|
|
5172
|
+
else if (tempDir && skill.path.startsWith(tempDir + sep)) skillFiles[skill.name] = skill.path.slice(tempDir.length + 1).split(sep).join("/") + "/SKILL.md";
|
|
5173
|
+
return skillFiles;
|
|
5174
|
+
}
|
|
5175
|
+
function getProjectLockSource(parsed) {
|
|
5176
|
+
return getLockSource(parsed.url, getOwnerRepo(parsed)) || parsed.url;
|
|
5177
|
+
}
|
|
5178
|
+
function projectLockEntry(parsed, skillPath, computedHash) {
|
|
5179
|
+
const sourceUrl = getProjectLockSourceUrl(parsed.type, parsed.url);
|
|
5180
|
+
return {
|
|
5181
|
+
source: getProjectLockSource(parsed),
|
|
5182
|
+
...sourceUrl && { sourceUrl },
|
|
5183
|
+
ref: parsed.ref,
|
|
5184
|
+
sourceType: parsed.type,
|
|
5185
|
+
...skillPath && { skillPath },
|
|
5186
|
+
computedHash
|
|
5187
|
+
};
|
|
5188
|
+
}
|
|
5189
|
+
async function installFromSource(parsed, options) {
|
|
5190
|
+
const spinner$4 = spinner();
|
|
5191
|
+
let resolved = null;
|
|
5192
|
+
try {
|
|
5193
|
+
resolved = await resolveSkills(parsed, { includeInternal: options.skills.length > 0 }, spinner$4);
|
|
5194
|
+
let selected = options.skills.length > 0 ? filterSkills(resolved.skills, options.skills) : resolved.skills;
|
|
5195
|
+
if (selected.length === 0) {
|
|
5196
|
+
spinner$4.stop(import_picocolors.default.red("No matching skills found"));
|
|
5197
|
+
return {
|
|
5198
|
+
installed: [],
|
|
5199
|
+
failed: [],
|
|
5200
|
+
error: "No matching skills found"
|
|
5201
|
+
};
|
|
5202
|
+
}
|
|
5203
|
+
spinner$4.stop(`Found ${import_picocolors.default.green(selected.length)} skill${selected.length > 1 ? "s" : ""}`);
|
|
5204
|
+
if (options.select) selected = await options.select(selected);
|
|
5205
|
+
const results = await installToTargets(resolved, selected, options.agents.map((agent) => ({ agent })), {
|
|
5206
|
+
global: false,
|
|
5207
|
+
mode: "symlink",
|
|
5208
|
+
createMissingAgentRoot: () => true
|
|
5209
|
+
});
|
|
5210
|
+
const installed = new Set(results.filter((r) => r.success).map((r) => r.skill));
|
|
5211
|
+
const repoPaths = getSkillRepoPaths(resolved, selected);
|
|
5212
|
+
for (const skill of selected) {
|
|
5213
|
+
if (!installed.has(getSkillDisplayName(skill))) continue;
|
|
5214
|
+
const entry = projectLockEntry(parsed, repoPaths[skill.name], await sourceSkillHash(skill));
|
|
5215
|
+
await addSkillToLocalLock(skill.name, {
|
|
5216
|
+
...entry,
|
|
5217
|
+
...options.via && { via: options.via }
|
|
5218
|
+
});
|
|
5219
|
+
}
|
|
5220
|
+
return {
|
|
5221
|
+
installed: [...installed],
|
|
5222
|
+
failed: results.filter((r) => !r.success).map((r) => `${r.skill} → ${r.agent}: ${r.error}`)
|
|
5223
|
+
};
|
|
5224
|
+
} catch (error) {
|
|
5225
|
+
spinner$4.stop(import_picocolors.default.red("Installation failed"));
|
|
5226
|
+
return {
|
|
5227
|
+
installed: [],
|
|
5228
|
+
failed: [],
|
|
5229
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5230
|
+
};
|
|
5231
|
+
} finally {
|
|
5232
|
+
await cleanup(resolved?.tempDir ?? null);
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
function sourceSkillHash(skill) {
|
|
5236
|
+
return "snapshotHash" in skill ? Promise.resolve(skill.snapshotHash) : computeSkillFolderHash(skill.path);
|
|
5237
|
+
}
|
|
5051
5238
|
async function runAdd(args, options = {}) {
|
|
5052
5239
|
const source = args[0];
|
|
5053
5240
|
let installTipShown = false;
|
|
@@ -5162,8 +5349,6 @@ async function runAdd(args, options = {}) {
|
|
|
5162
5349
|
if (!options.skill.includes(parsed.skillFilter)) options.skill.push(parsed.skillFilter);
|
|
5163
5350
|
}
|
|
5164
5351
|
const includeInternal = !!(options.skill && options.skill.length > 0 && !options.skill.includes("*"));
|
|
5165
|
-
let skills;
|
|
5166
|
-
let blobResult = null;
|
|
5167
5352
|
if (parsed.type === "local") {
|
|
5168
5353
|
spinner$3.start("Validating local path…");
|
|
5169
5354
|
if (!existsSync(parsed.localPath)) {
|
|
@@ -5172,67 +5357,13 @@ async function runAdd(args, options = {}) {
|
|
|
5172
5357
|
emitJsonAndExit(1, `Local path does not exist: ${parsed.localPath}`);
|
|
5173
5358
|
}
|
|
5174
5359
|
spinner$3.stop("Local path validated");
|
|
5175
|
-
spinner$3.start("Discovering skills…");
|
|
5176
|
-
skills = await discoverSkills(parsed.localPath, parsed.subpath, {
|
|
5177
|
-
includeInternal,
|
|
5178
|
-
fullDepth: options.fullDepth
|
|
5179
|
-
});
|
|
5180
|
-
} else if (parsed.type === "well-known" || parsed.type === "download") {
|
|
5181
|
-
spinner$3.start("Downloading source...");
|
|
5182
|
-
const downloaded = await downloadSource(parsed.url);
|
|
5183
|
-
tempDir = downloaded.tempDir;
|
|
5184
|
-
spinner$3.stop(`Downloaded ${downloaded.kind === "skill-md" ? "SKILL.md file" : "archive"}`);
|
|
5185
|
-
spinner$3.start("Discovering skills...");
|
|
5186
|
-
skills = await discoverSkills(downloaded.rootDir, parsed.subpath, {
|
|
5187
|
-
includeInternal,
|
|
5188
|
-
fullDepth: options.fullDepth
|
|
5189
|
-
});
|
|
5190
|
-
} else if (parsed.type === "github" && !options.fullDepth) {
|
|
5191
|
-
let attemptedBlobInstall = false;
|
|
5192
|
-
const BLOB_ALLOWED_OWNERS = [
|
|
5193
|
-
"vercel",
|
|
5194
|
-
"vercel-labs",
|
|
5195
|
-
"heygen-com",
|
|
5196
|
-
"remotion-dev"
|
|
5197
|
-
];
|
|
5198
|
-
const ownerRepo = getOwnerRepo(parsed);
|
|
5199
|
-
const owner = ownerRepo?.split("/")[0]?.toLowerCase();
|
|
5200
|
-
const isSelfHostedRepo = !!ownerRepo && Object.hasOwn(BLOB_ALLOWED_REPOS, ownerRepo.toLowerCase());
|
|
5201
|
-
if (ownerRepo && owner && (isSelfHostedRepo || BLOB_ALLOWED_OWNERS.includes(owner))) {
|
|
5202
|
-
attemptedBlobInstall = true;
|
|
5203
|
-
spinner$3.start("Fetching skills…");
|
|
5204
|
-
blobResult = await tryBlobInstall(ownerRepo, {
|
|
5205
|
-
subpath: parsed.subpath,
|
|
5206
|
-
skillFilter: parsed.skillFilter,
|
|
5207
|
-
ref: parsed.ref,
|
|
5208
|
-
getToken: getGitHubToken,
|
|
5209
|
-
includeInternal
|
|
5210
|
-
});
|
|
5211
|
-
}
|
|
5212
|
-
if (blobResult) {
|
|
5213
|
-
skills = blobResult.skills;
|
|
5214
|
-
spinner$3.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
|
|
5215
|
-
} else {
|
|
5216
|
-
if (attemptedBlobInstall) spinner$3.message("Cloning repository…");
|
|
5217
|
-
else spinner$3.start("Cloning repository…");
|
|
5218
|
-
tempDir = await cloneRepo(parsed.url, parsed.ref);
|
|
5219
|
-
spinner$3.stop("Repository cloned");
|
|
5220
|
-
spinner$3.start("Discovering skills…");
|
|
5221
|
-
skills = await discoverSkills(tempDir, parsed.subpath, {
|
|
5222
|
-
includeInternal,
|
|
5223
|
-
fullDepth: options.fullDepth
|
|
5224
|
-
});
|
|
5225
|
-
}
|
|
5226
|
-
} else {
|
|
5227
|
-
spinner$3.start("Cloning repository…");
|
|
5228
|
-
tempDir = await cloneRepo(parsed.url, parsed.ref);
|
|
5229
|
-
spinner$3.stop("Repository cloned");
|
|
5230
|
-
spinner$3.start("Discovering skills…");
|
|
5231
|
-
skills = await discoverSkills(tempDir, parsed.subpath, {
|
|
5232
|
-
includeInternal,
|
|
5233
|
-
fullDepth: options.fullDepth
|
|
5234
|
-
});
|
|
5235
5360
|
}
|
|
5361
|
+
const resolved = await resolveSkills(parsed, {
|
|
5362
|
+
includeInternal,
|
|
5363
|
+
fullDepth: options.fullDepth
|
|
5364
|
+
}, spinner$3);
|
|
5365
|
+
const { skills, blobResult } = resolved;
|
|
5366
|
+
tempDir = resolved.tempDir ?? tempDir;
|
|
5236
5367
|
if (skills.length === 0) {
|
|
5237
5368
|
spinner$3.stop(import_picocolors.default.red("No skills found"));
|
|
5238
5369
|
outro(import_picocolors.default.red("No valid skills found. Skills require a SKILL.md with name and description."));
|
|
@@ -5554,47 +5685,19 @@ async function runAdd(args, options = {}) {
|
|
|
5554
5685
|
}
|
|
5555
5686
|
}
|
|
5556
5687
|
spinner$3.start("Installing skills…");
|
|
5557
|
-
const results =
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
const blobSkill = skill;
|
|
5563
|
-
result = await installBlobSkillForAgent({
|
|
5564
|
-
installName: blobSkill.name,
|
|
5565
|
-
files: blobSkill.files
|
|
5566
|
-
}, agent, {
|
|
5567
|
-
global: installGlobally,
|
|
5568
|
-
mode: installMode,
|
|
5569
|
-
eveSubagent: subagent,
|
|
5570
|
-
createMissingAgentRoot: explicitlySelectedAgents.has(agent)
|
|
5571
|
-
});
|
|
5572
|
-
} else result = await installSkillForAgent(skill, agent, {
|
|
5573
|
-
global: installGlobally,
|
|
5574
|
-
mode: installMode,
|
|
5575
|
-
eveSubagent: subagent,
|
|
5576
|
-
createMissingAgentRoot: explicitlySelectedAgents.has(agent)
|
|
5577
|
-
});
|
|
5578
|
-
results.push({
|
|
5579
|
-
skill: getSkillDisplayName(skill),
|
|
5580
|
-
agent: targetDisplayName(target),
|
|
5581
|
-
pluginName: skill.pluginName,
|
|
5582
|
-
...result
|
|
5583
|
-
});
|
|
5584
|
-
}
|
|
5688
|
+
const results = await installToTargets(resolved, selectedSkills, installTargets, {
|
|
5689
|
+
global: installGlobally,
|
|
5690
|
+
mode: installMode,
|
|
5691
|
+
createMissingAgentRoot: (agent) => explicitlySelectedAgents.has(agent)
|
|
5692
|
+
});
|
|
5585
5693
|
spinner$3.stop("Installation complete");
|
|
5586
5694
|
console.log();
|
|
5587
5695
|
const successful = results.filter((r) => r.success);
|
|
5588
5696
|
const failed = results.filter((r) => !r.success);
|
|
5589
5697
|
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
5590
|
-
const skillFiles =
|
|
5591
|
-
for (const skill of selectedSkills) if (blobResult && "repoPath" in skill) skillFiles[skill.name] = skill.repoPath;
|
|
5592
|
-
else if (tempDir && skill.path === tempDir) skillFiles[skill.name] = "SKILL.md";
|
|
5593
|
-
else if (tempDir && skill.path.startsWith(tempDir + sep)) skillFiles[skill.name] = skill.path.slice(tempDir.length + 1).split(sep).join("/") + "/SKILL.md";
|
|
5594
|
-
else continue;
|
|
5698
|
+
const skillFiles = getSkillRepoPaths(resolved, selectedSkills);
|
|
5595
5699
|
const normalizedSource = directDownload ? null : getOwnerRepo(parsed);
|
|
5596
5700
|
const lockSource = directDownload ? null : getLockSource(parsed.url, normalizedSource);
|
|
5597
|
-
const projectLockSourceUrl = directDownload ? void 0 : getProjectLockSourceUrl(parsed.type, parsed.url);
|
|
5598
5701
|
if (normalizedSource) if (parseOwnerRepo(normalizedSource)) {
|
|
5599
5702
|
if (await repoPrivacyPromise === false) track({
|
|
5600
5703
|
event: "install",
|
|
@@ -5619,8 +5722,7 @@ async function runAdd(args, options = {}) {
|
|
|
5619
5722
|
const skillDisplayName = getSkillDisplayName(skill);
|
|
5620
5723
|
if (!successfulSkillNames.has(skillDisplayName)) continue;
|
|
5621
5724
|
try {
|
|
5622
|
-
|
|
5623
|
-
installedSkillHashes.set(skillDisplayName, computedHash);
|
|
5725
|
+
installedSkillHashes.set(skillDisplayName, await sourceSkillHash(skill));
|
|
5624
5726
|
} catch {}
|
|
5625
5727
|
}
|
|
5626
5728
|
if (successful.length > 0 && installGlobally && (normalizedSource || parsed.type === "local" && !directDownload)) {
|
|
@@ -5666,12 +5768,7 @@ async function runAdd(args, options = {}) {
|
|
|
5666
5768
|
if (computedHash === void 0) continue;
|
|
5667
5769
|
const skillPathValue = skillFiles[skill.name];
|
|
5668
5770
|
await addSkillToLocalLock(skill.name, {
|
|
5669
|
-
|
|
5670
|
-
...projectLockSourceUrl && { sourceUrl: projectLockSourceUrl },
|
|
5671
|
-
ref: parsed.ref,
|
|
5672
|
-
sourceType: parsed.type,
|
|
5673
|
-
...skillPathValue && { skillPath: skillPathValue },
|
|
5674
|
-
computedHash,
|
|
5771
|
+
...projectLockEntry(parsed, skillPathValue, computedHash),
|
|
5675
5772
|
...recordSubagents && { subagents: eveSubagents }
|
|
5676
5773
|
}, cwd);
|
|
5677
5774
|
} catch {}
|
|
@@ -6173,76 +6270,402 @@ ${DIM$3} 2) npx skills add <owner/repo@skill>${RESET$3}`;
|
|
|
6173
6270
|
else console.log(`${DIM$3}Discover more skills at${RESET$3} ${TEXT$2}https://skills.sh${RESET$3}`);
|
|
6174
6271
|
console.log();
|
|
6175
6272
|
}
|
|
6273
|
+
const NPM_PREFIX = "npm:";
|
|
6274
|
+
const REMOTE_SOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
6275
|
+
"github",
|
|
6276
|
+
"gitlab",
|
|
6277
|
+
"git"
|
|
6278
|
+
]);
|
|
6279
|
+
function isSkillsFieldEntry(value) {
|
|
6280
|
+
if (typeof value === "string") return true;
|
|
6281
|
+
if (!value || typeof value !== "object" || !("source" in value)) return false;
|
|
6282
|
+
if (typeof value.source !== "string") return false;
|
|
6283
|
+
const skills = "skills" in value ? value.skills : void 0;
|
|
6284
|
+
const ref = "ref" in value ? value.ref : void 0;
|
|
6285
|
+
return (skills === void 0 || Array.isArray(skills) && skills.every((s) => typeof s === "string")) && (ref === void 0 || typeof ref === "string");
|
|
6286
|
+
}
|
|
6287
|
+
function parseSkillsField(entries, declarer) {
|
|
6288
|
+
const parsed = {
|
|
6289
|
+
npm: [],
|
|
6290
|
+
remote: [],
|
|
6291
|
+
errors: []
|
|
6292
|
+
};
|
|
6293
|
+
for (const raw of entries) {
|
|
6294
|
+
if (!isSkillsFieldEntry(raw)) {
|
|
6295
|
+
parsed.errors.push(`${declarer}: invalid "skills" entry ${JSON.stringify(raw)}`);
|
|
6296
|
+
continue;
|
|
6297
|
+
}
|
|
6298
|
+
const { source, skills = [], ref } = typeof raw === "string" ? { source: raw } : raw;
|
|
6299
|
+
if (!source.startsWith(NPM_PREFIX)) {
|
|
6300
|
+
const remote = parseSource(source);
|
|
6301
|
+
if (!REMOTE_SOURCE_TYPES.has(remote.type)) parsed.errors.push(`${declarer}: "${source}" is not a git source`);
|
|
6302
|
+
else if (ref !== void 0 && remote.ref !== void 0) parsed.errors.push(`${declarer}: "${source}" already has a ref; remove "ref"`);
|
|
6303
|
+
else parsed.remote.push({
|
|
6304
|
+
parsed: {
|
|
6305
|
+
...remote,
|
|
6306
|
+
ref: ref ?? remote.ref
|
|
6307
|
+
},
|
|
6308
|
+
skills: remote.skillFilter ? [...skills, remote.skillFilter] : skills
|
|
6309
|
+
});
|
|
6310
|
+
} else if (ref !== void 0) parsed.errors.push(`${declarer}: "ref" cannot be used with "${source}"`);
|
|
6311
|
+
else parsed.npm.push({
|
|
6312
|
+
package: source.slice(4),
|
|
6313
|
+
skills
|
|
6314
|
+
});
|
|
6315
|
+
}
|
|
6316
|
+
return parsed;
|
|
6317
|
+
}
|
|
6176
6318
|
const isCancelled = (value) => typeof value === "symbol";
|
|
6319
|
+
function matchesSkill(skill, patterns) {
|
|
6320
|
+
return patterns.some((pattern) => {
|
|
6321
|
+
const hash = pattern.indexOf("#");
|
|
6322
|
+
const packagePattern = hash === -1 ? pattern : pattern.slice(0, hash);
|
|
6323
|
+
const skillPattern = hash === -1 ? "**" : pattern.slice(hash + 1);
|
|
6324
|
+
return posix.matchesGlob(skill.packageName, packagePattern) && posix.matchesGlob(sanitizeName(skill.name), skillPattern);
|
|
6325
|
+
});
|
|
6326
|
+
}
|
|
6327
|
+
function allowedByFilters(skill, { include, exclude }) {
|
|
6328
|
+
return (!include?.length || matchesSkill(skill, include)) && !(exclude?.length && matchesSkill(skill, exclude));
|
|
6329
|
+
}
|
|
6177
6330
|
function shortenPath$1(fullPath, cwd) {
|
|
6178
6331
|
const home = homedir();
|
|
6179
6332
|
if (fullPath === home || fullPath.startsWith(home + sep)) return "~" + fullPath.slice(home.length);
|
|
6180
6333
|
if (fullPath === cwd || fullPath.startsWith(cwd + sep)) return "." + fullPath.slice(cwd.length);
|
|
6181
6334
|
return fullPath;
|
|
6182
6335
|
}
|
|
6183
|
-
async function
|
|
6184
|
-
const nodeModulesDir = join(cwd, "node_modules");
|
|
6185
|
-
const skills = [];
|
|
6186
|
-
let topNames;
|
|
6336
|
+
async function readPackageJson(dir) {
|
|
6187
6337
|
try {
|
|
6188
|
-
|
|
6338
|
+
return JSON.parse(await readFile(join(dir, "package.json"), "utf-8"));
|
|
6189
6339
|
} catch {
|
|
6190
|
-
return
|
|
6191
|
-
}
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6340
|
+
return null;
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
async function discoverPackageSkills(pkgDir, packageName, depth) {
|
|
6344
|
+
const pkg = await readPackageJson(pkgDir);
|
|
6345
|
+
if (!pkg) return [];
|
|
6346
|
+
const rootSkill = await hasSkillMd(pkgDir) ? await parseSkillMd(join(pkgDir, "SKILL.md")) : null;
|
|
6347
|
+
if (rootSkill) return [{
|
|
6348
|
+
...rootSkill,
|
|
6349
|
+
packageName,
|
|
6350
|
+
packageVersion: pkg.version,
|
|
6351
|
+
skillPath: "SKILL.md",
|
|
6352
|
+
depth
|
|
6353
|
+
}];
|
|
6354
|
+
const skills = [];
|
|
6355
|
+
for (const dir of ["skills", "dist/skills"]) for (const name of await readdir(join(pkgDir, dir)).catch(() => [])) {
|
|
6356
|
+
const skillDir = join(pkgDir, dir, name);
|
|
6357
|
+
if (!await hasSkillMd(skillDir)) continue;
|
|
6358
|
+
const skill = await parseSkillMd(join(skillDir, "SKILL.md"));
|
|
6359
|
+
if (skill) skills.push({
|
|
6360
|
+
...skill,
|
|
6361
|
+
packageName,
|
|
6362
|
+
packageVersion: pkg.version,
|
|
6363
|
+
skillPath: `${dir}/${name}/SKILL.md`,
|
|
6364
|
+
depth
|
|
6365
|
+
});
|
|
6366
|
+
}
|
|
6367
|
+
return skills;
|
|
6368
|
+
}
|
|
6369
|
+
function findInstalledPackage(from, name) {
|
|
6370
|
+
for (let dir = from;; dir = dirname(dir)) {
|
|
6371
|
+
const candidate = join(dir, "node_modules", name);
|
|
6372
|
+
if (existsSync(join(candidate, "package.json"))) return candidate;
|
|
6373
|
+
if (dirname(dir) === dir) return void 0;
|
|
6374
|
+
}
|
|
6375
|
+
}
|
|
6376
|
+
function remoteAllowsSkill(request, name, options) {
|
|
6377
|
+
return request.declarers.some((packageName) => allowedByFilters({
|
|
6378
|
+
name,
|
|
6379
|
+
packageName
|
|
6380
|
+
}, options));
|
|
6381
|
+
}
|
|
6382
|
+
function filterRemoteRequests(requests, options) {
|
|
6383
|
+
return requests.flatMap((request) => {
|
|
6384
|
+
const declarers = request.declarers.filter((name) => (!options.include?.length || options.include.some((p) => posix.matchesGlob(name, p.split("#")[0]))) && !options.exclude?.some((p) => !p.includes("#") && posix.matchesGlob(name, p)));
|
|
6385
|
+
if (declarers.length === 0) return [];
|
|
6386
|
+
const filtered = {
|
|
6387
|
+
...request,
|
|
6388
|
+
via: declarers[0],
|
|
6389
|
+
declarers
|
|
6390
|
+
};
|
|
6391
|
+
if (request.skills.length === 0) return [filtered];
|
|
6392
|
+
const skills = request.skills.filter((name) => remoteAllowsSkill(filtered, name, options));
|
|
6393
|
+
return skills.length > 0 ? [{
|
|
6394
|
+
...filtered,
|
|
6395
|
+
skills
|
|
6396
|
+
}] : [];
|
|
6397
|
+
});
|
|
6398
|
+
}
|
|
6399
|
+
function requestsLockedSkill(request, name, entry) {
|
|
6400
|
+
return isFromRequest(entry, request) && (request.skills.length === 0 || request.skills.some((selected) => sanitizeName(selected) === sanitizeName(name) || selected === posix.basename(posix.dirname(entry.skillPath ?? ""))));
|
|
6401
|
+
}
|
|
6402
|
+
const DEPENDENCY_FIELDS = [
|
|
6403
|
+
"dependencies",
|
|
6404
|
+
"devDependencies",
|
|
6405
|
+
"optionalDependencies",
|
|
6406
|
+
"peerDependencies"
|
|
6407
|
+
];
|
|
6408
|
+
async function findWorkspacePackages(cwd, pkg) {
|
|
6409
|
+
const patterns = [...Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages ?? []];
|
|
6410
|
+
try {
|
|
6411
|
+
const pnpm = parse(await readFile(join(cwd, "pnpm-workspace.yaml"), "utf-8"));
|
|
6412
|
+
if (pnpm && typeof pnpm === "object" && "packages" in pnpm && Array.isArray(pnpm.packages)) patterns.push(...pnpm.packages.filter((p) => typeof p === "string"));
|
|
6413
|
+
} catch {}
|
|
6414
|
+
const negated = patterns.filter((p) => p.startsWith("!")).map((p) => p.slice(1));
|
|
6415
|
+
const included = patterns.filter((p) => !p.startsWith("!"));
|
|
6416
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
6417
|
+
for await (const file of glob(included.map((p) => posix.join(p, "package.json")), {
|
|
6418
|
+
cwd,
|
|
6419
|
+
exclude: (path) => basename(path) === "node_modules"
|
|
6420
|
+
})) {
|
|
6421
|
+
const dir = dirname(file).split(sep).join("/");
|
|
6422
|
+
if (dir !== "." && !negated.some((n) => posix.matchesGlob(dir, n))) dirs.add(join(cwd, dir));
|
|
6423
|
+
}
|
|
6424
|
+
return [...dirs].sort();
|
|
6425
|
+
}
|
|
6426
|
+
async function discoverNodeModuleSkills(cwd, recursive) {
|
|
6427
|
+
const warnings = [];
|
|
6428
|
+
const errors = [];
|
|
6429
|
+
const remote = /* @__PURE__ */ new Map();
|
|
6430
|
+
const pkg = await readPackageJson(cwd);
|
|
6431
|
+
if (!pkg) return {
|
|
6432
|
+
skills: [],
|
|
6433
|
+
remote: [],
|
|
6434
|
+
warnings,
|
|
6435
|
+
errors
|
|
6436
|
+
};
|
|
6437
|
+
const skills = [];
|
|
6438
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6439
|
+
const add = async (found) => {
|
|
6440
|
+
for (const skill of found) {
|
|
6441
|
+
const key = await realpath(skill.path).catch(() => skill.path);
|
|
6442
|
+
if (seen.has(key)) continue;
|
|
6443
|
+
seen.add(key);
|
|
6444
|
+
skills.push(skill);
|
|
6445
|
+
}
|
|
6446
|
+
};
|
|
6447
|
+
const projects = [{
|
|
6448
|
+
name: ".",
|
|
6449
|
+
dir: cwd,
|
|
6450
|
+
pkg,
|
|
6451
|
+
depth: 0
|
|
6452
|
+
}];
|
|
6453
|
+
for (const dir of recursive ? await findWorkspacePackages(cwd, pkg) : []) {
|
|
6454
|
+
const workspacePkg = await readPackageJson(dir);
|
|
6455
|
+
const name = workspacePkg?.name ?? relative(cwd, dir).split(sep).join("/");
|
|
6456
|
+
if (workspacePkg) projects.push({
|
|
6457
|
+
name,
|
|
6458
|
+
dir,
|
|
6459
|
+
pkg: workspacePkg,
|
|
6460
|
+
depth: 1
|
|
6461
|
+
});
|
|
6462
|
+
}
|
|
6463
|
+
const declarers = [];
|
|
6464
|
+
for (const project of projects) {
|
|
6465
|
+
const installed = [...new Set(DEPENDENCY_FIELDS.flatMap((field) => Object.keys(project.pkg[field] ?? {})))].flatMap((name) => {
|
|
6466
|
+
const dir = findInstalledPackage(project.dir, name);
|
|
6467
|
+
return dir ? [{
|
|
6468
|
+
name,
|
|
6469
|
+
dir
|
|
6470
|
+
}] : [];
|
|
6471
|
+
});
|
|
6472
|
+
await add((await Promise.all(installed.map(({ name, dir }) => discoverPackageSkills(dir, name, project.depth)))).flat());
|
|
6473
|
+
declarers.push({
|
|
6474
|
+
name: project.name,
|
|
6475
|
+
dir: project.dir,
|
|
6476
|
+
depth: project.depth
|
|
6477
|
+
}, ...installed.map(({ name, dir }) => ({
|
|
6478
|
+
name,
|
|
6479
|
+
dir,
|
|
6480
|
+
depth: project.depth + 1
|
|
6481
|
+
})));
|
|
6482
|
+
}
|
|
6483
|
+
const visited = /* @__PURE__ */ new Set();
|
|
6484
|
+
for (const declarer of declarers) {
|
|
6485
|
+
const dir = await realpath(declarer.dir).catch(() => null);
|
|
6486
|
+
if (!dir || visited.has(dir)) continue;
|
|
6487
|
+
visited.add(dir);
|
|
6488
|
+
const problems = declarer.name === "." ? errors : warnings;
|
|
6489
|
+
const field = (await readPackageJson(dir))?.skills;
|
|
6490
|
+
if (field === void 0) continue;
|
|
6491
|
+
if (!Array.isArray(field)) {
|
|
6492
|
+
if (problems === errors) errors.push("package.json: \"skills\" must be an array");
|
|
6493
|
+
continue;
|
|
6494
|
+
}
|
|
6495
|
+
const parsed = parseSkillsField(field, declarer.name);
|
|
6496
|
+
problems.push(...parsed.errors);
|
|
6497
|
+
for (const request of parsed.remote) {
|
|
6498
|
+
const { url, ref, subpath } = request.parsed;
|
|
6499
|
+
const key = JSON.stringify([
|
|
6500
|
+
url,
|
|
6501
|
+
ref,
|
|
6502
|
+
subpath,
|
|
6503
|
+
request.skills
|
|
6504
|
+
]);
|
|
6505
|
+
const existing = remote.get(key);
|
|
6506
|
+
if (existing) {
|
|
6507
|
+
if (!existing.declarers.includes(declarer.name)) existing.declarers.push(declarer.name);
|
|
6508
|
+
} else remote.set(key, {
|
|
6509
|
+
...request,
|
|
6510
|
+
via: declarer.name,
|
|
6511
|
+
declarers: [declarer.name]
|
|
6198
6512
|
});
|
|
6199
|
-
return;
|
|
6200
6513
|
}
|
|
6201
|
-
const
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
for (const searchDir of searchDirs) try {
|
|
6207
|
-
const entries = await readdir(searchDir);
|
|
6208
|
-
for (const name of entries) {
|
|
6209
|
-
const skillDir = join(searchDir, name);
|
|
6210
|
-
try {
|
|
6211
|
-
if (!(await stat(skillDir)).isDirectory()) continue;
|
|
6212
|
-
} catch {
|
|
6213
|
-
continue;
|
|
6214
|
-
}
|
|
6215
|
-
const skill = await parseSkillMd(join(skillDir, "SKILL.md"));
|
|
6216
|
-
if (skill) skills.push({
|
|
6217
|
-
...skill,
|
|
6218
|
-
packageName
|
|
6219
|
-
});
|
|
6514
|
+
for (const request of parsed.npm) {
|
|
6515
|
+
const target = findInstalledPackage(dir, request.package);
|
|
6516
|
+
if (!target) {
|
|
6517
|
+
problems.push(`${declarer.name}: cannot resolve "npm:${request.package}"; is it a dependency?`);
|
|
6518
|
+
continue;
|
|
6220
6519
|
}
|
|
6221
|
-
|
|
6520
|
+
await add((await discoverPackageSkills(target, request.package, declarer.depth)).filter((skill) => request.skills.length === 0 || request.skills.includes(basename(skill.path)) || request.skills.includes(sanitizeName(skill.name))).map((skill) => ({
|
|
6521
|
+
...skill,
|
|
6522
|
+
via: declarer.name
|
|
6523
|
+
})));
|
|
6524
|
+
declarers.push({
|
|
6525
|
+
name: request.package,
|
|
6526
|
+
dir: target,
|
|
6527
|
+
depth: declarer.depth + 1
|
|
6528
|
+
});
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
return {
|
|
6532
|
+
skills,
|
|
6533
|
+
remote: [...remote.values()],
|
|
6534
|
+
warnings,
|
|
6535
|
+
errors
|
|
6222
6536
|
};
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6537
|
+
}
|
|
6538
|
+
function isUnderNodeModules(path) {
|
|
6539
|
+
return path.split(sep).includes("node_modules");
|
|
6540
|
+
}
|
|
6541
|
+
function installedBySync(entry) {
|
|
6542
|
+
return entry?.sourceType === "node_modules" || entry?.via !== void 0;
|
|
6543
|
+
}
|
|
6544
|
+
async function foreignDestination(dest, canonicalDir, lockEntry, cwd) {
|
|
6545
|
+
let stats;
|
|
6546
|
+
try {
|
|
6547
|
+
stats = await lstat(dest);
|
|
6548
|
+
} catch {
|
|
6549
|
+
return null;
|
|
6550
|
+
}
|
|
6551
|
+
if (stats.isSymbolicLink()) {
|
|
6552
|
+
const target = resolve(dirname(dest), await readlink(dest));
|
|
6553
|
+
if (isUnderNodeModules(target) || target === canonicalDir) return null;
|
|
6554
|
+
return `${shortenPath$1(dest, cwd)} is a symlink to ${shortenPath$1(target, cwd)}`;
|
|
6555
|
+
}
|
|
6556
|
+
if (installedBySync(lockEntry)) return null;
|
|
6557
|
+
return `${shortenPath$1(dest, cwd)} already exists and was not installed by sync`;
|
|
6558
|
+
}
|
|
6559
|
+
async function linksIntoNodeModules(path) {
|
|
6560
|
+
try {
|
|
6561
|
+
if (!(await lstat(path)).isSymbolicLink()) return false;
|
|
6562
|
+
return isUnderNodeModules(resolve(dirname(path), await readlink(path)));
|
|
6563
|
+
} catch {
|
|
6564
|
+
return false;
|
|
6565
|
+
}
|
|
6566
|
+
}
|
|
6567
|
+
async function pruneStaleSkills(cwd, keep, keepRemote, lock, dryRun) {
|
|
6568
|
+
const canonicalBase = getCanonicalSkillsDir(false, cwd);
|
|
6569
|
+
const lockKeys = /* @__PURE__ */ new Map();
|
|
6570
|
+
const explicit = /* @__PURE__ */ new Set();
|
|
6571
|
+
for (const [key, entry] of Object.entries(lock.skills)) {
|
|
6572
|
+
const remote = entry.sourceType !== "node_modules";
|
|
6573
|
+
if (installedBySync(entry) && !(remote && keepRemote(entry, key))) lockKeys.set(sanitizeName(key), key);
|
|
6574
|
+
else explicit.add(sanitizeName(key));
|
|
6575
|
+
}
|
|
6576
|
+
const candidates = new Set(lockKeys.keys());
|
|
6577
|
+
for (const name of await readdir(canonicalBase).catch(() => [])) if (await linksIntoNodeModules(join(canonicalBase, name))) candidates.add(name);
|
|
6578
|
+
const stale = [...candidates].filter((name) => !keep.has(name) && !explicit.has(name)).sort();
|
|
6579
|
+
if (dryRun || stale.length === 0) return stale;
|
|
6580
|
+
const allAgents = Object.keys(agents);
|
|
6581
|
+
for (const name of stale) {
|
|
6582
|
+
const canonicalDir = join(canonicalBase, name);
|
|
6583
|
+
const key = lockKeys.get(name);
|
|
6584
|
+
const lockEntry = key ? lock.skills[key] : void 0;
|
|
6585
|
+
const destinations = /* @__PURE__ */ new Set([canonicalDir, ...allAgents.map((agent) => getInstallPath(name, agent, { cwd }))]);
|
|
6586
|
+
for (const dest of destinations) if (!await foreignDestination(dest, canonicalDir, lockEntry, cwd)) await rm(dest, {
|
|
6587
|
+
recursive: true,
|
|
6588
|
+
force: true
|
|
6589
|
+
});
|
|
6590
|
+
if (key) delete lock.skills[key];
|
|
6591
|
+
}
|
|
6592
|
+
await writeLocalLock(lock, cwd);
|
|
6593
|
+
return stale;
|
|
6594
|
+
}
|
|
6595
|
+
async function blockedReason(name, targetAgents, lockEntry, cwd) {
|
|
6596
|
+
if (lockEntry && !installedBySync(lockEntry)) return `installed with \`skills add\` from ${lockEntry.source}`;
|
|
6597
|
+
const canonicalDir = getCanonicalPath(name, { cwd });
|
|
6598
|
+
const destinations = /* @__PURE__ */ new Set([canonicalDir, ...targetAgents.map((agent) => getInstallPath(name, agent, { cwd }))]);
|
|
6599
|
+
for (const dest of destinations) {
|
|
6600
|
+
const reason = await foreignDestination(dest, canonicalDir, lockEntry, cwd);
|
|
6601
|
+
if (reason) return reason;
|
|
6602
|
+
}
|
|
6603
|
+
return null;
|
|
6604
|
+
}
|
|
6605
|
+
async function resolveConflicts(skills, targetAgents, lockSkills, cwd) {
|
|
6606
|
+
const lockBySanitizedName = new Map(Object.entries(lockSkills).map(([name, entry]) => [sanitizeName(name), entry]));
|
|
6607
|
+
const byName = Map.groupBy(skills, (skill) => sanitizeName(skill.name));
|
|
6608
|
+
const install = [];
|
|
6609
|
+
const skipped = [];
|
|
6610
|
+
for (const [name, all] of byName) {
|
|
6611
|
+
const depth = Math.min(...all.map((c) => c.depth));
|
|
6612
|
+
const candidates = all.filter((c) => c.depth === depth);
|
|
6613
|
+
for (const skill of all.filter((c) => c.depth !== depth)) skipped.push({
|
|
6614
|
+
skill,
|
|
6615
|
+
reason: `${candidates[0].packageName} is closer to the project`
|
|
6616
|
+
});
|
|
6617
|
+
if (candidates.length > 1) {
|
|
6618
|
+
const packages = candidates.map((c) => c.packageName).join(", ");
|
|
6619
|
+
for (const skill of candidates) skipped.push({
|
|
6620
|
+
skill,
|
|
6621
|
+
reason: `shipped by ${packages}; drop this one with --exclude ${skill.packageName}#${name}`
|
|
6622
|
+
});
|
|
6623
|
+
continue;
|
|
6230
6624
|
}
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6625
|
+
const skill = candidates[0];
|
|
6626
|
+
const reason = await blockedReason(name, targetAgents, lockBySanitizedName.get(name), cwd);
|
|
6627
|
+
if (reason) skipped.push({
|
|
6628
|
+
skill,
|
|
6629
|
+
reason
|
|
6630
|
+
});
|
|
6631
|
+
else install.push(skill);
|
|
6632
|
+
}
|
|
6633
|
+
return {
|
|
6634
|
+
install,
|
|
6635
|
+
skipped
|
|
6636
|
+
};
|
|
6637
|
+
}
|
|
6638
|
+
async function promptForAgentChoice(choices, fallback) {
|
|
6639
|
+
const universalAgents = getUniversalAgents();
|
|
6640
|
+
const visibleUniversalAgents = getVisibleUniversalAgents();
|
|
6641
|
+
const last = await getLastSelectedAgents().catch(() => void 0);
|
|
6642
|
+
const remembered = choices.filter((a) => last?.includes(a));
|
|
6643
|
+
const selected = await searchMultiselect({
|
|
6644
|
+
message: "Which agents do you want to install to?",
|
|
6645
|
+
items: choices.map((a) => ({
|
|
6646
|
+
value: a,
|
|
6647
|
+
label: agents[a].displayName,
|
|
6648
|
+
hint: agents[a].skillsDir
|
|
6649
|
+
})),
|
|
6650
|
+
initialSelected: remembered.length > 0 ? remembered : fallback,
|
|
6651
|
+
lockedSection: {
|
|
6652
|
+
title: "Universal (.agents/skills)",
|
|
6653
|
+
items: visibleUniversalAgents.map((a) => ({
|
|
6654
|
+
value: a,
|
|
6655
|
+
label: agents[a].displayName
|
|
6656
|
+
})),
|
|
6657
|
+
hiddenCount: universalAgents.length - visibleUniversalAgents.length
|
|
6658
|
+
}
|
|
6659
|
+
});
|
|
6660
|
+
if (!isCancelled(selected)) await saveSelectedAgents(selected).catch(() => {});
|
|
6661
|
+
return selected;
|
|
6662
|
+
}
|
|
6663
|
+
function isFromRequest(entry, request) {
|
|
6664
|
+
return entry.via !== void 0 && entry.source === getProjectLockSource(request.parsed) && entry.ref === request.parsed.ref;
|
|
6665
|
+
}
|
|
6666
|
+
function isRemoteInstalled(request, lock, cwd) {
|
|
6667
|
+
const installed = Object.entries(lock.skills).filter(([name, entry]) => isFromRequest(entry, request) && existsSync(getCanonicalPath(name, { cwd }))).map(([name]) => sanitizeName(name));
|
|
6668
|
+
return request.skills.length === 0 ? installed.length > 0 : request.skills.every((name) => installed.includes(sanitizeName(name)));
|
|
6246
6669
|
}
|
|
6247
6670
|
async function runSync(args, options = {}) {
|
|
6248
6671
|
const cwd = process.cwd();
|
|
@@ -6259,50 +6682,54 @@ async function runSync(args, options = {}) {
|
|
|
6259
6682
|
}
|
|
6260
6683
|
}
|
|
6261
6684
|
console.log();
|
|
6262
|
-
if (!agentResult.isAgent) intro(import_picocolors.default.bgCyan(import_picocolors.default.black(" skills
|
|
6685
|
+
if (!agentResult.isAgent) intro(import_picocolors.default.bgCyan(import_picocolors.default.black(" skills sync ")));
|
|
6263
6686
|
if (agentResult.isAgent) log.info(import_picocolors.default.bgCyan(import_picocolors.default.black(import_picocolors.default.bold(` ${agentResult.agent.name} `))) + " Agent detected — installing non-interactively");
|
|
6264
6687
|
const spinner$2 = spinner();
|
|
6265
6688
|
spinner$2.start("Scanning node_modules for skills…");
|
|
6266
|
-
const
|
|
6267
|
-
if (
|
|
6268
|
-
spinner$2.stop(import_picocolors.default.
|
|
6269
|
-
|
|
6689
|
+
const discovery = await discoverNodeModuleSkills(cwd, options.recursive ?? false);
|
|
6690
|
+
if (discovery.errors.length > 0) {
|
|
6691
|
+
spinner$2.stop(import_picocolors.default.red("Invalid skills field"));
|
|
6692
|
+
for (const error of discovery.errors) log.error(error);
|
|
6693
|
+
outro(import_picocolors.default.red("Fix the \"skills\" field in package.json and run sync again."));
|
|
6694
|
+
process.exitCode = 1;
|
|
6695
|
+
return;
|
|
6696
|
+
}
|
|
6697
|
+
const discoveredSkills = discovery.skills.filter((skill) => allowedByFilters(skill, options));
|
|
6698
|
+
const remote = filterRemoteRequests(discovery.remote, options);
|
|
6699
|
+
spinner$2.stop(discoveredSkills.length === 0 ? import_picocolors.default.yellow("No skills found") : `Found ${import_picocolors.default.green(String(discoveredSkills.length))} skill${discoveredSkills.length > 1 ? "s" : ""} in node_modules`);
|
|
6700
|
+
for (const warning of discovery.warnings) log.warn(warning);
|
|
6701
|
+
const localLock = await readLocalLock(cwd);
|
|
6702
|
+
if (options.cleanup !== false) {
|
|
6703
|
+
const keep = new Set(discoveredSkills.map((skill) => sanitizeName(skill.name)));
|
|
6704
|
+
const keepRemote = (entry, name) => options.remote === false || remote.some((r) => requestsLockedSkill(r, name, entry) && remoteAllowsSkill(r, name, options));
|
|
6705
|
+
const stale = await pruneStaleSkills(cwd, keep, keepRemote, localLock, options.dryRun ?? false);
|
|
6706
|
+
for (const name of stale) log.info(`${options.dryRun ? "Would remove" : "Removed"} ${import_picocolors.default.cyan(name)} ${import_picocolors.default.dim("(no longer provided by a dependency)")}`);
|
|
6707
|
+
}
|
|
6708
|
+
const remoteRequests = (options.remote === false ? [] : remote).filter((request) => !isRemoteInstalled(request, localLock, cwd));
|
|
6709
|
+
const cachedRemoteSkills = [];
|
|
6710
|
+
if (options.remote !== false) {
|
|
6711
|
+
const shippedNames = new Set(discoveredSkills.map((skill) => sanitizeName(skill.name)));
|
|
6712
|
+
for (const [name, entry] of Object.entries(localLock.skills)) {
|
|
6713
|
+
if (shippedNames.has(sanitizeName(name))) continue;
|
|
6714
|
+
if (!remote.some((request) => !remoteRequests.includes(request) && requestsLockedSkill(request, name, entry) && remoteAllowsSkill(request, name, options))) continue;
|
|
6715
|
+
const skill = await parseSkillMd(join(getCanonicalPath(name, { cwd }), "SKILL.md"));
|
|
6716
|
+
if (skill) cachedRemoteSkills.push({
|
|
6717
|
+
skill,
|
|
6718
|
+
source: entry.source
|
|
6719
|
+
});
|
|
6720
|
+
}
|
|
6721
|
+
}
|
|
6722
|
+
if (discoveredSkills.length === 0 && remoteRequests.length === 0 && cachedRemoteSkills.length === 0) {
|
|
6723
|
+
outro(import_picocolors.default.dim("Nothing to sync."));
|
|
6270
6724
|
return;
|
|
6271
6725
|
}
|
|
6272
|
-
spinner$2.stop(`Found ${import_picocolors.default.green(String(discoveredSkills.length))} skill${discoveredSkills.length > 1 ? "s" : ""} in node_modules`);
|
|
6273
6726
|
for (const skill of discoveredSkills) {
|
|
6274
6727
|
log.info(`${import_picocolors.default.cyan(skill.name)} ${import_picocolors.default.dim(`from ${skill.packageName}`)}`);
|
|
6275
6728
|
if (skill.description) log.message(import_picocolors.default.dim(` ${skill.description}`));
|
|
6276
6729
|
}
|
|
6277
|
-
const localLock = await readLocalLock(cwd);
|
|
6278
|
-
const toInstall = [];
|
|
6279
|
-
const upToDate = [];
|
|
6280
|
-
if (options.force) {
|
|
6281
|
-
toInstall.push(...discoveredSkills);
|
|
6282
|
-
log.info(import_picocolors.default.dim("Force mode: reinstalling all skills"));
|
|
6283
|
-
} else {
|
|
6284
|
-
for (const skill of discoveredSkills) {
|
|
6285
|
-
const existingEntry = localLock.skills[skill.name];
|
|
6286
|
-
if (existingEntry) {
|
|
6287
|
-
if (await computeSkillFolderHash(skill.path) === existingEntry.computedHash) {
|
|
6288
|
-
upToDate.push(skill.name);
|
|
6289
|
-
continue;
|
|
6290
|
-
}
|
|
6291
|
-
}
|
|
6292
|
-
toInstall.push(skill);
|
|
6293
|
-
}
|
|
6294
|
-
if (upToDate.length > 0) log.info(import_picocolors.default.dim(`${upToDate.length} skill${upToDate.length !== 1 ? "s" : ""} already up to date`));
|
|
6295
|
-
if (toInstall.length === 0) {
|
|
6296
|
-
console.log();
|
|
6297
|
-
outro(import_picocolors.default.green("All skills are up to date."));
|
|
6298
|
-
return;
|
|
6299
|
-
}
|
|
6300
|
-
}
|
|
6301
|
-
log.info(`${toInstall.length} skill${toInstall.length !== 1 ? "s" : ""} to install/update`);
|
|
6302
6730
|
let targetAgents;
|
|
6303
6731
|
const validAgents = Object.keys(agents);
|
|
6304
6732
|
const universalAgents = getUniversalAgents();
|
|
6305
|
-
const visibleUniversalAgents = getVisibleUniversalAgents();
|
|
6306
6733
|
if (options.agent?.includes("*")) {
|
|
6307
6734
|
targetAgents = validAgents;
|
|
6308
6735
|
log.info(`Installing to all ${targetAgents.length} agents`);
|
|
@@ -6319,54 +6746,14 @@ async function runSync(args, options = {}) {
|
|
|
6319
6746
|
const installedAgents = await detectInstalledAgents();
|
|
6320
6747
|
const totalAgents = Object.keys(agents).length;
|
|
6321
6748
|
spinner$2.stop(`${totalAgents} agents`);
|
|
6322
|
-
if (installedAgents.length === 0
|
|
6749
|
+
if (installedAgents.length === 0 && options.yes) {
|
|
6323
6750
|
targetAgents = universalAgents;
|
|
6324
6751
|
log.info("Installing to universal agents");
|
|
6325
|
-
} else {
|
|
6326
|
-
const selected = await searchMultiselect({
|
|
6327
|
-
message: "Which agents do you want to install to?",
|
|
6328
|
-
items: getNonUniversalAgents().map((a) => ({
|
|
6329
|
-
value: a,
|
|
6330
|
-
label: agents[a].displayName,
|
|
6331
|
-
hint: agents[a].skillsDir
|
|
6332
|
-
})),
|
|
6333
|
-
initialSelected: [],
|
|
6334
|
-
lockedSection: {
|
|
6335
|
-
title: "Universal (.agents/skills)",
|
|
6336
|
-
items: visibleUniversalAgents.map((a) => ({
|
|
6337
|
-
value: a,
|
|
6338
|
-
label: agents[a].displayName
|
|
6339
|
-
})),
|
|
6340
|
-
hiddenCount: universalAgents.length - visibleUniversalAgents.length
|
|
6341
|
-
}
|
|
6342
|
-
});
|
|
6343
|
-
if (isCancelled(selected)) {
|
|
6344
|
-
cancel("Sync cancelled");
|
|
6345
|
-
process.exit(0);
|
|
6346
|
-
}
|
|
6347
|
-
targetAgents = selected;
|
|
6348
|
-
}
|
|
6349
|
-
else if (installedAgents.length === 1 || options.yes) {
|
|
6752
|
+
} else if (installedAgents.length === 1 || options.yes) {
|
|
6350
6753
|
targetAgents = [...installedAgents];
|
|
6351
6754
|
for (const ua of universalAgents) if (!targetAgents.includes(ua)) targetAgents.push(ua);
|
|
6352
6755
|
} else {
|
|
6353
|
-
const selected = await
|
|
6354
|
-
message: "Which agents do you want to install to?",
|
|
6355
|
-
items: getNonUniversalAgents().filter((a) => installedAgents.includes(a)).map((a) => ({
|
|
6356
|
-
value: a,
|
|
6357
|
-
label: agents[a].displayName,
|
|
6358
|
-
hint: agents[a].skillsDir
|
|
6359
|
-
})),
|
|
6360
|
-
initialSelected: installedAgents.filter((a) => !universalAgents.includes(a)),
|
|
6361
|
-
lockedSection: {
|
|
6362
|
-
title: "Universal (.agents/skills)",
|
|
6363
|
-
items: visibleUniversalAgents.map((a) => ({
|
|
6364
|
-
value: a,
|
|
6365
|
-
label: agents[a].displayName
|
|
6366
|
-
})),
|
|
6367
|
-
hiddenCount: universalAgents.length - visibleUniversalAgents.length
|
|
6368
|
-
}
|
|
6369
|
-
});
|
|
6756
|
+
const selected = await promptForAgentChoice(getNonUniversalAgents().filter((a) => installedAgents.length === 0 || installedAgents.includes(a)), installedAgents.filter((a) => !universalAgents.includes(a)));
|
|
6370
6757
|
if (isCancelled(selected)) {
|
|
6371
6758
|
cancel("Sync cancelled");
|
|
6372
6759
|
process.exit(0);
|
|
@@ -6374,14 +6761,40 @@ async function runSync(args, options = {}) {
|
|
|
6374
6761
|
targetAgents = selected;
|
|
6375
6762
|
}
|
|
6376
6763
|
}
|
|
6764
|
+
const { install: toInstall, skipped } = await resolveConflicts(discoveredSkills, targetAgents, localLock.skills, cwd);
|
|
6765
|
+
for (const { skill, reason } of skipped) log.warn(`Skipped ${import_picocolors.default.cyan(skill.name)} ${import_picocolors.default.dim(`from ${skill.packageName}`)}: ${reason}`);
|
|
6766
|
+
const cachedToInstall = [];
|
|
6767
|
+
for (const cached of cachedRemoteSkills) {
|
|
6768
|
+
const { skill } = cached;
|
|
6769
|
+
const missing = targetAgents.filter((agent) => !existsSync(getInstallPath(skill.name, agent, { cwd })));
|
|
6770
|
+
if (missing.length === 0) continue;
|
|
6771
|
+
const reason = await blockedReason(skill.name, missing, localLock.skills[skill.name], cwd);
|
|
6772
|
+
if (reason) log.warn(`Skipped ${import_picocolors.default.cyan(skill.name)}: ${reason}`);
|
|
6773
|
+
else cachedToInstall.push({
|
|
6774
|
+
...cached,
|
|
6775
|
+
agents: missing
|
|
6776
|
+
});
|
|
6777
|
+
}
|
|
6778
|
+
if (toInstall.length === 0 && remoteRequests.length === 0 && cachedToInstall.length === 0) {
|
|
6779
|
+
console.log();
|
|
6780
|
+
outro(import_picocolors.default.yellow("Nothing to sync."));
|
|
6781
|
+
return;
|
|
6782
|
+
}
|
|
6783
|
+
const mode = options.copy ? "copy" : "link";
|
|
6377
6784
|
const summaryLines = [];
|
|
6378
6785
|
for (const skill of toInstall) {
|
|
6379
|
-
const
|
|
6786
|
+
const canonicalPath = getCanonicalPath(skill.name, { cwd });
|
|
6380
6787
|
summaryLines.push(`${import_picocolors.default.cyan(skill.name)} ${import_picocolors.default.dim(`← ${skill.packageName}`)}`);
|
|
6381
|
-
summaryLines.push(` ${import_picocolors.default.dim(
|
|
6788
|
+
summaryLines.push(` ${import_picocolors.default.dim(`${shortenPath$1(canonicalPath, cwd)} ${mode === "link" ? "→" : "copied from"} ${shortenPath$1(skill.path, cwd)}`)}`);
|
|
6382
6789
|
}
|
|
6790
|
+
for (const request of remoteRequests) summaryLines.push(`${import_picocolors.default.cyan(getProjectLockSource(request.parsed))} ${import_picocolors.default.dim(`← ${request.via} (remote)`)}`);
|
|
6791
|
+
for (const { skill, source, agents: missing } of cachedToInstall) summaryLines.push(`${import_picocolors.default.cyan(skill.name)} ${import_picocolors.default.dim(`← ${source} (cached), link to ${missing.join(", ")}`)}`);
|
|
6383
6792
|
console.log();
|
|
6384
6793
|
note(summaryLines.join("\n"), "Sync Summary");
|
|
6794
|
+
if (options.dryRun) {
|
|
6795
|
+
outro(import_picocolors.default.dim("Dry run — nothing changed."));
|
|
6796
|
+
return;
|
|
6797
|
+
}
|
|
6385
6798
|
if (!options.yes) {
|
|
6386
6799
|
const confirmed = await confirm({ message: "Proceed with sync?" });
|
|
6387
6800
|
if (isCancel(confirmed) || !confirmed) {
|
|
@@ -6391,11 +6804,27 @@ async function runSync(args, options = {}) {
|
|
|
6391
6804
|
}
|
|
6392
6805
|
spinner$2.start("Syncing skills…");
|
|
6393
6806
|
const results = [];
|
|
6807
|
+
for (const { skill, source, agents: missing } of cachedToInstall) for (const agent of missing) {
|
|
6808
|
+
const result = await installSkillForAgent(skill, agent, {
|
|
6809
|
+
cwd,
|
|
6810
|
+
mode: "symlink",
|
|
6811
|
+
createMissingAgentRoot: true
|
|
6812
|
+
});
|
|
6813
|
+
results.push({
|
|
6814
|
+
skill: skill.name,
|
|
6815
|
+
packageName: source,
|
|
6816
|
+
agent: agents[agent].displayName,
|
|
6817
|
+
success: result.success,
|
|
6818
|
+
path: result.path,
|
|
6819
|
+
canonicalPath: result.canonicalPath,
|
|
6820
|
+
error: result.error
|
|
6821
|
+
});
|
|
6822
|
+
}
|
|
6394
6823
|
for (const skill of toInstall) for (const agent of targetAgents) {
|
|
6395
6824
|
const result = await installSkillForAgent(skill, agent, {
|
|
6396
6825
|
global: false,
|
|
6397
6826
|
cwd,
|
|
6398
|
-
mode: "
|
|
6827
|
+
mode: agent === "eve" ? "copy" : mode
|
|
6399
6828
|
});
|
|
6400
6829
|
results.push({
|
|
6401
6830
|
skill: skill.name,
|
|
@@ -6416,9 +6845,45 @@ async function runSync(args, options = {}) {
|
|
|
6416
6845
|
await addSkillToLocalLock(skill.name, {
|
|
6417
6846
|
source: skill.packageName,
|
|
6418
6847
|
sourceType: "node_modules",
|
|
6848
|
+
skillPath: skill.skillPath,
|
|
6849
|
+
...skill.packageVersion && { version: skill.packageVersion },
|
|
6850
|
+
...skill.via && { via: skill.via },
|
|
6419
6851
|
computedHash
|
|
6420
6852
|
}, cwd);
|
|
6421
6853
|
} catch {}
|
|
6854
|
+
const claimed = new Set(discoveredSkills.map((skill) => sanitizeName(skill.name)));
|
|
6855
|
+
const lockEntryFor = (name) => Object.entries(localLock.skills).find(([key]) => sanitizeName(key) === name)?.[1];
|
|
6856
|
+
for (const request of remoteRequests) {
|
|
6857
|
+
const label = getProjectLockSource(request.parsed);
|
|
6858
|
+
const result = await installFromSource(request.parsed, {
|
|
6859
|
+
skills: request.skills,
|
|
6860
|
+
agents: targetAgents,
|
|
6861
|
+
via: request.via,
|
|
6862
|
+
select: async (skills) => {
|
|
6863
|
+
const selected = [];
|
|
6864
|
+
for (const skill of skills) {
|
|
6865
|
+
const name = sanitizeName(skill.name);
|
|
6866
|
+
if (!remoteAllowsSkill(request, name, options)) continue;
|
|
6867
|
+
const reason = claimed.has(name) ? "another source in this sync provides it" : await blockedReason(name, targetAgents, lockEntryFor(name), cwd);
|
|
6868
|
+
if (reason) {
|
|
6869
|
+
log.warn(`Skipped ${import_picocolors.default.cyan(name)} from ${label}: ${reason}`);
|
|
6870
|
+
continue;
|
|
6871
|
+
}
|
|
6872
|
+
claimed.add(name);
|
|
6873
|
+
selected.push(skill);
|
|
6874
|
+
}
|
|
6875
|
+
return selected;
|
|
6876
|
+
}
|
|
6877
|
+
});
|
|
6878
|
+
if (result.error) {
|
|
6879
|
+
log.error(`Failed to install from ${import_picocolors.default.cyan(label)}: ${result.error}`);
|
|
6880
|
+
process.exitCode = 1;
|
|
6881
|
+
continue;
|
|
6882
|
+
}
|
|
6883
|
+
if (result.installed.length > 0) log.success(`Installed ${result.installed.map((name) => import_picocolors.default.cyan(name)).join(", ")} from ${label}`);
|
|
6884
|
+
for (const failure of result.failed) log.error(failure);
|
|
6885
|
+
if (result.failed.length > 0) process.exitCode = 1;
|
|
6886
|
+
}
|
|
6422
6887
|
console.log();
|
|
6423
6888
|
if (successful.length > 0) {
|
|
6424
6889
|
const bySkill = /* @__PURE__ */ new Map();
|
|
@@ -6430,7 +6895,7 @@ async function runSync(args, options = {}) {
|
|
|
6430
6895
|
const resultLines = [];
|
|
6431
6896
|
for (const [skillName, skillResults] of bySkill) {
|
|
6432
6897
|
const firstResult = skillResults[0];
|
|
6433
|
-
const pkg =
|
|
6898
|
+
const pkg = firstResult.packageName;
|
|
6434
6899
|
if (firstResult.canonicalPath) {
|
|
6435
6900
|
const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
|
|
6436
6901
|
resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim(`← ${pkg}`)}`);
|
|
@@ -6442,13 +6907,14 @@ async function runSync(args, options = {}) {
|
|
|
6442
6907
|
note(resultLines.join("\n"), title);
|
|
6443
6908
|
}
|
|
6444
6909
|
if (failed.length > 0) {
|
|
6910
|
+
process.exitCode = 1;
|
|
6445
6911
|
console.log();
|
|
6446
6912
|
log.error(import_picocolors.default.red(`Failed to install ${failed.length}`));
|
|
6447
6913
|
for (const r of failed) log.message(` ${import_picocolors.default.red("✗")} ${r.skill} → ${r.agent}: ${import_picocolors.default.dim(r.error)}`);
|
|
6448
6914
|
}
|
|
6449
6915
|
track({
|
|
6450
6916
|
event: "experimental_sync",
|
|
6451
|
-
skillCount: String(toInstall.length),
|
|
6917
|
+
skillCount: String(toInstall.length + cachedToInstall.length),
|
|
6452
6918
|
successCount: String(successfulSkillNames.size),
|
|
6453
6919
|
agents: targetAgents.join(",")
|
|
6454
6920
|
});
|
|
@@ -6457,21 +6923,23 @@ async function runSync(args, options = {}) {
|
|
|
6457
6923
|
}
|
|
6458
6924
|
function parseSyncOptions(args) {
|
|
6459
6925
|
const options = {};
|
|
6460
|
-
|
|
6926
|
+
let i = 0;
|
|
6927
|
+
const takeValues = () => {
|
|
6928
|
+
const values = [];
|
|
6929
|
+
while (args[i + 1] !== void 0 && !args[i + 1].startsWith("-")) values.push(args[++i]);
|
|
6930
|
+
return values;
|
|
6931
|
+
};
|
|
6932
|
+
for (; i < args.length; i++) {
|
|
6461
6933
|
const arg = args[i];
|
|
6462
6934
|
if (arg === "-y" || arg === "--yes") options.yes = true;
|
|
6463
|
-
else if (arg === "
|
|
6464
|
-
else if (arg === "-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
nextArg = args[i];
|
|
6472
|
-
}
|
|
6473
|
-
i--;
|
|
6474
|
-
}
|
|
6935
|
+
else if (arg === "--copy") options.copy = true;
|
|
6936
|
+
else if (arg === "--dry-run") options.dryRun = true;
|
|
6937
|
+
else if (arg === "--no-cleanup") options.cleanup = false;
|
|
6938
|
+
else if (arg === "--no-remote") options.remote = false;
|
|
6939
|
+
else if (arg === "-r" || arg === "--recursive") options.recursive = true;
|
|
6940
|
+
else if (arg === "-a" || arg === "--agent") options.agent = [...options.agent ?? [], ...takeValues()];
|
|
6941
|
+
else if (arg === "--include") options.include = [...options.include ?? [], ...takeValues()];
|
|
6942
|
+
else if (arg === "--exclude") options.exclude = [...options.exclude ?? [], ...takeValues()];
|
|
6475
6943
|
}
|
|
6476
6944
|
return { options };
|
|
6477
6945
|
}
|
|
@@ -6568,14 +7036,27 @@ async function runInstallFromLock(args) {
|
|
|
6568
7036
|
}
|
|
6569
7037
|
const remoteCount = skillEntries.length - nodeModuleSkills.length;
|
|
6570
7038
|
if (remoteCount > 0) log.info(`Restoring ${import_picocolors.default.cyan(String(remoteCount))} skill${remoteCount !== 1 ? "s" : ""} from skills-lock.json into ${import_picocolors.default.dim(".agents/skills/")}`);
|
|
6571
|
-
for (const [source, { skills }] of bySource)
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
7039
|
+
for (const [source, { sourceType, skills }] of bySource) {
|
|
7040
|
+
if (sourceType === "well-known") {
|
|
7041
|
+
await runAdd([source], {
|
|
7042
|
+
skill: skills,
|
|
7043
|
+
agent: universalAgentNames,
|
|
7044
|
+
yes: true
|
|
7045
|
+
});
|
|
7046
|
+
continue;
|
|
7047
|
+
}
|
|
7048
|
+
const result = await installFromSource(parseSource(source), {
|
|
7049
|
+
skills,
|
|
7050
|
+
agents: universalAgentNames
|
|
6576
7051
|
});
|
|
6577
|
-
|
|
6578
|
-
|
|
7052
|
+
if (result.error) {
|
|
7053
|
+
log.error(`Failed to install from ${import_picocolors.default.cyan(source)}: ${result.error}`);
|
|
7054
|
+
process.exitCode = 1;
|
|
7055
|
+
continue;
|
|
7056
|
+
}
|
|
7057
|
+
log.success(`Restored ${result.installed.map((name) => import_picocolors.default.cyan(name)).join(", ")}`);
|
|
7058
|
+
for (const failure of result.failed) log.error(failure);
|
|
7059
|
+
if (result.failed.length > 0) process.exitCode = 1;
|
|
6579
7060
|
}
|
|
6580
7061
|
if (nodeModuleSkills.length > 0) {
|
|
6581
7062
|
log.info(`${import_picocolors.default.cyan(String(nodeModuleSkills.length))} skill${nodeModuleSkills.length !== 1 ? "s" : ""} from node_modules`);
|
|
@@ -6739,14 +7220,18 @@ async function runList(args) {
|
|
|
6739
7220
|
console.log();
|
|
6740
7221
|
}
|
|
6741
7222
|
}
|
|
6742
|
-
function resolveSkillsToRemove(requested, folderNames, lockKeys = []) {
|
|
7223
|
+
function resolveSkillsToRemove(requested, folderNames, lockKeys = [], sourceByKey = {}) {
|
|
6743
7224
|
const identityBySanitized = /* @__PURE__ */ new Map();
|
|
6744
7225
|
for (const folder of folderNames) identityBySanitized.set(sanitizeName(folder), folder);
|
|
6745
7226
|
for (const key of lockKeys) identityBySanitized.set(sanitizeName(key), key);
|
|
6746
7227
|
const matched = /* @__PURE__ */ new Set();
|
|
6747
7228
|
for (const name of requested) {
|
|
6748
7229
|
const hit = identityBySanitized.get(sanitizeName(name));
|
|
6749
|
-
if (hit)
|
|
7230
|
+
if (hit) {
|
|
7231
|
+
matched.add(hit);
|
|
7232
|
+
continue;
|
|
7233
|
+
}
|
|
7234
|
+
for (const key of lockKeys) if (sourceByKey[key] === name) matched.add(key);
|
|
6750
7235
|
}
|
|
6751
7236
|
return Array.from(matched);
|
|
6752
7237
|
}
|
|
@@ -6794,9 +7279,11 @@ async function removeCommand(skillNames, options) {
|
|
|
6794
7279
|
}
|
|
6795
7280
|
const installedSkills = Array.from(skillNamesSet).sort();
|
|
6796
7281
|
spinner$1.stop(`Found ${installedSkills.length} unique installed skill(s)`);
|
|
6797
|
-
const
|
|
7282
|
+
const lockEntries = isGlobal ? (await readSkillLock()).skills : (await readLocalLock(cwd)).skills;
|
|
7283
|
+
const lockSkillsKeys = Object.keys(lockEntries);
|
|
7284
|
+
const sourceByKey = Object.fromEntries(lockSkillsKeys.map((key) => [key, lockEntries[key]?.source]));
|
|
6798
7285
|
const requestedSkills = options.all ? [...installedSkills, ...lockSkillsKeys] : skillNames;
|
|
6799
|
-
const resolvedRequestedSkills = options.all || skillNames.length > 0 ? resolveSkillsToRemove(requestedSkills, installedSkills, lockSkillsKeys) : [];
|
|
7286
|
+
const resolvedRequestedSkills = options.all || skillNames.length > 0 ? resolveSkillsToRemove(requestedSkills, installedSkills, lockSkillsKeys, sourceByKey) : [];
|
|
6800
7287
|
if (installedSkills.length === 0 && resolvedRequestedSkills.length === 0) {
|
|
6801
7288
|
outro(import_picocolors.default.yellow("No skills found to remove."));
|
|
6802
7289
|
return;
|
|
@@ -6993,7 +7480,7 @@ function normalizeSkillName(name) {
|
|
|
6993
7480
|
function normalizeSkillPath(path) {
|
|
6994
7481
|
return path.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
6995
7482
|
}
|
|
6996
|
-
function resolveSkillLocations(lockedSkillNames, lockSkills, discovered) {
|
|
7483
|
+
function resolveSkillLocations(lockedSkillNames, lockSkills, discovered, options = {}) {
|
|
6997
7484
|
const discoveredPaths = new Set(discovered.map((skill) => normalizeSkillPath(skill.skillPath)));
|
|
6998
7485
|
const pathsByName = /* @__PURE__ */ new Map();
|
|
6999
7486
|
for (const skill of discovered) {
|
|
@@ -7010,11 +7497,16 @@ function resolveSkillLocations(lockedSkillNames, lockSkills, discovered) {
|
|
|
7010
7497
|
if (!lockedPath) continue;
|
|
7011
7498
|
const normalizedLockedPath = normalizeSkillPath(lockedPath);
|
|
7012
7499
|
const candidates = [...pathsByName.get(normalizeSkillName(name)) ?? []];
|
|
7500
|
+
const lockedPathStillExists = discoveredPaths.has(normalizedLockedPath);
|
|
7501
|
+
if (lockedPathStillExists && options.exactPathDisambiguates) {
|
|
7502
|
+
resolvedPaths.set(name, normalizedLockedPath);
|
|
7503
|
+
continue;
|
|
7504
|
+
}
|
|
7013
7505
|
if (candidates.length > 1) {
|
|
7014
7506
|
ambiguousSkills.push(name);
|
|
7015
7507
|
continue;
|
|
7016
7508
|
}
|
|
7017
|
-
if (
|
|
7509
|
+
if (lockedPathStillExists) {
|
|
7018
7510
|
resolvedPaths.set(name, normalizedLockedPath);
|
|
7019
7511
|
continue;
|
|
7020
7512
|
}
|
|
@@ -7173,8 +7665,8 @@ async function promptDeletions(source, deletedSkills, isGlobal, options) {
|
|
|
7173
7665
|
});
|
|
7174
7666
|
}
|
|
7175
7667
|
}
|
|
7176
|
-
async function checkAndPromptForDeletions(source, allLockedForSource, lockSkills, isGlobal, options, discovered) {
|
|
7177
|
-
const resolution = resolveSkillLocations(allLockedForSource, lockSkills, discovered);
|
|
7668
|
+
async function checkAndPromptForDeletions(source, allLockedForSource, lockSkills, isGlobal, options, discovered, locationOptions = {}) {
|
|
7669
|
+
const resolution = resolveSkillLocations(allLockedForSource, lockSkills, discovered, locationOptions);
|
|
7178
7670
|
if (resolution.ambiguousSkills.length > 0) {
|
|
7179
7671
|
console.log();
|
|
7180
7672
|
console.log(`${DIM$1}Warning:${RESET$1} Multiple current paths match these skills from ${DIM$1}${source}${RESET$1}; skipping them rather than deleting or migrating the wrong skill:`);
|
|
@@ -7387,7 +7879,7 @@ async function updateGlobalSkills(options = {}) {
|
|
|
7387
7879
|
name: skill.name,
|
|
7388
7880
|
skillPath: join(relative(tempDir, skill.path), "SKILL.md").split(sep).join("/")
|
|
7389
7881
|
}));
|
|
7390
|
-
const resolution = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source && entry.ref === firstEntry.ref).map(([name, _]) => name), lock.skills, true, options, discoveredLocations);
|
|
7882
|
+
const resolution = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source && entry.ref === firstEntry.ref).map(([name, _]) => name), lock.skills, true, options, discoveredLocations, { exactPathDisambiguates: !shouldUseFullDepthForUpdate(firstEntry) });
|
|
7391
7883
|
const deletedSkillSet = new Set(resolution.deletedSkills);
|
|
7392
7884
|
for (const { name: skillName, entry } of itemsForSource) {
|
|
7393
7885
|
if (deletedSkillSet.has(skillName)) continue;
|
|
@@ -7595,7 +8087,7 @@ async function updateProjectSkills(options = {}) {
|
|
|
7595
8087
|
name: skill.name,
|
|
7596
8088
|
skillPath: join(relative(tempDir, skill.path), "SKILL.md").split(sep).join("/")
|
|
7597
8089
|
}));
|
|
7598
|
-
const resolution = await checkAndPromptForDeletions(source, allLockedForSource, localLock.skills, false, options, discoveredLocations);
|
|
8090
|
+
const resolution = await checkAndPromptForDeletions(source, allLockedForSource, localLock.skills, false, options, discoveredLocations, { exactPathDisambiguates: !shouldUseFullDepthForUpdate(firstEntry) });
|
|
7599
8091
|
deletedSkills = resolution.deletedSkills;
|
|
7600
8092
|
resolvedPaths = resolution.resolvedPaths;
|
|
7601
8093
|
for (const [name, skillPath] of resolution.resolvedPaths) try {
|
|
@@ -8160,7 +8652,7 @@ function showBanner() {
|
|
|
8160
8652
|
console.log();
|
|
8161
8653
|
console.log(` ${DIM}$${RESET} ${TEXT}npx skills experimental_install${RESET} ${DIM}Restore from skills-lock.json${RESET}`);
|
|
8162
8654
|
console.log(` ${DIM}$${RESET} ${TEXT}npx skills init ${DIM}[name]${RESET} ${DIM}Create a new skill${RESET}`);
|
|
8163
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills
|
|
8655
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills sync${RESET} ${DIM}Sync skills from node_modules${RESET}`);
|
|
8164
8656
|
console.log();
|
|
8165
8657
|
console.log(`${DIM}try:${RESET} npx skills add vercel-labs/agent-skills`);
|
|
8166
8658
|
console.log();
|
|
@@ -8168,6 +8660,37 @@ function showBanner() {
|
|
|
8168
8660
|
console.log();
|
|
8169
8661
|
}
|
|
8170
8662
|
function showHelp() {
|
|
8663
|
+
const examples = [
|
|
8664
|
+
["skills add vercel-labs/agent-skills"],
|
|
8665
|
+
["skills use vercel-labs/agent-skills@vercel-optimize | claude"],
|
|
8666
|
+
["skills use vercel-labs/agent-skills --skill vercel-optimize --agent claude-code"],
|
|
8667
|
+
["skills add vercel-labs/agent-skills -g"],
|
|
8668
|
+
["skills add vercel-labs/agent-skills --agent claude-code cursor"],
|
|
8669
|
+
["skills add vercel-labs/agent-skills --skill pr-review commit"],
|
|
8670
|
+
["skills add vercel-labs/agent-skills --json -y", "JSON output"],
|
|
8671
|
+
["skills remove", "interactive remove"],
|
|
8672
|
+
["skills remove web-design", "remove by name"],
|
|
8673
|
+
["skills rm --global frontend-design"],
|
|
8674
|
+
["skills list", "list project skills"],
|
|
8675
|
+
["skills ls -g", "list global skills"],
|
|
8676
|
+
["skills ls -a claude-code", "filter by agent"],
|
|
8677
|
+
["skills ls --json", "JSON output"],
|
|
8678
|
+
["skills find", "interactive search"],
|
|
8679
|
+
["skills find typescript", "search by keyword"],
|
|
8680
|
+
["skills find react --owner vercel", "search within an owner"],
|
|
8681
|
+
["skills update"],
|
|
8682
|
+
["skills update my-skill", "update a single skill"],
|
|
8683
|
+
["skills update -g", "update global skills only"],
|
|
8684
|
+
["skills experimental_install", "restore from skills-lock.json"],
|
|
8685
|
+
["skills init my-skill"],
|
|
8686
|
+
["skills sync", "sync from node_modules"],
|
|
8687
|
+
["skills sync -y", "sync without prompts"]
|
|
8688
|
+
];
|
|
8689
|
+
const exampleWidth = Math.max(...examples.filter(([, description]) => description).map(([command]) => command.length));
|
|
8690
|
+
const formattedExamples = examples.map(([command, description]) => {
|
|
8691
|
+
const comment = description ? `${" ".repeat(exampleWidth - command.length + 2)}${DIM}# ${description}${RESET}` : "";
|
|
8692
|
+
return ` ${DIM}$${RESET} ${command}${comment}`;
|
|
8693
|
+
}).join("\n");
|
|
8171
8694
|
console.log(`
|
|
8172
8695
|
${BOLD}Usage:${RESET} skills <command> [options]
|
|
8173
8696
|
|
|
@@ -8197,7 +8720,7 @@ ${BOLD}Update Options:${RESET}
|
|
|
8197
8720
|
${BOLD}Project:${RESET}
|
|
8198
8721
|
experimental_install Restore skills from skills-lock.json
|
|
8199
8722
|
init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
|
|
8200
|
-
|
|
8723
|
+
sync Sync skills from node_modules into agent directories (alias: experimental_sync)
|
|
8201
8724
|
|
|
8202
8725
|
${BOLD}Add Options:${RESET}
|
|
8203
8726
|
-g, --global Install skill globally (user-level) instead of project-level
|
|
@@ -8224,9 +8747,16 @@ ${BOLD}Remove Options:${RESET}
|
|
|
8224
8747
|
-y, --yes Skip confirmation prompts
|
|
8225
8748
|
--all Remove every installed skill (-y implied). Do not combine with named skills.
|
|
8226
8749
|
|
|
8227
|
-
${BOLD}
|
|
8750
|
+
${BOLD}Sync Options:${RESET}
|
|
8228
8751
|
-a, --agent <agents> Specify agents to install to (use '*' for all agents)
|
|
8229
8752
|
-y, --yes Skip confirmation prompts
|
|
8753
|
+
--copy Copy skills instead of linking them to node_modules
|
|
8754
|
+
--dry-run Show what would change without changing anything
|
|
8755
|
+
--no-cleanup Keep skills whose package no longer ships them
|
|
8756
|
+
--no-remote Skip git sources listed in package.json skills fields
|
|
8757
|
+
-r, --recursive Also sync the dependencies of each workspace package
|
|
8758
|
+
--include <patterns> Only sync matching packages (<pkg>) or skills (<pkg>#<skill>)
|
|
8759
|
+
--exclude <patterns> Skip matching packages (<pkg>) or skills (<pkg>#<skill>)
|
|
8230
8760
|
|
|
8231
8761
|
${BOLD}List Options:${RESET}
|
|
8232
8762
|
-g, --global List global skills (default: project)
|
|
@@ -8238,30 +8768,7 @@ ${BOLD}Options:${RESET}
|
|
|
8238
8768
|
--version, -v Show version number
|
|
8239
8769
|
|
|
8240
8770
|
${BOLD}Examples:${RESET}
|
|
8241
|
-
|
|
8242
|
-
${DIM}$${RESET} skills use vercel-labs/agent-skills@vercel-optimize | claude
|
|
8243
|
-
${DIM}$${RESET} skills use vercel-labs/agent-skills --skill vercel-optimize --agent claude-code
|
|
8244
|
-
${DIM}$${RESET} skills add vercel-labs/agent-skills -g
|
|
8245
|
-
${DIM}$${RESET} skills add vercel-labs/agent-skills --agent claude-code cursor
|
|
8246
|
-
${DIM}$${RESET} skills add vercel-labs/agent-skills --skill pr-review commit
|
|
8247
|
-
${DIM}$${RESET} skills add vercel-labs/agent-skills --json -y ${DIM}# JSON output${RESET}
|
|
8248
|
-
${DIM}$${RESET} skills remove ${DIM}# interactive remove${RESET}
|
|
8249
|
-
${DIM}$${RESET} skills remove web-design ${DIM}# remove by name${RESET}
|
|
8250
|
-
${DIM}$${RESET} skills rm --global frontend-design
|
|
8251
|
-
${DIM}$${RESET} skills list ${DIM}# list project skills${RESET}
|
|
8252
|
-
${DIM}$${RESET} skills ls -g ${DIM}# list global skills${RESET}
|
|
8253
|
-
${DIM}$${RESET} skills ls -a claude-code ${DIM}# filter by agent${RESET}
|
|
8254
|
-
${DIM}$${RESET} skills ls --json ${DIM}# JSON output${RESET}
|
|
8255
|
-
${DIM}$${RESET} skills find ${DIM}# interactive search${RESET}
|
|
8256
|
-
${DIM}$${RESET} skills find typescript ${DIM}# search by keyword${RESET}
|
|
8257
|
-
${DIM}$${RESET} skills find react --owner vercel ${DIM}# search within an owner${RESET}
|
|
8258
|
-
${DIM}$${RESET} skills update
|
|
8259
|
-
${DIM}$${RESET} skills update my-skill ${DIM}# update a single skill${RESET}
|
|
8260
|
-
${DIM}$${RESET} skills update -g ${DIM}# update global skills only${RESET}
|
|
8261
|
-
${DIM}$${RESET} skills experimental_install ${DIM}# restore from skills-lock.json${RESET}
|
|
8262
|
-
${DIM}$${RESET} skills init my-skill
|
|
8263
|
-
${DIM}$${RESET} skills experimental_sync ${DIM}# sync from node_modules${RESET}
|
|
8264
|
-
${DIM}$${RESET} skills experimental_sync -y ${DIM}# sync without prompts${RESET}
|
|
8771
|
+
${formattedExamples}
|
|
8265
8772
|
|
|
8266
8773
|
Discover more skills at ${TEXT}https://skills.sh/${RESET}
|
|
8267
8774
|
`);
|
|
@@ -8402,6 +8909,7 @@ async function main() {
|
|
|
8402
8909
|
await removeCommand(skills, removeOptions);
|
|
8403
8910
|
break;
|
|
8404
8911
|
}
|
|
8912
|
+
case "sync":
|
|
8405
8913
|
case "experimental_sync": {
|
|
8406
8914
|
if (!inAgent) showLogo();
|
|
8407
8915
|
const { options: syncOptions } = parseSyncOptions(restArgs);
|