bip-skills 1.4.11 → 1.4.13
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.mjs +386 -144
- package/package.json +19 -14
package/dist/cli.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { t as require_gray_matter } from "./_chunks/libs/gray-matter.mjs";
|
|
|
9
9
|
import "./_chunks/libs/extend-shallow.mjs";
|
|
10
10
|
import "./_chunks/libs/esprima.mjs";
|
|
11
11
|
import { t as xdgConfig } from "./_chunks/libs/xdg-basedir.mjs";
|
|
12
|
-
import { execSync, spawnSync } from "child_process";
|
|
12
|
+
import { execFile, execSync, spawnSync } from "child_process";
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
14
14
|
import { basename, dirname, isAbsolute, join, normalize, posix, relative, resolve, sep } from "path";
|
|
15
15
|
import { homedir, platform, tmpdir } from "os";
|
|
@@ -17,6 +17,7 @@ import { fileURLToPath } from "url";
|
|
|
17
17
|
import * as readline from "readline";
|
|
18
18
|
import { Writable } from "stream";
|
|
19
19
|
import { Buffer } from "node:buffer";
|
|
20
|
+
import { promisify } from "util";
|
|
20
21
|
import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
|
|
21
22
|
import http from "node:http";
|
|
22
23
|
import https from "node:https";
|
|
@@ -683,6 +684,118 @@ function filterSkills(skills, inputNames) {
|
|
|
683
684
|
return normalizedInputs.some((input) => input === name || input === displayName);
|
|
684
685
|
});
|
|
685
686
|
}
|
|
687
|
+
const execFileAsync = promisify(execFile);
|
|
688
|
+
const GATEWAY_PROCESS_RE = /(?:yonclaw|openclaw)-gateway/i;
|
|
689
|
+
const YONCLAW_PROCESS_RE = /yonclaw|openclaw-gateway|yonclaw-gateway/i;
|
|
690
|
+
function getYonClawBaseDir(options = {}) {
|
|
691
|
+
if (options.baseDir) return options.baseDir;
|
|
692
|
+
const home = options.homeDir ?? homedir();
|
|
693
|
+
const currentPlatform = options.platform ?? platform();
|
|
694
|
+
const env = options.env ?? process.env;
|
|
695
|
+
if (env.YONCLAW_HOME) return env.YONCLAW_HOME;
|
|
696
|
+
if (currentPlatform === "darwin") return join(home, "Library", "Application Support", "yonclaw");
|
|
697
|
+
if (currentPlatform === "win32") return join(env.APPDATA || join(home, "AppData", "Roaming"), "yonclaw");
|
|
698
|
+
return join(options.xdgConfigDir || env.XDG_CONFIG_HOME || xdgConfig || join(home, ".config"), "yonclaw");
|
|
699
|
+
}
|
|
700
|
+
function getYonClawProfileSkillsDir(baseDir, profileKey) {
|
|
701
|
+
return join(baseDir, "profiles", profileKey, "userData", "runtime", "openclaw", "skills");
|
|
702
|
+
}
|
|
703
|
+
function getYonClawGlobalSkillsDirHint(options = {}) {
|
|
704
|
+
return getYonClawProfileSkillsDir(getYonClawBaseDir(options), "profile-*");
|
|
705
|
+
}
|
|
706
|
+
async function readJson(path) {
|
|
707
|
+
try {
|
|
708
|
+
return JSON.parse(await readFile(path, "utf-8"));
|
|
709
|
+
} catch {
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
async function listProfileKeys(baseDir) {
|
|
714
|
+
try {
|
|
715
|
+
return (await readdir(join(baseDir, "profiles"), { withFileTypes: true })).filter((entry) => entry.isDirectory() && entry.name.startsWith("profile-")).map((entry) => entry.name).sort();
|
|
716
|
+
} catch {
|
|
717
|
+
return [];
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function unique(items) {
|
|
721
|
+
return Array.from(new Set(items));
|
|
722
|
+
}
|
|
723
|
+
function parseUserDataProfile(command) {
|
|
724
|
+
const match = command.match(/--user-data-dir=(?:"([^"]+)"|'([^']+)'|(\S+))/);
|
|
725
|
+
const userDataDir = match?.[1] ?? match?.[2] ?? match?.[3];
|
|
726
|
+
if (!userDataDir) return null;
|
|
727
|
+
return normalize(userDataDir).match(/[\\/]profiles[\\/]([^\\/]+)[\\/]userData$/)?.[1] ?? null;
|
|
728
|
+
}
|
|
729
|
+
function isLivePid(pid) {
|
|
730
|
+
try {
|
|
731
|
+
process.kill(pid, 0);
|
|
732
|
+
return true;
|
|
733
|
+
} catch (error) {
|
|
734
|
+
return error instanceof Error && "code" in error && error.code === "EPERM";
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function parsePsOutput(stdout) {
|
|
738
|
+
return stdout.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
|
|
739
|
+
const match = line.match(/^(\d+)\s+(.+)$/);
|
|
740
|
+
if (!match) return [];
|
|
741
|
+
return [{
|
|
742
|
+
pid: Number(match[1]),
|
|
743
|
+
command: match[2]
|
|
744
|
+
}];
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
function parsePowershellProcesses(stdout) {
|
|
748
|
+
try {
|
|
749
|
+
const parsed = JSON.parse(stdout);
|
|
750
|
+
return (Array.isArray(parsed) ? parsed : [parsed]).flatMap((item) => {
|
|
751
|
+
if (typeof item.ProcessId !== "number" || typeof item.CommandLine !== "string") return [];
|
|
752
|
+
return [{
|
|
753
|
+
pid: item.ProcessId,
|
|
754
|
+
command: item.CommandLine
|
|
755
|
+
}];
|
|
756
|
+
});
|
|
757
|
+
} catch {
|
|
758
|
+
return [];
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
async function listYonClawProcesses(currentPlatform = platform()) {
|
|
762
|
+
try {
|
|
763
|
+
if (currentPlatform === "win32") {
|
|
764
|
+
const { stdout } = await execFileAsync("powershell.exe", [
|
|
765
|
+
"-NoProfile",
|
|
766
|
+
"-Command",
|
|
767
|
+
"Get-CimInstance Win32_Process | Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress"
|
|
768
|
+
]);
|
|
769
|
+
return parsePowershellProcesses(stdout);
|
|
770
|
+
}
|
|
771
|
+
const { stdout } = await execFileAsync("ps", ["-axo", "pid=,command="]);
|
|
772
|
+
return parsePsOutput(stdout);
|
|
773
|
+
} catch {
|
|
774
|
+
return [];
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
async function resolveYonClawGlobalSkillsDirs(options = {}) {
|
|
778
|
+
const baseDir = getYonClawBaseDir(options);
|
|
779
|
+
const profileKeys = await listProfileKeys(baseDir);
|
|
780
|
+
if (profileKeys.length === 0) return [];
|
|
781
|
+
const processes = await (options.processProvider ?? { listProcesses: () => listYonClawProcesses(options.platform) }).listProcesses();
|
|
782
|
+
const processByPid = new Map(processes.map((processInfo) => [processInfo.pid, processInfo]));
|
|
783
|
+
const liveGatewayProfiles = [];
|
|
784
|
+
for (const profileKey of profileKeys) {
|
|
785
|
+
const runtime = await readJson(join(baseDir, "profiles", profileKey, "userData", "gateway-runtime.json"));
|
|
786
|
+
if (typeof runtime?.pid !== "number") continue;
|
|
787
|
+
const processInfo = processByPid.get(runtime.pid);
|
|
788
|
+
if (processInfo && GATEWAY_PROCESS_RE.test(processInfo.command) && isLivePid(runtime.pid)) liveGatewayProfiles.push(profileKey);
|
|
789
|
+
}
|
|
790
|
+
if (liveGatewayProfiles.length > 0) return unique(liveGatewayProfiles).map((profileKey) => getYonClawProfileSkillsDir(baseDir, profileKey));
|
|
791
|
+
const userDataProfiles = unique(processes.filter((processInfo) => YONCLAW_PROCESS_RE.test(processInfo.command)).map((processInfo) => parseUserDataProfile(processInfo.command)).filter((profileKey) => Boolean(profileKey)).filter((profileKey) => profileKeys.includes(profileKey)));
|
|
792
|
+
if (userDataProfiles.length > 0) return userDataProfiles.map((profileKey) => getYonClawProfileSkillsDir(baseDir, profileKey));
|
|
793
|
+
if (processes.some((processInfo) => YONCLAW_PROCESS_RE.test(processInfo.command))) {
|
|
794
|
+
const activeProfile = await readJson(join(baseDir, "device", "active-profile.json"));
|
|
795
|
+
if (typeof activeProfile?.profileKey === "string" && profileKeys.includes(activeProfile.profileKey)) return [getYonClawProfileSkillsDir(baseDir, activeProfile.profileKey)];
|
|
796
|
+
}
|
|
797
|
+
return profileKeys.map((profileKey) => getYonClawProfileSkillsDir(baseDir, profileKey));
|
|
798
|
+
}
|
|
686
799
|
const home = homedir();
|
|
687
800
|
const configHome = xdgConfig ?? join(home, ".config");
|
|
688
801
|
const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
|
|
@@ -703,6 +816,17 @@ const agents = {
|
|
|
703
816
|
return existsSync(join(configHome, "amp"));
|
|
704
817
|
}
|
|
705
818
|
},
|
|
819
|
+
yonclaw: {
|
|
820
|
+
name: "yonclaw",
|
|
821
|
+
displayName: "YonClaw",
|
|
822
|
+
skillsDir: "runtime/openclaw/skills",
|
|
823
|
+
globalSkillsDir: getYonClawGlobalSkillsDirHint(),
|
|
824
|
+
resolveGlobalSkillsDirs: resolveYonClawGlobalSkillsDirs,
|
|
825
|
+
supportsProjectInstall: false,
|
|
826
|
+
detectInstalled: async () => {
|
|
827
|
+
return existsSync(getYonClawBaseDir());
|
|
828
|
+
}
|
|
829
|
+
},
|
|
706
830
|
antigravity: {
|
|
707
831
|
name: "antigravity",
|
|
708
832
|
displayName: "Antigravity",
|
|
@@ -1079,6 +1203,10 @@ function getNonUniversalAgents() {
|
|
|
1079
1203
|
function isUniversalAgent(type) {
|
|
1080
1204
|
return agents[type].skillsDir === ".agents/skills";
|
|
1081
1205
|
}
|
|
1206
|
+
function agentSupportsGlobalInstall$1(agentType) {
|
|
1207
|
+
const agent = agents[agentType];
|
|
1208
|
+
return agent.globalSkillsDir !== void 0 || agent.resolveGlobalSkillsDirs !== void 0;
|
|
1209
|
+
}
|
|
1082
1210
|
function sanitizeName(name) {
|
|
1083
1211
|
return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").substring(0, 255) || "unnamed-skill";
|
|
1084
1212
|
}
|
|
@@ -1100,6 +1228,13 @@ function getAgentBaseDir(agentType, global, cwd) {
|
|
|
1100
1228
|
}
|
|
1101
1229
|
return join(baseDir, agent.skillsDir);
|
|
1102
1230
|
}
|
|
1231
|
+
async function getAgentBaseDirs(agentType, global, cwd) {
|
|
1232
|
+
const agent = agents[agentType];
|
|
1233
|
+
if (!global && agent.supportsProjectInstall === false) return [];
|
|
1234
|
+
if (global && agent.resolveGlobalSkillsDirs) return agent.resolveGlobalSkillsDirs();
|
|
1235
|
+
if (global && !agentSupportsGlobalInstall$1(agentType)) return [];
|
|
1236
|
+
return [getAgentBaseDir(agentType, global, cwd)];
|
|
1237
|
+
}
|
|
1103
1238
|
function resolveSymlinkTarget(linkPath, linkTarget) {
|
|
1104
1239
|
return resolve(dirname(linkPath), linkTarget);
|
|
1105
1240
|
}
|
|
@@ -1151,7 +1286,13 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
1151
1286
|
const agent = agents[agentType];
|
|
1152
1287
|
const isGlobal = options.global ?? false;
|
|
1153
1288
|
const cwd = options.cwd || process.cwd();
|
|
1154
|
-
if (isGlobal && agent.
|
|
1289
|
+
if (!isGlobal && agent.supportsProjectInstall === false) return {
|
|
1290
|
+
success: false,
|
|
1291
|
+
path: "",
|
|
1292
|
+
mode: options.mode ?? "symlink",
|
|
1293
|
+
error: `${agent.displayName} only supports global skill installation. Use --global.`
|
|
1294
|
+
};
|
|
1295
|
+
if (isGlobal && !agentSupportsGlobalInstall$1(agentType)) return {
|
|
1155
1296
|
success: false,
|
|
1156
1297
|
path: "",
|
|
1157
1298
|
mode: options.mode ?? "symlink",
|
|
@@ -1160,28 +1301,37 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
1160
1301
|
const skillName = sanitizeName(skill.name || basename(skill.path));
|
|
1161
1302
|
const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
|
|
1162
1303
|
const canonicalDir = join(canonicalBase, skillName);
|
|
1163
|
-
const
|
|
1164
|
-
const
|
|
1304
|
+
const agentBases = await getAgentBaseDirs(agentType, isGlobal, cwd);
|
|
1305
|
+
const agentDirs = agentBases.map((agentBase) => join(agentBase, skillName));
|
|
1306
|
+
const agentDir = agentDirs[0] ?? "";
|
|
1165
1307
|
const installMode = options.mode ?? "symlink";
|
|
1308
|
+
if (agentBases.length === 0) return {
|
|
1309
|
+
success: false,
|
|
1310
|
+
path: "",
|
|
1311
|
+
mode: installMode,
|
|
1312
|
+
error: `No ${agent.displayName} skills directories found`
|
|
1313
|
+
};
|
|
1166
1314
|
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1167
1315
|
success: false,
|
|
1168
1316
|
path: agentDir,
|
|
1169
1317
|
mode: installMode,
|
|
1170
1318
|
error: "Invalid skill name: potential path traversal detected"
|
|
1171
1319
|
};
|
|
1172
|
-
if (!isPathSafe(agentBase,
|
|
1320
|
+
for (const [index, agentBase] of agentBases.entries()) if (!isPathSafe(agentBase, agentDirs[index])) return {
|
|
1173
1321
|
success: false,
|
|
1174
|
-
path:
|
|
1322
|
+
path: agentDirs[index],
|
|
1175
1323
|
mode: installMode,
|
|
1176
1324
|
error: "Invalid skill name: potential path traversal detected"
|
|
1177
1325
|
};
|
|
1178
1326
|
try {
|
|
1179
1327
|
if (installMode === "copy") {
|
|
1180
|
-
await
|
|
1181
|
-
|
|
1328
|
+
await Promise.all(agentDirs.map(async (targetDir) => {
|
|
1329
|
+
await cleanAndCreateDirectory(targetDir);
|
|
1330
|
+
await copyDirectory(skill.path, targetDir);
|
|
1331
|
+
}));
|
|
1182
1332
|
return {
|
|
1183
1333
|
success: true,
|
|
1184
|
-
path:
|
|
1334
|
+
path: agentDirs.join(", "),
|
|
1185
1335
|
mode: "copy"
|
|
1186
1336
|
};
|
|
1187
1337
|
}
|
|
@@ -1193,22 +1343,18 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
1193
1343
|
canonicalPath: canonicalDir,
|
|
1194
1344
|
mode: "symlink"
|
|
1195
1345
|
};
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
path: agentDir,
|
|
1202
|
-
canonicalPath: canonicalDir,
|
|
1203
|
-
mode: "symlink",
|
|
1204
|
-
symlinkFailed: true
|
|
1205
|
-
};
|
|
1346
|
+
let symlinkFailed = false;
|
|
1347
|
+
for (const targetDir of agentDirs) if (!await createSymlink(canonicalDir, targetDir)) {
|
|
1348
|
+
symlinkFailed = true;
|
|
1349
|
+
await cleanAndCreateDirectory(targetDir);
|
|
1350
|
+
await copyDirectory(skill.path, targetDir);
|
|
1206
1351
|
}
|
|
1207
1352
|
return {
|
|
1208
1353
|
success: true,
|
|
1209
|
-
path:
|
|
1354
|
+
path: agentDirs.join(", "),
|
|
1210
1355
|
canonicalPath: canonicalDir,
|
|
1211
|
-
mode: "symlink"
|
|
1356
|
+
mode: "symlink",
|
|
1357
|
+
...symlinkFailed && { symlinkFailed: true }
|
|
1212
1358
|
};
|
|
1213
1359
|
} catch (error) {
|
|
1214
1360
|
return {
|
|
@@ -1243,25 +1389,27 @@ async function copyDirectory(src, dest) {
|
|
|
1243
1389
|
async function isSkillInstalled(skillName, agentType, options = {}) {
|
|
1244
1390
|
const agent = agents[agentType];
|
|
1245
1391
|
const sanitized = sanitizeName(skillName);
|
|
1246
|
-
if (options.global && agent.
|
|
1247
|
-
|
|
1248
|
-
const
|
|
1249
|
-
if (
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1392
|
+
if (!options.global && agent.supportsProjectInstall === false) return false;
|
|
1393
|
+
if (options.global && !agentSupportsGlobalInstall$1(agentType)) return false;
|
|
1394
|
+
const targetBases = await getAgentBaseDirs(agentType, options.global ?? false, options.cwd);
|
|
1395
|
+
if (targetBases.length === 0) return false;
|
|
1396
|
+
for (const targetBase of targetBases) {
|
|
1397
|
+
const skillDir = join(targetBase, sanitized);
|
|
1398
|
+
if (!isPathSafe(targetBase, skillDir)) continue;
|
|
1399
|
+
try {
|
|
1400
|
+
await access(skillDir);
|
|
1401
|
+
return true;
|
|
1402
|
+
} catch {}
|
|
1255
1403
|
}
|
|
1404
|
+
return false;
|
|
1256
1405
|
}
|
|
1257
|
-
function
|
|
1258
|
-
agents[agentType];
|
|
1259
|
-
options.cwd || process.cwd();
|
|
1406
|
+
async function getInstallPaths(skillName, agentType, options = {}) {
|
|
1260
1407
|
const sanitized = sanitizeName(skillName);
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1408
|
+
return (await getAgentBaseDirs(agentType, options.global ?? false, options.cwd)).map((targetBase) => {
|
|
1409
|
+
const installPath = join(targetBase, sanitized);
|
|
1410
|
+
if (!isPathSafe(targetBase, installPath)) throw new Error("Invalid skill name: potential path traversal detected");
|
|
1411
|
+
return installPath;
|
|
1412
|
+
});
|
|
1265
1413
|
}
|
|
1266
1414
|
function getCanonicalPath(skillName, options = {}) {
|
|
1267
1415
|
const sanitized = sanitizeName(skillName);
|
|
@@ -1275,7 +1423,13 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1275
1423
|
const isGlobal = options.global ?? false;
|
|
1276
1424
|
const cwd = options.cwd || process.cwd();
|
|
1277
1425
|
const installMode = options.mode ?? "symlink";
|
|
1278
|
-
if (isGlobal && agent.
|
|
1426
|
+
if (!isGlobal && agent.supportsProjectInstall === false) return {
|
|
1427
|
+
success: false,
|
|
1428
|
+
path: "",
|
|
1429
|
+
mode: installMode,
|
|
1430
|
+
error: `${agent.displayName} only supports global skill installation. Use --global.`
|
|
1431
|
+
};
|
|
1432
|
+
if (isGlobal && !agentSupportsGlobalInstall$1(agentType)) return {
|
|
1279
1433
|
success: false,
|
|
1280
1434
|
path: "",
|
|
1281
1435
|
mode: installMode,
|
|
@@ -1284,17 +1438,24 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1284
1438
|
const skillName = sanitizeName(skill.installName);
|
|
1285
1439
|
const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
|
|
1286
1440
|
const canonicalDir = join(canonicalBase, skillName);
|
|
1287
|
-
const
|
|
1288
|
-
const
|
|
1441
|
+
const agentBases = await getAgentBaseDirs(agentType, isGlobal, cwd);
|
|
1442
|
+
const agentDirs = agentBases.map((agentBase) => join(agentBase, skillName));
|
|
1443
|
+
const agentDir = agentDirs[0] ?? "";
|
|
1444
|
+
if (agentBases.length === 0) return {
|
|
1445
|
+
success: false,
|
|
1446
|
+
path: "",
|
|
1447
|
+
mode: installMode,
|
|
1448
|
+
error: `No ${agent.displayName} skills directories found`
|
|
1449
|
+
};
|
|
1289
1450
|
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1290
1451
|
success: false,
|
|
1291
1452
|
path: agentDir,
|
|
1292
1453
|
mode: installMode,
|
|
1293
1454
|
error: "Invalid skill name: potential path traversal detected"
|
|
1294
1455
|
};
|
|
1295
|
-
if (!isPathSafe(agentBase,
|
|
1456
|
+
for (const [index, agentBase] of agentBases.entries()) if (!isPathSafe(agentBase, agentDirs[index])) return {
|
|
1296
1457
|
success: false,
|
|
1297
|
-
path:
|
|
1458
|
+
path: agentDirs[index],
|
|
1298
1459
|
mode: installMode,
|
|
1299
1460
|
error: "Invalid skill name: potential path traversal detected"
|
|
1300
1461
|
};
|
|
@@ -1309,11 +1470,13 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1309
1470
|
}
|
|
1310
1471
|
try {
|
|
1311
1472
|
if (installMode === "copy") {
|
|
1312
|
-
await
|
|
1313
|
-
|
|
1473
|
+
await Promise.all(agentDirs.map(async (targetDir) => {
|
|
1474
|
+
await cleanAndCreateDirectory(targetDir);
|
|
1475
|
+
await writeSkillFiles(targetDir);
|
|
1476
|
+
}));
|
|
1314
1477
|
return {
|
|
1315
1478
|
success: true,
|
|
1316
|
-
path:
|
|
1479
|
+
path: agentDirs.join(", "),
|
|
1317
1480
|
mode: "copy"
|
|
1318
1481
|
};
|
|
1319
1482
|
}
|
|
@@ -1325,22 +1488,18 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1325
1488
|
canonicalPath: canonicalDir,
|
|
1326
1489
|
mode: "symlink"
|
|
1327
1490
|
};
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
path: agentDir,
|
|
1334
|
-
canonicalPath: canonicalDir,
|
|
1335
|
-
mode: "symlink",
|
|
1336
|
-
symlinkFailed: true
|
|
1337
|
-
};
|
|
1491
|
+
let symlinkFailed = false;
|
|
1492
|
+
for (const targetDir of agentDirs) if (!await createSymlink(canonicalDir, targetDir)) {
|
|
1493
|
+
symlinkFailed = true;
|
|
1494
|
+
await cleanAndCreateDirectory(targetDir);
|
|
1495
|
+
await writeSkillFiles(targetDir);
|
|
1338
1496
|
}
|
|
1339
1497
|
return {
|
|
1340
1498
|
success: true,
|
|
1341
|
-
path:
|
|
1499
|
+
path: agentDirs.join(", "),
|
|
1342
1500
|
canonicalPath: canonicalDir,
|
|
1343
|
-
mode: "symlink"
|
|
1501
|
+
mode: "symlink",
|
|
1502
|
+
...symlinkFailed && { symlinkFailed: true }
|
|
1344
1503
|
};
|
|
1345
1504
|
} catch (error) {
|
|
1346
1505
|
return {
|
|
@@ -1367,10 +1526,10 @@ async function listInstalledSkills(options = {}) {
|
|
|
1367
1526
|
path: getCanonicalSkillsDir(isGlobal, cwd)
|
|
1368
1527
|
});
|
|
1369
1528
|
for (const agentType of agentsToCheck) {
|
|
1370
|
-
|
|
1371
|
-
if (isGlobal &&
|
|
1372
|
-
const
|
|
1373
|
-
if (!scopes.some((s) => s.path === agentDir && s.global === isGlobal)) scopes.push({
|
|
1529
|
+
agents[agentType];
|
|
1530
|
+
if (isGlobal && !agentSupportsGlobalInstall$1(agentType)) continue;
|
|
1531
|
+
const agentDirs = await getAgentBaseDirs(agentType, isGlobal, cwd);
|
|
1532
|
+
for (const agentDir of agentDirs) if (!scopes.some((s) => s.path === agentDir && s.global === isGlobal)) scopes.push({
|
|
1374
1533
|
global: isGlobal,
|
|
1375
1534
|
path: agentDir,
|
|
1376
1535
|
agentType
|
|
@@ -1409,41 +1568,47 @@ async function listInstalledSkills(options = {}) {
|
|
|
1409
1568
|
const sanitizedSkillName = sanitizeName(skill.name);
|
|
1410
1569
|
const installedAgents = [];
|
|
1411
1570
|
for (const agentType of agentsToCheck) {
|
|
1412
|
-
|
|
1413
|
-
if (scope.global &&
|
|
1414
|
-
const agentBase = scope.global ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
|
|
1571
|
+
agents[agentType];
|
|
1572
|
+
if (scope.global && !agentSupportsGlobalInstall$1(agentType)) continue;
|
|
1415
1573
|
let found = false;
|
|
1574
|
+
const agentBases = await getAgentBaseDirs(agentType, scope.global, cwd);
|
|
1416
1575
|
const possibleNames = Array.from(new Set([
|
|
1417
1576
|
entry.name,
|
|
1418
1577
|
sanitizedSkillName,
|
|
1419
1578
|
skill.name.toLowerCase().replace(/\s+/g, "-").replace(/[\/\\:\0]/g, "")
|
|
1420
1579
|
]));
|
|
1421
|
-
for (const
|
|
1422
|
-
const
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
await access(agentSkillDir);
|
|
1426
|
-
found = true;
|
|
1427
|
-
break;
|
|
1428
|
-
} catch {}
|
|
1429
|
-
}
|
|
1430
|
-
if (!found) try {
|
|
1431
|
-
const agentEntries = await readdir(agentBase, { withFileTypes: true });
|
|
1432
|
-
for (const agentEntry of agentEntries) {
|
|
1433
|
-
if (!agentEntry.isDirectory()) continue;
|
|
1434
|
-
const candidateDir = join(agentBase, agentEntry.name);
|
|
1435
|
-
if (!isPathSafe(agentBase, candidateDir)) continue;
|
|
1580
|
+
for (const agentBase of agentBases) {
|
|
1581
|
+
for (const possibleName of possibleNames) {
|
|
1582
|
+
const agentSkillDir = join(agentBase, possibleName);
|
|
1583
|
+
if (!isPathSafe(agentBase, agentSkillDir)) continue;
|
|
1436
1584
|
try {
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
if (candidateSkill && candidateSkill.name === skill.name) {
|
|
1441
|
-
found = true;
|
|
1442
|
-
break;
|
|
1443
|
-
}
|
|
1585
|
+
await access(agentSkillDir);
|
|
1586
|
+
found = true;
|
|
1587
|
+
break;
|
|
1444
1588
|
} catch {}
|
|
1445
1589
|
}
|
|
1446
|
-
|
|
1590
|
+
if (found) break;
|
|
1591
|
+
}
|
|
1592
|
+
if (!found) for (const agentBase of agentBases) {
|
|
1593
|
+
try {
|
|
1594
|
+
const agentEntries = await readdir(agentBase, { withFileTypes: true });
|
|
1595
|
+
for (const agentEntry of agentEntries) {
|
|
1596
|
+
if (!agentEntry.isDirectory()) continue;
|
|
1597
|
+
const candidateDir = join(agentBase, agentEntry.name);
|
|
1598
|
+
if (!isPathSafe(agentBase, candidateDir)) continue;
|
|
1599
|
+
try {
|
|
1600
|
+
const candidateSkillMd = join(candidateDir, "SKILL.md");
|
|
1601
|
+
await stat(candidateSkillMd);
|
|
1602
|
+
const candidateSkill = await parseSkillMd(candidateSkillMd);
|
|
1603
|
+
if (candidateSkill && candidateSkill.name === skill.name) {
|
|
1604
|
+
found = true;
|
|
1605
|
+
break;
|
|
1606
|
+
}
|
|
1607
|
+
} catch {}
|
|
1608
|
+
}
|
|
1609
|
+
} catch {}
|
|
1610
|
+
if (found) break;
|
|
1611
|
+
}
|
|
1447
1612
|
if (found) installedAgents.push(agentType);
|
|
1448
1613
|
}
|
|
1449
1614
|
if (skillsMap.has(skillKey)) {
|
|
@@ -2075,7 +2240,7 @@ function createEmptyLocalLock() {
|
|
|
2075
2240
|
skills: {}
|
|
2076
2241
|
};
|
|
2077
2242
|
}
|
|
2078
|
-
var version$1 = "1.4.
|
|
2243
|
+
var version$1 = "1.4.13";
|
|
2079
2244
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
2080
2245
|
async function isSourcePrivate(source) {
|
|
2081
2246
|
const ownerRepo = parseOwnerRepo(source);
|
|
@@ -2213,6 +2378,47 @@ function ensureUniversalAgents(targetAgents) {
|
|
|
2213
2378
|
for (const ua of universalAgents) if (!result.includes(ua)) result.push(ua);
|
|
2214
2379
|
return result;
|
|
2215
2380
|
}
|
|
2381
|
+
function agentSupportsGlobalInstall(agent) {
|
|
2382
|
+
return agents[agent].globalSkillsDir !== void 0 || agents[agent].resolveGlobalSkillsDirs !== void 0;
|
|
2383
|
+
}
|
|
2384
|
+
function agentSupportsScope(agent, global) {
|
|
2385
|
+
if (global === true) return agentSupportsGlobalInstall(agent);
|
|
2386
|
+
if (global === false) return agents[agent].supportsProjectInstall !== false;
|
|
2387
|
+
return true;
|
|
2388
|
+
}
|
|
2389
|
+
function resolveAgentScopes(targetAgents, installGlobally, explicitlyRequestedAgents) {
|
|
2390
|
+
if (installGlobally) return {
|
|
2391
|
+
targetAgents,
|
|
2392
|
+
autoGlobalAgents: []
|
|
2393
|
+
};
|
|
2394
|
+
const unsupported = targetAgents.filter((agent) => agents[agent].supportsProjectInstall === false);
|
|
2395
|
+
if (unsupported.length === 0) return {
|
|
2396
|
+
targetAgents,
|
|
2397
|
+
autoGlobalAgents: []
|
|
2398
|
+
};
|
|
2399
|
+
if (explicitlyRequestedAgents?.includes("*")) {
|
|
2400
|
+
const filtered = targetAgents.filter((agent) => agents[agent].supportsProjectInstall !== false);
|
|
2401
|
+
if (filtered.length === 0) return {
|
|
2402
|
+
targetAgents: unsupported,
|
|
2403
|
+
autoGlobalAgents: unsupported
|
|
2404
|
+
};
|
|
2405
|
+
return {
|
|
2406
|
+
targetAgents: filtered,
|
|
2407
|
+
autoGlobalAgents: []
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
return {
|
|
2411
|
+
targetAgents,
|
|
2412
|
+
autoGlobalAgents: unsupported
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
function shouldInstallAgentGlobally(agent, installGlobally, autoGlobalAgents) {
|
|
2416
|
+
return installGlobally || autoGlobalAgents.includes(agent);
|
|
2417
|
+
}
|
|
2418
|
+
function warnAutoGlobalAgents(autoGlobalAgents) {
|
|
2419
|
+
if (autoGlobalAgents.length === 0) return;
|
|
2420
|
+
M.warn(`${formatList$1(autoGlobalAgents.map((agent) => agents[agent].displayName))} only supports global skill installation and was installed globally.`);
|
|
2421
|
+
}
|
|
2216
2422
|
function buildResultLines(results, targetAgents) {
|
|
2217
2423
|
const lines = [];
|
|
2218
2424
|
const { universal, symlinked: symlinkAgents } = splitAgentsByType(targetAgents);
|
|
@@ -2256,9 +2462,9 @@ async function promptForAgents(message, choices) {
|
|
|
2256
2462
|
return selected;
|
|
2257
2463
|
}
|
|
2258
2464
|
async function selectAgentsInteractive(options) {
|
|
2259
|
-
const
|
|
2260
|
-
const universalAgents = getUniversalAgents().filter(
|
|
2261
|
-
const otherAgents = getNonUniversalAgents().filter(
|
|
2465
|
+
const supportsScopeFilter = (a) => agentSupportsScope(a, options.global);
|
|
2466
|
+
const universalAgents = getUniversalAgents().filter(supportsScopeFilter);
|
|
2467
|
+
const otherAgents = getNonUniversalAgents().filter(supportsScopeFilter);
|
|
2262
2468
|
const universalSection = {
|
|
2263
2469
|
title: "Universal (.agents/skills)",
|
|
2264
2470
|
items: universalAgents.map((a) => ({
|
|
@@ -2449,7 +2655,7 @@ async function handleWellKnownSkills(source, url, options, spinner, allowFallbac
|
|
|
2449
2655
|
}
|
|
2450
2656
|
}
|
|
2451
2657
|
let installGlobally = options.global ?? false;
|
|
2452
|
-
const supportsGlobal = targetAgents.some(
|
|
2658
|
+
const supportsGlobal = targetAgents.some(agentSupportsGlobalInstall);
|
|
2453
2659
|
if (options.global === void 0 && !options.yes && supportsGlobal) {
|
|
2454
2660
|
const scope = await ve({
|
|
2455
2661
|
message: "Installation scope",
|
|
@@ -2469,6 +2675,9 @@ async function handleWellKnownSkills(source, url, options, spinner, allowFallbac
|
|
|
2469
2675
|
}
|
|
2470
2676
|
installGlobally = scope;
|
|
2471
2677
|
}
|
|
2678
|
+
const scopeResolution = resolveAgentScopes(targetAgents, installGlobally, options.agent);
|
|
2679
|
+
targetAgents = scopeResolution.targetAgents;
|
|
2680
|
+
const autoGlobalAgents = scopeResolution.autoGlobalAgents;
|
|
2472
2681
|
let installMode = options.copy ? "copy" : "symlink";
|
|
2473
2682
|
if (!options.copy && !options.yes) {
|
|
2474
2683
|
const modeChoice = await ve({
|
|
@@ -2495,7 +2704,7 @@ async function handleWellKnownSkills(source, url, options, spinner, allowFallbac
|
|
|
2495
2704
|
const overwriteChecks = await Promise.all(selectedSkills.flatMap((skill) => targetAgents.map(async (agent) => ({
|
|
2496
2705
|
skillName: skill.installName,
|
|
2497
2706
|
agent,
|
|
2498
|
-
installed: await isSkillInstalled(skill.installName, agent, { global: installGlobally })
|
|
2707
|
+
installed: await isSkillInstalled(skill.installName, agent, { global: shouldInstallAgentGlobally(agent, installGlobally, autoGlobalAgents) })
|
|
2499
2708
|
}))));
|
|
2500
2709
|
const overwriteStatus = /* @__PURE__ */ new Map();
|
|
2501
2710
|
for (const { skillName, agent, installed } of overwriteChecks) {
|
|
@@ -2504,7 +2713,7 @@ async function handleWellKnownSkills(source, url, options, spinner, allowFallbac
|
|
|
2504
2713
|
}
|
|
2505
2714
|
for (const skill of selectedSkills) {
|
|
2506
2715
|
if (summaryLines.length > 0) summaryLines.push("");
|
|
2507
|
-
const shortCanonical = shortenPath$2(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
|
|
2716
|
+
const shortCanonical = shortenPath$2(getCanonicalPath(skill.installName, { global: installGlobally || targetAgents.every((agent) => autoGlobalAgents.includes(agent)) }), cwd);
|
|
2508
2717
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
2509
2718
|
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
|
|
2510
2719
|
const fileCount = getWellKnownFileCount(skill);
|
|
@@ -2557,11 +2766,11 @@ async function handleWellKnownSkills(source, url, options, spinner, allowFallbac
|
|
|
2557
2766
|
error: "Repository-backed skill was not prepared"
|
|
2558
2767
|
};
|
|
2559
2768
|
else installOutcome = await installSkillForAgent(preparedSkill, agent, {
|
|
2560
|
-
global: installGlobally,
|
|
2769
|
+
global: shouldInstallAgentGlobally(agent, installGlobally, autoGlobalAgents),
|
|
2561
2770
|
mode: installMode
|
|
2562
2771
|
});
|
|
2563
2772
|
} else installOutcome = await installWellKnownSkillForAgent(skill, agent, {
|
|
2564
|
-
global: installGlobally,
|
|
2773
|
+
global: shouldInstallAgentGlobally(agent, installGlobally, autoGlobalAgents),
|
|
2565
2774
|
mode: installMode
|
|
2566
2775
|
});
|
|
2567
2776
|
results.push({
|
|
@@ -2572,9 +2781,11 @@ async function handleWellKnownSkills(source, url, options, spinner, allowFallbac
|
|
|
2572
2781
|
}
|
|
2573
2782
|
await Promise.all(repositoryTempDirs.map((dir) => cleanupTempDir(dir).catch(() => {})));
|
|
2574
2783
|
spinner.stop("Installation complete");
|
|
2784
|
+
warnAutoGlobalAgents(autoGlobalAgents);
|
|
2575
2785
|
console.log();
|
|
2576
2786
|
const successful = results.filter((r) => r.success);
|
|
2577
2787
|
const failed = results.filter((r) => !r.success);
|
|
2788
|
+
const hasAnyGlobalInstall = installGlobally || autoGlobalAgents.length > 0;
|
|
2578
2789
|
const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
|
|
2579
2790
|
const skillFiles = {};
|
|
2580
2791
|
for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
|
|
@@ -2583,7 +2794,7 @@ async function handleWellKnownSkills(source, url, options, spinner, allowFallbac
|
|
|
2583
2794
|
source: sourceIdentifier,
|
|
2584
2795
|
skills: selectedSkills.map((s) => s.installName).join(","),
|
|
2585
2796
|
agents: targetAgents.join(","),
|
|
2586
|
-
...
|
|
2797
|
+
...hasAnyGlobalInstall && { global: "1" },
|
|
2587
2798
|
skillFiles: JSON.stringify(skillFiles),
|
|
2588
2799
|
sourceType: "well-known"
|
|
2589
2800
|
});
|
|
@@ -2592,14 +2803,14 @@ async function handleWellKnownSkills(source, url, options, spinner, allowFallbac
|
|
|
2592
2803
|
sourceUrl: url,
|
|
2593
2804
|
skills: selectedSkills.map((s) => s.installName),
|
|
2594
2805
|
agents: targetAgents,
|
|
2595
|
-
installGlobally,
|
|
2806
|
+
installGlobally: hasAnyGlobalInstall,
|
|
2596
2807
|
skillFiles,
|
|
2597
2808
|
sourceType: "well-known",
|
|
2598
2809
|
successfulCount: successful.length,
|
|
2599
2810
|
failedCount: failed.length,
|
|
2600
2811
|
failedDetails: buildFailedDetails(results)
|
|
2601
2812
|
});
|
|
2602
|
-
if (successful.length > 0 &&
|
|
2813
|
+
if (successful.length > 0 && hasAnyGlobalInstall) {
|
|
2603
2814
|
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
2604
2815
|
for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
|
|
2605
2816
|
await addSkillToLock(skill.installName, {
|
|
@@ -2613,18 +2824,21 @@ async function handleWellKnownSkills(source, url, options, spinner, allowFallbac
|
|
|
2613
2824
|
}
|
|
2614
2825
|
if (successful.length > 0 && !installGlobally) {
|
|
2615
2826
|
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
2616
|
-
for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName))
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
const
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2827
|
+
for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) {
|
|
2828
|
+
if (!successful.some((r) => r.skill === skill.installName && targetAgents.some((agent) => agents[agent].displayName === r.agent && !shouldInstallAgentGlobally(agent, installGlobally, autoGlobalAgents)))) continue;
|
|
2829
|
+
try {
|
|
2830
|
+
const matchingResult = successful.find((r) => r.skill === skill.installName);
|
|
2831
|
+
const installDir = matchingResult?.canonicalPath || matchingResult?.path;
|
|
2832
|
+
if (installDir) {
|
|
2833
|
+
const computedHash = await computeSkillFolderHash(installDir);
|
|
2834
|
+
await addSkillToLocalLock(skill.installName, {
|
|
2835
|
+
source: sourceIdentifier,
|
|
2836
|
+
sourceType: "well-known",
|
|
2837
|
+
computedHash
|
|
2838
|
+
}, cwd);
|
|
2839
|
+
}
|
|
2840
|
+
} catch {}
|
|
2841
|
+
}
|
|
2628
2842
|
}
|
|
2629
2843
|
if (successful.length > 0) {
|
|
2630
2844
|
const bySkill = /* @__PURE__ */ new Map();
|
|
@@ -2908,7 +3122,7 @@ async function runAdd(args, options = {}) {
|
|
|
2908
3122
|
}
|
|
2909
3123
|
}
|
|
2910
3124
|
let installGlobally = options.global ?? false;
|
|
2911
|
-
const supportsGlobal = targetAgents.some(
|
|
3125
|
+
const supportsGlobal = targetAgents.some(agentSupportsGlobalInstall);
|
|
2912
3126
|
if (options.global === void 0 && !options.yes && supportsGlobal) {
|
|
2913
3127
|
const scope = await ve({
|
|
2914
3128
|
message: "Installation scope",
|
|
@@ -2929,6 +3143,9 @@ async function runAdd(args, options = {}) {
|
|
|
2929
3143
|
}
|
|
2930
3144
|
installGlobally = scope;
|
|
2931
3145
|
}
|
|
3146
|
+
const scopeResolution = resolveAgentScopes(targetAgents, installGlobally, options.agent);
|
|
3147
|
+
targetAgents = scopeResolution.targetAgents;
|
|
3148
|
+
const autoGlobalAgents = scopeResolution.autoGlobalAgents;
|
|
2932
3149
|
let installMode = options.copy ? "copy" : "symlink";
|
|
2933
3150
|
if (!options.copy && !options.yes) {
|
|
2934
3151
|
const modeChoice = await ve({
|
|
@@ -2956,7 +3173,7 @@ async function runAdd(args, options = {}) {
|
|
|
2956
3173
|
const overwriteChecks = await Promise.all(selectedSkills.flatMap((skill) => targetAgents.map(async (agent) => ({
|
|
2957
3174
|
skillName: skill.name,
|
|
2958
3175
|
agent,
|
|
2959
|
-
installed: await isSkillInstalled(skill.name, agent, { global: installGlobally })
|
|
3176
|
+
installed: await isSkillInstalled(skill.name, agent, { global: shouldInstallAgentGlobally(agent, installGlobally, autoGlobalAgents) })
|
|
2960
3177
|
}))));
|
|
2961
3178
|
const overwriteStatus = /* @__PURE__ */ new Map();
|
|
2962
3179
|
for (const { skillName, agent, installed } of overwriteChecks) {
|
|
@@ -2973,7 +3190,7 @@ async function runAdd(args, options = {}) {
|
|
|
2973
3190
|
const printSkillSummary = (skills) => {
|
|
2974
3191
|
for (const skill of skills) {
|
|
2975
3192
|
if (summaryLines.length > 0) summaryLines.push("");
|
|
2976
|
-
const shortCanonical = shortenPath$2(getCanonicalPath(skill.name, { global: installGlobally }), cwd);
|
|
3193
|
+
const shortCanonical = shortenPath$2(getCanonicalPath(skill.name, { global: installGlobally || targetAgents.every((agent) => autoGlobalAgents.includes(agent)) }), cwd);
|
|
2977
3194
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
2978
3195
|
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
|
|
2979
3196
|
const skillOverwrites = overwriteStatus.get(skill.name);
|
|
@@ -3019,7 +3236,7 @@ async function runAdd(args, options = {}) {
|
|
|
3019
3236
|
const results = [];
|
|
3020
3237
|
for (const skill of selectedSkills) for (const agent of targetAgents) {
|
|
3021
3238
|
const result = await installSkillForAgent(skill, agent, {
|
|
3022
|
-
global: installGlobally,
|
|
3239
|
+
global: shouldInstallAgentGlobally(agent, installGlobally, autoGlobalAgents),
|
|
3023
3240
|
mode: installMode
|
|
3024
3241
|
});
|
|
3025
3242
|
results.push({
|
|
@@ -3030,9 +3247,11 @@ async function runAdd(args, options = {}) {
|
|
|
3030
3247
|
});
|
|
3031
3248
|
}
|
|
3032
3249
|
spinner.stop("Installation complete");
|
|
3250
|
+
warnAutoGlobalAgents(autoGlobalAgents);
|
|
3033
3251
|
console.log();
|
|
3034
3252
|
const successful = results.filter((r) => r.success);
|
|
3035
3253
|
const failed = results.filter((r) => !r.success);
|
|
3254
|
+
const hasAnyGlobalInstall = installGlobally || autoGlobalAgents.length > 0;
|
|
3036
3255
|
const skillFiles = {};
|
|
3037
3256
|
for (const skill of selectedSkills) {
|
|
3038
3257
|
let relativePath;
|
|
@@ -3047,7 +3266,7 @@ async function runAdd(args, options = {}) {
|
|
|
3047
3266
|
sourceUrl: parsed.url,
|
|
3048
3267
|
skills: selectedSkills.map((s) => s.name),
|
|
3049
3268
|
agents: targetAgents,
|
|
3050
|
-
installGlobally,
|
|
3269
|
+
installGlobally: hasAnyGlobalInstall,
|
|
3051
3270
|
sourceType: parsed.type,
|
|
3052
3271
|
skillFiles
|
|
3053
3272
|
};
|
|
@@ -3059,7 +3278,7 @@ async function runAdd(args, options = {}) {
|
|
|
3059
3278
|
source: normalizedSource,
|
|
3060
3279
|
skills: selectedSkills.map((s) => s.name).join(","),
|
|
3061
3280
|
agents: targetAgents.join(","),
|
|
3062
|
-
...
|
|
3281
|
+
...hasAnyGlobalInstall && { global: "1" },
|
|
3063
3282
|
skillFiles: JSON.stringify(skillFiles)
|
|
3064
3283
|
});
|
|
3065
3284
|
} else track({
|
|
@@ -3067,7 +3286,7 @@ async function runAdd(args, options = {}) {
|
|
|
3067
3286
|
source: normalizedSource,
|
|
3068
3287
|
skills: selectedSkills.map((s) => s.name).join(","),
|
|
3069
3288
|
agents: targetAgents.join(","),
|
|
3070
|
-
...
|
|
3289
|
+
...hasAnyGlobalInstall && { global: "1" },
|
|
3071
3290
|
skillFiles: JSON.stringify(skillFiles)
|
|
3072
3291
|
});
|
|
3073
3292
|
}
|
|
@@ -3083,7 +3302,7 @@ async function runAdd(args, options = {}) {
|
|
|
3083
3302
|
failedCount: failed.length,
|
|
3084
3303
|
failedDetails: buildFailedDetails(results)
|
|
3085
3304
|
});
|
|
3086
|
-
if (successful.length > 0 && installGlobally && normalizedSource) {
|
|
3305
|
+
if (successful.length > 0 && (installGlobally || autoGlobalAgents.length > 0) && normalizedSource) {
|
|
3087
3306
|
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
3088
3307
|
for (const skill of selectedSkills) {
|
|
3089
3308
|
const skillDisplayName = getSkillDisplayName(skill);
|
|
@@ -3109,14 +3328,17 @@ async function runAdd(args, options = {}) {
|
|
|
3109
3328
|
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
3110
3329
|
for (const skill of selectedSkills) {
|
|
3111
3330
|
const skillDisplayName = getSkillDisplayName(skill);
|
|
3112
|
-
if (successfulSkillNames.has(skillDisplayName))
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3331
|
+
if (successfulSkillNames.has(skillDisplayName)) {
|
|
3332
|
+
if (!successful.some((r) => r.skill === skillDisplayName && targetAgents.some((agent) => agents[agent].displayName === r.agent && !shouldInstallAgentGlobally(agent, installGlobally, autoGlobalAgents)))) continue;
|
|
3333
|
+
try {
|
|
3334
|
+
const computedHash = await computeSkillFolderHash(skill.path);
|
|
3335
|
+
await addSkillToLocalLock(skill.name, {
|
|
3336
|
+
source: normalizedSource || parsed.url,
|
|
3337
|
+
sourceType: parsed.type,
|
|
3338
|
+
computedHash
|
|
3339
|
+
}, cwd);
|
|
3340
|
+
} catch {}
|
|
3341
|
+
}
|
|
3120
3342
|
}
|
|
3121
3343
|
}
|
|
3122
3344
|
if (successful.length > 0) {
|
|
@@ -3284,6 +3506,12 @@ function parseAddOptions(args) {
|
|
|
3284
3506
|
i--;
|
|
3285
3507
|
} else if (arg === "--full-depth") options.fullDepth = true;
|
|
3286
3508
|
else if (arg === "--copy") options.copy = true;
|
|
3509
|
+
else if (arg && /^-[ygld]+$/.test(arg)) for (const flag of arg.slice(1)) {
|
|
3510
|
+
if (flag === "y") options.yes = true;
|
|
3511
|
+
if (flag === "g") options.global = true;
|
|
3512
|
+
if (flag === "l") options.list = true;
|
|
3513
|
+
if (flag === "d") options.fullDepth = true;
|
|
3514
|
+
}
|
|
3287
3515
|
else if (arg && !arg.startsWith("-")) source.push(arg);
|
|
3288
3516
|
}
|
|
3289
3517
|
return {
|
|
@@ -3619,9 +3847,10 @@ async function runSync(args, options = {}) {
|
|
|
3619
3847
|
M.info(`${toInstall.length} skill${toInstall.length !== 1 ? "s" : ""} to install/update`);
|
|
3620
3848
|
let targetAgents;
|
|
3621
3849
|
const validAgents = Object.keys(agents);
|
|
3850
|
+
const projectAgents = validAgents.filter((agent) => agents[agent].supportsProjectInstall !== false);
|
|
3622
3851
|
const universalAgents = getUniversalAgents();
|
|
3623
3852
|
if (options.agent?.includes("*")) {
|
|
3624
|
-
targetAgents =
|
|
3853
|
+
targetAgents = projectAgents;
|
|
3625
3854
|
M.info(`Installing to all ${targetAgents.length} agents`);
|
|
3626
3855
|
} else if (options.agent && options.agent.length > 0) {
|
|
3627
3856
|
const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
|
|
@@ -3630,7 +3859,11 @@ async function runSync(args, options = {}) {
|
|
|
3630
3859
|
M.info(`Valid agents: ${validAgents.join(", ")}`);
|
|
3631
3860
|
process.exit(1);
|
|
3632
3861
|
}
|
|
3633
|
-
targetAgents = options.agent
|
|
3862
|
+
targetAgents = options.agent.filter((agent) => {
|
|
3863
|
+
if (agents[agent].supportsProjectInstall !== false) return true;
|
|
3864
|
+
M.error(`${agents[agent].displayName} only supports global skill installation.`);
|
|
3865
|
+
process.exit(1);
|
|
3866
|
+
});
|
|
3634
3867
|
} else {
|
|
3635
3868
|
spinner.start("Loading agents...");
|
|
3636
3869
|
const installedAgents = await detectInstalledAgents();
|
|
@@ -3642,7 +3875,7 @@ async function runSync(args, options = {}) {
|
|
|
3642
3875
|
} else {
|
|
3643
3876
|
const selected = await searchMultiselect({
|
|
3644
3877
|
message: "Which agents do you want to install to?",
|
|
3645
|
-
items: getNonUniversalAgents().map((a) => ({
|
|
3878
|
+
items: getNonUniversalAgents().filter((agent) => agents[agent].supportsProjectInstall !== false).map((a) => ({
|
|
3646
3879
|
value: a,
|
|
3647
3880
|
label: agents[a].displayName,
|
|
3648
3881
|
hint: agents[a].skillsDir
|
|
@@ -3663,12 +3896,12 @@ async function runSync(args, options = {}) {
|
|
|
3663
3896
|
targetAgents = selected;
|
|
3664
3897
|
}
|
|
3665
3898
|
else if (installedAgents.length === 1 || options.yes) {
|
|
3666
|
-
targetAgents = [
|
|
3899
|
+
targetAgents = installedAgents.filter((agent) => agents[agent].supportsProjectInstall !== false);
|
|
3667
3900
|
for (const ua of universalAgents) if (!targetAgents.includes(ua)) targetAgents.push(ua);
|
|
3668
3901
|
} else {
|
|
3669
3902
|
const selected = await searchMultiselect({
|
|
3670
3903
|
message: "Which agents do you want to install to?",
|
|
3671
|
-
items: getNonUniversalAgents().filter((a) => installedAgents.includes(a)).map((a) => ({
|
|
3904
|
+
items: getNonUniversalAgents().filter((a) => installedAgents.includes(a) && agents[a].supportsProjectInstall !== false).map((a) => ({
|
|
3672
3905
|
value: a,
|
|
3673
3906
|
label: agents[a].displayName,
|
|
3674
3907
|
hint: agents[a].skillsDir
|
|
@@ -3949,10 +4182,13 @@ async function removeCommand(skillNames, options) {
|
|
|
3949
4182
|
};
|
|
3950
4183
|
if (isGlobal) {
|
|
3951
4184
|
await scanDir(getCanonicalSkillsDir(true, cwd));
|
|
3952
|
-
for (const
|
|
4185
|
+
for (const agentType of Object.keys(agents)) {
|
|
4186
|
+
const agent = agents[agentType];
|
|
4187
|
+
if (agent.globalSkillsDir !== void 0 || agent.resolveGlobalSkillsDirs !== void 0) for (const agentDir of await getAgentBaseDirs(agentType, true, cwd)) await scanDir(agentDir);
|
|
4188
|
+
}
|
|
3953
4189
|
} else {
|
|
3954
4190
|
await scanDir(getCanonicalSkillsDir(false, cwd));
|
|
3955
|
-
for (const agent of Object.values(agents)) await scanDir(join(cwd, agent.skillsDir));
|
|
4191
|
+
for (const agent of Object.values(agents).filter((agent) => agent.supportsProjectInstall !== false)) await scanDir(join(cwd, agent.skillsDir));
|
|
3956
4192
|
}
|
|
3957
4193
|
const installedSkills = Array.from(skillNamesSet).sort();
|
|
3958
4194
|
spinner.stop(`Found ${installedSkills.length} unique installed skill(s)`);
|
|
@@ -4019,14 +4255,16 @@ async function removeCommand(skillNames, options) {
|
|
|
4019
4255
|
});
|
|
4020
4256
|
for (const agentKey of targetAgents) {
|
|
4021
4257
|
const agent = agents[agentKey];
|
|
4022
|
-
const
|
|
4258
|
+
const skillPaths = await getInstallPaths(skillName, agentKey, {
|
|
4023
4259
|
global: isGlobal,
|
|
4024
4260
|
cwd
|
|
4025
4261
|
});
|
|
4026
|
-
const pathsToCleanup = new Set(
|
|
4262
|
+
const pathsToCleanup = new Set(skillPaths);
|
|
4027
4263
|
const sanitizedName = sanitizeName(skillName);
|
|
4028
|
-
if (isGlobal
|
|
4029
|
-
|
|
4264
|
+
if (isGlobal) {
|
|
4265
|
+
const globalDirs = await getAgentBaseDirs(agentKey, true, cwd);
|
|
4266
|
+
for (const globalDir of globalDirs) pathsToCleanup.add(join(globalDir, sanitizedName));
|
|
4267
|
+
} else if (agent.supportsProjectInstall !== false) pathsToCleanup.add(join(cwd, agent.skillsDir, sanitizedName));
|
|
4030
4268
|
for (const pathToCleanup of pathsToCleanup) {
|
|
4031
4269
|
if (pathToCleanup === canonicalPath) continue;
|
|
4032
4270
|
try {
|
|
@@ -4041,12 +4279,16 @@ async function removeCommand(skillNames, options) {
|
|
|
4041
4279
|
}
|
|
4042
4280
|
const remainingAgents = (await detectInstalledAgents()).filter((a) => !targetAgents.includes(a));
|
|
4043
4281
|
let isStillUsed = false;
|
|
4044
|
-
for (const agentKey of remainingAgents)
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4282
|
+
for (const agentKey of remainingAgents) {
|
|
4283
|
+
const paths = await getInstallPaths(skillName, agentKey, {
|
|
4284
|
+
global: isGlobal,
|
|
4285
|
+
cwd
|
|
4286
|
+
});
|
|
4287
|
+
for (const path of paths) if (await lstat(path).catch(() => null)) {
|
|
4288
|
+
isStillUsed = true;
|
|
4289
|
+
break;
|
|
4290
|
+
}
|
|
4291
|
+
if (isStillUsed) break;
|
|
4050
4292
|
}
|
|
4051
4293
|
if (!isStillUsed) await rm(canonicalPath, {
|
|
4052
4294
|
recursive: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bip-skills",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.13",
|
|
4
4
|
"description": "The open agent skills ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,21 @@
|
|
|
14
14
|
"README.md",
|
|
15
15
|
"ThirdPartyNoticeText.txt"
|
|
16
16
|
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "obuild && node scripts/generate-licenses.ts",
|
|
19
|
+
"generate-licenses": "node scripts/generate-licenses.ts",
|
|
20
|
+
"dev": "node src/cli.ts",
|
|
21
|
+
"exec:test": "node scripts/execute-tests.ts",
|
|
22
|
+
"pack:check": "npm pack --dry-run --ignore-scripts --cache .npm-cache",
|
|
23
|
+
"prepublishOnly": "npm run build",
|
|
24
|
+
"publish:npm": "node scripts/publish-npm.ts",
|
|
25
|
+
"format": "prettier --write 'src/**/*.ts' 'scripts/**/*.ts'",
|
|
26
|
+
"format:check": "prettier --check 'src/**/*.ts' 'scripts/**/*.ts'",
|
|
27
|
+
"prepare": "husky",
|
|
28
|
+
"test": "vitest",
|
|
29
|
+
"type-check": "tsc --noEmit",
|
|
30
|
+
"publish:snapshot": "npm version prerelease --preid=snapshot --no-git-tag-version && npm publish --tag snapshot"
|
|
31
|
+
},
|
|
17
32
|
"lint-staged": {
|
|
18
33
|
"src/**/*.ts": "prettier --write",
|
|
19
34
|
"scripts/**/*.ts": "prettier --write",
|
|
@@ -29,6 +44,7 @@
|
|
|
29
44
|
"augment",
|
|
30
45
|
"claude-code",
|
|
31
46
|
"openclaw",
|
|
47
|
+
"yonclaw",
|
|
32
48
|
"cline",
|
|
33
49
|
"codebuddy",
|
|
34
50
|
"codex",
|
|
@@ -95,16 +111,5 @@
|
|
|
95
111
|
"engines": {
|
|
96
112
|
"node": ">=18"
|
|
97
113
|
},
|
|
98
|
-
"
|
|
99
|
-
|
|
100
|
-
"generate-licenses": "node scripts/generate-licenses.ts",
|
|
101
|
-
"dev": "node src/cli.ts",
|
|
102
|
-
"exec:test": "node scripts/execute-tests.ts",
|
|
103
|
-
"pack:check": "npm pack --dry-run --ignore-scripts --cache .npm-cache",
|
|
104
|
-
"format": "prettier --write 'src/**/*.ts' 'scripts/**/*.ts'",
|
|
105
|
-
"format:check": "prettier --check 'src/**/*.ts' 'scripts/**/*.ts'",
|
|
106
|
-
"test": "vitest",
|
|
107
|
-
"type-check": "tsc --noEmit",
|
|
108
|
-
"publish:snapshot": "npm version prerelease --preid=snapshot --no-git-tag-version && npm publish --tag snapshot"
|
|
109
|
-
}
|
|
110
|
-
}
|
|
114
|
+
"packageManager": "pnpm@10.17.1"
|
|
115
|
+
}
|