skills 1.5.23 → 1.5.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.
Files changed (3) hide show
  1. package/README.md +6 -6
  2. package/dist/cli.mjs +256 -99
  3. package/package.json +3 -1
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  The CLI for the open agent skills ecosystem.
4
4
 
5
5
  <!-- agent-list:start -->
6
- Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [73 more](#supported-agents).
6
+ Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [75 more](#supported-agents).
7
7
  <!-- agent-list:end -->
8
8
 
9
9
  [![skills.sh](https://skills.sh/b/vercel-labs/skills)](https://skills.sh/vercel-labs/skills)
@@ -279,7 +279,7 @@ Skills can be installed to any of these agents:
279
279
  | IBM Bob | `bob` | `.bob/skills/` | `~/.bob/skills/` |
280
280
  | Claude Code | `claude-code` | `.claude/skills/` | `~/.claude/skills/` |
281
281
  | OpenClaw | `openclaw` | `skills/` | `~/.openclaw/skills/` |
282
- | Cline, Dexto, Kimi Code CLI, Loaf, Warp, Zed | `cline`, `dexto`, `kimi-code-cli`, `loaf`, `warp`, `zed` | `.agents/skills/` | `~/.agents/skills/` |
282
+ | Cline, Dexto, Kimi Code CLI, Loaf, Sarvam Code, Warp, Zed | `cline`, `dexto`, `kimi-code-cli`, `loaf`, `sarvam-code`, `warp`, `zed` | `.agents/skills/` | `~/.agents/skills/` |
283
283
  | CodeArts Agent | `codearts-agent` | `.codeartsdoer/skills/` | `~/.codeartsdoer/skills/` |
284
284
  | CodeBuddy | `codebuddy` | `.codebuddy/skills/` | `~/.codebuddy/skills/` |
285
285
  | Codemaker | `codemaker` | `.codemaker/skills/` | `~/.codemaker/skills/` |
@@ -292,10 +292,11 @@ Skills can be installed to any of these agents:
292
292
  | Cursor | `cursor` | `.agents/skills/` | `~/.cursor/skills/` |
293
293
  | Deep Agents | `deepagents` | `.agents/skills/` | `~/.deepagents/agent/skills/` |
294
294
  | Devin for Terminal | `devin` | `.devin/skills/` | `~/.config/devin/skills/` |
295
- | Droid | `droid` | `.factory/skills/` | `~/.factory/skills/` |
295
+ | Droid | `droid` | `.agents/skills/` | `~/.factory/skills/` |
296
296
  | Eve | `eve` | `agent/skills/` | N/A (project-only) |
297
297
  | Firebender | `firebender` | `.agents/skills/` | `~/.firebender/skills/` |
298
298
  | ForgeCode | `forgecode` | `.forge/skills/` | `~/.forge/skills/` |
299
+ | fx | `fx` | `.fx/skills/` | `~/.fx/skills/` |
299
300
  | Gemini CLI | `gemini-cli` | `.agents/skills/` | `~/.gemini/skills/` |
300
301
  | GitHub Copilot | `github-copilot` | `.agents/skills/` | `~/.copilot/skills/` |
301
302
  | Goose | `goose` | `.goose/skills/` | `~/.config/goose/skills/` |
@@ -305,7 +306,7 @@ Skills can be installed to any of these agents:
305
306
  | Jazz | `jazz` | `.jazz/skills/` | `~/.jazz/skills/` |
306
307
  | Junie | `junie` | `.junie/skills/` | `~/.junie/skills/` |
307
308
  | iFlow CLI | `iflow-cli` | `.iflow/skills/` | `~/.iflow/skills/` |
308
- | Kilo Code | `kilo` | `.kilocode/skills/` | `~/.kilocode/skills/` |
309
+ | Kilo Code | `kilo` | `.agents/skills/` | `~/.kilo/skills/` |
309
310
  | Kimchi | `kimchi` | `.kimchi/skills/` | `~/.config/kimchi/harness/skills/` |
310
311
  | Kiro CLI | `kiro-cli` | `.kiro/skills/` | `~/.kiro/skills/` |
311
312
  | Kode | `kode` | `.kode/skills/` | `~/.kode/skills/` |
@@ -430,9 +431,9 @@ discover `SKILL.md` files outside these container directories (e.g. under
430
431
  - `.cortex/skills/`
431
432
  - `.crush/skills/`
432
433
  - `.devin/skills/`
433
- - `.factory/skills/`
434
434
  - `agent/skills/`
435
435
  - `.forge/skills/`
436
+ - `.fx/skills/`
436
437
  - `.goose/skills/`
437
438
  - `.grok/skills/`
438
439
  - `.hermes/skills/`
@@ -440,7 +441,6 @@ discover `SKILL.md` files outside these container directories (e.g. under
440
441
  - `.jazz/skills/`
441
442
  - `.junie/skills/`
442
443
  - `.iflow/skills/`
443
- - `.kilocode/skills/`
444
444
  - `.kimchi/skills/`
445
445
  - `.kiro/skills/`
446
446
  - `.kode/skills/`
package/dist/cli.mjs CHANGED
@@ -598,21 +598,30 @@ async function searchMultiselect(options) {
598
598
  lastRenderHeight = countVisualRowsForLines(lines, process.stdout.columns);
599
599
  };
600
600
  const cleanup = () => {
601
+ rl.removeListener("close", closeHandler);
601
602
  process.stdin.removeListener("keypress", keypressHandler);
602
603
  if (process.stdin.isTTY) process.stdin.setRawMode(false);
603
604
  rl.close();
604
605
  };
606
+ let settled = false;
605
607
  const submit = () => {
608
+ if (settled) return;
606
609
  if (required && selected.size === 0 && lockedValues.length === 0) return;
610
+ settled = true;
607
611
  render("submit");
608
612
  cleanup();
609
613
  resolve([...lockedValues, ...Array.from(selected)]);
610
614
  };
611
615
  const cancel = () => {
616
+ if (settled) return;
617
+ settled = true;
612
618
  render("cancel");
613
619
  cleanup();
614
620
  resolve(cancelSymbol);
615
621
  };
622
+ const closeHandler = () => {
623
+ cancel();
624
+ };
616
625
  const keypressHandler = (_str, key) => {
617
626
  if (!key) return;
618
627
  const entries = buildSearchEntries(getFiltered(), selectGroups, collapsedGroups);
@@ -673,6 +682,11 @@ async function searchMultiselect(options) {
673
682
  }
674
683
  };
675
684
  process.stdin.on("keypress", keypressHandler);
685
+ rl.on("close", closeHandler);
686
+ if (process.stdin.readableEnded || process.stdin.destroyed) {
687
+ cancel();
688
+ return;
689
+ }
676
690
  render();
677
691
  });
678
692
  }
@@ -685,6 +699,25 @@ const CLONE_TIMEOUT_MS = (() => {
685
699
  return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CLONE_TIMEOUT_MS;
686
700
  })();
687
701
  const execFileAsync = promisify(execFile);
702
+ function isCommitSha(ref) {
703
+ return /^[0-9a-f]{40}$/i.test(ref);
704
+ }
705
+ function isMissingRefError(message) {
706
+ return /Remote branch .* not found in upstream origin/i.test(message) || /couldn't find remote ref/i.test(message) || /upload-pack: not our ref/i.test(message);
707
+ }
708
+ async function cloneAtSha(url, sha, tempDir, extraEnv) {
709
+ const git = createGitClient(extraEnv);
710
+ await git.cwd(tempDir);
711
+ await git.init();
712
+ await git.addRemote("origin", url);
713
+ await git.fetch([
714
+ "--depth",
715
+ "1",
716
+ "origin",
717
+ sha
718
+ ]);
719
+ await git.checkout("FETCH_HEAD");
720
+ }
688
721
  var GitCloneError = class extends Error {
689
722
  url;
690
723
  isTimeout;
@@ -742,7 +775,7 @@ function isGitHubSsoAuthError(message) {
742
775
  function isAuthFailure(message) {
743
776
  return message.includes("Authentication failed") || message.includes("could not read Username") || message.includes("Permission denied") || message.includes("Repository not found") || message.includes("requested URL returned error: 403") || isGitHubSsoAuthError(message);
744
777
  }
745
- function createGitClient(sshCommand) {
778
+ function createGitClient(extraEnv) {
746
779
  const git = esm_default({
747
780
  timeout: { block: CLONE_TIMEOUT_MS },
748
781
  config: [
@@ -778,7 +811,7 @@ function createGitClient(sshCommand) {
778
811
  GIT_TERMINAL_PROMPT: "0",
779
812
  GIT_ALLOW_PROTOCOL: ALLOWED_GIT_PROTOCOLS,
780
813
  GIT_LFS_SKIP_SMUDGE: "1",
781
- ...sshCommand ? { GIT_SSH_COMMAND: sshCommand } : {}
814
+ ...extraEnv
782
815
  });
783
816
  return git;
784
817
  }
@@ -846,12 +879,18 @@ async function cloneRepo(url, ref) {
846
879
  "--branch",
847
880
  ref
848
881
  ] : ["--depth", "1"];
882
+ const refCanBeSha = !!ref && isCommitSha(ref);
849
883
  const repo = parseGitHubRepoUrl(url);
850
884
  try {
851
885
  await createGitClient().clone(url, tempDir, cloneOptions);
852
886
  return tempDir;
853
887
  } catch (error) {
854
888
  const errorMessage = error instanceof Error ? error.message : String(error);
889
+ if (refCanBeSha && isMissingRefError(errorMessage)) try {
890
+ await resetTempDir(tempDir);
891
+ await cloneAtSha(url, ref, tempDir);
892
+ return tempDir;
893
+ } catch {}
855
894
  const isTimeout = errorMessage.includes("block timeout") || errorMessage.includes("timed out");
856
895
  const isAuthError = isAuthFailure(errorMessage);
857
896
  if (isTimeout) {
@@ -868,7 +907,16 @@ async function cloneRepo(url, ref) {
868
907
  } catch {}
869
908
  try {
870
909
  await resetTempDir(tempDir);
871
- await createGitClient(process.env.GIT_SSH_COMMAND ?? "ssh -o BatchMode=yes").clone(repo.sshUrl, tempDir, cloneOptions);
910
+ const sshEnv = { GIT_SSH_COMMAND: process.env.GIT_SSH_COMMAND ?? "ssh -o BatchMode=yes" };
911
+ try {
912
+ await createGitClient(sshEnv).clone(repo.sshUrl, tempDir, cloneOptions);
913
+ } catch (sshError) {
914
+ const sshMessage = sshError instanceof Error ? sshError.message : String(sshError);
915
+ if (refCanBeSha && isMissingRefError(sshMessage)) {
916
+ await resetTempDir(tempDir);
917
+ await cloneAtSha(repo.sshUrl, ref, tempDir, sshEnv);
918
+ } else throw sshError;
919
+ }
872
920
  return tempDir;
873
921
  } catch {}
874
922
  }
@@ -1105,11 +1153,13 @@ const AGENT_PROJECT_SKILL_DIRS = [
1105
1153
  ".codex/skills",
1106
1154
  ".commandcode/skills",
1107
1155
  ".continue/skills",
1156
+ ".factory/skills",
1108
1157
  ".github/skills",
1109
1158
  ".goose/skills",
1110
1159
  ".grok/skills",
1111
1160
  ".iflow/skills",
1112
1161
  ".junie/skills",
1162
+ ".kilo/skills",
1113
1163
  ".kilocode/skills",
1114
1164
  ".kimchi/skills",
1115
1165
  ".kiro/skills",
@@ -1127,7 +1177,7 @@ const AGENT_PROJECT_SKILL_DIRS = [
1127
1177
  ".zcode/skills",
1128
1178
  ".zencoder/skills"
1129
1179
  ];
1130
- function normalizeSkillName(name) {
1180
+ function normalizeSkillName$1(name) {
1131
1181
  return name.toLowerCase().replace(/[\s_]+/g, "-");
1132
1182
  }
1133
1183
  function normalizeRelativePath(path) {
@@ -1207,7 +1257,7 @@ async function discoverSkills(basePath, subpath, options) {
1207
1257
  const seenNames = /* @__PURE__ */ new Set();
1208
1258
  const parsedSkillPaths = /* @__PURE__ */ new Set();
1209
1259
  const localLock = await readLocalLock(basePath);
1210
- const lockedSkillNames = new Set(Object.keys(localLock.skills).map(normalizeSkillName));
1260
+ const lockedSkillNames = new Set(Object.keys(localLock.skills).map(normalizeSkillName$1));
1211
1261
  if (subpath && !isSubpathSafe(basePath, subpath)) throw new Error(`Invalid subpath: "${subpath}" resolves outside the repository directory. Subpath must not contain ".." segments that escape the base path.`);
1212
1262
  const searchPath = subpath ? join(basePath, subpath) : basePath;
1213
1263
  const pluginGroupings = await getPluginGroupings(searchPath);
@@ -1220,8 +1270,8 @@ async function discoverSkills(basePath, subpath, options) {
1220
1270
  if (lockedSkillNames.size === 0) return false;
1221
1271
  const relativeDir = normalizeRelativePath(relative(basePath, skill.path));
1222
1272
  if (!AGENT_PROJECT_SKILL_DIRS.some((dir) => relativeDir === dir || relativeDir.startsWith(`${dir}/`))) return false;
1223
- const skillName = normalizeSkillName(skill.name);
1224
- const directoryName = normalizeSkillName(basename(skill.path));
1273
+ const skillName = normalizeSkillName$1(skill.name);
1274
+ const directoryName = normalizeSkillName$1(basename(skill.path));
1225
1275
  return lockedSkillNames.has(skillName) || lockedSkillNames.has(directoryName);
1226
1276
  };
1227
1277
  const parseSkillAt = async (skillDir) => {
@@ -1254,7 +1304,7 @@ async function discoverSkills(basePath, subpath, options) {
1254
1304
  const tryAddSkillAt = async (skillDir) => {
1255
1305
  if (!await hasSkillMd(skillDir)) return false;
1256
1306
  let skill = await parseSkillAt(skillDir);
1257
- if (!skill || seenNames.has(skill.name)) return true;
1307
+ if (!skill || !options?.includeDuplicateNames && seenNames.has(skill.name)) return true;
1258
1308
  if (isInstalledProjectSkill(skill)) return true;
1259
1309
  skill = enhanceSkill(skill);
1260
1310
  skills.push(skill);
@@ -1277,7 +1327,7 @@ async function discoverSkills(basePath, subpath, options) {
1277
1327
  const allSkillDirs = await findSkillDirs(searchPath);
1278
1328
  for (const skillDir of allSkillDirs) {
1279
1329
  let skill = await parseSkillAt(skillDir);
1280
- if (skill && !seenNames.has(skill.name) && !isInstalledProjectSkill(skill)) {
1330
+ if (skill && (options?.includeDuplicateNames || !seenNames.has(skill.name)) && !isInstalledProjectSkill(skill)) {
1281
1331
  skill = enhanceSkill(skill);
1282
1332
  skills.push(skill);
1283
1333
  seenNames.add(skill.name);
@@ -1305,6 +1355,7 @@ const vibeHome = process.env.VIBE_HOME?.trim() || join(home, ".vibe");
1305
1355
  const hermesHome = process.env.HERMES_HOME?.trim() || join(home, ".hermes");
1306
1356
  const autohandHome = process.env.AUTOHAND_HOME?.trim() || join(home, ".autohand");
1307
1357
  const grokHome = process.env.GROK_HOME?.trim() || join(home, ".grok");
1358
+ const sarvamHome = process.env.SARVAM_HOME?.trim() || join(home, ".sarvam");
1308
1359
  const zedAppDataHome = process.env.APPDATA?.trim();
1309
1360
  const zedFlatpakConfigHome = process.env.FLATPAK_XDG_CONFIG_HOME?.trim();
1310
1361
  function packageJsonHasDependency(packageJsonPath, dependencyName) {
@@ -1357,6 +1408,7 @@ const agents = {
1357
1408
  displayName: "Antigravity",
1358
1409
  skillsDir: ".agents/skills",
1359
1410
  globalSkillsDir: join(home, ".gemini/antigravity/skills"),
1411
+ showInUniversalPrompt: false,
1360
1412
  detectInstalled: async () => {
1361
1413
  return existsSync(join(home, ".gemini/antigravity"));
1362
1414
  }
@@ -1366,6 +1418,7 @@ const agents = {
1366
1418
  displayName: "Antigravity CLI",
1367
1419
  skillsDir: ".agents/skills",
1368
1420
  globalSkillsDir: join(home, ".gemini/antigravity-cli/skills"),
1421
+ showInUniversalPrompt: false,
1369
1422
  detectInstalled: async () => {
1370
1423
  return existsSync(join(home, ".gemini/antigravity-cli"));
1371
1424
  }
@@ -1528,6 +1581,7 @@ const agents = {
1528
1581
  displayName: "Deep Agents",
1529
1582
  skillsDir: ".agents/skills",
1530
1583
  globalSkillsDir: join(home, ".deepagents/agent/skills"),
1584
+ showInUniversalPrompt: false,
1531
1585
  detectInstalled: async () => {
1532
1586
  return existsSync(join(home, ".deepagents"));
1533
1587
  }
@@ -1554,7 +1608,7 @@ const agents = {
1554
1608
  droid: {
1555
1609
  name: "droid",
1556
1610
  displayName: "Droid",
1557
- skillsDir: ".factory/skills",
1611
+ skillsDir: ".agents/skills",
1558
1612
  globalSkillsDir: join(home, ".factory/skills"),
1559
1613
  detectInstalled: async () => {
1560
1614
  return existsSync(join(home, ".factory"));
@@ -1589,6 +1643,15 @@ const agents = {
1589
1643
  return existsSync(join(home, ".forge"));
1590
1644
  }
1591
1645
  },
1646
+ fx: {
1647
+ name: "fx",
1648
+ displayName: "fx",
1649
+ skillsDir: ".fx/skills",
1650
+ globalSkillsDir: join(home, ".fx/skills"),
1651
+ detectInstalled: async () => {
1652
+ return existsSync(join(home, ".fx"));
1653
+ }
1654
+ },
1592
1655
  "gemini-cli": {
1593
1656
  name: "gemini-cli",
1594
1657
  displayName: "Gemini CLI",
@@ -1673,10 +1736,10 @@ const agents = {
1673
1736
  kilo: {
1674
1737
  name: "kilo",
1675
1738
  displayName: "Kilo Code",
1676
- skillsDir: ".kilocode/skills",
1677
- globalSkillsDir: join(home, ".kilocode/skills"),
1739
+ skillsDir: ".agents/skills",
1740
+ globalSkillsDir: join(home, ".kilo/skills"),
1678
1741
  detectInstalled: async () => {
1679
- return existsSync(join(home, ".kilocode"));
1742
+ return existsSync(join(home, ".kilo")) || existsSync(join(home, ".kilocode"));
1680
1743
  }
1681
1744
  },
1682
1745
  kimchi: {
@@ -1888,6 +1951,16 @@ const agents = {
1888
1951
  return existsSync(join(home, ".roo"));
1889
1952
  }
1890
1953
  },
1954
+ "sarvam-code": {
1955
+ name: "sarvam-code",
1956
+ displayName: "Sarvam Code",
1957
+ skillsDir: ".agents/skills",
1958
+ globalSkillsDir: join(home, ".agents/skills"),
1959
+ showInUniversalPrompt: false,
1960
+ detectInstalled: async () => {
1961
+ return existsSync(sarvamHome);
1962
+ }
1963
+ },
1891
1964
  "tabnine-cli": {
1892
1965
  name: "tabnine-cli",
1893
1966
  displayName: "Tabnine CLI",
@@ -3837,11 +3910,13 @@ const PRIORITY_PREFIXES = [
3837
3910
  ".codex/skills/",
3838
3911
  ".commandcode/skills/",
3839
3912
  ".continue/skills/",
3913
+ ".factory/skills/",
3840
3914
  ".github/skills/",
3841
3915
  ".goose/skills/",
3842
3916
  ".grok/skills/",
3843
3917
  ".iflow/skills/",
3844
3918
  ".junie/skills/",
3919
+ ".kilo/skills/",
3845
3920
  ".kilocode/skills/",
3846
3921
  ".kimchi/skills/",
3847
3922
  ".kiro/skills/",
@@ -3943,6 +4018,35 @@ function computeSnapshotHash(files) {
3943
4018
  }
3944
4019
  return hash.digest("hex");
3945
4020
  }
4021
+ function getSkillFolderPath(skillMdPath) {
4022
+ const pathLower = skillMdPath.toLowerCase();
4023
+ if (pathLower.endsWith("/skill.md")) return skillMdPath.slice(0, -9);
4024
+ if (pathLower === "skill.md") return "";
4025
+ return skillMdPath.slice(0, -9);
4026
+ }
4027
+ const SNAPSHOT_EXCLUDED_FILES = /* @__PURE__ */ new Set(["metadata.json"]);
4028
+ const SNAPSHOT_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
4029
+ ".git",
4030
+ "__pycache__",
4031
+ "__pypackages__"
4032
+ ]);
4033
+ function isInstallableSnapshotPath(path) {
4034
+ const parts = path.split("/");
4035
+ const fileName = parts.at(-1);
4036
+ if (!fileName || SNAPSHOT_EXCLUDED_FILES.has(fileName)) return false;
4037
+ return parts.slice(0, -1).every((part) => !SNAPSHOT_EXCLUDED_DIRS.has(part));
4038
+ }
4039
+ function hasCompleteNestedSnapshot(tree, skillMdPath, files) {
4040
+ const folderPath = getSkillFolderPath(skillMdPath);
4041
+ if (!folderPath) return true;
4042
+ const folderPrefix = `${folderPath}/`;
4043
+ const snapshotPaths = new Set(files.map((file) => file.path));
4044
+ return tree.tree.every((entry) => {
4045
+ if (entry.type !== "blob" || !entry.path.startsWith(folderPrefix)) return true;
4046
+ const relativePath = entry.path.slice(folderPrefix.length);
4047
+ return !isInstallableSnapshotPath(relativePath) || snapshotPaths.has(relativePath);
4048
+ });
4049
+ }
3946
4050
  async function tryBlobInstall(ownerRepo, options = {}) {
3947
4051
  if (options.ref !== void 0) return null;
3948
4052
  const tree = await fetchRepoTree(ownerRepo, options.ref, options.getToken);
@@ -3999,10 +4103,10 @@ async function tryBlobInstall(ownerRepo, options = {}) {
3999
4103
  };
4000
4104
  }));
4001
4105
  if (!downloads.every((d) => d.download !== null)) return null;
4106
+ if (!downloads.every(({ skill, download }) => hasCompleteNestedSnapshot(tree, skill.mdPath, download.files))) return null;
4002
4107
  return {
4003
4108
  skills: downloads.map(({ skill, download }) => {
4004
- const mdPathLower = skill.mdPath.toLowerCase();
4005
- const files = (mdPathLower.endsWith("/skill.md") ? skill.mdPath.slice(0, -9) : mdPathLower === "skill.md" ? "" : skill.mdPath.slice(0, -9)) ? download.files : download.files.filter((file) => file.path.toLowerCase() === "skill.md");
4109
+ const files = getSkillFolderPath(skill.mdPath) ? download.files : download.files.filter((file) => file.path.toLowerCase() === "skill.md");
4006
4110
  return {
4007
4111
  name: skill.name,
4008
4112
  description: skill.description,
@@ -4017,7 +4121,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
4017
4121
  tree
4018
4122
  };
4019
4123
  }
4020
- var version$1 = "1.5.23";
4124
+ var version$1 = "1.5.25";
4021
4125
  const isCancelled$1 = (value) => typeof value === "symbol";
4022
4126
  const EVE_AGENT_LABEL = "eve agent";
4023
4127
  async function isSourcePrivate(source) {
@@ -4170,6 +4274,14 @@ function buildResultLines(results, targetAgents) {
4170
4274
  if (failedSymlinks.length > 0) lines.push(` ${import_picocolors.default.yellow("copied:")} ${formatList$1(failedSymlinks)}`);
4171
4275
  return lines;
4172
4276
  }
4277
+ function exitInstallationCancelled() {
4278
+ cancel("Installation cancelled");
4279
+ if (!process.stdin.isTTY) {
4280
+ console.error("Interactive prompt required but stdin is not a TTY. Nothing was installed. Use --agent <name> (or --agent '*') and -y to run non-interactively.");
4281
+ process.exit(1);
4282
+ }
4283
+ process.exit(0);
4284
+ }
4173
4285
  async function promptForAgents(message, choices) {
4174
4286
  let lastSelected;
4175
4287
  try {
@@ -4303,10 +4415,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4303
4415
  maxVisible: 20,
4304
4416
  selectAll: true
4305
4417
  });
4306
- if (isCancelled$1(selected)) {
4307
- cancel("Installation cancelled");
4308
- process.exit(0);
4309
- }
4418
+ if (isCancelled$1(selected)) exitInstallationCancelled();
4310
4419
  selectedSkills = selected;
4311
4420
  }
4312
4421
  let targetAgents;
@@ -4336,10 +4445,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4336
4445
  value: key,
4337
4446
  label: config.displayName
4338
4447
  })));
4339
- if (isCancel(selected)) {
4340
- cancel("Installation cancelled");
4341
- process.exit(0);
4342
- }
4448
+ if (isCancelled$1(selected)) exitInstallationCancelled();
4343
4449
  targetAgents = selected;
4344
4450
  }
4345
4451
  else if (installedAgents.length === 1 || options.yes) {
@@ -4350,10 +4456,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4350
4456
  } else log.info(`Installing to: ${installedAgents.map((a) => import_picocolors.default.cyan(agents[a].displayName)).join(", ")}`);
4351
4457
  } else {
4352
4458
  const selected = await selectAgentsInteractive({ global: options.global });
4353
- if (isCancel(selected)) {
4354
- cancel("Installation cancelled");
4355
- process.exit(0);
4356
- }
4459
+ if (isCancelled$1(selected)) exitInstallationCancelled();
4357
4460
  targetAgents = selected;
4358
4461
  }
4359
4462
  }
@@ -4372,10 +4475,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4372
4475
  hint: "Install in home directory (available across all projects)"
4373
4476
  }]
4374
4477
  });
4375
- if (isCancel(scope)) {
4376
- cancel("Installation cancelled");
4377
- process.exit(0);
4378
- }
4478
+ if (isCancel(scope)) exitInstallationCancelled();
4379
4479
  installGlobally = scope;
4380
4480
  }
4381
4481
  let installMode = options.copy ? "copy" : "symlink";
@@ -4393,10 +4493,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4393
4493
  hint: "Independent copies for each agent"
4394
4494
  }]
4395
4495
  });
4396
- if (isCancel(modeChoice)) {
4397
- cancel("Installation cancelled");
4398
- process.exit(0);
4399
- }
4496
+ if (isCancel(modeChoice)) exitInstallationCancelled();
4400
4497
  installMode = modeChoice;
4401
4498
  } else if (uniqueDirs.size <= 1) installMode = "copy";
4402
4499
  const cwd = process.cwd();
@@ -4426,10 +4523,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4426
4523
  note(summaryLines.join("\n"), "Installation Summary");
4427
4524
  if (!options.yes) {
4428
4525
  const confirmed = await confirm({ message: "Proceed with installation?" });
4429
- if (isCancel(confirmed) || !confirmed) {
4430
- cancel("Installation cancelled");
4431
- process.exit(0);
4432
- }
4526
+ if (isCancel(confirmed) || !confirmed) exitInstallationCancelled();
4433
4527
  }
4434
4528
  const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
4435
4529
  const wellKnownPrivacyPromise = isSourcePrivate(sourceIdentifier).catch(() => null);
@@ -4758,9 +4852,8 @@ async function runAdd(args, options = {}) {
4758
4852
  selectAll: true
4759
4853
  });
4760
4854
  if (isCancelled$1(selected)) {
4761
- cancel("Installation cancelled");
4762
4855
  await cleanup(tempDir);
4763
- process.exit(0);
4856
+ exitInstallationCancelled();
4764
4857
  }
4765
4858
  selectedSkills = selected;
4766
4859
  }
@@ -4791,19 +4884,17 @@ async function runAdd(args, options = {}) {
4791
4884
  initialValue: true
4792
4885
  });
4793
4886
  if (isCancel(useEve)) {
4794
- cancel("Installation cancelled");
4795
4887
  await cleanup(tempDir);
4796
- process.exit(0);
4888
+ exitInstallationCancelled();
4797
4889
  }
4798
4890
  if (useEve) {
4799
4891
  targetAgents = ["eve"];
4800
4892
  log.info(`Installing to: ${import_picocolors.default.cyan(EVE_AGENT_LABEL)}`);
4801
4893
  } else {
4802
4894
  const selected = await selectAgentsInteractive({ global: options.global });
4803
- if (isCancel(selected)) {
4804
- cancel("Installation cancelled");
4895
+ if (isCancelled$1(selected)) {
4805
4896
  await cleanup(tempDir);
4806
- process.exit(0);
4897
+ exitInstallationCancelled();
4807
4898
  }
4808
4899
  targetAgents = selected;
4809
4900
  }
@@ -4816,10 +4907,9 @@ async function runAdd(args, options = {}) {
4816
4907
  value: key,
4817
4908
  label: config.displayName
4818
4909
  })));
4819
- if (isCancel(selected)) {
4820
- cancel("Installation cancelled");
4910
+ if (isCancelled$1(selected)) {
4821
4911
  await cleanup(tempDir);
4822
- process.exit(0);
4912
+ exitInstallationCancelled();
4823
4913
  }
4824
4914
  targetAgents = selected;
4825
4915
  }
@@ -4831,10 +4921,9 @@ async function runAdd(args, options = {}) {
4831
4921
  } else log.info(`Installing to: ${installedAgents.map((a) => import_picocolors.default.cyan(agents[a].displayName)).join(", ")}`);
4832
4922
  } else {
4833
4923
  const selected = await selectAgentsInteractive({ global: options.global });
4834
- if (isCancel(selected)) {
4835
- cancel("Installation cancelled");
4924
+ if (isCancelled$1(selected)) {
4836
4925
  await cleanup(tempDir);
4837
- process.exit(0);
4926
+ exitInstallationCancelled();
4838
4927
  }
4839
4928
  targetAgents = selected;
4840
4929
  }
@@ -4860,9 +4949,8 @@ async function runAdd(args, options = {}) {
4860
4949
  required: true
4861
4950
  });
4862
4951
  if (isCancel(selectedSubagents)) {
4863
- cancel("Installation cancelled");
4864
4952
  await cleanup(tempDir);
4865
- process.exit(0);
4953
+ exitInstallationCancelled();
4866
4954
  }
4867
4955
  eveSubagentTargets = selectedSubagents.map((s) => s === "" ? void 0 : s);
4868
4956
  }
@@ -4884,9 +4972,8 @@ async function runAdd(args, options = {}) {
4884
4972
  }]
4885
4973
  });
4886
4974
  if (isCancel(scope)) {
4887
- cancel("Installation cancelled");
4888
4975
  await cleanup(tempDir);
4889
- process.exit(0);
4976
+ exitInstallationCancelled();
4890
4977
  }
4891
4978
  installGlobally = scope;
4892
4979
  }
@@ -4907,9 +4994,8 @@ async function runAdd(args, options = {}) {
4907
4994
  }]
4908
4995
  });
4909
4996
  if (isCancel(modeChoice)) {
4910
- cancel("Installation cancelled");
4911
4997
  await cleanup(tempDir);
4912
- process.exit(0);
4998
+ exitInstallationCancelled();
4913
4999
  }
4914
5000
  installMode = modeChoice;
4915
5001
  } else if (uniqueDirs.size <= 1 || allEve) installMode = "copy";
@@ -4979,9 +5065,8 @@ async function runAdd(args, options = {}) {
4979
5065
  if (!options.yes) {
4980
5066
  const confirmed = await confirm({ message: "Proceed with installation?" });
4981
5067
  if (isCancel(confirmed) || !confirmed) {
4982
- cancel("Installation cancelled");
4983
5068
  await cleanup(tempDir);
4984
- process.exit(0);
5069
+ exitInstallationCancelled();
4985
5070
  }
4986
5071
  }
4987
5072
  spinner$3.start("Installing skills…");
@@ -6158,7 +6243,11 @@ async function removeCommand(skillNames, options) {
6158
6243
  const scanDir = async (dir) => {
6159
6244
  try {
6160
6245
  const entries = await readdir(dir, { withFileTypes: true });
6161
- for (const entry of entries) if (entry.isDirectory()) skillNamesSet.add(entry.name);
6246
+ for (const entry of entries) {
6247
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
6248
+ if (!await hasSkillMd(join(dir, entry.name))) continue;
6249
+ skillNamesSet.add(entry.name);
6250
+ }
6162
6251
  } catch (err) {
6163
6252
  if (err instanceof Error && err.code !== "ENOENT") log.warn(`Could not scan directory ${dir}: ${err.message}`);
6164
6253
  }
@@ -6366,6 +6455,46 @@ function parseRemoveOptions(args) {
6366
6455
  options
6367
6456
  };
6368
6457
  }
6458
+ function normalizeSkillName(name) {
6459
+ return name.toLowerCase().replace(/[\s_]+/g, "-");
6460
+ }
6461
+ function normalizeSkillPath(path) {
6462
+ return path.replace(/\\/g, "/").replace(/\/+/g, "/");
6463
+ }
6464
+ function resolveSkillLocations(lockedSkillNames, lockSkills, discovered) {
6465
+ const discoveredPaths = new Set(discovered.map((skill) => normalizeSkillPath(skill.skillPath)));
6466
+ const pathsByName = /* @__PURE__ */ new Map();
6467
+ for (const skill of discovered) {
6468
+ const key = normalizeSkillName(skill.name);
6469
+ const paths = pathsByName.get(key) ?? /* @__PURE__ */ new Set();
6470
+ paths.add(normalizeSkillPath(skill.skillPath));
6471
+ pathsByName.set(key, paths);
6472
+ }
6473
+ const deletedSkills = [];
6474
+ const ambiguousSkills = [];
6475
+ const resolvedPaths = /* @__PURE__ */ new Map();
6476
+ for (const name of lockedSkillNames) {
6477
+ const lockedPath = lockSkills[name]?.skillPath;
6478
+ if (!lockedPath) continue;
6479
+ const normalizedLockedPath = normalizeSkillPath(lockedPath);
6480
+ const candidates = [...pathsByName.get(normalizeSkillName(name)) ?? []];
6481
+ if (candidates.length > 1) {
6482
+ ambiguousSkills.push(name);
6483
+ continue;
6484
+ }
6485
+ if (discoveredPaths.has(normalizedLockedPath)) {
6486
+ resolvedPaths.set(name, normalizedLockedPath);
6487
+ continue;
6488
+ }
6489
+ if (candidates.length === 1) resolvedPaths.set(name, candidates[0]);
6490
+ else deletedSkills.push(name);
6491
+ }
6492
+ return {
6493
+ deletedSkills,
6494
+ ambiguousSkills,
6495
+ resolvedPaths
6496
+ };
6497
+ }
6369
6498
  const __dirname$1 = dirname(fileURLToPath(import.meta.url));
6370
6499
  const RESET$1 = "\x1B[0m";
6371
6500
  const BOLD$1 = "\x1B[1m";
@@ -6512,14 +6641,15 @@ async function promptDeletions(source, deletedSkills, isGlobal, options) {
6512
6641
  });
6513
6642
  }
6514
6643
  }
6515
- async function checkAndPromptForDeletions(source, allLockedForSource, lockSkills, isGlobal, options, discoveredPaths) {
6516
- const deletedSkills = allLockedForSource.filter((name) => {
6517
- const entry = lockSkills[name];
6518
- if (!entry?.skillPath) return false;
6519
- return !discoveredPaths.includes(entry.skillPath);
6520
- });
6521
- await promptDeletions(source, deletedSkills, isGlobal, options);
6522
- return deletedSkills;
6644
+ async function checkAndPromptForDeletions(source, allLockedForSource, lockSkills, isGlobal, options, discovered) {
6645
+ const resolution = resolveSkillLocations(allLockedForSource, lockSkills, discovered);
6646
+ if (resolution.ambiguousSkills.length > 0) {
6647
+ console.log();
6648
+ console.log(`${DIM$1}Warning:${RESET$1} Multiple current paths match these skills from ${DIM$1}${source}${RESET$1}; skipping them rather than deleting or migrating the wrong skill:`);
6649
+ for (const name of resolution.ambiguousSkills) console.log(` ${DIM$1}•${RESET$1} ${sanitizeMetadata(name)}`);
6650
+ }
6651
+ await promptDeletions(source, resolution.deletedSkills, isGlobal, options);
6652
+ return resolution;
6523
6653
  }
6524
6654
  async function checkWellKnownForUpdates(baseUrl, items) {
6525
6655
  let indexResult;
@@ -6703,36 +6833,42 @@ async function updateGlobalSkills(options = {}) {
6703
6833
  const tree = await fetchRepoTree(source, firstEntry.ref, getGitHubToken);
6704
6834
  if (tree) {
6705
6835
  const discoveredPaths = tree.tree.filter((entry) => entry.type === "blob").map((entry) => entry.path);
6706
- const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source && entry.ref === firstEntry.ref).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
6707
- const deletedSkillSet = new Set(deletedSkills);
6708
- for (const { name: skillName, entry } of itemsForSource) {
6709
- if (deletedSkillSet.has(skillName)) continue;
6710
- const latestHash = getSkillFolderHashFromTree(tree, entry.skillPath);
6711
- if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
6712
- name: skillName,
6713
- source,
6714
- entry
6715
- });
6836
+ if (!Object.entries(lock.skills).filter(([_, entry]) => entry.source === source && entry.ref === firstEntry.ref).map(([name, _]) => name).some((name) => lock.skills[name]?.skillPath && !discoveredPaths.includes(lock.skills[name].skillPath))) {
6837
+ for (const { name: skillName, entry } of itemsForSource) {
6838
+ const latestHash = getSkillFolderHashFromTree(tree, entry.skillPath);
6839
+ if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
6840
+ name: skillName,
6841
+ source,
6842
+ entry
6843
+ });
6844
+ }
6845
+ continue;
6716
6846
  }
6717
- continue;
6718
- }
6719
- console.log(` ${DIM$1}GitHub API unavailable; checking via Git clone${RESET$1}`);
6847
+ console.log(` ${DIM$1}Skill paths changed; resolving via Git clone${RESET$1}`);
6848
+ } else console.log(` ${DIM$1}GitHub API unavailable; checking via Git clone${RESET$1}`);
6720
6849
  }
6721
6850
  tempDir = await cloneRepo(sourceUrl, firstEntry.ref);
6722
- const discoveredPaths = (await discoverSkills(tempDir, void 0, { fullDepth: true })).map((skill) => {
6723
- return join(relative(tempDir, skill.path), "SKILL.md").split(sep).join("/");
6724
- });
6725
- const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source && entry.ref === firstEntry.ref).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
6726
- const deletedSkillSet = new Set(deletedSkills);
6851
+ const discoveredLocations = (await discoverSkills(tempDir, void 0, {
6852
+ fullDepth: true,
6853
+ includeDuplicateNames: true
6854
+ })).map((skill) => ({
6855
+ name: skill.name,
6856
+ skillPath: join(relative(tempDir, skill.path), "SKILL.md").split(sep).join("/")
6857
+ }));
6858
+ const resolution = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source && entry.ref === firstEntry.ref).map(([name, _]) => name), lock.skills, true, options, discoveredLocations);
6859
+ const deletedSkillSet = new Set(resolution.deletedSkills);
6727
6860
  for (const { name: skillName, entry } of itemsForSource) {
6728
6861
  if (deletedSkillSet.has(skillName)) continue;
6729
- const skillPath = entry.skillPath;
6730
- if (!discoveredPaths.includes(skillPath)) continue;
6862
+ const skillPath = resolution.resolvedPaths.get(skillName);
6863
+ if (!skillPath) continue;
6731
6864
  const latestHash = isGitHubSource && /^[0-9a-f]{40}$/i.test(entry.skillFolderHash) ? await getGitTreeHash(tempDir, skillPath) : await computeSkillFolderHash(join(tempDir, dirname(skillPath)));
6732
- if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
6865
+ if (skillPath !== entry.skillPath || latestHash && latestHash !== entry.skillFolderHash) updates.push({
6733
6866
  name: skillName,
6734
6867
  source,
6735
- entry
6868
+ entry: {
6869
+ ...entry,
6870
+ skillPath
6871
+ }
6736
6872
  });
6737
6873
  }
6738
6874
  } catch (error) {
@@ -6910,6 +7046,7 @@ async function updateProjectSkills(options = {}) {
6910
7046
  const allLockedForSource = Object.entries(localLock.skills).filter(([_, entry]) => (entry.sourceUrl || entry.source) === source && entry.ref === ref).map(([name, _]) => name);
6911
7047
  let tempDir = null;
6912
7048
  let deletedSkills = [];
7049
+ let resolvedPaths = null;
6913
7050
  if (cloneSource === null) {
6914
7051
  failCount += skillsForSource.length;
6915
7052
  console.log(`${DIM$1}✗ Cannot update ${source}: skills-lock.json is missing sourceUrl for this generic Git source${RESET$1}`);
@@ -6917,27 +7054,43 @@ async function updateProjectSkills(options = {}) {
6917
7054
  }
6918
7055
  try {
6919
7056
  tempDir = await cloneRepo(cloneSource, ref);
6920
- const discoveredPaths = (await discoverSkills(tempDir, void 0, { fullDepth: true })).map((s) => {
6921
- return join(relative(tempDir, s.path), "SKILL.md").split(sep).join("/");
6922
- });
6923
- deletedSkills = await checkAndPromptForDeletions(source, allLockedForSource, localLock.skills, false, options, discoveredPaths);
7057
+ const discoveredLocations = (await discoverSkills(tempDir, void 0, {
7058
+ fullDepth: true,
7059
+ includeDuplicateNames: true
7060
+ })).map((skill) => ({
7061
+ name: skill.name,
7062
+ skillPath: join(relative(tempDir, skill.path), "SKILL.md").split(sep).join("/")
7063
+ }));
7064
+ const resolution = await checkAndPromptForDeletions(source, allLockedForSource, localLock.skills, false, options, discoveredLocations);
7065
+ deletedSkills = resolution.deletedSkills;
7066
+ resolvedPaths = resolution.resolvedPaths;
6924
7067
  } catch (error) {
6925
7068
  console.log(`${DIM$1}✗ Failed to check for deleted skills from ${source}${RESET$1}`);
6926
7069
  } finally {
6927
7070
  if (tempDir) await cleanupTempDir(tempDir);
6928
7071
  }
7072
+ if (resolvedPaths === null) {
7073
+ failCount += skillsForSource.length;
7074
+ continue;
7075
+ }
6929
7076
  const remainingSkills = skillsForSource.filter((s) => !deletedSkills.includes(s.name));
6930
7077
  for (const skill of remainingSkills) {
6931
7078
  const safeName = sanitizeMetadata(skill.name);
7079
+ const resolvedPath = resolvedPaths?.get(skill.name);
7080
+ if (resolvedPaths && !resolvedPath) continue;
7081
+ const entry = resolvedPath ? {
7082
+ ...skill.entry,
7083
+ skillPath: resolvedPath
7084
+ } : skill.entry;
6932
7085
  console.log(`${TEXT$1}Updating ${safeName}…${RESET$1}`);
6933
- const installUrl = buildLocalUpdateSource(skill.entry);
7086
+ const installUrl = buildLocalUpdateSource(entry);
6934
7087
  if (!installUrl) {
6935
7088
  failCount++;
6936
7089
  console.log(` ${DIM$1}✗ Cannot update ${safeName}: skills-lock.json is missing sourceUrl for this generic Git source${RESET$1}`);
6937
7090
  continue;
6938
7091
  }
6939
7092
  const subagentArgs = skill.entry.subagents?.length ? ["--subagent", ...skill.entry.subagents.map((s) => s === "" ? "root" : s)] : [];
6940
- const fullDepthArgs = shouldUseFullDepthForUpdate(skill.entry) ? ["--full-depth"] : [];
7093
+ const fullDepthArgs = shouldUseFullDepthForUpdate(entry) ? ["--full-depth"] : [];
6941
7094
  if (spawnSync(process.execPath, [
6942
7095
  cliEntry,
6943
7096
  "add",
@@ -6954,7 +7107,7 @@ async function updateProjectSkills(options = {}) {
6954
7107
  "pipe"
6955
7108
  ],
6956
7109
  encoding: "utf-8",
6957
- env: getUpdateChildEnv(skill.entry.sourceType),
7110
+ env: getUpdateChildEnv(entry.sourceType),
6958
7111
  shell: false
6959
7112
  }).status === 0) {
6960
7113
  successCount++;
@@ -7042,6 +7195,10 @@ const USE_AGENT_CONFIGS = {
7042
7195
  codex: {
7043
7196
  command: "codex",
7044
7197
  args: []
7198
+ },
7199
+ "sarvam-code": {
7200
+ command: "sarvam-code",
7201
+ args: []
7045
7202
  }
7046
7203
  };
7047
7204
  const SUPPORTED_USE_AGENTS = Object.keys(USE_AGENT_CONFIGS);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills",
3
- "version": "1.5.23",
3
+ "version": "1.5.25",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -64,6 +64,7 @@
64
64
  "eve",
65
65
  "firebender",
66
66
  "forgecode",
67
+ "fx",
67
68
  "gemini-cli",
68
69
  "github-copilot",
69
70
  "goose",
@@ -97,6 +98,7 @@
97
98
  "reasonix",
98
99
  "rovodev",
99
100
  "roo",
101
+ "sarvam-code",
100
102
  "tabnine-cli",
101
103
  "terramind",
102
104
  "tinycloud",