bip-skills 1.4.11 → 1.4.12

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 +315 -110
  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,117 @@ 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 (currentPlatform === "darwin") return join(home, "Library", "Application Support", "yonclaw");
696
+ if (currentPlatform === "win32") return join(env.APPDATA || join(home, "AppData", "Roaming"), "yonclaw");
697
+ return join(options.xdgConfigDir || env.XDG_CONFIG_HOME || xdgConfig || join(home, ".config"), "yonclaw");
698
+ }
699
+ function getYonClawProfileSkillsDir(baseDir, profileKey) {
700
+ return join(baseDir, "profiles", profileKey, "userData", "runtime", "openclaw", "skills");
701
+ }
702
+ function getYonClawGlobalSkillsDirHint(options = {}) {
703
+ return getYonClawProfileSkillsDir(getYonClawBaseDir(options), "profile-*");
704
+ }
705
+ async function readJson(path) {
706
+ try {
707
+ return JSON.parse(await readFile(path, "utf-8"));
708
+ } catch {
709
+ return null;
710
+ }
711
+ }
712
+ async function listProfileKeys(baseDir) {
713
+ try {
714
+ return (await readdir(join(baseDir, "profiles"), { withFileTypes: true })).filter((entry) => entry.isDirectory() && entry.name.startsWith("profile-")).map((entry) => entry.name).sort();
715
+ } catch {
716
+ return [];
717
+ }
718
+ }
719
+ function unique(items) {
720
+ return Array.from(new Set(items));
721
+ }
722
+ function parseUserDataProfile(command) {
723
+ const match = command.match(/--user-data-dir=(?:"([^"]+)"|'([^']+)'|(\S+))/);
724
+ const userDataDir = match?.[1] ?? match?.[2] ?? match?.[3];
725
+ if (!userDataDir) return null;
726
+ return normalize(userDataDir).match(/[\\/]profiles[\\/]([^\\/]+)[\\/]userData$/)?.[1] ?? null;
727
+ }
728
+ function isLivePid(pid) {
729
+ try {
730
+ process.kill(pid, 0);
731
+ return true;
732
+ } catch (error) {
733
+ return error instanceof Error && "code" in error && error.code === "EPERM";
734
+ }
735
+ }
736
+ function parsePsOutput(stdout) {
737
+ return stdout.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
738
+ const match = line.match(/^(\d+)\s+(.+)$/);
739
+ if (!match) return [];
740
+ return [{
741
+ pid: Number(match[1]),
742
+ command: match[2]
743
+ }];
744
+ });
745
+ }
746
+ function parsePowershellProcesses(stdout) {
747
+ try {
748
+ const parsed = JSON.parse(stdout);
749
+ return (Array.isArray(parsed) ? parsed : [parsed]).flatMap((item) => {
750
+ if (typeof item.ProcessId !== "number" || typeof item.CommandLine !== "string") return [];
751
+ return [{
752
+ pid: item.ProcessId,
753
+ command: item.CommandLine
754
+ }];
755
+ });
756
+ } catch {
757
+ return [];
758
+ }
759
+ }
760
+ async function listYonClawProcesses(currentPlatform = platform()) {
761
+ try {
762
+ if (currentPlatform === "win32") {
763
+ const { stdout } = await execFileAsync("powershell.exe", [
764
+ "-NoProfile",
765
+ "-Command",
766
+ "Get-CimInstance Win32_Process | Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress"
767
+ ]);
768
+ return parsePowershellProcesses(stdout);
769
+ }
770
+ const { stdout } = await execFileAsync("ps", ["-axo", "pid=,command="]);
771
+ return parsePsOutput(stdout);
772
+ } catch {
773
+ return [];
774
+ }
775
+ }
776
+ async function resolveYonClawGlobalSkillsDirs(options = {}) {
777
+ const baseDir = getYonClawBaseDir(options);
778
+ const profileKeys = await listProfileKeys(baseDir);
779
+ if (profileKeys.length === 0) return [];
780
+ const processes = await (options.processProvider ?? { listProcesses: () => listYonClawProcesses(options.platform) }).listProcesses();
781
+ const processByPid = new Map(processes.map((processInfo) => [processInfo.pid, processInfo]));
782
+ const liveGatewayProfiles = [];
783
+ for (const profileKey of profileKeys) {
784
+ const runtime = await readJson(join(baseDir, "profiles", profileKey, "userData", "gateway-runtime.json"));
785
+ if (typeof runtime?.pid !== "number") continue;
786
+ const processInfo = processByPid.get(runtime.pid);
787
+ if (processInfo && GATEWAY_PROCESS_RE.test(processInfo.command) && isLivePid(runtime.pid)) liveGatewayProfiles.push(profileKey);
788
+ }
789
+ if (liveGatewayProfiles.length > 0) return unique(liveGatewayProfiles).map((profileKey) => getYonClawProfileSkillsDir(baseDir, profileKey));
790
+ 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)));
791
+ if (userDataProfiles.length > 0) return userDataProfiles.map((profileKey) => getYonClawProfileSkillsDir(baseDir, profileKey));
792
+ if (processes.some((processInfo) => YONCLAW_PROCESS_RE.test(processInfo.command))) {
793
+ const activeProfile = await readJson(join(baseDir, "device", "active-profile.json"));
794
+ if (typeof activeProfile?.profileKey === "string" && profileKeys.includes(activeProfile.profileKey)) return [getYonClawProfileSkillsDir(baseDir, activeProfile.profileKey)];
795
+ }
796
+ return profileKeys.map((profileKey) => getYonClawProfileSkillsDir(baseDir, profileKey));
797
+ }
686
798
  const home = homedir();
687
799
  const configHome = xdgConfig ?? join(home, ".config");
688
800
  const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
@@ -703,6 +815,17 @@ const agents = {
703
815
  return existsSync(join(configHome, "amp"));
704
816
  }
705
817
  },
818
+ yonclaw: {
819
+ name: "yonclaw",
820
+ displayName: "YonClaw",
821
+ skillsDir: "runtime/openclaw/skills",
822
+ globalSkillsDir: getYonClawGlobalSkillsDirHint(),
823
+ resolveGlobalSkillsDirs: resolveYonClawGlobalSkillsDirs,
824
+ supportsProjectInstall: false,
825
+ detectInstalled: async () => {
826
+ return existsSync(getYonClawBaseDir());
827
+ }
828
+ },
706
829
  antigravity: {
707
830
  name: "antigravity",
708
831
  displayName: "Antigravity",
@@ -1079,6 +1202,10 @@ function getNonUniversalAgents() {
1079
1202
  function isUniversalAgent(type) {
1080
1203
  return agents[type].skillsDir === ".agents/skills";
1081
1204
  }
1205
+ function agentSupportsGlobalInstall$1(agentType) {
1206
+ const agent = agents[agentType];
1207
+ return agent.globalSkillsDir !== void 0 || agent.resolveGlobalSkillsDirs !== void 0;
1208
+ }
1082
1209
  function sanitizeName(name) {
1083
1210
  return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").substring(0, 255) || "unnamed-skill";
1084
1211
  }
@@ -1100,6 +1227,13 @@ function getAgentBaseDir(agentType, global, cwd) {
1100
1227
  }
1101
1228
  return join(baseDir, agent.skillsDir);
1102
1229
  }
1230
+ async function getAgentBaseDirs(agentType, global, cwd) {
1231
+ const agent = agents[agentType];
1232
+ if (!global && agent.supportsProjectInstall === false) return [];
1233
+ if (global && agent.resolveGlobalSkillsDirs) return agent.resolveGlobalSkillsDirs();
1234
+ if (global && !agentSupportsGlobalInstall$1(agentType)) return [];
1235
+ return [getAgentBaseDir(agentType, global, cwd)];
1236
+ }
1103
1237
  function resolveSymlinkTarget(linkPath, linkTarget) {
1104
1238
  return resolve(dirname(linkPath), linkTarget);
1105
1239
  }
@@ -1151,7 +1285,13 @@ async function installSkillForAgent(skill, agentType, options = {}) {
1151
1285
  const agent = agents[agentType];
1152
1286
  const isGlobal = options.global ?? false;
1153
1287
  const cwd = options.cwd || process.cwd();
1154
- if (isGlobal && agent.globalSkillsDir === void 0) return {
1288
+ if (!isGlobal && agent.supportsProjectInstall === false) return {
1289
+ success: false,
1290
+ path: "",
1291
+ mode: options.mode ?? "symlink",
1292
+ error: `${agent.displayName} only supports global skill installation. Use --global.`
1293
+ };
1294
+ if (isGlobal && !agentSupportsGlobalInstall$1(agentType)) return {
1155
1295
  success: false,
1156
1296
  path: "",
1157
1297
  mode: options.mode ?? "symlink",
@@ -1160,28 +1300,37 @@ async function installSkillForAgent(skill, agentType, options = {}) {
1160
1300
  const skillName = sanitizeName(skill.name || basename(skill.path));
1161
1301
  const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
1162
1302
  const canonicalDir = join(canonicalBase, skillName);
1163
- const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
1164
- const agentDir = join(agentBase, skillName);
1303
+ const agentBases = await getAgentBaseDirs(agentType, isGlobal, cwd);
1304
+ const agentDirs = agentBases.map((agentBase) => join(agentBase, skillName));
1305
+ const agentDir = agentDirs[0] ?? "";
1165
1306
  const installMode = options.mode ?? "symlink";
1307
+ if (agentBases.length === 0) return {
1308
+ success: false,
1309
+ path: "",
1310
+ mode: installMode,
1311
+ error: `No ${agent.displayName} skills directories found`
1312
+ };
1166
1313
  if (!isPathSafe(canonicalBase, canonicalDir)) return {
1167
1314
  success: false,
1168
1315
  path: agentDir,
1169
1316
  mode: installMode,
1170
1317
  error: "Invalid skill name: potential path traversal detected"
1171
1318
  };
1172
- if (!isPathSafe(agentBase, agentDir)) return {
1319
+ for (const [index, agentBase] of agentBases.entries()) if (!isPathSafe(agentBase, agentDirs[index])) return {
1173
1320
  success: false,
1174
- path: agentDir,
1321
+ path: agentDirs[index],
1175
1322
  mode: installMode,
1176
1323
  error: "Invalid skill name: potential path traversal detected"
1177
1324
  };
1178
1325
  try {
1179
1326
  if (installMode === "copy") {
1180
- await cleanAndCreateDirectory(agentDir);
1181
- await copyDirectory(skill.path, agentDir);
1327
+ await Promise.all(agentDirs.map(async (targetDir) => {
1328
+ await cleanAndCreateDirectory(targetDir);
1329
+ await copyDirectory(skill.path, targetDir);
1330
+ }));
1182
1331
  return {
1183
1332
  success: true,
1184
- path: agentDir,
1333
+ path: agentDirs.join(", "),
1185
1334
  mode: "copy"
1186
1335
  };
1187
1336
  }
@@ -1193,22 +1342,18 @@ async function installSkillForAgent(skill, agentType, options = {}) {
1193
1342
  canonicalPath: canonicalDir,
1194
1343
  mode: "symlink"
1195
1344
  };
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
- };
1345
+ let symlinkFailed = false;
1346
+ for (const targetDir of agentDirs) if (!await createSymlink(canonicalDir, targetDir)) {
1347
+ symlinkFailed = true;
1348
+ await cleanAndCreateDirectory(targetDir);
1349
+ await copyDirectory(skill.path, targetDir);
1206
1350
  }
1207
1351
  return {
1208
1352
  success: true,
1209
- path: agentDir,
1353
+ path: agentDirs.join(", "),
1210
1354
  canonicalPath: canonicalDir,
1211
- mode: "symlink"
1355
+ mode: "symlink",
1356
+ ...symlinkFailed && { symlinkFailed: true }
1212
1357
  };
1213
1358
  } catch (error) {
1214
1359
  return {
@@ -1243,25 +1388,27 @@ async function copyDirectory(src, dest) {
1243
1388
  async function isSkillInstalled(skillName, agentType, options = {}) {
1244
1389
  const agent = agents[agentType];
1245
1390
  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;
1391
+ if (!options.global && agent.supportsProjectInstall === false) return false;
1392
+ if (options.global && !agentSupportsGlobalInstall$1(agentType)) return false;
1393
+ const targetBases = await getAgentBaseDirs(agentType, options.global ?? false, options.cwd);
1394
+ if (targetBases.length === 0) return false;
1395
+ for (const targetBase of targetBases) {
1396
+ const skillDir = join(targetBase, sanitized);
1397
+ if (!isPathSafe(targetBase, skillDir)) continue;
1398
+ try {
1399
+ await access(skillDir);
1400
+ return true;
1401
+ } catch {}
1255
1402
  }
1403
+ return false;
1256
1404
  }
1257
- function getInstallPath(skillName, agentType, options = {}) {
1258
- agents[agentType];
1259
- options.cwd || process.cwd();
1405
+ async function getInstallPaths(skillName, agentType, options = {}) {
1260
1406
  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;
1407
+ return (await getAgentBaseDirs(agentType, options.global ?? false, options.cwd)).map((targetBase) => {
1408
+ const installPath = join(targetBase, sanitized);
1409
+ if (!isPathSafe(targetBase, installPath)) throw new Error("Invalid skill name: potential path traversal detected");
1410
+ return installPath;
1411
+ });
1265
1412
  }
1266
1413
  function getCanonicalPath(skillName, options = {}) {
1267
1414
  const sanitized = sanitizeName(skillName);
@@ -1275,7 +1422,13 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
1275
1422
  const isGlobal = options.global ?? false;
1276
1423
  const cwd = options.cwd || process.cwd();
1277
1424
  const installMode = options.mode ?? "symlink";
1278
- if (isGlobal && agent.globalSkillsDir === void 0) return {
1425
+ if (!isGlobal && agent.supportsProjectInstall === false) return {
1426
+ success: false,
1427
+ path: "",
1428
+ mode: installMode,
1429
+ error: `${agent.displayName} only supports global skill installation. Use --global.`
1430
+ };
1431
+ if (isGlobal && !agentSupportsGlobalInstall$1(agentType)) return {
1279
1432
  success: false,
1280
1433
  path: "",
1281
1434
  mode: installMode,
@@ -1284,17 +1437,24 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
1284
1437
  const skillName = sanitizeName(skill.installName);
1285
1438
  const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
1286
1439
  const canonicalDir = join(canonicalBase, skillName);
1287
- const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
1288
- const agentDir = join(agentBase, skillName);
1440
+ const agentBases = await getAgentBaseDirs(agentType, isGlobal, cwd);
1441
+ const agentDirs = agentBases.map((agentBase) => join(agentBase, skillName));
1442
+ const agentDir = agentDirs[0] ?? "";
1443
+ if (agentBases.length === 0) return {
1444
+ success: false,
1445
+ path: "",
1446
+ mode: installMode,
1447
+ error: `No ${agent.displayName} skills directories found`
1448
+ };
1289
1449
  if (!isPathSafe(canonicalBase, canonicalDir)) return {
1290
1450
  success: false,
1291
1451
  path: agentDir,
1292
1452
  mode: installMode,
1293
1453
  error: "Invalid skill name: potential path traversal detected"
1294
1454
  };
1295
- if (!isPathSafe(agentBase, agentDir)) return {
1455
+ for (const [index, agentBase] of agentBases.entries()) if (!isPathSafe(agentBase, agentDirs[index])) return {
1296
1456
  success: false,
1297
- path: agentDir,
1457
+ path: agentDirs[index],
1298
1458
  mode: installMode,
1299
1459
  error: "Invalid skill name: potential path traversal detected"
1300
1460
  };
@@ -1309,11 +1469,13 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
1309
1469
  }
1310
1470
  try {
1311
1471
  if (installMode === "copy") {
1312
- await cleanAndCreateDirectory(agentDir);
1313
- await writeSkillFiles(agentDir);
1472
+ await Promise.all(agentDirs.map(async (targetDir) => {
1473
+ await cleanAndCreateDirectory(targetDir);
1474
+ await writeSkillFiles(targetDir);
1475
+ }));
1314
1476
  return {
1315
1477
  success: true,
1316
- path: agentDir,
1478
+ path: agentDirs.join(", "),
1317
1479
  mode: "copy"
1318
1480
  };
1319
1481
  }
@@ -1325,22 +1487,18 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
1325
1487
  canonicalPath: canonicalDir,
1326
1488
  mode: "symlink"
1327
1489
  };
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
- };
1490
+ let symlinkFailed = false;
1491
+ for (const targetDir of agentDirs) if (!await createSymlink(canonicalDir, targetDir)) {
1492
+ symlinkFailed = true;
1493
+ await cleanAndCreateDirectory(targetDir);
1494
+ await writeSkillFiles(targetDir);
1338
1495
  }
1339
1496
  return {
1340
1497
  success: true,
1341
- path: agentDir,
1498
+ path: agentDirs.join(", "),
1342
1499
  canonicalPath: canonicalDir,
1343
- mode: "symlink"
1500
+ mode: "symlink",
1501
+ ...symlinkFailed && { symlinkFailed: true }
1344
1502
  };
1345
1503
  } catch (error) {
1346
1504
  return {
@@ -1367,10 +1525,10 @@ async function listInstalledSkills(options = {}) {
1367
1525
  path: getCanonicalSkillsDir(isGlobal, cwd)
1368
1526
  });
1369
1527
  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({
1528
+ agents[agentType];
1529
+ if (isGlobal && !agentSupportsGlobalInstall$1(agentType)) continue;
1530
+ const agentDirs = await getAgentBaseDirs(agentType, isGlobal, cwd);
1531
+ for (const agentDir of agentDirs) if (!scopes.some((s) => s.path === agentDir && s.global === isGlobal)) scopes.push({
1374
1532
  global: isGlobal,
1375
1533
  path: agentDir,
1376
1534
  agentType
@@ -1409,41 +1567,47 @@ async function listInstalledSkills(options = {}) {
1409
1567
  const sanitizedSkillName = sanitizeName(skill.name);
1410
1568
  const installedAgents = [];
1411
1569
  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);
1570
+ agents[agentType];
1571
+ if (scope.global && !agentSupportsGlobalInstall$1(agentType)) continue;
1415
1572
  let found = false;
1573
+ const agentBases = await getAgentBaseDirs(agentType, scope.global, cwd);
1416
1574
  const possibleNames = Array.from(new Set([
1417
1575
  entry.name,
1418
1576
  sanitizedSkillName,
1419
1577
  skill.name.toLowerCase().replace(/\s+/g, "-").replace(/[\/\\:\0]/g, "")
1420
1578
  ]));
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;
1579
+ for (const agentBase of agentBases) {
1580
+ for (const possibleName of possibleNames) {
1581
+ const agentSkillDir = join(agentBase, possibleName);
1582
+ if (!isPathSafe(agentBase, agentSkillDir)) continue;
1436
1583
  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
- }
1584
+ await access(agentSkillDir);
1585
+ found = true;
1586
+ break;
1444
1587
  } catch {}
1445
1588
  }
1446
- } catch {}
1589
+ if (found) break;
1590
+ }
1591
+ if (!found) for (const agentBase of agentBases) {
1592
+ try {
1593
+ const agentEntries = await readdir(agentBase, { withFileTypes: true });
1594
+ for (const agentEntry of agentEntries) {
1595
+ if (!agentEntry.isDirectory()) continue;
1596
+ const candidateDir = join(agentBase, agentEntry.name);
1597
+ if (!isPathSafe(agentBase, candidateDir)) continue;
1598
+ try {
1599
+ const candidateSkillMd = join(candidateDir, "SKILL.md");
1600
+ await stat(candidateSkillMd);
1601
+ const candidateSkill = await parseSkillMd(candidateSkillMd);
1602
+ if (candidateSkill && candidateSkill.name === skill.name) {
1603
+ found = true;
1604
+ break;
1605
+ }
1606
+ } catch {}
1607
+ }
1608
+ } catch {}
1609
+ if (found) break;
1610
+ }
1447
1611
  if (found) installedAgents.push(agentType);
1448
1612
  }
1449
1613
  if (skillsMap.has(skillKey)) {
@@ -2075,7 +2239,7 @@ function createEmptyLocalLock() {
2075
2239
  skills: {}
2076
2240
  };
2077
2241
  }
2078
- var version$1 = "1.4.11";
2242
+ var version$1 = "1.4.12";
2079
2243
  const isCancelled$1 = (value) => typeof value === "symbol";
2080
2244
  async function isSourcePrivate(source) {
2081
2245
  const ownerRepo = parseOwnerRepo(source);
@@ -2213,6 +2377,31 @@ function ensureUniversalAgents(targetAgents) {
2213
2377
  for (const ua of universalAgents) if (!result.includes(ua)) result.push(ua);
2214
2378
  return result;
2215
2379
  }
2380
+ function agentSupportsGlobalInstall(agent) {
2381
+ return agents[agent].globalSkillsDir !== void 0 || agents[agent].resolveGlobalSkillsDirs !== void 0;
2382
+ }
2383
+ function agentSupportsScope(agent, global) {
2384
+ if (global === true) return agentSupportsGlobalInstall(agent);
2385
+ if (global === false) return agents[agent].supportsProjectInstall !== false;
2386
+ return true;
2387
+ }
2388
+ function filterProjectUnsupportedAgents(targetAgents, installGlobally, explicitlyRequestedAgents) {
2389
+ if (installGlobally) return targetAgents;
2390
+ const unsupported = targetAgents.filter((agent) => agents[agent].supportsProjectInstall === false);
2391
+ if (unsupported.length === 0) return targetAgents;
2392
+ const explicitUnsupported = explicitlyRequestedAgents?.filter((agent) => agent !== "*" && unsupported.includes(agent));
2393
+ if (explicitUnsupported && explicitUnsupported.length > 0) {
2394
+ M.error(`${formatList$1(unsupported.map((agent) => agents[agent].displayName))} only supports global skill installation. Use --global.`);
2395
+ process.exit(1);
2396
+ }
2397
+ M.warn(`Skipping ${formatList$1(unsupported.map((agent) => agents[agent].displayName))}: global-only agent`);
2398
+ const filtered = targetAgents.filter((agent) => agents[agent].supportsProjectInstall !== false);
2399
+ if (filtered.length === 0) {
2400
+ M.error("No selected agents support project skill installation.");
2401
+ process.exit(1);
2402
+ }
2403
+ return filtered;
2404
+ }
2216
2405
  function buildResultLines(results, targetAgents) {
2217
2406
  const lines = [];
2218
2407
  const { universal, symlinked: symlinkAgents } = splitAgentsByType(targetAgents);
@@ -2256,9 +2445,9 @@ async function promptForAgents(message, choices) {
2256
2445
  return selected;
2257
2446
  }
2258
2447
  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);
2448
+ const supportsScopeFilter = (a) => agentSupportsScope(a, options.global);
2449
+ const universalAgents = getUniversalAgents().filter(supportsScopeFilter);
2450
+ const otherAgents = getNonUniversalAgents().filter(supportsScopeFilter);
2262
2451
  const universalSection = {
2263
2452
  title: "Universal (.agents/skills)",
2264
2453
  items: universalAgents.map((a) => ({
@@ -2449,7 +2638,7 @@ async function handleWellKnownSkills(source, url, options, spinner, allowFallbac
2449
2638
  }
2450
2639
  }
2451
2640
  let installGlobally = options.global ?? false;
2452
- const supportsGlobal = targetAgents.some((a) => agents[a].globalSkillsDir !== void 0);
2641
+ const supportsGlobal = targetAgents.some(agentSupportsGlobalInstall);
2453
2642
  if (options.global === void 0 && !options.yes && supportsGlobal) {
2454
2643
  const scope = await ve({
2455
2644
  message: "Installation scope",
@@ -2469,6 +2658,7 @@ async function handleWellKnownSkills(source, url, options, spinner, allowFallbac
2469
2658
  }
2470
2659
  installGlobally = scope;
2471
2660
  }
2661
+ targetAgents = filterProjectUnsupportedAgents(targetAgents, installGlobally, options.agent);
2472
2662
  let installMode = options.copy ? "copy" : "symlink";
2473
2663
  if (!options.copy && !options.yes) {
2474
2664
  const modeChoice = await ve({
@@ -2908,7 +3098,7 @@ async function runAdd(args, options = {}) {
2908
3098
  }
2909
3099
  }
2910
3100
  let installGlobally = options.global ?? false;
2911
- const supportsGlobal = targetAgents.some((a) => agents[a].globalSkillsDir !== void 0);
3101
+ const supportsGlobal = targetAgents.some(agentSupportsGlobalInstall);
2912
3102
  if (options.global === void 0 && !options.yes && supportsGlobal) {
2913
3103
  const scope = await ve({
2914
3104
  message: "Installation scope",
@@ -2929,6 +3119,7 @@ async function runAdd(args, options = {}) {
2929
3119
  }
2930
3120
  installGlobally = scope;
2931
3121
  }
3122
+ targetAgents = filterProjectUnsupportedAgents(targetAgents, installGlobally, options.agent);
2932
3123
  let installMode = options.copy ? "copy" : "symlink";
2933
3124
  if (!options.copy && !options.yes) {
2934
3125
  const modeChoice = await ve({
@@ -3619,9 +3810,10 @@ async function runSync(args, options = {}) {
3619
3810
  M.info(`${toInstall.length} skill${toInstall.length !== 1 ? "s" : ""} to install/update`);
3620
3811
  let targetAgents;
3621
3812
  const validAgents = Object.keys(agents);
3813
+ const projectAgents = validAgents.filter((agent) => agents[agent].supportsProjectInstall !== false);
3622
3814
  const universalAgents = getUniversalAgents();
3623
3815
  if (options.agent?.includes("*")) {
3624
- targetAgents = validAgents;
3816
+ targetAgents = projectAgents;
3625
3817
  M.info(`Installing to all ${targetAgents.length} agents`);
3626
3818
  } else if (options.agent && options.agent.length > 0) {
3627
3819
  const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
@@ -3630,7 +3822,11 @@ async function runSync(args, options = {}) {
3630
3822
  M.info(`Valid agents: ${validAgents.join(", ")}`);
3631
3823
  process.exit(1);
3632
3824
  }
3633
- targetAgents = options.agent;
3825
+ targetAgents = options.agent.filter((agent) => {
3826
+ if (agents[agent].supportsProjectInstall !== false) return true;
3827
+ M.error(`${agents[agent].displayName} only supports global skill installation.`);
3828
+ process.exit(1);
3829
+ });
3634
3830
  } else {
3635
3831
  spinner.start("Loading agents...");
3636
3832
  const installedAgents = await detectInstalledAgents();
@@ -3642,7 +3838,7 @@ async function runSync(args, options = {}) {
3642
3838
  } else {
3643
3839
  const selected = await searchMultiselect({
3644
3840
  message: "Which agents do you want to install to?",
3645
- items: getNonUniversalAgents().map((a) => ({
3841
+ items: getNonUniversalAgents().filter((agent) => agents[agent].supportsProjectInstall !== false).map((a) => ({
3646
3842
  value: a,
3647
3843
  label: agents[a].displayName,
3648
3844
  hint: agents[a].skillsDir
@@ -3663,12 +3859,12 @@ async function runSync(args, options = {}) {
3663
3859
  targetAgents = selected;
3664
3860
  }
3665
3861
  else if (installedAgents.length === 1 || options.yes) {
3666
- targetAgents = [...installedAgents];
3862
+ targetAgents = installedAgents.filter((agent) => agents[agent].supportsProjectInstall !== false);
3667
3863
  for (const ua of universalAgents) if (!targetAgents.includes(ua)) targetAgents.push(ua);
3668
3864
  } else {
3669
3865
  const selected = await searchMultiselect({
3670
3866
  message: "Which agents do you want to install to?",
3671
- items: getNonUniversalAgents().filter((a) => installedAgents.includes(a)).map((a) => ({
3867
+ items: getNonUniversalAgents().filter((a) => installedAgents.includes(a) && agents[a].supportsProjectInstall !== false).map((a) => ({
3672
3868
  value: a,
3673
3869
  label: agents[a].displayName,
3674
3870
  hint: agents[a].skillsDir
@@ -3949,10 +4145,13 @@ async function removeCommand(skillNames, options) {
3949
4145
  };
3950
4146
  if (isGlobal) {
3951
4147
  await scanDir(getCanonicalSkillsDir(true, cwd));
3952
- for (const agent of Object.values(agents)) if (agent.globalSkillsDir !== void 0) await scanDir(agent.globalSkillsDir);
4148
+ for (const agentType of Object.keys(agents)) {
4149
+ const agent = agents[agentType];
4150
+ if (agent.globalSkillsDir !== void 0 || agent.resolveGlobalSkillsDirs !== void 0) for (const agentDir of await getAgentBaseDirs(agentType, true, cwd)) await scanDir(agentDir);
4151
+ }
3953
4152
  } else {
3954
4153
  await scanDir(getCanonicalSkillsDir(false, cwd));
3955
- for (const agent of Object.values(agents)) await scanDir(join(cwd, agent.skillsDir));
4154
+ for (const agent of Object.values(agents).filter((agent) => agent.supportsProjectInstall !== false)) await scanDir(join(cwd, agent.skillsDir));
3956
4155
  }
3957
4156
  const installedSkills = Array.from(skillNamesSet).sort();
3958
4157
  spinner.stop(`Found ${installedSkills.length} unique installed skill(s)`);
@@ -4019,14 +4218,16 @@ async function removeCommand(skillNames, options) {
4019
4218
  });
4020
4219
  for (const agentKey of targetAgents) {
4021
4220
  const agent = agents[agentKey];
4022
- const skillPath = getInstallPath(skillName, agentKey, {
4221
+ const skillPaths = await getInstallPaths(skillName, agentKey, {
4023
4222
  global: isGlobal,
4024
4223
  cwd
4025
4224
  });
4026
- const pathsToCleanup = new Set([skillPath]);
4225
+ const pathsToCleanup = new Set(skillPaths);
4027
4226
  const sanitizedName = sanitizeName(skillName);
4028
- if (isGlobal && agent.globalSkillsDir) pathsToCleanup.add(join(agent.globalSkillsDir, sanitizedName));
4029
- else pathsToCleanup.add(join(cwd, agent.skillsDir, sanitizedName));
4227
+ if (isGlobal) {
4228
+ const globalDirs = await getAgentBaseDirs(agentKey, true, cwd);
4229
+ for (const globalDir of globalDirs) pathsToCleanup.add(join(globalDir, sanitizedName));
4230
+ } else if (agent.supportsProjectInstall !== false) pathsToCleanup.add(join(cwd, agent.skillsDir, sanitizedName));
4030
4231
  for (const pathToCleanup of pathsToCleanup) {
4031
4232
  if (pathToCleanup === canonicalPath) continue;
4032
4233
  try {
@@ -4041,12 +4242,16 @@ async function removeCommand(skillNames, options) {
4041
4242
  }
4042
4243
  const remainingAgents = (await detectInstalledAgents()).filter((a) => !targetAgents.includes(a));
4043
4244
  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;
4245
+ for (const agentKey of remainingAgents) {
4246
+ const paths = await getInstallPaths(skillName, agentKey, {
4247
+ global: isGlobal,
4248
+ cwd
4249
+ });
4250
+ for (const path of paths) if (await lstat(path).catch(() => null)) {
4251
+ isStillUsed = true;
4252
+ break;
4253
+ }
4254
+ if (isStillUsed) break;
4050
4255
  }
4051
4256
  if (!isStillUsed) await rm(canonicalPath, {
4052
4257
  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.12",
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
+ }