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.
- package/dist/cli.mjs +315 -110
- 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.
|
|
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
|
|
1164
|
-
const
|
|
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,
|
|
1319
|
+
for (const [index, agentBase] of agentBases.entries()) if (!isPathSafe(agentBase, agentDirs[index])) return {
|
|
1173
1320
|
success: false,
|
|
1174
|
-
path:
|
|
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
|
|
1181
|
-
|
|
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:
|
|
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
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
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:
|
|
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.
|
|
1247
|
-
|
|
1248
|
-
const
|
|
1249
|
-
if (
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
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
|
|
1258
|
-
agents[agentType];
|
|
1259
|
-
options.cwd || process.cwd();
|
|
1405
|
+
async function getInstallPaths(skillName, agentType, options = {}) {
|
|
1260
1406
|
const sanitized = sanitizeName(skillName);
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
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.
|
|
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
|
|
1288
|
-
const
|
|
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,
|
|
1455
|
+
for (const [index, agentBase] of agentBases.entries()) if (!isPathSafe(agentBase, agentDirs[index])) return {
|
|
1296
1456
|
success: false,
|
|
1297
|
-
path:
|
|
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
|
|
1313
|
-
|
|
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:
|
|
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
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
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:
|
|
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
|
-
|
|
1371
|
-
if (isGlobal &&
|
|
1372
|
-
const
|
|
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
|
-
|
|
1413
|
-
if (scope.global &&
|
|
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
|
|
1422
|
-
const
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
await access(agentSkillDir);
|
|
1426
|
-
found = true;
|
|
1427
|
-
break;
|
|
1428
|
-
} catch {}
|
|
1429
|
-
}
|
|
1430
|
-
if (!found) try {
|
|
1431
|
-
const agentEntries = await readdir(agentBase, { withFileTypes: true });
|
|
1432
|
-
for (const agentEntry of agentEntries) {
|
|
1433
|
-
if (!agentEntry.isDirectory()) continue;
|
|
1434
|
-
const candidateDir = join(agentBase, agentEntry.name);
|
|
1435
|
-
if (!isPathSafe(agentBase, candidateDir)) continue;
|
|
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
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
2260
|
-
const universalAgents = getUniversalAgents().filter(
|
|
2261
|
-
const otherAgents = getNonUniversalAgents().filter(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 = [
|
|
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
|
|
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
|
|
4221
|
+
const skillPaths = await getInstallPaths(skillName, agentKey, {
|
|
4023
4222
|
global: isGlobal,
|
|
4024
4223
|
cwd
|
|
4025
4224
|
});
|
|
4026
|
-
const pathsToCleanup = new Set(
|
|
4225
|
+
const pathsToCleanup = new Set(skillPaths);
|
|
4027
4226
|
const sanitizedName = sanitizeName(skillName);
|
|
4028
|
-
if (isGlobal
|
|
4029
|
-
|
|
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)
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
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.
|
|
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
|
-
"
|
|
99
|
-
|
|
100
|
-
"generate-licenses": "node scripts/generate-licenses.ts",
|
|
101
|
-
"dev": "node src/cli.ts",
|
|
102
|
-
"exec:test": "node scripts/execute-tests.ts",
|
|
103
|
-
"pack:check": "npm pack --dry-run --ignore-scripts --cache .npm-cache",
|
|
104
|
-
"format": "prettier --write 'src/**/*.ts' 'scripts/**/*.ts'",
|
|
105
|
-
"format:check": "prettier --check 'src/**/*.ts' 'scripts/**/*.ts'",
|
|
106
|
-
"test": "vitest",
|
|
107
|
-
"type-check": "tsc --noEmit",
|
|
108
|
-
"publish:snapshot": "npm version prerelease --preid=snapshot --no-git-tag-version && npm publish --tag snapshot"
|
|
109
|
-
}
|
|
110
|
-
}
|
|
114
|
+
"packageManager": "pnpm@10.17.1"
|
|
115
|
+
}
|