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.
Files changed (2) hide show
  1. package/dist/cli.mjs +386 -144
  2. 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.globalSkillsDir === void 0) return {
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 agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
1164
- const agentDir = join(agentBase, skillName);
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, agentDir)) return {
1320
+ for (const [index, agentBase] of agentBases.entries()) if (!isPathSafe(agentBase, agentDirs[index])) return {
1173
1321
  success: false,
1174
- path: agentDir,
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 cleanAndCreateDirectory(agentDir);
1181
- await copyDirectory(skill.path, agentDir);
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: agentDir,
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
- if (!await createSymlink(canonicalDir, agentDir)) {
1197
- await cleanAndCreateDirectory(agentDir);
1198
- await copyDirectory(skill.path, agentDir);
1199
- return {
1200
- success: true,
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: agentDir,
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.globalSkillsDir === void 0) return false;
1247
- const targetBase = options.global ? agent.globalSkillsDir : join(options.cwd || process.cwd(), agent.skillsDir);
1248
- const skillDir = join(targetBase, sanitized);
1249
- if (!isPathSafe(targetBase, skillDir)) return false;
1250
- try {
1251
- await access(skillDir);
1252
- return true;
1253
- } catch {
1254
- return false;
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 getInstallPath(skillName, agentType, options = {}) {
1258
- agents[agentType];
1259
- options.cwd || process.cwd();
1406
+ async function getInstallPaths(skillName, agentType, options = {}) {
1260
1407
  const sanitized = sanitizeName(skillName);
1261
- const targetBase = getAgentBaseDir(agentType, options.global ?? false, options.cwd);
1262
- const installPath = join(targetBase, sanitized);
1263
- if (!isPathSafe(targetBase, installPath)) throw new Error("Invalid skill name: potential path traversal detected");
1264
- return installPath;
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.globalSkillsDir === void 0) return {
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 agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
1288
- const agentDir = join(agentBase, skillName);
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, agentDir)) return {
1456
+ for (const [index, agentBase] of agentBases.entries()) if (!isPathSafe(agentBase, agentDirs[index])) return {
1296
1457
  success: false,
1297
- path: agentDir,
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 cleanAndCreateDirectory(agentDir);
1313
- await writeSkillFiles(agentDir);
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: agentDir,
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
- if (!await createSymlink(canonicalDir, agentDir)) {
1329
- await cleanAndCreateDirectory(agentDir);
1330
- await writeSkillFiles(agentDir);
1331
- return {
1332
- success: true,
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: agentDir,
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
- const agent = agents[agentType];
1371
- if (isGlobal && agent.globalSkillsDir === void 0) continue;
1372
- const agentDir = isGlobal ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
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
- const agent = agents[agentType];
1413
- if (scope.global && agent.globalSkillsDir === void 0) continue;
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 possibleName of possibleNames) {
1422
- const agentSkillDir = join(agentBase, possibleName);
1423
- if (!isPathSafe(agentBase, agentSkillDir)) continue;
1424
- try {
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
- const candidateSkillMd = join(candidateDir, "SKILL.md");
1438
- await stat(candidateSkillMd);
1439
- const candidateSkill = await parseSkillMd(candidateSkillMd);
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
- } catch {}
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.11";
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 supportsGlobalFilter = (a) => !options.global || agents[a].globalSkillsDir;
2260
- const universalAgents = getUniversalAgents().filter(supportsGlobalFilter);
2261
- const otherAgents = getNonUniversalAgents().filter(supportsGlobalFilter);
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((a) => agents[a].globalSkillsDir !== void 0);
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
- ...installGlobally && { global: "1" },
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 && installGlobally) {
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)) try {
2617
- const matchingResult = successful.find((r) => r.skill === skill.installName);
2618
- const installDir = matchingResult?.canonicalPath || matchingResult?.path;
2619
- if (installDir) {
2620
- const computedHash = await computeSkillFolderHash(installDir);
2621
- await addSkillToLocalLock(skill.installName, {
2622
- source: sourceIdentifier,
2623
- sourceType: "well-known",
2624
- computedHash
2625
- }, cwd);
2626
- }
2627
- } catch {}
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((a) => agents[a].globalSkillsDir !== void 0);
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
- ...installGlobally && { global: "1" },
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
- ...installGlobally && { global: "1" },
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)) try {
3113
- const computedHash = await computeSkillFolderHash(skill.path);
3114
- await addSkillToLocalLock(skill.name, {
3115
- source: normalizedSource || parsed.url,
3116
- sourceType: parsed.type,
3117
- computedHash
3118
- }, cwd);
3119
- } catch {}
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 = validAgents;
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 = [...installedAgents];
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 agent of Object.values(agents)) if (agent.globalSkillsDir !== void 0) await scanDir(agent.globalSkillsDir);
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 skillPath = getInstallPath(skillName, agentKey, {
4258
+ const skillPaths = await getInstallPaths(skillName, agentKey, {
4023
4259
  global: isGlobal,
4024
4260
  cwd
4025
4261
  });
4026
- const pathsToCleanup = new Set([skillPath]);
4262
+ const pathsToCleanup = new Set(skillPaths);
4027
4263
  const sanitizedName = sanitizeName(skillName);
4028
- if (isGlobal && agent.globalSkillsDir) pathsToCleanup.add(join(agent.globalSkillsDir, sanitizedName));
4029
- else pathsToCleanup.add(join(cwd, agent.skillsDir, sanitizedName));
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) if (await lstat(getInstallPath(skillName, agentKey, {
4045
- global: isGlobal,
4046
- cwd
4047
- })).catch(() => null)) {
4048
- isStillUsed = true;
4049
- break;
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.11",
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
- "scripts": {
99
- "build": "obuild && node scripts/generate-licenses.ts",
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
+ }