ali-skills 0.0.23 → 0.0.25

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.23";
2343
+ var version$1 = "2.0.25";
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,55 +2470,65 @@ 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);
2377
2534
  async function handleWellKnownSkills(source, url, options, spinner) {
@@ -2447,7 +2604,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2447
2604
  M.info(`Valid agents: ${validAgents.join(", ")}`);
2448
2605
  process.exit(1);
2449
2606
  }
2450
- targetAgents = ensureUniversalAgents(options.agent);
2607
+ targetAgents = options.agent;
2451
2608
  } else {
2452
2609
  spinner.start("Loading agents...");
2453
2610
  const installedAgents = await detectInstalledAgents();
@@ -2469,7 +2626,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2469
2626
  targetAgents = selected;
2470
2627
  }
2471
2628
  else if (installedAgents.length === 1 || options.yes) {
2472
- targetAgents = ensureUniversalAgents(installedAgents);
2629
+ targetAgents = installedAgents;
2473
2630
  if (installedAgents.length === 1) {
2474
2631
  const firstAgent = installedAgents[0];
2475
2632
  M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
@@ -2508,15 +2665,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2508
2665
  if (!options.copy && !options.yes) {
2509
2666
  const modeChoice = await ve({
2510
2667
  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
- }]
2668
+ options: getInstallMethodOptions()
2520
2669
  });
2521
2670
  if (pD(modeChoice)) {
2522
2671
  xe("Installation cancelled");
@@ -2538,7 +2687,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2538
2687
  }
2539
2688
  for (const skill of selectedSkills) {
2540
2689
  if (summaryLines.length > 0) summaryLines.push("");
2541
- const shortCanonical = shortenPath$2(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
2690
+ const shortCanonical = shortenPath$2(getEntityPath(skill.installName, installMode, installGlobally, cwd), cwd);
2542
2691
  summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2543
2692
  summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2544
2693
  if (skill.files.size > 1) summaryLines.push(` ${import_picocolors.default.dim("files:")} ${skill.files.size}`);
@@ -2557,7 +2706,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2557
2706
  }
2558
2707
  spinner.start("Installing skills...");
2559
2708
  const results = [];
2560
- const effectiveAgents = installMode === "copy" ? targetAgents.filter((a, i, arr) => arr.findIndex((b) => agents[b].skillsDir === agents[a].skillsDir) === i) : targetAgents;
2709
+ const effectiveAgents = installMode === "copy" || installMode === "copy-isolated" ? targetAgents.filter((a, i, arr) => arr.findIndex((b) => agents[b].skillsDir === agents[a].skillsDir) === i) : targetAgents;
2561
2710
  for (const skill of selectedSkills) for (const agent of effectiveAgents) {
2562
2711
  const result = await installWellKnownSkillForAgent(skill, agent, {
2563
2712
  global: installGlobally,
@@ -2627,12 +2776,13 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2627
2776
  bySkill.set(r.skill, skillResults);
2628
2777
  }
2629
2778
  const skillCount = bySkill.size;
2630
- const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
2779
+ const symlinkFailures = successful.filter((r) => (r.mode === "symlink" || r.mode === "symlink-isolated") && r.symlinkFailed);
2631
2780
  const copiedAgents = symlinkFailures.map((r) => r.agent);
2781
+ const sharedFallbackAgents = successful.filter((r) => r.sharedFallbackUsed).map((r) => r.agent);
2632
2782
  const resultLines = [];
2633
2783
  for (const [skillName, skillResults] of bySkill) {
2634
2784
  const firstResult = skillResults[0];
2635
- if (firstResult.mode === "copy") {
2785
+ if (firstResult.mode === "copy" || firstResult.mode === "copy-isolated") {
2636
2786
  resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim("(copied)")}`);
2637
2787
  for (const r of skillResults) {
2638
2788
  const shortPath = shortenPath$2(r.path, cwd);
@@ -2643,7 +2793,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2643
2793
  const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
2644
2794
  resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
2645
2795
  } else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
2646
- resultLines.push(...buildResultLines(skillResults, targetAgents));
2796
+ resultLines.push(...buildResultLines(skillResults));
2647
2797
  }
2648
2798
  }
2649
2799
  const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
@@ -2652,6 +2802,10 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2652
2802
  M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList$1(copiedAgents)}`));
2653
2803
  M.message(import_picocolors.default.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
2654
2804
  }
2805
+ if (sharedFallbackAgents.length > 0) {
2806
+ M.warn(import_picocolors.default.bgYellow(import_picocolors.default.black(" FALLBACK ")) + " " + import_picocolors.default.yellow(`No private skills dir for: ${formatList$1(sharedFallbackAgents)}`));
2807
+ M.message(import_picocolors.default.dim(" Installed to shared .agents/skills for compatibility"));
2808
+ }
2655
2809
  }
2656
2810
  if (failed.length > 0) {
2657
2811
  console.log();
@@ -2947,7 +3101,7 @@ async function runAdd(args, options = {}) {
2947
3101
  await cleanup(tempDir);
2948
3102
  process.exit(1);
2949
3103
  }
2950
- targetAgents = ensureUniversalAgents(options.agent);
3104
+ targetAgents = options.agent;
2951
3105
  } else {
2952
3106
  spinner.start("Loading agents...");
2953
3107
  const installedAgents = await detectInstalledAgents();
@@ -2970,7 +3124,7 @@ async function runAdd(args, options = {}) {
2970
3124
  targetAgents = selected;
2971
3125
  }
2972
3126
  else if (installedAgents.length === 1 || options.yes) {
2973
- targetAgents = ensureUniversalAgents(installedAgents);
3127
+ targetAgents = installedAgents;
2974
3128
  if (installedAgents.length === 1) {
2975
3129
  const firstAgent = installedAgents[0];
2976
3130
  M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
@@ -3011,15 +3165,7 @@ async function runAdd(args, options = {}) {
3011
3165
  if (!options.copy && !options.yes) {
3012
3166
  const modeChoice = await ve({
3013
3167
  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
- }]
3168
+ options: getInstallMethodOptions()
3023
3169
  });
3024
3170
  if (pD(modeChoice)) {
3025
3171
  xe("Installation cancelled");
@@ -3050,7 +3196,7 @@ async function runAdd(args, options = {}) {
3050
3196
  const printSkillSummary = (skills) => {
3051
3197
  for (const skill of skills) {
3052
3198
  if (summaryLines.length > 0) summaryLines.push("");
3053
- const shortCanonical = shortenPath$2(getCanonicalPath(skill.name, { global: installGlobally }), cwd);
3199
+ const shortCanonical = shortenPath$2(getEntityPath(skill.name, installMode, installGlobally, cwd), cwd);
3054
3200
  summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
3055
3201
  summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
3056
3202
  const skillOverwrites = overwriteStatus.get(skill.name);
@@ -3094,7 +3240,7 @@ async function runAdd(args, options = {}) {
3094
3240
  }
3095
3241
  spinner.start("Installing skills...");
3096
3242
  const results = [];
3097
- const effectiveAgents = installMode === "copy" ? targetAgents.filter((a, i, arr) => arr.findIndex((b) => agents[b].skillsDir === agents[a].skillsDir) === i) : targetAgents;
3243
+ const effectiveAgents = installMode === "copy" || installMode === "copy-isolated" ? targetAgents.filter((a, i, arr) => arr.findIndex((b) => agents[b].skillsDir === agents[a].skillsDir) === i) : targetAgents;
3098
3244
  for (const skill of selectedSkills) for (const agent of effectiveAgents) {
3099
3245
  const result = await installSkillForAgent(skill, agent, {
3100
3246
  global: installGlobally,
@@ -3264,14 +3410,15 @@ async function runAdd(args, options = {}) {
3264
3410
  } else ungroupedResults.push(r);
3265
3411
  }
3266
3412
  const skillCount = bySkill.size;
3267
- const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
3413
+ const symlinkFailures = successful.filter((r) => (r.mode === "symlink" || r.mode === "symlink-isolated") && r.symlinkFailed);
3268
3414
  const copiedAgents = symlinkFailures.map((r) => r.agent);
3415
+ const sharedFallbackAgents = successful.filter((r) => r.sharedFallbackUsed).map((r) => r.agent);
3269
3416
  const resultLines = [];
3270
3417
  const printSkillResults = (entries) => {
3271
3418
  for (const entry of entries) {
3272
3419
  const skillResults = bySkill.get(entry.skill) || [];
3273
3420
  const firstResult = skillResults[0];
3274
- if (firstResult.mode === "copy") {
3421
+ if (firstResult.mode === "copy" || firstResult.mode === "copy-isolated") {
3275
3422
  resultLines.push(`${import_picocolors.default.green("✓")} ${entry.skill} ${import_picocolors.default.dim("(copied)")}`);
3276
3423
  for (const r of skillResults) {
3277
3424
  const shortPath = shortenPath$2(r.path, cwd);
@@ -3282,7 +3429,7 @@ async function runAdd(args, options = {}) {
3282
3429
  const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
3283
3430
  resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
3284
3431
  } else resultLines.push(`${import_picocolors.default.green("✓")} ${entry.skill}`);
3285
- resultLines.push(...buildResultLines(skillResults, targetAgents));
3432
+ resultLines.push(...buildResultLines(skillResults));
3286
3433
  }
3287
3434
  }
3288
3435
  };
@@ -3306,6 +3453,10 @@ async function runAdd(args, options = {}) {
3306
3453
  M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList$1(copiedAgents)}`));
3307
3454
  M.message(import_picocolors.default.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
3308
3455
  }
3456
+ if (sharedFallbackAgents.length > 0) {
3457
+ M.warn(import_picocolors.default.bgYellow(import_picocolors.default.black(" FALLBACK ")) + " " + import_picocolors.default.yellow(`No private skills dir for: ${formatList$1(sharedFallbackAgents)}`));
3458
+ M.message(import_picocolors.default.dim(" Installed to shared .agents/skills for compatibility"));
3459
+ }
3309
3460
  }
3310
3461
  if (failed.length > 0) {
3311
3462
  console.log();
@@ -4140,6 +4291,10 @@ async function removeCommand(skillNames, options) {
4140
4291
  global: isGlobal,
4141
4292
  cwd
4142
4293
  });
4294
+ const isolatedStorePath = getIsolatedStorePath(skillName, {
4295
+ global: isGlobal,
4296
+ cwd
4297
+ });
4143
4298
  for (const agentKey of targetAgents) {
4144
4299
  const agent = agents[agentKey];
4145
4300
  const skillPath = getInstallPath(skillName, agentKey, {
@@ -4171,10 +4326,16 @@ async function removeCommand(skillNames, options) {
4171
4326
  isStillUsed = true;
4172
4327
  break;
4173
4328
  }
4174
- if (!isStillUsed) await rm(canonicalPath, {
4175
- recursive: true,
4176
- force: true
4177
- });
4329
+ if (!isStillUsed) {
4330
+ await rm(canonicalPath, {
4331
+ recursive: true,
4332
+ force: true
4333
+ });
4334
+ await rm(isolatedStorePath, {
4335
+ recursive: true,
4336
+ force: true
4337
+ });
4338
+ }
4178
4339
  const lockEntry = isGlobal ? await getSkillFromLock(skillName) : null;
4179
4340
  const effectiveSource = lockEntry?.source || "local";
4180
4341
  const effectiveSourceType = lockEntry?.sourceType || "local";
@@ -5381,6 +5542,17 @@ const REDIRECT_STATUS_CODES = new Set([
5381
5542
  308
5382
5543
  ]);
5383
5544
  const MAX_REDIRECTS = 5;
5545
+ function normalizeEscapedUrlInput(rawUrl) {
5546
+ if (!rawUrl.includes("\\")) return {
5547
+ normalizedUrl: rawUrl,
5548
+ changed: false
5549
+ };
5550
+ const normalizedUrl = rawUrl.replace(/\\([?&=#])/g, "$1");
5551
+ return {
5552
+ normalizedUrl,
5553
+ changed: normalizedUrl !== rawUrl
5554
+ };
5555
+ }
5384
5556
  function detectAuthMode(platformKey, platform, hostname) {
5385
5557
  if (platform.authType === "private_token") return "private_token";
5386
5558
  if (platformKey === "o2") return "auto";
@@ -5709,7 +5881,7 @@ async function sendSsoTicketDirectRequest(targetUrl, options) {
5709
5881
  } else console.log(outputBody);
5710
5882
  if (!response.ok) process.exit(1);
5711
5883
  }
5712
- async function sendPlainDirectRequest(targetUrl, options) {
5884
+ async function sendPlainDirectRequest(targetUrl, options, allowSsoCookieFallback = false) {
5713
5885
  const requestHeaders = { "User-Agent": "ali-skills-cli/1.0" };
5714
5886
  if (options.headers) Object.entries(options.headers).forEach(([key, value]) => {
5715
5887
  requestHeaders[key] = value;
@@ -5723,11 +5895,19 @@ async function sendPlainDirectRequest(targetUrl, options) {
5723
5895
  console.log(`${DIM$2}${options.method.toUpperCase()} ${targetUrl}${RESET$2}`);
5724
5896
  console.log(`${DIM$2}→ 直接请求 (No Auth)${RESET$2}`);
5725
5897
  }
5726
- const controller = new AbortController();
5727
5898
  const timeout = options.timeout || 3e4;
5728
- const timeoutId = setTimeout(() => controller.abort(), timeout);
5729
- const { response, responseBody } = await fetchWithRedirects(targetUrl, options.method.toUpperCase(), requestHeaders, body, timeout, options.quiet);
5730
- clearTimeout(timeoutId);
5899
+ const { response, responseBody, sawAlibabaLoginRedirect } = await fetchWithRedirects(targetUrl, options.method.toUpperCase(), requestHeaders, body, timeout, options.quiet);
5900
+ if (allowSsoCookieFallback && sawAlibabaLoginRedirect) {
5901
+ if (!options.quiet) {
5902
+ console.log(`${YELLOW$2}检测到请求被重定向到 login.alibaba-inc.com,目标接口可能需要登录鉴权${RESET$2}`);
5903
+ console.log(`${DIM$2}将自动使用 --auth sso_cookie 重试原始接口;如不符合预期,请显式指定 --auth${RESET$2}`);
5904
+ }
5905
+ await sendSsoCookieDirectRequest(targetUrl, {
5906
+ ...options,
5907
+ authMode: "sso_cookie"
5908
+ });
5909
+ return;
5910
+ }
5731
5911
  if (!options.quiet) {
5732
5912
  const statusColor = response.ok ? GREEN$1 : RED$2;
5733
5913
  console.log(`${statusColor}${response.status} ${response.statusText}${RESET$2}`);
@@ -5757,6 +5937,7 @@ async function fetchWithRedirects(initialUrl, method, headers, body, timeout, qu
5757
5937
  const timeoutId = setTimeout(() => controller.abort(), timeout);
5758
5938
  let currentUrl = initialUrl;
5759
5939
  let redirects = 0;
5940
+ let sawAlibabaLoginRedirect = false;
5760
5941
  try {
5761
5942
  while (true) {
5762
5943
  const response = await fetch(currentUrl, {
@@ -5768,15 +5949,18 @@ async function fetchWithRedirects(initialUrl, method, headers, body, timeout, qu
5768
5949
  });
5769
5950
  if (!REDIRECT_STATUS_CODES.has(response.status)) return {
5770
5951
  response,
5771
- responseBody: await response.text()
5952
+ responseBody: await response.text(),
5953
+ sawAlibabaLoginRedirect
5772
5954
  };
5773
5955
  const location = response.headers.get("location");
5774
5956
  if (!location) return {
5775
5957
  response,
5776
- responseBody: await response.text()
5958
+ responseBody: await response.text(),
5959
+ sawAlibabaLoginRedirect
5777
5960
  };
5778
5961
  if (redirects >= MAX_REDIRECTS) throw new Error(`重定向次数超过上限(${MAX_REDIRECTS})`);
5779
5962
  const nextUrl = new URL(location, currentUrl).toString();
5963
+ if (new URL(nextUrl).hostname === "login.alibaba-inc.com") sawAlibabaLoginRedirect = true;
5780
5964
  redirects += 1;
5781
5965
  if (!quiet) console.log(`${DIM$2}↪ 重定向 ${redirects}: ${response.status} ${response.statusText} -> ${nextUrl}${RESET$2}`);
5782
5966
  currentUrl = nextUrl;
@@ -5863,8 +6047,9 @@ async function sendGatewayRequest(targetUrl, platformKey, options, authMode = "g
5863
6047
  }
5864
6048
  async function proxyRequest(targetUrl, options) {
5865
6049
  let url;
6050
+ const { normalizedUrl, changed: urlInputNormalized } = normalizeEscapedUrlInput(targetUrl);
5866
6051
  try {
5867
- url = new URL(targetUrl);
6052
+ url = new URL(normalizedUrl);
5868
6053
  } catch {
5869
6054
  console.error(`${RED$2}Error: URL 必须是绝对地址,收到 "${targetUrl}"${RESET$2}`);
5870
6055
  console.log(`${DIM$2}未传 platform 时,请使用完整 URL(包含协议和域名)${RESET$2}`);
@@ -5885,12 +6070,17 @@ async function proxyRequest(targetUrl, options) {
5885
6070
  ssoType: "refresh_token"
5886
6071
  };
5887
6072
  try {
6073
+ if (urlInputNormalized && !options.quiet) {
6074
+ console.log(`${DIM$2}检测到 URL 中包含转义分隔符,已自动还原后请求${RESET$2}`);
6075
+ console.log(`${DIM$2}原始: ${targetUrl}${RESET$2}`);
6076
+ console.log(`${DIM$2}还原: ${normalizedUrl}${RESET$2}`);
6077
+ }
5888
6078
  if (!isKnownPlatform && !hasExplicitAuthMode) {
5889
6079
  if (!options.quiet) {
5890
6080
  console.log(`${DIM$2}平台: Custom Host${RESET$2}`);
5891
6081
  console.log(`${DIM$2}认证方式: Auto (未知平台默认直连,无鉴权)${RESET$2}`);
5892
6082
  }
5893
- await sendPlainDirectRequest(targetUrl, options);
6083
+ await sendPlainDirectRequest(normalizedUrl, options, true);
5894
6084
  return;
5895
6085
  }
5896
6086
  if (!options.quiet) console.log(`${DIM$2}平台: ${platform.name}${RESET$2}`);
@@ -5902,7 +6092,7 @@ async function proxyRequest(targetUrl, options) {
5902
6092
  console.error(`${RED$2}Error: o2 仅支持 --auth auto 或 --auth sso_ticket${RESET$2}`);
5903
6093
  process.exit(1);
5904
6094
  }
5905
- await sendO2SdkRequest(targetUrl, options, authMode === "sso_ticket");
6095
+ await sendO2SdkRequest(normalizedUrl, options, authMode === "sso_ticket");
5906
6096
  return;
5907
6097
  }
5908
6098
  switch (authMode) {
@@ -5921,17 +6111,17 @@ async function proxyRequest(targetUrl, options) {
5921
6111
  process.exit(1);
5922
6112
  }
5923
6113
  }
5924
- await sendDirectRequest(targetUrl, platform, token, options);
6114
+ await sendDirectRequest(normalizedUrl, platform, token, options);
5925
6115
  return;
5926
6116
  }
5927
6117
  case "sso_cookie":
5928
- await sendSsoCookieDirectRequest(targetUrl, options);
6118
+ await sendSsoCookieDirectRequest(normalizedUrl, options);
5929
6119
  return;
5930
6120
  case "sso_ticket":
5931
- await sendSsoTicketDirectRequest(targetUrl, options);
6121
+ await sendSsoTicketDirectRequest(normalizedUrl, options);
5932
6122
  return;
5933
6123
  case "gateway":
5934
- await sendGatewayRequest(targetUrl, platformKey, options, "gateway");
6124
+ await sendGatewayRequest(normalizedUrl, platformKey, options, "gateway");
5935
6125
  return;
5936
6126
  default: break;
5937
6127
  }
@@ -5941,12 +6131,12 @@ async function proxyRequest(targetUrl, options) {
5941
6131
  console.error(`${RED$2}Error: o2 仅支持 --auth auto 或 --auth sso_ticket${RESET$2}`);
5942
6132
  process.exit(1);
5943
6133
  }
5944
- await sendO2SdkRequest(targetUrl, options, authMode === "sso_ticket");
6134
+ await sendO2SdkRequest(normalizedUrl, options, authMode === "sso_ticket");
5945
6135
  return;
5946
6136
  }
5947
6137
  switch (authMode) {
5948
6138
  case "private_token": {
5949
- let token = getPlatformPrivateToken(platformKey, options.token, targetUrl);
6139
+ let token = getPlatformPrivateToken(platformKey, options.token, normalizedUrl);
5950
6140
  if (!token) {
5951
6141
  token = await promptConfigureToken(platformKey, platform);
5952
6142
  if (!token) {
@@ -5960,19 +6150,19 @@ async function proxyRequest(targetUrl, options) {
5960
6150
  process.exit(1);
5961
6151
  }
5962
6152
  }
5963
- await sendDirectRequest(targetUrl, platform, token, options);
6153
+ await sendDirectRequest(normalizedUrl, platform, token, options);
5964
6154
  break;
5965
6155
  }
5966
6156
  case "sso_ticket":
5967
6157
  case "gateway":
5968
- await sendGatewayRequest(targetUrl, platformKey, options, authMode);
6158
+ await sendGatewayRequest(normalizedUrl, platformKey, options, authMode);
5969
6159
  break;
5970
6160
  case "sso_cookie":
5971
- await sendSsoCookieDirectRequest(targetUrl, options);
6161
+ await sendSsoCookieDirectRequest(normalizedUrl, options);
5972
6162
  break;
5973
6163
  default: if (platform.authType === "private_token") {
5974
- const token = getPlatformPrivateToken(platformKey, options.token, targetUrl);
5975
- if (token) await sendDirectRequest(targetUrl, platform, token, options);
6164
+ const token = getPlatformPrivateToken(platformKey, options.token, normalizedUrl);
6165
+ if (token) await sendDirectRequest(normalizedUrl, platform, token, options);
5976
6166
  else {
5977
6167
  console.error(`${RED$2}Error: ${platform.name} 需要配置 Private Token${RESET$2}`);
5978
6168
  console.log(`${DIM$2}您可以通过以下方式配置:${RESET$2}`);
@@ -5983,8 +6173,8 @@ async function proxyRequest(targetUrl, options) {
5983
6173
  console.log(` 参数: --token <your_token>`);
5984
6174
  process.exit(1);
5985
6175
  }
5986
- } else if (isAlibabaIncRootDomain) await sendSsoCookieDirectRequest(targetUrl, options);
5987
- else await sendGatewayRequest(targetUrl, platformKey, options, "sso_ticket");
6176
+ } else if (isAlibabaIncRootDomain) await sendSsoCookieDirectRequest(normalizedUrl, options);
6177
+ else await sendGatewayRequest(normalizedUrl, platformKey, options, "sso_ticket");
5988
6178
  }
5989
6179
  } catch (error) {
5990
6180
  if (error instanceof Error) if (error.name === "AbortError") console.error(`${RED$2}Error: 请求超时${RESET$2}`);
@@ -6452,19 +6642,20 @@ ${BOLD}Gateway Commands:${RESET}
6452
6642
  request [platform] <url|path> Send HTTP request (supports platform + path shorthand)
6453
6643
  req [platform] <url|path> Alias for request, recommended
6454
6644
 
6645
+ ${BOLD}Platform Values:${RESET}
6646
+ ${platformKeysForHelp}
6647
+
6455
6648
  ${BOLD}Yuque Commands:${RESET}
6456
6649
  login yuque [group] Configure/switch specified Yuque group directly
6457
6650
  logout yuque [group] [--all] Choose one/many Yuque groups to clear, or clear all
6458
6651
  status yuque [group] [--show|-s] [--pretty] Show Yuque group/all config JSON
6459
6652
 
6460
- ${BOLD}Platform Values:${RESET}
6461
- ${platformKeysForHelp}
6462
-
6463
6653
  ${BOLD}Request Options:${RESET}
6464
6654
  -X, --method <method> HTTP method (default: GET)
6465
6655
  -b, --body <body> Request body (JSON string or @file)
6466
6656
  -h, --header <header> Custom header (can be used multiple times)
6467
6657
  --auth, --auth-mode <mode> Auth mode: auto/private_token/sso_cookie/sso_ticket
6658
+ --sso <cookie|ticket> Shortcut for --auth sso_cookie/sso_ticket
6468
6659
  --token <token> Explicit token for private_token mode
6469
6660
  -o, --output <file> Save response to file
6470
6661
  -i, --include-headers Include response headers in output
@@ -6473,46 +6664,104 @@ ${BOLD}Request Options:${RESET}
6473
6664
  --raw Raw output without formatting
6474
6665
  -q, --quiet Quiet mode, only output response body
6475
6666
 
6476
- ${BOLD}Environment:${RESET}
6477
- ALI_SKILLS_YUQUE_GROUP Yuque team slug when multiple teams have tokens in config
6478
- O2_API_ENV O2 SDK env override (default online; set pre for pre env)
6479
- ALI_SKILLS_DEBUG_GATEWAY Enable gateway auth debug logs (1/true/on)
6480
-
6481
6667
  ${BOLD}Examples:${RESET}
6482
6668
  ${DIM}$${RESET} ali-skills login
6483
6669
  ${DIM}$${RESET} ali-skills login code
6484
6670
  ${DIM}$${RESET} ali-skills login yuque
6485
- ${DIM}$${RESET} ali-skills login yuque fliggy
6671
+ ${DIM}$${RESET} ali-skills login yuque group
6486
6672
  ${DIM}$${RESET} ali-skills status --all
6673
+ ${DIM}$${RESET} ali-skills status
6487
6674
  ${DIM}$${RESET} ali-skills status code
6488
- ${DIM}$${RESET} ali-skills status -s
6489
- ${DIM}$${RESET} ali-skills status -s --pretty
6675
+ ${DIM}$${RESET} ali-skills status yuque
6676
+ ${DIM}$${RESET} ali-skills status yuque group
6677
+ ${DIM}$${RESET} ali-skills status o2
6678
+ ${DIM}$${RESET} ali-skills status --show
6490
6679
  ${DIM}$${RESET} ali-skills status code --show
6491
6680
  ${DIM}$${RESET} ali-skills status yuque --show
6492
- ${DIM}$${RESET} ali-skills status yuque fliggy
6493
6681
  ${DIM}$${RESET} ali-skills status yuque fliggy --show
6682
+ ${DIM}$${RESET} ali-skills logout
6494
6683
  ${DIM}$${RESET} ali-skills logout code
6495
6684
  ${DIM}$${RESET} ali-skills logout yuque
6496
6685
  ${DIM}$${RESET} ali-skills logout yuque my-team
6497
6686
  ${DIM}$${RESET} ali-skills logout yuque --all
6498
- ${DIM}$${RESET} ali-skills req o2 /v1.0/work/apps
6499
- ${DIM}$${RESET} ali-skills req o2 /v1.0/work/aone/issues/search -X POST -b '{"keyword":"auth"}'
6500
- ${DIM}$${RESET} ali-skills req o2 /v1.0/work/issues/123 --auth sso_ticket
6501
- ${DIM}$${RESET} ali-skills req https://code.alibaba-inc.com/api/v4/projects -X GET
6502
- ${DIM}$${RESET} ali-skills req https://yuque-api.antfin-inc.com/api/v2/repos -o repos.json
6503
- ${DIM}$${RESET} ali-skills req yuque "/api/v2/search?q=buc&type=doc"
6504
- ${DIM}$${RESET} ali-skills req yuque "/api/v2/search?q=buc&type=doc&scope=fliggyfe-infra"
6505
- ${DIM}$${RESET} ali-skills logout
6687
+ ${DIM}$${RESET} ali-skills req 'https://subdomain.alibaba-inc.com/path?q=v'
6688
+ ${DIM}$${RESET} ali-skills req 'https://subdomain.alibaba-inc.com/path?q=v' --auth sso_cookie
6689
+ ${DIM}$${RESET} ali-skills req 'https://subdomain.alibaba-inc.com/path?q=v' --auth sso_cookie -X POST -b '{"k": "v"}' -h 'X-Custom-Header: value'
6690
+ ${DIM}$${RESET} ali-skills req 'https://support-buc-sso-ticket-site/path?q=v' --auth sso_ticket
6691
+ ${DIM}$${RESET} ali-skills req code '/api/v3/project?path=clam-ng/clam'
6692
+ ${DIM}$${RESET} ali-skills req yuque '/api/v2/repos/fliggyfe-infra/ali-skills-dev/docs/mll9xim6y6lcnvyg'
6693
+ ${DIM}$${RESET} ali-skills req yuque '/api/v2/search?q=buc&type=doc&scope=fliggyfe-infra'
6694
+ ${DIM}$${RESET} ali-skills req o2 '/v1.0/work/apps/rxpi%252Fmtop/trunk'
6695
+ ${DIM}$${RESET} ali-skills req o2 '/v1.0/work/apps/rxpi%252Fmtop/detail' --auth sso_ticket
6506
6696
  `);
6507
6697
  }
6508
6698
  function parseRequestOptions(args) {
6509
6699
  let url = "";
6510
6700
  const options = { method: "GET" };
6511
6701
  const headers = {};
6702
+ const parseSsoShortcut = (raw) => {
6703
+ if (!raw) return void 0;
6704
+ const normalized = raw.trim().toLowerCase();
6705
+ if (normalized === "cookie") return "sso_cookie";
6706
+ if (normalized === "ticket") return "sso_ticket";
6707
+ };
6512
6708
  for (let i = 0; i < args.length; i++) {
6513
6709
  const arg = args[i];
6710
+ const parseLongOptionValue = (name) => {
6711
+ const prefix = `--${name}=`;
6712
+ if (!arg?.startsWith(prefix)) return void 0;
6713
+ return arg.slice(prefix.length);
6714
+ };
6715
+ const methodValue = parseLongOptionValue("method");
6716
+ if (methodValue !== void 0) {
6717
+ options.method = methodValue || "GET";
6718
+ continue;
6719
+ }
6720
+ const bodyValue = parseLongOptionValue("body");
6721
+ if (bodyValue !== void 0) {
6722
+ options.body = bodyValue;
6723
+ continue;
6724
+ }
6725
+ const headerValue = parseLongOptionValue("header");
6726
+ if (headerValue !== void 0) {
6727
+ const [key, ...valueParts] = headerValue.split(":");
6728
+ if (key && valueParts.length > 0) headers[key.trim()] = valueParts.join(":").trim();
6729
+ continue;
6730
+ }
6731
+ const outputValue = parseLongOptionValue("output");
6732
+ if (outputValue !== void 0) {
6733
+ options.output = outputValue;
6734
+ continue;
6735
+ }
6736
+ const timeoutValue = parseLongOptionValue("timeout");
6737
+ if (timeoutValue !== void 0) {
6738
+ if (timeoutValue) options.timeout = parseInt(timeoutValue, 10);
6739
+ continue;
6740
+ }
6741
+ const authModeValue = parseLongOptionValue("auth-mode") ?? parseLongOptionValue("auth");
6742
+ if (authModeValue !== void 0) {
6743
+ options.authMode = authModeValue || "auto";
6744
+ continue;
6745
+ }
6746
+ const tokenValue = parseLongOptionValue("token");
6747
+ if (tokenValue !== void 0) {
6748
+ options.token = tokenValue;
6749
+ continue;
6750
+ }
6751
+ const ssoValue = parseLongOptionValue("sso");
6752
+ if (ssoValue !== void 0) {
6753
+ const mapped = parseSsoShortcut(ssoValue);
6754
+ if (!mapped) {
6755
+ console.error(`Unknown value for --sso: ${ssoValue}, expected cookie or ticket`);
6756
+ process.exit(1);
6757
+ }
6758
+ options.authMode = mapped;
6759
+ continue;
6760
+ }
6514
6761
  switch (arg) {
6515
6762
  case "-m":
6763
+ case "-x":
6764
+ case "-X":
6516
6765
  case "--method":
6517
6766
  options.method = args[++i] || "GET";
6518
6767
  break;
@@ -6556,6 +6805,15 @@ function parseRequestOptions(args) {
6556
6805
  case "--token":
6557
6806
  options.token = args[++i];
6558
6807
  break;
6808
+ case "--sso": {
6809
+ const mode = parseSsoShortcut(args[++i]);
6810
+ if (!mode) {
6811
+ console.error("Unknown value for --sso, expected cookie or ticket");
6812
+ process.exit(1);
6813
+ }
6814
+ options.authMode = mode;
6815
+ break;
6816
+ }
6559
6817
  case "-q":
6560
6818
  case "--quiet":
6561
6819
  options.quiet = true;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ali/cli-skills",
3
- "version": "2.0.23",
3
+ "version": "2.0.25",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ali-skills",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,7 @@
31
31
  "author": "",
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
- "@ali/cli-skills": "^2.0.23"
34
+ "@ali/cli-skills": "^2.0.25"
35
35
  },
36
36
  "bundleDependencies": [
37
37
  "@ali/cli-skills"