bip-skills 1.4.10 → 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 +326 -113
  2. package/package.json +22 -17
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";
@@ -103,7 +104,12 @@ function isInternalSkillSourceUrl(sourceUrl) {
103
104
  return hostname ? isInternalSkillSourceHost(hostname) : false;
104
105
  }
105
106
  function isLocalPath(input) {
106
- return isAbsolute(input) || input.startsWith("./") || input.startsWith("../") || input === "." || input === ".." || /^[a-zA-Z]:[/\\]/.test(input);
107
+ return isAbsolute(input) || input === "~" || input.startsWith("~/") || input.startsWith("~\\") || input.startsWith("./") || input.startsWith("../") || input === "." || input === ".." || /^[a-zA-Z]:[/\\]/.test(input);
108
+ }
109
+ function expandLocalPath(input) {
110
+ if (input === "~") return homedir();
111
+ if (input.startsWith("~/") || input.startsWith("~\\")) return resolve(homedir(), input.slice(2));
112
+ return input;
107
113
  }
108
114
  const SOURCE_ALIASES = { "coinbase/agentWallet": "coinbase/agentic-wallet-skills" };
109
115
  function buildDefaultGitUrl(repoPath) {
@@ -113,7 +119,7 @@ function parseSource(input) {
113
119
  const alias = SOURCE_ALIASES[input];
114
120
  if (alias) input = alias;
115
121
  if (isLocalPath(input)) {
116
- const resolvedPath = resolve(input);
122
+ const resolvedPath = resolve(expandLocalPath(input));
117
123
  return {
118
124
  type: "local",
119
125
  url: resolvedPath,
@@ -678,6 +684,117 @@ function filterSkills(skills, inputNames) {
678
684
  return normalizedInputs.some((input) => input === name || input === displayName);
679
685
  });
680
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
+ }
681
798
  const home = homedir();
682
799
  const configHome = xdgConfig ?? join(home, ".config");
683
800
  const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
@@ -698,6 +815,17 @@ const agents = {
698
815
  return existsSync(join(configHome, "amp"));
699
816
  }
700
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
+ },
701
829
  antigravity: {
702
830
  name: "antigravity",
703
831
  displayName: "Antigravity",
@@ -1074,6 +1202,10 @@ function getNonUniversalAgents() {
1074
1202
  function isUniversalAgent(type) {
1075
1203
  return agents[type].skillsDir === ".agents/skills";
1076
1204
  }
1205
+ function agentSupportsGlobalInstall$1(agentType) {
1206
+ const agent = agents[agentType];
1207
+ return agent.globalSkillsDir !== void 0 || agent.resolveGlobalSkillsDirs !== void 0;
1208
+ }
1077
1209
  function sanitizeName(name) {
1078
1210
  return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").substring(0, 255) || "unnamed-skill";
1079
1211
  }
@@ -1095,6 +1227,13 @@ function getAgentBaseDir(agentType, global, cwd) {
1095
1227
  }
1096
1228
  return join(baseDir, agent.skillsDir);
1097
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
+ }
1098
1237
  function resolveSymlinkTarget(linkPath, linkTarget) {
1099
1238
  return resolve(dirname(linkPath), linkTarget);
1100
1239
  }
@@ -1146,7 +1285,13 @@ async function installSkillForAgent(skill, agentType, options = {}) {
1146
1285
  const agent = agents[agentType];
1147
1286
  const isGlobal = options.global ?? false;
1148
1287
  const cwd = options.cwd || process.cwd();
1149
- 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 {
1150
1295
  success: false,
1151
1296
  path: "",
1152
1297
  mode: options.mode ?? "symlink",
@@ -1155,28 +1300,37 @@ async function installSkillForAgent(skill, agentType, options = {}) {
1155
1300
  const skillName = sanitizeName(skill.name || basename(skill.path));
1156
1301
  const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
1157
1302
  const canonicalDir = join(canonicalBase, skillName);
1158
- const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
1159
- 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] ?? "";
1160
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
+ };
1161
1313
  if (!isPathSafe(canonicalBase, canonicalDir)) return {
1162
1314
  success: false,
1163
1315
  path: agentDir,
1164
1316
  mode: installMode,
1165
1317
  error: "Invalid skill name: potential path traversal detected"
1166
1318
  };
1167
- if (!isPathSafe(agentBase, agentDir)) return {
1319
+ for (const [index, agentBase] of agentBases.entries()) if (!isPathSafe(agentBase, agentDirs[index])) return {
1168
1320
  success: false,
1169
- path: agentDir,
1321
+ path: agentDirs[index],
1170
1322
  mode: installMode,
1171
1323
  error: "Invalid skill name: potential path traversal detected"
1172
1324
  };
1173
1325
  try {
1174
1326
  if (installMode === "copy") {
1175
- await cleanAndCreateDirectory(agentDir);
1176
- await copyDirectory(skill.path, agentDir);
1327
+ await Promise.all(agentDirs.map(async (targetDir) => {
1328
+ await cleanAndCreateDirectory(targetDir);
1329
+ await copyDirectory(skill.path, targetDir);
1330
+ }));
1177
1331
  return {
1178
1332
  success: true,
1179
- path: agentDir,
1333
+ path: agentDirs.join(", "),
1180
1334
  mode: "copy"
1181
1335
  };
1182
1336
  }
@@ -1188,22 +1342,18 @@ async function installSkillForAgent(skill, agentType, options = {}) {
1188
1342
  canonicalPath: canonicalDir,
1189
1343
  mode: "symlink"
1190
1344
  };
1191
- if (!await createSymlink(canonicalDir, agentDir)) {
1192
- await cleanAndCreateDirectory(agentDir);
1193
- await copyDirectory(skill.path, agentDir);
1194
- return {
1195
- success: true,
1196
- path: agentDir,
1197
- canonicalPath: canonicalDir,
1198
- mode: "symlink",
1199
- symlinkFailed: true
1200
- };
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);
1201
1350
  }
1202
1351
  return {
1203
1352
  success: true,
1204
- path: agentDir,
1353
+ path: agentDirs.join(", "),
1205
1354
  canonicalPath: canonicalDir,
1206
- mode: "symlink"
1355
+ mode: "symlink",
1356
+ ...symlinkFailed && { symlinkFailed: true }
1207
1357
  };
1208
1358
  } catch (error) {
1209
1359
  return {
@@ -1238,25 +1388,27 @@ async function copyDirectory(src, dest) {
1238
1388
  async function isSkillInstalled(skillName, agentType, options = {}) {
1239
1389
  const agent = agents[agentType];
1240
1390
  const sanitized = sanitizeName(skillName);
1241
- if (options.global && agent.globalSkillsDir === void 0) return false;
1242
- const targetBase = options.global ? agent.globalSkillsDir : join(options.cwd || process.cwd(), agent.skillsDir);
1243
- const skillDir = join(targetBase, sanitized);
1244
- if (!isPathSafe(targetBase, skillDir)) return false;
1245
- try {
1246
- await access(skillDir);
1247
- return true;
1248
- } catch {
1249
- 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 {}
1250
1402
  }
1403
+ return false;
1251
1404
  }
1252
- function getInstallPath(skillName, agentType, options = {}) {
1253
- agents[agentType];
1254
- options.cwd || process.cwd();
1405
+ async function getInstallPaths(skillName, agentType, options = {}) {
1255
1406
  const sanitized = sanitizeName(skillName);
1256
- const targetBase = getAgentBaseDir(agentType, options.global ?? false, options.cwd);
1257
- const installPath = join(targetBase, sanitized);
1258
- if (!isPathSafe(targetBase, installPath)) throw new Error("Invalid skill name: potential path traversal detected");
1259
- 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
+ });
1260
1412
  }
1261
1413
  function getCanonicalPath(skillName, options = {}) {
1262
1414
  const sanitized = sanitizeName(skillName);
@@ -1270,7 +1422,13 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
1270
1422
  const isGlobal = options.global ?? false;
1271
1423
  const cwd = options.cwd || process.cwd();
1272
1424
  const installMode = options.mode ?? "symlink";
1273
- 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 {
1274
1432
  success: false,
1275
1433
  path: "",
1276
1434
  mode: installMode,
@@ -1279,17 +1437,24 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
1279
1437
  const skillName = sanitizeName(skill.installName);
1280
1438
  const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
1281
1439
  const canonicalDir = join(canonicalBase, skillName);
1282
- const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
1283
- 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
+ };
1284
1449
  if (!isPathSafe(canonicalBase, canonicalDir)) return {
1285
1450
  success: false,
1286
1451
  path: agentDir,
1287
1452
  mode: installMode,
1288
1453
  error: "Invalid skill name: potential path traversal detected"
1289
1454
  };
1290
- if (!isPathSafe(agentBase, agentDir)) return {
1455
+ for (const [index, agentBase] of agentBases.entries()) if (!isPathSafe(agentBase, agentDirs[index])) return {
1291
1456
  success: false,
1292
- path: agentDir,
1457
+ path: agentDirs[index],
1293
1458
  mode: installMode,
1294
1459
  error: "Invalid skill name: potential path traversal detected"
1295
1460
  };
@@ -1304,11 +1469,13 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
1304
1469
  }
1305
1470
  try {
1306
1471
  if (installMode === "copy") {
1307
- await cleanAndCreateDirectory(agentDir);
1308
- await writeSkillFiles(agentDir);
1472
+ await Promise.all(agentDirs.map(async (targetDir) => {
1473
+ await cleanAndCreateDirectory(targetDir);
1474
+ await writeSkillFiles(targetDir);
1475
+ }));
1309
1476
  return {
1310
1477
  success: true,
1311
- path: agentDir,
1478
+ path: agentDirs.join(", "),
1312
1479
  mode: "copy"
1313
1480
  };
1314
1481
  }
@@ -1320,22 +1487,18 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
1320
1487
  canonicalPath: canonicalDir,
1321
1488
  mode: "symlink"
1322
1489
  };
1323
- if (!await createSymlink(canonicalDir, agentDir)) {
1324
- await cleanAndCreateDirectory(agentDir);
1325
- await writeSkillFiles(agentDir);
1326
- return {
1327
- success: true,
1328
- path: agentDir,
1329
- canonicalPath: canonicalDir,
1330
- mode: "symlink",
1331
- symlinkFailed: true
1332
- };
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);
1333
1495
  }
1334
1496
  return {
1335
1497
  success: true,
1336
- path: agentDir,
1498
+ path: agentDirs.join(", "),
1337
1499
  canonicalPath: canonicalDir,
1338
- mode: "symlink"
1500
+ mode: "symlink",
1501
+ ...symlinkFailed && { symlinkFailed: true }
1339
1502
  };
1340
1503
  } catch (error) {
1341
1504
  return {
@@ -1362,10 +1525,10 @@ async function listInstalledSkills(options = {}) {
1362
1525
  path: getCanonicalSkillsDir(isGlobal, cwd)
1363
1526
  });
1364
1527
  for (const agentType of agentsToCheck) {
1365
- const agent = agents[agentType];
1366
- if (isGlobal && agent.globalSkillsDir === void 0) continue;
1367
- const agentDir = isGlobal ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
1368
- 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({
1369
1532
  global: isGlobal,
1370
1533
  path: agentDir,
1371
1534
  agentType
@@ -1404,41 +1567,47 @@ async function listInstalledSkills(options = {}) {
1404
1567
  const sanitizedSkillName = sanitizeName(skill.name);
1405
1568
  const installedAgents = [];
1406
1569
  for (const agentType of agentsToCheck) {
1407
- const agent = agents[agentType];
1408
- if (scope.global && agent.globalSkillsDir === void 0) continue;
1409
- const agentBase = scope.global ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
1570
+ agents[agentType];
1571
+ if (scope.global && !agentSupportsGlobalInstall$1(agentType)) continue;
1410
1572
  let found = false;
1573
+ const agentBases = await getAgentBaseDirs(agentType, scope.global, cwd);
1411
1574
  const possibleNames = Array.from(new Set([
1412
1575
  entry.name,
1413
1576
  sanitizedSkillName,
1414
1577
  skill.name.toLowerCase().replace(/\s+/g, "-").replace(/[\/\\:\0]/g, "")
1415
1578
  ]));
1416
- for (const possibleName of possibleNames) {
1417
- const agentSkillDir = join(agentBase, possibleName);
1418
- if (!isPathSafe(agentBase, agentSkillDir)) continue;
1419
- try {
1420
- await access(agentSkillDir);
1421
- found = true;
1422
- break;
1423
- } catch {}
1424
- }
1425
- if (!found) try {
1426
- const agentEntries = await readdir(agentBase, { withFileTypes: true });
1427
- for (const agentEntry of agentEntries) {
1428
- if (!agentEntry.isDirectory()) continue;
1429
- const candidateDir = join(agentBase, agentEntry.name);
1430
- 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;
1431
1583
  try {
1432
- const candidateSkillMd = join(candidateDir, "SKILL.md");
1433
- await stat(candidateSkillMd);
1434
- const candidateSkill = await parseSkillMd(candidateSkillMd);
1435
- if (candidateSkill && candidateSkill.name === skill.name) {
1436
- found = true;
1437
- break;
1438
- }
1584
+ await access(agentSkillDir);
1585
+ found = true;
1586
+ break;
1439
1587
  } catch {}
1440
1588
  }
1441
- } 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
+ }
1442
1611
  if (found) installedAgents.push(agentType);
1443
1612
  }
1444
1613
  if (skillsMap.has(skillKey)) {
@@ -2070,7 +2239,7 @@ function createEmptyLocalLock() {
2070
2239
  skills: {}
2071
2240
  };
2072
2241
  }
2073
- var version$1 = "1.4.10";
2242
+ var version$1 = "1.4.12";
2074
2243
  const isCancelled$1 = (value) => typeof value === "symbol";
2075
2244
  async function isSourcePrivate(source) {
2076
2245
  const ownerRepo = parseOwnerRepo(source);
@@ -2208,6 +2377,31 @@ function ensureUniversalAgents(targetAgents) {
2208
2377
  for (const ua of universalAgents) if (!result.includes(ua)) result.push(ua);
2209
2378
  return result;
2210
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
+ }
2211
2405
  function buildResultLines(results, targetAgents) {
2212
2406
  const lines = [];
2213
2407
  const { universal, symlinked: symlinkAgents } = splitAgentsByType(targetAgents);
@@ -2251,9 +2445,9 @@ async function promptForAgents(message, choices) {
2251
2445
  return selected;
2252
2446
  }
2253
2447
  async function selectAgentsInteractive(options) {
2254
- const supportsGlobalFilter = (a) => !options.global || agents[a].globalSkillsDir;
2255
- const universalAgents = getUniversalAgents().filter(supportsGlobalFilter);
2256
- 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);
2257
2451
  const universalSection = {
2258
2452
  title: "Universal (.agents/skills)",
2259
2453
  items: universalAgents.map((a) => ({
@@ -2312,8 +2506,11 @@ function reportInstallToSunIfNeeded(params) {
2312
2506
  function isBareSkillNameSource(source) {
2313
2507
  return /^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/.test(source);
2314
2508
  }
2509
+ function isFilesystemPathLike(source) {
2510
+ return isAbsolute(source) || source === "~" || source.startsWith("~/") || source.startsWith("~\\") || source.startsWith("./") || source.startsWith("../") || source === "." || source === ".." || /^[a-zA-Z]:[/\\]/.test(source);
2511
+ }
2315
2512
  function shouldProbeDefaultWellKnownPath(source) {
2316
- return source.includes("/") && !source.includes("@") && !source.startsWith("http://") && !source.startsWith("https://") && !source.startsWith("git@") && !source.startsWith("ssh://") && !source.startsWith("./") && !source.startsWith("../") && source !== "." && source !== "..";
2513
+ return source.includes("/") && !source.includes("@") && !source.startsWith("http://") && !source.startsWith("https://") && !source.startsWith("git@") && !source.startsWith("ssh://") && !isFilesystemPathLike(source);
2317
2514
  }
2318
2515
  function buildFallbackGitSshUrl(source) {
2319
2516
  return `git@${DEFAULT_GIT_FALLBACK_SSH_HOST}:${source.replace(/\.git$/, "")}.git`;
@@ -2441,7 +2638,7 @@ async function handleWellKnownSkills(source, url, options, spinner, allowFallbac
2441
2638
  }
2442
2639
  }
2443
2640
  let installGlobally = options.global ?? false;
2444
- const supportsGlobal = targetAgents.some((a) => agents[a].globalSkillsDir !== void 0);
2641
+ const supportsGlobal = targetAgents.some(agentSupportsGlobalInstall);
2445
2642
  if (options.global === void 0 && !options.yes && supportsGlobal) {
2446
2643
  const scope = await ve({
2447
2644
  message: "Installation scope",
@@ -2461,6 +2658,7 @@ async function handleWellKnownSkills(source, url, options, spinner, allowFallbac
2461
2658
  }
2462
2659
  installGlobally = scope;
2463
2660
  }
2661
+ targetAgents = filterProjectUnsupportedAgents(targetAgents, installGlobally, options.agent);
2464
2662
  let installMode = options.copy ? "copy" : "symlink";
2465
2663
  if (!options.copy && !options.yes) {
2466
2664
  const modeChoice = await ve({
@@ -2900,7 +3098,7 @@ async function runAdd(args, options = {}) {
2900
3098
  }
2901
3099
  }
2902
3100
  let installGlobally = options.global ?? false;
2903
- const supportsGlobal = targetAgents.some((a) => agents[a].globalSkillsDir !== void 0);
3101
+ const supportsGlobal = targetAgents.some(agentSupportsGlobalInstall);
2904
3102
  if (options.global === void 0 && !options.yes && supportsGlobal) {
2905
3103
  const scope = await ve({
2906
3104
  message: "Installation scope",
@@ -2921,6 +3119,7 @@ async function runAdd(args, options = {}) {
2921
3119
  }
2922
3120
  installGlobally = scope;
2923
3121
  }
3122
+ targetAgents = filterProjectUnsupportedAgents(targetAgents, installGlobally, options.agent);
2924
3123
  let installMode = options.copy ? "copy" : "symlink";
2925
3124
  if (!options.copy && !options.yes) {
2926
3125
  const modeChoice = await ve({
@@ -3611,9 +3810,10 @@ async function runSync(args, options = {}) {
3611
3810
  M.info(`${toInstall.length} skill${toInstall.length !== 1 ? "s" : ""} to install/update`);
3612
3811
  let targetAgents;
3613
3812
  const validAgents = Object.keys(agents);
3813
+ const projectAgents = validAgents.filter((agent) => agents[agent].supportsProjectInstall !== false);
3614
3814
  const universalAgents = getUniversalAgents();
3615
3815
  if (options.agent?.includes("*")) {
3616
- targetAgents = validAgents;
3816
+ targetAgents = projectAgents;
3617
3817
  M.info(`Installing to all ${targetAgents.length} agents`);
3618
3818
  } else if (options.agent && options.agent.length > 0) {
3619
3819
  const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
@@ -3622,7 +3822,11 @@ async function runSync(args, options = {}) {
3622
3822
  M.info(`Valid agents: ${validAgents.join(", ")}`);
3623
3823
  process.exit(1);
3624
3824
  }
3625
- 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
+ });
3626
3830
  } else {
3627
3831
  spinner.start("Loading agents...");
3628
3832
  const installedAgents = await detectInstalledAgents();
@@ -3634,7 +3838,7 @@ async function runSync(args, options = {}) {
3634
3838
  } else {
3635
3839
  const selected = await searchMultiselect({
3636
3840
  message: "Which agents do you want to install to?",
3637
- items: getNonUniversalAgents().map((a) => ({
3841
+ items: getNonUniversalAgents().filter((agent) => agents[agent].supportsProjectInstall !== false).map((a) => ({
3638
3842
  value: a,
3639
3843
  label: agents[a].displayName,
3640
3844
  hint: agents[a].skillsDir
@@ -3655,12 +3859,12 @@ async function runSync(args, options = {}) {
3655
3859
  targetAgents = selected;
3656
3860
  }
3657
3861
  else if (installedAgents.length === 1 || options.yes) {
3658
- targetAgents = [...installedAgents];
3862
+ targetAgents = installedAgents.filter((agent) => agents[agent].supportsProjectInstall !== false);
3659
3863
  for (const ua of universalAgents) if (!targetAgents.includes(ua)) targetAgents.push(ua);
3660
3864
  } else {
3661
3865
  const selected = await searchMultiselect({
3662
3866
  message: "Which agents do you want to install to?",
3663
- items: getNonUniversalAgents().filter((a) => installedAgents.includes(a)).map((a) => ({
3867
+ items: getNonUniversalAgents().filter((a) => installedAgents.includes(a) && agents[a].supportsProjectInstall !== false).map((a) => ({
3664
3868
  value: a,
3665
3869
  label: agents[a].displayName,
3666
3870
  hint: agents[a].skillsDir
@@ -3941,10 +4145,13 @@ async function removeCommand(skillNames, options) {
3941
4145
  };
3942
4146
  if (isGlobal) {
3943
4147
  await scanDir(getCanonicalSkillsDir(true, cwd));
3944
- 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
+ }
3945
4152
  } else {
3946
4153
  await scanDir(getCanonicalSkillsDir(false, cwd));
3947
- 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));
3948
4155
  }
3949
4156
  const installedSkills = Array.from(skillNamesSet).sort();
3950
4157
  spinner.stop(`Found ${installedSkills.length} unique installed skill(s)`);
@@ -4011,14 +4218,16 @@ async function removeCommand(skillNames, options) {
4011
4218
  });
4012
4219
  for (const agentKey of targetAgents) {
4013
4220
  const agent = agents[agentKey];
4014
- const skillPath = getInstallPath(skillName, agentKey, {
4221
+ const skillPaths = await getInstallPaths(skillName, agentKey, {
4015
4222
  global: isGlobal,
4016
4223
  cwd
4017
4224
  });
4018
- const pathsToCleanup = new Set([skillPath]);
4225
+ const pathsToCleanup = new Set(skillPaths);
4019
4226
  const sanitizedName = sanitizeName(skillName);
4020
- if (isGlobal && agent.globalSkillsDir) pathsToCleanup.add(join(agent.globalSkillsDir, sanitizedName));
4021
- 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));
4022
4231
  for (const pathToCleanup of pathsToCleanup) {
4023
4232
  if (pathToCleanup === canonicalPath) continue;
4024
4233
  try {
@@ -4033,12 +4242,16 @@ async function removeCommand(skillNames, options) {
4033
4242
  }
4034
4243
  const remainingAgents = (await detectInstalledAgents()).filter((a) => !targetAgents.includes(a));
4035
4244
  let isStillUsed = false;
4036
- for (const agentKey of remainingAgents) if (await lstat(getInstallPath(skillName, agentKey, {
4037
- global: isGlobal,
4038
- cwd
4039
- })).catch(() => null)) {
4040
- isStillUsed = true;
4041
- 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;
4042
4255
  }
4043
4256
  if (!isStillUsed) await rm(canonicalPath, {
4044
4257
  recursive: true,
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "bip-skills",
3
- "version": "1.4.10",
3
+ "version": "1.4.12",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
7
- "bip-skills": "./bin/cli.mjs",
8
- "skills": "./bin/cli.mjs",
9
- "add-skill": "./bin/cli.mjs"
7
+ "bip-skills": "bin/cli.mjs",
8
+ "skills": "bin/cli.mjs",
9
+ "add-skill": "bin/cli.mjs"
10
10
  },
11
11
  "files": [
12
12
  "dist",
@@ -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
+ }