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: ".agents/skills",
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: ".agents/skills",
941
- globalSkillsDir: join(home, ".agents/skills"),
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
- "qwen-code": {
956
- name: "qwen-code",
957
- displayName: "Qwen Code",
958
- skillsDir: ".qwen/skills",
959
- globalSkillsDir: join(home, ".qwen/skills"),
958
+ openclaw: {
959
+ name: "openclaw",
960
+ displayName: "OpenClaw",
961
+ skillsDir: "skills",
962
+ globalSkillsDir: getOpenClawGlobalSkillsDir(),
960
963
  detectInstalled: async () => {
961
- return existsSync(join(home, ".qwen"));
964
+ return existsSync(join(home, ".openclaw")) || existsSync(join(home, ".clawdbot")) || existsSync(join(home, ".moltbot"));
962
965
  }
963
966
  },
964
- "iflow-cli": {
965
- name: "iflow-cli",
966
- displayName: "iFlow CLI",
967
- skillsDir: ".iflow/skills",
968
- globalSkillsDir: join(home, ".iflow/skills"),
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, ".iflow"));
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: ".agents/skills",
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: ".agents/skills",
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: ".agents/skills",
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
- antigravity: {
1019
- name: "antigravity",
1020
- displayName: "Antigravity",
1021
- skillsDir: ".agent/skills",
1022
- globalSkillsDir: join(home, ".gemini/antigravity/skills"),
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, ".gemini/antigravity"));
1013
+ return existsSync(join(home, ".qwen"));
1025
1014
  }
1026
1015
  },
1027
- cline: {
1028
- name: "cline",
1029
- displayName: "Cline",
1030
- skillsDir: ".agents/skills",
1031
- globalSkillsDir: join(home, ".agents", "skills"),
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, ".cline"));
1023
+ return existsSync(join(home, ".kimi"));
1034
1024
  }
1035
1025
  },
1036
- continue: {
1037
- name: "continue",
1038
- displayName: "Continue",
1039
- skillsDir: ".continue/skills",
1040
- globalSkillsDir: join(home, ".continue/skills"),
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(process.cwd(), ".continue")) || existsSync(join(home, ".continue"));
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
- "kimi-cli": {
1064
- name: "kimi-cli",
1065
- displayName: "Kimi Code CLI",
1066
- skillsDir: ".agents/skills",
1067
- globalSkillsDir: join(home, ".config/agents/skills"),
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, ".kimi"));
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: ".agents/skills",
1103
- globalSkillsDir: join(home, ".agents/skills"),
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(([_, config]) => config.skillsDir === ".agents/skills" && config.showInUniversalList !== false).map(([type]) => type);
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(([_, config]) => config.skillsDir !== ".agents/skills").map(([type]) => type);
1330
+ return Object.entries(agents).filter(([, config]) => config.supportsSharedAgentsDir !== true).map(([type]) => type);
1318
1331
  }
1319
1332
  function isUniversalAgent(type) {
1320
- return agents[type].skillsDir === ".agents/skills";
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(/^[.\-]+|[.\-]+$/g, "").substring(0, 255) || "unnamed-skill";
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 agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
1406
- const agentDir = join(agentBase, skillName);
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 agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
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(/[\/\\:\0]/g, "")
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.24";
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 splitAgentsByType(agentTypes) {
2276
- const universal = [];
2277
- const symlinked = [];
2278
- for (const a of agentTypes) if (isUniversalAgent(a)) universal.push(agents[a].displayName);
2279
- else symlinked.push(agents[a].displayName);
2280
- return {
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 ensureUniversalAgents(targetAgents) {
2298
- const universalAgents = getUniversalAgents();
2299
- const result = [...targetAgents];
2300
- for (const ua of universalAgents) if (!result.includes(ua)) result.push(ua);
2301
- return result;
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 buildResultLines(results, targetAgents) {
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 { universal } = splitAgentsByType(targetAgents);
2306
- const successfulSymlinks = results.filter((r) => !r.symlinkFailed && !universal.includes(r.agent)).map((r) => r.agent);
2307
- const failedSymlinks = results.filter((r) => r.symlinkFailed).map((r) => r.agent);
2308
- if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList$1(universal)}`);
2309
- if (successfulSymlinks.length > 0) lines.push(` ${import_picocolors.default.dim("symlinked:")} ${formatList$1(successfulSymlinks)}`);
2310
- if (failedSymlinks.length > 0) lines.push(` ${import_picocolors.default.yellow("copied:")} ${formatList$1(failedSymlinks)}`);
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
- if (initialValues.length === 0) initialValues = defaultValues;
2334
- const selected = await searchMultiselect({
2335
- message,
2336
- items: choices,
2337
- initialSelected: initialValues,
2338
- required: true
2339
- });
2340
- if (!isCancelled$1(selected)) try {
2341
- await saveSelectedAgents(selected);
2342
- } catch {}
2343
- return selected;
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 universalAgents = getUniversalAgents().filter(supportsGlobalFilter);
2348
- const otherAgents = getNonUniversalAgents().filter(supportsGlobalFilter);
2349
- const universalSection = {
2350
- title: "Universal (.agents/skills)",
2351
- items: universalAgents.map((a) => ({
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
- const otherChoices = otherAgents.map((a) => ({
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 selected = await searchMultiselect({
2366
- message: "Which agents do you want to install to?",
2367
- items: otherChoices,
2368
- initialSelected: lastSelected ? lastSelected.filter((a) => otherAgents.includes(a) && !universalAgents.includes(a)) : [],
2369
- lockedSection: universalSection
2370
- });
2371
- if (!isCancelled$1(selected)) try {
2372
- await saveSelectedAgents(selected);
2373
- } catch {}
2374
- return selected;
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 = ensureUniversalAgents(options.agent);
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 = ensureUniversalAgents(installedAgents);
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.copy ? "copy" : "symlink";
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(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
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, targetAgents));
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 = ensureUniversalAgents(options.agent);
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 = ensureUniversalAgents(installedAgents);
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.copy ? "copy" : "symlink";
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(getCanonicalPath(skill.name, { global: installGlobally }), cwd);
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, targetAgents));
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 && !arg.startsWith("-")) source.push(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) await rm(canonicalPath, {
4175
- recursive: true,
4176
- force: true
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 login() {
4975
- const existingAuth = readAuth();
4976
- if (existingAuth && !isAuthExpired(existingAuth)) {
4977
- console.log(`${YELLOW$3}! 您已经登录为: ${existingAuth.name} (${existingAuth.account})${RESET$3}`);
4978
- console.log();
4979
- console.log(`${DIM$3}如需重新登录,请先执行: npx ali-skills logout${RESET$3}`);
4980
- return;
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 (!ssoRefreshToken) console.log(`${DIM$3}注意: SSO_REFRESH_TOKEN 未获取,需要时将从服务端获取${RESET$3}`);
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
- try {
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: data.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 console.log(outputBody);
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") sawAlibabaLoginRedirect = true;
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
- --copy Copy files instead of symlinking to agent directories
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] Login to Gateway or configure platform token
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
- await login();
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
- const { source: addSource, options: addOpts } = parseAddOptions(restArgs);
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
- if (restArgs.length > 0) await runPlatformLogin(restArgs);
7472
- else await login();
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);