ali-skills 0.0.24 → 0.0.26
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.
|
@@ -916,29 +916,32 @@ const agents = {
|
|
|
916
916
|
return existsSync(claudeHome);
|
|
917
917
|
}
|
|
918
918
|
},
|
|
919
|
+
codex: {
|
|
920
|
+
name: "codex",
|
|
921
|
+
displayName: "Codex",
|
|
922
|
+
skillsDir: ".codex/skills",
|
|
923
|
+
globalSkillsDir: join(codexHome, "skills"),
|
|
924
|
+
supportsSharedAgentsDir: true,
|
|
925
|
+
detectInstalled: async () => {
|
|
926
|
+
return existsSync(codexHome) || existsSync("/etc/codex");
|
|
927
|
+
}
|
|
928
|
+
},
|
|
919
929
|
cursor: {
|
|
920
930
|
name: "cursor",
|
|
921
931
|
displayName: "Cursor",
|
|
922
|
-
skillsDir: ".
|
|
932
|
+
skillsDir: ".cursor/skills",
|
|
923
933
|
globalSkillsDir: join(home, ".cursor/skills"),
|
|
934
|
+
supportsSharedAgentsDir: true,
|
|
924
935
|
detectInstalled: async () => {
|
|
925
936
|
return existsSync(join(home, ".cursor"));
|
|
926
937
|
}
|
|
927
938
|
},
|
|
928
|
-
"aone-copilot": {
|
|
929
|
-
name: "aone-copilot",
|
|
930
|
-
displayName: "Aone Copilot",
|
|
931
|
-
skillsDir: ".agents/skills",
|
|
932
|
-
globalSkillsDir: join(configHome, "agents/skills"),
|
|
933
|
-
detectInstalled: async () => {
|
|
934
|
-
return existsSync(join(home, ".aone-copilot"));
|
|
935
|
-
}
|
|
936
|
-
},
|
|
937
939
|
qoder: {
|
|
938
940
|
name: "qoder",
|
|
939
941
|
displayName: "Qoder",
|
|
940
|
-
skillsDir: ".
|
|
941
|
-
globalSkillsDir: join(home, ".
|
|
942
|
+
skillsDir: ".qoder/skills",
|
|
943
|
+
globalSkillsDir: join(home, ".qoder/skills"),
|
|
944
|
+
supportsSharedAgentsDir: true,
|
|
942
945
|
detectInstalled: async () => {
|
|
943
946
|
return existsSync(join(home, ".qoder"));
|
|
944
947
|
}
|
|
@@ -952,94 +955,90 @@ const agents = {
|
|
|
952
955
|
return existsSync(join(home, ".qoderwork"));
|
|
953
956
|
}
|
|
954
957
|
},
|
|
955
|
-
|
|
956
|
-
name: "
|
|
957
|
-
displayName: "
|
|
958
|
-
skillsDir: "
|
|
959
|
-
globalSkillsDir:
|
|
958
|
+
openclaw: {
|
|
959
|
+
name: "openclaw",
|
|
960
|
+
displayName: "OpenClaw",
|
|
961
|
+
skillsDir: "skills",
|
|
962
|
+
globalSkillsDir: getOpenClawGlobalSkillsDir(),
|
|
960
963
|
detectInstalled: async () => {
|
|
961
|
-
return existsSync(join(home, ".
|
|
964
|
+
return existsSync(join(home, ".openclaw")) || existsSync(join(home, ".clawdbot")) || existsSync(join(home, ".moltbot"));
|
|
962
965
|
}
|
|
963
966
|
},
|
|
964
|
-
"
|
|
965
|
-
name: "
|
|
966
|
-
displayName: "
|
|
967
|
-
skillsDir: ".
|
|
968
|
-
globalSkillsDir: join(home, ".
|
|
967
|
+
"aone-copilot": {
|
|
968
|
+
name: "aone-copilot",
|
|
969
|
+
displayName: "Aone Copilot",
|
|
970
|
+
skillsDir: ".aone_copilot/skills",
|
|
971
|
+
globalSkillsDir: join(home, ".aone_copilot/skills"),
|
|
972
|
+
supportsSharedAgentsDir: true,
|
|
969
973
|
detectInstalled: async () => {
|
|
970
|
-
return existsSync(join(home, ".
|
|
974
|
+
return existsSync(join(home, ".aone-copilot"));
|
|
971
975
|
}
|
|
972
976
|
},
|
|
973
977
|
"github-copilot": {
|
|
974
978
|
name: "github-copilot",
|
|
975
979
|
displayName: "GitHub Copilot",
|
|
976
|
-
skillsDir: ".
|
|
980
|
+
skillsDir: ".github/skills",
|
|
977
981
|
globalSkillsDir: join(home, ".copilot/skills"),
|
|
982
|
+
supportsSharedAgentsDir: true,
|
|
978
983
|
detectInstalled: async () => {
|
|
979
984
|
return existsSync(join(home, ".copilot"));
|
|
980
985
|
}
|
|
981
986
|
},
|
|
982
|
-
codex: {
|
|
983
|
-
name: "codex",
|
|
984
|
-
displayName: "Codex",
|
|
985
|
-
skillsDir: ".agents/skills",
|
|
986
|
-
globalSkillsDir: join(codexHome, "skills"),
|
|
987
|
-
detectInstalled: async () => {
|
|
988
|
-
return existsSync(codexHome) || existsSync("/etc/codex");
|
|
989
|
-
}
|
|
990
|
-
},
|
|
991
987
|
opencode: {
|
|
992
988
|
name: "opencode",
|
|
993
989
|
displayName: "OpenCode",
|
|
994
|
-
skillsDir: ".
|
|
990
|
+
skillsDir: ".opencode/skills",
|
|
995
991
|
globalSkillsDir: join(configHome, "opencode/skills"),
|
|
992
|
+
supportsSharedAgentsDir: true,
|
|
996
993
|
detectInstalled: async () => {
|
|
997
994
|
return existsSync(join(configHome, "opencode"));
|
|
998
995
|
}
|
|
999
996
|
},
|
|
1000
|
-
openclaw: {
|
|
1001
|
-
name: "openclaw",
|
|
1002
|
-
displayName: "OpenClaw",
|
|
1003
|
-
skillsDir: "skills",
|
|
1004
|
-
globalSkillsDir: getOpenClawGlobalSkillsDir(),
|
|
1005
|
-
detectInstalled: async () => {
|
|
1006
|
-
return existsSync(join(home, ".openclaw")) || existsSync(join(home, ".clawdbot")) || existsSync(join(home, ".moltbot"));
|
|
1007
|
-
}
|
|
1008
|
-
},
|
|
1009
997
|
"gemini-cli": {
|
|
1010
998
|
name: "gemini-cli",
|
|
1011
999
|
displayName: "Gemini CLI",
|
|
1012
|
-
skillsDir: ".
|
|
1000
|
+
skillsDir: ".gemini/skills",
|
|
1013
1001
|
globalSkillsDir: join(home, ".gemini/skills"),
|
|
1002
|
+
supportsSharedAgentsDir: true,
|
|
1014
1003
|
detectInstalled: async () => {
|
|
1015
1004
|
return existsSync(join(home, ".gemini"));
|
|
1016
1005
|
}
|
|
1017
1006
|
},
|
|
1018
|
-
|
|
1019
|
-
name: "
|
|
1020
|
-
displayName: "
|
|
1021
|
-
skillsDir: ".
|
|
1022
|
-
globalSkillsDir: join(home, ".
|
|
1007
|
+
"qwen-code": {
|
|
1008
|
+
name: "qwen-code",
|
|
1009
|
+
displayName: "Qwen Code",
|
|
1010
|
+
skillsDir: ".qwen/skills",
|
|
1011
|
+
globalSkillsDir: join(home, ".qwen/skills"),
|
|
1023
1012
|
detectInstalled: async () => {
|
|
1024
|
-
return existsSync(join(home, ".
|
|
1013
|
+
return existsSync(join(home, ".qwen"));
|
|
1025
1014
|
}
|
|
1026
1015
|
},
|
|
1027
|
-
|
|
1028
|
-
name: "
|
|
1029
|
-
displayName: "
|
|
1030
|
-
skillsDir: ".
|
|
1031
|
-
globalSkillsDir: join(home, ".
|
|
1016
|
+
"kimi-cli": {
|
|
1017
|
+
name: "kimi-cli",
|
|
1018
|
+
displayName: "Kimi Code CLI",
|
|
1019
|
+
skillsDir: ".kimi/skills",
|
|
1020
|
+
globalSkillsDir: join(home, ".kimi/skills"),
|
|
1021
|
+
supportsSharedAgentsDir: true,
|
|
1032
1022
|
detectInstalled: async () => {
|
|
1033
|
-
return existsSync(join(home, ".
|
|
1023
|
+
return existsSync(join(home, ".kimi"));
|
|
1034
1024
|
}
|
|
1035
1025
|
},
|
|
1036
|
-
|
|
1037
|
-
name: "
|
|
1038
|
-
displayName: "
|
|
1039
|
-
skillsDir: ".
|
|
1040
|
-
globalSkillsDir: join(home, ".
|
|
1026
|
+
"iflow-cli": {
|
|
1027
|
+
name: "iflow-cli",
|
|
1028
|
+
displayName: "iFlow CLI",
|
|
1029
|
+
skillsDir: ".iflow/skills",
|
|
1030
|
+
globalSkillsDir: join(home, ".iflow/skills"),
|
|
1041
1031
|
detectInstalled: async () => {
|
|
1042
|
-
return existsSync(join(
|
|
1032
|
+
return existsSync(join(home, ".iflow"));
|
|
1033
|
+
}
|
|
1034
|
+
},
|
|
1035
|
+
antigravity: {
|
|
1036
|
+
name: "antigravity",
|
|
1037
|
+
displayName: "Antigravity",
|
|
1038
|
+
skillsDir: ".agent/skills",
|
|
1039
|
+
globalSkillsDir: join(home, ".gemini/antigravity/skills"),
|
|
1040
|
+
detectInstalled: async () => {
|
|
1041
|
+
return existsSync(join(home, ".gemini/antigravity"));
|
|
1043
1042
|
}
|
|
1044
1043
|
},
|
|
1045
1044
|
"kiro-cli": {
|
|
@@ -1060,13 +1059,23 @@ const agents = {
|
|
|
1060
1059
|
return existsSync(join(home, ".codeium/windsurf"));
|
|
1061
1060
|
}
|
|
1062
1061
|
},
|
|
1063
|
-
|
|
1064
|
-
name: "
|
|
1065
|
-
displayName: "
|
|
1066
|
-
skillsDir: ".
|
|
1067
|
-
globalSkillsDir: join(home, ".
|
|
1062
|
+
cline: {
|
|
1063
|
+
name: "cline",
|
|
1064
|
+
displayName: "Cline",
|
|
1065
|
+
skillsDir: ".cline/skills",
|
|
1066
|
+
globalSkillsDir: join(home, ".cline", "skills"),
|
|
1067
|
+
supportsSharedAgentsDir: true,
|
|
1068
1068
|
detectInstalled: async () => {
|
|
1069
|
-
return existsSync(join(home, ".
|
|
1069
|
+
return existsSync(join(home, ".cline"));
|
|
1070
|
+
}
|
|
1071
|
+
},
|
|
1072
|
+
continue: {
|
|
1073
|
+
name: "continue",
|
|
1074
|
+
displayName: "Continue",
|
|
1075
|
+
skillsDir: ".continue/skills",
|
|
1076
|
+
globalSkillsDir: join(home, ".continue/skills"),
|
|
1077
|
+
detectInstalled: async () => {
|
|
1078
|
+
return existsSync(join(process.cwd(), ".continue")) || existsSync(join(home, ".continue"));
|
|
1070
1079
|
}
|
|
1071
1080
|
},
|
|
1072
1081
|
trae: {
|
|
@@ -1099,8 +1108,9 @@ const agents = {
|
|
|
1099
1108
|
warp: {
|
|
1100
1109
|
name: "warp",
|
|
1101
1110
|
displayName: "Warp",
|
|
1102
|
-
skillsDir: ".
|
|
1103
|
-
globalSkillsDir: join(home, ".
|
|
1111
|
+
skillsDir: ".warp/skills",
|
|
1112
|
+
globalSkillsDir: join(home, ".warp/skills"),
|
|
1113
|
+
supportsSharedAgentsDir: true,
|
|
1104
1114
|
detectInstalled: async () => {
|
|
1105
1115
|
return existsSync(join(home, ".warp"));
|
|
1106
1116
|
}
|
|
@@ -1128,6 +1138,7 @@ const agents = {
|
|
|
1128
1138
|
displayName: "Amp",
|
|
1129
1139
|
skillsDir: ".agents/skills",
|
|
1130
1140
|
globalSkillsDir: join(configHome, "agents/skills"),
|
|
1141
|
+
supportsSharedAgentsDir: true,
|
|
1131
1142
|
detectInstalled: async () => {
|
|
1132
1143
|
return existsSync(join(configHome, "amp"));
|
|
1133
1144
|
}
|
|
@@ -1272,6 +1283,7 @@ const agents = {
|
|
|
1272
1283
|
displayName: "Replit",
|
|
1273
1284
|
skillsDir: ".agents/skills",
|
|
1274
1285
|
globalSkillsDir: join(configHome, "agents/skills"),
|
|
1286
|
+
supportsSharedAgentsDir: true,
|
|
1275
1287
|
showInUniversalList: false,
|
|
1276
1288
|
detectInstalled: async () => {
|
|
1277
1289
|
return existsSync(join(process.cwd(), ".replit"));
|
|
@@ -1300,6 +1312,7 @@ const agents = {
|
|
|
1300
1312
|
displayName: "Universal",
|
|
1301
1313
|
skillsDir: ".agents/skills",
|
|
1302
1314
|
globalSkillsDir: join(configHome, "agents/skills"),
|
|
1315
|
+
supportsSharedAgentsDir: true,
|
|
1303
1316
|
showInUniversalList: false,
|
|
1304
1317
|
detectInstalled: async () => false
|
|
1305
1318
|
}
|
|
@@ -1311,18 +1324,18 @@ async function detectInstalledAgents() {
|
|
|
1311
1324
|
})))).filter((r) => r.installed).map((r) => r.type);
|
|
1312
1325
|
}
|
|
1313
1326
|
function getUniversalAgents() {
|
|
1314
|
-
return Object.entries(agents).filter(([
|
|
1327
|
+
return Object.entries(agents).filter(([, config]) => config.supportsSharedAgentsDir === true && config.showInUniversalList !== false).map(([type]) => type);
|
|
1315
1328
|
}
|
|
1316
1329
|
function getNonUniversalAgents() {
|
|
1317
|
-
return Object.entries(agents).filter(([
|
|
1330
|
+
return Object.entries(agents).filter(([, config]) => config.supportsSharedAgentsDir !== true).map(([type]) => type);
|
|
1318
1331
|
}
|
|
1319
1332
|
function isUniversalAgent(type) {
|
|
1320
|
-
return agents[type].
|
|
1333
|
+
return agents[type].supportsSharedAgentsDir === true;
|
|
1321
1334
|
}
|
|
1322
1335
|
const AGENTS_DIR$2 = ".agents";
|
|
1323
1336
|
const SKILLS_SUBDIR = "skills";
|
|
1324
1337
|
function sanitizeName(name) {
|
|
1325
|
-
return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[
|
|
1338
|
+
return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.-]+|[.-]+$/g, "").substring(0, 255) || "unnamed-skill";
|
|
1326
1339
|
}
|
|
1327
1340
|
function isPathSafe(basePath, targetPath) {
|
|
1328
1341
|
const normalizedBase = normalize(resolve(basePath));
|
|
@@ -1332,6 +1345,27 @@ function isPathSafe(basePath, targetPath) {
|
|
|
1332
1345
|
function getCanonicalSkillsDir(global, cwd) {
|
|
1333
1346
|
return join(global ? homedir() : cwd || process.cwd(), AGENTS_DIR$2, SKILLS_SUBDIR);
|
|
1334
1347
|
}
|
|
1348
|
+
function isSharedDir(path) {
|
|
1349
|
+
return path === ".agents/skills" || path === "agents/skills";
|
|
1350
|
+
}
|
|
1351
|
+
function hasPrivateSkillsDir(agentType, global) {
|
|
1352
|
+
const agent = agents[agentType];
|
|
1353
|
+
if (global) {
|
|
1354
|
+
if (!agent.globalSkillsDir) return false;
|
|
1355
|
+
return !agent.globalSkillsDir.endsWith("/.agents/skills") && !agent.globalSkillsDir.endsWith("/agents/skills");
|
|
1356
|
+
}
|
|
1357
|
+
return !isSharedDir(agent.skillsDir);
|
|
1358
|
+
}
|
|
1359
|
+
function getIsolatedStoreBase(global, cwd) {
|
|
1360
|
+
return join(global ? homedir() : cwd || process.cwd(), ".skills-store", SKILLS_SUBDIR);
|
|
1361
|
+
}
|
|
1362
|
+
function getIsolatedStorePath(skillName, options = {}) {
|
|
1363
|
+
const sanitized = sanitizeName(skillName);
|
|
1364
|
+
const storeBase = getIsolatedStoreBase(options.global ?? false, options.cwd);
|
|
1365
|
+
const storePath = join(storeBase, sanitized);
|
|
1366
|
+
if (!isPathSafe(storeBase, storePath)) throw new Error("Invalid skill name: potential path traversal detected");
|
|
1367
|
+
return storePath;
|
|
1368
|
+
}
|
|
1335
1369
|
function getAgentBaseDir(agentType, global, cwd) {
|
|
1336
1370
|
if (isUniversalAgent(agentType)) return getCanonicalSkillsDir(global, cwd);
|
|
1337
1371
|
const agent = agents[agentType];
|
|
@@ -1342,6 +1376,17 @@ function getAgentBaseDir(agentType, global, cwd) {
|
|
|
1342
1376
|
}
|
|
1343
1377
|
return join(baseDir, agent.skillsDir);
|
|
1344
1378
|
}
|
|
1379
|
+
function getAgentPrivateBaseDir(agentType, global, cwd) {
|
|
1380
|
+
const agent = agents[agentType];
|
|
1381
|
+
const baseDir = global ? homedir() : cwd || process.cwd();
|
|
1382
|
+
if (global) {
|
|
1383
|
+
if (!agent.globalSkillsDir) return getCanonicalSkillsDir(global, cwd);
|
|
1384
|
+
if (hasPrivateSkillsDir(agentType, true)) return agent.globalSkillsDir;
|
|
1385
|
+
return getCanonicalSkillsDir(global, cwd);
|
|
1386
|
+
}
|
|
1387
|
+
if (hasPrivateSkillsDir(agentType, false)) return join(baseDir, agent.skillsDir);
|
|
1388
|
+
return getCanonicalSkillsDir(global, cwd);
|
|
1389
|
+
}
|
|
1345
1390
|
function resolveSymlinkTarget(linkPath, linkTarget) {
|
|
1346
1391
|
return resolve(dirname(linkPath), linkTarget);
|
|
1347
1392
|
}
|
|
@@ -1402,9 +1447,11 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
1402
1447
|
const skillName = sanitizeName(skill.name || basename(skill.path));
|
|
1403
1448
|
const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
|
|
1404
1449
|
const canonicalDir = join(canonicalBase, skillName);
|
|
1405
|
-
const
|
|
1406
|
-
const
|
|
1450
|
+
const isolatedStoreBase = getIsolatedStoreBase(isGlobal, cwd);
|
|
1451
|
+
const isolatedStoreDir = join(isolatedStoreBase, skillName);
|
|
1407
1452
|
const installMode = options.mode ?? "symlink";
|
|
1453
|
+
const agentBase = installMode === "symlink-isolated" || installMode === "copy-isolated" ? getAgentPrivateBaseDir(agentType, isGlobal, cwd) : getAgentBaseDir(agentType, isGlobal, cwd);
|
|
1454
|
+
const agentDir = join(agentBase, skillName);
|
|
1408
1455
|
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1409
1456
|
success: false,
|
|
1410
1457
|
path: agentDir,
|
|
@@ -1417,14 +1464,58 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
1417
1464
|
mode: installMode,
|
|
1418
1465
|
error: "Invalid skill name: potential path traversal detected"
|
|
1419
1466
|
};
|
|
1467
|
+
if (!isPathSafe(isolatedStoreBase, isolatedStoreDir)) return {
|
|
1468
|
+
success: false,
|
|
1469
|
+
path: agentDir,
|
|
1470
|
+
mode: installMode,
|
|
1471
|
+
error: "Invalid skill name: potential path traversal detected"
|
|
1472
|
+
};
|
|
1420
1473
|
try {
|
|
1421
1474
|
if (installMode === "copy") {
|
|
1475
|
+
const targetDir = join(isUniversalAgent(agentType) ? getCanonicalSkillsDir(isGlobal, cwd) : getAgentBaseDir(agentType, isGlobal, cwd), skillName);
|
|
1476
|
+
const sharedFallbackUsed = isUniversalAgent(agentType) && !hasPrivateSkillsDir(agentType, isGlobal);
|
|
1477
|
+
await cleanAndCreateDirectory(targetDir);
|
|
1478
|
+
await copyDirectory(skill.path, targetDir);
|
|
1479
|
+
return {
|
|
1480
|
+
success: true,
|
|
1481
|
+
path: targetDir,
|
|
1482
|
+
mode: "copy",
|
|
1483
|
+
sharedFallbackUsed
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
if (installMode === "copy-isolated") {
|
|
1487
|
+
const sharedFallbackUsed = !hasPrivateSkillsDir(agentType, isGlobal);
|
|
1422
1488
|
await cleanAndCreateDirectory(agentDir);
|
|
1423
1489
|
await copyDirectory(skill.path, agentDir);
|
|
1424
1490
|
return {
|
|
1425
1491
|
success: true,
|
|
1426
1492
|
path: agentDir,
|
|
1427
|
-
mode: "copy"
|
|
1493
|
+
mode: "copy-isolated",
|
|
1494
|
+
sharedFallbackUsed
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
if (installMode === "symlink-isolated") {
|
|
1498
|
+
const sharedFallbackUsed = !hasPrivateSkillsDir(agentType, isGlobal);
|
|
1499
|
+
await cleanAndCreateDirectory(isolatedStoreDir);
|
|
1500
|
+
await copyDirectory(skill.path, isolatedStoreDir);
|
|
1501
|
+
if (!await createSymlink(isolatedStoreDir, agentDir)) {
|
|
1502
|
+
await cleanAndCreateDirectory(agentDir);
|
|
1503
|
+
await copyDirectory(skill.path, agentDir);
|
|
1504
|
+
return {
|
|
1505
|
+
success: true,
|
|
1506
|
+
path: agentDir,
|
|
1507
|
+
canonicalPath: isolatedStoreDir,
|
|
1508
|
+
mode: "symlink-isolated",
|
|
1509
|
+
symlinkFailed: true,
|
|
1510
|
+
sharedFallbackUsed
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
return {
|
|
1514
|
+
success: true,
|
|
1515
|
+
path: agentDir,
|
|
1516
|
+
canonicalPath: isolatedStoreDir,
|
|
1517
|
+
mode: "symlink-isolated",
|
|
1518
|
+
sharedFallbackUsed
|
|
1428
1519
|
};
|
|
1429
1520
|
}
|
|
1430
1521
|
await cleanAndCreateDirectory(canonicalDir);
|
|
@@ -1506,8 +1597,6 @@ async function isSkillInstalled(skillName, agentType, options = {}) {
|
|
|
1506
1597
|
}
|
|
1507
1598
|
}
|
|
1508
1599
|
function getInstallPath(skillName, agentType, options = {}) {
|
|
1509
|
-
agents[agentType];
|
|
1510
|
-
options.cwd || process.cwd();
|
|
1511
1600
|
const sanitized = sanitizeName(skillName);
|
|
1512
1601
|
const targetBase = getAgentBaseDir(agentType, options.global ?? false, options.cwd);
|
|
1513
1602
|
const installPath = join(targetBase, sanitized);
|
|
@@ -1535,7 +1624,9 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1535
1624
|
const skillName = sanitizeName(skill.installName);
|
|
1536
1625
|
const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
|
|
1537
1626
|
const canonicalDir = join(canonicalBase, skillName);
|
|
1538
|
-
const
|
|
1627
|
+
const isolatedStoreBase = getIsolatedStoreBase(isGlobal, cwd);
|
|
1628
|
+
const isolatedStoreDir = join(isolatedStoreBase, skillName);
|
|
1629
|
+
const agentBase = installMode === "symlink-isolated" || installMode === "copy-isolated" ? getAgentPrivateBaseDir(agentType, isGlobal, cwd) : getAgentBaseDir(agentType, isGlobal, cwd);
|
|
1539
1630
|
const agentDir = join(agentBase, skillName);
|
|
1540
1631
|
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1541
1632
|
success: false,
|
|
@@ -1549,6 +1640,12 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1549
1640
|
mode: installMode,
|
|
1550
1641
|
error: "Invalid skill name: potential path traversal detected"
|
|
1551
1642
|
};
|
|
1643
|
+
if (!isPathSafe(isolatedStoreBase, isolatedStoreDir)) return {
|
|
1644
|
+
success: false,
|
|
1645
|
+
path: agentDir,
|
|
1646
|
+
mode: installMode,
|
|
1647
|
+
error: "Invalid skill name: potential path traversal detected"
|
|
1648
|
+
};
|
|
1552
1649
|
async function writeSkillFiles(targetDir) {
|
|
1553
1650
|
for (const [filePath, content] of skill.files) {
|
|
1554
1651
|
const fullPath = join(targetDir, filePath);
|
|
@@ -1560,12 +1657,50 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1560
1657
|
}
|
|
1561
1658
|
try {
|
|
1562
1659
|
if (installMode === "copy") {
|
|
1660
|
+
const targetDir = join(isUniversalAgent(agentType) ? getCanonicalSkillsDir(isGlobal, cwd) : getAgentBaseDir(agentType, isGlobal, cwd), skillName);
|
|
1661
|
+
const sharedFallbackUsed = isUniversalAgent(agentType) && !hasPrivateSkillsDir(agentType, isGlobal);
|
|
1662
|
+
await cleanAndCreateDirectory(targetDir);
|
|
1663
|
+
await writeSkillFiles(targetDir);
|
|
1664
|
+
return {
|
|
1665
|
+
success: true,
|
|
1666
|
+
path: targetDir,
|
|
1667
|
+
mode: "copy",
|
|
1668
|
+
sharedFallbackUsed
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
if (installMode === "copy-isolated") {
|
|
1672
|
+
const sharedFallbackUsed = !hasPrivateSkillsDir(agentType, isGlobal);
|
|
1563
1673
|
await cleanAndCreateDirectory(agentDir);
|
|
1564
1674
|
await writeSkillFiles(agentDir);
|
|
1565
1675
|
return {
|
|
1566
1676
|
success: true,
|
|
1567
1677
|
path: agentDir,
|
|
1568
|
-
mode: "copy"
|
|
1678
|
+
mode: "copy-isolated",
|
|
1679
|
+
sharedFallbackUsed
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
if (installMode === "symlink-isolated") {
|
|
1683
|
+
const sharedFallbackUsed = !hasPrivateSkillsDir(agentType, isGlobal);
|
|
1684
|
+
await cleanAndCreateDirectory(isolatedStoreDir);
|
|
1685
|
+
await writeSkillFiles(isolatedStoreDir);
|
|
1686
|
+
if (!await createSymlink(isolatedStoreDir, agentDir)) {
|
|
1687
|
+
await cleanAndCreateDirectory(agentDir);
|
|
1688
|
+
await writeSkillFiles(agentDir);
|
|
1689
|
+
return {
|
|
1690
|
+
success: true,
|
|
1691
|
+
path: agentDir,
|
|
1692
|
+
canonicalPath: isolatedStoreDir,
|
|
1693
|
+
mode: "symlink-isolated",
|
|
1694
|
+
symlinkFailed: true,
|
|
1695
|
+
sharedFallbackUsed
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
return {
|
|
1699
|
+
success: true,
|
|
1700
|
+
path: agentDir,
|
|
1701
|
+
canonicalPath: isolatedStoreDir,
|
|
1702
|
+
mode: "symlink-isolated",
|
|
1703
|
+
sharedFallbackUsed
|
|
1569
1704
|
};
|
|
1570
1705
|
}
|
|
1571
1706
|
await cleanAndCreateDirectory(canonicalDir);
|
|
@@ -1631,7 +1766,7 @@ async function listInstalledSkills(options = {}) {
|
|
|
1631
1766
|
for (const scope of scopes) try {
|
|
1632
1767
|
const entries = await readdir(scope.path, { withFileTypes: true });
|
|
1633
1768
|
for (const entry of entries) {
|
|
1634
|
-
if (!entry.isDirectory()) continue;
|
|
1769
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
1635
1770
|
const skillDir = join(scope.path, entry.name);
|
|
1636
1771
|
const skillMdPath = join(skillDir, "SKILL.md");
|
|
1637
1772
|
try {
|
|
@@ -1667,7 +1802,7 @@ async function listInstalledSkills(options = {}) {
|
|
|
1667
1802
|
const possibleNames = Array.from(new Set([
|
|
1668
1803
|
entry.name,
|
|
1669
1804
|
sanitizedSkillName,
|
|
1670
|
-
skill.name.toLowerCase().replace(/\s+/g, "-").replace(/[
|
|
1805
|
+
skill.name.toLowerCase().replace(/\s+/g, "-").replace(/[/\\:\0]/g, "")
|
|
1671
1806
|
]));
|
|
1672
1807
|
for (const possibleName of possibleNames) {
|
|
1673
1808
|
const agentSkillDir = join(agentBase, possibleName);
|
|
@@ -2205,7 +2340,7 @@ function logSkillParseFailures(failures) {
|
|
|
2205
2340
|
if (f.hint) M.message(import_picocolors.default.yellow(` 提示: ${f.hint}`));
|
|
2206
2341
|
}
|
|
2207
2342
|
}
|
|
2208
|
-
var version$1 = "2.0.
|
|
2343
|
+
var version$1 = "2.0.27";
|
|
2209
2344
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
2210
2345
|
function redactSensitiveText(text) {
|
|
2211
2346
|
return text.replace(/(https?:\/\/)([^/\s:@]+)(?::([^@/\s]*))?@/gi, "$1").replace(/([?&](?:private_token|access_token|token|api_key|access_key|secret|password)=)[^&\s]+/gi, "$1***");
|
|
@@ -2272,42 +2407,54 @@ function formatList$1(items, maxShow = 5) {
|
|
|
2272
2407
|
const remaining = items.length - maxShow;
|
|
2273
2408
|
return `${shown.join(", ")} +${remaining} more`;
|
|
2274
2409
|
}
|
|
2275
|
-
function
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
universal,
|
|
2282
|
-
symlinked
|
|
2283
|
-
};
|
|
2284
|
-
}
|
|
2285
|
-
function buildAgentSummaryLines(targetAgents, installMode) {
|
|
2286
|
-
const lines = [];
|
|
2287
|
-
const { universal, symlinked } = splitAgentsByType(targetAgents);
|
|
2288
|
-
if (installMode === "symlink") {
|
|
2289
|
-
if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList$1(universal)}`);
|
|
2290
|
-
if (symlinked.length > 0) lines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList$1(symlinked)}`);
|
|
2291
|
-
} else {
|
|
2292
|
-
const allNames = targetAgents.map((a) => agents[a].displayName);
|
|
2293
|
-
lines.push(` ${import_picocolors.default.dim("copy →")} ${formatList$1(allNames)}`);
|
|
2294
|
-
}
|
|
2295
|
-
return lines;
|
|
2410
|
+
function getEntityPath(skillName, installMode, global, cwd) {
|
|
2411
|
+
if (installMode === "symlink-isolated" || installMode === "copy-isolated") return join(global ? homedir() : cwd, ".skills-store", "skills", skillName);
|
|
2412
|
+
return getCanonicalPath(skillName, {
|
|
2413
|
+
global,
|
|
2414
|
+
cwd
|
|
2415
|
+
});
|
|
2296
2416
|
}
|
|
2297
|
-
function
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2417
|
+
function getInstallMethodOptions() {
|
|
2418
|
+
return [
|
|
2419
|
+
{
|
|
2420
|
+
value: "symlink",
|
|
2421
|
+
label: "Symlink",
|
|
2422
|
+
hint: "拷贝技能到公认目录 .agents/skills【实现 agent 间共享】+ 软链到不支持公认目录的 agent 的私有目录"
|
|
2423
|
+
},
|
|
2424
|
+
{
|
|
2425
|
+
value: "symlink-isolated",
|
|
2426
|
+
label: "Symlink & Isolated",
|
|
2427
|
+
hint: "拷贝技能到隔离目录 .skills-store + 软链到各 agent 的私有目录【实现 agent 间的技能独享】"
|
|
2428
|
+
},
|
|
2429
|
+
{
|
|
2430
|
+
value: "copy",
|
|
2431
|
+
label: "Copy",
|
|
2432
|
+
hint: "拷贝技能到公认目录 .agents/skills【实现 agent 间共享】,不支持公认目录的 agent 会拷贝到其私有目录"
|
|
2433
|
+
},
|
|
2434
|
+
{
|
|
2435
|
+
value: "copy-isolated",
|
|
2436
|
+
label: "Copy & Isolated",
|
|
2437
|
+
hint: "拷贝技能到各 agent 的私有目录,不写入共享目录【实现 agent 间的技能独享】"
|
|
2438
|
+
}
|
|
2439
|
+
];
|
|
2302
2440
|
}
|
|
2303
|
-
function
|
|
2441
|
+
function buildAgentSummaryLines(targetAgents, installMode) {
|
|
2442
|
+
const allNames = targetAgents.map((a) => agents[a].displayName);
|
|
2443
|
+
return [` ${import_picocolors.default.dim(`${{
|
|
2444
|
+
symlink: "symlink(shared)",
|
|
2445
|
+
copy: "copy(shared)",
|
|
2446
|
+
"symlink-isolated": "symlink(isolated)",
|
|
2447
|
+
"copy-isolated": "copy(isolated)"
|
|
2448
|
+
}[installMode]} →`)} ${formatList$1(allNames)}`];
|
|
2449
|
+
}
|
|
2450
|
+
function buildResultLines(results) {
|
|
2304
2451
|
const lines = [];
|
|
2305
|
-
const
|
|
2306
|
-
const
|
|
2307
|
-
const
|
|
2308
|
-
if (
|
|
2309
|
-
if (
|
|
2310
|
-
if (
|
|
2452
|
+
const symlinked = results.filter((r) => !r.symlinkFailed).map((r) => r.agent);
|
|
2453
|
+
const copied = results.filter((r) => r.symlinkFailed).map((r) => r.agent);
|
|
2454
|
+
const sharedFallback = results.filter((r) => r.sharedFallbackUsed).map((r) => r.agent);
|
|
2455
|
+
if (symlinked.length > 0) lines.push(` ${import_picocolors.default.dim("symlinked:")} ${formatList$1(symlinked)}`);
|
|
2456
|
+
if (copied.length > 0) lines.push(` ${import_picocolors.default.yellow("copied:")} ${formatList$1(copied)}`);
|
|
2457
|
+
if (sharedFallback.length > 0) lines.push(` ${import_picocolors.default.yellow("shared fallback:")} ${formatList$1(sharedFallback)}`);
|
|
2311
2458
|
return lines;
|
|
2312
2459
|
}
|
|
2313
2460
|
function multiselect(opts) {
|
|
@@ -2323,57 +2470,79 @@ async function promptForAgents(message, choices) {
|
|
|
2323
2470
|
lastSelected = await getLastSelectedAgents();
|
|
2324
2471
|
} catch {}
|
|
2325
2472
|
const validAgents = choices.map((c) => c.value);
|
|
2326
|
-
const defaultValues = [
|
|
2327
|
-
"claude-code",
|
|
2328
|
-
"opencode",
|
|
2329
|
-
"codex"
|
|
2330
|
-
].filter((a) => validAgents.includes(a));
|
|
2331
2473
|
let initialValues = [];
|
|
2332
2474
|
if (lastSelected && lastSelected.length > 0) initialValues = lastSelected.filter((a) => validAgents.includes(a));
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2475
|
+
let needsReselect = true;
|
|
2476
|
+
while (needsReselect) {
|
|
2477
|
+
const selected = await searchMultiselect({
|
|
2478
|
+
message,
|
|
2479
|
+
items: choices,
|
|
2480
|
+
initialSelected: initialValues,
|
|
2481
|
+
required: true
|
|
2482
|
+
});
|
|
2483
|
+
if (isCancelled$1(selected)) return selected;
|
|
2484
|
+
if (selected.length === 0) {
|
|
2485
|
+
M.warn("请至少选择一个 agent");
|
|
2486
|
+
continue;
|
|
2487
|
+
}
|
|
2488
|
+
try {
|
|
2489
|
+
await saveSelectedAgents(selected);
|
|
2490
|
+
} catch {}
|
|
2491
|
+
needsReselect = false;
|
|
2492
|
+
return selected;
|
|
2493
|
+
}
|
|
2494
|
+
return [];
|
|
2344
2495
|
}
|
|
2345
2496
|
async function selectAgentsInteractive(options) {
|
|
2346
2497
|
const supportsGlobalFilter = (a) => !options.global || agents[a].globalSkillsDir;
|
|
2347
|
-
const
|
|
2348
|
-
const
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2498
|
+
const availableAgents = Object.keys(agents).filter(supportsGlobalFilter);
|
|
2499
|
+
const choices = availableAgents.map((a) => {
|
|
2500
|
+
const targetDir = options.global ? agents[a].globalSkillsDir : agents[a].skillsDir;
|
|
2501
|
+
const sharedSupport = agents[a].supportsSharedAgentsDir ? ", supports .agents/skills" : "";
|
|
2502
|
+
return {
|
|
2352
2503
|
value: a,
|
|
2353
|
-
label: agents[a].displayName
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
value: a,
|
|
2358
|
-
label: agents[a].displayName,
|
|
2359
|
-
hint: options.global ? agents[a].globalSkillsDir : agents[a].skillsDir
|
|
2360
|
-
}));
|
|
2504
|
+
label: agents[a].displayName,
|
|
2505
|
+
hint: `${targetDir}${sharedSupport}`
|
|
2506
|
+
};
|
|
2507
|
+
});
|
|
2361
2508
|
let lastSelected;
|
|
2362
2509
|
try {
|
|
2363
2510
|
lastSelected = await getLastSelectedAgents();
|
|
2364
2511
|
} catch {}
|
|
2365
|
-
const
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2512
|
+
const initialSelected = lastSelected ? lastSelected.filter((a) => availableAgents.includes(a)) : [];
|
|
2513
|
+
let needsReselect = true;
|
|
2514
|
+
while (needsReselect) {
|
|
2515
|
+
const selected = await searchMultiselect({
|
|
2516
|
+
message: "Which agents do you want to install to?",
|
|
2517
|
+
items: choices,
|
|
2518
|
+
initialSelected
|
|
2519
|
+
});
|
|
2520
|
+
if (isCancelled$1(selected)) return selected;
|
|
2521
|
+
if (selected.length === 0) {
|
|
2522
|
+
M.warn("请至少选择一个 agent");
|
|
2523
|
+
continue;
|
|
2524
|
+
}
|
|
2525
|
+
try {
|
|
2526
|
+
await saveSelectedAgents(selected);
|
|
2527
|
+
} catch {}
|
|
2528
|
+
needsReselect = false;
|
|
2529
|
+
return selected;
|
|
2530
|
+
}
|
|
2531
|
+
return [];
|
|
2375
2532
|
}
|
|
2376
2533
|
setVersion(version$1);
|
|
2534
|
+
function isCiEnvironment() {
|
|
2535
|
+
if (process.env.CI) return true;
|
|
2536
|
+
if (process.env.GITHUB_ACTIONS) return true;
|
|
2537
|
+
if (process.env.GITLAB_CI) return true;
|
|
2538
|
+
if (process.env.BUILDKITE) return true;
|
|
2539
|
+
if (process.env.JENKINS_URL) return true;
|
|
2540
|
+
return false;
|
|
2541
|
+
}
|
|
2542
|
+
function resolveInstallMode(options) {
|
|
2543
|
+
if (options.installMode) return options.installMode;
|
|
2544
|
+
return options.copy ? "copy" : "symlink";
|
|
2545
|
+
}
|
|
2377
2546
|
async function handleWellKnownSkills(source, url, options, spinner) {
|
|
2378
2547
|
spinner.start("Discovering skills from well-known endpoint...");
|
|
2379
2548
|
const skills = await wellKnownProvider.fetchAllSkills(url);
|
|
@@ -2447,7 +2616,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2447
2616
|
M.info(`Valid agents: ${validAgents.join(", ")}`);
|
|
2448
2617
|
process.exit(1);
|
|
2449
2618
|
}
|
|
2450
|
-
targetAgents =
|
|
2619
|
+
targetAgents = options.agent;
|
|
2451
2620
|
} else {
|
|
2452
2621
|
spinner.start("Loading agents...");
|
|
2453
2622
|
const installedAgents = await detectInstalledAgents();
|
|
@@ -2469,7 +2638,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2469
2638
|
targetAgents = selected;
|
|
2470
2639
|
}
|
|
2471
2640
|
else if (installedAgents.length === 1 || options.yes) {
|
|
2472
|
-
targetAgents =
|
|
2641
|
+
targetAgents = installedAgents;
|
|
2473
2642
|
if (installedAgents.length === 1) {
|
|
2474
2643
|
const firstAgent = installedAgents[0];
|
|
2475
2644
|
M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
|
|
@@ -2504,19 +2673,11 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2504
2673
|
}
|
|
2505
2674
|
installGlobally = scope;
|
|
2506
2675
|
}
|
|
2507
|
-
let installMode = options
|
|
2508
|
-
if (!options.copy && !options.yes) {
|
|
2676
|
+
let installMode = resolveInstallMode(options);
|
|
2677
|
+
if (!options.installMode && !options.copy && !options.yes) {
|
|
2509
2678
|
const modeChoice = await ve({
|
|
2510
2679
|
message: "Installation method",
|
|
2511
|
-
options:
|
|
2512
|
-
value: "symlink",
|
|
2513
|
-
label: "Symlink (Recommended)",
|
|
2514
|
-
hint: "Single source of truth, easy updates"
|
|
2515
|
-
}, {
|
|
2516
|
-
value: "copy",
|
|
2517
|
-
label: "Copy to all agents",
|
|
2518
|
-
hint: "Independent copies for each agent"
|
|
2519
|
-
}]
|
|
2680
|
+
options: getInstallMethodOptions()
|
|
2520
2681
|
});
|
|
2521
2682
|
if (pD(modeChoice)) {
|
|
2522
2683
|
xe("Installation cancelled");
|
|
@@ -2538,7 +2699,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2538
2699
|
}
|
|
2539
2700
|
for (const skill of selectedSkills) {
|
|
2540
2701
|
if (summaryLines.length > 0) summaryLines.push("");
|
|
2541
|
-
const shortCanonical = shortenPath$2(
|
|
2702
|
+
const shortCanonical = shortenPath$2(getEntityPath(skill.installName, installMode, installGlobally, cwd), cwd);
|
|
2542
2703
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
2543
2704
|
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
|
|
2544
2705
|
if (skill.files.size > 1) summaryLines.push(` ${import_picocolors.default.dim("files:")} ${skill.files.size}`);
|
|
@@ -2557,7 +2718,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2557
2718
|
}
|
|
2558
2719
|
spinner.start("Installing skills...");
|
|
2559
2720
|
const results = [];
|
|
2560
|
-
const effectiveAgents = installMode === "copy" ? targetAgents.filter((a, i, arr) => arr.findIndex((b) => agents[b].skillsDir === agents[a].skillsDir) === i) : targetAgents;
|
|
2721
|
+
const effectiveAgents = installMode === "copy" || installMode === "copy-isolated" ? targetAgents.filter((a, i, arr) => arr.findIndex((b) => agents[b].skillsDir === agents[a].skillsDir) === i) : targetAgents;
|
|
2561
2722
|
for (const skill of selectedSkills) for (const agent of effectiveAgents) {
|
|
2562
2723
|
const result = await installWellKnownSkillForAgent(skill, agent, {
|
|
2563
2724
|
global: installGlobally,
|
|
@@ -2627,12 +2788,13 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2627
2788
|
bySkill.set(r.skill, skillResults);
|
|
2628
2789
|
}
|
|
2629
2790
|
const skillCount = bySkill.size;
|
|
2630
|
-
const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
|
|
2791
|
+
const symlinkFailures = successful.filter((r) => (r.mode === "symlink" || r.mode === "symlink-isolated") && r.symlinkFailed);
|
|
2631
2792
|
const copiedAgents = symlinkFailures.map((r) => r.agent);
|
|
2793
|
+
const sharedFallbackAgents = successful.filter((r) => r.sharedFallbackUsed).map((r) => r.agent);
|
|
2632
2794
|
const resultLines = [];
|
|
2633
2795
|
for (const [skillName, skillResults] of bySkill) {
|
|
2634
2796
|
const firstResult = skillResults[0];
|
|
2635
|
-
if (firstResult.mode === "copy") {
|
|
2797
|
+
if (firstResult.mode === "copy" || firstResult.mode === "copy-isolated") {
|
|
2636
2798
|
resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim("(copied)")}`);
|
|
2637
2799
|
for (const r of skillResults) {
|
|
2638
2800
|
const shortPath = shortenPath$2(r.path, cwd);
|
|
@@ -2643,7 +2805,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2643
2805
|
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
2644
2806
|
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
2645
2807
|
} else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
|
|
2646
|
-
resultLines.push(...buildResultLines(skillResults
|
|
2808
|
+
resultLines.push(...buildResultLines(skillResults));
|
|
2647
2809
|
}
|
|
2648
2810
|
}
|
|
2649
2811
|
const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
|
|
@@ -2652,6 +2814,10 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2652
2814
|
M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList$1(copiedAgents)}`));
|
|
2653
2815
|
M.message(import_picocolors.default.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
|
|
2654
2816
|
}
|
|
2817
|
+
if (sharedFallbackAgents.length > 0) {
|
|
2818
|
+
M.warn(import_picocolors.default.bgYellow(import_picocolors.default.black(" FALLBACK ")) + " " + import_picocolors.default.yellow(`No private skills dir for: ${formatList$1(sharedFallbackAgents)}`));
|
|
2819
|
+
M.message(import_picocolors.default.dim(" Installed to shared .agents/skills for compatibility"));
|
|
2820
|
+
}
|
|
2655
2821
|
}
|
|
2656
2822
|
if (failed.length > 0) {
|
|
2657
2823
|
console.log();
|
|
@@ -2665,6 +2831,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2665
2831
|
async function runAdd(args, options = {}) {
|
|
2666
2832
|
const source = args[0];
|
|
2667
2833
|
let installTipShown = false;
|
|
2834
|
+
const ciMode = options.ci ?? isCiEnvironment();
|
|
2668
2835
|
const showInstallTip = () => {
|
|
2669
2836
|
if (installTipShown) return;
|
|
2670
2837
|
M.message(import_picocolors.default.dim("Tip: use the --yes (-y) and --global (-g) flags to install without prompts."));
|
|
@@ -2682,6 +2849,7 @@ async function runAdd(args, options = {}) {
|
|
|
2682
2849
|
console.log();
|
|
2683
2850
|
process.exit(1);
|
|
2684
2851
|
}
|
|
2852
|
+
if (ciMode && !options.yes) options.yes = true;
|
|
2685
2853
|
if (options.all) {
|
|
2686
2854
|
options.skill = ["*"];
|
|
2687
2855
|
options.agent = ["*"];
|
|
@@ -2947,7 +3115,7 @@ async function runAdd(args, options = {}) {
|
|
|
2947
3115
|
await cleanup(tempDir);
|
|
2948
3116
|
process.exit(1);
|
|
2949
3117
|
}
|
|
2950
|
-
targetAgents =
|
|
3118
|
+
targetAgents = options.agent;
|
|
2951
3119
|
} else {
|
|
2952
3120
|
spinner.start("Loading agents...");
|
|
2953
3121
|
const installedAgents = await detectInstalledAgents();
|
|
@@ -2970,7 +3138,7 @@ async function runAdd(args, options = {}) {
|
|
|
2970
3138
|
targetAgents = selected;
|
|
2971
3139
|
}
|
|
2972
3140
|
else if (installedAgents.length === 1 || options.yes) {
|
|
2973
|
-
targetAgents =
|
|
3141
|
+
targetAgents = installedAgents;
|
|
2974
3142
|
if (installedAgents.length === 1) {
|
|
2975
3143
|
const firstAgent = installedAgents[0];
|
|
2976
3144
|
M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
|
|
@@ -3007,19 +3175,11 @@ async function runAdd(args, options = {}) {
|
|
|
3007
3175
|
}
|
|
3008
3176
|
installGlobally = scope;
|
|
3009
3177
|
}
|
|
3010
|
-
let installMode = options
|
|
3011
|
-
if (!options.copy && !options.yes) {
|
|
3178
|
+
let installMode = resolveInstallMode(options);
|
|
3179
|
+
if (!options.installMode && !options.copy && !options.yes) {
|
|
3012
3180
|
const modeChoice = await ve({
|
|
3013
3181
|
message: "Installation method",
|
|
3014
|
-
options:
|
|
3015
|
-
value: "symlink",
|
|
3016
|
-
label: "Symlink (Recommended)",
|
|
3017
|
-
hint: "Single source of truth, easy updates"
|
|
3018
|
-
}, {
|
|
3019
|
-
value: "copy",
|
|
3020
|
-
label: "Copy to all agents",
|
|
3021
|
-
hint: "Independent copies for each agent"
|
|
3022
|
-
}]
|
|
3182
|
+
options: getInstallMethodOptions()
|
|
3023
3183
|
});
|
|
3024
3184
|
if (pD(modeChoice)) {
|
|
3025
3185
|
xe("Installation cancelled");
|
|
@@ -3050,7 +3210,7 @@ async function runAdd(args, options = {}) {
|
|
|
3050
3210
|
const printSkillSummary = (skills) => {
|
|
3051
3211
|
for (const skill of skills) {
|
|
3052
3212
|
if (summaryLines.length > 0) summaryLines.push("");
|
|
3053
|
-
const shortCanonical = shortenPath$2(
|
|
3213
|
+
const shortCanonical = shortenPath$2(getEntityPath(skill.name, installMode, installGlobally, cwd), cwd);
|
|
3054
3214
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
3055
3215
|
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
|
|
3056
3216
|
const skillOverwrites = overwriteStatus.get(skill.name);
|
|
@@ -3094,7 +3254,7 @@ async function runAdd(args, options = {}) {
|
|
|
3094
3254
|
}
|
|
3095
3255
|
spinner.start("Installing skills...");
|
|
3096
3256
|
const results = [];
|
|
3097
|
-
const effectiveAgents = installMode === "copy" ? targetAgents.filter((a, i, arr) => arr.findIndex((b) => agents[b].skillsDir === agents[a].skillsDir) === i) : targetAgents;
|
|
3257
|
+
const effectiveAgents = installMode === "copy" || installMode === "copy-isolated" ? targetAgents.filter((a, i, arr) => arr.findIndex((b) => agents[b].skillsDir === agents[a].skillsDir) === i) : targetAgents;
|
|
3098
3258
|
for (const skill of selectedSkills) for (const agent of effectiveAgents) {
|
|
3099
3259
|
const result = await installSkillForAgent(skill, agent, {
|
|
3100
3260
|
global: installGlobally,
|
|
@@ -3264,14 +3424,15 @@ async function runAdd(args, options = {}) {
|
|
|
3264
3424
|
} else ungroupedResults.push(r);
|
|
3265
3425
|
}
|
|
3266
3426
|
const skillCount = bySkill.size;
|
|
3267
|
-
const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
|
|
3427
|
+
const symlinkFailures = successful.filter((r) => (r.mode === "symlink" || r.mode === "symlink-isolated") && r.symlinkFailed);
|
|
3268
3428
|
const copiedAgents = symlinkFailures.map((r) => r.agent);
|
|
3429
|
+
const sharedFallbackAgents = successful.filter((r) => r.sharedFallbackUsed).map((r) => r.agent);
|
|
3269
3430
|
const resultLines = [];
|
|
3270
3431
|
const printSkillResults = (entries) => {
|
|
3271
3432
|
for (const entry of entries) {
|
|
3272
3433
|
const skillResults = bySkill.get(entry.skill) || [];
|
|
3273
3434
|
const firstResult = skillResults[0];
|
|
3274
|
-
if (firstResult.mode === "copy") {
|
|
3435
|
+
if (firstResult.mode === "copy" || firstResult.mode === "copy-isolated") {
|
|
3275
3436
|
resultLines.push(`${import_picocolors.default.green("✓")} ${entry.skill} ${import_picocolors.default.dim("(copied)")}`);
|
|
3276
3437
|
for (const r of skillResults) {
|
|
3277
3438
|
const shortPath = shortenPath$2(r.path, cwd);
|
|
@@ -3282,7 +3443,7 @@ async function runAdd(args, options = {}) {
|
|
|
3282
3443
|
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
3283
3444
|
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
3284
3445
|
} else resultLines.push(`${import_picocolors.default.green("✓")} ${entry.skill}`);
|
|
3285
|
-
resultLines.push(...buildResultLines(skillResults
|
|
3446
|
+
resultLines.push(...buildResultLines(skillResults));
|
|
3286
3447
|
}
|
|
3287
3448
|
}
|
|
3288
3449
|
};
|
|
@@ -3306,6 +3467,10 @@ async function runAdd(args, options = {}) {
|
|
|
3306
3467
|
M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList$1(copiedAgents)}`));
|
|
3307
3468
|
M.message(import_picocolors.default.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
|
|
3308
3469
|
}
|
|
3470
|
+
if (sharedFallbackAgents.length > 0) {
|
|
3471
|
+
M.warn(import_picocolors.default.bgYellow(import_picocolors.default.black(" FALLBACK ")) + " " + import_picocolors.default.yellow(`No private skills dir for: ${formatList$1(sharedFallbackAgents)}`));
|
|
3472
|
+
M.message(import_picocolors.default.dim(" Installed to shared .agents/skills for compatibility"));
|
|
3473
|
+
}
|
|
3309
3474
|
}
|
|
3310
3475
|
if (failed.length > 0) {
|
|
3311
3476
|
console.log();
|
|
@@ -3347,6 +3512,12 @@ async function promptForFindSkills(options, targetAgents) {
|
|
|
3347
3512
|
function parseAddOptions(args) {
|
|
3348
3513
|
const options = {};
|
|
3349
3514
|
const source = [];
|
|
3515
|
+
const installModes = [
|
|
3516
|
+
"symlink",
|
|
3517
|
+
"copy",
|
|
3518
|
+
"symlink-isolated",
|
|
3519
|
+
"copy-isolated"
|
|
3520
|
+
];
|
|
3350
3521
|
for (let i = 0; i < args.length; i++) {
|
|
3351
3522
|
const arg = args[i];
|
|
3352
3523
|
if (arg === "-g" || arg === "--global") options.global = true;
|
|
@@ -3380,7 +3551,13 @@ function parseAddOptions(args) {
|
|
|
3380
3551
|
options.branch = args[i];
|
|
3381
3552
|
} else if (arg === "--full-depth") options.fullDepth = true;
|
|
3382
3553
|
else if (arg === "--copy") options.copy = true;
|
|
3383
|
-
else if (arg
|
|
3554
|
+
else if (arg === "--ci") options.ci = true;
|
|
3555
|
+
else if (arg === "--install-mode") {
|
|
3556
|
+
i++;
|
|
3557
|
+
const mode = args[i];
|
|
3558
|
+
if (!mode || !installModes.includes(mode)) throw new Error(`Invalid value for --install-mode: ${mode ?? "(empty)"} (expected: symlink, copy, symlink-isolated, copy-isolated)`);
|
|
3559
|
+
options.installMode = mode;
|
|
3560
|
+
} else if (arg && !arg.startsWith("-")) source.push(arg);
|
|
3384
3561
|
}
|
|
3385
3562
|
return {
|
|
3386
3563
|
source,
|
|
@@ -4140,6 +4317,10 @@ async function removeCommand(skillNames, options) {
|
|
|
4140
4317
|
global: isGlobal,
|
|
4141
4318
|
cwd
|
|
4142
4319
|
});
|
|
4320
|
+
const isolatedStorePath = getIsolatedStorePath(skillName, {
|
|
4321
|
+
global: isGlobal,
|
|
4322
|
+
cwd
|
|
4323
|
+
});
|
|
4143
4324
|
for (const agentKey of targetAgents) {
|
|
4144
4325
|
const agent = agents[agentKey];
|
|
4145
4326
|
const skillPath = getInstallPath(skillName, agentKey, {
|
|
@@ -4171,10 +4352,16 @@ async function removeCommand(skillNames, options) {
|
|
|
4171
4352
|
isStillUsed = true;
|
|
4172
4353
|
break;
|
|
4173
4354
|
}
|
|
4174
|
-
if (!isStillUsed)
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4355
|
+
if (!isStillUsed) {
|
|
4356
|
+
await rm(canonicalPath, {
|
|
4357
|
+
recursive: true,
|
|
4358
|
+
force: true
|
|
4359
|
+
});
|
|
4360
|
+
await rm(isolatedStorePath, {
|
|
4361
|
+
recursive: true,
|
|
4362
|
+
force: true
|
|
4363
|
+
});
|
|
4364
|
+
}
|
|
4178
4365
|
const lockEntry = isGlobal ? await getSkillFromLock(skillName) : null;
|
|
4179
4366
|
const effectiveSource = lockEntry?.source || "local";
|
|
4180
4367
|
const effectiveSourceType = lockEntry?.sourceType || "local";
|
|
@@ -4559,6 +4746,10 @@ function readConfig() {
|
|
|
4559
4746
|
function saveConfig(config) {
|
|
4560
4747
|
safeWriteFile(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
4561
4748
|
}
|
|
4749
|
+
function getSsoRefreshCookieTtlMs() {
|
|
4750
|
+
const sec = Number(process.env.ALI_SKILLS_SSO_REFRESH_COOKIE_TTL_SECONDS?.trim() || process.env.BUC_SSO_REFRESH_COOKIE_TTL_SECONDS?.trim() || "7200");
|
|
4751
|
+
return (Number.isFinite(sec) && sec > 0 ? sec : 7200) * 1e3;
|
|
4752
|
+
}
|
|
4562
4753
|
function readAuth() {
|
|
4563
4754
|
try {
|
|
4564
4755
|
if (!existsSync(AUTH_FILE)) return null;
|
|
@@ -4790,6 +4981,9 @@ function startLocalServer() {
|
|
|
4790
4981
|
const name = url.searchParams.get("name") || "";
|
|
4791
4982
|
const privateToken = url.searchParams.get("private_token") || "";
|
|
4792
4983
|
const ssoRefreshToken = url.searchParams.get("sso_refresh_token") || "";
|
|
4984
|
+
const ssoRtExpRaw = url.searchParams.get("sso_refresh_token_expires_at") || "";
|
|
4985
|
+
const ssoRtExpSec = parseInt(ssoRtExpRaw, 10);
|
|
4986
|
+
const ssoRefreshTokenExpiresAtMs = ssoRefreshToken && ssoRtExpRaw !== "" && Number.isFinite(ssoRtExpSec) && ssoRtExpSec > 0 ? ssoRtExpSec * 1e3 : void 0;
|
|
4793
4987
|
const expiresAt = url.searchParams.get("expires_at") || "0";
|
|
4794
4988
|
if (!empId || !privateToken) {
|
|
4795
4989
|
res.writeHead(400, { "Content-Type": "text/html" });
|
|
@@ -4946,6 +5140,7 @@ function startLocalServer() {
|
|
|
4946
5140
|
name,
|
|
4947
5141
|
privateToken,
|
|
4948
5142
|
ssoRefreshToken,
|
|
5143
|
+
ssoRefreshTokenExpiresAtMs,
|
|
4949
5144
|
expiresAt: parseInt(expiresAt, 10)
|
|
4950
5145
|
});
|
|
4951
5146
|
setTimeout(() => server.close(), 1e3);
|
|
@@ -4971,13 +5166,51 @@ function openBrowser$2(url) {
|
|
|
4971
5166
|
stdio: "ignore"
|
|
4972
5167
|
});
|
|
4973
5168
|
}
|
|
4974
|
-
async function
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
5169
|
+
async function revokeGatewaySessionIfPossible(auth) {
|
|
5170
|
+
try {
|
|
5171
|
+
const revokeUrl = `${auth.gatewayUrl || getGatewayUrl()}/api/gateway/revoke`;
|
|
5172
|
+
const revokeRes = await fetch(revokeUrl, {
|
|
5173
|
+
method: "POST",
|
|
5174
|
+
headers: { "X-Private-Token": auth.privateToken }
|
|
5175
|
+
});
|
|
5176
|
+
if (revokeRes.ok) console.log(`${GREEN$2}✓ 已撤销当前网关会话${RESET$3}`);
|
|
5177
|
+
else if (revokeRes.status === 404) console.log(`${DIM$3}服务端暂不支持撤销网关会话,继续本机登出${RESET$3}`);
|
|
5178
|
+
else console.log(`${YELLOW$3}⚠️ 撤销网关会话失败,继续本机登出${RESET$3}`);
|
|
5179
|
+
} catch {
|
|
5180
|
+
console.log(`${YELLOW$3}⚠️ 撤销网关会话失败,继续本机登出${RESET$3}`);
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
function getEffectiveSsoRefreshCookieExpiresAtMs(auth) {
|
|
5184
|
+
if (!auth.ssoRefreshToken?.trim()) return null;
|
|
5185
|
+
if (typeof auth.ssoRefreshTokenExpiresAt === "number" && auth.ssoRefreshTokenExpiresAt > 0) return auth.ssoRefreshTokenExpiresAt;
|
|
5186
|
+
if (typeof auth.ssoRefreshTokenFetchedAt === "number" && auth.ssoRefreshTokenFetchedAt > 0) return auth.ssoRefreshTokenFetchedAt + getSsoRefreshCookieTtlMs();
|
|
5187
|
+
return null;
|
|
5188
|
+
}
|
|
5189
|
+
function exitIfSsoRefreshCookieExpired(auth, opts) {
|
|
5190
|
+
const exp = getEffectiveSsoRefreshCookieExpiresAtMs(auth);
|
|
5191
|
+
if (exp === null) return;
|
|
5192
|
+
if (Date.now() < exp) return;
|
|
5193
|
+
if (!opts?.quiet) {
|
|
5194
|
+
console.error(`${YELLOW$3}SSO_REFRESH_TOKEN 已超过预估有效期,根域 cookie 直连会鉴权失败${RESET$3}`);
|
|
5195
|
+
console.log(`${DIM$3}请执行: npx ali-skills login --reauth${RESET$3}`);
|
|
5196
|
+
}
|
|
5197
|
+
process.exit(1);
|
|
5198
|
+
}
|
|
5199
|
+
async function login(options) {
|
|
5200
|
+
if (options?.reauth) {
|
|
5201
|
+
const auth = readAuth();
|
|
5202
|
+
if (auth) {
|
|
5203
|
+
await revokeGatewaySessionIfPossible(auth);
|
|
5204
|
+
removeAuth();
|
|
5205
|
+
}
|
|
5206
|
+
} else {
|
|
5207
|
+
const existingAuth = readAuth();
|
|
5208
|
+
if (existingAuth && !isAuthExpired(existingAuth)) {
|
|
5209
|
+
console.log(`${YELLOW$3}! 您已经登录为: ${existingAuth.name} (${existingAuth.account})${RESET$3}`);
|
|
5210
|
+
console.log();
|
|
5211
|
+
console.log(`${DIM$3}如需重新登录,请执行: npx ali-skills login --reauth${RESET$3}`);
|
|
5212
|
+
return;
|
|
5213
|
+
}
|
|
4981
5214
|
}
|
|
4982
5215
|
console.log(`${TEXT$3}正在启动本地服务...${RESET$3}`);
|
|
4983
5216
|
const { port, waitForCallback, close } = await startLocalServer();
|
|
@@ -4990,7 +5223,9 @@ async function login() {
|
|
|
4990
5223
|
openBrowser$2(loginUrl);
|
|
4991
5224
|
console.log(`${TEXT$3}等待授权完成...${RESET$3}`);
|
|
4992
5225
|
try {
|
|
4993
|
-
const { empId, account, name, privateToken, ssoRefreshToken, expiresAt } = await waitForCallback();
|
|
5226
|
+
const { empId, account, name, privateToken, ssoRefreshToken, ssoRefreshTokenExpiresAtMs, expiresAt } = await waitForCallback();
|
|
5227
|
+
let ssoRtExpStored;
|
|
5228
|
+
if (ssoRefreshToken) ssoRtExpStored = ssoRefreshTokenExpiresAtMs ?? Date.now() + getSsoRefreshCookieTtlMs();
|
|
4994
5229
|
saveAuth({
|
|
4995
5230
|
empId,
|
|
4996
5231
|
account,
|
|
@@ -5000,13 +5235,19 @@ async function login() {
|
|
|
5000
5235
|
ssoRefreshToken,
|
|
5001
5236
|
ssoRefreshTokenFetchedAt: ssoRefreshToken ? Date.now() : void 0,
|
|
5002
5237
|
ssoRefreshTokenLastCheckedAt: ssoRefreshToken ? Date.now() : void 0,
|
|
5238
|
+
ssoRefreshTokenExpiresAt: ssoRtExpStored,
|
|
5003
5239
|
expiresAt: expiresAt * 1e3
|
|
5004
5240
|
});
|
|
5005
5241
|
console.log();
|
|
5006
5242
|
console.log(`${GREEN$2}✓ 登录成功!${RESET$3}`);
|
|
5007
5243
|
console.log(`${TEXT$3}欢迎: ${name} (${account})${RESET$3}`);
|
|
5008
5244
|
console.log(`${DIM$3}Token 有效期至: ${(/* @__PURE__ */ new Date(expiresAt * 1e3)).toLocaleDateString()}${RESET$3}`);
|
|
5009
|
-
if (
|
|
5245
|
+
if (ssoRefreshToken && ssoRtExpStored) console.log(`${DIM$3}SSO_REFRESH 预估过期: ${new Date(ssoRtExpStored).toLocaleString()}${RESET$3}`);
|
|
5246
|
+
if (!ssoRefreshToken) {
|
|
5247
|
+
console.log();
|
|
5248
|
+
console.log(`${YELLOW$3}⚠ SSO_REFRESH_TOKEN 获取异常:本次授权回调未携带该凭证,网关代理等依赖 SSO Cookie 的能力可能不可用${RESET$3}`);
|
|
5249
|
+
console.log(`${DIM$3}可尝试:在网页端(https://ali-skills.alibaba-inc.com)退出平台登录后重新执行 login 命令。同时请将此问题反馈给本工具开发者处理,感谢!${RESET$3}`);
|
|
5250
|
+
}
|
|
5010
5251
|
} catch (error) {
|
|
5011
5252
|
close();
|
|
5012
5253
|
throw error;
|
|
@@ -5101,18 +5342,7 @@ async function logout(platformInput, yuqueGroup, clearAll) {
|
|
|
5101
5342
|
return;
|
|
5102
5343
|
}
|
|
5103
5344
|
try {
|
|
5104
|
-
|
|
5105
|
-
const revokeUrl = `${getGatewayUrl()}/api/gateway/revoke`;
|
|
5106
|
-
const revokeRes = await fetch(revokeUrl, {
|
|
5107
|
-
method: "POST",
|
|
5108
|
-
headers: { "X-Private-Token": auth.privateToken }
|
|
5109
|
-
});
|
|
5110
|
-
if (revokeRes.ok) console.log(`${GREEN$2}✓ 已撤销当前网关会话${RESET$3}`);
|
|
5111
|
-
else if (revokeRes.status === 404) console.log(`${DIM$3}服务端暂不支持撤销网关会话,继续本机登出${RESET$3}`);
|
|
5112
|
-
else console.log(`${YELLOW$3}⚠️ 撤销网关会话失败,继续本机登出${RESET$3}`);
|
|
5113
|
-
} catch {
|
|
5114
|
-
console.log(`${YELLOW$3}⚠️ 撤销网关会话失败,继续本机登出${RESET$3}`);
|
|
5115
|
-
}
|
|
5345
|
+
await revokeGatewaySessionIfPossible(auth);
|
|
5116
5346
|
removeAuth();
|
|
5117
5347
|
console.log(`${GREEN$2}✓ 已登出${RESET$3}`);
|
|
5118
5348
|
} catch (error) {
|
|
@@ -5272,6 +5502,11 @@ async function status(args = {}) {
|
|
|
5272
5502
|
console.log(` 工号: ${auth.empId}`);
|
|
5273
5503
|
console.log(` 网关: ${getGatewayUrl()}`);
|
|
5274
5504
|
console.log(` SSO_REFRESH_TOKEN: ${auth.ssoRefreshToken ? `${GREEN$2}已缓存${RESET$3}` : `${YELLOW$3}未缓存${RESET$3}`}`);
|
|
5505
|
+
const ssoRtExp = getEffectiveSsoRefreshCookieExpiresAtMs(auth);
|
|
5506
|
+
if (auth.ssoRefreshToken && ssoRtExp) {
|
|
5507
|
+
const rtDead = Date.now() >= ssoRtExp;
|
|
5508
|
+
console.log(` SSO_REFRESH 预估过期: ${new Date(ssoRtExp).toLocaleString()}${rtDead ? ` ${YELLOW$3}(已过期)${RESET$3}` : ""}`);
|
|
5509
|
+
} else if (auth.ssoRefreshToken) console.log(` SSO_REFRESH 预估过期: ${YELLOW$3}未知${RESET$3} ${DIM$3}(可 login --reauth 重新写入)${RESET$3}`);
|
|
5275
5510
|
if (expired) {
|
|
5276
5511
|
console.log(` Token: ${YELLOW$3}即将过期${RESET$3}`);
|
|
5277
5512
|
console.log(`${TEXT$3}建议重新登录: npx ali-skills login${RESET$3}`);
|
|
@@ -5300,13 +5535,14 @@ async function ensureSsoRefreshToken() {
|
|
|
5300
5535
|
});
|
|
5301
5536
|
if (response.status === 200) {
|
|
5302
5537
|
const data = await response.json();
|
|
5538
|
+
const mergedRtExp = typeof data.ssoRefreshTokenExpiresAt === "number" && data.ssoRefreshTokenExpiresAt > 0 ? data.ssoRefreshTokenExpiresAt : Date.now() + getSsoRefreshCookieTtlMs();
|
|
5303
5539
|
saveAuth({
|
|
5304
5540
|
...auth,
|
|
5305
5541
|
gatewayUrl: auth.gatewayUrl || getGatewayUrl(),
|
|
5306
5542
|
ssoRefreshToken: data.ssoRefreshToken,
|
|
5307
5543
|
ssoRefreshTokenFetchedAt: Date.now(),
|
|
5308
5544
|
ssoRefreshTokenLastCheckedAt: Date.now(),
|
|
5309
|
-
ssoRefreshTokenExpiresAt:
|
|
5545
|
+
ssoRefreshTokenExpiresAt: mergedRtExp
|
|
5310
5546
|
});
|
|
5311
5547
|
console.log(`${DIM$3}已从服务端获取 SSO_REFRESH_TOKEN${RESET$3}`);
|
|
5312
5548
|
return data.ssoRefreshToken;
|
|
@@ -5607,6 +5843,13 @@ async function sendDirectRequest(targetUrl, platform, token, options) {
|
|
|
5607
5843
|
if (!response.ok) process.exit(1);
|
|
5608
5844
|
}
|
|
5609
5845
|
async function sendSsoCookieDirectRequest(targetUrl, options) {
|
|
5846
|
+
const printReauthAndRetryHint = (location, forceStderr = false) => {
|
|
5847
|
+
const log = forceStderr ? console.error : console.log;
|
|
5848
|
+
log(`${YELLOW$2}检测到被重定向到登录页,当前 SSO_REFRESH_TOKEN 很可能已失效${RESET$2}`);
|
|
5849
|
+
if (location) log(`${DIM$2}Location: ${location}${RESET$2}`);
|
|
5850
|
+
log(`${TEXT$2}请先执行: npx ali-skills login --reauth(刷新 SSO_REFRESH_TOKEN)${RESET$2}`);
|
|
5851
|
+
log(`${TEXT$2}刷新后请重新发起当前请求${RESET$2}`);
|
|
5852
|
+
};
|
|
5610
5853
|
let ssoRefreshToken = null;
|
|
5611
5854
|
try {
|
|
5612
5855
|
await ensureLogin();
|
|
@@ -5624,6 +5867,8 @@ async function sendSsoCookieDirectRequest(targetUrl, options) {
|
|
|
5624
5867
|
console.log(`${TEXT$2}建议执行: npx ali-skills login${RESET$2}`);
|
|
5625
5868
|
process.exit(1);
|
|
5626
5869
|
}
|
|
5870
|
+
const authForRt = readAuth();
|
|
5871
|
+
if (authForRt) exitIfSsoRefreshCookieExpired(authForRt, { quiet: options.quiet });
|
|
5627
5872
|
const requestHeaders = {
|
|
5628
5873
|
"User-Agent": "ali-skills-cli/1.0",
|
|
5629
5874
|
Cookie: `SSO_REFRESH_TOKEN=${ssoRefreshToken}`
|
|
@@ -5643,7 +5888,7 @@ async function sendSsoCookieDirectRequest(targetUrl, options) {
|
|
|
5643
5888
|
const controller = new AbortController();
|
|
5644
5889
|
const timeout = options.timeout || 3e4;
|
|
5645
5890
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
5646
|
-
const { response, responseBody } = await fetchWithRedirects(targetUrl, options.method.toUpperCase(), requestHeaders, body, timeout, options.quiet);
|
|
5891
|
+
const { response, responseBody, loginRedirectUrl } = await fetchWithRedirects(targetUrl, options.method.toUpperCase(), requestHeaders, body, timeout, options.quiet, { stopOnAlibabaLoginRedirect: true });
|
|
5647
5892
|
clearTimeout(timeoutId);
|
|
5648
5893
|
if (!options.quiet) {
|
|
5649
5894
|
const statusColor = response.ok ? GREEN$1 : RED$2;
|
|
@@ -5655,7 +5900,15 @@ async function sendSsoCookieDirectRequest(targetUrl, options) {
|
|
|
5655
5900
|
console.log(`${DIM$2} 2. 目标服务未启用该认证链路${RESET$2}`);
|
|
5656
5901
|
console.log(`${DIM$2} 3. 当前账号在目标系统无访问权限${RESET$2}`);
|
|
5657
5902
|
console.log(`${TEXT$2}建议: 重新登录后重试,或联系目标系统确认登录态策略${RESET$2}`);
|
|
5658
|
-
}
|
|
5903
|
+
} else if (loginRedirectUrl) printReauthAndRetryHint(loginRedirectUrl);
|
|
5904
|
+
}
|
|
5905
|
+
if (options.includeHeaders) {
|
|
5906
|
+
console.log();
|
|
5907
|
+
console.log(`${TEXT$2}Response Headers:${RESET$2}`);
|
|
5908
|
+
response.headers.forEach((value, key) => {
|
|
5909
|
+
console.log(` ${key}: ${value}`);
|
|
5910
|
+
});
|
|
5911
|
+
console.log();
|
|
5659
5912
|
}
|
|
5660
5913
|
let outputBody = responseBody;
|
|
5661
5914
|
if (!options.raw && options.pretty !== false) try {
|
|
@@ -5666,7 +5919,14 @@ async function sendSsoCookieDirectRequest(targetUrl, options) {
|
|
|
5666
5919
|
const { writeFileSync } = __require("fs");
|
|
5667
5920
|
writeFileSync(options.output, outputBody);
|
|
5668
5921
|
console.log(`${GREEN$1}✓ 已保存到 ${options.output}${RESET$2}`);
|
|
5669
|
-
} else
|
|
5922
|
+
} else {
|
|
5923
|
+
if (options.quiet && !response.ok && outputBody.trim().length === 0 && (loginRedirectUrl || response.headers.get("location"))) {
|
|
5924
|
+
const location = loginRedirectUrl || response.headers.get("location") || "(empty)";
|
|
5925
|
+
console.error(`HTTP ${response.status} ${response.statusText} (Location: ${location})`);
|
|
5926
|
+
printReauthAndRetryHint(location, true);
|
|
5927
|
+
}
|
|
5928
|
+
console.log(outputBody);
|
|
5929
|
+
}
|
|
5670
5930
|
if (!response.ok) process.exit(1);
|
|
5671
5931
|
}
|
|
5672
5932
|
async function sendSsoTicketDirectRequest(targetUrl, options) {
|
|
@@ -5771,7 +6031,7 @@ async function sendPlainDirectRequest(targetUrl, options, allowSsoCookieFallback
|
|
|
5771
6031
|
} else console.log(outputBody);
|
|
5772
6032
|
if (!response.ok) process.exit(1);
|
|
5773
6033
|
}
|
|
5774
|
-
async function fetchWithRedirects(initialUrl, method, headers, body, timeout, quiet) {
|
|
6034
|
+
async function fetchWithRedirects(initialUrl, method, headers, body, timeout, quiet, opts) {
|
|
5775
6035
|
const controller = new AbortController();
|
|
5776
6036
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
5777
6037
|
let currentUrl = initialUrl;
|
|
@@ -5799,7 +6059,15 @@ async function fetchWithRedirects(initialUrl, method, headers, body, timeout, qu
|
|
|
5799
6059
|
};
|
|
5800
6060
|
if (redirects >= MAX_REDIRECTS) throw new Error(`重定向次数超过上限(${MAX_REDIRECTS})`);
|
|
5801
6061
|
const nextUrl = new URL(location, currentUrl).toString();
|
|
5802
|
-
if (new URL(nextUrl).hostname === "login.alibaba-inc.com")
|
|
6062
|
+
if (new URL(nextUrl).hostname === "login.alibaba-inc.com") {
|
|
6063
|
+
sawAlibabaLoginRedirect = true;
|
|
6064
|
+
if (opts?.stopOnAlibabaLoginRedirect) return {
|
|
6065
|
+
response,
|
|
6066
|
+
responseBody: await response.text(),
|
|
6067
|
+
sawAlibabaLoginRedirect,
|
|
6068
|
+
loginRedirectUrl: nextUrl
|
|
6069
|
+
};
|
|
6070
|
+
}
|
|
5803
6071
|
redirects += 1;
|
|
5804
6072
|
if (!quiet) console.log(`${DIM$2}↪ 重定向 ${redirects}: ${response.status} ${response.statusText} -> ${nextUrl}${RESET$2}`);
|
|
5805
6073
|
currentUrl = nextUrl;
|
|
@@ -6358,9 +6626,6 @@ ${BOLD}Manage Skills:${RESET}
|
|
|
6358
6626
|
list, ls List installed skills
|
|
6359
6627
|
find [query] Search for skills interactively
|
|
6360
6628
|
|
|
6361
|
-
${BOLD}Publish:${RESET}
|
|
6362
|
-
publish [options] Publish skills from current repo to OSS (alias: pub)
|
|
6363
|
-
|
|
6364
6629
|
${BOLD}Updates:${RESET}
|
|
6365
6630
|
check Check for available skill updates (project-level by default)
|
|
6366
6631
|
check -g Check global skill updates
|
|
@@ -6371,9 +6636,7 @@ ${BOLD}Updates:${RESET}
|
|
|
6371
6636
|
update <skill> Update specific skill(s)
|
|
6372
6637
|
|
|
6373
6638
|
${BOLD}Project:${RESET}
|
|
6374
|
-
experimental_install Restore skills from skills-lock.json
|
|
6375
6639
|
init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
|
|
6376
|
-
experimental_sync Sync skills from node_modules into agent directories
|
|
6377
6640
|
|
|
6378
6641
|
${BOLD}Gateway:${RESET}
|
|
6379
6642
|
gateway 鉴权请求能力入口(login/logout/status/req)
|
|
@@ -6387,8 +6650,10 @@ ${BOLD}Add Options:${RESET}
|
|
|
6387
6650
|
-s, --skill <skills> Specify skill names to install (use '*' for all skills)
|
|
6388
6651
|
-l, --list List available skills in the repository without installing
|
|
6389
6652
|
-y, --yes Skip confirmation prompts
|
|
6653
|
+
--ci Enable CI mode (auto non-interactive, implies --yes)
|
|
6390
6654
|
--github Force fetching from GitHub when using owner/repo shorthand
|
|
6391
|
-
--
|
|
6655
|
+
--install-mode <mode> Install mode: symlink|copy|symlink-isolated|copy-isolated (default: symlink)
|
|
6656
|
+
--copy Backward-compatible alias of --install-mode copy (recommended: use --install-mode)
|
|
6392
6657
|
--all Shorthand for --skill '*' --agent '*' -y
|
|
6393
6658
|
--full-depth Search all subdirectories even when a root SKILL.md exists
|
|
6394
6659
|
|
|
@@ -6399,10 +6664,6 @@ ${BOLD}Remove Options:${RESET}
|
|
|
6399
6664
|
-y, --yes Skip confirmation prompts
|
|
6400
6665
|
--all Shorthand for --skill '*' --agent '*' -y
|
|
6401
6666
|
|
|
6402
|
-
${BOLD}Experimental Sync Options:${RESET}
|
|
6403
|
-
-a, --agent <agents> Specify agents to install to (use '*' for all agents)
|
|
6404
|
-
-y, --yes Skip confirmation prompts
|
|
6405
|
-
|
|
6406
6667
|
${BOLD}List Options:${RESET}
|
|
6407
6668
|
-g, --global List global skills (default: project)
|
|
6408
6669
|
-a, --agent <agents> Filter by specific agents
|
|
@@ -6412,14 +6673,12 @@ ${BOLD}Options:${RESET}
|
|
|
6412
6673
|
--help, -h Show this help message
|
|
6413
6674
|
--version, -v Show version number
|
|
6414
6675
|
|
|
6415
|
-
${BOLD}Debug:${RESET}
|
|
6416
|
-
ALI_SKILLS_DEBUG_UPDATE=1 Print detailed logs for check/update diagnosis
|
|
6417
|
-
|
|
6418
6676
|
${BOLD}Examples:${RESET}
|
|
6419
6677
|
${DIM}$${RESET} ali-skills add group/project
|
|
6420
6678
|
${DIM}$${RESET} ali-skills add group/project -g
|
|
6421
6679
|
${DIM}$${RESET} ali-skills add group/project --agent claude-code cursor
|
|
6422
6680
|
${DIM}$${RESET} ali-skills add group/project --skill pr-review commit
|
|
6681
|
+
${DIM}$${RESET} ali-skills add group/project --install-mode symlink-isolated
|
|
6423
6682
|
${DIM}$${RESET} ali-skills remove ${DIM}# interactive remove${RESET}
|
|
6424
6683
|
${DIM}$${RESET} ali-skills remove web-design ${DIM}# remove by name${RESET}
|
|
6425
6684
|
${DIM}$${RESET} ali-skills rm --global frontend-design
|
|
@@ -6432,10 +6691,7 @@ ${BOLD}Examples:${RESET}
|
|
|
6432
6691
|
${DIM}$${RESET} ali-skills check
|
|
6433
6692
|
${DIM}$${RESET} ali-skills check web-design react-best-practices
|
|
6434
6693
|
${DIM}$${RESET} ali-skills update
|
|
6435
|
-
${DIM}$${RESET} ali-skills experimental_install ${DIM}# restore from skills-lock.json${RESET}
|
|
6436
6694
|
${DIM}$${RESET} ali-skills init my-skill
|
|
6437
|
-
${DIM}$${RESET} ali-skills experimental_sync ${DIM}# sync from node_modules${RESET}
|
|
6438
|
-
${DIM}$${RESET} ali-skills experimental_sync -y ${DIM}# sync without prompts${RESET}
|
|
6439
6695
|
|
|
6440
6696
|
Discover more skills at ${TEXT}https://ali-skills.alibaba-inc.com/${RESET}
|
|
6441
6697
|
`);
|
|
@@ -6475,7 +6731,7 @@ function showGatewayHelp() {
|
|
|
6475
6731
|
const platformKeysForHelp = listPlatforms().filter((p) => p.key !== "aone").map((p) => p.key).join(", ");
|
|
6476
6732
|
console.log(`
|
|
6477
6733
|
${BOLD}Gateway Commands:${RESET}
|
|
6478
|
-
login [platform]
|
|
6734
|
+
login [--reauth] [platform] Login to Gateway or configure platform token; --reauth 等价于先 logout 再登录
|
|
6479
6735
|
logout [platform] Logout BUC or clear platform token
|
|
6480
6736
|
status [platform] [--all] Show login status, single platform, or all platform statuses
|
|
6481
6737
|
request [platform] <url|path> Send HTTP request (supports platform + path shorthand)
|
|
@@ -6505,6 +6761,7 @@ ${BOLD}Request Options:${RESET}
|
|
|
6505
6761
|
|
|
6506
6762
|
${BOLD}Examples:${RESET}
|
|
6507
6763
|
${DIM}$${RESET} ali-skills login
|
|
6764
|
+
${DIM}$${RESET} ali-skills login --reauth
|
|
6508
6765
|
${DIM}$${RESET} ali-skills login code
|
|
6509
6766
|
${DIM}$${RESET} ali-skills login yuque
|
|
6510
6767
|
${DIM}$${RESET} ali-skills login yuque group
|
|
@@ -6768,6 +7025,16 @@ function parseLogoutArgs(args) {
|
|
|
6768
7025
|
clearAll
|
|
6769
7026
|
};
|
|
6770
7027
|
}
|
|
7028
|
+
function stripReauthFlag(args) {
|
|
7029
|
+
let reauth = false;
|
|
7030
|
+
const rest = [];
|
|
7031
|
+
for (const a of args) if (a === "--reauth") reauth = true;
|
|
7032
|
+
else rest.push(a);
|
|
7033
|
+
return {
|
|
7034
|
+
reauth,
|
|
7035
|
+
rest
|
|
7036
|
+
};
|
|
7037
|
+
}
|
|
6771
7038
|
async function runGatewayCommand(args) {
|
|
6772
7039
|
if (args.length === 0) {
|
|
6773
7040
|
showGatewayHelp();
|
|
@@ -6776,11 +7043,14 @@ async function runGatewayCommand(args) {
|
|
|
6776
7043
|
const command = args[0];
|
|
6777
7044
|
const rest = args.slice(1);
|
|
6778
7045
|
switch (command) {
|
|
6779
|
-
case "login":
|
|
7046
|
+
case "login": {
|
|
6780
7047
|
showLogo();
|
|
6781
7048
|
console.log();
|
|
6782
|
-
|
|
7049
|
+
const { reauth, rest: loginRest } = stripReauthFlag(rest);
|
|
7050
|
+
if (loginRest.length > 0) await runPlatformLogin(loginRest);
|
|
7051
|
+
else await login({ reauth });
|
|
6783
7052
|
break;
|
|
7053
|
+
}
|
|
6784
7054
|
case "logout": {
|
|
6785
7055
|
const lo = parseLogoutArgs(rest);
|
|
6786
7056
|
await logout(lo.platform, lo.yuqueGroup, lo.clearAll);
|
|
@@ -7432,7 +7702,14 @@ async function main() {
|
|
|
7432
7702
|
case "a":
|
|
7433
7703
|
case "add": {
|
|
7434
7704
|
showLogo();
|
|
7435
|
-
|
|
7705
|
+
let addParsed;
|
|
7706
|
+
try {
|
|
7707
|
+
addParsed = parseAddOptions(restArgs);
|
|
7708
|
+
} catch (error) {
|
|
7709
|
+
console.error(error instanceof Error ? error.message : "Invalid add options");
|
|
7710
|
+
process.exit(1);
|
|
7711
|
+
}
|
|
7712
|
+
const { source: addSource, options: addOpts } = addParsed;
|
|
7436
7713
|
await runAdd(addSource, addOpts);
|
|
7437
7714
|
break;
|
|
7438
7715
|
}
|
|
@@ -7467,10 +7744,12 @@ async function main() {
|
|
|
7467
7744
|
case "check":
|
|
7468
7745
|
await runCheck(restArgs);
|
|
7469
7746
|
break;
|
|
7470
|
-
case "login":
|
|
7471
|
-
|
|
7472
|
-
|
|
7747
|
+
case "login": {
|
|
7748
|
+
const { reauth, rest: loginRest } = stripReauthFlag(restArgs);
|
|
7749
|
+
if (loginRest.length > 0) await runPlatformLogin(loginRest);
|
|
7750
|
+
else await login({ reauth });
|
|
7473
7751
|
break;
|
|
7752
|
+
}
|
|
7474
7753
|
case "logout": {
|
|
7475
7754
|
const lo = parseLogoutArgs(restArgs);
|
|
7476
7755
|
await logout(lo.platform, lo.yuqueGroup, lo.clearAll);
|